AVR ATmega продолжает сбрасываться при использовании printf перед основным циклом
Я занимаюсь разработкой приложения на C с использованием avr-libc на микроконтроллере AVR ATmega328P. Поскольку у меня нет отладчика ICE для него, я следовал этим инструкциям и этому учебнику для создания stdio.h
такие функции, как printf
возможность использовать аппаратный UART как stdout
,
Это работает, и я вижу вывод на терминале ПК, подключенном к моей целевой плате, но странная вещь: когда у меня есть только один printf
на основной, но до основного цикла что-то вызывает сброс процессора, а если у меня есть printf
только внутри основного цикла или перед основным циклом И внутри цикла он работает нормально. Что-то вроде этого:
#include <stdio.h>
/* stream definitions for UART input/output */
FILE uart_output = FDEV_SETUP_STREAM(uart_drv_send_byte, NULL, _FDEV_SETUP_WRITE);
FILE uart_input = FDEV_SETUP_STREAM(NULL, uart_drv_read_byte, _FDEV_SETUP_READ);
int main() {
/* Definition of stdout and stdin */
stdout = &uart_output;
stdin = &uart_input;
/* Configures Timer1 for generating a compare interrupt each 1ms (1kHz) */
timer_init()
/* UART initialization */
uart_drv_start(UBRRH_VALUE, UBRRL_VALUE, USE_2X, &PORTB, 2);
/* Sets the sleep mode to idle */
set_sleep_mode(SLEEP_MODE_IDLE);
printf("START ");
/* main loop */
while(1) {
printf("LOOP ");
/* Sleeps so the main loop iterates only on interrupts (avoids busy loop) */
sleep_mode();
}
}
Код выше производит следующий вывод:
START LOOP LOOP LOOP LOOP LOOP LOOP ... LOOP
что ожидается. Если мы прокомментируем printf("START ")
Строка это производит это:
LOOP LOOP LOOP LOOP LOOP LOOP LOOP ... LOOP
что тоже хорошо. Проблема в том, что если у меня нет printf
внутри while
цикл, это выглядит так:
START START START START START START ... START
Это ясно показывает, что процессор перезагружается, так как ожидаемый результат будет только один START
и ничего больше, пока бесконечный цикл продолжает пробуждаться только при прерываниях таймера 1 кГц. Почему это происходит? Я должен подчеркнуть, что не настроен сторожевой таймер (если был, то случаи, когда только LOOP
печатается будет прерван новым START
также).
Мониторинг выполнения с помощью выводов GPIO
Чтобы попытаться понять ситуацию, я включил и выключил контакты GPIO вокруг проблемных print("START ")
а также sleep_mode
в основном цикле:
int main() {
/* Irrelevant parts suppressed... */
GPIO1_ON;
printf("START ");
GPIO1_OFF;
/* Main loop */
while(1) {
/* Sleeps so the main loop iterates only on interrupts (avoids busy loop) */
GPIO2_ON;
sleep_mode();
GPIO2_OFF;
}
}
Оказалось, что GPIO1 остается включенным в течение 132 мкс (printf("START ")
время вызова), а затем ВЫКЛ на 6,6 мс - примерно время передачи шести символов со скоростью 9600 бит / с - и GPIO2 переключается 12 раз (шесть раз по два прерывания: прерывание, готовое к передаче UART, и пустое UART- прерывание регистра данных), показывающее, что режим сна активен в течение еще 1,4 мс, прежде чем GPIO1 снова включается, что указывает на новый printf("START ")
- следовательно, после сброса. Мне, вероятно, придется проверить код UART, но я почти уверен, что версия UART без прерываний также показывает ту же проблему, и это также не объясняет, почему наличие printf
внутри основного цикла работает нормально, без сброса (я ожидаю, что сброс произойдет в любом случае, если код UART будет неисправен).
(Решено!): Для полноты код инициализации и передачи UART указан ниже **
Это была моя первая попытка написания драйвера UART, управляемого прерыванием, для AVR, но тот, который можно было использовать либо на RS-232, либо на RS-485, который требует активации вывода TX_ENABLE при передаче данных. Оказалось, что, поскольку мне пришлось сделать код пригодным для использования либо на ATmega328P, либо на ATmega644, векторы прерываний имеют разные имена, поэтому я использовал #define TX_VECTOR
принять правильное имя в соответствии с используемым процессором. В процессе создания и тестирования драйвера выбор "TX_VECTOR" для пустого прерывания данных UDRE в конечном итоге скрыл тот факт, что я не определил USART0_TX_vect
пока (это было в процессе разработки, мне может даже не понадобиться оба в любом случае...)
Прямо сейчас я только что определил пустую подпрограмму обработки прерываний (ISR) для USART0_TX_vect
и вещь больше не сбрасывается, показывая, что @PeterGibson прибил это прямо. Большое спасибо!
// Interrupt vectors for Atmega328P
#if defined(__AVR_ATmega328P__)
#define RX_VECTOR USART_RX_vect
#define TX_VECTOR USART_UDRE_vect
// Interrupt vectors for Atmega644
#elif defined(__AVR_ATmega644P__)
#define RX_VECTOR USART0_RX_vect
#define TX_VECTOR USART0_UDRE_vect
#endif
ISR(TX_VECTOR)
{
uint8_t byte;
if (!ringbuffer_read_byte(&txrb, &byte)) {
/* If RS-485 is enabled, sets TX_ENABLE high */
if (TX_ENABLE_PORT)
*TX_ENABLE_PORT |= _BV(TX_ENABLE_PIN);
UDR0 = byte;
}
else {
/* No more chars to be read from ringbuffer, disables empty
* data register interrupt */
UCSR0B &= ~_BV(UDRIE0);
}
/* If RS-485 mode is on and the interrupt was called with TXC0 set it
* means transmission is over. TX_ENABLED should be cleared. */
if ((TX_ENABLE_PORT) && (UCSR0A & _BV(TXC0) & _BV(UDR0))) {
*TX_ENABLE_PORT &= ~_BV(TX_ENABLE_PIN);
UCSR0B &= ~_BV(UDRIE0);
}
}
void uart_drv_start(uint8_t ubrrh, uint8_t ubrrl, uint8_t use2x,
volatile uint8_t* rs485_tx_enable_io_port,
uint8_t rs485_tx_enable_io_pin)
{
/* Initializes TX and RX ring buffers */
ringbuffer_init(&txrb, &tx_buffer[0], UART_TX_BUFSIZE);
ringbuffer_init(&rxrb, &rx_buffer[0], UART_RX_BUFSIZE);
/* Disables UART */
UCSR0B = 0x00;
/* Initializes baud rate */
UBRR0H = ubrrh;
UBRR0L = ubrrl;
if (use2x)
UCSR0A |= _BV(U2X0);
else
UCSR0A &= ~_BV(U2X0);
/* Configures async 8N1 operation */
UCSR0C = _BV(UCSZ00) | _BV(UCSZ01);
/* If a port was specified for a pin to be used as a RS-485 driver TX_ENABLE,
* configures the pin as output and enables the TX data register empty
* interrupt so it gets disabled in the end of transmission */
if (rs485_tx_enable_io_port) {
TX_ENABLE_PORT = rs485_tx_enable_io_port;
TX_ENABLE_PIN = rs485_tx_enable_io_pin;
/* Configures the RS-485 driver as an output (on the datasheet the data
* direction register is always on the byte preceding the I/O port addr) */
*(TX_ENABLE_PORT-1) |= _BV(TX_ENABLE_PIN);
/* Clears TX_ENABLE pin (active high) */
*TX_ENABLE_PORT &= ~_BV(TX_ENABLE_PIN);
/* Enables end of transmission interrupt */
UCSR0B = _BV(TXCIE0);
}
/* Enables receptor, transmitter and RX complete interrupts */
UCSR0B |= _BV(RXEN0) | _BV(TXEN0) | _BV(RXCIE0);
}
ФИКСИРОВАННЫЙ КОД УАРТА (СЕЙЧАС РАБОТАЕТ 100%!)
Чтобы помочь любому, кто заинтересован или разрабатывает аналогичный драйвер UART, управляемый прерываниями, для AVR ATmega, здесь приведен код с указанными выше проблемами, исправленный и протестированный. Спасибо всем, кто помог мне определить проблему с отсутствующим ISR!
// Interrupt vectors for Atmega328P
#if defined(__AVR_ATmega328P__)
#define RX_BYTE_AVAILABLE USART_RX_vect
#define TX_FRAME_ENDED USART_TX_vect
#define TX_DATA_REGISTER_EMPTY USART_UDRE_vect
// Interrupt vectors for Atmega644
#elif defined(__AVR_ATmega644P__)
#define RX_BYTE_AVAILABLE USART0_RX_vect
#define TX_FRAME_ENDED USART0_TX_vect
#define TX_DATA_REGISTER_EMPTY USART0_UDRE_vect
#endif
/* I/O port containing the pin to be used as TX_ENABLE for the RS-485 driver */
static volatile uint8_t* TX_ENABLE_PORT = NULL;
/** Pin from the I/O port to be used as TX_ENABLE for the RS-485 driver */
static volatile uint8_t TX_ENABLE_PIN = 0;
ISR(RX_BYTE_AVAILABLE)
{
// Read the status and RX registers.
uint8_t status = UCSR0A;
// Framing error - treat as EOF.
if (status & _BV(FE0)) {
/* TODO: increment statistics */
}
// Overrun or parity error.
if (status & (_BV(DOR0) | _BV(UPE0))) {
/* TODO: increment statistics */
}
ringbuffer_write_byte(&rxrb, UDR0);
}
ISR(TX_FRAME_ENDED)
{
/* The end of frame interrupt will be enabled only when in RS-485 mode, so
* there is no need to test, just turn off the TX_ENABLE pin */
*TX_ENABLE_PORT &= ~_BV(TX_ENABLE_PIN);
}
ISR(TX_DATA_REGISTER_EMPTY)
{
uint8_t byte;
if (!ringbuffer_read_byte(&txrb, &byte)) {
/* If RS-485 is enabled, sets TX_ENABLE high */
if (TX_ENABLE_PORT)
*TX_ENABLE_PORT |= _BV(TX_ENABLE_PIN);
UDR0 = byte;
}
else {
/* No more chars to be read from ringbuffer, disables empty
* data register interrupt */
UCSR0B &= ~_BV(UDRIE0);
}
}
void uart_drv_start(uint8_t ubrrh, uint8_t ubrrl, uint8_t use2x,
volatile uint8_t* rs485_tx_enable_io_port,
uint8_t rs485_tx_enable_io_pin)
{
/* Initializes TX and RX ring buffers */
ringbuffer_init(&txrb, &tx_buffer[0], UART_TX_BUFSIZE);
ringbuffer_init(&rxrb, &rx_buffer[0], UART_RX_BUFSIZE);
cli();
/* Disables UART */
UCSR0B = 0x00;
/* Initializes baud rate */
UBRR0H = ubrrh;
UBRR0L = ubrrl;
if (use2x)
UCSR0A |= _BV(U2X0);
else
UCSR0A &= ~_BV(U2X0);
/* Configures async 8N1 operation */
UCSR0C = _BV(UCSZ00) | _BV(UCSZ01);
/* If a port was specified for a pin to be used as a RS-485 driver TX_ENABLE,
* configures the pin as output and enables the TX data register empty
* interrupt so it gets disabled in the end of transmission */
if (rs485_tx_enable_io_port) {
TX_ENABLE_PORT = rs485_tx_enable_io_port;
TX_ENABLE_PIN = rs485_tx_enable_io_pin;
/* Configures the RS-485 driver as an output (on the datasheet the data
* direction register is always on the byte preceding the I/O port addr) */
*(TX_ENABLE_PORT-1) |= _BV(TX_ENABLE_PIN);
/* Clears TX_ENABLE pin (active high) */
*TX_ENABLE_PORT &= ~_BV(TX_ENABLE_PIN);
/* Enables end of transmission interrupt */
UCSR0B = _BV(TXCIE0);
}
/* Enables receptor, transmitter and RX complete interrupts */
UCSR0B |= _BV(RXEN0) | _BV(TXEN0) | _BV(RXCIE0);
sei();
}
void uart_drv_send_byte(uint8_t byte, FILE *stream)
{
if (byte == '\n') {
uart_drv_send_byte('\r', stream);
}
uint8_t sreg = SREG;
cli();
/* Write byte to the ring buffer, blocking while it is full */
while(ringbuffer_write_byte(&txrb, byte)) {
/* Enable interrupts to allow emptying a full buffer */
SREG = sreg;
_NOP();
sreg = SREG;
cli();
}
/* Enables empty data register interrupt */
UCSR0B |= _BV(UDRIE0);
SREG = sreg;
}
uint8_t uart_drv_read_byte(FILE *stream)
{
uint8_t byte;
uint8_t sreg = SREG;
cli();
ringbuffer_read_byte(&rxrb, &byte);
SREG = sreg;
return byte;
}
2 ответа
Возможно, вы включили прерывание UDRE (пустой регистр данных Uart) и не задали для него вектор, поэтому, когда прерывание запускается, процессор сбрасывается (в соответствии со значениями по умолчанию). когда printf
непрерывно вызывается в основном цикле, это прерывание никогда не срабатывает.
Из документов
Поймать всех прерываний вектор
Если происходит непредвиденное прерывание (прерывание включено и никакой обработчик не установлен, что обычно указывает на ошибку), то действие по умолчанию - сброс устройства путем перехода к вектору сброса. Вы можете переопределить это, предоставив функцию с именем BADISR_vect, которая должна быть определена с помощью ISR() как таковой. (Имя BADISR_vect на самом деле является псевдонимом для __vector_default. Последний должен использоваться внутри кода сборки, если он не включен.)
Я столкнулся с такой же ситуацией прямо сейчас, но, поскольку у меня нет высокой репутации в stackru, я не могу голосовать.
Вот фрагмент моей процедуры инициализации, которая вызвала у меня эту проблему:
void USART_Init()
{
cli();
/* Set baud rate */
UBRR0H = (uint8_t)(BAUD_PRESCALE>>8);
UBRR0L = (uint8_t)BAUD_PRESCALE;
/* Enable receiver and transmitter */
UCSR0B |= (1<<RXEN0)|(1<<TXEN0);
/* Set frame format: 8data, 1stop bit 8N1 => 86uS for a byte*/
UCSR0C |= (1<<UCSZ01)|(1<<UCSZ00);
/*enable Rx and Tx Interrupts*/
UCSR0B |= (1 << RXCIE0) | (1 << TXCIE0); //<- this was the problem
/*initialize the RingBuffer*/
RingBuffer_Init(&RxBuffer);
sei();
}
Проблема заключалась в том, что я первоначально использовал передачу на основе прерываний, но позже я изменил дизайн и пошел на опрос 10 мс для последовательности Tx, а также забыл изменить эту строку в процедуре инициализации.
Большое спасибо за указание на это Питер Гибсон.