Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: AVR Надежная работа UART и прерывания прочей переферии
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > MCS51, AVR, PIC, STM8, 8bit
Didro
Добрый день,

столкнулся с такой непонятной для меня ситуацией.
Есть ATMega32, которая реализует: ШИМ (1 таймер), импульсы переменной длительности (1 таймер), замеры напряжения с двух каналов АЦП и общение с ПК. Общение с ПК по UART. Протокол общения строго синхронный. Запрос с ПК - ответ с МК.

Взял код UART из спецификации на mega32.

Если контроллер не загружен - буквально, если выключить АЦП (предделитель 64), а всю остальную переферию оставить (2 таймера), то UART работает нормально - проходит тестирование в 60 тыс. обменов с ПК.

Только стоит подключить АЦП начинаются проблемы - ПК фиксирует, что контроллер не отвечает ему за отведенное время.
При этом если код функций работы UART заключить в скобки cli-sei, то даже при включенном АЦП, обмен идет хорошо. Если запрещение прерываний убрать, начинаются проблемы.

Код UART навсякий случай:
Код
void usrtSendByte( u08 data )
{
  cli();
    /* Wait for empty transmit buffer */
    while ( !( UCSRA & (1<<UDRE)) )
       _delay_ms(2);
    /* Put data into buffer, sends the data */
    UDR = data;
    sei();
}

u08 usrtReadByte( void )
{
  cli();
    /* Wait for data to be received */
    while ( !(UCSRA & (1<<RXC)) )
        _delay_ms(2);
    /* Get and return received data from buffer */
    u08 temp=UDR;
    sei();
    return temp;
}


На лицо полное не понимание чего-то smile.gif

Прошу помощи.

Спасибо
aaarrr
Цитата(Didro @ Mar 10 2010, 15:30) *
Код UART навсякий случай:

Зачем, если очевидно, что виновато прерывание АЦП? Его и приведите.

P.S. _delay_ms(2) зачем?
Didro
Цитата(aaarrr @ Mar 10 2010, 15:47) *
Зачем, если очевидно, что виновато прерывание АЦП? Его и приведите.

Написал минимальный код при котором проявляется проблема:

CODE
#include <avr/io.h>
#include <avr/interrupt.h>

// Code compatibility to new AVR-libc
// outb(), inb(), inw(), outw(), BV(), sbi(), cbi(), sei(), cli()
#ifndef outb
#define outb(addr, data) addr = (data)
#endif
#ifndef inb
#define inb(addr) (addr)
#endif
#ifndef inw
#define inw(addr) (addr)
#endif
#ifndef BV
#define BV(bit) (1<<(bit))
#endif
#ifndef cbi
#define cbi(reg,bit) reg &= ~(BV(bit))
#endif
#ifndef sbi
#define sbi(reg,bit) reg |= (BV(bit))
#endif
#ifndef cli
#define cli() __asm__ __volatile__ ("cli" :smile.gif
#endif
#ifndef sei
#define sei() __asm__ __volatile__ ("sei" :smile.gif
#endif

//*****************************************************************************
//
// TIMERS
//
//*****************************************************************************

void initTimer()
{
outb(TCNT0, 0); // reset TCNT0
sbi(TIMSK, TOIE0); // enable TCNT0 overflow interrupt
outb(TCCR0, (inb(TCCR0) & ~0x07) | 0x02);

outb(TCNT1H, 0); // reset TCNT1
outb(TCNT1L, 0);
sbi(TIMSK, TOIE1); // enable TCNT1 overflow
outb(TCCR1B, (inb(TCCR1B) & ~0x07) | 0x02);

outb(TCNT2, 0); // reset TCNT2
sbi(TIMSK, TOIE2); // enable TCNT2 overflow
outb(TCCR2, (inb(TCCR2) & ~0x07) | 0x02);
}

//! Interrupt handler for tcnt0 overflow interrupt
ISR(TIMER0_OVF_vect)
{
PORTD=0xFF;
}

//! Interrupt handler for tcnt1 overflow interrupt
ISR(TIMER1_OVF_vect)
{

}

//! Interrupt handler for tcnt2 overflow interrupt
ISR(TIMER2_OVF_vect)
{

}

