Логотип

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

Программирование микроконтроллеров. Урок 6
(Процедуры и функции, а также оператор ветвления «switch»)

Урок 6 (Процедуры и функции, а также оператор ветвления «switch»)

На прошлом занятии мы изучили переменные и разобрались с арифметическими операциями. А сегодня мы разберемся с организацией процедур и функций. Но сначала я отвечу на вопрос прошлого урока. (Изменится ли частота переключения светодиода если мы включим в цикл W полезный код? И если измениться, то как и почему?) Да, при добавлении в цикл полезного кода частота переключения светодиода будет уменьшаться. Это связано со временем, затраченным на выполнения этого кода. И поверьте, если код будет объемным, то и замедление будет весьма, весьма существенным.

Теперь можно продолжить о процедурах, функциях и библиотеках.

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

Для языка Си написано большое количество разного рода библиотек. Например, чтобы пользоваться некоторыми арифметическими функциями необходимо подключить библиотеку «math.h», а, чтобы работать со строковыми переменными «string.h». Но те библиотеки что написаны для работы программ, запускаемых на компьютерах очень громоздкие и не всегда годятся для микроконтроллеров. С библиотечными функциями можно ознакомится, почитав М.Б. Лебедева «CodeVisionAVR пособие для начинающих» 2008г. Страницы 254…373. На данном этапе особо вникать в них не следует, но про то, что они есть надо знать. С другой стороны, никто нам не запрещает сделать свою библиотеку процедур и функций для дальнейшего применения в проектах.

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

Блок-схема 1

Локализуем только ту часть блок-схемы, которая относится непосредственно к работе светодиода.

Блок-схема 2

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

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

		тип_возвращаемого_значения  имя_функции (список_передаваемых_параметров)
		{
			Внутренние переменные;
			Тело функции;
			return (возвращаемое_значение);
		} 

Тип возвращаемого аргумента может быть любой, но если возвращать ничего не требуется, а это как раз наш случай, то в это место записывается слово void. Тоже самое и со списком параметров. Если предаваемых в функцию параметров нет, то в скобках после имени функции пишется void. В конце функции находится оператор «return». Если возвращаемых аргументов нет, то скобки можно не ставить, в противном случае в скобках располагается внутренняя переменная, которая содержит передаваемое значение.

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

Организуем нашу моргалку записав ее с использованием функции.

/*
Уроки по изучению основ программирования микроконтроллеров
Тестовая программа к уроку 6
Процедуры и функции, оператор switch
Сайт: red-resisror.ru
*/
#include <mega8.h>
//раздел 3 Псевдонимы и макросы
	#define CLEAN 0        	//Очистить переменную
	#define LED_PORT PORTB  //Порт подключенного светодиода
	#define LED_PIN 0       //Вывод подключенного светодиода
	#define LED_ON 0        //Светодиод включен
	#define LED_OFF 1       //Светодиод выключен
	#define LED_MAX 60000   //Максимальное значение счетчика

//раздел 5 Процедуры и функции
	void lib_driver_led (void)  {				//Процедура управления светодиодом
		static unsigned int c_pausa_led = CLEAN;	//Объявление и инициализация переменной счетчика
		if (!(c_pausa_led)){				//если счетчик равер нулю
			if(LED_PORT.LED_PIN) LED_PORT.LED_PIN = LED_ON;	// зажигаем светодиод
			else LED_PORT.LED_PIN = LED_OFF;	//гасим светодиод
			c_pausa_led = LED_MAX;			//устанавливаем максимальное значение счетчика
			}
		else c_pausa_led--;			//Декрементируем счетчик (c_pausa_led = c_pausa_led - 1)
	}

void main(void)
{
//раздел 7 Рабочие переменные и настройки
	DDRB.0 = 1;				//Инициализация вывода порта как выход
	
while (1)
	{
		lib_driver_led();		//вызов процедуры управления светодиодом
	}
}

Теперь наша процедура находится не в цикле W, а является самостоятельной подпрограммой, которая вызывается в основном цикле программы. Размещать подпрограммы можно двумя способами. Первый это когда вся подпрограмма находится в разделе 5 (Процедуры и функции), а второй это когда в этом разделе мы только объявляем нашу процедуру, а сама она находится за приделами функции main. Как показано ниже.

/*
Уроки по изучению основ программирования микроконтроллеров
Тестовая программа к уроку 6
Процедуры и функции, оператор switch
Сайт: red-resisror.ru
*/
#include <mega8.h>
//раздел 3 Псевдонимы и макросы
	#define CLEAN 0		//Очистить переменную
	#define LED_PORT PORTB	//Порт подключенного светодиода
	#define LED_PIN 0	//Вывод подключенного светодиода
	#define LED_ON 0	//Светодиод включен
	#define LED_OFF 1	//Светодиод выключен
	#define LED_MAX 60000	//Максимальное значение счетчика

//раздел 5 Процедуры и функции
	void lib_driver_led (void);	//объявление процедуры управления светодиодом
	
//*****************************************************************************
void main(void)
{
//раздел 7 Рабочие переменные и настройки
	DDRB.0 = 1;				//Инициализация вывода порта как выход
	
while (1)					//Тело программы
	{		
		lib_driver_led();		//вызов процедуры управления светодиодом
	} 
} //END main
//*****************************************************************************
//раздел 5 Процедуры и функции
	void lib_driver_led (void)  {				//Процедура управления светодиодом
		static unsigned int c_pausa_led = CLEAN;	//Объявление и инициализация переменной счетчика
		if (!(c_pausa_led)){				//если счетчик равер нулю
			if(LED_PORT.LED_PIN) LED_PORT.LED_PIN = LED_ON;	// зажигаем светодиод
			else LED_PORT.LED_PIN = LED_OFF;	//гасим светодиод
			c_pausa_led = LED_MAX;			//устанавливаем максимальное значение счетчика
			}
		else c_pausa_led--;			//Декрементируем счетчик (c_pausa_led = c_pausa_led - 1)
	}

Теперь откомпилируем нашу программу и прошьём ее в МК. Светодиод должен моргать немного медленней чем на прошлом занятии. Это связано с временем затраченным на вызов процедуры. Но просто моргать светодиодом беспонтово. Необходимо расширить функциональные возможности нашей светодиодной индикации. Давайте сделаем чтобы наш светодиод мог не только моргать, но и просто гореть, а также мог находиться в выключенном состоянии. А значит в нашей подпрограмме должен появится параметр управления, который мы передадим в нашу процедуру. Условимся, что параметр (0) будет означать, что светодиод будет потушен, параметр (1) – горящий постоянно светодиод, параметр (2) – светодиод моргает. Теперь давайте рассмотрим блок-схему нашей светодиодной индикации.

Блок-схема 3

Из блок-схемы видно, что подпрограмма для своего выполнения должна выбрать один из трех путей работы в зависимости от управляющего параметра. Конечно можно организовать выбор с помощью оператора «if» но такая организацию будет не совсем корректной и потребует либо проверки по всем значениям, либо использование оператора «goto», что не приветствуется в современном высокоуровневом программировании. Для того, чтобы все сделать «по циркулю» в данном случае необходимо использовать оператор ветвления «switch».

Про этот оператор быстренько читаем:
   Майк МакГрат «Программирование на С для начинающих» 2016. Страницы 76…77.
   Ю.А. Шпак «Программирование на языке С для AVR и PIC микроконтроллеров» 2006. Страница 127.
   К. Поляков «Программирование на языке Си» 1995-2012. Страницы 17…18.
   М.Б. Лебедев «CodeVisionAVR пособие для начинающих» 2008г. Страницы 217…218.

Теперь с помощью оператора «switch» организуем нашу подпрограмму. Она будет выглядеть вот так.

//Процедура управления светодиодом
	void lib_driver_led (unsigned char data)  {
		// Рабочие переменные
		static unsigned int c_pausa_led = CLEAN;

		//Тело подпрограммы
		switch (data) {
			case LED_DATA_OFF:LED_PORT.LED_PIN = LED_OFF;	
				break;
			case LED_DATA_ON:LED_PORT.LED_PIN = LED_ON;	
				break;
			default:{					//моргаем светодиодом
				if (!(c_pausa_led)){			//если счетчик равер нулю
					if(LED_PORT.LED_PIN) LED_PORT.LED_PIN = LED_ON;	
					else LED_PORT.LED_PIN = LED_OFF;
					c_pausa_led = LED_MAX;	//устанавливаем максимальное значение счетчика
				}
				else c_pausa_led--;			//Декрементируем счетчик 
			};
				break;
		};
	}

Теперь в зависимости от входящих данных мы либо отключаем светодиод и с помощью оператора «break» уходим в конец подпрограммы. Или наоборот включаем светодиод, а если входящие данные будут больше единицы, то запускаем счетчик и наш светодиод начинает моргать. Прошу обратить внимание на то как мы вызываем подпрограмму. Т.к. наша подпрограмма не возвращает никаких значений, то переменных для приема данных не требуется. Но в передаваемые параметры необходимо записать либо переменную со значениями управления светодиодом, либо непосредственные значения. Откомпилируйте программу с разными значениями и убедитесь в ее работоспособности.

