Chapter 23

Advanced Topics

Abstract

This chapter covers a range of advanced topics such as bit data handling, creation of startup code in C, stack overflow prevention, workaround to allow reentrant interrupt, semaphore implementation and memory ordering, and how memory barrier instructions are related to it and various cases that memory barrier should be used to enforce ordering of certain operations.

Keywords

Memory barrier use cases; Memory ordering; Reentrant interrupt handling; Semaphore implementations; Stack overflow prevention; Startup code in C

23.1. Bit Data Handling in C Programming

While this is not really an advanced topic for experienced embedded software developers, many beginners do not know that in C/C++ you can define bit fields to make coding easier. Examples of bit field can be found in CMSIS-CORE header files. Useful application of bit fields could be definition of Program Status Register (xPSR), Application Program Status Register (APSR), and Internal Program Status Register.
/∗∗ ∖brief  Union type to access the Application Program Status Register (APSR).∗/
typedef union
{
  Struct
  {
#if (__CORTEX_M != 0x04)
    uint32_t _reserved0:27;   /∗!< bit:  0..26  Reserved                       ∗/
#else
    uint32_t _reserved0:16;   /∗!< bit:  0..15  Reserved                       ∗/
    uint32_t GE:4;            /∗!< bit: 16..19  Greater than or Equal flags    ∗/
    uint32_t _reserved1:7;    /∗!< bit: 20..26  Reserved                       ∗/
#endif
    uint32_t _reserved2:1;    /∗!< bit:     27  Reserved (Q flag for ARMv7-M)  ∗/
    uint32_t V:1;             /∗!< bit:     28  Overflow condition code flag   ∗/
    uint32_t C:1;             /∗!< bit:     29  Carry condition code flag      ∗/
    uint32_t Z:1;             /∗!< bit:     30 Zero condition code flag        ∗/
    uint32_t N:1;             /∗!< bit:     31 Negative condition code flag    ∗/
  } b;                        /∗!< Structure used for bit  access              ∗/
  uint32_t w;                 /∗!< Type used for word access                   ∗/
} APSR_Type;
You can utilize such bit field definition in application codes, for example:
int         x, y, z;
APSR_Type   foo;
    z = x + y;
    foo.w = __get_APSR();  // .w used for word accesses
    if (foo.b.V) {         // .b used for bit accesses
      printf ("Overflowed∖n");
    } else {
      printf ("No overflow∖n");
    }
You can also create helper structure and typedef to help extracting bits in peripheral registers:
Helper C Structure and Union Definition in Bit Data Handling
  typedef struct /∗ structure to define 32-bits ∗/
  {
    uint32_t bit0:1;
    uint32_t bit1:1;
    uint32_t bit2:1;
    uint32_t bit3:1;
    uint32_t bit4:1;
    uint32_t bit5:1;
    uint32_t bit6:1;
    uint32_t bit7:1;
    uint32_t bit8:1;
    uint32_t bit9:1;
    uint32_t bit10:1;
    uint32_t bit11:1;
    uint32_t bit12:1;
    uint32_t bit13:1;
    uint32_t bit14:1;
    uint32_t bit15:1;
    uint32_t bit16:1;
    uint32_t bit17:1;
    uint32_t bit18:1;
    uint32_t bit19:1;
    uint32_t bit20:1;
    uint32_t bit21:1;
    uint32_t bit22:1;
    uint32_t bit23:1;
    uint32_t bit24:1;
    uint32_t bit25:1;
    uint32_t bit26:1;
    uint32_t bit27:1;
    uint32_t bit28:1;
    uint32_t bit29:1;
    uint32_t bit30:1;
    uint32_t bit31:1;
  } ubit32_t;        /∗!< Structure used for bit  access  ∗/
  typedef union
  {
    ubit32_t ub; /∗!< Type used for unsigned bit  access  ∗/
    uint32_t uw; /∗!< Type used for unsigned word access  ∗/
  } bit32_Type;
