Перейти к содержанию
    

AVR Надежная работа UART и прерывания прочей переферии

Добрый день,

 

столкнулся с такой непонятной для меня ситуацией.

Есть 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;
}

 

На лицо полное не понимание чего-то :)

 

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

 

Спасибо

Изменено пользователем rezident
оформление цитаты исходника.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Код UART навсякий случай:

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

 

P.S. _delay_ms(2) зачем?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

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

 

#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" ::)
#endif
#ifndef sei
#define sei() __asm__ __volatile__ ("sei" ::)
#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 соответственно до и после послыки байта данных пробовал - не помогает.

 

P.S. _delay_ms(2) зачем?
Последствия танцев с бубном.

 

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

 

для удобства тот же код, только с подсветкой:

Код для AVR: http://pastebin.com/43J3unFw

Код для ПК: http://pastebin.com/ZJdip1ft

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

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

рекомендации:

а) написать действительно минимальный код - если проблема в АЦП, оставить только код относящийся к УАРТУ И АЦП

б) оформить обработчик прерывания от АЦП аналогично остальным, при этом не извращаться при чтении АЦП, а сделать так

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?

Изменено пользователем smac

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

рекомендации:

а) написать действительно минимальный код - если проблема в АЦП, оставить только код относящийся к УАРТУ И АЦП

Сделано - минимальный код только АЦП и UART: http://pastebin.com/BGZUMdz9

Вот asm-листинг этого кода при компиляции с ключом -Os: http://pastebin.com/9sZTuTXS

Вот asm-листинг этого кода при компиляции с ключом -O1: http://pastebin.com/d5VbNSL8

 

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

 

ЗЫ ~30000 это случайно не 32767?
Нет падения происходят в различное время (от 7 тыс до 50 тыс).

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

Что-то я тоже ничего криминального не вижу. Попробуйте все-же оформить обработчик АЦП как ISR(ADC_vect){...} и еще попробуйте сделать следующее в майне сначала настройте уарт, затем настройте АЦП, затем запустите АЦП и только потом глобально разрешите прерывания, т. е. sei() должна стоять непосредственно перед входом в бесконечный цикл.

Опишите поподробней в чем выражаются "падения".

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Нет падения происходят в различное время (от 7 тыс до 50 тыс).

Вероятно на ПК что-то тормозит как всегда.

В программе на PC с портом лучше работать напрямую без сторонних компонентов, т.к. непонятно что и как они делают.

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

 

По коду - Вы смотрели, что задает port.ReadTimeout = 50; куда он в итоге пишет?

В ComTimeouts->ReadIntervalTimeout или в ComTimeouts->ReadTotalTimeoutConstant?

Хотя если используется USB<>COM переходник то оба параметра игнорируются и равны 1ms (по крайней мере с драйверами от FTDI было так).

 

 

Воспользуйтесь помехоустойчивым протоколом обмена, а на МК реализуйте UART на прерываниях и будет щастье.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Вероятно на ПК что-то тормозит как всегда.

В программе на PC с портом лучше работать напрямую без сторонних компонентов, т.к. непонятно что и как они делают.

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

 

По коду - Вы смотрели, что задает port.ReadTimeout = 50; куда он в итоге пишет?

В ComTimeouts->ReadIntervalTimeout или в ComTimeouts->ReadTotalTimeoutConstant?

Хотя если используется USB<>COM переходник то оба параметра игнорируются и равны 1ms (по крайней мере с драйверами от FTDI было так).

Спасибо за ответ.

Вожусь уже не первую неделю с этим делом, готов понаделать всяких параноидальных выводов после многочисленных тестов. Сейчас вот переписал часть на ПК с C# на Си с прямым обращением к COM-порту как к файлу - запустил на тестирование. USB-COM переходники не использовал при тестировании.

 

