Как остановить отправку последнего полученного символа функции UART echo?

avr uart loop isr

Я разработал код для ISR на базе UART с линейными буферами. Проблема в том, что он продолжает отправлять последний полученный символ в Arduino IDE.

Вот код приложения:

#include "uart_new.h"

void setup() {
uart_init_new(103);
DDRB = 0xff;
}

void loop() {
uint16_t c;
c = get_char();
if (c & NO_DATA)
{
  PORTB |= (1<<PB5);
  _delay_ms(500);
  PORTB &= ~(1<<PB5);
  _delay_ms(500);
}
else
{
  put_char(c);
}
}

Заголовочный файл:

#ifndef uart_new_H_
#define uart_new_H_

#define NO_DATA     0x0100
#define Buf_OF      0x0200
#define FR_Er       0x0400
#define DOR_Er      0x0800
#define PARTY_Er    0x1000

#define buf_s 32
#define buf_mask Buf_s - 1
#define baud_rate 9600

void uart_init_new(uint16_t ubrr);
void put_char(uint8_t data);
uint16_t get_char(void);
void put_s(uint8_t *str);

#endif // uart_new_H_

Исходный код:

#include <avr/io.h>
#include <Arduino.h>
#include "uart_new.h"

/*******************************************
If you want to prevent reading a string
backwards then you need FIFO implementation
*******************************************/

static volatile uint8_t rx_buf[32],tx_buf[32];
static volatile uint8_t rx_p,tx_p,rx_cnt,tx_cnt;
static volatile uint8_t rx_errors,tx_sBuf,rx_sBuf;

void uart_init_new(uint16_t ubrr)
{
    rx_p=0,tx_p=0,rx_cnt=0,tx_cnt=0;
    UBRR0H = (uint8_t)(ubrr>>8);
    UBRR0L = (uint8_t)ubrr;
    UCSR0B = (1<<RXEN0)|(1<<TXEN0)|(1<<RXCIE0); //Enable Rx and Tx & Receive interrupt
    UCSR0C = (1<<USBS0)|(3<<UCSZ00);            //Set frame format: 8data, 2stop bit
}

uint16_t get_char(void)
{
    if (rx_cnt == 0)
        return NO_DATA;
    else if (rx_p < rx_cnt)
        return rx_buf[rx_p++];
}

void put_char(uint8_t data)
{
    if (tx_cnt < 32)                        // start count the input data
        tx_buf[tx_cnt++] = data;            // 1st input location tx_cnt

        UCSR0B |= (1<<UDRIE0);
}

void put_s(uint8_t *str)
{
    while (*str)
        put_char(*str++);
}

ISR(USART_RX_vect)
{
    if (rx_cnt < 32)                        // start count the received data
        rx_buf[rx_cnt++]=UDR0;              // 1st output location rx_cnt
    else
        rx_cnt = 0;                         // when the counter reached max value, reset it, overwrite everything

if (!(UCSR0A & (1<<RXC0)))
        UCSR0B &= ~(1<<UDRIE0);
}
ISR(USART_UDRE_vect)
{
    uint8_t i;
    if (tx_p < tx_cnt)                      // check if pointer < counted data
        UDR0 = tx_buf[tx_p++];
        else
        {
            for (i=0;i<tx_cnt;i++)
                tx_buf[i]=0;
            tx_p = 0;                       // when the pointer reach max value, reset it
            tx_cnt= 0;                      // also reset counter for new transmission
            UCSR0B &= ~(1<<UDRIE0);         // disable empty data register interrupt
        }
}

Если получен символ, то выполняется ISR, функция выходит из цикла мигания светодиода, но когда прием прекращается и данных больше нет, она не возвращает состояние мигания и продолжает отправлять последний полученный символ. Как решить эту проблему?

, 👍0


1 ответ


Лучший ответ:

2

В этом коде очень много проблем. Самый фундаментальный недостаток заключается в том, что он использует линейные буферы, тогда как вам следует использовать круговые буферы. буферы вместо этого. Я вернусь к этому позже. Теперь, если мы посмотрим подробности в заголовочном файле:

#define Buf_OF      0x0200

Этот и несколько других макросов не служат никакой цели. Не помещайте элементы «TODO» в коде: поместите их в файл TODO или в виде комментариев. Стремитесь всегда ваш код должен быть в чистом состоянии. Тем более, когда вы размещаете его в открытом доступе место.

#define buf_mask Buf_s - 1

Предположительно, это предназначено для использования в качестве битовой маски, но это не так. работают надежно. Например, если вы попытаетесь дополнить маску как ~buf_mask, препроцессор расширит это до ~Buf_s - 1, и компилятор интерпретирует это как (~Buf_s) - 1, что не то, что вы хотите. Вы всегда должны заключать в скобки любое выражение, которое вы используете в макросе, как

#define buf_mask (Buf_s - 1)

Позже,

void put_s(uint8_t *str);

Если вы хотите иметь возможность делать вызовы типа put_s("Hello!");, то вам следует объявить функцию как:

void put_s(const char *str);

Теперь, в реализации, у вас есть:

static volatile uint8_t rx_errors,tx_sBuf,rx_sBuf;

