Дополнительные внешние прерывания, относительно новая штука в периферии AVR. В микроконтроллерах ATmega8/ATmega16 их нет, зато они есть в ATmega88/ATmega168. В официальной документации они описаны в главе 13 "Внешние прерывания". В отличии от обычных внешних прерываний, в дополнительных, одно прерывание отведено на один порт (т.е. на восемь пинов), и в них нельзя выставить условия срабатывания. Прерывание будет генерироваться при любом изменении сигнала, даже если он был изменен программно, самим микроконтроллером.
Для считывания энкодера я использовал ИК-оптопару на компараторе LM393. Плата имеет цифровой выход D0. Если в проём оптопары вставить непрозрачный предмет, например монетку, то выход D0 примет значение логической единицы.
Для тестирования соберем такую схему:
В качестве основы для первого наброска я взял пример из поста: "ATmega8: Blink без delay на Си" и немного доработав, написал простой повторитель.
#include <util/delay.h> #include <avr/io.h> #include <avr/interrupt.h> #include <uart.h> #define LED PB5 #define LED_PORT PORTB #define LEFT PB0 #define LEFT_PIN PINB static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE); volatile unsigned long millis; ISR(TIMER0_OVF_vect) { millis++; } int main(void) { //---- init init_uart(); DDRB |= (1<<LED); // led indicator DDRB &= ~(1<<LEFT); // input left encoder // init 8-bit timer0 TIMSK0 =(1<<TOIE0); // timer0 overflow enable TCCR0B = (1<<CS01) | (1<<CS00); // prescaler 1/64 unsigned long curTime, lastTime, period; // uint16_t sec; sec=0; millis=0; lastTime=0; period=20; // 1 sec //---- ready indication blink13(3); stdout = &mystdout; printf("Ok, I'm ready!\n"); sei(); //---- main body for (;;) { uint8_t val; curTime=millis; if ((curTime -lastTime)> period) { val=LEFT_PIN & (1<<LEFT); if (val) LED_PORT |= (1<<LED); else LED_PORT &= ~(1<<LED); lastTime=curTime; } }; return 0; }
Здесь, в главном цикле программы реализуется опрос порта, и при получении сигнала энкодера, загорается светодиод на PB5(pin 13)
На следующем этапе, нужно будет опрос порта перекинуть из главного цикла в прерывание. Также требуется узнать временной интервал, для предотвращения дребезга.
#include <util/delay.h> #include <avr/io.h> #include <avr/interrupt.h> #include <uart.h> #define LED PB5 #define LED_PORT PORTB #define LEFT PB0 #define LEFT_PIN PINB static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE); volatile unsigned long millis; volatile unsigned long lastTime; volatile unsigned long curTime; volatile uint8_t val; volatile uint8_t last; volatile uint16_t data; ISR(TIMER0_OVF_vect) { millis++; } ISR(PCINT0_vect) { val=LEFT_PIN & (1<<LEFT); if (val != last) { if (val) LED_PORT |= (1<<LED); else { LED_PORT &= ~(1<<LED); curTime=millis-lastTime; data=(uint16_t)curTime; lastTime=millis; } last=val; } } int main(void) { //---- init init_uart(); DDRB |= (1<<LED); // led indicator DDRB &= ~(1<<LEFT); // input left encoder // init 8-bit timer0 TIMSK0 =(1<<TOIE0); // timer0 overflow enable TCCR0B = (1<<CS01) | (1<<CS00); // prescaler 1/64 // init external interrupt PCICR = (1<<PCIE0); // PCIE0 enable PCMSK0 = (1<<PCINT0); // PCINT0 enable last=0; //---- ready indication blink13(3); stdout = &mystdout; printf("Ok, I'm ready!\n"); sei(); //---- main body for (;;) { _delay_ms(100); printf("period: %u\n", data); }; return 0; }
Я подключил двигатель на холостом ходу чтобы узнать максимальную скорость которую он способен развить. Через UART получил такую картинку:
т.е. одно деление энкодера проходится минимум за 15 миллисекунд. Если одно деление энкодера равно 1 сантиметру пройденного пути, то 1000/15 ~66 сантиметров в секунду. Теперь, для устранения дребезга энкодера, для отсечки ложных срабатываний, можно будет взять величину в 10мс, предполагая, что быстрее колесо уже не покатиться.
Теперь, чтобы реализовать исходную задачу, нужно чтобы счетчики от двух энкодерах работали на одном прерывании. Получилось это так:
#include <util/delay.h> #include <avr/io.h> #include <avr/interrupt.h> #include <uart.h> #define LED PB5 #define LED_PORT PORTB #define LEFT PB0 #define LEFT_PIN PINB #define RIGHT PB1 #define RIGHT_PIN PINB #define MIN 10 static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE); volatile unsigned long millis; volatile unsigned long lastTimeR, curTimeR; volatile unsigned long lastTimeL, curTimeL; volatile uint8_t valR, lastR; volatile uint8_t valL, lastL; volatile uint16_t dataL, dataR; ISR(TIMER0_OVF_vect) { millis++; } ISR(PCINT0_vect) { valL=LEFT_PIN & (1<<LEFT); valR=RIGHT_PIN & (1<<RIGHT); curTimeL=millis-lastTimeL; curTimeR=millis-lastTimeR; if ((valL != lastL) && (curTimeL > MIN)) { LED_PORT ^= (1<<LED); if (!valL) { lastTimeL=curTimeL; dataL++; } lastL=valL; } else if ((valR != lastR) && (curTimeR > MIN)) { LED_PORT ^= (1<<LED); if (!valR) { lastTimeR=curTimeR; dataR++; } lastR=valR; } } int main(void) { //---- init init_uart(); DDRB |= (1<<LED); // led indicator DDRB &= ~(1<<LEFT); // input left encoder // init 8-bit timer0 TIMSK0 =(1<<TOIE0); // timer0 overflow enable TCCR0B = (1<<CS01) | (1<<CS00); // prescaler 1/64 // init external interrupt PCICR = (1<<PCIE0); // PCIE0 enable PCMSK0 = (1<<PCINT0) | (1<<PCINT1); // PCINT0 and PCINT1 enable lastL=0; lastR=0; dataL=0; dataR=0; //---- ready indication blink13(3); stdout = &mystdout; printf("Ok, I'm ready!\n"); sei(); //---- main body for (;;) { _delay_ms(1000); printf("Left: %u Right: %u\n", dataL, dataR); cli(); dataL=0; dataR=0; sei(); }; return 0; }
второй сенсор был подключен на PB1(digital pin 9). Значения счетчиков обоих энкодеров полученных за одну секунду посылаются на UART, после чего обнуляются.
Результат работы:
Из скриншота видно, что колесо за одну секунду совершает 3 с небольшим оборотов. т.е. максимальная скорость будет около 65 сантиметров в секунду или 2.34 км/ч. Половина скорости человеческого шага.
скачать архив с исходниками и Makefile можно здесь: