1 Section: Introducing the UART (page 236)
2 Section: Creating a polled UART driver (page 241)
3 Section: Queue-based driver (page 249)
4 Section: A buffer-based driver (page 255)
5 Section: Configuring DMA peripherals (page 263)
Chapter 10: bugs and clarifications
11/23/2021
Chapter 10: Drivers and ISRs (pg 235)
● Additional info, page 237
o The pin specifications for the board are defined in the board's user-manual
■ STM32 Nucleo-144 boards : User manual
o Page 32 shows the connectors
● Clarification, pg 239
o For step 5:
■ The book doesn't state where that code is. It's in initUart4Pins(), in the file:
● C:\projects\packtBookRTOS\BSP\UartQuickDirtyInit.c
■ initUart4Pins()is called from STM_UartInit(). Both functions are in that file.
o For step 6:
■ The book doesn't state where the function-call is. It's in STM_UartInit(), also in:
● C:\projects\packtBookRTOS\BSP\UartQuickDirtyInit.c
■ My notes:
● STM_UartInit() is called from all of the Chapter 10 example-programs in the directory:
o C:\projects\packtBookRTOS\Chapter_10\Src\
■ mainUartDMABuff.c
■ mainUartDMAStreamBuffer.c
■ mainUartDMAStreamBufferCont.c
■ mainUartInterruptBuff.c
■ mainUartInterruptQueue.c
■ mainUartPolled.c
■ Uart4Setup.c
o For step 9:
■ UartQuickDirtyInit.c is referenced, and it includes the two functions: initUart2Pins() and initUart4Pins().
● I was initially puzzled by those functions as I misread their names:
o For initUart2Pins(), the intended reading of the name is: "initialize UART-2's pins".
o How I initially read the name was: "initialize the UART to operate with 2 pins"
● Also, initUart2Pins() is misnamed as it initializes USART-2, not UART-2.
● Bug in the code (UartQuickDirtyInit.c), page 239
o There's a typo in this comment, in initUart2Pins():
■ //PD5 is USART2_RX PD6 is USART2_TX
o The comment should be:
■ //PD6 is USART2_RX PD5 is USART2_TX
● Bug in the code (Chapter_10.jdebug), page 241
o Each chapter's .jdebug file has the same bugs
o The bugs, and their solution, are described in the study-guide's web-page on Ozone.
● Bug in the code (mainUartPolled.c), page 241
o Problem:
■ When I ran the code, SystemView did not display the messages generated by the calls to SEGGER_SYSVIEW_PrintfHost().
o Analysis:
■ Context:
● For the .jdebug file, I had applied the fixes described above.
● The SystemView Recorder was started after the scheduler started.
■ One hypothesis is that the polling process was generating so many events that it was overwhelming the debugger and SystemView, when the SystemView Recorder was started.
o Solution:
■ I added code that allowed the SystemView Recorder to be started after the scheduler started, but before the polledUartReceive() task was run.
■ This code was added to the beginning of uartPrintOutTask():
RedLed.On();
while(!ReadPushButton());
■ How to run the program:
● Run the program on the board via Ozone.
● Start the SystemView Recorder after the red LED is on.
● After the Recorder is started, press the board's blue push-button
■ The messages from SEGGER_SYSVIEW_PrintfHost()now get displayed. However, SystemView also displays a message indicating overflow has occurred. That problem is described next.
● Bug in the code (mainUartPolled.c), page 241
o Problem:
■ To demonstrate the UARTs' data-transfer is working, each received byte is displayed using SEGGER_SYSVIEW_PrintfHost(). However, for many of the calls to SEGGER_SYSVIEW_PrintfHost(), the data is not displayed due to problems in SystemView.
■ When SystemView Recorder is run, SystemView displays a message stating that overflow has occurred, and that SystemView is unable to record all generated events (screen-shot below).
■ The problem is that, with the overflow, it's not possible to know if missing messages are due to the overflow, or due to UART-related errors. The messages don't demonstrate whether the UARTs' data-transfer is working.
o Background:
■ UART-4 sends the string defined in uart4Msg[] in Uart4Setup.c. The string's bytes are sent serially and continuously.
■ The bytes are received by the task polledUartReceive(), in mainUartPolled.c. The task uartPrintOutTask() writes each byte, one at a time, using SEGGER_SYSVIEW_PrintfHost().
o Solution:
■ One way to solve the problem is to collect stats on the data received. The stats are displayed infrequently to reduce the overflow, e.g., every second. The stats are:
● Counts of properly-received strings, i.e., the chars in uart4Msg[] are all received and in order.
● Counts of chars received that are not among the chars in uart4Msg[].
■ An implementation of the solution is here:
■ Running the implemented solution showed the bytes are being transferred with little or no data loss
o Additional info:
■ For fixing SystemView overflow, general techniques are presented in the study-guide's SystemView page.
● Clarification, page 241
o In uartPrintOutTask(), the bytes received are recorded using:
■ SEGGER_SYSVIEW_PrintfHost("%c", nextByte);
o For the text-string that is sent by the UART, it's null-terminator is also sent. When the null-terminator is displayed by SEGGER_SYSVIEW_PrintfHost(), just an empty string is shown on the SystemView app.
● Clarification, page 250-254
o There's a typo regarding the baud-rate used. The book says it's 9,600, but in the distributed version of mainUartInterruptQueue.c, the baud-rate is 256,400.
● Additional info, page 252
o This section describes how the address for USART2_IRQHandler() is put in the ISR vector-table.
o This link explains how the address for USART2_IRQHandler() is put in the ISR vector-table:
o The data-structures related to the ISR vector-table are:
■ The ISR vector-table is defined in:
● Drivers\CMSIS\Device\ST\STM32F7xx\Include\stm32f767xx.h
● There is a vector-table entry for USART2_IRQHandler()
● There are also linker entries for USART2_IRQHandler():
.weak USART2_IRQHandler
.thumb_set USART2_IRQHandler,Default_Handler
■ In the ISR vector-table, the index for USART2_IRQHandler() is the USART2 interrupt-number. The interrupt number is defined in:
● Drivers\CMSIS\Device\ST\STM32F7xx\Include\stm32f767xx.h:
USART2_IRQn = 38, /*!< USART2 global Interrupt
● Clarification, page 252
o This paragraph was hard to follow:
■ "The xHigherPriorityTaskWoken variable is initialized to false...."
o The FreeRTOS documentation clarified it for me. For xQueueSendFromISR(), the third parameter is pxHigherPriorityTaskWoken, and it's described as:
■ xQueueSendFromISR() will set *pxHigherPriorityTaskWoken to pdTRUE if sending to the queue caused a task to unblock, and the unblocked task has a priority higher than the currently running task. If xQueueSendFromISR() sets this value to pdTRUE then a context switch should be requested before the interrupt is exited.
● Additional info, page 254
o The app mainUartInterruptQueue.c consists of over 30 function calls in total, and many are system APIs. The following table shows the functions used, and what file and library provides them.
o Chapter 10 includes a good section on using those libraries. The section is, "Using third-party libraries (STM HAL)" on page 285.
o The app consists of three program files:
■ BSP\UartQuickDirtyInit.c
■ Chapter_10/Src/mainUartInterruptQueue.c
■ Chapter_10\Src\Uart4Setup.c
o The system functions used are from five libraries:
■ Arm
■ FreeRTOS
■ SEGGER SystemView
■ STMicroelectronics
■ The book
● The following files appear to be config-files that were copied from other libraries, and modified:
● BSP\Nucleo_F767ZI_Init.c
● Chapter_10\Inc\stm32f7xx_hal_conf.h
o See STM's "User Manual : Description of STM32F7 HAL and low-layer drivers"
Function/macro provider |
Function/macro |
Containing file |
Arm |
NVIC_EnableIRQ |
Drivers\CMSIS\Include\core_cm7.h |
Arm |
NVIC_SetPriority |
Drivers\CMSIS\Include\core_cm7.h |
Arm |
NVIC_SetPriorityGrouping |
Drivers\CMSIS\Include\core_cm7.h |
book |
HWInit |
BSP\Nucleo_F767ZI_Init.c |
book |
initUart2Pins |
BSP\UartQuickDirtyInit.c |
book |
initUart4Pins |
BSP\UartQuickDirtyInit.c |
book |
STM_UartInit |
BSP\UartQuickDirtyInit.c |
book |
startReceiveInt |
Chapter_10/Src/mainUartInterruptQueue.c |
book |
startUart4Traffic |
Chapter_10/Src/mainUartInterruptQueue.c |
book |
uartPrintOutTask |
Chapter_10/Src/mainUartInterruptQueue.c |
book |
USART2_IRQHandler |
Chapter_10/Src/mainUartInterruptQueue.c |
book |
SetupUart4ExternalSim |
Chapter_10\Src\Uart4Setup.c |
book |
uart4TxDmaSetup |
Chapter_10\Src\Uart4Setup.c |
book |
uart4TxDmaStartRepeat |
Chapter_10\Src\Uart4Setup.c |
FreeRTOS |
xQueueCreate |
Middleware\Third_Party\FreeRTOS\Source\include\queue.h |
FreeRTOS |
xQueueSendFromISR |
Middleware\Third_Party\FreeRTOS\Source\include\queue.h |
FreeRTOS |
xTimerStart |
Middleware\Third_Party\FreeRTOS\Source\include\timers.h |
FreeRTOS |
portYIELD_FROM_ISR |
Middleware\Third_Party\FreeRTOS\Source\portable\GCC\ARM_CM7\r0p1\portmacro.h |
FreeRTOS |
xQueueReceive |
Middleware\Third_Party\FreeRTOS\Source\queue.c |
FreeRTOS |
vTaskStartScheduler |
Middleware\Third_Party\FreeRTOS\Source\tasks.c |
FreeRTOS |
xTimerCreate |
Middleware\Third_Party\FreeRTOS\Source\timers.c |
SEGGER SystemView |
SEGGER_SYSVIEW_PrintfHost |
Middleware\Third_Party\SEGGER\SEGGER_SYSVIEW.c |
SEGGER SystemView |
SEGGER_SYSVIEW_RecordEnterISR |
Middleware\Third_Party\SEGGER\SEGGER_SYSVIEW.c |
SEGGER SystemView |
SEGGER_SYSVIEW_RecordExitISR |
Middleware\Third_Party\SEGGER\SEGGER_SYSVIEW.c |
SEGGER SystemView |
SEGGER_SYSVIEW_Conf |
Third_Party\SEGGER\SEGGER_SYSVIEW_Config_FreeRTOS.c |
STMicroelectronics/book |
assert_param |
Chapter_10\Inc\stm32f7xx_hal_conf.h |
STMicroelectronics |
HAL_RCC_DMA1_CLK_ENABLE |
Drivers\STM32F7xx_HAL_Driver\Inc\stm32f7xx_hal_rcc.h |
STMicroelectronics |
HAL_NVIC_EnableIRQ |
Drivers\STM32F7xx_HAL_Driver\Src\stm32f7xx_hal_cortex.c |
STMicroelectronics |
HAL_NVIC_SetPriority |
Drivers\STM32F7xx_HAL_Driver\Src\stm32f7xx_hal_cortex.c |
STMicroelectronics |
HAL_GPIO_Init |
Drivers\STM32F7xx_HAL_Driver\Src\stm32f7xx_hal_gpio.c |
STMicroelectronics |
HAL_UART_Init |
Drivers\STM32F7xx_HAL_Driver\Src\stm32f7xx_hal_uart.c |
● Additional info, page 254
o In running mainUartInterruptQueue.c, the SystemView app reports overflow.
o I attempted to prevent overflow by doing the following. However, overflow still occurred.
■ The baud-rate was reduced from 256,400 to 150 (in mainUartInterruptQueue.c).
● SystemView was used to measure throughput. For a specified baud-rate of 150, the actual baud-rate was 700 (bytes-received/second). This measurement's accuracy was not affected by the SystemView overflow.
■ SEGGER_SYSVIEW_RTT_BUFFER_SIZE was increased from 10,240 to 32,000, in SEGGER_SYSVIEW_Conf.h. (I assumed that extra memory is available, but I don't know if it is.)
■ In uartPrintOutTask(), code was added so SEGGER_SYSVIEW_PrintfHost() is issued once for every 2,000 receives.
o For fixing SystemView overflow, general techniques are presented in the study-guide's SystemView page.
● Additional info, page 254
o I ran some experiments to see if the actual baud-rate is the same as the specified baud-rate.
o Several baud-rates were tried using mainUartInterruptQueue.c. The program was modified to call SEGGER_SYSVIEW_PrintfHost() for every 2,000 characters received.
o The actual throughput was close to the specified baud-rate, for baud-rates 9,600 and 256,400. For a specified baud-rate of 150, the actual throughput was much more. It was assumed that 10 bits are sent for each character (includes start and stop bits).
Baud rate |
Characters/Sec |
Bits/Sec |
150 |
700 |
7,000 |
9,600 |
967 |
9,670 |
256,400 |
25,394 |
253,940 |
● Bug in the code (mainUartInterruptBuff.c), page 256
o There are bugs in the interactions between the interrupt-handler and the task uartPrintOutTask(). This includes bugs in the mutual-exclusion scheme for shared data, and a bug in the sequencing of events.
o Problem #1:
■ startReceiveInt() alters data that that can be concurrently referenced and altered by the interrupt handler and by the USART. These concurrent operations are problematic and appear to have one or more bugs. At the least, it is difficult to determine that these concurrent operations do not have bugs.
■ Two variables are used to specify the state of the byte-receive process. They are both set in startReceiveInt(), and setting the pair of them should be an atomic operation (relative to the interrupt handler). However, it's not:
rxInProgress = true;
rxItr = 0;
● The interrupt handler could be run after the first assignment statement, and before the second one. A potential consequence is that rxItr can be incremented in the interrupt handler, then set to zero in startReceiveInt(). This can cause a byte to be lost.
● If xSemaphoreTake() times-out, there can potentially be an interrupt-handler call that sets rxItr to expectedLen+1, and rxData[expectedLen] to a non-zero value. This call would occur after rxInProgress is set to true, and before rxItr is set to zero. It would add an extra unintended character to the output string. That extra character will appear in all future calls to SEGGER_SYSVIEW_Print((char*)rxData).
● Each of the assignment-statements themselves might not be atomic. If an assignment is partially completed when an interrupt occurs, the altered variable's value would effectively be undefined then.
■ In startReceiveInt(), four statements are used to set-up the USART and interrupt-handler. There might be two problems with those statements, though it's speculation:
● The USART and interrupt-handler is set-up the first time startReceiveInt() is called. However, those four statements are run again for each iteration of the while{} loop. It's not clear what the effects are of running those instructions for an interrupt-handler and USART that is already set-up and running.
● Also, each of the four statements might not be atomic. However, they can be run while the USART is receiving data. For each statement, if altered data is partially altered when an interrupt occurs, the data's value would effectively be undefined.
o Problem #2:
■ The interrupt-handler can issue xSemaphoreGiveFromISR() between when xSemaphoreTake() times-out, and when startReceiveInt() is run. If the full buffer was received, the message issued would be incorrect. The message would state zero bytes were received.
o Solution:
■ I didn't see an easy fix for these problems, and it appeared refactoring was needed.
■ A fixed version of mainUartInterruptBuff.c is here:
■ The fix uses two semaphores, instead of one, to control the interactions between the interrupt-handler and the task uardtPrintOutTask().
● Bug in the code (mainUartInterruptBuff.c), page 258
o Problem:
■ SEGGER_SYSVIEW_Print((char*)rxData) does not display the whole buffer if the buffer contains a null-terminator before the end of the buffer.
■ For example, the string transmitted by UART4 could be: "data from uart4\0"
■ When the transmitted-string is received by USART2_IRQHandler(), the first character put in the buffer might not be first byte in the transmitted-string. For example the full buffer could be like this:
● " uart4\0data from \0"
■ The string provided to SEGGER_SYSVIEW_Print() is displayed in the SystemView app. However, the app will only display characters up to the first null-terminator in the provided string.
o Solution:
■ One solution is for USART2_IRQHandler() to translate received zero-bytes to a printable character that is not in the transmitted string, e.g., "!"
■ This is done in the fixed version of mainUartInterruptBuff.c, cited earlier.
● Clarification, page 261
o The version of mainUartInterruptBuff.c from the repo is different than what is shown in the book. The code from the repo includes the task wastefulTask().
o When running under SystemView, wastefulTask() shows the effect of xHigherPriorityTaskWoken on the scheduler.
● Additional info, page 263
o Some helpful DMA references:
o Reference manual : STM32F76xxx and STM32F77xxx advanced Arm®-based 32-bit MCUs
■ Section 8 Direct memory access controller (DMA)
o Blog-articles:
■ "STM32 DMA Cheat Sheet"
● https://adammunich.com/stm32-dma-cheat-sheet/
● The article starts with, "STMicro’s documentation about the subject is some of the most terse documentation I’ve ever seen in the business!"
■ "STM32F2xx DMA Controllers – Frank's Random Wanderings"
● https://blog.frankvh.com/2011/08/18/stm32f2xx-dma-controllers/
● Additional info, page 266
o The whole DMA set-up has to be performed after each full DMA transfer (here, it's 16 bytes).
o startReceiveDMA() sets-up the DMA. startReceiveDMA() is called for each 16 bytes received via the DMA. (In uartPrintOutTask(), startReceiveDMA() is called for each iteration of the while-loop.) After the first call to startReceiveDMA(), could it be that subsequent calls don't have to do all of the DMA set-up, and just part of the set-up is needed, e.g., just issue HAL_DMA_Start()?
o From experimenting, it appears that once the 16-byte DMA transfer is done, all of the DMA set-up has to be performed again, in order to get more data.
o A blog article confirms this: If circular buffer mode is not enabled, you must manually reload the source address, destination address, and number of bytes to be transferred, into the DMA stream’s configuration registers prior to re-launching the stream. The DMA controller uses these registers for the address pointers and byte counter, with no separate mechanism to reload them in normal usage. This is pretty weak, as it wastes a bunch of clock cycles for short transfers.
■ https://adammunich.com/stm32-dma-cheat-sheet/
● Clarification, page 268
o Apparently the baud rate is in bits/sec, and there's 10 bits sent per byte (8 data bits and stop/start bits).
o The time to send 16 bytes can be calculated mathematically:
■ At 9600 bits/sec, 16 bytes is sent in 0.0167 sec
■ At 256,400 bits/sec, 16 bytes is sent in 624 micro-sec
o These calculated periods are the same as those shown empirically on page 268
● Added info, page 269
o In the FreeRTOS docs, stream-buffers are described here:
■ https://www.freertos.org/RTOS-stream-message-buffers.html
● Clarification, page 270
o The xStreamBuffer* functions are FreeRTOS APIs, e.g.,
■ xStreamBufferCreate()
■ xStreamBufferReceive()
■ xStreamBufferSendFromISR()
● Bug in the code (mainUartDMAStreamBufferCont.c), page 270f
o Problem #1:
■ Typo in main(): rxStream = xStreamBufferCreate( 100, 1);
o Solution #1:
■ In the book it is: rxStream = xStreamBufferCreate( 100, 2);
o Problem #2:
■ Buffer-overflow in uartPrintOutTask(), in the memset():
● memset(rxBufferedData, 0, 20);
o Solution #2:
■ memset(rxBufferedData, 0, maxBytesReceived);
● Additional info, page 274
o How to set-up the second DMA-buffer is discussed in the book. The code for it is in startCircularReceiveDMA():
DMA1_Stream5->M1AR = (uint32_t) rxData2;
o However, setting-up the first DMA-buffer isn't discussed, specifically. The code-comments state that the first buffer is specified in DMA1_Stream5->M0AR.
o DMA1_Stream5->M0AR is set by HAL_DMA_Start() (it also sets the buffers' length).
o HAL_DMA_Start() is called in startCircularReceiveDMA():
HAL_DMA_Start(&usart2DmaRx, (uint32_t)&(USART2->RDR), (uint32_t)rxData1, RX_BUFF_LEN)
● Additional info, page 275
o DMA's double-buffer-mode is also described here:
■ Reference manual : STM32F76xxx and STM32F77xxx advanced Arm®-based 32-bit MCUs
● Section 8 Direct memory access controller (DMA)
o Section 8.3.10 Double-buffer mode
■ Blog post: "STM32F2xx DMA Controllers – Frank's Random Wanderings"
● https://blog.frankvh.com/2011/08/18/stm32f2xx-dma-controllers/
● Additional info, page 282
o In the FreeRTOS docs, message-buffers are described here: