ES1 PRJ2, PRJV & PRJD: STM32F05x microcontroller

Lesson 2: Architecture of the STM32F051x & USART

By Hugo Arends

© 2018 HAN University of Applied Sciences, version 1.0


STM32F051x microcontroller architecture

The STM32F051x microcontroller system architecture is described in chapter 2 of the Reference Manual RM0091.

Copyright STMicroelectronics (2012). Retrieved from RM0091.

The Cortex-M0 consists of a 32-bit processor core and the following core peripherals[1]:

A Direct Memory Access (DMA) Controller is available in the microcontroller. A DMA Controller allows peripherals to access memory (or other peripherals) independently from the Cortex-M0 core. This has great advantage when, for instance, a large amount of data needs to be read from the USART and transferred to the SRAM. Instead of passing this data ‘through’ the Cortex-M0 core, the DMA Controller directly places it in SRAM. Programmers need to initially setup DMA and on a DMA request the DMA Controller handles the data transfer.

Both Cortex-M0 and DMA Controller can be masters of the main system bus. This is managed by the BusMatrix. There are four bus slaves: internal SRAM, internal Flash memory, Advanced High-performance Bus 1 (AHB1) to connect the peripherals (through the Advanced Peripheral Bus (APB)) and AHB2 dedicated to the GPIOs.

Using this model, everything looks like memory to the Cortex-M0 core. So program memory, data memory, registers and GPIO ports are organized within a 4 GB (232) address space. The memory map and register boundaries are described in the Reference Manual RM0091.

A more detailed description of the available peripherals in the STM32F051xx is provided in the Datasheet DS8668. The block diagram shows all of the building blocks mentioned above, and also these key features[2]:

– ARM® Cortex™-M0 0.9 DMIPS/MHz up to 48 MHz

– 1.8/2.0 to 3.6 V supply range

– 6 Mbit/s USART

– 18 Mbit/s SPI with 4- to 16-bit data frame

– 1 Mbit/s I²C fast-mode plus

– HDMI CEC

– 1x 16-bit 3-phase PWM motor control timer

– 5x 16-bit PWM timers

– 1x 16-bit basic timer

– 1x 32-bit PWM timer

– 12 MHz I/O toggling

Copyright STMicroelectronics (2012). Retrieved from DS8668

Debugging

The Cortex-M0 implements a complete hardware debug solution, with extensive hardware breakpoint and watchpoint options. This provides high system visibility of the processor, memory and peripherals through a 2-pin Serial Wire Debug (SWD) port that is ideal for small package devices.[3]

The STM32F0DISCOVERY board includes additional hardware to communicate with this SWD port. This is called the ST-LINK/V2, which implements an in-circuit debugger and programmer for the STM32 microcontroller family.

The Keil MDK-ARM toolchain supports the ST-LINK/V2. So when plugging in the STM32F0DISCOVERY in the USB port of a host PC, the ‘Found new hardware wizard” appears and prompts the user to install the ST-LINK_V2_USB driver. If the driver cannot be found, download it from STM's website: http://www.st.com/web/catalog/tools/FM146/CL1984/SC724/SS1677/PF251168.

The debugger is started with CTRL+F5 (or see the Debug menu). This programs the microcontroller and then starts the debug session. The options for the debugger can be changed in the Options for Target ... menu (ALT+F7) on the Debug tab.

 

Programming the target microcontroller is started with the ‘Load’ button (or see the Flash menu). This programs and then resets the microcontroller. The options for the programmer can be changed in the Options for Target ... menu (ALT+F7) on the Utilities tab.

Exceptions & Interrupts

Exceptions are events that cause change in normal program execution. Let’s say that the main-loop is executed by the microprocessor and an exception occurs. The microprocessor saves the current state and starts executing a handler specific to that exception. After handling the exception, the microprocessor resumes executing the main-loop. There are various types of exceptions and interrupts are a subset of them.

The STM32F051x has a sophisticated controller to handle exceptions. This is called the Nested Vectored Interrupt Controller (NVIC), which is one of the Cortex-M0 core peripherals. The NVIC and the Cortex-M0 core interface are closely coupled, which enables low latency interrupt processing and efficient processing of late arriving interrupts. The Reference Manual RM0091 describes all interrupts and exception vectors:

Copyright STMicroelectronics (2012). Retrieved from RM0091.

Let’s take a look at vector number 27. All USART1 interrupts (such as Transmit data register empty, CTS interrupt, Transmission Complete, etc.) are connected to this one vector. The Reference Manual shows this like this:

Copyright STMicroelectronics (2012). Retrieved from RM0091.

This means, from a programmer's point of view, that within the exception handler the programmer must verify which interrupt line caused the exception. This is done in the Interrupt & Status Register related to the peripheral.

In the table above, special exceptions are marked grey. Some of these special exception types are:[4]

USART