Воспользуйтесь помехоустойчивым протоколом обмена, а на МК реализуйте UART на прерываниях и будет щастье.
Вы не первый, кто советует переписать в МК UART и сделать его по прерываниям - так и попробую. Но все равно не понимаю, почему должно стать надежнее ? Вроде как оба случая с точки зрения надежности приема байта равнозначны - UART все равно реализован аппаратно и никто из МК не может (в теории) помешать ему принять байт.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Вы не первый, кто советует переписать в МК 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) ;>

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

//! Interrupt handler for tcnt0 overflow interrupt

ISR(TIMER0_OVF_vect)

{

PORTD=0xFF;

}

 

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

 

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

 

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

 

Ну тогда надо ещё какую-нибудь ерунду спросить. О. осциллятор у вас внешний или что?

Изменено пользователем RodionGork

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Самый простой надежный протокол - эхоплекс.
Да, спасибо за подробное описание. Именно "эхоплекс" сейчас и реализован. МК высылает Эхом то, что ему прислали.

Но CRC или какие-то ухищрения с протоколом, конкретно мою проблему не решают. У меня ведь посылка\отсылка не зависят от логики пересылаемых значений - пусть даже полезная часть пакета (данные) будет искажена "помехой" полностью - все равно МК её должен принять и тут же выслать назад - посмотите код, он не анализирует даные, просто шлет эхо. Аналогично ситуация обстоит и на ПК.

Если уж говорить о предполагаемых причинах, то мне кажутся более вероятными несоответствие baudrate часототе тактирования (по даташиту у меня ~0.2% ошибка (9600bod\16Mhz)) или рассинхронизация по старт-стоповым битам - как это точно диагностировать и как бороться, не понятно.

 

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

 

//! Interrupt handler for tcnt0 overflow interrupt

ISR(TIMER0_OVF_vect)

{

PORTD=0xFF;

}

Сигнализировало, что контроллер включен.

 

Ну тогда надо ещё какую-нибудь ерунду спросить. О. осциллятор у вас внешний или что?
Внешний, кристал 16МГц.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Сигнализировало, что контроллер включен.

 

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

 

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

 

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

 

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

 

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

 

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

 

P.S. Не надо переписывать программу. По крайней мере пока ошибка не найдена. Вы правы насчёт того что идеологически программа выглядит правильной и значит должна правильно работать. Если работает неправильно, значит есть нюанс. Если никто не может сказать, в чём он - это ещё не значит, что его нет. И если вы перепишете программу и глюк _вроде бы_ пропадёт - как можно будет знать что от него точно избавились и что он вас не подведёт как-нибудь в дальнейшем. ;-)

Изменено пользователем RodionGork

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Если уж говорить о предполагаемых причинах, то мне кажутся более вероятными несоответствие baudrate часототе тактирования (по даташиту у меня ~0.2% ошибка (9600bod\16Mhz)) или рассинхронизация по старт-стоповым битам - как это точно диагностировать и как бороться, не понятно.

Если после 30-ти тысячного символа у вас искажаются все последующие символы, тогда да - имеет место рассинхронизация, и ошибка у вас не 0.2%, а около 2.5%.

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

 

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

 

 

У меня ведь посылка\отсылка не зависят от логики пересылаемых значений - пусть даже полезная часть пакета (данные) будет искажена "помехой" полностью - все равно МК её должен принять и тут же выслать назад - посмотите код, он не анализирует даные, просто шлет эхо.

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

 

Вы считаете, что все каналы связи всегда все передают без ошибок?

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

 

 

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

 

насчёт того что идеологически программа выглядит правильной и значит должна правильно работать.

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

 

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Если после 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 не гарантирует передачу данных без потерь, поэтому программу все равно придется доработать с учетом этой особенности используемого интерфейса.

 

Да это разумеется, верхний уровень передачи ещё предстоит обдумывать поскольку в существующем виде там не остаётся времени для работы программы ;-) Но проблемы, указанные автором темы свидетельствуют о том что надо что-то в нижнем уровне отыскать сначала странное. А потом переписывать. ;-)))

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Вчера обнаружил неправильно запрограммированный fuse CKOPT. Как говорится, и как я его раньше не замечал.

Теперь работает стабильно.

Всем большое спасибо за поддержку ! :)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

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

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...