Все это не используется. Удалите их. Опять же, это должно быть программа, а не список дел.

Позже,

uint16_t get_char(void)
{
    if (rx_cnt == 0)
        return NO_DATA;
    else if (rx_p < rx_cnt)
        return rx_buf[rx_p++];
}

Компилятор должен был предупредить вас об этом: что, если оба условия ложны? В этом случае функция вернется, достигнув своего конца, без указанного возвращаемого значения. Это неопределенное поведение. Вы должны вероятно, вернет NO_DATA в этом случае. Но тогда это то, что вы должны возвращать каждый раз, когда вы не возвращаете допустимые данные, поэтому функция может можно просто записать как:

uint16_t get_char(void)
{
    if (rx_p < rx_cnt)
        return rx_buf[rx_p++];
    else
        return NO_DATA;
}

Позже, в ISR(USART_RX_vect),

if (rx_cnt < 32)
    rx_buf[rx_cnt++]=UDR0;
else
    rx_cnt = 0;

Если вы установите rx_cnt на ноль без обновления rx_p, то последующее символы, которые вы поместите в буфер, будут проигнорированы get_char() пока вы не заполните буфер до rx_p. Таким образом вы, по-видимому, хотите также установить rx_p на ноль. И если вы сделаете это в начале, если ISR, вы гарантируете, что по крайней мере текущий символ не будет потерян:

// когда счетчик достигает максимального значения, сбрасываем буфер
if (rx_cnt >= 32) {
    rx_p = 0;
    rx_cnt = 0;
}
rx_buf[rx_cnt++]=UDR0;

Далее в ISR:

if (!(UCSR0A & (1<<RXC0)))
    UCSR0B &= ~(1<<UDRIE0);

Это не имеет смысла: если у вас нет ожидающего персонажа в получении зарегистрироваться (чего вам не следовало делать, так как вы только что это прочитали) регистр), затем вы отключаете ISR, который обслуживает передатчик. передатчик и приемник должны иметь возможность работать независимо: удалите эти строки.

Затем в ISR(USART_UDRE_vect):

for (i=0;i<tx_cnt;i++)
    tx_buf[i]=0;

Это бесполезно. Если вы правильно обращаетесь со своими буферами, вы должны никогда не обращайтесь к неинициализированным данным в них. Таким образом, вы никогда не прочтете нули вы тут пишете. Опять же, удалите эти строки.

Если вы реализуете все вышеперечисленные исправления, вы должны получить код, который «Вроде» работает. Но есть еще пара проблем:

Условия гонки

Если get_char() прерывается ISR(USART_RX_vect), вы можете получить то, что известно как «состояние гонки», которое может привести к непоследовательным поведение или повреждение данных. Например, тестирование rx_p<rx_cnt не атомарно: ЦП должен прочитать rx_cnt из ОЗУ, затем прочитать rx_p, затем сравните их. Если прерывание срабатывает между двумя чтениями памяти, ISR может установить оба значения на ноль, тогда тест сравнит старые rx_cnt с новым rx_p (который теперь равен нулю) и неверно предполагают необходимо вернуть действительные данные.

Есть и другие места, где это может вам помешать. Например, rx_p++ — это не атомарно, это последовательность чтения-изменения-записи, которая может очень хорошо быть прерванным в середине. И есть похожие проблемы, если put_char() прерывается ISR(USART_UDRE_vect).

Для решения этих проблем необходимо отключить прерывания, пока вы находитесь в «критические разделы», которые обращаются к данным, совместно используемым с ISR. Это может быть сделано с помощью пар noInterrupts()/interrupts() или, если вы предпочитаете Стиль avr-libc с атомарно выполняемыми блоками кода. Для пример:

#include <util/atomic.h>

uint16_t get_char(void)
{
    uint16_t result = NO_DATA;
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
        if (rx_p < rx_cnt)
            result = rx_buf[rx_p++];
    }
    return result;
}

Аналогичная защита необходима в put_char().

Линейные буферы

Это главный недостаток, потому что он потребует от вас переосмысления значительная часть вашего кода. Представьте, что может случиться, если UART передатчик немного отстает от вашей программы, вызывающей put_char(). Скажем вы достигаете tx_p = 30 и tx_cnt = 32. Затем ваш буфер имеет 30 «пустых» ячеек (от 0 до 29 свободное место, т.е. что данные уже отправлены) и две ячейки с байтами, ожидающими отправки отправлено (в позициях 30 и 31). Если вы сейчас вызовете put_char() для отправки дополнительный байт, этот байт будет потерян (потому что tx_cnt<32 имеет значение false). Это неприемлемо: вы не должны терять данные, когда буфер почти заполнен пусто!

Решение состоит в том, чтобы поместить дополнительный байт в позицию 0, а следующий — в позиция 1 и т. д. Но это создает сложность: теперь ожидающие байты находятся в позициях 30, 31, 0, 1... В этот момент ваш буфер становится кольцевой буфер. Это не так уж важно, но вы должны прочтите информацию по теме, прежде чем пытаться ее реализовать.

Обратите внимание, что та же проблема касается и приемного буфера: оба буфера следует сделать круглым.

,