С функциями закончено. Осталось только организовать нашу подпрограмму в отдельный файл сделать небольшое описание к нему и использовать его как библиотеку. Для этого необходимо открыть новое окно (File/New/Source File). Затем скопировать нашу подпрограмму на новое место. После чего добавить описание и сохранить отдельным файлом. В названии файла лучше всего указать что это «библиотечный» файл.

/*
Библиотечная подпрограмма управления одним светодиодом
версия:     1.0
дата:       29.05.18
сайт:       red-resisror.ru
файл:       #include <lib_led_driver.c>
входные параметры:
	0 - светодиод погашен
	1 - светодиод горит
	1<x<256 - светодиод моргает
*/
//Псевдонимы
	#define LED_PAUSA_MAX 60000	//Максимальное значение счетчика

	#define LED_PORT PORTB.0	//Порт и пин подключенного светодиода

	#define LED_ON LED_PORT = 0	//Светодиод включен
	#define LED_OFF LED_PORT = 1	//Светодиод выключен

	#define DATA_OFF    0		//Cветодиод погашен
	#define DATA_ON     1		//Cветодиод горит
	#define DATA_BLINC_FAST  2	//Cветодиод моргает быстрее

//Процедура управления светодиодом
	void lib_driver_led (unsigned char data_led)  {

	// Рабочие переменные
        static unsigned int c_pausa_led = LED_PAUSA_MAX;

        //Тело подпрограммы
        switch (data_led) {
            case DATA_OFF: LED_OFF;
                break;
            case DATA_ON: LED_ON;
                break;
            default:{                       //моргаем светодиодом
                if (!(c_pausa_led)){        //если счетчик равер нулю
                    if(LED_PORT) LED_ON;
                    else LED_OFF;
                    c_pausa_led = LED_PAUSA_MAX;  //устанавливаем максимальное значение
                }
                else c_pausa_led--;         //Декрементируем счетчик
            };
                break;
        };
    }

Организация подключаемых библиотек CV-AVR эта отдельная тема и я ее сейчас не хочу касаться. Тут уж каждый может изучить ее самостоятельно детально и подробно. Я для себя решил, что мне будет достаточно множества файлов, со специальными функциями, которые я подключаю по необходимости в свои проекты. Расширения для библиотечных файлов я оставляю «*.с».

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

/*
Уроки по изучению основ программирования микроконтроллеров
Тестовая программа к уроку 6
Процедуры и функции, оператор switch
Сайт: red-resisror.ru
*/
	#include <mega8.h>		//апноты

//Процедуры и функции
	#include <lib_led_driver.c> //Процедура управления светодиодом
	
//*****************************************************************************
	void main(void){
//Рабочие переменные и настройки
	DDRB.0 = 1;                         //Инициализация вывода порта как выход
	
while (1){	//Тело программы

        lib_driver_led(2);			//вызов процедуры управления светодиодом
		
	} //END while
} //END main
//*****************************************************************************

При вызове подпрограммы отправляем в нее управляющий параметр (2). Теперь компилируем и прошиваем. Светодиод должен моргать.

Теперь уже точно всё с подпрограммами.

Домашним заданием будет изменить файл нашей библиотеки так, чтобы при входящем параметре (2) светодиод моргал в два раза быстрее чем по параметру (3...255).


Необходимо будет почитать всё связанное с подпрограммами и библиотеками:
   М.Б. Лебедев «CodeVisionAVR пособие для начинающих» 2008г. Страницы 229…231, 250…253.
   К. Поляков «Программирование на языке Си» 1995-2012. Страницы 35…46.
   Майк МакГрат «Программирование на С для начинающих» 2016. Страницы 88…98.м
   Ю.А. Шпак «Программирование на языке С для AVR и PIC микроконтроллеров» 2006. Страницы 117…120.


После чего читаем про диоды, транзисторы и катушки индуктивности, а также о реле.
   М.А. Згут «Условные обозначения и радиосхемы» 1964 год. Станицы 49…57, 69…87
   В.Ю. Иваницкий «Советы начинающему радиолюбителю» 1982 год. Страницы 104…116.
   С.А. Никулин, А.В. Повный «Энциклопедия начинающего радиолюбителя» 2011 год. Страницы 69…71, 84…101.


На следующим занятии мы соберем схему задержки включения реле, рассмотрим, как она работает и напишем программу которая будет включать реле по истечении некоторого времени после включения МК. Для этого по мимо того что уже собрано нам понадобится:
   Резисторы 500Ом – 1 шт. 1кОм – 1 шт.
   Светодиод – 1 шт.
   Транзистор кт815 – 1 шт.
   Диод FR101 или аналог – 1 шт.
   Реле HLS-14F1L-DC5V-C – 1 шт. или любое другое маломощное реле с управляющей катушкой на 5 вольт.


05.06.18


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




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