The Universal Synchronous Asynchronous Receiver Transmitter (USART) is a widely used communication interface. The STM32F051R8 features two USARTs. This lesson shows two ways to implement USART communication: based on polling and interrupts. In a later lesson we’ll see how to implement USART communication with DMA.

First, let’s discuss the 11 registers associated with an USART.

Three registers are dedicated to initial setup and configuration of the USART. There exists a variety of options, such as enabling receiver and transmitter, enabling interrupts, configuring the number of stop bits, DMA enable, etc.:

One register is used to set the baud rate:

Three registers are used for special modes of operation, such as IrDA, SmartCard, etc.:

Two registers are associated with interrupts:

Two registers are associated with receiving/sending data:

An overview of these registers and their dependencies is given in the block diagram:

Copyright STMicroelectronics (2012). Retrieved from RM0091.

Polling

To make USART1 communicate with 115200 Bd, 8 data bits, no parity and 1 stop bit, we first need to find out what pins should be used. The User Manual UM1525 can be used to find out. Table 6 describes the Microcontroller Unit (MCU) pins and the mapping to the STM32F0DISCOVERY board headers.

The minimum required connection for the USART is Transmit (TX), Receive (RX) and GND. Looking in the column for Alternate functions USART1 Transmit (1_TX) is mapped to MCU I/O pin PA9. And USART1 Receive (1_RX) is mapped to PA10.

