Логотип

Если Вы хотите обучаться программированию микроконтроллеров, но попали, сюда не прочитав предыдущих уроков, то советую начать изучение материала с самого начала.

Программирование микроконтроллеров. Урок 9
Заканчиваем с циклами и подключаем кнопку

Урок 9 (Заканчиваем с циклами и подключаем кнопку)

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

Но перед этим я бы хотел прояснить некоторые нюансы прошлого урока и поговорить о домашнем задании. Самые наблюдательные и самые терпеливые, те кто дождался конца счета, наверняка заметили то, что наш счетчик не отображает число 255. Это происходит по тому, что структура цикла «for» не позволяет нам выполнить код цикла, когда счетчик равен нулю. Т.к. этот цикл с предусловием и проверка происходит до исполнения очеродной интерации. Как только счетчик доходит до нуля, цикл прекращает свою работу и все повторяется с начала, а значение 255 не отображается на индикаторе. Уверен, что те, кто выполнил домашнее задание и заставил светодиод «бегать» по линейке уже столкнулись с этой трудностью.

Полноценный код для нашего счетчика должен выглядеть вот так.

/*
Уроки по изучению основ программирования микроконтроллеров
Тестовая программа к уроку 8_1
Системы счисления
Сайт: red-resisror.ru
*/
    #include <mega8.h> 		//апноты
	
    #define CLEAN 0 		//Очистка
    #define PAUSA 10000 	//пауза
    #define SCHET     255	//Начало отсчета

//Объявляем подпрограммы
void pausa (unsigned long int c_pausa); //подпрограмма паузы


//----------------------------------------------------------------------------------
void main(void){
//Рабочие переменные и настройки
	unsigned char c_binary = CLEAN;          //счетчик

	PORTD = 0x00;      	//Проверка работоспособности всех диодов
	DDRD = 0xff; 	//Инициализация вывода порта как выход
	pausa (PAUSA);  	//Задержка отображения всей линейки
	PORTD = 0xff;   	//гасим все светодиоды

while (1){    //Тело программы
	for (c_binary = SCHET;c_binary!=0;c_binary--){
		PORTD = c_binary;
		pausa (PAUSA);
	};
	
	PORTD = CLEAN;       //Показываем число 255
	pausa (PAUSA);
	
    } //END while
} //END main
//----------------------------------------------------------------------------------
//подпограмма паузы
void pausa (unsigned long int c_pausa) {
    for (c_pausa;c_pausa!=0;c_pausa--){};
}

Из листинга видно, что мы после окончания основного цикла добавляем краевое значение для отображения. После которого обязательно должна быть пауза. Иначе мы его не увидим. Прошейте эту программу на МК и посмотрите как это работает. А что бы не ждать окончания счета, можно в начальных значениях поставить вместо 255 цифру близкую к нулю.


Теперь давайте поговорим о домашнем задании. Безусловно его можно было сделать разными способами, но т.к. мы изучали циклы я надеялся, что, встретившись с вышеописанной проблемой циклов с предусловием и почитав о циклах «while» и «do-while» попробуете сделать программу на них. Точнее на цикле «do-while». Из-за того, что этот цикл проверяет условие после исполнения кода цикла, то он корректно работает с краевыми условиями. Когда счетчик будет нулевым он все равно выполнит код и только после этого организует выход из цикла. Но с применением этих циклов тоже есть нюансы. Т.к. в этих циклах указывается только условие выхода, то всё остальное связанное с изменением счетчика и начальных значений приходится прописывать самому. Наша программа для бегающего огонька будет иметь следующую блок-схему.

Блок-схема 1

Мы видим, что для корректной работы цикла необходимо задать начальные значения счетчика до входа в него, а изменение счетчика прописывать в явном виде как строку кода. И да… Прошу обратить внимание на то, что переменная счетчика у нас не char, а int!

Исполнительный код программы можно посмотреть в прилагаемых файлах. И вообще я советую поупражняться в написании программ отображающих разные световые эффекты.


При изучении циклов нельзя не упомянуть об операторах «break» и «continue». Эти операторы используют для досрочного выхода из цикла «break» или для пропуска одной из итерации цикла «continue». Например, если нам надо чтобы счетчик не отображал цифру 7, то в нашу программу необходимо дописать строку с «continue».

