the following structure is a structure of registers with type timer_t
typedef struct { /*!< TIMER0 Structure */
uint32_t CFG; /*!< GPTM Configuration */
uint32_t TAMR; /*!< GPTM Timer A Mode */
uint32_t TBMR; /*!< GPTM Timer B Mode */
uint32_t CTL; /*!< GPTM Control */
uint32_t SYNC; /*!< GPTM Synchronize */
uint32_t RESERVED;
uint32_t IMR; /*!< GPTM Interrupt Mask */
uint32_t RIS; /*!< GPTM Raw Interrupt Status */
uint32_t MIS; /*!< GPTM Masked Interrupt Status */
uint32_t ICR; /*!< GPTM Interrupt Clear */
uint32_t TAILR; /*!< GPTM Timer A Interval Load */
uint32_t TBILR; /*!< GPTM Timer B Interval Load */
uint32_t TBMATCHR; /*!< GPTM Timer B Match */
uint32_t TAPR; /*!< GPTM Timer A Prescale */
uint32_t TBPR; /*!< GPTM Timer B Prescale */
uint32_t TAPMR; /*!< GPTM TimerA Prescale Match */
uint32_t TBPMR; /*!< GPTM TimerB Prescale Match */
uint32_t TAR; /*!< GPTM Timer A */
uint32_t TBR; /*!< GPTM Timer B */
uint32_t TAV; /*!< GPTM Timer A Value */
uint32_t TBV; /*!< GPTM Timer B Value */
uint32_t RTCPD; /*!< GPTM RTC Predivide */
uint32_t TAPS; /*!< GPTM Timer A Prescale Snapshot */
uint32_t TBPS; /*!< GPTM Timer B Prescale Snapshot */
uint32_t TAPV; /*!< GPTM Timer A Prescale Value */
uint32_t TBPV; /*!< GPTM Timer B Prescale Value */
uint32_t RESERVED1[981];
uint32_t PP; /*!< GPTM Peripheral Properties */
}timer_t;
and this #define TIMER0 ((timer_t *) TIMER0_BASE) is used to dereference them so I can write on them as TIMER0_BASE is the base address of the used module. now I want to declare another bit field with type timerAModeBitField_t;for every register something to be like this
typedef struct timerAModeBitField{
uint32_t timerAMode : 2;
uint32_t timerACaptMode : 1;
uint32_t timeraPWMMode : 1;
uint32_t timerACountDir : 1;
uint32_t timerAMatchIntEn : 1;
uint32_t timerAWaitOnTrig : 1;
uint32_t timerASnapShotMode : 1;
uint32_t timerAIntervalLoad : 1;
}timerAModeBitField_t;
so I can directly write to their specific bits is it possible to implement something like that so I can first dereference the register and then dereference the bit field TIMER0->TAMR->timerAmode = 0x04
In portable C, the layout of bit-fields is unspecified. So you can't use bit-fields to describe a specific layout in portable C.
However, if you're writing code for a specific architecture, your C implementation may have additional guarantees. On Arm platforms, in particular, the Arm Architecture Procedure Call Standard (AAPCS) applies. It has a rule for bit-fields, and specifically for bit-fields in C and C++. If the bit-field description were provided by your vendor and the peripheral was intended to be used on an Arm processor, the AAPCS guarantees that you're both making the same assumptions on the data layout, so your code will use the intended data layout.
However using the same data layout is not sufficient! You also have to ensure that the compiler will do the right thing when a field is written in one way (say, through the uint32_t) and then read back in the other way (say, through the bit-field). C compilers are generally allowed to assume that when memory is accessed through different types, it's different memory. This allows compilers to optimize code better.
There are several exceptions which I won't go into here, but in general you can't assume that writing memory in one way and reading it back in some other way will give consistent results. Accessing the same memory in different ways is called aliasing. What is the strict aliasing rule? has a very good explanation of how this applies to C. Be careful: violating the aliasing rules can often result in code that fails only at certain levels of optimization, and only in certain contexts (e.g. depending on the number of registers that the compiler had available for a particular block of code), so this can be hard to debug. In particular, beware that casts can often get around compiler warnings, but not against aliasing bugs: casts just tell the compiler to stop complaining about broken code, they don't make the code less broken.
One way to let the compiler know that you're accessing the same memory through different types is to put those types in a union. (This is guaranteed in C11, and not officially guaranteed but widely implemented in C99.) That is:
typedef union {
uint32_t value;
timerAModeBitField bits;
} timerAModeUnion;
typedef struct {
…
timerAModeUnion TAMR;
…
} timer_t;
Then you can reliably do things like bulk-write TIMER0->TAMR.value and read a specific bit-field via TIMER0->TAMR.bits.timerAmode.
You can use the (inner) structure as the data type of an element of another (outer) structure, like this:
typedef struct timerAModeBitField {
uint32_t timerAMode : 2;
uint32_t timerACaptMode : 1;
uint32_t timeraPWMMode : 1;
uint32_t timerACountDir : 1;
uint32_t timerAMatchIntEn : 1;
uint32_t timerAWaitOnTrig : 1;
uint32_t timerASnapShotMode : 1;
uint32_t timerAIntervalLoad : 1;
uint32_t padding : 23;
} timerAModeBitField_t;
typedef struct { /*!< TIMER0 Structure */
uint32_t CFG; /*!< GPTM Configuration */
timerAModeBitField_t TAMR; /*!< GPTM Timer A Mode */
uint32_t TBMR; /*!< GPTM Timer B Mode */
uint32_t CTL; /*!< GPTM Control */
uint32_t SYNC; /*!< GPTM Synchronize */
uint32_t RESERVED;
uint32_t IMR; /*!< GPTM Interrupt Mask */
uint32_t RIS; /*!< GPTM Raw Interrupt Status */
uint32_t MIS; /*!< GPTM Masked Interrupt Status */
uint32_t ICR; /*!< GPTM Interrupt Clear */
uint32_t TAILR; /*!< GPTM Timer A Interval Load */
uint32_t TBILR; /*!< GPTM Timer B Interval Load */
uint32_t TBMATCHR; /*!< GPTM Timer B Match */
uint32_t TAPR; /*!< GPTM Timer A Prescale */
uint32_t TBPR; /*!< GPTM Timer B Prescale */
uint32_t TAPMR; /*!< GPTM TimerA Prescale Match */
uint32_t TBPMR; /*!< GPTM TimerB Prescale Match */
uint32_t TAR; /*!< GPTM Timer A */
uint32_t TBR; /*!< GPTM Timer B */
uint32_t TAV; /*!< GPTM Timer A Value */
uint32_t TBV; /*!< GPTM Timer B Value */
uint32_t RTCPD; /*!< GPTM RTC Predivide */
uint32_t TAPS; /*!< GPTM Timer A Prescale Snapshot */
uint32_t TBPS; /*!< GPTM Timer B Prescale Snapshot */
uint32_t TAPV; /*!< GPTM Timer A Prescale Value */
uint32_t TBPV; /*!< GPTM Timer B Prescale Value */
uint32_t RESERVED1[981];
uint32_t PP; /*!< GPTM Peripheral Properties */
} timer_t;
Then you can access mode bits in this way:
TIMER0->TAMR.timerAmode = 2;
Note: You should not assign 0x04 to a bitfield of width 2, the width of the value is too big. Possible values range from 0 to 3.
EDIT:
Let's minize the example:
typedef union {
struct {
unsigned int mode: 2;
unsigned int padding: 30;
};
uint32_t value;
} control_t;
typedef struct {
control_t control;
uint32_t stuff;
} module_t;
You can always obtain the address of an element of a structure:
module_t module;
control_t* mode_p = &module.control;
mode_p->mode = 3; // write only to 2 bits
uint32_t v = mode_p->value; // read all 32 bits
Via the union you can use either the complete control register or conveniently access just some bits:
module.control.value = 0x12345678;
module.control.mode = 2;
Don't fall into the trap to think too much from a machine code perspective. Use the abstraction of the higher level language to gain better source control by the compiler. Avoid casts as much as possible, since they tell the compiler to think, "The programmer is always right, who am I to doubt her?" Even in cases where your code has errors.
Caution: In fact it is not defined by the C standard how exactly bitfields are allocated. However, compilers do this in a reproducable way, it might well be documented. Make sure you guard your code with a check on the compiler you validate for your usage.
You could try this approach using unnamed members:
#include <stdint.h>
typedef struct {
...
union {
struct {
uint32_t timerAMode : 2;
uint32_t timerACaptMode : 1;
uint32_t timeraPWMMode : 1;
uint32_t timerACountDir : 1;
uint32_t timerAMatchIntEn : 1;
uint32_t timerAWaitOnTrig : 1;
uint32_t timerASnapShotMode : 1;
uint32_t timerAIntervalLoad : 1;
};
uint32_t TAMR;
};
...
} timer_t;
#define TIMER0_BASE (0x40001000)
volatile timer_t *TIMER0 = (timer_t*) TIMER0_BASE;
int main(void)
{
TIMER0->TAMR = 0x1234;
TIMER0->timerAMode = 2;
}
By using unnamend members you can access members of the union/struct directly without adding a field name for that union.
This allows accessing TAMR at once as well as the separate fields of it.
The standard technique to enforce atomic access to volatile variables shared with ISRs, via "atomic access guards" or "interrupt guards", in particular when running a bare metal, single-threaded cooperative multi-tasking application with no operating system, is as follows:
// 1. save interrupt state
// 2. disable only the interrupts necessary
// You get atomic access to volatile variables shared with ISRs here,
// since ISRs are the only other "context" or running "thread" which
// might attempt to modify a shared memory block or variable.
// 3. restore interrupt state
See also where I describe this in detail here, including best practices (keep interrupts off for short period of time) and how to do atomic reads withOUT disabling interrupts first, via my doAtomicRead() repeat-read-loop function: Reading a 64 bit variable that is updated by an ISR.
I have previously documented how to do this for AVR microcontrollers/Arduino: How do I force atomicity in Atmel AVR mcus/Arduino?.
But, how do I do this for STM32 microcontrollers? I know there are a lot of ways.
Please cover the following techniques:
Via ARM-core CMSIS:
for global interrupts
for specific IRQs (Interrupt Requests)
Via STM32 HAL (Hardware Abstraction Layer)
Via FreeRTOS
This answer is related, but insufficient: How can I re-enable the stm32f103's external interrupt after I disable it?
Multiple ways to enable/disable interrupts in STM32 mcus:
1. Via ARM-core CMSIS:
1.A. For global interrupts
__enable_irq() // enable all interrupts
__disable_irq() // disable all interrupts
// Returns the current state of the priority mask bit from the Priority Mask
// Register. [0 if global interrupts are **enabled** and non-zero if they
// are **disabled**]
__get_PRIMASK()
For the definition of these functions, see:
https://github.com/ARM-software/CMSIS/blob/master/CMSIS/Include/cmsis_gcc.h
Contains at least:
__enable_irq()
__disable_irq()
__get_PRIMASK()
__set_PRIMASK()
STM32 example location: "stm/stm32f2xx/st_hal_v1.1.3/CMSIS/Include/cmsis_gcc.h":
To save and restore the interrupt state, use __get_PRIMASK(), like this:
// 1. back up interrupt state; `__get_PRIMASK()` returns 0 if interrupts
// are **enabled**, and non-zero if they are **disabled**.
bool interrupts_enabled = (__get_PRIMASK() == 0);
// do stuff
// 2. Disable interrupts
__disable_irq();
// 3. Restore backed-up-state
if (interrupts_enabled) {
__enable_irq();
}
When dealing with global interrupts, this is the best way for bare-metal, non-FreeRTOS code!
I think this technique is also cross-compatible with ALL ARM-core mcus, not just STM32.
I first learned this technique from Tilen Majerle, here: https://stm32f4-discovery.net/2015/06/how-to-properly-enabledisable-interrupts-in-arm-cortex-m/. His work and contributions to clear up this super-obfuscated stuff are infinitely valuable and appreciated!
His example:
void ImportantFunction1(void) {
/* Important function 1 */
uint32_t prim;
/* Do some stuff here which can be interrupted */
/* Read PRIMASK register, check interrupt status before you disable them */
/* Returns 0 if they are enabled, or non-zero if disabled */
prim = __get_PRIMASK();
/* Disable interrupts */
__disable_irq();
/* Do some stuff here which can not be interrupted */
/* Call subfunction */
ImportantFunction2();
/* Do some stuff here which can not be interrupted */
/* This part is still interrupt safe because ImportantFunction2 will not enable interrupts */
/* Enable interrupts back */
if (!prim) {
__enable_irq();
}
/* Do some stuff here which can be interrupted */
}
1.B. For specific IRQs (Interrupt Requests)
It is best to avoid disabling global interrupts, if possible, and disable only the fewest number of specific interrupts possible to achieve atomicity for your specific code. So, using these functions allows you to enable or disable only the specific interrupts you need to!
Enable or disable specific types of interrupts:
void NVIC_EnableIRQ(IRQn_Type IRQn);
void NVIC_DisableIRQ(IRQn_Type IRQn);
NVIC stands for "Nested Vector Interrupt Controller". Nested interrupts (meaning: a higher-priority interrupt can still fire within an ISR) are enabled by default on STM32 microcontrollers. Each interrupt type has a priority assigned to it, with lower priority numbers being higher priority, and higher-priority interrupts are able to fire while an ISR is being processed for a lower-priority interrupt. See here for a little more information on the STM32 NVIC: https://stm32f4-discovery.net/2014/05/stm32f4-stm32f429-nvic-or-nested-vector-interrupt-controller/.
Contrast this to AVR microcontrollers (ex: ATMega328 / Arduino Uno), which do not have priority-based interrupts, so by default, when any ISR is being processed, all interrupts (ie: global interrupts) are automatically disabled as the program enters the ISR. Note that even on AVR mcus, however, you can still manually enable nested interrupts / ISRs if you like by manually re-enabling global interrupts inside your ISR, via a call to interrupts() on Arduino or sei() (set interrupts) on raw AVR.
Each ARM-core microcontroller manufacturer, I believe, including STM32 types, must define and create its own list of IRQn_Type interrupt request types, so see below for the STM32 details on their specific interrupt types defined for each mcu.
2. Via STM32 HAL (Hardware Abstraction Layer) libraries
Enable or disable specific types of interrupts:
// enable interrupts
HAL_NVIC_EnableIRQ(IRQn_Type IRQn);
// disable interrupts
HAL_NVIC_DisableIRQ(IRQn_Type IRQn);
See, for example: "stm/stm32f2xx/st_hal_v1.1.3/STM32F2xx_HAL_Driver/Src/stm32f2xx_hal_cortex.c/.h" - definitions for those functions above are in those files. See them online:
https://github.com/STMicroelectronics/STM32CubeF2/blob/master/Drivers/STM32F2xx_HAL_Driver/Inc/stm32f2xx_hal_cortex.h#L264-L265
https://github.com/STMicroelectronics/STM32CubeF2/blob/master/Drivers/STM32F2xx_HAL_Driver/Src/stm32f2xx_hal_cortex.c#L178-L210
Here are the definitions of HAL_NVIC_EnableIRQ() and HAL_NVIC_DisableIRQ(). Notice that they just check to ensure your IRQn is valid, then they pass the input argument on to the ARM-core CMSIS NVIC_EnableIRQ() and NVIC_DisableIRQ() functions above!:
/**
* #brief Enables a device specific interrupt in the NVIC interrupt controller.
* #note To configure interrupts priority correctly, the NVIC_PriorityGroupConfig()
* function should be called before.
* #param IRQn External interrupt number.
* This parameter can be an enumerator of IRQn_Type enumeration
* (For the complete STM32 Devices IRQ Channels list, please refer to the appropriate CMSIS device file (stm32f2xxxx.h))
* #retval None
*/
void HAL_NVIC_EnableIRQ(IRQn_Type IRQn)
{
/* Check the parameters */
assert_param(IS_NVIC_DEVICE_IRQ(IRQn));
/* Enable interrupt */
NVIC_EnableIRQ(IRQn);
}
/**
* #brief Disables a device specific interrupt in the NVIC interrupt controller.
* #param IRQn External interrupt number.
* This parameter can be an enumerator of IRQn_Type enumeration
* (For the complete STM32 Devices IRQ Channels list, please refer to the appropriate CMSIS device file (stm32f2xxxx.h))
* #retval None
*/
void HAL_NVIC_DisableIRQ(IRQn_Type IRQn)
{
/* Check the parameters */
assert_param(IS_NVIC_DEVICE_IRQ(IRQn));
/* Disable interrupt */
NVIC_DisableIRQ(IRQn);
}
For IRQn_Types: see the appropriate definition file for your specific board! These are board-specific definitions, for your board from your manufacturer. Here are all of the boards in the STM32 F2xx line, for instance: https://github.com/STMicroelectronics/STM32CubeF2/tree/master/Drivers/CMSIS/Device/ST/STM32F2xx/Include. Let's look at the stm32f217xx.h file specifically:
https://github.com/STMicroelectronics/STM32CubeF2/blob/master/Drivers/CMSIS/Device/ST/STM32F2xx/Include/stm32f217xx.h
Raw view (since file is too big to view on GitHub otherwise): https://raw.githubusercontent.com/STMicroelectronics/STM32CubeF2/master/Drivers/CMSIS/Device/ST/STM32F2xx/Include/stm32f217xx.h
From this file, we can see the typedef enum definition for the IRQn_Type, which is the "STM32F2XX Interrupt Number Definition". Here is what it looks like:
/**
* #brief STM32F2XX Interrupt Number Definition, according to the selected device
* in #ref Library_configuration_section
*/
typedef enum
{
/****** Cortex-M3 Processor Exceptions Numbers ****************************************************************/
NonMaskableInt_IRQn = -14, /*!< 2 Non Maskable Interrupt */
HardFault_IRQn = -13, /*!< 3 Hard Fault Interrupt */
MemoryManagement_IRQn = -12, /*!< 4 Cortex-M3 Memory Management Interrupt */
BusFault_IRQn = -11, /*!< 5 Cortex-M3 Bus Fault Interrupt */
UsageFault_IRQn = -10, /*!< 6 Cortex-M3 Usage Fault Interrupt */
SVCall_IRQn = -5, /*!< 11 Cortex-M3 SV Call Interrupt */
DebugMonitor_IRQn = -4, /*!< 12 Cortex-M3 Debug Monitor Interrupt */
PendSV_IRQn = -2, /*!< 14 Cortex-M3 Pend SV Interrupt */
SysTick_IRQn = -1, /*!< 15 Cortex-M3 System Tick Interrupt */
/****** STM32 specific Interrupt Numbers **********************************************************************/
WWDG_IRQn = 0, /*!< Window WatchDog Interrupt */
PVD_IRQn = 1, /*!< PVD through EXTI Line detection Interrupt */
TAMP_STAMP_IRQn = 2, /*!< Tamper and TimeStamp interrupts through the EXTI line */
RTC_WKUP_IRQn = 3, /*!< RTC Wakeup interrupt through the EXTI line */
FLASH_IRQn = 4, /*!< FLASH global Interrupt */
RCC_IRQn = 5, /*!< RCC global Interrupt */
EXTI0_IRQn = 6, /*!< EXTI Line0 Interrupt */
EXTI1_IRQn = 7, /*!< EXTI Line1 Interrupt */
EXTI2_IRQn = 8, /*!< EXTI Line2 Interrupt */
EXTI3_IRQn = 9, /*!< EXTI Line3 Interrupt */
EXTI4_IRQn = 10, /*!< EXTI Line4 Interrupt */
DMA1_Stream0_IRQn = 11, /*!< DMA1 Stream 0 global Interrupt */
DMA1_Stream1_IRQn = 12, /*!< DMA1 Stream 1 global Interrupt */
DMA1_Stream2_IRQn = 13, /*!< DMA1 Stream 2 global Interrupt */
DMA1_Stream3_IRQn = 14, /*!< DMA1 Stream 3 global Interrupt */
DMA1_Stream4_IRQn = 15, /*!< DMA1 Stream 4 global Interrupt */
DMA1_Stream5_IRQn = 16, /*!< DMA1 Stream 5 global Interrupt */
DMA1_Stream6_IRQn = 17, /*!< DMA1 Stream 6 global Interrupt */
ADC_IRQn = 18, /*!< ADC1, ADC2 and ADC3 global Interrupts */
CAN1_TX_IRQn = 19, /*!< CAN1 TX Interrupt */
CAN1_RX0_IRQn = 20, /*!< CAN1 RX0 Interrupt */
CAN1_RX1_IRQn = 21, /*!< CAN1 RX1 Interrupt */
CAN1_SCE_IRQn = 22, /*!< CAN1 SCE Interrupt */
EXTI9_5_IRQn = 23, /*!< External Line[9:5] Interrupts */
TIM1_BRK_TIM9_IRQn = 24, /*!< TIM1 Break interrupt and TIM9 global interrupt */
TIM1_UP_TIM10_IRQn = 25, /*!< TIM1 Update Interrupt and TIM10 global interrupt */
TIM1_TRG_COM_TIM11_IRQn = 26, /*!< TIM1 Trigger and Commutation Interrupt and TIM11 global interrupt */
TIM1_CC_IRQn = 27, /*!< TIM1 Capture Compare Interrupt */
TIM2_IRQn = 28, /*!< TIM2 global Interrupt */
TIM3_IRQn = 29, /*!< TIM3 global Interrupt */
TIM4_IRQn = 30, /*!< TIM4 global Interrupt */
I2C1_EV_IRQn = 31, /*!< I2C1 Event Interrupt */
I2C1_ER_IRQn = 32, /*!< I2C1 Error Interrupt */
I2C2_EV_IRQn = 33, /*!< I2C2 Event Interrupt */
I2C2_ER_IRQn = 34, /*!< I2C2 Error Interrupt */
SPI1_IRQn = 35, /*!< SPI1 global Interrupt */
SPI2_IRQn = 36, /*!< SPI2 global Interrupt */
USART1_IRQn = 37, /*!< USART1 global Interrupt */
USART2_IRQn = 38, /*!< USART2 global Interrupt */
USART3_IRQn = 39, /*!< USART3 global Interrupt */
EXTI15_10_IRQn = 40, /*!< External Line[15:10] Interrupts */
RTC_Alarm_IRQn = 41, /*!< RTC Alarm (A and B) through EXTI Line Interrupt */
OTG_FS_WKUP_IRQn = 42, /*!< USB OTG FS Wakeup through EXTI line interrupt */
TIM8_BRK_TIM12_IRQn = 43, /*!< TIM8 Break Interrupt and TIM12 global interrupt */
TIM8_UP_TIM13_IRQn = 44, /*!< TIM8 Update Interrupt and TIM13 global interrupt */
TIM8_TRG_COM_TIM14_IRQn = 45, /*!< TIM8 Trigger and Commutation Interrupt and TIM14 global interrupt */
TIM8_CC_IRQn = 46, /*!< TIM8 Capture Compare Interrupt */
DMA1_Stream7_IRQn = 47, /*!< DMA1 Stream7 Interrupt */
FSMC_IRQn = 48, /*!< FSMC global Interrupt */
SDIO_IRQn = 49, /*!< SDIO global Interrupt */
TIM5_IRQn = 50, /*!< TIM5 global Interrupt */
SPI3_IRQn = 51, /*!< SPI3 global Interrupt */
UART4_IRQn = 52, /*!< UART4 global Interrupt */
UART5_IRQn = 53, /*!< UART5 global Interrupt */
TIM6_DAC_IRQn = 54, /*!< TIM6 global and DAC1&2 underrun error interrupts */
TIM7_IRQn = 55, /*!< TIM7 global interrupt */
DMA2_Stream0_IRQn = 56, /*!< DMA2 Stream 0 global Interrupt */
DMA2_Stream1_IRQn = 57, /*!< DMA2 Stream 1 global Interrupt */
DMA2_Stream2_IRQn = 58, /*!< DMA2 Stream 2 global Interrupt */
DMA2_Stream3_IRQn = 59, /*!< DMA2 Stream 3 global Interrupt */
DMA2_Stream4_IRQn = 60, /*!< DMA2 Stream 4 global Interrupt */
ETH_IRQn = 61, /*!< Ethernet global Interrupt */
ETH_WKUP_IRQn = 62, /*!< Ethernet Wakeup through EXTI line Interrupt */
CAN2_TX_IRQn = 63, /*!< CAN2 TX Interrupt */
CAN2_RX0_IRQn = 64, /*!< CAN2 RX0 Interrupt */
CAN2_RX1_IRQn = 65, /*!< CAN2 RX1 Interrupt */
CAN2_SCE_IRQn = 66, /*!< CAN2 SCE Interrupt */
OTG_FS_IRQn = 67, /*!< USB OTG FS global Interrupt */
DMA2_Stream5_IRQn = 68, /*!< DMA2 Stream 5 global interrupt */
DMA2_Stream6_IRQn = 69, /*!< DMA2 Stream 6 global interrupt */
DMA2_Stream7_IRQn = 70, /*!< DMA2 Stream 7 global interrupt */
USART6_IRQn = 71, /*!< USART6 global interrupt */
I2C3_EV_IRQn = 72, /*!< I2C3 event interrupt */
I2C3_ER_IRQn = 73, /*!< I2C3 error interrupt */
OTG_HS_EP1_OUT_IRQn = 74, /*!< USB OTG HS End Point 1 Out global interrupt */
OTG_HS_EP1_IN_IRQn = 75, /*!< USB OTG HS End Point 1 In global interrupt */
OTG_HS_WKUP_IRQn = 76, /*!< USB OTG HS Wakeup through EXTI interrupt */
OTG_HS_IRQn = 77, /*!< USB OTG HS global interrupt */
DCMI_IRQn = 78, /*!< DCMI global interrupt */
CRYP_IRQn = 79, /*!< CRYP crypto global interrupt */
HASH_RNG_IRQn = 80 /*!< Hash and Rng global interrupt */
} IRQn_Type;
2.A. Example usage using STM32 HAL:
To get exclusive access (to ensure strings are atomically printed, for instance) to the USART1 for printing debug chars via a HAL-based blocking (polled) mode (ie: via HAL_UART_Transmit()), you need to disable all interrupts for USART1_IRQn by doing the following. (This guarantees you get atomic access to this device):
// 1. Disable the UART IRQ
HAL_NVIC_DisableIRQ(USART1_IRQn);
// 2. Send your string (in HAL blocking/polled mode)
// Prototype for this function is from
// "...stm/stm32f7xx/st_hal_v1.1.2/STM32F7xx_HAL_Driver/Src/stm32f7xx_hal_uart.c":
// - `HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart,
// uint8_t *pData, uint16_t Size, uint32_t Timeout)`
// Note: `huart` is defined by STM32CubeMX as `UART_HandleTypeDef huart1;`
// in "hal_source/Src/usart.c"
HAL_UART_Transmit(&huart1, (uint8_t *)my_str, strlen(my_str), HAL_MAX_DELAY);
// 3. Enable the UART_IRQ
// FUTURE WORK: make this nestable/more robust by only enabling the
// IRQ here if it was previously enabled before disabling it!
HAL_NVIC_EnableIRQ(USART1_IRQn);
3. Via FreeRTOS:
The FreeRTOS atomic-access-guard / interrupt-related functions are listed under the "Modules" section of the Kernel Control API here: Kernel Control:
taskYIELD()
taskENTER_CRITICAL() // interrupts off
taskEXIT_CRITICAL() // restore interrupts
taskENTER_CRITICAL_FROM_ISR() // interrupts off
taskEXIT_CRITICAL_FROM_ISR() // restore interrupts
taskDISABLE_INTERRUPTS() // interrupts off
taskENABLE_INTERRUPTS() // interrupts on
3.A. Higher-level macros:
These are the preferred macros to use, and are the freertos-recommended ones!
These all support nested calls, and end up calling portDISABLE_INTERRUPTS() anyway, which is the port implementation of the lower-level taskDISABLE_INTERRUPTS(), shown below.
From: https://www.freertos.org/taskENTER_CRITICAL_taskEXIT_CRITICAL.html
taskENTER_CRITICAL() // interrupts off
taskEXIT_CRITICAL() // restore interrupts
From: https://www.freertos.org/taskENTER_CRITICAL_FROM_ISR_taskEXIT_CRITICAL_FROM_ISR.html
taskENTER_CRITICAL_FROM_ISR()
taskEXIT_CRITICAL_FROM_ISR()
3.B. Lower-level macros:
These do NOT support nested calls!
Official documentation on them is on the main "Kernel Control" page:
taskDISABLE_INTERRUPTS()
taskENABLE_INTERRUPTS()
Notes and limiatations:
taskDISABLE_INTERRUPTS() at the link above states:
Normally this macro would not be called directly and taskENTER_CRITICAL() and taskEXIT_CRITICAL() should be used in its place.
taskENABLE_INTERRUPTS() at the link above states:
Normally this macro would not be called directly and taskENTER_CRITICAL() and taskEXIT_CRITICAL() should be used in its place.
Note also that the use of taskDISABLE_INTERRUPTS() is demonstrated as the technique used to panic inside an example macro definition for configASSERT().
From here: https://www.freertos.org/a00110.html#configASSERT, when used with a debugger, it is defined as:
/* Define configASSERT() to disable interrupts and sit in a loop. */
#define configASSERT( ( x ) ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); }
My thoughts: perhaps in this one case (ie: hard asserting, or panicking), taskDISABLE_INTERRUPTS() might be preferred over taskENTER_CRITICAL() because no amount of calling taskEXIT_CRITICAL() from another thread will re-enable interrupts once taskDISABLE_INTERRUPTS() has been called [I think!?]--rather, one would have to explicitly (and accidentally) call taskENABLE_INTERRUPTS() (ex: from another thread) to re-enable interrupts once taskDISABLE_INTERRUPTS() has been called. In other words, using the low-level taskDISABLE_INTERRUPTS() call is appropriate here because it will truly cause the system to sit in a loop, as desired, whereas taskENTER_CRITICAL() would not.
3.C. Mutexes and other OS (Operating System)-enabled synchronization primitives
Beyond the examples above, you can also use FreeRTOS queues (which are thread-safe, unlike all containers in the C++ std library), mutexes, semaphores, task notifications, and other synchronization primitives, where able and where appropriate, to protect certain data which is shared between FreeRTOS tasks (threads), assuming you are running FreeRTOS.
See the list of these tools here: https://www.freertos.org/a00106.html, and in the left-hand navigation menus once you click on that link.
4. TODO: mutex primitives: raw, OS-free spin locks via atomic set_and_test() (read, modify, write) instructions
Add an atomic test_and_set() (set_and_test() or read_modify_write() really makes more sense as a function name for this, I think) demo using ARM-core CMSIS functions, or assembly, or whatever means necessary, to demonstrate writing a spin lock in STM32. I don't know how to do this yet so it will require finding the right function or operation to use. See here: https://en.wikipedia.org/wiki/Test-and-set#Pseudo-C_implementation_of_a_spin_lock:
volatile int lock = 0;
void critical() {
// Spin lock: loop forever until we get the lock; we know the lock was
// successfully obtained after exiting this while loop because the
// test_and_set() function locks the lock and returns the previous lock
// value. If the previous lock value was 1 then the lock was **already**
// locked by another thread or process. Once the previous lock value
// was 0, however, then it indicates the lock was **not** locked before we
// locked it, but now it **is** locked because we locked it, indicating
// we own the lock.
while (test_and_set(&lock) == 1);
critical section // only one process can be in this section at a time
lock = 0; // release lock when finished with the critical section
}
Here is a spin lock implementation I did in C11 using _Atomic types. It should work just fine for STM32 as well, and probably compiles to use the underlying exclusive STREX/LDREX operations to store (write) and read (load), but I'd have to check that by looking at the assembly. Additionally, this implementation would need to be modified to add safety anti-deadlock mechanisms such as automatic deferral, timeout, and retry, to prevent deadlock. See my notes here: Add basic mutex (lock) and spin lock implementations in C11, C++11, AVR, and STM32
5. See also:
My answer on this for AVR mcus/Arduino
My answer on the general practice and demo of using atomic access guards, and my doAtomicRead() func which ensures atomic access withOUT turning interrupts off
[my Q&A] Which variable types/sizes are atomic on STM32 microcontrollers?
[my answer] Programing STM32 like STM8 (register-level GPIO)
Atomic access to shared variables should only be achieved by turning interrupts off where more modern alternatives are not available, or sometimes in very simple projects where performance and latency are not an issue.
Disabling interrupts increases the latency of the system in ways which are difficult to predict, and should be avoided wherever possible.
On ARMv7M and higher cores (including all STM32F1xx, STM32F2xx, STM32F3xx, STM32F4xx, STM32F7xx, STM32H7xx, STM32G4xx, STM32L1xx, STM32L4xx, SRM32L5xx, STM32U5xx) atomic access should be achieved using LDREX/STREX exclusive access instructions. Complex message queues and semaphore systems can be built upon these primitives which do not ever require to disable interrupts. For an example look at the semaphore implementation in mbed-os.
The other members of the STM32 family (STM32F0xx, STM32G0xx and STM32L0xx) may turn individual interrupts off using NVIC_EnableIRQ/NVIC_EnableIRQ or as a last resort turn all interrupts off with __disable_irq()/__enable_irq().
I know that port mapped IO are accessed thorugh in/out CPU instructions and memory (& memory mapped registers) are accessed through load/store CPU instructions (similar to memory). But, with a pointer in C code, how compiler knows if a address is port mapped IO register or memory and then inserts the correct CPU instructions?
Example:
uint16_t const* uart_reg = 0x8000c000;
uint16_t const* ram_addr = 0x4000c000;
*uart_reg = 0x1;
*ram_addr = 0x12;
If you have non-memory-mapped I/O, you're not going to be able to access it from C as if it were memory.
You must use some platform-specific trickery to get the proper I/O instructions. This is a pain, and might be one reason why modern hardware seems to favor memory-mapped I/O.
C compilers for platforms with port-mapped I/O had to include this, see for instance the inp() function in the old Turbo C++ for DOS.
Two steps:
Install the correct toolchain for your target hardware.
Use provided header files with all the necessary properly dome definitions
For example if you want to program ARM STM uC bare metal gcc compiler - use arm-none-eabi-gcc and tools. Use header CMSIS header files provided by STM & ARM (usually they are already included if you choose a "ready made" toolchain)
Example declarations and definitions (only for ADC1):
#define FLASH_BASE ((uint32_t)0x08000000U) /*!< FLASH base address in the alias region */
#define CCMDATARAM_BASE ((uint32_t)0x10000000U) /*!< CCM(core coupled memory) data RAM base address in the alias region */
#define SRAM_BASE ((uint32_t)0x20000000U) /*!< SRAM base address in the alias region */
#define PERIPH_BASE ((uint32_t)0x40000000U) /*!< Peripheral base address in the alias region */
#define SRAM_BB_BASE ((uint32_t)0x22000000U) /*!< SRAM base address in the bit-band region */
#define PERIPH_BB_BASE ((uint32_t)0x42000000U) /*!< Peripheral base address in the bit-band region */
#define AHB3PERIPH_BASE (PERIPH_BASE + 0x10000000U)
#define ADC1_BASE (AHB3PERIPH_BASE + 0x00000000U)
#define ADC1 ((ADC_TypeDef *) ADC1_BASE)
#ifdef __cplusplus
#define __I volatile /*!< Defines 'read only' permissions */
#else
#define __I volatile const /*!< Defines 'read only' permissions */
#endif
#define __O volatile /*!< Defines 'write only' permissions */
#define __IO volatile /*!< Defines 'read / write' permissions */
/* following defines should be used for structure members */
#define __IM volatile const /*! Defines 'read only' structure member permissions */
#define __OM volatile /*! Defines 'write only' structure member permissions */
#define __IOM volatile /*! Defines 'read / write' structure member permissions */
typedef struct
{
__IO uint32_t ISR; /*!< ADC Interrupt and Status Register, Address offset: 0x00 */
__IO uint32_t IER; /*!< ADC Interrupt Enable Register, Address offset: 0x04 */
__IO uint32_t CR; /*!< ADC control register, Address offset: 0x08 */
__IO uint32_t CFGR; /*!< ADC Configuration register, Address offset: 0x0C */
uint32_t RESERVED0; /*!< Reserved, 0x010 */
__IO uint32_t SMPR1; /*!< ADC sample time register 1, Address offset: 0x14 */
__IO uint32_t SMPR2; /*!< ADC sample time register 2, Address offset: 0x18 */
uint32_t RESERVED1; /*!< Reserved, 0x01C */
__IO uint32_t TR1; /*!< ADC watchdog threshold register 1, Address offset: 0x20 */
__IO uint32_t TR2; /*!< ADC watchdog threshold register 2, Address offset: 0x24 */
__IO uint32_t TR3; /*!< ADC watchdog threshold register 3, Address offset: 0x28 */
uint32_t RESERVED2; /*!< Reserved, 0x02C */
__IO uint32_t SQR1; /*!< ADC regular sequence register 1, Address offset: 0x30 */
__IO uint32_t SQR2; /*!< ADC regular sequence register 2, Address offset: 0x34 */
__IO uint32_t SQR3; /*!< ADC regular sequence register 3, Address offset: 0x38 */
__IO uint32_t SQR4; /*!< ADC regular sequence register 4, Address offset: 0x3C */
__IO uint32_t DR; /*!< ADC regular data register, Address offset: 0x40 */
uint32_t RESERVED3; /*!< Reserved, 0x044 */
uint32_t RESERVED4; /*!< Reserved, 0x048 */
__IO uint32_t JSQR; /*!< ADC injected sequence register, Address offset: 0x4C */
uint32_t RESERVED5[4]; /*!< Reserved, 0x050 - 0x05C */
__IO uint32_t OFR1; /*!< ADC offset register 1, Address offset: 0x60 */
__IO uint32_t OFR2; /*!< ADC offset register 2, Address offset: 0x64 */
__IO uint32_t OFR3; /*!< ADC offset register 3, Address offset: 0x68 */
__IO uint32_t OFR4; /*!< ADC offset register 4, Address offset: 0x6C */
uint32_t RESERVED6[4]; /*!< Reserved, 0x070 - 0x07C */
__IO uint32_t JDR1; /*!< ADC injected data register 1, Address offset: 0x80 */
__IO uint32_t JDR2; /*!< ADC injected data register 2, Address offset: 0x84 */
__IO uint32_t JDR3; /*!< ADC injected data register 3, Address offset: 0x88 */
__IO uint32_t JDR4; /*!< ADC injected data register 4, Address offset: 0x8C */
uint32_t RESERVED7[4]; /*!< Reserved, 0x090 - 0x09C */
__IO uint32_t AWD2CR; /*!< ADC Analog Watchdog 2 Configuration Register, Address offset: 0xA0 */
__IO uint32_t AWD3CR; /*!< ADC Analog Watchdog 3 Configuration Register, Address offset: 0xA4 */
uint32_t RESERVED8; /*!< Reserved, 0x0A8 */
uint32_t RESERVED9; /*!< Reserved, 0x0AC */
__IO uint32_t DIFSEL; /*!< ADC Differential Mode Selection Register, Address offset: 0xB0 */
__IO uint32_t CALFACT; /*!< ADC Calibration Factors, Address offset: 0xB4 */
} ADC_TypeDef;
and it expands to: ((ADC_TypeDef *) ((((uint32_t)0x40000000U) + 0x10000000U) + 0x00000000U))
It is not worth to do it manually.
I have the following structs (from a library I'm using) with some fields and I would like to assign with OR operation a new value. But I'm debugging and I can't see how anything is writing there in stm32l4xx_hal_tim.c file:
typedef struct
{
TIM_TypeDef *Instance; /*!< Register base address */
TIM_Base_InitTypeDef Init; /*!< TIM Time Base required parameters */
HAL_TIM_ActiveChannel Channel; /*!< Active channel */
DMA_HandleTypeDef *hdma[7]; /*!< DMA Handlers array This array is accessed by a #ref DMA_Handle_index */
HAL_LockTypeDef Lock; /*!< Locking object */
__IO HAL_TIM_StateTypeDef State; /*!< TIM operation state */
}TIM_HandleTypeDef;
typedef struct
{
__IO uint32_t CR1; /*!< TIM control register 1, Address offset: 0x00 */
__IO uint32_t CR2; /*!< TIM control register 2, Address offset: 0x04 */
__IO uint32_t SMCR; /*!< TIM slave mode control register, Address offset: 0x08 */
__IO uint32_t DIER; /*!< TIM DMA/interrupt enable register, Address offset: 0x0C */
__IO uint32_t SR; /*!< TIM status register, Address offset: 0x10 */
__IO uint32_t EGR; /*!< TIM event generation register, Address offset: 0x14 */
__IO uint32_t CCMR1; /*!< TIM capture/compare mode register 1, Address offset: 0x18 */
__IO uint32_t CCMR2; /*!< TIM capture/compare mode register 2, Address offset: 0x1C */
__IO uint32_t CCER; /*!< TIM capture/compare enable register, Address offset: 0x20 */
__IO uint32_t CNT; /*!< TIM counter register,
} TIM_TypeDef;
I have a part of code where I defined: TIM_HandleTypeDef TIMER_Struct;
And I would like to access the field "CR1" of TIM_TypeDef struct that is "*Instance" field of TIM_HandleTypeDef. So I have done it by this way in a function DRV_TIMER_init():
#include "main.h"
#include "stm32l4xx_hal_tim.h"
uint32_t uwPrescalerValue = 0;
TIM_HandleTypeDef TIMER_Struct;
void DRV_TIMER_init(void);
int main(void)
{
DRV_TIMER_init();
while(1)
{
}
}
//where uint32_t SystemCoreClock = 4000000; in other system source file.
void DRV_TIMER_init(void)
{
uwPrescalerValue = (uint32_t)(SystemCoreClock / 1000000) - 1;
TIMER_Struct.Init.Period = 100 - 1;
TIMER_Struct.Init.Prescaler = uwPrescalerValue;
TIMER_Struct.Init.ClockDivision = 0; // these accesses work
TIMER_Struct.Instance -> CR1 |= 0x01 << 3; // this no works
}
Even if I write directly:
TIMER_Struct.Instance -> CR1 = 0xFFFFFFFF;
It stills without having effect.
I think It could be a fact that I'm not controlling appropiately the pointer access or similar. But I don't see how can I access and modify the content of the commented field. Since I can see (debug mode) how the rest of struct fields updates are writen correctly.
Any correction suggested here?
TIMER_Struct.Instance -> CR1 = 0xFFFFFFFF;
I try different ways to get it with no success. I need new ideas.
TIM_TypeDef *Instance;
is just a pointer which is not pointing to anywhere so you can't dereference it
you have defined somewhere in the lib a macro like this:
#define TIM (TIM_TypeDef*)0xDEADBEEF;
this is how you map the registers to the memory
try modifying your code like this:
TIMER_Struct.Instance = (TIM_Typedef*)0xDEADBEEF;
or just
TIMER_Struct.Instance = TIM;
than
TIMER_Struct.Instance -> CR1 |= 0x01 << 3;
should work
I think you forgot to define TIMER_Struct.Instance
TIM_HandleTypeDef TIMER_Struct;
TIMER_Struct.Instance = TIM1;
//Now you can access TIMER_Struct.Instance
TIMER_Struct.Instance->CR1 = (uint32_t)0xFFFFFFFF;
But I prefer to use CMSIS for writing to registers. No need for the HAL. With CMSIS writing to a register could look like:
TIM1->CR1=(uint32_t)0xFFFFFFFF
I have found the solution. First you must enable the peripheral clock. If not, there won't be any effect over timer registers, because the pointer structs fields were pointing directly to the hardware registers allocation. This was the cause why I can't see any update when watching my struct var at debugging.
I was enabling clk (with hal_xxx_init()) after writing to the allocation. That was my mistake.
Here in this part of code is the correction:
//where uint32_t SystemCoreClock = 4000000; in other system source file.
void DRV_TIMER_init(void)
{
uwPrescalerValue = (uint32_t)(SystemCoreClock / 1000000) - 1;
TIMER_Struct.Init.Period = 100 - 1;
TIMER_Struct.Init.Prescaler = uwPrescalerValue;
TIMER_Struct.Init.ClockDivision = 0; // these accesses work
if (HAL_TIM_Base_Init(&TIMER_Struct) != HAL_OK)
{
/* Initialization Error */
TIM_Error_Handler();
}
TIMER_Struct.Instance -> CR1 |= 0x01 << 3; // this works now
}
If you try to put the last sentence before the:
HAL_TIM_Base_Init(&TIMER_Struct);
no
TIMER_Struct.Instance -> CR1 = xxx; //write mode register acces
will have effect.