You can then declare variables using the newly created data type. For example:
  bit32_Type foo;
  foo.uw = GPIOD->IDR;  // .uw access using word size
  if (foo.ub.bit14) {   // .ub access using bit size
      GPIOD->BSRRH = (1<<14); // Clear bit 14 
    } else {  
      GPIOD->BSRRL = (1<<14); // Set bit 14
    }
You can also declare a point to the register:
  volatile bit32_Type  LED;
  LED = (bit32_Type ∗) (&GPIOD->IDR);
  if (LED->ub.bit12) {
      GPIOD->BSRRH = (1<<12); // Clear bit 12
    } else {  
      GPIOD->BSRRL = (1<<12); // Set   bit 12
    }
Please note that using bit field in programming does not give you atomic bit accesses. When writing to a bit or bit field, the compiler can generate a software read-modify-write sequence, and interrupts could take place in between and the Interrupt Service Routine (ISR) can modify other bits of the same register, and result in conflicts when the ISR return and resume the write operation.

23.2. Startup Code in C

Most of the examples in this book use startup codes (or boot codes) that are written in assembly language. It is possible to have the startup codes written in C. However, this requires importing compiler-specific symbols and in some case compiler-specific directives. So the C startup codes are tool chain dependent, just like assembly.
For example, with Keil® MDK-ARM™ environment, you can also define the startup code and the vector table written in C, as follows:
An example C startup code for Keil MDK-ARM (for STM32L053C8T6 device)
#include <rt_misc.h>
// Define where the top of memory is.
#define TOP_OF_RAM 0x20002000U
extern void __main(void);     // Use C-library initialization function.
extern void NMI_Handler(void);
extern void HardFault_Handler(void);
extern void Reset_Handler(void);
extern void SVC_Handler(void);
extern void PendSV_Handler(void);
extern void SysTick_Handler(void);
extern void WWDG_IRQHandler(void);
extern void PVD_IRQHandler(void);
extern void RTC_IRQHandler(void);
extern void FLASH_IRQHandler(void);
extern void RCC_CRS_IRQHandler(void);
extern void EXTI0_1_IRQHandler(void);
extern void EXTI2_3_IRQHandler(void);
extern void EXTI4_15_IRQHandler(void);
extern void TSC_IRQHandler(void);
extern void DMA1_Channel1_IRQHandler(void);
extern void DMA1_Channel2_3_IRQHandler(void);
extern void DMA1_Channel4_5_6_7_IRQHandler(void);
extern void ADC1_COMP_IRQHandler(void);
extern void LPTIM1_IRQHandler(void);
extern void TIM2_IRQHandler(void);
extern void TIM6_DAC_IRQHandler(void);
extern void TIM21_IRQHandler(void);
extern void TIM22_IRQHandler(void);
extern void I2C1_IRQHandler(void);
extern void I2C2_IRQHandler(void);
extern void SPI1_IRQHandler(void);
extern void SPI2_IRQHandler(void);
extern void USART1_IRQHandler(void);
extern void USART2_IRQHandler(void);
extern void RNG_LPUART1_IRQHandler(void);
extern void LCD_IRQHandler(void);
extern void USB_IRQHandler(void);
extern void SystemInit(void);
//------------------------------------------------------------------------------
// Define location of C stack and heap
//------------------------------------------------------------------------------
// Initialize stack and heap to span from the end of the zero-initialized
// region to the value defined by TOP_OF_RAM; see the "ARM Compiler toolchain
// Linker Reference" (ARM DUI 0493) and the "ARM Compiler toolchain Using ARM
// C and C++ Libraries and Floating-Point Support" (ARM DUI 0475) for further
// details.
extern unsigned int Image$$ZI$$Limit;
struct __initial_stackheap
__user_initial_stackheap
(unsigned int r0,  // heap_base
 unsigned int r1,  // stack_base
 unsigned int r2,  // heap limit
 unsigned int r3)__value_in_regs //stacklimit
{
  struct __initial_stackheap sh;
  sh.heap_base   = Image$$ZI$$Limit;
  sh.stack_base  = TOP_OF_RAM;    // Place Stack at top of SRAM
  sh.heap_limit  = sh.stack_base; // Or if Heap size is known
                                  // sh.heap_limit = Image$$ZI$$Limit + HEAP_SIZE
  sh.stack_limit = sh.heap_base;  // Or if Stack size is known
                                  // sh.stack_limit = TOP_OF_RAM - STACK_SIZE
  return sh;
}
//------------------------------------------------------------------------------
// Implement the vector table in its own area to facilitate linking first
//------------------------------------------------------------------------------
typedef void(∗ const ExecFuncPtr)(void) __irq;
/∗ Place table in separate section ∗/
#pragma arm section rodata="Vectors"
__attribute__ ((section("Vectors")))
ExecFuncPtr __Vectors[] = {
  (ExecFuncPtr) TOP_OF_RAM, // Initial value for stack pointer.
  (ExecFuncPtr) __main,            // Reset handler is C initialization.
  (ExecFuncPtr) NMI_Handler,       // NMI handler
  (ExecFuncPtr) HardFault_Handler, // HardFault
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  (ExecFuncPtr) SVC_Handler,
  0,
  0,
  (ExecFuncPtr) PendSV_Handler,
  (ExecFuncPtr) SysTick_Handler,
  (ExecFuncPtr) WWDG_IRQHandler, // Window Watchdog
  (ExecFuncPtr) PVD_IRQHandler, //  PVD through EXTI Line detect
  (ExecFuncPtr) RTC_IRQHandler, //  RTC through EXTI Line
  (ExecFuncPtr) FLASH_IRQHandler,               // FLASH
  (ExecFuncPtr) RCC_CRS_IRQHandler,             // RCC and CRS
  (ExecFuncPtr) EXTI0_1_IRQHandler,             // EXTI Line 0 and 1
  (ExecFuncPtr) EXTI2_3_IRQHandler,             // EXTI Line 2 and 3
  (ExecFuncPtr) EXTI4_15_IRQHandler,            // EXTI Line 4 to 15
  (ExecFuncPtr) TSC_IRQHandler,                 // TSC
  (ExecFuncPtr) DMA1_Channel1_IRQHandler,       // DMA1 Channel 1
  (ExecFuncPtr) DMA1_Channel2_3_IRQHandler,     // DMA1 Channel 2 and Channel 3
  (ExecFuncPtr) DMA1_Channel4_5_6_7_IRQHandler, // DMA1 Channel 4 to 7
  (ExecFuncPtr) ADC1_COMP_IRQHandler,           // ADC1, COMP1 and COMP2 
  (ExecFuncPtr) LPTIM1_IRQHandler,              // LPTIM1
  0,                                            // Reserved
  (ExecFuncPtr) TIM2_IRQHandler,                // TIM2
  (ExecFuncPtr) 0,                              // Reserved
  (ExecFuncPtr) TIM6_DAC_IRQHandler,            // TIM6 and DAC
  0,                                            // Reserved
  0,                                            // Reserved
  (ExecFuncPtr) TIM21_IRQHandler,               // TIM21
  0,                                            // Reserved
  (ExecFuncPtr) TIM22_IRQHandler,               // TIM22
  (ExecFuncPtr) I2C1_IRQHandler,                // I2C1
  (ExecFuncPtr) I2C2_IRQHandler,                // I2C2
  (ExecFuncPtr) SPI1_IRQHandler,                // SPI1
  (ExecFuncPtr) SPI2_IRQHandler,                // SPI2
  (ExecFuncPtr) USART1_IRQHandler,              // USART1
  (ExecFuncPtr) USART2_IRQHandler,              // USART2
  (ExecFuncPtr) RNG_LPUART1_IRQHandler,         // RNG and LPUART1
  (ExecFuncPtr) LCD_IRQHandler,                 // LCD
  (ExecFuncPtr) USB_IRQHandler,                 // USB
};
#pragma arm section
void Reset_Handler(void)
{
  SystemInit();
  __main();
}
__attribute__ ((weak)) void NMI_Handler(void)
{ while(1); }
__attribute__ ((weak)) void HardFault_Handler(void)
{ while(1); }
__attribute__ ((weak)) void SVC_Handler(void)
{ while(1); }
__attribute__ ((weak)) void PendSV_Handler(void)
{ while(1); }
__attribute__ ((weak)) void SysTick_Handler(void)
{ while(1); }
__attribute__ ((weak)) void WWDG_IRQHandler(void)
{ while(1); }
__attribute__ ((weak)) void PVD_IRQHandler(void)
{ while(1); }
__attribute__ ((weak)) void RTC_IRQHandler(void)
{ while(1); }
__attribute__ ((weak)) void FLASH_IRQHandler(void)
{ while(1); }
__attribute__ ((weak)) void RCC_CRS_IRQHandler(void)
{ while(1); }
__attribute__ ((weak)) void EXTI0_1_IRQHandler(void)
{ while(1); }
__attribute__ ((weak)) void EXTI2_3_IRQHandler(void)
{ while(1); }
__attribute__ ((weak)) void EXTI4_15_IRQHandler(void)
{ while(1); }
__attribute__ ((weak)) void TSC_IRQHandler(void)
{ while(1); }
__attribute__ ((weak)) void DMA1_Channel1_IRQHandler(void)
{ while(1); }
__attribute__ ((weak)) void DMA1_Channel2_3_IRQHandler(void)
{ while(1); }
__attribute__ ((weak)) void DMA1_Channel4_5_6_7_IRQHandler(void)
{ while(1); }
__attribute__ ((weak)) void ADC1_COMP_IRQHandler(void)
{ while(1); }
__attribute__ ((weak)) void LPTIM1_IRQHandler(void)
{ while(1); }
__attribute__ ((weak)) void TIM2_IRQHandler(void)
{ while(1); }
__attribute__ ((weak)) void TIM6_DAC_IRQHandler(void)
{ while(1); }
__attribute__ ((weak)) void TIM21_IRQHandler(void)
{ while(1); }
__attribute__ ((weak)) void TIM22_IRQHandler(void)
{ while(1); }
__attribute__ ((weak)) void I2C1_IRQHandler(void)
{ while(1); }
__attribute__ ((weak)) void I2C2_IRQHandler(void)
{ while(1); }
__attribute__ ((weak)) void SPI1_IRQHandler(void)
{ while(1); }
__attribute__ ((weak)) void SPI2_IRQHandler(void)
{ while(1); }
__attribute__ ((weak)) void USART1_IRQHandler(void)
{ while(1); }
__attribute__ ((weak)) void USART2_IRQHandler(void)
{ while(1); }
__attribute__ ((weak)) void RNG_LPUART1_IRQHandler(void)
{ while(1); }
__attribute__ ((weak)) void LCD_IRQHandler(void)
{ while(1); }
__attribute__ ((weak)) void USB_IRQHandler(void)
{ while(1); }
In typical software development environment, the example software package provided by the microcontroller vendor would have the startup code and header files for various tool chains. So you do not have to worry about creating your own startup code and header files for the microcontroller devices.
You might have noticed that the reset handler called the SystemInit() function before calling the C startup code (“__main()”). Starting from CMSIS-CORE v1.3, the System Initialization function SystemInit() is called from the reset handler. This change allows the SystemInit() function to initialize external memory interface controller(s) before starting the C runtime startup code. In this way, you can place the stack and heap memories used by the C program at external memory locations.
The initial value for the Main Stack Pointer (MSP), however, still needs to point to a RAM region that does not require initialization because some of the exceptions (e.g., NMI, HardFault) could happen at the beginning of the boot process.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.217.144.32