Логотип

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

Программирование микроконтроллеров. Урок 13
Подключение трехсекционного семисегментного индикатора

Урок 13 (Подключение трехсекционного семисегментного индикатора)

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

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

Схема трехсекционного семисигментного индикатора

Схема трехсекционного семисигментного индикатора

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

Схема подключения семисигментного индикатора

Схема подключения семисигментного индикатора

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

Монтажная плата 1 Монтажная плата 2 Монтажная плата 3

Монтажная плата 1

Монтажная плата 2

Монтажная плата 3

Чтобы организовать вывод данных на индикатор нам необходимо выполнить следующую процедуру:
   1. Записать в порт данные выводимого (первого) знака;
   2. Подать на общий контакт первой секции питание;
   3. Выждать небольшое время индикации;
   4. Снять питание с общего контакта первой секции;
   5. Записать в порт данные выводимого символа второй секции;
   6. Подать на общий контакт второй секции питание;
   7. Выждать небольшое время индикации…

И так по кругу без остановки. Вроде все понятно. Но вот незадача. Максимальное отображаемое значение, которое может вывести трехсекционный индикатор 999. В программе оно записывается в переменную типа (int). В тоже время в порт, каждый раз нам надо записывать не все число, которое туда может и не вместиться, а только его отображаемые составные части (сотни, десятки и единицы). И да… Нам также надо рассмотреть вариант, при котором в функцию для отображения поступает число большее чем максимально отображаемое значение. То есть перед тем как выводить переданное число на индикатор нам необходимо проверить может ли оно быть корректно отображено. В случае если число удовлетворяет этому требованию мы разбираем его на составные части, а если нет, то выдаем короткое сообщение об ошибке. Более того, если это необходимо, то можно прописать не только обработчик ошибок, но и выдачу сообщений по передаваемому коду. Например, передаваемые значения от 0 до 999 отображаются как цифры, значение 1000 будет кодом ошибки и будет выдавать «Еrr», 1001 будет кодом включения «On», а 1002 кодом выключения «Off» и т.д. В общем здесь есть место для творчества.

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

Путей реализации вывода на индикатор великое множество, но я предлагаю вам разобрать мою версию. Она безусловно не идеальна. Более того, для большей наглядности я специально разбил ее на отдельные блоки в ущерб быстродействию. Итак, …

Блок схема алгоритма часть 1

Блок схема алгоритма часть 1

После того как мы вошли в подпрограмму, объявили необходимые внутренние переменные и передали данные для отображения. Сравниваем полученное число с максимально допустимым значением. Если оно больше, то выполняется ветка обработчика ошибок. В данном случае на индикатор будет выводиться сообщение об ошибке «Err». Но, как я писал выше, можно написать дополнительный обработчик для вывода коротких сообщений. Если полученное число проходит валидацию, то происходит сравнение его с поступившим ранее. И если значения не совпадают, то число разбирается на сотни, десятки и единицы. В конце блока новое число записывается в память для сравнения при следующей итерации.

Затем программа переходит к блоку ротации секций индикатора в начале которого проверяется счетчик времени включения сегмента. И если он обнулен, то происходит ротация отображаемого сегмента и установка нового значения счетчика для его включения.

Блок схема алгоритма часть 2

Блок схема алгоритма часть 2

После чего программа переходит в блок отображения где и происходит всё то, о чем я писал ранее. Отключается предыдущая секция записывается в порт число для отображения, добавляется точка если нужно и включается необходимая секция индикатора. После чего происходит выход из подпрограммы.

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

Вот так выглядет описанная выше подпрограмма.