//*****************************************************************************
//
// UART
//
//*****************************************************************************

#define BAUD_RATE 9600ul
#define USART_UBBR_VALUE ((F_CPU/(BAUD_RATE<<4))-1)

void usrtInit( )
{
/* Set baud rate */
UBRRH = (unsigned char)(USART_UBBR_VALUE>>8);
UBRRL = (unsigned char)USART_UBBR_VALUE;
/* Enable Receiver and Transmitter */
UCSRB = (1<<RXEN)|(1<<TXEN);
/* Set frame format: 8data, 2stop bit */
UCSRC = (1<<URSEL)|(1<<USBS)|(3<<UCSZ0);
}

void usrtSendByte( unsigned char data )
{
/* Wait for empty transmit buffer */
while ( !( UCSRA & (1<<UDRE)) )
;
/* Put data into buffer, sends the data */
UDR = data;
}

unsigned char usrtReadByte( void )
{
/* Wait for data to be received */
while ( !(UCSRA & (1<<RXC)) )
;
/* Get and return received data from buffer */
return UDR;
}

//*****************************************************************************
//
// ADC
//
//*****************************************************************************

// initialize a2d converter
void a2dInit(void)
{
sbi(ADCSRA, ADEN); // enable ADC (turn on ADC power)
sbi(ADCSRA, ADATE); // choose free running mode
outb(ADCSRA, ((inb(ADCSRA) & ~0x07) | 0x06));
outb(ADMUX, ((inb(ADMUX) & ~0xC0) | (0x01<<6)));
cbi(ADMUX, ADLAR); // set to right-adjusted result

outb(ADMUX, (inb(ADMUX) & ~0x1F) | (0 & 0x1F)); // set channel

sbi(ADCSRA, ADIE); // enable ADC interrupts
}

// start a conversion on the current a2d input channel
void a2dStartConvert(void)
{
sbi(ADCSRA, ADIF); // clear hardware "conversion complete" flag
sbi(ADCSRA, ADSC); // start conversion
}

//! Interrupt handler for ADC complete interrupt.
SIGNAL(SIG_ADC)
{
unsigned char sample=(inb(ADCL) | (inb(ADCH)<<8)) >>2; // n?eoaai iiia?yiiia 10-aeoiia cia?aiea e ioa?inei 2 ieaaoeo aeoa
}

//*****************************************************************************
//
// MAIN
//
//*****************************************************************************

int main()
{
sei();
DDRD=0xFF;

initTimer();
a2dInit();
a2dStartConvert();

unsigned char data;
usrtInit( );
while(1)
{
data=usrtReadByte();
usrtSendByte(data);
}
}


Код для ПК (на C#):
Код
private static void VerySimpleEchoTest()
{
  port = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.Two);
  port.Open();

  port.DiscardInBuffer(); port.DiscardOutBuffer();
  port.ReadTimeout = 50;
  int i = 0;
            
  while (true)
  {
     i++;
                
     port.Write(new byte[] { (byte)random.Next(0, 255) }, 0, 1);
     Console.WriteLine("   echo:          " + port.ReadByte());
                
     Console.WriteLine("# " + i);
   }
}
Этот код падает при пересылке ~30000 сообщений (5-10 минут работы).
При этом до момента падения пересылаемое значение совпадает с ответом из AVR.

Контроллер ATMega32A-PU. Связь с ПК через Max232 (осциллограф каких-то проблем (завалы) на сигнальных линиях UART не показывает). Питание 5В от компьютерного БП.

Компилятор WinAVR-20090313, AVRStudio 4, -0s.
Фреймирование с помощью байтов со значениями 0, 0xFF соответственно до и после послыки байта данных пробовал - не помогает.

Цитата(aaarrr @ Mar 10 2010, 15:47) *
P.S. _delay_ms(2) зачем?
Последствия танцев с бубном.

Никак не пойму, где же быть ошибке...

для удобства тот же код, только с подсветкой:
Код для AVR: http://pastebin.com/43J3unFw
Код для ПК: http://pastebin.com/ZJdip1ft
smac
Цитата(Didro @ Mar 14 2010, 15:30) *
Написал минимальный код при котором проявляется проблема:

Компилировать Ваш код и смотреть асм. листинг желания нет, уж больно длинный.
рекомендации:
а) написать действительно минимальный код - если проблема в АЦП, оставить только код относящийся к УАРТУ И АЦП
б) оформить обработчик прерывания от АЦП аналогично остальным, при этом не извращаться при чтении АЦП, а сделать так
Код
ISR(ADC_vect){
  unsigned char sample = ADCW;
/*Я поначалу не стал заводить лишнюю переменную и написал просто ADCW; но скомпилив увидел, что там в обработчике прерывания какой-то rcall нарисовался, ну его нафиг*/
}


в) на мой взгляд выражения типа outb(TCCR1B, (inb(TCCR1B) & ~0x07) | 0x02) не добавляют читаемости кода, наверное лучше так TCCR1B = (TCCR1B&0xf8)|(1<<CS11)

Да, кстати, может это и новый стиль, но я чего-то не понимаю, зачем эти sbi() и outb() мне они глаз режут, по-моему обычным макаром
Код
TCNT0 = 0;
код лучше выглядит.

ЗЫ ~30000 это случайно не 32767?
Didro
Цитата(smac @ Mar 14 2010, 21:52) *
Компилировать Ваш код и смотреть асм. листинг желания нет, уж больно длинный.
рекомендации:
а) написать действительно минимальный код - если проблема в АЦП, оставить только код относящийся к УАРТУ И АЦП

Сделано - минимальный код только АЦП и UART: http://pastebin.com/BGZUMdz9
Вот asm-листинг этого кода при компиляции с ключом -Os: http://pastebin.com/9sZTuTXS
Вот asm-листинг этого кода при компиляции с ключом -O1: http://pastebin.com/d5VbNSL8

При компиляции с ключом -O1 проблема не наблюдается (версия WinAVR от 20100110), при компиляции с ключом -Os стабильно падает - проблем в asm коде не обнаружил (видимо что-то упускаю)

Цитата(smac @ Mar 14 2010, 21:52) *
ЗЫ ~30000 это случайно не 32767?
Нет падения происходят в различное время (от 7 тыс до 50 тыс).
smac
Цитата(Didro @ Mar 15 2010, 12:39) *
При компиляции с ключом -O1 проблема не наблюдается (версия WinAVR от 20100110), при компиляции с ключом -Os стабильно падает - проблем в asm коде не обнаружил (видимо что-то упускаю)

Что-то я тоже ничего криминального не вижу. Попробуйте все-же оформить обработчик АЦП как ISR(ADC_vect){...} и еще попробуйте сделать следующее в майне сначала настройте уарт, затем настройте АЦП, затем запустите АЦП и только потом глобально разрешите прерывания, т. е. sei() должна стоять непосредственно перед входом в бесконечный цикл.
Опишите поподробней в чем выражаются "падения".
defunct
Цитата(Didro @ Mar 15 2010, 11:39) *
Нет падения происходят в различное время (от 7 тыс до 50 тыс).

Вероятно на ПК что-то тормозит как всегда.
В программе на PC с портом лучше работать напрямую без сторонних компонентов, т.к. непонятно что и как они делают.
Не пользуйте USB<>COM переходники для тестов надежности, потому что это зло которое неадкватно воспринимает параметры COM TIMEOUTS, вследствие чего могут теряться символы.

По коду - Вы смотрели, что задает port.ReadTimeout = 50; куда он в итоге пишет?
В ComTimeouts->ReadIntervalTimeout или в ComTimeouts->ReadTotalTimeoutConstant?
Хотя если используется USB<>COM переходник то оба параметра игнорируются и равны 1ms (по крайней мере с драйверами от FTDI было так).


Воспользуйтесь помехоустойчивым протоколом обмена, а на МК реализуйте UART на прерываниях и будет щастье.
Didro
Цитата(defunct @ Mar 21 2010, 02:46) *
Вероятно на ПК что-то тормозит как всегда.
В программе на PC с портом лучше работать напрямую без сторонних компонентов, т.к. непонятно что и как они делают.
Не пользуйте USB<>COM переходники для тестов надежности, потому что это зло которое неадкватно воспринимает параметры COM TIMEOUTS, вследствие чего могут теряться символы.

По коду - Вы смотрели, что задает port.ReadTimeout = 50; куда он в итоге пишет?
В ComTimeouts->ReadIntervalTimeout или в ComTimeouts->ReadTotalTimeoutConstant?
Хотя если используется USB<>COM переходник то оба параметра игнорируются и равны 1ms (по крайней мере с драйверами от FTDI было так).
Спасибо за ответ.
Вожусь уже не первую неделю с этим делом, готов понаделать всяких параноидальных выводов после многочисленных тестов. Сейчас вот переписал часть на ПК с C# на Си с прямым обращением к COM-порту как к файлу - запустил на тестирование. USB-COM переходники не использовал при тестировании.

Цитата(defunct @ Mar 21 2010, 02:46) *
Воспользуйтесь помехоустойчивым протоколом обмена, а на МК реализуйте UART на прерываниях и будет щастье.
Вы не первый, кто советует переписать в МК UART и сделать его по прерываниям - так и попробую. Но все равно не понимаю, почему должно стать надежнее ? Вроде как оба случая с точки зрения надежности приема байта равнозначны - UART все равно реализован аппаратно и никто из МК не может (в теории) помешать ему принять байт.
defunct
Цитата(Didro @ Mar 21 2010, 02:35) *
Вы не первый, кто советует переписать в МК UART и сделать его по прерываниям - так и попробую. Но все равно не понимаю, почему должно стать надежнее ? Вроде как оба случая с точки зрения надежности приема байта равнозначны - UART все равно реализован аппаратно и никто из МК не может (в теории) помешать ему принять байт.

Надежнее станет не столько за счет прерываний, сколько за счет помехоустойчивого пакетного протокола (с использованием CRC / FEC / повторов ).
Учитывая, что пакетный обмен предполагает прием пакетов состоящих из, пусть минимум 3-х 4-х байт, а приемный FIFO в AVR всего 1 байт. Очевидно что без прерываний можно потерять части пакета, если другая задача (например обработка данных АЦП) будет длиться долго..

Самый простой надежный протокол - эхоплекс. Приемник шлет назад передатчику то, что он принял, передатчик повторяет посылку если обнаруживает ошибку.
как реализовать. Допустим вы собираетесь слать на PC данные с АЦП. Пусть по 16 байт в одном пакете.
тогда можно сделать такой формат пакета:

[ 1 байт sn ][ 16 байт данные с АЦП ]

алгоритм МК:
1. Заполняем пакет новыми данными с АЦП.
1. Шлем пакет на PC.
2. Принимаем эхо этого пакета с PC.
3. Если принятный с PC пакет полностью совпадает с тем что отправляли, то увеличиваем sn на 1 и goto 1.
4. иначе goto 2 (шлем тот же пакет без изменений).

алгоритм PC:
1. принимаем пакет A;
2. шлем пакет ( A ) обратно;
3. принимаем пакет B;
4. шлем пакет ( B )обратно;
5. если sn( B ) - sn( A ) = 1, то обрабатываем пакет ( A ), и копируем пакет B на место пакета A.
6. гото 3.

