1

I am trying to send a string "hello" continiously through UART1 from Atmega 328P to the BLUEPILL board. I am doing the reception by by interrupt. The problem is after few times it starts to give "ello" "hello" alternatively on my serial monitor, I am not able to figure out whats going wrong. BAUD RATE-9600 parity bit- NONE STop bit- 1

main.c:

The usart_IRQ is called here under "usart code begin here":

#include "main.h"
#include "uart.h"
UART_HandleTypeDef huart1;
char *readBuffer;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);

int main(void)
{
 HAL_Init();
 SystemClock_Config();
 MX_GPIO_Init();
  MX_USART1_UART_Init();

 /* USER CODE BEGIN 2 */
  __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);
    HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(USART1_IRQn);
  /* USER CODE END 2 */

The string received is stored in the "readBuffer" using the readstring and the printed to the serial monitor using the writestring forwhich i created a separate UART library.

while (1)
  {
    /* USER CODE END WHILE */

      readBuffer=serialReadString1();
      writeString1(readBuffer);
      HAL_Delay(500);

    /* USER CODE BEGIN 3 */
  }

USART1_IRQHandler

I have enabled the Global interrupt and called the received function there, and used the return because I dont want to use the HAL_irq:

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
 receivestring1();
 return;
  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

uart.c

for write string:

#include"main.h"
#include "uart.h"
#define RX_BUFFER_SIZE1 144

char rxBuffer1[RX_BUFFER_SIZE1];
uint16_t rx1ReadPos;


void sendChar1( uint8_t ch)
{
  while(!(USART1->SR & USART_SR_TXE));
  USART1->DR = ch;
}


void writeString1(const char c[])
{
    uint16_t i = 0;
        for (i = 0; i<= strlen(c); i++)
        {
            sendChar1(c[i]);
        }
        memset(rxBuffer1,0,RX_BUFFER_SIZE1);
        rx1ReadPos = 0;
}

for read string:

 unsigned char receivedChar1(void)
    {
       while(!(USART1->SR & USART_SR_RXNE));//check whether any tx is going on
       return USART1->DR;
    }
    
    void receivestring1()
    {
        while(!(USART1->SR & USART_SR_RXNE));//check whether any tx is going on
        rxBuffer1[rx1ReadPos] = USART1->DR;
    
                if (rxBuffer1[rx1ReadPos] != '\n')
                {
                    rx1ReadPos++;
                    if (rx1ReadPos >= RX_BUFFER_SIZE1)
                    {
                        rx1ReadPos = 0;
                    }
                }
    }
    
    char* serialReadString1()
    {
        return rxBuffer1;
    }
Ax111_
  • 9
  • 4
  • What specifically event on UART is supposed to trigger UART IRQ? Your code is missing vital for understanding pieces. 1) UART setup. 2) Full main.c, because I'm kinda lost in terms of what calls what and where. Also, you could help make the code more readable by making proper tabs and stuff, it's easy when you just wrote it, but reading someone else's code with wrong tabs kinda hurts and takes much more time – Ilya May 19 '22 at 13:46
  • I have modified my code for better understanding. – Ax111_ May 20 '22 at 06:25
  • before we actually troubleshoot the receiver, have you connected the transmitter to PC and checked whether your transmitted sends everything correctly all the time? There is a slight chance that you're seeing "ello", because transmitted is sending "ello". Connect arduino MCU tx to pc (and leave it also connected to stm32, make it blink if you receive "ello"). Just make sure the problem is where it is. – Ilya May 20 '22 at 08:06
  • hello, i have tested the transmitter part, it is sending the string without any problem. so there might be something wrong with the receivers part. – Ax111_ May 20 '22 at 11:16
  • Do you mean you saw the very same message as "hello" on pc and "ello" on stm32? The very same one? – Ilya May 20 '22 at 12:08
  • yes, exactly @Ilya – Ax111_ May 20 '22 at 12:24
  • In receivestring1(), why do you need while(!(USART1->SR & USART_SR_RXNE));//check whether any tx is going on? The function is called by interrupt, which means there already IS new data there. You don't need that wait at all. Seems like a leftover from blocking uart implementation – Ilya May 20 '22 at 12:36
  • @Ilya I did change that too, but nothing seems to change in the reception, it is still showing "ello" – Ax111_ May 20 '22 at 13:06

