on 2016-02-23 in stm32-from-scratch
In the first part of this series i detailed how to blink an LED. That’s nice and usually the first thing i try to get to work when bringing up a microcontroller. But it doesn‘t really allow much data to be communicated. So it’s always good to get the serial port to work to do some good classic printf style debugging and explorations.
The serial port in the STM32F103 series doesn't have an internal FIFO buffer. So it’s not really for quick debugging without altering the timing of the running code to much. But for now a simple implementation without interrupts that just waits for the previous byte to be fully transmitted is ok. That’s fairly easy to achive.
But let’s stop using an minimal header file with just hand selected parts of the register definitions. That’s just too boring.
So from this point on i use the headers from STM32CubeF1 (i used version 1.2).
I copied the the files from STM32Cube_FW_F1_V1.2.0/Drivers/CMSIS/Include
to common/cmsis_include
and from
STM32Cube_FW_F1_V1.2.0/Drivers/CMSIS/Device/ST/STM32F1xx/Include
to common/stm_include
and added -I common/stm_include -I common/cmsis_include
to the compilation command.
void setup_serial(int baud) {
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
int divider = 8000000 / (16 * baud);
USART1->CR1 = USART_CR1_UE | USART_CR1_TE;
USART1->CR2 = 0;
USART1->CR3 = 0;
USART1->BRR = divider << 4;
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
GPIOA->CRH |= GPIO_CRH_MODE9_1 | GPIO_CRH_CNF9_1;
GPIOA->CRH &= ~(GPIO_CRH_CNF9_0 | GPIO_CRH_MODE9_0);
}
Of course the serial port needs some setup. As usual we need to enable the clock before doing anything else. The serial port in the STM32103 chips has lots of strange and wonderful features. But it also does the plain boring 8N1 (8bit, no parity, 1 stop bit) serial communication we need. The details are documented in Chapter 27 of the reference manual.
Next we need to calculate the parameters for clock generation. USART1
is connected to the PCLK2
clock. Looking at the clock tree we see that it’s derived from HCLK
(the core cpu clock) via the APB1
prescaler. The reset value of RCC->CFGR[PPRE1]
sets this prescaler to pass the clock on undivided. The serial peripheral has a fractional baud rate generator that is setup in units of 1/16 of the input frequency. Thus the baud rate is result of the PCLK2 / baud
division is further divided by 16.
The control registers all have reset values of 0. The only bits that need to be altered are uart enable (UE) and transmit enable (TE). The rest defaults to usable values.
Next we need to enable routing the serial signals. The serial transmit is connected to pin A9. So we first enable GPIO port A. Then set index 9 to be a low speed push-pull output for the 'alternate function', which is the serial port in this case. If you also want to receive on the serial line you would need to make sure PA10 is configured as input of suitable type.
bool serial_writebyte_wait(unsigned char val) {
while ((USART1->SR & USART_SR_TXE) == 0) ;
USART1->DR = val;
return 1;
}
void serial_writestr(const char* s) {
for (const char*ch = s; *ch; ch++) {
serial_writebyte(*ch);
}
}
After calling this initialization, we can start writing data to the serial port. Because the hardware doesn't have a FIFO buffer we will most likely loose output when just writing without checking if the last byte written is still waiting to be transfered to the output shift register. Therefor a busy loop ensures that the serial part is prepared to take an new output byte by waiting for the “transmit data register empty” bit to be set. As we don't use any flow control this should never stall longer than the time to transmit one character. After ensuring that the data register is empty we just place the new byte into the data register for the hardware to transmit out when ready.
For testing it’s often useful to be able to output strings. So serial_writestr
implements a simple wrapper to output null terminated strings.
setup_serial(19200);
serial_writestr("test\r\n");
Calling this code in mainFn
will yield a test string output just after reset.
For full code see here