В очередном уроке, я поведаю читателю о том, как пишутся статьи на данном сайте, чтобы можно было не только скопировать готовый код, но и проследить за ходом мыслей. Поэтому урок будет в другом формате.
Первым делом выбирается тема изучения. Эта статья — про матричную клавиатуру. Далее нужно разобраться, что это такое, основной источник — википедия. Если там нет информации — гугл. После прочтения 3-5 ссылок, главное понять что оно из себя представляет и зачем вообще нужно. Ответ на первый вопрос должен быть примерно таким — это набор кнопок, включенных и опрашиваемых особым образом. Так почему не использовать обычное включение кнопок? Это ответ на второй вопрос — подобное включение, при использовании 16 и более кнопок, позволяет сэкономить ножки микроконтроллера. Все очень просто, на первом этапе этой информации более чем достаточно.
Теперь следующий шаг, изучение схемы. Во время изучения схемы микроконтроллер представляем черным ящиком, который может либо дрыгать ножками, либо читать информацию с ножек. Смотрим на особенности схемы, на одну ножку приходится 4 кнопки по горизонтали (строки), аналогично 4 кнопки приходится на одну ножку по вертикали (столбцы). Кнопки в пределах одной строки/столбца объединены общим проводом. Получается матрица 4х4.
Теперь нужно понять принцип работы более детально. Ножки, подключенные к общему проводу строк, настраиваются как вход, те что подключены к столбцам настроены как выход. Когда активен только первый столбец, то мы однозначно знаем, что нажаты могут быть только кнопки с 1 по 4. Далее, переключаемся на второй столбец, сканируем кнопки с 5 по 8 и т.д. Остается лишь читать состояния входов. Диоды нужны, чтобы защитить входы микроконтроллера, если нажато несколько кнопок одновременно. Этой информации должно быть достаточно для того, чтобы написать собственную прошивку. Написать свою прошивку это интересно и главное, ты знаешь как она работает. Поэтому я стараюсь все прошивки писать с нуля. Перейдем к программной части.
Начнем с того, что опрос кнопок, сам по себе, не является целью. Возможно в основном цикле будет программа, использующая клавиатуру, поэтому логично производить опрос независимо от основного цикла, через равные промежутки времени. Поможет нам в этом прерывание таймера по совпадению. Его суть в том, что когда таймер сделает заданное количество тиков основная программа встанет на «паузу» и выполнится код записанный отдельно от основного цикла. Этот код и будет нашим опросом.
Можно использовать Таймер1, но он имеет множество полезных функций и может пригодиться для других вещей, поэтому задействуем таймер2. С какой периодичностью производить опрос? С такой, чтобы не пропустить самые быстрые нажатия. Для надежности, решил взять 20 раз в секунду, т.е. прерывание должно происходить раз в 50мс. Таймер2 не очень годится для длинных отсчетов, потому что максимальное число, которое можно записать в регистр сравнения 0xFF = 255. При минимальной частоте таймера 7813Гц, один тик будет длиться 1/7813=0,000128сек, т.е. максимальный промежуток между прерываниями 0,000128*255=32,6мс. В данном случае не принципиально 50 или 32, поэтому этот результат меня устроил.
За одно прерывание нужно опросить 4 столбца, для этого сделаем цикл for, переменная i хранит номер опрашиваемого столбца.
int i=0; // Timer 2 output compare interrupt service routine interrupt [TIM2_COMP] void timer2_comp_isr(void) { for(i=0; i<4; i++) { } } |
Прерывание работает, цикл крутится, нужно добавить переключения столбцов. Для этого, проще всего, создать массив, в котором будут заранее сконфигурированы состояния PORTD, эти состояния будут по очереди записываться в порт.
int i=0; char portState[4]= {0xEF,0xDF,0xBF,0x7F}; // Timer 2 output compare interrupt service routine interrupt [TIM2_COMP] void timer2_comp_isr(void) { for(i=0; i<4; i++) { PORTD=portState[i]; } } |
С переключениями порта разобрались, теперь нужно в течение каждого переключения, проверить каждую строку — не замкнута ли кнопка, т.е. произвести опрос PIND.0-PIND.3. Для этого сделаем в одном цикле еще один цикл, используем for, переменная j хранит номер строки.
int i=0,j=0; char portState[4]= {0xEF,0xDF,0xBF,0x7F}; interrupt [TIM2_COMP] void timer2_comp_isr(void) { for(i=0; i<4; i++) { PORTD=portState[i]; for(j=0; j<4; j++) { } } } |
Теперь нужно добавить сам опрос входа, но если писать if(PIND.0==0), то получится много условий, код будет не компактным, поэтому, вспоминаем про «логическое и»: если число 1 и число 2 равны единице, то результат будет равен единице, во всех остальных случаях результат 0. Добавляем массив, в котором записаны номера пинов от 0 до 3. Выделяем из всего PIND, при помощи «логического и», нужный пин и проверяем равен ли он нулю.
int i=0,j=0; char portState[4]= {0xEF,0xDF,0xBF,0x7F}; char inputState[4]={0x01,0x02,0x04,0x08}; interrupt [TIM2_COMP] void timer2_comp_isr(void) { for(i=0; i<4; i++) { PORTD=portState[i]; for(j=0; j<4; j++) { if(((PIND&inputState[j])==0)) { } } } } |
Неплохо еще вывести на экран какой нибудь символ. Для этого можно создать двухмерный массив, выбор символа, будет зависеть от номера столбца и строки. Символы до 9 соответствуют номерам кнопок, на остальные задействованы буквы. Символ выводится в момент сканирования строки, что очень удобно, так как мы знаем номер столбца и строки.
int i=0,j=0; char portState[4]= {0xEF,0xDF,0xBF,0x7F}; char inputState[4]={0x01,0x02,0x04,0x08}; char symbol[4][4]={{'1','2','3','4'}, {'5','6','7','8'}, {'9','A','B','C'}, {'D','E','F','D'}}; // Timer 2 output compare interrupt service routine interrupt [TIM2_COMP] void timer2_comp_isr(void) { for(i=0; i<4; i++) { PORTD=portState[i]; for(j=0; j<4; j++) { if(((PIND&inputState[j])==0)) { lcd_putchar(symbol[i][j]); } } } } |
Все супер, все работает, но… когда нажимаешь на кнопку один раз выводится сразу несколько символов. Поэтому введем проверку. После того, как кнопка была нажата, программа крутится в пустом бесконечном цикле, до тех пор, пока кнопка не будет отжата. Это позволит выводить символы по одному. В итоговую программу добавлена кнопка для очистки экрана.
#include <mega8.h> // Alphanumeric LCD Module functions #asm .equ __lcd_port=0x18 ;PORTB #endasm #include <lcd.h> int i=0,j=0; char portState[4]= {0xEF,0xDF,0xBF,0x7F}; char inputState[4]={0x01,0x02,0x04,0x08}; char mass2[4][4]={{'1','2','3','4'}, {'5','6','7','8'}, {'9','A','B','C'}, {'D','E','F','D'}}; // Timer 2 output compare interrupt service routine interrupt [TIM2_COMP] void timer2_comp_isr(void) { for(i=0; i<4; i++) { PORTD=portState[i]; for(j=0; j<4; j++) { if(((PIND&inputState[j])==0)) { while((PIND&inputState[j])!=inputState[j]){}; lcd_putchar(mass2[i][j]); } } } } void main(void) { // Port C initialization // Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In // State6=T State5=T State4=T State3=T State2=T State1=P State0=P PORTC=0x03; DDRC=0x00; // Port D initialization // Func7=Out Func6=Out Func5=Out Func4=Out Func3=In Func2=In Func1=In Func0=In // State7=1 State6=1 State5=1 State4=1 State3=P State2=P State1=P State0=P PORTD=0xFF; DDRD=0xF0; // Timer/Counter 2 initialization // Clock source: System Clock // Clock value: 7,813 kHz // Mode: CTC top=OCR2 // OC2 output: Disconnected ASSR=0x00; TCCR2=0x0F; TCNT2=0x00; OCR2=0xC3; // Timer(s)/Counter(s) Interrupt(s) initialization TIMSK=0x80; lcd_init(8); // Global enable interrupts #asm("sei") while (1) { if(PINC.0==0) { lcd_clear(); } }; } |
Примерно в такой последовательности я рассуждал, когда писал данную прошивку. Дальше должна следовать еще отладка на железе. Но в пределах данной статьи ограничусь симулятором.
Все что я хотел сказать данной статьей, что собирать из маленьких готовых кирпичиков гораздо проще, чем пытаться сразу написать все целиком. Разбейте большую задачу на множество маленьких задач и постепенно их решайте. Главное, разобраться с принципом работы устройства и заставить прошивку работать, оптимизировать ее можно после. Прошивка и схема доступны тут.
Update: Зачем нужны диоды? Допустим некий момент времени, когда опрашиваем первый столбец, соответственно на остальных столбцах логическая единица. Если диодов нет и нажать кнопку 1 и 5, то будет короткое замыкание.
#include
// Alphanumeric LCD Module functions
char mass2[4][4]={{‘1′,’2′,’3′,’4’},
{‘5′,’6′,’7′,’8’},
{‘9′,’A’,’B’,’C’},
{‘D’,’E’,’F’,’D’}};
void main(void)
{
PORTB=0x00;
DDRB=0xFF;
while (1)
{
PORTB=(mass2[0][0]);
};
}
здраствуйте. в своей программе не могу осуществить сравнение if chislomass==0…. не могу понять проблему. как будто число «0» и число с массива «0» разные. заметил то что при таких условиях в portb записываються непонятные биты в 4 и 5 разряд. как их убрать? и как сделать так что бы сравнение работало
конечно не одно и тоже 🙂
У меня вопрос?.Порты установленные D установленые в логическую 1?
При одновременном нажатии на несколько клавиш ни одна из них не должна сработать. как это можно реализовать?
после того как кнопка была нажата, вешаете флаг, если в следующий проход кнопка не отжата — выходите из обработчика.
Добрый вечер
с помощью клавиатуры мы выводим числа на экран,
но как можно эти числа использовать?
например управление ШИМом
вывели на экран число «0-100» ШИМ = 0-100%.
Привет. Вы не знаете, как записать в переменную, с помощью этой клавиатуры, например 7 значное число ? Если например во второй старший разряд я хочу записать ноль, допустим 7065783, это нужно записать в переменную, каким образом это сделать ? Интереснен момент с нулями в разрядах
Здравствуйте. А как можно реализовать отправку набранных символов только после нажатия кнопки * ?
если не * — сохраняете нажатия в буфер, если * то отправляете буфер