//********************************************************
//Подпрограмма 3х сегментного индикатора.
//      Дата    26.10.18
//      Версия  0.01
//********************************************************
//  data - отображаемое число  (0...999) или код сообщения (большее 999)
//  point - место точки        (3, 2, 1, 0 - точки нет)

    void three_led_seven (unsigned int data, unsigned char point){
	
    //Внутренние константы
	#define SIMBOL      PORTD 		//определение порта передачи данных
	#define SECTION_3  PORTB.0		//вывод порта управленя секцией 3
	#define SECTION_2  PORTB.7		//вывод порта управленя секцией 2
	#define SECTION_1  PORTB.6		//вывод порта управленя секцией 1
	#define VISION_MAX  20    		//максимальная длительность отображения сегмента
	#define POINT_ON (SIMBOL &= ~(1<<3))	//отображение точки
	#define DARK 10				//ячейка массива выключения индикации
	#define E 11				//ячейка массива со знаком Е
	#define R 12				//ячейка массива со знаком r
	#define IND_ON 0			//секция индикатора влючена
	#define IND_OFF 1			//секция индикатора выключена
	#define SEC_1 0				//секция 1
	#define SEC_2 1				//секция 2
	#define SEC_3 2				//секция 3
	#define POINT_1 1			//позиция точки первой секции
	#define POINT_2 2			//позиция точки первой секции
	#define POINT_3 3			//позиция точки первой секции
	#define MAX_DATA 999			//максимально отображаемая дата

    //Внутренние переменные
        static unsigned char vision_symbol = VISION_MAX;//счетчик длительности отображения
        static unsigned char step = SEC_1;          	//счетчик ротации секций
        static unsigned int old_data = ZERO;       	//данные предыдущего отображаемого числа
        static unsigned char data_1 = ZERO;        	//данные единиц
        static unsigned char data_2 = ZERO;        	//данные десяток
        static unsigned char data_3 = ZERO;        	//данные соток
        const unsigned char symbol_numer [13] = {   	//массив переменных отображения идикатора
            (0b10001000),         			//0
            (0b11101011),         			//1
            (0b01001100),         			//2
            (0b01001001),         			//3
            (0b00101011),         			//4
            (0b00011001),         			//5
            (0b00011000),         			//6
            (0b11001011),         			//7
            (0b00001000),         			//8
            (0b00001001),         			//9
            (0b11111111),         			//OFF 10
            (0b00011100),         			//E 11
            (0b01111110),         			//r 12
        };

    //Тело программы
        //подготовка числа к отображению
        if (data > MAX_DATA) {		//сюда пишем обработчик для отображения сообщений 
		    data_3 = E;		//Err - сообщение об ошибке при превышении
		    data_2 = R; 
		    data_1 = R;
        }
	else{					//Определение отображаемого числа
	    if (old_data != data) {		//пропускаем процедуру вычисления чисел если данные не менялись
		    data_3 = data/100;		//сотки
		    data_2 = (data%100)/10;	//десятки
		    data_1 = data%10;		//единицы
            };
        };
        old_data = data;        		//сохранение новых данных

        //Ротация отображения сегментов
        if (!(vision_symbol)) {               	//проверка счетчика отображения
            vision_symbol = VISION_MAX;        	//установка максимального времени отображения секции
            step ++;                          	//ротация отображаемой секции
            if (step > SEC_3) step = SEC_1;    	//Проверка границ ротации
        }
        else vision_symbol--;                 	//декремент счетчика отображения сегмента

        //Отображение сегментов
        switch (step){                         		//отображение данных сегмента
            case SEC_1 : {
                SECTION_3 = IND_OFF;   			//выключаем позицию 3
                SIMBOL = symbol_numer [data_1]; 	//Отображение символа без точки
                if (point == POINT_1)POINT_ON; 		//отображаем символ с точкой
                SECTION_1 = IND_ON;   			//включаем позищию 1
            }
            break;
            case SEC_2 : {
                SECTION_1 = IND_OFF;
                SIMBOL = symbol_numer [data_2];	
                if (point == POINT_2)POINT_ON;
                SECTION_2 = IND_ON;
            }
            break;
            case SEC_3 : {
                SECTION_2 = IND_OFF;
                SIMBOL = symbol_numer [data_3];	
                if (point == POINT_3)POINT_ON;	
                SECTION_3 = IND_ON;
            }
        };

        return;
    }

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

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

На этот раз. В качестве домашнего задания я попрошу вас сделать таймер, отсчитывающий секунды после первого нажатия на кнопку и завершающий свой счет после второго нажатия или по достижению максимально-отображаемого числа (999). Т.к. вы еще не умеете подключать часовой кварц (32768 Гц) к МК то, чтобы проводить отсчет секунд, придется организовать задержку и уточнить ее величину на больших промежутках времени. Ну, скажем, чтобы на глаз за 900 секунд наш таймер не спешил или не отставал больше чем на секунду. Вот так.

И да… Надо уже понемногу вникать в архитектуру микроконтроллеров и поэтому необходимо будет вдумчиво и неспешно почитать:
   Ю.А. Шпак «Программирование на языке С для AVR и PIC микроконтроллеров» 2006. Страницы 22…28.
   Евставфьев А.В. «Микроконтроллеры AVR семейства Tiny и Mega фирмы Atmel» 2008г. Страницы 148…158; 174…199.


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


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


12.11.18



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



Приложение:
Схема и проект CV_AVR.