Подавляющее большинство работавших с микроконтроллерами однажды оказывались перед сложным выбором:
— Так, на эту ногу ШИМ, на эту — кнопку, на эти — светодиоды… Оп-па… А ноги-то закончились. Без светодиодов некрасиво, без кнопок нефункционально. Придется брать кристалл пожирнее :(
Или другая ситуация — плата с микроконтроллером запрятана с глаз долой, но жгут проводов до индикатора печальной змеей пронзает недра прибора.
Настало время для укрощения этого кабельно-ножечного монстра путем усекновения лишних ног, и поможет нам в этом
АЦП и вот такая простая схемка:
А теперь для заинтересовавшихся вкратце принцип работы.
Основную часть времени схема занимается индикацией. Подав на одну ногу 1, а на другую 0, мы зажигаем соответствующий светодиод. Одна комбинация дает зеленый цвет, вторая красный, а если быстро дергать туда-сюда — получаем оранжевый.
С индикацией разобрались, а теперь о кнопках. Предположим, что на Port2 мы подаем логический 0, а Port1 включаем как вход для встроенного АЦП. При этом благодаря резистору R1 мы получаем некоторый небольшой потенциал на светодиоде. Обратите внимание, что в прямом направлении включен именно зеленый светодиод. Как известно, зеленые (и желтые, кстати, тоже) светодиоды имеют ощутимо большее прямое падение напряжения, чем красные. В нашем случае этого напряжения вполне достаточно, чтобы надежно считывать состояние нажатых кнопок. При нажатии любой из них получается делитель напряжения, и теперь достаточно просто оцифровать полученный сигнал и перекодировать в нажатие кнопок. Кстати, даже если перепутать полярность включения проводов — при правильном подборе делителей кнопки сохранят работоспособность, разве что индикация перепутается. Выбор резисторов R3 и R4 достаточно вольный, главное помнить два правила — меньший резистор не должен мешать зажигать светодиоды, а больший резистор должен давать падение напряжения, достаточное для надежного детектирования. Указанные на схеме номиналы я просто поставил из тех, что под рукой валялись, а потом просто в программе прописал получившиеся напряжения. R2 — токоограничивающий, подбираете исходя из используемых светодиодов и параметров ног микроконтроллера, иногда можно обойтись вообще без него.
А теперь собственно библиотека для работы с этой схемой. В данном конкретном случае в качестве платформы использовалась ATMega48, но я старался писать код максимально железонезависимо, и для перехода на другой кристалл достаточно поменять инициализацию порта и АЦП.
Скачать .h
Ну и сам исходник:
Скачать .c
Вот и все. Вставляйте этот код в свои исходники и экономьте провода и ноги. Из минусов — разве что несколько «тупая» реакция на кнопку, но к этому быстро привыкаешь.
Еще можно добавить третий массив после bwsignalsR/bwsignalsG, например bwsignalsB, и с его помощью делать dimming для плавного включения-выключения. Оставлю это вам как домашнее задание :)
Ах да, чуть не забыл. Пример работы с библиотекой:
PS: Странно, что-то мне такое смутно помнится, что в старом движке можно было напрямую с PasteBin вставлять исходники? Зачем такую хорошую возможность убрали? Или я что-то путаю?
— Так, на эту ногу ШИМ, на эту — кнопку, на эти — светодиоды… Оп-па… А ноги-то закончились. Без светодиодов некрасиво, без кнопок нефункционально. Придется брать кристалл пожирнее :(
Или другая ситуация — плата с микроконтроллером запрятана с глаз долой, но жгут проводов до индикатора печальной змеей пронзает недра прибора.
Настало время для укрощения этого кабельно-ножечного монстра путем усекновения лишних ног, и поможет нам в этом
АЦП и вот такая простая схемка:
А теперь для заинтересовавшихся вкратце принцип работы.
Основную часть времени схема занимается индикацией. Подав на одну ногу 1, а на другую 0, мы зажигаем соответствующий светодиод. Одна комбинация дает зеленый цвет, вторая красный, а если быстро дергать туда-сюда — получаем оранжевый.
С индикацией разобрались, а теперь о кнопках. Предположим, что на Port2 мы подаем логический 0, а Port1 включаем как вход для встроенного АЦП. При этом благодаря резистору R1 мы получаем некоторый небольшой потенциал на светодиоде. Обратите внимание, что в прямом направлении включен именно зеленый светодиод. Как известно, зеленые (и желтые, кстати, тоже) светодиоды имеют ощутимо большее прямое падение напряжения, чем красные. В нашем случае этого напряжения вполне достаточно, чтобы надежно считывать состояние нажатых кнопок. При нажатии любой из них получается делитель напряжения, и теперь достаточно просто оцифровать полученный сигнал и перекодировать в нажатие кнопок. Кстати, даже если перепутать полярность включения проводов — при правильном подборе делителей кнопки сохранят работоспособность, разве что индикация перепутается. Выбор резисторов R3 и R4 достаточно вольный, главное помнить два правила — меньший резистор не должен мешать зажигать светодиоды, а больший резистор должен давать падение напряжения, достаточное для надежного детектирования. Указанные на схеме номиналы я просто поставил из тех, что под рукой валялись, а потом просто в программе прописал получившиеся напряжения. R2 — токоограничивающий, подбираете исходя из используемых светодиодов и параметров ног микроконтроллера, иногда можно обойтись вообще без него.
А теперь собственно библиотека для работы с этой схемой. В данном конкретном случае в качестве платформы использовалась ATMega48, но я старался писать код максимально железонезависимо, и для перехода на другой кристалл достаточно поменять инициализацию порта и АЦП.
/*
* 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
Если вдруг у вас оказалась одна лишняя нога, то вполне можно задействовать её pull-up вот в этом месте вместо резистора R1
// 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
Ну и сам исходник:
/*
* 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
Internal reference у Атмелей всего 1.1 вольта, что при отсутствии нажатых кнопок дает надежное и непробиваемое 0xFF на выходе АЦП. 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;
}
Таймерное прерывание можете настроить по своему вкусу. Играясь делителями и color_mix можно подбирать скорость мигания и время реакции на нажатие кнопки.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;
}
Аналогичным образом можно определить диапазон для третьей и всех последующих кнопок, но с точностью срабатывания придется повозиться } 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;
}
Если здесь не проскакивать насквозь стейт ADC_WAIT, то можно и седьмой бит в цикл мигания светоидода добавить. Но мне шести бит хватает. А для шестнадцатибитного BwState можно из светодиодов вообще сумасшедшую дискотеку устроить. 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 вставлять исходники? Зачем такую хорошую возможность убрали? Или я что-то путаю?