1

I am working on a function for an STM32 Micro-controller that sends a string of a given length through a uart port. To handle uart communication, I have created a Serial class that has a transmission and receive buffer that are popped and transmitted in an interrupt handler. The function I am currently working on is actually an overload of a function that I wrote earlier that works. Below is the working function:

void Serial::sendString(char* str) {
// Writes a string to txBuffer. If Transmit interrupts are enabled, and
// the Data register is empty, the txBuffer will be popped into the DR to
// prime the interrupts.

__HAL_UART_DISABLE_IT(uart, UART_IT_TXE); // Keeps our spaghetti straightened out...

while (*str != '\0') { // While char is not a null terminator...
    txBuffer->push(*str); // Push first char into queue as we know it is valid
    str++; // Pointer goes to next char in string
}

 uint32_t isrflags   = READ_REG(uart->Instance->SR); // Reads the flags and control register
 //uint32_t cr1its     = READ_REG(uart->Instance->CR1); // Into variables

 // If the DR is empty and Transmission interrupts are disabled...
 if ((isrflags & USART_SR_TXE) != RESET) {
    uart->Instance->DR = txBuffer->pop(); // Reenable interrupts and prime the DR
    }

 __HAL_UART_ENABLE_IT(uart, UART_IT_TXE); // Alright, time to cook the pasta

}

The overload is the function I am having issues on. For some reason, the debugger shows that the variable "i" initializes with value "14", and won't increment when stepping with the debugger. In fact, the debugger won't allow me to step into the for loop at all. Here is the overload:

void Serial::sendString(char* str, unsigned int len) {
// Writes a string to txBuffer. If Transmit interrupts are enabled, and
// the Data register is empty, the txBuffer will be popped into the DR to
// prime the interrupts.
// Rather than being terminated by a null character, this method instead
// sends each char in an array of a specified length. Note that this overload
// MUST be used in any situation that a null terminator might appear in a char
// array!

__HAL_UART_DISABLE_IT(uart, UART_IT_TXE); // Keeps our spaghetti straightened out...

for (unsigned int i = 0; i < len; i++) { // While char is not a null terminator...
    txBuffer->push(str[i]); // Push first char into queue as we know it is valid
    //str++; // Pointer goes to next char in string
}

 uint32_t isrflags   = READ_REG(uart->Instance->SR); // Reads the flags and control register
// uint32_t cr1its     = READ_REG(uart->Instance->CR1); // Into variables

 // If the DR is empty...
 if ((isrflags & USART_SR_TXE) != RESET) {
    uart->Instance->DR = txBuffer->pop();
    }
 __HAL_UART_ENABLE_IT(uart, UART_IT_TXE); // Alright, time to cook the pasta

}

These functions are called within a terminal while loop in main. When debugging, the issues happens immediately; I am not able to run through the overload at all. My code appears to just hit a dead-stop at this location.

I have been able to run the overload successfully before. This bug only appeared when I was trying to address another bug in the function where the first character in the string was only getting transmitted half of the time. I set a breakpoint and set out to debug and now it won't work at all....

  • Update: I restarted my IDE, swapped out my board for another, flashed the code, and it magically started working. I am somewhat newer to firmware engineering, could anyone care to explain what might have been causing these strange bugs? They seem to be pretty small and specific to be a hardware issue..... – Jonathan Just Feb 01 '21 at 16:23
  • 1
    btw your code makes a very little sense. The interrupt handling has to look completely different. It "works" by accident and it will stop working when you write more code. – 0___________ Feb 01 '21 at 16:25
  • 1
    Could you elaborate? The board I am working with has an interrupt that triggers when the data register is emptied after a transmission. In my ISR I pop what is in the txBuffer into the DR for the byte to be transmitted. – Jonathan Just Feb 01 '21 at 16:30
  • 1
    You simply do it the incorrect way. See any STM32 decent IT UART code and spot the differences. It is too much to explain the comment. – 0___________ Feb 01 '21 at 16:38
  • Hey there, I looked into some STM32 interrupt transmission code. Is putting data into the DR within the send method improper? Thank you for your feedback – Jonathan Just Feb 01 '21 at 17:13
  • I think you do not understand what interrupts are for and how to use it. – 0___________ Feb 01 '21 at 18:19
  • Turn off any compiler optimizations and see if the debugger behaves like you expect. The optimizer can optimize away variables and loops and stepping through optimized code may not behave like you expect. – kkrambo Feb 01 '21 at 20:04
  • There pretty much only exist two valid ways of receiving a lot of data from UART in a complex program. Either some manner of ring buffer FIFO which is written to by an rx interrupt. Or preferably, by using DMA if available. For simpler applications, the third option to quickly poll the rx buffer might work too. – Lundin Feb 02 '21 at 13:47
  • Otherwise, a classic problem is where you have a UART peripheral with status registers that are cleared by reading them. A debugger showing those registers in a memory map might then accidentally destroy those registers. Typically you notice this when the code works fine free running, but not when single-stepping. Though in case of Eclipse, the debugger is so horrible that you can barely make it produce a memory map in the first place... – Lundin Feb 02 '21 at 13:56

1 Answers1

1

It sounds like the compiler has optimized away your loop control variables.

If you have a high level of optimization enabled then the loop could be unrolled or if you are calling the function from the same file where it is defined then it could be inlined allowing to eliminate the loop control variables.

You haven't actually described what the problem is that you are trying to debug. Rather than expecting the debug experience to be perfect just try to solve the problem that you have in spite of i always being 14!

Looking just at the code you have posted I can't see any great problem. There could of course be a bug in the code you don't show.

I disagree strongly with the unhelpful comment that this code is fundamentally rubbish. Turning interrupts on and off to access shared data is old fashioned and inefficient, but also simple to do and may be good enough for your purposes.

Writing the first byte to the UART in this function does save you the cost of one interrupt, but if you are writing a string of 20 bytes, do you really care whether 20 or 19 interrupts are required to do it? A good design principle is that you should only make the code more complicated if it gains you something that you don't want to be without.

Tom V
  • 1,320
  • 3
  • 10
  • Debuggers don't optimize code. As for the compiler, it will not optimize away code containing reads/writes to hardware peripheral registers. If that's happening, then it can only be because the whole register definition code is haywire, which sounds far-fetched. – Lundin Feb 02 '21 at 13:53
  • Just a typo ...corrected. – Tom V Feb 02 '21 at 14:45
  • Thanks for your feedback! The issue was I trying to solve WAS the issue with the variable getting stuck, I was working on implementing Dynamixel's Protocol 2.0 into a larger project when my code just magically stopped working. I'm not sure if the optimization was at fault because I was geting issues in my debug and release builds... The issue disappeared when I swapped out for another board and reconnected my ST-Link... So far I have not actually been able to reproduce the bug, but I will update this thread if I ever do encounter the issue again... – Jonathan Just Feb 03 '21 at 17:05
  • Can you accept the answer? thanks. – Tom V Feb 03 '21 at 18:05