Теперь думаем как это ускорить, и сделать еще более надежным. Приходим к CRC и к короткому подтверждению (ACK'у) вместо эха, далее к слайд окну, - чтобы МК мог слать следующий пакет не дожидаясь подтвержения текущего и т.д. и т.п. (получаем в итоге нечто похожее на TCP) ;>
RodionGork
//! Interrupt handler for tcnt0 overflow interrupt
ISR(TIMER0_OVF_vect)
{
PORTD=0xFF;
}

Вот эта штука зачем?

Она как-то помогает формированию сигналов UART висящего на нижних битах порта D?

А не... Я не прав... когда уарт включен он пины захватывает и по крайней мере помешать это не должно...

Ну тогда надо ещё какую-нибудь ерунду спросить. О. осциллятор у вас внешний или что?
Didro
Цитата(RodionGork @ Mar 21 2010, 08:03) *
Самый простой надежный протокол - эхоплекс.
Да, спасибо за подробное описание. Именно "эхоплекс" сейчас и реализован. МК высылает Эхом то, что ему прислали.
Но CRC или какие-то ухищрения с протоколом, конкретно мою проблему не решают. У меня ведь посылка\отсылка не зависят от логики пересылаемых значений - пусть даже полезная часть пакета (данные) будет искажена "помехой" полностью - все равно МК её должен принять и тут же выслать назад - посмотите код, он не анализирует даные, просто шлет эхо. Аналогично ситуация обстоит и на ПК.
Если уж говорить о предполагаемых причинах, то мне кажутся более вероятными несоответствие baudrate часототе тактирования (по даташиту у меня ~0.2% ошибка (9600bod\16Mhz)) или рассинхронизация по старт-стоповым битам - как это точно диагностировать и как бороться, не понятно.

С прерываниями попробую, а вдруг... smile.gif

Цитата(RodionGork @ Mar 21 2010, 08:03) *
//! Interrupt handler for tcnt0 overflow interrupt
ISR(TIMER0_OVF_vect)
{
PORTD=0xFF;
}
Сигнализировало, что контроллер включен.

Цитата(RodionGork @ Mar 21 2010, 08:03) *
Ну тогда надо ещё какую-нибудь ерунду спросить. О. осциллятор у вас внешний или что?
Внешний, кристал 16МГц.
RodionGork
Цитата(Didro @ Mar 21 2010, 09:36) *
Сигнализировало, что контроллер включен.

Внешний, кристал 16МГц.


Частота тактирования у вас отличная получается. Должна. Там до 5% допускается если мне не изменяет склероз и сам я неоднократно с гораздо большей ошибкой чем 0.2% работал.

Вы можете проверить "рассинхронизацию по старт-стоповым битам" как вы это называете проверяя ошибку Frame Error в UCSRA.

Внешний кристалл это хорошо. К этому вопросов больше нет;-)))

PORTD всё-таки уберите, попробуйте, если не сложно. UART при подключении перекрывает управление DDRD 1 и 0, но подтягивающим резистором на RX, например, вы всё равно при этом машете. Насчёт TX не знаю. А нет, тоже фигня. Если его один раз установили в 1 (подтяжку) то больше вы её не сбрасываете. Ну, я опять не прав, видимо.

Другое дело, что, безусловно, не ясно, причём тут подключение АЦП. а вот cli и sei устанавливаемые для функций UART конечно запрещают таймеру манипулировать портом D.

P.S. Не надо переписывать программу. По крайней мере пока ошибка не найдена. Вы правы насчёт того что идеологически программа выглядит правильной и значит должна правильно работать. Если работает неправильно, значит есть нюанс. Если никто не может сказать, в чём он - это ещё не значит, что его нет. И если вы перепишете программу и глюк _вроде бы_ пропадёт - как можно будет знать что от него точно избавились и что он вас не подведёт как-нибудь в дальнейшем. ;-)
defunct
Цитата(Didro @ Mar 21 2010, 08:36) *
Если уж говорить о предполагаемых причинах, то мне кажутся более вероятными несоответствие baudrate часототе тактирования (по даташиту у меня ~0.2% ошибка (9600bod\16Mhz)) или рассинхронизация по старт-стоповым битам - как это точно диагностировать и как бороться, не понятно.

Если после 30-ти тысячного символа у вас искажаются все последующие символы, тогда да - имеет место рассинхронизация, и ошибка у вас не 0.2%, а около 2.5%.
Чтобы проверить действительно ли дело в этом - сделайте паузы длиной в 1-2 символа через каждые n символов (пусть n=100). После очередной паузы, синхронизация восстановится.

Ошибка в 0.2% - это допустимая ошибка и никак не должна вызывать рассинхронизацию. Рассинхронизацию может вызывать суммарная ошибка в 5% (2.5% приемник, 2.5% в противоположную сторону - передатчик) которая на 10 бит превращается в 50%, т.е. последний передающийся бит читается неверно, ну а затем если передача идет непрерывно, искажаются и все последующие символы..


Цитата(Didro @ Mar 21 2010, 08:36) *
У меня ведь посылка\отсылка не зависят от логики пересылаемых значений - пусть даже полезная часть пакета (данные) будет искажена "помехой" полностью - все равно МК её должен принять и тут же выслать назад - посмотите код, он не анализирует даные, просто шлет эхо.

Я видел ваш код. Но я не понимаю какая цель ваших экспериментов.

