вчера в 19:28

Вы еще не программируете микроконтроллеры? Тогда мы идем к вам! из песочницы

Здравствуйте, уважаемые Хабражители!

В этой статье я хочу рассказать о том, как однажды решил начать программировать микроконтроллеры, что для этого понадобилось и что в итоге получилось.

Тема микроконтроллеров меня заинтересовала очень давно, году этак в 2001. Но тогда достать программатор по месту жительства оказалось проблематично, а о покупке через Интернет и речи не было. Пришлось отложить это дело до лучших времен. И вот, в один прекрасный день я обнаружил, что лучшие времена пришли не выходя из дома можно купить все, что мне было нужно. Решил попробовать. Итак, что нам понадобится:

1. Программатор

На рынке предлагается много вариантов — от самых дешевых ISP (In-System Programming) программаторов за несколько долларов, до мощных программаторов-отладчиков за пару сотен. Не имея большого опыта в этом деле, для начала я решил попробовать один из самых простых и дешевых — USBasp. Купил в свое время на eBay за $12, сейчас можно найти даже за $3-4. На самом деле это китайская версия программатора от Thomas Fischl. Что могу сказать про него? Только одно — он работает. К тому же поддерживает достаточно много AVR контроллеров серий ATmega и ATtiny. Под Linux не требует драйвера.



Для прошивки надо соединить выходы программатора VCC, GND, RESET, SCK, MOSI, MISO с соответствующими выходами микроконтроллера. Для простоты я собрал вспомогательную схему прямо на макетной плате:

image


Слева на плате — тот самый микроконтроллер, который мы собираемся прошивать.

2. Микроконтроллер

С выбором микроконтроллера я особо не заморачивался и взял ATmega8 от Atmel — 23 пина ввода/вывода, два 8-битных таймера, один 16-битный, частота — до 16 Мгц, маленькое потребление (1-3.6 мА), дешевый ($2). В общем, для начала — более чем достаточно.

image


Под Linux для компиляции и загрузки прошивки на контроллер отлично работает связка avr-gcc + avrdude. Установка тривиальная. Следуя инструкции, можно за несколько минут установить все необходимое ПО. Единственный ньюанс, на который следует обратить внимание — avrdude (ПО для записи на контроллер) может потребовать права супер-пользователя для доступа к программатору. Выход — запустить через sudo (не очень хорошая идея), либо прописать специальные udev права. Синтаксис может отличаться в разных версиях ОС, но в моем случае (Linux Mint 15) сработало добавление следующего правила в файл /etc/udev/rules.d/41-atmega.rules:

# USBasp programmer
SUBSYSTEM=="usb", ATTR{idVendor}=="16c0", ATTR{idProduct}=="05dc", GROUP="plugdev", MODE="0666"


После этого, естественно, необходим перезапуск сервиса
service udev restart

Компилировать и прошивать без проблем можно прямо из командной строки (кто бы сомневался), но если проектов много, то удобнее поставить плагин AVR Eclipse и делать все прямо из среды Eclipse.

Под Windows придется поставить драйвер. В остальном проблем нет. Ради научного интереса попробовал связку AVR Studio + eXtreme Burner в Windows. Опять-таки, все работает на ура.

Начинаем программировать


Программировать AVR контроллеры можно как на ассемблере (AVR assembler), так и на Си. Тут, думаю, каждый должен сделать свой выбор сам в зависимости от конкретной задачи и своих предпочтений. Лично я в первую очередь начал ковырять ассемблер. При программировании на ассемблере архитектура устройства становится понятнее и появляется ощущение, что копаешься непосредственно во внутренностях контроллера. К тому же полагаю, что в особенно критических по размеру и производительности программах знание ассемблера может очень пригодиться. После ознакомления с AVR ассемблером я переполз на Си.

После знакомства с архитектурой и основными принципами, решил собрать что-то полезное и интересное. Тут мне помогла дочурка, она занимается шахматами и в один прекрасный вечер заявила, что хочет иметь часы-таймер для партий на время. БАЦ! Вот она — идея первого проекта! Можно было конечно заказать их на том же eBay, но захотелось сделать свои собственные часы, с блэк… эээ… с индикаторами и кнопочками. Сказано — сделано!

В качестве дисплея решено было использовать два 7-сегментных диодных индикатора. Для управления достаточно было 5 кнопок — “Игрок 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;
}


Настройка портов ввода/вывода происходит очень просто — в регистр DDRx (где x — буква, обозначающая порт) записивается число, каждый бит которого означает, будет ли соответствующий пин устройством ввода (соответствует 0) либо вывода (соответствует 1). Таким образом, заслав в DDRB и DDRD число 0xFF, мы сделали B и D портами вывода. Соответственно, команда DDRC = 0b11100000; превращает первые 5 пинов порта C во входные пины, а оставшиеся — в выходные. Команда PORTC |= 0b00011111; включает внутренние подтягивающие резисторы на 5 входах контроллера. Согласно схеме, к этим входам подключены кнопки, которые при нажатии замкнут их на землю. Таким образом контроллер понимает, что кнопка нажата.