/*
Уроки по изучению основ программирования микроконтроллеров
Тестовая программа к уроку 8_2
Системы счисления
Сайт: red-resisror.ru
*/
    #include <mega8.h> 			//апноты
    #define CLEAN 0  		//Очистка
    #define FULL 255    	//заполнение регистра единицами
    #define PAUSA 10000  	//пауза
    #define FERST 0     	//Начальное значение счетчика

//Объявляем подпрограммы
void pausa (unsigned long int c_pausa); //подпрограмма паузы


//----------------------------------------------------------------------------------
    void main(void){
//Рабочие переменные и настройки
    unsigned int c_binary = CLEAN;          //счетчик

    PORTD = 0x00;	//Проверка работоспособности всех диодов
    DDRD = 0xff; 	//Инициализация вывода порта как выход
    pausa (PAUSA);	//Задержка отображения всей линейки
    PORTD = FULL; 	//гасим все светодиоды

while (1){      	//Тело программы

    PORTD = FULL;	//Гасим все светодиоды
    pausa (PAUSA);
    c_binary = FERST;	//счетчик 0

    do {
        c_binary ++;	//прирощение счетчика
        if (c_binary == 7) continue; //пропуск итерации
        PORTD = FULL - c_binary;
        pausa (PAUSA);
    }
    while (c_binary<256);


    } //END while
} //END main
//----------------------------------------------------------------------------------
//подпограмма паузы
void pausa (unsigned long int c_pausa) {
    for (c_pausa;c_pausa!=0;c_pausa--){};

}

Теперь можно увидеть, что наш счетчик пропускает отображение цифры 7. Ну а если нам необходимо прекратить работу нашего счетчика на счете 10, то программу можно записать таким образом.

while (1){  			//Тело программы

    PORTD = FULL; 		//Гасим все светодиоды
    pausa (PAUSEA);
    c_binary = FERST;		//счетчик 0

    do {
        c_binary ++;		//прирощение счетчика
        if (c_binary == 7) continue;	//пропуск итерации
        PORTD = FULL - c_binary;
        pausa (PAUSEA);
        if (c_binary == 10) break;	//досрочный выход
    }
    while (c_binary<256);

    } //END while

Ну вот с циклами можно сказать закончили. Осталось только положить вишенку на торт и упомянуть о подвергнутом остракизму операторе «goto». Дело в том, что с помощью него можно легко строить любые циклы. Например, цикл счета от нуля до 255 с помощью этого оператора будет выглядеть так:

while (1){              //Тело программы

	PORTD = FULL;       //Гасим все светодиоды
	pausa (PAUSEA);
	c_binary = FERST;    //счетчик 0

   NEXT_STEP:
	c_binary ++;     //прирощение счетчика
	PORTD = FULL - c_binary;
	pausa (PAUSEA);
	if (c_binary<256) goto NEXT_STEP;

    } //END while

Убедитесь, что этот код верен запустив его на своём МК. Как видно представленный код мало отличается от описанных выше. Но считается, что из-за плохой структурированности программа с большим содержанием безусловных переходов плохо читается. И это действительно правда. Читать программные этюды на ассемблере, где безусловные переходы встречаются на каждом шагу, очень непросто. Например, программа с выходом из цикла по достижении числа 10 будет выглядеть вот так:

while (1){              //Тело программы

    PORTD = FULL;       //Гасим все светодиоды
    pausa (PAUSEA);
    c_binary = FERST;    //счетчик 0

 NEXT_STEP:
    c_binary ++;     //прирощение счетчика
    PORTD = FULL - c_binary;
    pausa (PAUSEA);
    if (c_binary == 10) goto EXIT;	//досрочный выход	
    if (c_binary<256) goto NEXT_STEP;
 EXIT:

    } //END while

А если дополнить программу еще и пропуском одной итерации по цифре 7, то это будет выглядеть вот так:

while (1){			//Тело программы

    PORTD = FULL; 		//Гасим все светодиоды
    pausa (PAUSEA);
    c_binary = FERST;		//счетчик 0

 NEXT_STEP:
    c_binary ++;		//прирощение счетчика
    if (c_binary == 7) goto NEXT_STEP;	//пропуск итерации
    PORTD = FULL - c_binary;
    pausa (PAUSEA);
    if (c_binary == 10) goto EXIT;	//досрочный выход
    if (c_binary<256) goto NEXT_STEP;	//окончание цикла
 EXIT:

    } //END while

Ну как удобно читать? ...


