VCC, GND, RESET, SCK, MOSI, MISO
с соответствующими выходами микроконтроллера. Для простоты я собрал вспомогательную схему прямо на макетной плате:/etc/udev/rules.d/41-atmega.rules
:# USBasp programmer
SUBSYSTEM=="usb", ATTR{idVendor}=="16c0", ATTR{idProduct}=="05dc", GROUP="plugdev", MODE="0666"
service udev restart
“Игрок 1”
, “Игрок 2”
, “Сброс”
, “Настройка”
и “Пауза”
. Ну и не забываем про звуковую индикацию окончания игры. Вроде все. На рисунке ниже представлена общая схема подключения микроконтроллера к индикаторам и кнопкам. Она понадобится нам при разборе исходного кода программы:main
. На самом деле ничего примечательного в ней нет — настройка портов, инициализация данных и бесконечный цикл обработки нажатий кнопок. Ну и вызов sei()
— разрешение обработки прерываний, о них немного позже.int main(void)
{
init_io();
init_data();
sound_off();
sei();
while(1)
{
handle_buttons();
}
return 0;
}
void init_io()
{
// set output
DDRB = 0xFF;
DDRD = 0xFF;
// set input
DDRC = 0b11100000;
// pull-up resistors
PORTC |= 0b00011111;
// timer interrupts
TIMSK = (1<<OCIE1A) | (1<<TOIE0);
TCCR0 |= (1 << CS01) | (1 << CS00);
TCCR1B = (1<<CS12|1<<WGM12);
//OCRn = (clock_speed / prescaler) * seconds - 1
OCR1A = (F_CPU / 256) * 1 -1;
}
DDRC = 0b11100000;
превращает первые 5 пинов порта C во входные пины, а оставшиеся — в выходные. Команда PORTC |= 0b00011111;
включает внутренние подтягивающие резисторы на 5 входах контроллера. Согласно схеме, к этим входам подключены кнопки, которые при нажатии замкнут их на землю. Таким образом контроллер понимает, что кнопка нажата. ISR (TIMER0_OVF_vect)
{
display();
if (_buzzer > 0)
{
_buzzer--;
if (_buzzer == 0)
sound_off();
}
}
ISR(TIMER1_COMPA_vect)
{
if (ActiveTimer == 1 && Timer1 > 0)
{
Timer1--;
if (Timer1 == 0)
process_timeoff();
}
if (ActiveTimer == 2 && Timer2 > 0)
{
Timer2--;
if (Timer2 == 0)
process_timeoff();
}
}
void display()
{
display_number((Timer1/60)/10, 0b00001000);
_delay_ms(0.25);
display_number((Timer1/60)%10, 0b00000100);
_delay_ms(0.25);
display_number((Timer1%60)/10, 0b00000010);
_delay_ms(0.25);
display_number((Timer1%60)%10, 0b00000001);
_delay_ms(0.25);
display_number((Timer2/60)/10, 0b10000000);
_delay_ms(0.25);
display_number((Timer2/60)%10, 0b01000000);
_delay_ms(0.25);
display_number((Timer2%60)/10, 0b00100000);
_delay_ms(0.25);
display_number((Timer2%60)%10, 0b00010000);
_delay_ms(0.25);
PORTD = 0;
}
void display_number(int number, int mask)
{
PORTB = number_mask(number);
PORTD = mask;
}
display
использует метод динамической индикации. Дело в том, что каждый отдельно взятый индикатор имеет 9 контактов (7 для управления сегментами, 1 для точки и 1 для питания). Для управления 4 цифрами понадобилось бы 36 контактов. Слишком расточительно. Поэтому вывод разрядов на индикатор с несколькими цифрами организован по следующему принципу:PORTD = 0;
). Если этого не сделать, то последняя выводимая цифра будет продолжать гореть до следующего вызова функции display, что приведет к ее более яркому свечению по сравнению с остальными. void handle_buttons()
{
handle_button(KEY_SETUP);
handle_button(KEY_RESET);
handle_button(KEY_PAUSE);
handle_button(KEY_PLAYER1);
handle_button(KEY_PLAYER2);
}
void handle_button(int key)
{
int bit;
switch (key)
{
case KEY_SETUP: bit = SETUP_BIT; break;
case KEY_RESET: bit = RESET_BIT; break;
case KEY_PAUSE: bit = PAUSE_BIT; break;
case KEY_PLAYER1: bit = PLAYER1_BIT; break;
case KEY_PLAYER2: bit = PLAYER2_BIT; break;
default: return;
}
if (bit_is_clear(BUTTON_PIN, bit))
{
if (_pressed == 0)
{
_delay_ms(DEBOUNCE_TIME);
if (bit_is_clear(BUTTON_PIN, bit))
{
_pressed |= key;
// key action
switch (key)
{
case KEY_SETUP: process_setup(); break;
case KEY_RESET: process_reset(); break;
case KEY_PAUSE: process_pause(); break;
case KEY_PLAYER1: process_player1(); break;
case KEY_PLAYER2: process_player2(); break;
}
sound_on(15);
}
}
}
else
{
_pressed &= ~key;
}
}
bit_is_clear(BUTTON_PIN, bit)
, т.е. кнопка нажата в том случае, если соответствующий ей вход соединен с землей, что и произойдет, согласно схеме, при нажатии кнопки. Задержка длительностью DEBOUNCE_TIME
и повторная проверка нужна во избежание множественных лишних срабатываний из-за дребезга контактов. Сохранение статуса нажатия в соответствующих битах переменной _pressed
используется для исключения повторного срабатывания при длительном нажатии на кнопку. #define F_CPU 4000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#define DEBOUNCE_TIME 20
#define BUTTON_PIN PINC
#define SETUP_BIT PC0
#define RESET_BIT PC1
#define PAUSE_BIT PC2
#define PLAYER1_BIT PC3
#define PLAYER2_BIT PC4
#define KEY_SETUP 0b00000001
#define KEY_RESET 0b00000010
#define KEY_PAUSE 0b00000100
#define KEY_PLAYER1 0b00001000
#define KEY_PLAYER2 0b00010000
volatile int ActiveTimer = 0;
volatile int Timer1 = 0;
volatile int Timer2 = 0;
volatile int _buzzer = 0;
volatile int _pressed = 0;
// function declarations
void init_io();
void init_data();
int number_mask(int num);
void handle_buttons();
void handle_button(int key);
void process_setup();
void process_reset();
void process_pause();
void process_timeoff();
void process_player1();
void process_player2();
void display();
void display_number(int mask, int number);
void sound_on(int interval);
void sound_off();
// interrupts
ISR (TIMER0_OVF_vect)
{
display();
if (_buzzer > 0)
{
_buzzer--;
if (_buzzer == 0)
sound_off();
}
}
ISR(TIMER1_COMPA_vect)
{
if (ActiveTimer == 1 && Timer1 > 0)
{
Timer1--;
if (Timer1 == 0)
process_timeoff();
}
if (ActiveTimer == 2 && Timer2 > 0)
{
Timer2--;
if (Timer2 == 0)
process_timeoff();
}
}
int main(void)
{
init_io();
init_data();
sound_off();
sei();
while(1)
{
handle_buttons();
}
return 0;
}
void init_io()
{
// set output
DDRB = 0xFF;
DDRD = 0xFF;
// set input
DDRC = 0b11100000;
// pull-up resistors
PORTC |= 0b00011111;
// timer interrupts
TIMSK = (1<<OCIE1A) | (1<<TOIE0);
TCCR0 |= (1 << CS01) | (1 << CS00);
TCCR1B = (1<<CS12|1<<WGM12);
//OCRn = (clock_speed / prescaler) * seconds - 1
OCR1A = (F_CPU / 256) * 1 -1;
}
void init_data()
{
Timer1 = 0;
Timer2 = 0;
ActiveTimer = 0;
}
int number_mask(int num)
{
switch (num)
{
case 0 : return 0xC0;
case 1 : return 0xF9;
case 2 : return 0xA4;
case 3 : return 0xB0;
case 4 : return 0x99;
case 5 : return 0x92;
case 6 : return 0x82;
case 7 : return 0xF8;
case 8 : return 0x80;
case 9 : return 0x90;
};
return 0;
}
void process_setup()
{
Timer1 += 60;
Timer2 += 60;
// overflow check (5940 seconds == 99 minutes)
if (Timer1 > 5940 || Timer2 > 5940)
{
Timer1 = 0;
Timer2 = 0;
}
}
void process_reset()
{
init_data();
}
void process_timeoff()
{
init_data();
sound_on(30);
}
void process_pause()
{
ActiveTimer = 0;
}
void process_player1()
{
ActiveTimer = 2;
}
void process_player2()
{
ActiveTimer = 1;
}
void handle_button(int key)
{
int bit;
switch (key)
{
case KEY_SETUP: bit = SETUP_BIT; break;
case KEY_RESET: bit = RESET_BIT; break;
case KEY_PAUSE: bit = PAUSE_BIT; break;
case KEY_PLAYER1: bit = PLAYER1_BIT; break;
case KEY_PLAYER2: bit = PLAYER2_BIT; break;
default: return;
}
if (bit_is_clear(BUTTON_PIN, bit))
{
if (_pressed == 0)
{
_delay_ms(DEBOUNCE_TIME);
if (bit_is_clear(BUTTON_PIN, bit))
{
_pressed |= key;
// key action
switch (key)
{
case KEY_SETUP: process_setup(); break;
case KEY_RESET: process_reset(); break;
case KEY_PAUSE: process_pause(); break;
case KEY_PLAYER1: process_player1(); break;
case KEY_PLAYER2: process_player2(); break;
}
sound_on(15);
}
}
}
else
{
_pressed &= ~key;
}
}
void handle_buttons()
{
handle_button(KEY_SETUP);
handle_button(KEY_RESET);
handle_button(KEY_PAUSE);
handle_button(KEY_PLAYER1);
handle_button(KEY_PLAYER2);
}
void display()
{
display_number((Timer1/60)/10, 0b00001000);
_delay_ms(0.25);
display_number((Timer1/60)%10, 0b00000100);
_delay_ms(0.25);
display_number((Timer1%60)/10, 0b00000010);
_delay_ms(0.25);
display_number((Timer1%60)%10, 0b00000001);
_delay_ms(0.25);
display_number((Timer2/60)/10, 0b10000000);
_delay_ms(0.25);
display_number((Timer2/60)%10, 0b01000000);
_delay_ms(0.25);
display_number((Timer2%60)/10, 0b00100000);
_delay_ms(0.25);
display_number((Timer2%60)%10, 0b00010000);
_delay_ms(0.25);
PORTD = 0;
}
void display_number(int number, int mask)
{
PORTB = number_mask(number);
PORTD = mask;
}
void sound_on(int interval)
{
_buzzer = interval;
// put buzzer pin high
PORTC |= 0b00100000;
}
void sound_off()
{
// put buzzer pin low
PORTC &= ~0b00100000;
}
Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.
Комментарии (63)