Далее следует настройка двух таймеров, Timer0 и Timer1. Первый мы используем для обновления индикаторов, а второй — для обратного отсчета времени, предварительно настроив его на срабатывание каждую секунду. Подробное описание всех констант и метода настройки таймера на определенноый интервал можно найти в документации к ATmega8.

Обработка прерываний

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();
	}
}


При срабатывании таймера управление передается соответствующему обработчику прерывания. В нашем случае это обработчик TIMER0_OVF_vect, который вызывает процедуру вывода времени на индикаторы, и TIMER1_COMPA_vect, который обрабатывает обратный отсчет.

Вывод на индикаторы

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 контактов. Слишком расточительно. Поэтому вывод разрядов на индикатор с несколькими цифрами организован по следующему принципу:



Напряжение поочередно подается на каждый из общих контактов, что позволяет высветить на соответствующем индикаторе нужную цифру при помощи одних и тех же 8 управляющих контактов. При достаточно высокой частоте вывода это выглядит для глаза как статическая картинка. Именно поэтому все 8 питающих контактов обоих индикаторов на схеме подключены к 8 выходам порта D, а 16 управляющих сегментами контактов соединены попарно и подключены к 8 выходам порта B. Таким образом, функция display с задержкой в 0.25 мс попеременно выводит нужную цифру на каждый из индикаторов. Под конец отключаются все выходы, подающие напряжение на индикаторы (команда 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;
	}
}


Эта функция по очереди опрашивает все 5 кнопок и обрабатывает нажатие, если таковое случилось. Нажатие регистрируется проверкой 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;
}



Прототип был собран на макетной плате:



После тестирования прототипа пришло время все это добро разместить в корпусе, обеспечить питание и т.д.



Ниже показан окончательный вид устройства. Часы питаются от 9-вольтовой батарейки типа “Крона”. Потребление тока — 55 мА.



Заключение


Потратив $20-25 на оборудование и пару вечеров на начальное ознакомление с архитектурой микроконтроллера и основными принципами работы, можно начать делать интересные DIY проекты. Статья посвящается тем, кто, как и я в свое время, думает, что начать программировать микроконтроллеры — это сложно, долго или дорого. Поверьте, начать намного проще, чем может показаться. Если есть интерес и желание — пробуйте, не пожалете!

Удачного всем программирования!

P.S. Ну и напоследок, небольшая видео-демонстрация прототипа:

16292
349
ssha 47,5

комментарии (36)

+15
MainNika, #
Вспоминается крутая статья уважаемого barsmonster и слова в конце:
«Вы все еще покупаете 8-и битные AVR-рки по 200 рублей? Тогда ARM идет к вам!»
Хотя программаторы, видимо, там будут подороже.
+1
ssha, #
Спасибо за ссылку, интересная статья!
+4
faddistr, #
Подойдет SWD программатор-отладчик в составе любой из discovery плат от stm32. Единственный недостаток: дип-корпусов нет.
+1
dcoder_mm, #
Во первых это не такой уж недостаток, во вторых, в DIP есть LPC1114 (шаг выводов у него привычный — 2.54мм)
0
CodeRush, #
Программатор там делается из любого устройства на чипе FT2232 (и аналоге), которые стоят дешево, а сделать из них можно просто прорву всего.
В том числе и программатор\отладчик по JTAG.
Надо будет статью написать про этот чип, наверное.
+2
CodeRush, #
0
Magistr_AVSH, #
Эхх, а я в своё время программатор сам собирал, через LPT порт. Все теперь стало так просто, что даже подумываю откопать из шкафов паяльник.
+1
nesferatos, #
а я в армии свой первый LPT программатор спаял



0
Falling, #
А есть какие то набора для новичков? Программатор + плата для прототипирования + один(несколько) контроллеров + индикаторы/датчики
0
faddistr, #
Например такие
+1
sck_v, #
> Но тогда достать программатор по месту жительства оказалось проблематично, а о покупке через Интернет и речи не было.
Но а как же «5 проводков»?
0
KoteSoft, #
… или даже программатор Громова.
+1
iliasam, #
А вот здесь человека, начинающего работать с контроллерами, не остановило отсутствие программатора и документации.
0
imwode, #
Перепрошив это устройство можно сделать игру — рендомные числа по нажатию кнопки выводятся на индикатор. Выигрывает тот, у кого число больше. Можно решать споры или определять очередность подхода к снаряду во время застолья, можно мини-турниры устраивать.
+1
evocatus, #
Что меня удивило на схеме: всё подключено напрямую, никаких резисторов, конденсаторов и т.д. Это ок?
–1
imwode, #
обычно нет, но у современных МК не так просто выжечь ноги и они максимум 20мА отдают. Плюс там не постоянно сегменты светятся, а только часть периода. Так что жить будет.
+7
evocatus, #
Написал бы кто-нибудь эдакий гайдик по подключению всяких компонентов в цифровых схемах: джентльменский набор, исключающий перегрев, перегрузку и т.д.
+5
ssha, #
Отличная идея, сам бы с удовольствием почитал
+2
CRImier, #
Навскидку:
Конденсаторы по питанию — электролиты(+ иногда керамика)
Резисторы на светодиоды + тонкости типа «не ставить один резистор на два светодиода если они будут гореть одновременно»
Использование транзисторов, когда нужно управлять чем-то, чей ток превышает максимальный ток пина МК (добавить про ULN2003)
Осторожное использование регулятора 3.3 вольта на Arduino/Raspberry Pi/и так далее (макс. ток)
Защита от перенапряжения на 5 вольт
Конденсаторы на землю с каждой ноги резонатора (не знаю, правда, зачем)
Подтяжка резисторами линий данных к плюсу или минусу (также кнопок, светодиодов и прочего) и неиспользуемых контактов у микросхем
Защита от дребезга контактов
Объяснение про ёмкость проводов
Защита микросхем и прочего от статики
Фильтры напряжения
Длина проводов, по которым передаются данные, правильное экранирование
Диоды на выходе источников питания для защиты
Правильное заземление приборов
Советы для траблшутинга (даже и особенно те, что сам заучил из-за горького опыта)
Советы по пайке чего-нибудь в ограниченных условиях, вещи, необходимые для пайки, поскольку значительно её облегчают
Уход за паяльником
Использование различных клеёв — Момент/горячий/эпоксидный — в своих поделках
Правильная разводка высокочастотных компонентов