Теперь давайте поговорим о кнопках. Дело в том, что основной проблемой при нажатии и отпускании кнопки является ее «дребезг». Он происходит в первые доли секунды при нажатии, а также в момент, когда давление пальца на кнопку уменьшается. При этом происходит подача прерывистого сигнала на вход МК. Который рассматривается контроллером как многократное нажатие. Такое лечится как на «железном» уровне с помощью цепей сглаживающих этот сигнал, так и на программном уровне, когда после того как прошел первый сигнал, запрещают опрос входа на какое-то время. Давайте рассмотрим блок схему работы подпрограммы нашей кнопки.

Блок-схема 1

При входе в подпрограмму происходит опрос кнопки и в случае если она не нажата, обнуляется счетчик. А после проверки на выставленный флаг нажатия передаваемой переменной присваивается значения OFF и происходит выход из подпрограммы.В случае если кнопка нажата, начинается приращение счетчика блокировки, который нивелирует дребезг и дает гарантию нажатия кнопки. После окончания счета выставляется флаг нажатия и на всякий случай обнуляется значения счетчика.

После того как был зафиксирован факт нажатия и выставлен флаг, начинает работать вторая часть блок-схемы. Как только при выставленном флаге нажатия кнопка будет отпущена, то переменной, передаваемой параметр нажатия, будет присвоено значение ON, и подпрограмма передаст это значение в основную программу. В тоже время произойдет сброс флага нажатия, и подпрограмма снова встанет на «дежурство».

Давайте переведем нашу схему в программный код и организуем как библиотечную подпрограмму.

/*
Подпрограмма опроса одной кнопоки
Версия      	:01.4
Дата        	:05.02.15
Разработал      :PitonKaa <info@red-resistor.ru>
Сайт            :http://red-resistor.ru
Файл        	:#include <lib_batt_one_fix.c>

Подпрограмма:bat_pusch_one (void);
возвращает значение:
0 - кнопка не нажата
1 - кнопка нажата
*/
    //Настроечные параметры и псевдонимы
        #define PAUSA_BAT   	255 		//счетчик задержки нажания char
        #define PORT        	PINC.5 		//порт и пин кнопки
    //Внутренние псевдонимы
	    #define BAT_ON		1	//Передаваемый параметр кнопка нажата
	    #define BAT_OFF		0	//Передаваемый параметр кнопка отжата

	unsigned char bat_pusch_one (void){

		//Переменные
			unsigned char   bat_numer		= BAT_OFF;	//передаваемый параметр
			static unsigned char   f_bat_on		= OFF;		//флаг нажатия
			static  unsigned char   c_bat_on	= ZERO;		//счетчик
			
		//Тело программы
		if (PORT){					//если кнопка нажата
			if ((++c_bat_on) >= PAUSA_BAT){ 	//задержка нажатия кнопки
				c_bat_on = ZERO; 		//обнуление счетчика по нажатию
				f_bat_on = ON;			//выставить флаг нажатия кнопки
			}
		}
		else c_bat_on = ZERO;				//обнуления счетчика по недожиму	
		if ((f_bat_on) && (!(PORT))) {			//Проверка отжатия кнопки
			bat_numer = BAT_ON;			//выставить параметр кнопка нажата
			f_bat_on = OFF;				//снять флаг нажатия кнопки
		}
		else bat_numer = BAT_OFF;			//Передать параметр кнопка отжата
		return (bat_numer);
		
	} //END bat_pusch_one()
//Конец подпрограммы

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

Код тестовой программы можно посмотреть в прилагаемых файлах.


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


На следующем занятии мы подробно изучим поразрядные операции. Для этого у нас уже всё есть. Но к следующему уроку необходимо почитать:

Про работу с битами и флагами.
   М.Б. Лебедев «CodeVisionAVR пособие для начинающих» 2008г. Страницы 229…231, 209…214.
   Майк МакГрат «Программирование на С для начинающих» 2016. Страницы 66…72.
   И еще если что найдете в интернете по этому поводу.


Немного о разных электронных устройствах.
   С.А. Никулин, А.В. Повный «Энциклопедия начинающего радиолюбителя» 2011 год. Страницы 127…134.


А на сегодня всё. Удачи.


18.08.18


Если вдруг найдете в статье неточности или заблуждения. Напишите мне об этом. Я подправлю.




Приложение:
Блок-схемы, проект CV_AVR.