1 Answers1

1

I can spot several potential issues in your code:

  1. You should not skip the call of HAL_UART_IRQHandler() in USART1_IRQHandler(). Each interrupt usually needs to be acknowledged (cleared) in either the interrupt controller (NVIC) and/or the respective peripheral unit (the UART in your case). If this is not done, you might end up with the interrupt handler being called again right away, although there has not been a real new event.

  2. The while(!(USART1->SR & USART_SR_RXNE)) at the start of receivedChar1() and receivestring1() should not be necessary, since the interrupt should only be generated if the RX is not empty. Your interrupt configuration looks correct, so this might be only necessary due to issue #1 (see above).

  3. The variables rxBuffer1and rx1ReadPos are manipulated both in receivestring1() and writeString1(). Since receivestring1() is called from USART1_IRQHandler() while writeString1() is called from main() this manipulation can happen virtually in parallel. This can become problematic for two reasons:

    • The compiler might not be aware, the content of rxBuffer1 might change at any (e.g. also while processing the for loop in writeString1()). This can be avoided by adding the volatile keyword to rxBuffer1, rx1ReadPos and also the argument of writeString1(). This way the compiler will re-evaluate strlen(c) on every pass of the for loop.
    • The code might still try to change rx1ReadPos at virtually the same time, e.g. when a new character is received by receivestring1() just in the moment before rx1ReadPos is reset to zero in writeString1(), which would lead to losing that character. This is called a race-condition and can only be avoided by either blocking interrupts for a limited time or by constructing the code in a way that makes sure, that parallel execution can not lead to data-loss. One possible solution could be the use of two separate buffers - one used for receiving and one used for writing.
  4. writeString1() is writing the characters via sendChar1() to the same UART1 that is receiving the data. This is not a principally okay but sending the data is thus using the same slow 9600 baud-rate. This makes it really probable, that writeString1() gets interrupted by USART1_IRQHandler(), i.e. that additional data is received. It might be better to use a different UART configured to a higher baud-rate for debug purposes.

I suppose that your "ello" problem is primarily caused by issue #3.

Addendum

To check whether the stm32 correctly receives the characters from the Atmega you could simplify your code by sending out each received character directly from the interrupt handler (avoiding the complexity of the asynchronous buffer handling). This could look like this:

int main(void)
{
  [...]
  while(1)
  {
    // everything is done in IRQ handler
  }
}

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
  unsigned char newChar;
  newChar = USART1->DR; // read received char
  USART1->DR = newChar; // send it out again right away

  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

Addendum #2

By request here is a sketch how the buffer handling could be improved:

char rxBuffer1[RX_BUFFER_SIZE1];
char rxBuffer2[RX_BUFFER_SIZE1];
char* volatile rxBufferPtr = rxBuffer1;
volatile bool SwitchBufferRequest = false;

void writeString1(const char buffer[])
{
    for (int i = 0; i<= strlen(buffer); i++)
    {
        sendChar1(buffer[i]);
    }
    memset(buffer,0,RX_BUFFER_SIZE1);
}

void receivestring1()
{
    static uint16_t rxBufferPos = 0;

    if (SwitchBufferRequest)
    {
        if (rxBufferPtr == rxBuffer1)
        {
            rxBufferPtr = rxBuffer2;
        }
        else
        {
            rxBufferPtr = rxBuffer1;
        }
        rxBufferPos = 0;
        SwitchBufferRequest = false;
    }
    
    rxBufferPtr[rxBufferPos] = USART1->DR;
    
    if (rxBufferPos < (RX_BUFFER_SIZE1 - 2)) // buffer full?
    {
        rxBufferPos++;
    }
}
    
char* serialReadString1()
{
    char* rxBufferWithData = rxBufferPtr;

    SwitchBufferRequest = true;
    while (SwitchBufferRequest == true) {;} // wait for buffer switch
    return rxBufferWithData;
}

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
  receivestring1();
  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

int main(void)
{
  [...]
  while (1)
  {
    /* USER CODE END WHILE */

      readBuffer=serialReadString1();
      writeString1(readBuffer);
      HAL_Delay(500);

    /* USER CODE BEGIN 3 */
  }
}

Note: This sketch tries to modify your code as little as possible. The key ideas are:

  1. Use two separate buffers (rxBuffer1 and rxBuffer2) -- one for receiving data and one for sending received data out again.
  2. Buffers are switched each time new data is needed in main()
  3. Buffer switching is requested in serialReadString1() via the flag SwitchBufferRequest. Buffer switching itself is done within the ISR to avoid any race conditions.
  4. Note the order of operations in serialReadString1(): First the pointer to the latest received data is copied in a separate variable before SwitchBufferRequest is set to true because rxBufferPtr might change at any time afterwards.
  5. writeString1() is only modified slightly by removing the reset of rxBufferPos (now done in ISR) and clearing the RX buffer which is not used by the RX ISR.
Blue
  • 820
  • 4
  • 17
  • considering the above points I made some changes. Firstly I removed the "return" under the IRQ handler. Secondly, I added delay(2) in the write function and the receive function. Everything ran smoothly for first 30 minutes, but after restarting, after nearly 1000 readings it came "helo" – Ax111_ May 23 '22 at 10:15
  • The `delay(2)` might reduce the chance that issue #3 takes effect but it does not solve the fundamental problems. You need to address these problems to solve your issue. – Blue May 25 '22 at 21:09
  • I have updated my answer with a code example that avoids the critical asynchronous buffer handling and instead echoes each received byte back to UART1 TX right away. – Blue May 25 '22 at 21:36
  • I did this today and it ran without skipping any character, meaning it is receiving the character correctly. – Ax111_ May 30 '22 at 12:04
  • Great. That proves that the communication works fine over the serial wire and your problems are caused by the firmware implementation. Next thing you should do is adding the `volatile` keyword to all variables shared by `main()` and ISR functions. Furthermore you should improve the buffer handling. Do you need another example code for that? – Blue May 30 '22 at 15:40
  • Yes, it would be really helpful if you give me one, on using separate buffers. Also, I have tested using the "volatile" keyword, but it gives at least one 'helo' after 30 minutes. – Ax111_ May 31 '22 at 06:02
  • I have updated my answer with a sketch of a "double buffer" solution. The code is not tested by me but I hope it will give you a basic idea. – Blue Jun 01 '22 at 20:27
  • I tested this and now the characters are appended after every 5 secs i.e, it gives "hellohello". – Ax111_ Jun 03 '22 at 05:57
  • There was a typo in `receivestring1()`: there should be an assignment `rxBufferPtr = rxBuffer2;` not a useless comparison `rxBufferPtr == rxBuffer2;`. Same is true for the `else` tree. Didn't the compile issue a warning there? I fixed the example code - please try again. – Blue Jun 03 '22 at 19:13
  • hello, I tested with this, it comes "lo", "hel" and "llo" after nearly 10 readings – Ax111_ Jun 06 '22 at 06:32
  • Hmm, strange. Are the characters missing or are we talking about timing (time-wise gaps in the output)? – Blue Jun 08 '22 at 20:40
  • seems like the characters are missing due timing – Ax111_ Jun 09 '22 at 11:09
  • Looks like the buffer switching does not work as intended. Please make sure, that your code matches my example. I have added `USART1_IRQHandler()` and `main()` to my Addendum #2, just to be sure on the content. Furthermore I have added a "safety" busy waiting loop to `serialReadString1()`, just to make sure that buffer switching is not requested multiple times in parallel. – Blue Jun 09 '22 at 19:45
  • yes I did add in the ISR before and now added the "safety" like you said, but this time it does not give "hello" instead it came like 'o' 'llo' 'o' 'llo' 'lo' - in this particular loop. – Ax111_ Jun 13 '22 at 05:31
  • I am afraid, I am out of ideas for the moment. You should fire up the debugger and have a look, what exactly happens during the buffer switching. Is the ISR alternating between the two buffers while receiving. Is the `writeString1()` function alternating the buffers as expected? – Blue Jun 20 '22 at 20:10
  • okay, Thank you, I am working out.. – Ax111_ Jun 21 '22 at 13:26