Тут многие детали, конечно, опциональны, но всё же… Часть из этого хотел бы узнать сам, ну и объяснения некоторых явлений =) Реально, статья была бы отличной, только у самого не хватит ни знаний теоретических, ни опыта — напишу, а мне в комментариях вышлют кучу поправок и посоветуют не лезть, не зная =( Много кому бы вправила мозги. Я джва года жду такую статью, короче.
0
Pugnator, #
Это Proteus, там это не важно. Я имею в виду, что для быстрого прототипирования можно подключать диоды как есть. Точно так же, как и кварцевый резонатор можно не вешать — он все равно не симулируется. Вообще про баги/особеннопсти Proteus можно целую статью сдулать… и не одну
0
grossws, #
Особенно про симуляцию MAC-уровня с выводом на реальный tap-адаптер…
+2
imwode, #
Не постесняюсь на себя сослаться — может что интересное для себя вновь прибывшие найдут
habrahabr.ru/post/128904/
0
FloppyFormator, #
Зачем запитывать пятивольтовый контролер от кроны, рассеивая в тепло лишние 4 вольта? Хватило бы трёх-четырёх батареек AA.
0
evocatus, #
В такок корпус можно было бы запихнуть адаптер для зарядки телефонов.
0
ssha, #
Хотелось мобильности…
0
ssha, #
Я рассуждал так: при рабочем напряжении 4.5-5.5 В 3 батарейки вроде как маловато (быстро просядут), а 4 уже много и, скорее всего, надо стабилизировать. А у стабилизатора 7805 рабочее напряжение вроде как от 7 до 20 В. В общем, если эти рассуждения неверны, прошу сильно не пинать и поправить, это не совсем мой профиль.
0
FloppyFormator, #
Контроллер контроллеру рознь, конечно. Я запитывал Arduino шестью вольтами (четыре банки по 1,5В) — работал и не дымился. Вот от трёх не пробовал, поэтому не знаю, на каком напряжении вырубится.

Банки можно взять маленькие: половинка ААА или таблетка.
0
legrus, #
Пауза должна ставиться одновременным нажатием на кнопки игроков.
0
ssha, #
Спасибо, замечание принято. Я слишком поздно об этом вспомнил.
0
nesferatos, #
А я бы опрос кнопок повесил на внешнее прерывание, чтобы основной цикл не забивать.
Будет достаточно одного внешнего прерывания, к которому через диоды подключены все кнопки. В момент срабатывания прерывания проводить опрос, какие именно кнопки нажаты.
0
ssha, #
Я думал о внешнем прерывании, но после того, как индикация была вынесена из основного цикла в прерывание и ей нажатие кнопок больше не мешало, решил оставить так.
–4
vladzzag, #
Название напомнило с рекламы Tide: «Вы еще не пользуетесь Tide? Тогда мы идем к вам!»
+5
vvzvlad, #
Спасибо, КО!
0
CRImier, #
Ну как же Вы так? Ни транзисторов на выходе пинов МК, управляющих светодиодами, ни резисторов перед светодиодами, кнопки не подтянуты к какому-либо логическому уровню… Советую поизучать базовые советы, иначе будут непонятные глюки и отказы — а это довольно неприятно =(
Но в целом работа хорошая! Пару допилов — и будет стабильное устройство, которое проработает долго =) Удачи Вам в радиоэлектронике!
0
ssha, #
Спасибо за совет. На самом деле кнопки подтянуты внутренними резисторами, это указано в тексте.
0
CRImier, #
Ой, простите, не заметил, видимо =) Правда, иногда их может и не хватить, как было указано в некоторых прочитанных мной книгах, там рекомендовали для надёжности поставить ещё и внешние. Ну да ладно, думаю, не Ваш случай.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.