Семисегментный индикатор | Программирование микроконтроллеров
Семисегментный индикатор ввиду своей красочности часто применяется для отображения информации, например значения температуры, величины напряжения либо тока. В этой статье мы продолжаем изучать программирование микроконтроллеров и уже научимся подключать к микроконтроллеру ATmega8 простейший одноразрядный семисегментный индикатор, и будем отображать на нем цифры.
Давайте начнем все по порядку. Для начала рассмотрим, что собою представляет семисегментный индикатор. Внешне он имеет различные размеры. Главным идентификатором служит высота цифры, которая в справочниках приводится в дюймах. Высота цифры имеет стандартный ряд значений, который приводится в дюймах.
По количеству разрядов различают одно-, двух-, трех-, и четырехразрядные индикаторы. Бывает и более разрядов, но они встречаются довольно редко.
Семисегментный индикатор. Принцип работы семисегментного индикатора
Любой семисегментный индикатор обязательно состоит из семи сегментов. Отсюда и происходит его название. Каждый сегмент – это обычный отдельный светодиод. Мощные семисегментники могут содержать в одном сегменте несколько, как правило, последовательно соединенных светодиодов.
Кроме того в корпусе помимо сегментов находится еще и точка или запятая или другой символ.
С помощью семи сегментов можно изобразить десять цифр: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 и некоторые буквы, как латиницы, так и кириллицы.
Светодиоды всех элементов соединяются одноименными выводпми между собой или анодами, или катодами. Поэтому разделяют семисегментные индикаторы с общим анодом и общим катодом.
Вне зависимости от количества разрядов и размеров цифр каждый сегмент имеет название в виде одной из первых букв английского алфавита: a, b, c, d, e, f, g. Точка обозначается dp.
Для того чтобы засветить один из светодиодов семисегментного индикатора с общим анодом следует на общий вывод (анод) подать «+», а на соответствующий отдельный вывод – «-» источника питания.
Если применяется общий катод, — то наоборот – минус подается на общий, а плюс на отдельный вывод.
Чтобы отобразить на индикаторе цифру или букву следует засветить несколько сегментов. Например, для отображения единицы 1 задействуются сегменты b и c. При отображении восьмерки 8 задействуются все символы от a до g. Пятерка получается из таких символов: a, c, d, f, g.
Как подключить семисегментный индикатор к микроконтроллеру
Теперь рассмотрим, как подключить семисегментный индикатор к микроконтроллеру ATmega8. Подключим его к порту D. Данные порт имеет все восемь бит, что очень удобно сочетается с количеством выводов одноразрядного семисегментного индикатора, у которого их также восемь с учетом вывода для точки.
Схемы подключения с общим анодом ОА и общим катодом ОК аналогичны, только общий вывод подключается соответственно к плюсу или минусу источника питания.
Все светодиоды подключаются к выводам микроконтроллера через отдельные резисторы сопротивлением 220…330 Ом.
Не стоит экономить на резисторах и подключать все элементы через один общий резистор. Поскольку в таком случае с изменением числа задействованных сегментов будет изменяться величина тока, протекающего через них. Поэтому цифра 1 будет светиться ярче, чем 8.
Чтобы знать какой из выводов отвечает тому или иному сегменту нам понадобится распиновка семисегментного индикатора. Отсчет выводов, как и у микросхем, начинается с левого нижнего и продолжается против часовой стрелки. При этом лицевая сторона индикатора должна быть направлена вверх, а выводы вниз.
Теперь создадим модель в Протеусе и соберем схему на макетной плате. Далее по мере написания кода будем проверять работу микроконтроллера на модели и на реальном устройстве.
Семисегментный индикатор в Proteus находится в категории (Category) Optoelectronics (Оптоэлектроника). Ниже в подкатегории (Sub-category) следует кликнуть по строке 7-Segment Displays. После этого в окне результатов (Results) выбираем одноразрядный семисегментный индикатор 7SEG-MPX1-CC.
Код для микроконтроллера ATmega8
Теперь пишем код. Сначала настраиваем порт D полностью на выход. Для отображения единицы 1 задействуются сегменты b и c, выводы которых подключены к PD1 и PD2. Поэтому соответствующие биты регистр PORTD нужно установить в единицу.
#include <avr/io.h>
int main(void)
{
DDRD = 0b11111111;
while (1)
{
PORTD = 0b00000110; //1
}
}
После компиляции кода и прошивки кода результаты мы видим в Proteus и на макетной плате.
Аналогичным образом формируются все цифры.
Давайте сделаем программу более интересной, так, чтобы цифры изменялись в порядке нарастания от нуля до девяти с паузой 0,3 секунды.
#define F_CPU 1000000L
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
DDRD = 0b11111111;
while (1)
{
PORTD = 0b00111111; //0
_delay_ms(300);
PORTD = 0b00000110; //1
_delay_ms(300);
PORTD = 0b01011011; //2
_delay_ms(300);
PORTD = 0b01001111; //3
_delay_ms(300);
PORTD = 0b01100110; //4
_delay_ms(300);
PORTD = 0b01101101; //5
_delay_ms(300);
PORTD = 0b01111101; //6
_delay_ms(300);
PORTD = 0b00000111; //7
_delay_ms(300);
PORTD = 0b01111111; //8
_delay_ms(300);
PORTD = 0b01101111; //9
_delay_ms(300);
}
}
Данный код можно значительно упорядочить и этим мы займемся в последующих статьях по программированию микроконтроллера ATmega8. На этом заканчиваем наше первое знакомство с семисегментными индикаторами.
Тут такая засада.
На Atiny2323 все работает нормально, на Atmega8 семисегментный индикатор постоянно мерцает. Правда схема такая же как и на Atiny2323 т.е. без кварца. Не подскажите в чем дело?
Дело в том, что по-умолчанию частота работы встроенного генератора (RC-цепочки) Atiny2323 4 МГц, а ATmega8 — 1 МГц, поэтому на меге заметна частота мерцания.
Дмитрий,
Поизучал даташит, правильно понимаю что для установки 4Мгц нужно установить CKSEL3..0 в 0011?
Дмитрий, и еще вопрос в догонку.
Какие кварцы (частоты, типы, кол-во) стоит прикупить для изучения микроконтроллеров?
Сначало зажги светодиод,потом помигай им,потом подключи кнупку,чтоб программа заработал когда нажал кнопку и т.д.кварц для этого не нужен.От простого к сложному.
Автору большое спасибо. После видео уроков на ютубе появился интерес к МК.
Большая просьба автору. Вы собирались сделать материал по динамической индикации —
семи-сегментного индикатора. Возможно, вы бы, могли бы сделать материал, сегменты индикатора к разным портам. Например к двум портам МК.
Andrei, никто не хочет за это дерьмо с разными портами браться, я сам долго искал, у всех подключение к одному порту (так головняка меньше). То что я смог переварить взято с радиокота https://radiokot.ru/forum/viewtopic.php?f=57&t=131489. Да, через анус, но работает.
код:
/*
* M48_dinamind_radiokot.c
*
* Created: 28.11.2018 10:37:52
* Author: Left
*/
#define F_CPU 1000000UL
#include
#include
#include
//—————————————————————
#define SetBit(reg, bit) reg |= (1<<bit)
#define ClearBit(reg, bit) reg &= (~(1<<bit))
#define InvBit(reg, bit) reg ^= (1<<bit)
#define BitIsSet(reg, bit) ((reg & (1<<bit)) != 0)
#define BitIsClear(reg, bit) ((reg & (1<<bit)) == 0)
//——————————————————————
// anodes
#define DIG1_PC2 2 // первый анод
#define DIG2_PC3 3 // второй
#define DIG3_PC4 4 // третий
#define DIG4_PC5 5 // четвертый
//———————————————————————————-
#define dig1_ON PORTC |= (1<<DIG1_PC2); // включить первый (1000) анод
#define dig1_OFF PORTC &= ~(1<<DIG1_PC2); // выключить первый (1000) анод
//———————————————————————————-
#define dig2_ON PORTC |= (1<<DIG2_PC3); // включить второй (100) анод
#define dig2_OFF PORTC &= ~(1<<DIG2_PC3); // выключить второй (100) анод
//———————————————————————————-
#define dig3_ON PORTC |= (1<<DIG3_PC4); // включить третий (10) анод
#define dig3_OFF PORTC &= ~(1<<DIG3_PC4); // выключить третий (10) анод
//———————————————————————————-
#define dig4_ON PORTC |= (1<<DIG4_PC5); // включить четвертый (1) анод
#define dig4_OFF PORTC &= ~(1<<DIG4_PC5); // выключить четвертый (1) анод
//———————————————————————————-
#define dp_on PORTC &= ~(1<<DP_PC1); //
#define dp_off PORTC |= (1<<DP_PC1); //
#define dp_blink PORTC ^= (1<<DP_PC1)
// segmentes
#define A_PB0 0
#define B_PB1 1
#define C_PB2 2
#define D_PB3 3
#define E_PB4 4
#define F_PB5 5
#define G_PC0 0
#define DP_PC1 1
//——————————
#define delay_ms 250
volatile unsigned char data_raz1, data_raz2, N, data_raz3, data_raz4; // определяем переменные для каждого индикатора (4 индикатора)
volatile unsigned int Danie=0; // переменная в которой хранятся данные
//———————————————————————————-
//———————————————————————————-
void ports_init (void)
{
// PORT B
DDRB |= ((1<<A_PB0)|(1<<B_PB1)|(1<<C_PB2)|(1<<D_PB3)|(1<<E_PB4)|(1<<F_PB5)); // порты B на выход
PORTB |= ((1<<A_PB0)|(1<<B_PB1)|(1<<C_PB2)|(1<<D_PB3)|(1<<E_PB4)|(1<<F_PB5)); //выкл порты
// PORT C
DDRC |= ((1<<G_PC0)|(1<<DP_PC1)|(1<<DIG1_PC2)|(1<<DIG2_PC3)|(1<<DIG3_PC4)|(1<<DIG4_PC5)); // порты C на выход
PORTC |= ((1<<G_PC0)|(1<<DP_PC1));
PORTC &= ~((1<<DIG1_PC2)|(1<<DIG2_PC3)|(1<<DIG3_PC4)|(1<<DIG4_PC5));
DDRD = 0xFF;
PORTD = 0x00;
}
void timer0_OVF_init (void)
{
//TCCR0B |= (1<<CS00); // no prescaling
//TCCR0B |= (1<<CS01); // prescaler 8
TCCR0B |= ((1<<CS01)|(1<<CS00)); // prescaler 64
//TCCR0B |= (1<<CS02); // prescaler 256
//TCCR0B |= ((1<<CS02)|(1<<CS00)); // prescaler 1024
TIMSK0 |= (1<<TOIE0); // enable interrupt OVF
}
void clean_display (void)
{
PORTB |= ((1<<A_PB0)|(1<<B_PB1)|(1<<C_PB2)|(1<<D_PB3)|(1<<E_PB4)|(1<<F_PB5)); // all OFF
PORTC |= ((1<<G_PC0)|(1<<DP_PC1)); // all OFF
dig1_OFF; dig2_OFF; dig3_OFF; dig4_OFF;
}
void segment (N)
{
cli();
switch (N)
{
case 0:{PORTB &= ~((1<<A_PB0)|(1<<B_PB1)|(1<<C_PB2)|(1<<D_PB3)|(1<<E_PB4)|(1<<F_PB5));} break;// 0
case 1:{PORTB &= ~((1<<B_PB1)|(1<<C_PB2));} break;// 1
case 2:{PORTB &= ~((1<<A_PB0)|(1<<B_PB1)|(1<<D_PB3)|(1<<E_PB4)); PORTC &= ~(1<<G_PC0);} break;// 2
case 3:{PORTB &= ~((1<<A_PB0)|(1<<B_PB1)|(1<<C_PB2)|(1<<D_PB3)); PORTC &= ~(1<<G_PC0);} break;// 3
case 4:{PORTB &= ~((1<<B_PB1)|(1<<C_PB2)|(1<<F_PB5)); PORTC &= ~(1<<G_PC0);} break;// 4
case 5:{PORTB &= ~((1<<A_PB0)|(1<<C_PB2)|(1<<D_PB3)|(1<<F_PB5)); PORTC &= ~(1<<G_PC0);} break;// 5
case 6:{PORTB &= ~((1<<A_PB0)|(1<<C_PB2)|(1<<D_PB3)|(1<<E_PB4)|(1<<F_PB5)); PORTC &= ~(1<<G_PC0);} break;// 6
case 7:{PORTB &= ~((1<<A_PB0)|(1<<B_PB1)|(1<<C_PB2));} break;// 7
case 8:{PORTB &= ~((1<<A_PB0)|(1<<B_PB1)|(1<<C_PB2)|(1<<D_PB3)|(1<<E_PB4)|(1<<F_PB5)); PORTC &= ~(1<<G_PC0);} break;// 8
case 9:{PORTB &= ~((1<<A_PB0)|(1<<B_PB1)|(1<<C_PB2)|(1<<D_PB3)|(1<<F_PB5)); PORTC &= ~(1<<G_PC0);} break;// 9
case 10:{PORTC &= ~(1<<DP_PC1);} break;// point
case 11:{PORTC &= ~(1<<G_PC0);} break;// прочерк
case 12:{PORTB |= ((1<<A_PB0)|(1<<B_PB1)|(1<<C_PB2)|(1<<D_PB3)|(1<<E_PB4)|(1<<F_PB5)); PORTC |= ((1<<G_PC0)|(1<= 1000 )
{
Danie = Danie — 1000;
data_raz1++;
}
if (data_raz1 = 100 )
{
Danie = Danie — 100;
data_raz2++;
}
if (data_raz1==11 && data_raz2 = 10 )
{
data_raz3++;
Danie = Danie — 10;
}
if (data_raz1==11 && data_raz2==11 && data_raz3 < 1)
{
data_raz3=11;
}
data_raz4 = Danie;
//=========================================
sei();
}
ISR (TIMER0_OVF_vect)
{
static unsigned char count_OVF=0, segcounter;
//TCNT0=10;
count_OVF++;
if (count_OVF==50)
{
Danie++;
if (Danie==500)
{
Danie=0;
}
count_OVF=0;
}
clean_display(); // очистить дисплей
switch (segcounter)
{
case 0: segment(data_raz1); dig1_ON; break; // выводимм первый разряд
case 1: segment(data_raz2); dig2_ON; break; // выводим второй разряд
case 2: segment(data_raz3); dig3_ON; break; // выводим третий разряд
case 3: segment(data_raz4); dig4_ON; break; // выводим четвертый разряд
}
segcounter++;
if(segcounter==4) segcounter=0; // вывели все четыре разряда, обнуляем счетчик
}
int main(void)
{
ports_init();
timer0_OVF_init();
sei();
while(1)
{
data_convert(Danie);
}
}
Относительно просто динамическую индикацию подключать, применяя оператор switch case.
Да, просто, но как быть с сегментами на разных портах? да еще и так чтоб начинающий смог понять. То что что я видел, с кучей подключаемых библиотек, где хрен разберешь последовательность и что откуда берется, выглядит (как для начинающего) уж очень муторно. Если у Вас будет свободное время, сделайте урок по динамической индикации с сегментами на разных портах, это будет единственное видео на ютубе и единственный урок с комментариями (если будет конечно;) )
Это нужно быть совсем нубом в прерывании тратить время на перебор 13ти кейсов в свитче.
Все делается одним сдвигом N влево в зависимости от размера кейса и джамп на кейс по получившимся смещению после сдвига N. 2 инструкции чтобы попасть в кейс. Но для этого нужно уметь на ассемблере написать.
Еще гасится весь дисплей, потом куча кода проходит до момента отображения сегмента. Все это время дисплей не горит. Лажа полная, а не пример.
У меня даже на убогом нувотоне, где одна инструкция по 3 такта,а не один как у avr обновление дисплея занимает 4.7микросекунды. То есть я могу его обновлять с частотой аж 200 кГц. И каждая нога тоже отдельно дергается, а не просто в порт кинуть. И между моментом когда все гасится и отображается новый сегмент вообще нет пробела, следующей же ассемблерной инструкцией начинает зажигаться.
Так что хочешь сделать быстро и качественно динамическую индикацию без ассемблера не обойтись.
Что-то не весь код с копировался, напиши почту я сброшу.
Почему PC6 всегда красная
Потому что на РС6 высокий потенциал, так как этот вывод одновременно является и выводом сброса (reset). Сброс осуществляется путем соединения РС6 с нулевым потенциалом или с минусом источника питания микроконтроллера.