Therefore, the connections with for instance the USB-to-serial convertor ‘FT232RL Breakout Board’ (https://www.sparkfun.com/products/718) are:

Initialize

As we need to use the two GPIO pins PA9 and PA10, they must be configured to their Alternate Function 1 (AF1) (see the Datasheet DS8668). This is done by writing two GPIOA related registers MODER and AFR. But before any GPIOA register can be written, recall from the previous lesson that the GPIOA peripheral clock needs to be enabled:

RCC->AHBENR |= RCC_AHBENR_GPIOAEN;

Then the mode of the I/O pins should be set to alternate function:

GPIOA->MODER |= (GPIO_MODER_MODER9_1 | GPIO_MODER_MODER10_1);

Then AF1 must be selected:

GPIOA->AFR[1] |= 0x00000110;

Before writing to any USART1 register, the USART1 peripheral clock must be enabled:

RCC->APB2ENR |= RCC_APB2ENR_USART1EN;

Next, the baud rate is configured in register USART1_BRR. The Reference Manual RM0091 provides information to calculate the register’s value. Default oversampling (16) will be used (so bit OVER8 equals 0), which means that BRR[15:0] equals USARTDIV[15:0]. Calculating USARTDIV is described in paragraph 27.5.4:

So for our situation:

The rest of the settings, 8 data bits, no parity and 1 stop bit, is the default state of the USART. So finally, there is one last thing to do: enable the USART, enable the transmitter and enable the receiver:

USART1->CR1 = (uint32_t)(0x0000000D);

The function USART_init() that performs initialization is implemented like this:

void USART_init(void)

{

  // GPIOA Periph clock enable

  RCC->AHBENR |= RCC_AHBENR_GPIOAEN;

 

  // PA9 and PA10 Alternate function mode

  GPIOA->MODER |= (GPIO_MODER_MODER9_1 | GPIO_MODER_MODER10_1);

 

  // Set alternate functions AF1 for PA9 and PA10

  GPIOA->AFR[1] |= 0x00000110;

 

  // USART1 clock enable

  RCC->APB2ENR |= RCC_APB2ENR_USART1EN;

  // 115200 Bd @ 48 MHz

  // USARTDIV = 48 MHz / 115200 = 416 = 0x01A0

  // BRR[15:4] = USARTDIV[15:4]

  // When OVER8 = 0, BRR [3:0] = USARTDIV [3:0]

  USART1->BRR = (uint16_t)(0x01A0);

  // USART enable

  // Receiver enable

  // Transmitter enable

  USART1->CR1 = (uint32_t)(USART_CR1_UE |

                           USART_CR1_RE |

                           USART_CR1_TE);

  // Default value

  USART1->CR2 = (uint32_t)(0x00000000);

  USART1->CR3 = (uint32_t)(0x00000000);

}

Transmitting characters

Transmitting a character is easy. First we must make sure that the Transmit Data Register (TDR) is empty. This can be verified with the TXE bit in the USART’s Interrupt & Status Register (ISR):

while((USART1->ISR & USART_ISR_TXE) == 0){;}

This while-loop will be true as long as the TXE bit has not been set. In other words: the while-loop will be true as long as the Transmit Data Register (TDR) is not empty. Waiting like this, for a flag to be set, is called software polling.

As soon as the register is empty, we can write data to the TDR. Writing data to this register automatically transmits it and will clear the TXE flag:

USART1->TDR = c;

The function USART_putc(char c) is implemented like this:

void USART_putc(char c)

{

  // Wait for Transmit data register empty

  while((USART1->ISR & USART_ISR_TXE) == 0){;}

  // Transmit data by writing to TDR, clears TXE flag  

  USART1->TDR = c;

}

Receiving characters

Receiving a character is easy too. First we must make sure that there hasn’t been an overrun error. This can be verified with the ORE-bit in the Interrupt & Status Register (ISR). If there was an overrun error we simply clear it by reading from RDR:

if((USART1->ISR & USART_ISR_ORE) == 0)

{

  c = USART1->RDR;

}

Then we will wait until the Receive Data Register (RDR) has data in it (is not empty). This can be verified with the RXNE bit in the ISR:

while((USART1->ISR & USART_ISR_RXNE) == 0){;}

This while-loop will be true as long as the RXNE-bit has not been set. In other words: the while-loop will be true as long as the Receive Data Register (RDR) is empty.

As soon as the register is not empty, we can read data from the RDR. Reading data from this register will clear the RXNE flag:

c = USART1->RDR;

The function USART_getc(void) is implemented like this:

char USART_getc(void)

{

  char c;

  // Was there an Overrun error?

  if((USART1->ISR & USART_ISR_ORE) == 0)

  {

    // Yes, clear it by reading from RDR

    c = USART1->RDR;

  }

  // Wait for data in the Receive Data Register

  while((USART1->ISR & USART_ISR_RXNE) == 0){;}

  // Read data from RDR, clears the RXNE flag

  c = USART1->RDR;

  return(c);

}

Assignments 1

Download the project associated with this lesson. Compile and run it with the hardware setup mentioned earlier in this document. Use a terminal program such as PuTTY (http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html) to display messages sent by the STM32F0DISCOVERY board. Answer the question that is displayed in the terminal window.

Implement the function:

Interrupts

When using software polling, the microprocessor is spending all of its time checking a flag. In the meantime no other code can be executed. When using interrupts, normal code execution can take place and when a flag is raised, the corresponding interrupt handler is executed.

Let’s change the existing program so, that data is received interrupt driven.

Uncomment the following lines of code in the file usart.c:

// RXNE interrupt enable

USART1->CR1 |= (uint32_t)(USART_CR1_RXNEIE);

 

// USART1 interrupts enable in NVIC

NVIC_EnableIRQ(USART1_IRQn);

NVIC_SetPriority(USART1_IRQn, 0);

NVIC_ClearPendingIRQ(USART1_IRQn);

First, the RXNE interrupt is enabled in CR1. This interrupt is generated when new data is available in the Receive Data Register.

Secondly the USART1 interrupts are enabled in the NVIC. This is done with three CMSIS compliant functions. USART1_IRQn is a definition that holds the IRQ vector number of USART1. As we have seen before, this equals 27.

Now we have enabled the interrupts, let’s have a look at the interrupt handler. The file stm32f0xx_it.c is a central place for the Cortex-M0 Exception Handlers and the STM32F0xx Peripherals Interrupt Handlers. So if we want to add a handler for USART1 the C-like function looks like:

void USART1_IRQHandler(void)

{

  // Read Data Register not empty interrupt?

  if(USART1->ISR & USART_ISR_RXNE)

  {

    // Read the data, clears the interrupt flag

    rx_buffer = USART1->RDR;

  }

}

The name of all interrupt handlers is predefined in the file startup_stm32f0xx.s. By looking closely into this file, you will notice that it implements the entire exceptions vector table.

Remember that all USART1 interrupts will trigger this specific interrupt handler. Therefore the first thing to do is verify if the handler was called due to an RXNE interrupt. If so, then read the data from the Receive Data Register and put it in a global variable called ‘rx_buffer’.

The final thing to do is to make use of this rx_buffer in the main program. So instead of using the function USART_getc(), we will simply read from the rx_buffer.

c1 = USART_getc();

c1 = rx_buffer;

Test the program and see how the green LED continues to blink.

Assignments 2

For this assignment, you will need the discovery board and a USB-to-serial convertor. Use the USART and the GPIOs (see previous lesson) to implement the following functionality:

Assignments 3 - optional

The USART is able to automatically detect the baud rate on the reception of one character and set the USART_BRR register accordingly. Add a function to the usart driver that implements auto baud rate detection. The function prototype could look like this:

void USART_BaudrateDetect(void);

Tip: Study the Reference Manual to find out how this feature exactly works.

hugo.arends@han.nl More lessons? Click here!


[1] Ref.: STMicroelectronics (2012), Programming Manual PM0215, [Online publication]

[2] Ref.: STMicroelectronics (2012), User Manual UM1525, [Online publication]

[3] Ref.: STMicroelectronics (2012), Programming Manual PM0215, [Online publication]

[4] Ref.: STMicroelectronics (2012), Programming Manual PM0215, [Online publication]