Подавляющее большинство работавших с микроконтроллерами однажды оказывались перед сложным выбором:— Так, на эту ногу ШИМ, на эту — кнопку, на эти — светодиоды… Оп-па… А ноги-то закончились. Без светодиодов некрасиво, без кнопок нефункционально. Придется брать кристалл пожирнее :(
Или другая ситуация — плата с микроконтроллером запрятана с глаз долой, но жгут проводов до индикатора печальной змеей пронзает недра прибора.
Настало время для укрощения этого кабельно-ножечного монстра путем усекновения лишних ног, и поможет нам в этом
АЦП и вот такая простая схемка:

А теперь для заинтересовавшихся вкратце принцип работы.
Основную часть времени схема занимается индикацией. Подав на одну ногу 1, а на другую 0, мы зажигаем соответствующий светодиод. Одна комбинация дает зеленый цвет, вторая красный, а если быстро дергать туда-сюда — получаем оранжевый.
С индикацией разобрались, а теперь о кнопках. Предположим, что на Port2 мы подаем логический 0, а Port1 включаем как вход для встроенного АЦП. При этом благодаря резистору R1 мы получаем некоторый небольшой потенциал на светодиоде. Обратите внимание, что в прямом направлении включен именно зеленый светодиод. Как известно, зеленые (и желтые, кстати, тоже) светодиоды имеют ощутимо большее прямое падение напряжения, чем красные. В нашем случае этого напряжения вполне достаточно, чтобы надежно считывать состояние нажатых кнопок. При нажатии любой из них получается делитель напряжения, и теперь достаточно просто оцифровать полученный сигнал и перекодировать в нажатие кнопок. Кстати, даже если перепутать полярность включения проводов — при правильном подборе делителей кнопки сохранят работоспособность, разве что индикация перепутается. Выбор резисторов R3 и R4 достаточно вольный, главное помнить два правила — меньший резистор не должен мешать зажигать светодиоды, а больший резистор должен давать падение напряжения, достаточное для надежного детектирования. Указанные на схеме номиналы я просто поставил из тех, что под рукой валялись, а потом просто в программе прописал получившиеся напряжения. R2 — токоограничивающий, подбираете исходя из используемых светодиодов и параметров ног микроконтроллера, иногда можно обойтись вообще без него.
А теперь собственно библиотека для работы с этой схемой. В данном конкретном случае в качестве платформы использовалась ATMega48, но я старался писать код максимально железонезависимо, и для перехода на другой кристалл достаточно поменять инициализацию порта и АЦП.
Если вдруг у вас оказалась одна лишняя нога, то вполне можно задействовать её pull-up вот в этом месте вместо резистора R1/* * biwire.h */ #ifndef BIWIRE_H_ #define BIWIRE_H_ // Define connections (PORTC only!) #define BWPORT1 1 #define BWPORT2 2 // Set pin functions #define ADCPORT BWPORT1 #define GNDPORT BWPORT2
Здесь можно писать приблизительные значения плюс-минус автобусная остановка. Нам главное — задать границы для точного опеределения кнопок// State for interrupt loop #define ADC_SET 0x01 #define ADC_WAIT 0x02 // LED flashing frequency (in parrots, without small wing) #define SCAN_FREQ 48 // Work together with SCAN_FREQ to get orange color #define MAX_COLOR_MIX 50 // Event bits when scan complete // Main keyboard event indicator - must be cleared after handle #define BW_EVENT 0x80 // Button pressed #define BW_EVBT1 0x01 #define BW_EVBT2 0x02 // add additional flags here, like BW_EVBT3=0x04 // EVFIN - event "Indication finished" #define BW_EVFIN 0x10 // Button released #define BW_EVOPN 0x20 // If button holds more than BW_LONG_PRESS cycles #define BW_EVLNG 0x40 // How long we wait for 'LongPress' #define BW_LONG_PRESS 10
Аналогичным образом добавляете своих сигналов по вкусу// ADC compare values #define BW_VBTN1 0x1F #define BW_VBTN2 0xFE // Define names for signal array enum SignalNames { LedOff = 0, OneGreen = 1, OneRed = 2, OneOrange = 3, TwoGreen = 4, TwoRed = 5, Alarm = 6, OrangeRG = 7
}; // ====== Externals ======= // LED sequence to indicate extern volatile enum SignalNames BwIndicate; // Data about button events extern volatile uint8_t BwButton; // If not null - delay of led indication, then switch LED off extern volatile uint8_t BwLedDelay; // Call on start void biwire_init(void); #endif /* BIWIRE_H_ */
Скачать .h
Ну и сам исходник:
Internal reference у Атмелей всего 1.1 вольта, что при отсутствии нажатых кнопок дает надежное и непробиваемое 0xFF на выходе АЦП./* * biwire.c */ #include <avr/io.h> #include <avr/interrupt.h> #include <stdint.h> #include "biwire.h" // Led blink matrix. Only upper 6 bits used for indication loop static uint8_t bwsignalsR[] = { 0x00, 0x00, 0xC0, 0xC0,0x00, 0xA0, 0xA0, 0xFC }; static uint8_t bwsignalsG[] = { 0x00, 0xC0, 0x00, 0xC0,0xA0, 0x00, 0x50, 0xC0 }; // LED sequence to indicate volatile enum SignalNames BwIndicate; // FSM and sequence counter static volatile uint8_t BwState; // "LongPress" counter static volatile uint8_t btn_cnt; // Data about button events volatile uint8_t BwButton; // Orange color created by mixing green and red static volatile uint8_t color_mix; // If not null - delay of led indication, then switch LED off volatile uint8_t BwLedDelay; void biwire_init(void) { // Timer interrupt TCCR0B = _BV(CS00) | _BV(CS01) | _BV(WGM02); // use CLK/64 prescale value, clear timer/counter on compareA match OCR0A = SCAN_FREQ; // preset timer0 OCR byte TIMSK0 = _BV(OCIE0A); // enable Output Compare 0 overflow interrupt // PIN init DDRC |= _BV(BWPORT1) | _BV(BWPORT2); // ADC init ADCSRA = _BV(ADEN) | _BV(ADPS2); // Activate ADC with Prescaler 16 --> 1Mhz/16 = 62.5kHz ADMUX = ADCPORT | _BV(REFS0) | _BV(REFS1) | _BV(ADLAR); // Select input pin using MUX, left adjust and Internal reference
Таймерное прерывание можете настроить по своему вкусу. Играясь делителями и color_mix можно подбирать скорость мигания и время реакции на нажатие кнопки.DIDR0 = _BV(ADCPORT); // disable digital input for ADC // Set initial state for bw BwState = ADC_SET; BwIndicate = OneGreen; BwButton = BW_EVOPN; color_mix = 0; BwLedDelay = 0; }
Аналогичным образом можно определить диапазон для третьей и всех последующих кнопок, но с точностью срабатывания придется повозитьсяSIGNAL (SIG_OUTPUT_COMPARE0A) // handler for Output Compare 0 overflow interrupt { if (BwState&ADC_SET) { // Prepare to keyscan PORTC &= ~_BV(GNDPORT); DDRC &= ~_BV(ADCPORT); ADCSRA |= _BV(ADSC); //Start conversion BwState = ADC_WAIT; color_mix = MAX_COLOR_MIX; if (BwLedDelay) if (!(--BwLedDelay)) { BwIndicate = LedOff; BwButton |= BW_EVFIN; } } else { if (BwState&ADC_WAIT) { // Check if ADC complete if (ADCSRA & _BV(ADSC) ) return; // Wait another tick PORTB = ADCH; if (ADCH > BW_VBTN2) { // Both buttons open if (BwButton & (BW_EVBT1|BW_EVBT2)) { BwButton = BW_EVENT + BW_EVOPN; btn_cnt = 0; } } else if (ADCH > BW_VBTN1) { // Button 2 pressed PORTB ^= 0xff; if (BwButton & (BW_EVBT1|BW_EVOPN)) { BwButton = BW_EVENT + BW_EVBT2; btn_cnt = 1; }
Если здесь не проскакивать насквозь стейт ADC_WAIT, то можно и седьмой бит в цикл мигания светоидода добавить. Но мне шести бит хватает. А для шестнадцатибитного BwState можно из светодиодов вообще сумасшедшую дискотеку устроить.} else { // Button 1 pressed. Add compare values here if you need more than 2 buttons if (BwButton & (BW_EVBT2|BW_EVOPN)) { BwButton = BW_EVENT + BW_EVBT1; btn_cnt = 1; } } if (btn_cnt) { if(++btn_cnt > BW_LONG_PRESS) { // New event - long press BwButton |= BW_EVENT + BW_EVLNG; btn_cnt = 0; } } // Cleanup if (!(BwButton&(BW_EVBT1|BW_EVBT2|BW_EVOPN))) BwButton = BW_EVOPN; }
if (color_mix++ > MAX_COLOR_MIX) { // Get next state (LED sequence) BwState = (BwState >> 7) | (BwState << 1); // rotate left, replace by _asm to compact code // Indicate DDRC |= _BV(ADCPORT); PORTC &= ~(_BV(GNDPORT)|_BV(ADCPORT)); if(bwsignalsR[BwIndicate]&BwState) PORTC |= _BV(BWPORT2); if((bwsignalsG[BwIndicate]&BwState) && (!(bwsignalsR[BwIndicate]&BwState)) ) PORTC |= _BV(BWPORT1); color_mix = 0; } if((bwsignalsR[BwIndicate]&BwState) && (bwsignalsG[BwIndicate]&BwState)){ PORTC ^= (_BV(BWPORT1) | _BV(BWPORT2)); } } }
Скачать .c
Вот и все. Вставляйте этот код в свои исходники и экономьте провода и ноги. Из минусов — разве что несколько «тупая» реакция на кнопку, но к этому быстро привыкаешь.
Еще можно добавить третий массив после bwsignalsR/bwsignalsG, например bwsignalsB, и с его помощью делать dimming для плавного включения-выключения. Оставлю это вам как домашнее задание :)
Ах да, чуть не забыл. Пример работы с библиотекой:
#include "biwire.h" int main(void) { biwire_init(); sei(); while (1) { if ( BwButton & BW_EVENT ) { BwButton &= ~BW_EVENT; // Clear event flag if (BwButton & BW_EVLNG) { BwIndicate = OrangeRG; BwLedDelay = 5; } else if (BwButton & BW_EVOPN) { //BwIndicate = LedOff; } else if (BwButton & BW_EVBT1) { BwIndicate = OneRed; BwLedDelay = 5; } else if (BwButton & BW_EVBT2) { BwIndicate = OneGreen; BwLedDelay = 5; } } } }
PS: Странно, что-то мне такое смутно помнится, что в старом движке можно было напрямую с PasteBin вставлять исходники? Зачем такую хорошую возможность убрали? Или я что-то путаю?
