Search code examples
cinterruptstm32uart

STM32 UART Interrupt does not work after overflow


I am currently using SIM800C module with STM32F103C8 microcontroller. As SIM800C is configured using AT commands, I am using UART to communicate. Currently, I have configured USART3 at baud of 9600 in interrupt mode (I tried to use simple receive, but it only receives the first character. I think it is because the answer is too fast or smth). So, here is the code, where I send AT command:

static void send_at(char *at_cmd, char *reply, uint16_t timeout)
{

    offset = strlen(at_cmd) + 1; // offset of reply of SIM800
    len = strlen(reply) + 2 + offset; // overall reply length

    rx_buffer = (char *) calloc(len, sizeof(char));
    expected_ans = (char *) calloc(len, sizeof(char));

    sprintf(expected_ans, "%s\r\n", reply);

    HAL_UART_Receive_IT(sim_uart, (uint8_t *) rx_buffer, len); // \r\r\n
    HAL_Delay(10);
    HAL_UART_Transmit(sim_uart, (uint8_t *) at_cmd, strlen(at_cmd), timeout);
}

For "AT\r\n" command, SIM800 returns "AT\r\nOK\r\n", so, offset is pointer where only reply starts (in this case 4, which skips "AT\r\n" part). So, when I receive answer, I check it in callback function:


void HAL_UART_RxCpltCallback(UART_HandleTypeDef *uart)
{
    rx_buffer[len] = 0; // truncate garbage data

    if (!strcmp(rx_buffer + offset, expected_ans))
        ++state;
    else
        state = 0;
}

state is variable, which shows which AT command to send (apparently, when state increases, next AT command will be executed). Everything works fine, but... if reply overflows, everything is over.

For example, for AT command "AT+SAPBR=1,1\r\n", which enables GPRS, there can be an answer "ERROR" instead of "OK". As, I already called RX interrupt for the length of "OK", after "ER" interrupt fires. However, for every other interrupt call after this, interrupt does not fire, even if answer is "OK". Basically, reply "ERROR" overflows and next interrupts are disabled. I do not know what is the problem and why this happens. Is there any way to know the size of incoming data beforehand or to flush buffer in callback function?

Here is my main function:

void sim800_init(UART_HandleTypeDef *uart)
{
    sim_uart = uart;

    while(state < 8)
    {
        switch (state)
        {
            case 0:
                send_at(AT, "OK", 1000);
                break;
            case 1:
                send_at(AT_SAPBR_CONTYPE, "OK", 5000);
                break;
            case 2:
                send_at(AT_SAPBR_SET_APN, "OK", 5000);
                break;
            case 3:
                send_at(AT_ENABLE_GPRS, "OK", 5000);
                break;
            case 4:
                send_at(AT_HTTP_INIT, "OK", 10000);
                break;
            case 5:
                send_at(AT_HTTPPARA_CID, "OK", 10000);
                break;
            case 6:
                send_at(AT_HTTPPARA_URL, "OK", 5000);
                break;
            case 7:
                send_at(AT_HTTPPARA_CONTENT, "OK", 5000);
                break;
        }

        HAL_Delay(1000);
    }
}

Solution

  • Easy answer: use a circular buffer and receive one character a time. You can reload the reception in interrupt but it is not really clean. The interrupt updates the circular buffer head pointer. Main code just read the head pointer and use/update tail pointer to do the parsing. Just parse your circular buffer and search for "/r/n" if it is present just split there, parse and commute state if needed. Remenber that reception should be continuous not only when you expect data coming. Sooner or later you'll need to process unsolicited messages too. Also remember to use compiler barrier or at least "volatile attribute" on variable both written/read on interrupt and main cycle.

    Long answer hint: Use dma in circular mode, idle halffull and full dma interrupts will help you trigger parsing in main cycle.

    For your question:

    Is there any way to know the size of incoming data beforehand or to flush buffer in callback function?

    No you cannot. UARTS has 1 byte hardware buffer. If you leave it filled without reading it will trigger an overrun error on next byte reception and the whole reading process will HANG.