Вы считаете, что все каналы связи всегда все передают без ошибок?
Так вот смею вас уверить RS232 не гарантирует передачи без потерь. Банально щелкнет UPS - наведется помеха на кабель, которая исказит всего один битик (стартовый битик) и все - приплыли, при непрерывной передаче весь поток будет убит, даже без проблем с синхронизацией..


Если вы хотите добиться надежной передачи, примените помехоустойчивый протокол, с обнаружением и устранением ошибок, и все будет чудесно работать.

Цитата(RodionGork @ Mar 21 2010, 09:03) *
насчёт того что идеологически программа выглядит правильной и значит должна правильно работать.

Она и работает правильно, кто сказал, что нет? Сбоит часть на PC, небольшая задержка вводит PC в ступор, скорее всего просто что-то не так с COM TIMEOUTS как уже говорил выше.. Символ приходит, но по какой-то причине просто заданный таймаут в 50ms срабатывает раньше.

Цитата
P.S. Не надо переписывать программу.

RS232 не гарантирует передачу данных без потерь, поэтому программу все равно придется доработать с учетом этой особенности используемого интерфейса.
RodionGork
Цитата(defunct @ Mar 22 2010, 04:26) *
Если после 30-ти тысячного символа у вас искажаются все последующие символы, тогда да - имеет место рассинхронизация, и ошибка у вас не 0.2%, а около 2.5%.
Чтобы проверить действительно ли дело в этом - сделайте паузы длиной в 1-2 символа через каждые n символов (пусть n=100). После очередной паузы, синхронизация восстановится.

Ошибка в 0.2% - это допустимая ошибка и никак не должна вызывать рассинхронизацию. Рассинхронизацию может вызывать суммарная ошибка в 5% (2.5% приемник, 2.5% в противоположную сторону - передатчик) которая на 10 бит превращается в 50%, т.е. последний передающийся бит читается неверно, ну а затем если передача идет непрерывно, искажаются и все последующие символы..


Э-э-э... Да ну. У вас что, рассинхронизация накапливается со временем? Вы это как представляете? ;-)))

Для UART синхронизация каждого байта происходит отдельно по фронту стартового бита. Разве не так? Отсюда и берутся 5%. Если передаются 10 бит (start-8-stop) то последний не должен сдвинуться более чем на половину собственной величины, т.е. на 1/20 всего времени передачи байта.

Цитата
Цитата
P.S. Не надо переписывать программу.

RS232 не гарантирует передачу данных без потерь, поэтому программу все равно придется доработать с учетом этой особенности используемого интерфейса.


Да это разумеется, верхний уровень передачи ещё предстоит обдумывать поскольку в существующем виде там не остаётся времени для работы программы ;-) Но проблемы, указанные автором темы свидетельствуют о том что надо что-то в нижнем уровне отыскать сначала странное. А потом переписывать. ;-)))
Didro
Вчера обнаружил неправильно запрограммированный fuse CKOPT. Как говорится, и как я его раньше не замечал.
Теперь работает стабильно.
Всем большое спасибо за поддержку ! smile.gif
defunct
Цитата(RodionGork @ Mar 22 2010, 08:40) *
Э-э-э... Да ну. У вас что, рассинхронизация накапливается со временем? Вы это как представляете? ;-)))

В коменте выше я все написал.
И про то, что если рассинхронизация имеет место постоянно, то ошибка должна быть выше 0.2%, и про ошибку в 5%, и почему именно 5%.
И про то, что если идет непрерывная передача данных (без пауз), то достаточно пропустить/словить ложный - __один__ стартовый бит, и весь поток будет битым.

читайте, вникайте..
RodionGork
Цитата(Didro @ Mar 22 2010, 11:55) *
Вчера обнаружил неправильно запрограммированный fuse CKOPT. Как говорится, и как я его раньше не замечал.
Теперь работает стабильно.
Всем большое спасибо за поддержку ! smile.gif


Прикольно. Всё-таки осциллятор... ;-)))

Спасибо что не поленились таки найти источник бед. А то уж тут столько предложений у нас в народе народилось (не имевших отношения к реальной проблеме, как видим).

Цитата
читайте, вникайте..

Не, спасибо... Я как бы и так немножко в теме... ;-)
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.