Как остановить отправку последнего полученного символа функции UART echo?
Я разработал код для 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, функция выходит из цикла мигания светодиода, но когда прием прекращается и данных больше нет, она не возвращает состояние мигания и продолжает отправлять последний полученный символ. Как решить эту проблему?
@R1S8K, 👍0
1 ответ
Лучший ответ:
В этом коде очень много проблем. Самый фундаментальный недостаток заключается в том, что он использует линейные буферы, тогда как вам следует использовать круговые буферы. буферы вместо этого. Я вернусь к этому позже. Теперь, если мы посмотрим подробности в заголовочном файле:
#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... В этот момент ваш буфер становится кольцевой буфер. Это не так уж важно, но вы должны прочтите информацию по теме, прежде чем пытаться ее реализовать.
Обратите внимание, что та же проблема касается и приемного буфера: оба буфера следует сделать круглым.
- Контейнерная программа Arduino Timer0
- Возможно ли, что ATmega сгорел из-за плохой проводки?
- В чем разница между библиотеками Software Serial? Какая из них совместима с Arduino Nano?
- В чем разница/связь между Arduino и AVR?
- Почему необходимо использовать ключевое слово volatile для глобальных переменных при обработке прерываний в ардуино?
- Как преобразовать скетч примера Arduino в полный проект C++?
- Как использовать Serial.setTimeout()
- Аппаратная последовательная библиотека Arduino с поддержкой управления потоком rts/cts