Введение
Для отображения цифровой информации в системах на базе микроконтроллеров используются светодиодные семисегментные индикаторы. Они просты в управлении, имеет высокую яркость, широкий диапазон рабочих температур и низкую стоимость. К недостаткам светодиодных индикаторов относятся – высокое энергопотребление, отсутствие управляющего контроллера и скудные возможности по выводу буквенной информации.Светодиодный семисегментный индикатор представляет собой группу светодиодов расположенных в определенном порядке и объединенных конструктивно. Зажигая одновременно несколько светодиодов можно формировать на индикаторе символы цифр. Индикаторы различаются по типу соединения светодиодов – общий анод, общий катод, по количеству отображаемых разрядов – однораразрядные, двух разрядные и т.д. и по цвету – красные, зеленые, желтые и т.д.
Эксперименты с семисегментным индикатором
Рассмотрим простейший случай управления индикатором – вывод одной цифры. Схема для наших экспериментов приведена ниже.
Чтобы зажечь на индикаторе какую-то цифру нужно настроить порты, к которым подключен индикатор, в режим выхода, “открыть” транзистор (в данном случае подать на базу “единицу”) и установить в порту микроконтроллера её код.
В зависимости от того, куда подключены сегменты индикатора – коды могут быть разные. Для нашего случая коды цифр будут выглядеть так.
unsigned char number[] =
{
0x3f, //0
0x06, //1
0x5b, //2
0x4f, //3
0x66, //4
0x6d, //5
0x7d, //6
0x07, //7
0x7f, //8
0x6f //9
};
Используя десятичные цифры от 0 до 9 в качестве индекса массива, легко выводить в порт нужные коды.
Пример 1. Вывод цифр от 0 до 9
#include <ioavr.h>
#include <intrinsics.h>
unsigned char number[] =
{
0x3f, //0
0x06, //1
0x5b, //2
0x4f, //3
0x66, //4
0x6d, //5
0x7d, //6
0x07, //7
0x7f, //8
0x6f //9
};
unsigned char count = 0;
int main( void )
{
//порт, к которому подкл. сегменты
PORTB = 0xff;
DDRB = 0xff;
//вывод, к которому подкл. катод
PORTD |= (1<<0);
DDRD |= (1<<0);
while(1){
PORTB = number[count];
count++;
if (count == 10) count = 0;
__delay_cycles(8000000);
}
return 0;
}
Эта программа каждую секунду выводит значение переменной count на семисегментный индикатор. Индикация в данном случае - статическая.
Идем дальше – вывод двух цифр. Настройка портов и коды цифр остаются без изменений, но теперь нам нужно добавить кусок кода, который будет зажигать разряды индикатора по очереди. Частота смены разрядов должна быть достаточно высокой, чтобы свечение индикатора воспринималось без мерцания.
Пример2. Вывод цифр от 0 до 99
#include <ioavr.h>
#include <intrinsics.h>
unsigned char number[] =
{
0x3f, //0
0x06, //1
0x5b, //2
0x4f, //3
0x66, //4
0x6d, //5
0x7d, //6
0x07, //7
0x7f, //8
0x6f //9
};
unsigned char count = 0;
//числа для вывода на индикатор
unsigned char data1 = 2;
unsigned char data2 = 5;
int main( void )
{
//порт, к которому подкл. сегменты
PORTB = 0xff;
DDRB = 0xff;
//порт, к которому подкл. катод
PORTD = 0;
DDRD = (1<<1)|(1<<0);
while(1){
//гасим оба разряда
PORTD &= ~((1<<1)| (1<<0));
//выводим в порт код цифры
//и зажигаем следующий разряд
if (count == 0) {
PORTB = number[data2];
PORTD |= (1<<1);
}
if (count == 1) {
PORTB = number[data1];
PORTD |= (1<<0);
}
count++;
if (count == 2) count = 0;
//частота смены разрядов будет 100 Гц при кварце 8МГц
__delay_cycles(800000);
}
return 0;
}
Эта программа просто выводит любое поразрядно заданное число от 0 до 99.
Частота смены разрядов семисегментного индикатора задается с помощью программной задержки __delay_cycles(). Это не самое удачное решение, потому что добавление каких-нибудь других задач в цикл while будет мешать выводу на индикатор. Давайте организуем смену разрядов индикатора с помощью аппаратного таймера/счетчика Т0
Пример3. Вывод цифр от 0 до 99. Смена разрядов выполняется в прерывании таймера
#include <ioavr.h>
#include <intrinsics.h>
unsigned char number[] =
{
0x3f, //0
0x06, //1
0x5b, //2
0x4f, //3
0x66, //4
0x6d, //5
0x7d, //6
0x07, //7
0x7f, //8
0x6f //9
};
//числа для вывода на индикатор
volatile unsigned char data1 = 0;
volatile unsigned char data2 = 0;
int main( void )
{
//порт, к которому подкл. сегменты
PORTB = 0xff;
DDRB = 0xff;
//порт, к которому подкл. катод
PORTD = 0;
DDRD |= (1<<1)|(1<<0);
//инициализация таймера Т0
//частота прерываний 100Гц при кварце 8МГц
TIMSK = (1<<TOIE0);
TCCR0 = (1<<CS02)|(0<<CS01)|(1<<CS00);
TCNT0 = 0xb2;
__enable_interrupt();
while(1){
//программный счетчик секунд
data1++;
if (data1 == 10) {
data1 = 0;
data2++;
if (data2 == 10) data2 = 0;
}
__delay_cycles(8000000);
}
return 0;
}
//прерывания таймера Т0 - вывод на индикатор
#pragma vector = TIMER0_OVF_vect
__interrupt void Timer0_Ovf(void)
{
static unsigned char count = 0;
TCNT0 = 0xb2;
//гасим оба разряда
PORTD &= ~((1<<1)|(1<<0));
//выводим в порт код цифры
//и зажигаем следующий разряд
if (count == 0) {
PORTB = number[data2];
PORTD |= (1<<1);
}
if (count == 1) {
PORTB = number[data1];
PORTD |= (1<<0);
}
count++;
if (count == 2) count = 0;
}
Переменные data1, data2 объявлены с ключевым словом volatile, потому что они используются и в основном коде и в прерывании. В проекте под GCC я забыл поставить его поставить, и компилятор выкинул обе переменные, посчитав их ненужными!
Прерывания таймера происходят параллельно выполнению цикла while. Это позволяет выполнять в цикле какую-нибудь полезную задачу. В данном примере с помощью двух переменных в цикле организован программный счетчик от 0 до 99.
Использовать две восьмиразрядные переменные для организации счетчика от 0 до 99 неудобно и расточительно, ведь такой счетчик можно сделать и на одной переменной типа unsigned char. Хорошо, счетчик мы сделаем, а как вывести его значение на семисегментный индикатор? Нужен код “разбивающий” десятичное число на отдельные разряды и вот как он выглядит:
//программный счетчик
unsigned char counterProg = 35;
//”разбиваем” значение счетчика на отдельные разряды
data1 = counterProg % 10;
data2 = counterProg/10;
data1 = counterProg % 10 – это операция деления по модулю 10 (деление с остатком). Результатом этого выражения будет остаток от деления переменной counterProg на 10, то есть для нашего случая 5.
counterProg/10 – это целочисленное деление на 10. Результатом этого выражения будет число 3.
Таким образом, в переменные data2, data1 будут записаны числа 3 и 5 соответственно, значение счетчика counterProg при этом не изменится.
Пример 4. Вывод цифр от 0 до 99.
Преобразование двоичных чисел в двоично-десятичные (BCD)
#include <ioavr.h>
#include <intrinsics.h>
unsigned char number[] =
{
0x3f, //0
0x06, //1
0x5b, //2
0x4f, //3
0x66, //4
0x6d, //5
0x7d, //6
0x07, //7
0x7f, //8
0x6f //9
};
//числа для вывода на индикатор
volatile unsigned char data1 = 0;
volatile unsigned char data2 = 0;
//программный счетчик секунд
unsigned char counterProg = 0;
int main( void )
{
//порт, к которому подкл. сегменты
PORTB = 0xff;
DDRB = 0xff;
//порт, к которому подкл. катод
PORTD = 0;
DDRD |= (1<<1)|(1<<0);
//инициализация таймера Т0
TIMSK = (1<<TOIE0);
TCCR0 = (1<<CS02)|(0<<CS01)|(1<<CS00);
TCNT0 = 0xb2;
__enable_interrupt();
while(1){
//программный счетчик секунд
counterProg++;
if (counterProg == 100) counterProg = 0;
data1 = counterProg % 10;
data2 = counterProg/10;
__delay_cycles(8000000);
}
return 0;
}
//прерывания таймера Т0 - вывод на индикатор
#pragma vector = TIMER0_OVF_vect
__interrupt void Timer0_Ovf(void)
{
static unsigned char count = 0;
TCNT0 = 0xb2;
//гасим оба разряда
PORTD &= ~((1<<1)|(1<<0));
//выводим код цифры в порт
//и зажигаем следующий разряд
if (count == 0) {
PORTB = number[data2];
PORTD |= (1<<1);
}
if (count == 1) {
PORTB = number[data1];
PORTD |= (1<<0);
}
count++;
if (count == 2) count = 0;
}
Следующий этап работы над программой – выделение кода обслуживающего светодиодный семисегментный индикатор в отдельные функции. Какой минимальный набор функций нам необходим? Функция инициализации, функция вывода на индикатор и функция преобразования чисел и записи их в буфер.
Функция инициализации
#define PORT_IND PORTB
#define DDR_IND DDRB
#define PORT_K PORTD
#define DDR_K DDRD
#define KAT1 0
#define KAT2 1
volatile unsigned char data[2];
void IND_Init(void)
{
//порт к которому подкл. сегменты
PORT_IND = 0xff;
DDR_IND = 0xff;
//порт, к которому подкл. катоды
PORT_K &= ~((1<<KAT2)|(1<<KAT1));
DDR_K |= (1<<KAT2)|(1<<KAT1);
//очистка буфера
data[0] = 0;
data[1] = 0;
}
Порты, к которым подключен семисегментный индикатор, определены с помощью директивы #define – в будущем это позволит быстро править код. Вместо двух переменных data1, data2 удобнее использовать массив unsigned char data[2].
Функция преобразования
void IND_Conv(unsigned char value)
{
unsigned char tmp;
tmp = value % 10;
data[0] = number[tmp];
tmp = value/10;
data[1] = number[tmp];
}
Процедура преобразования чисел аналогична описанной выше. Единственное отличие – в буфере (data[]) мы теперь сохраняем не результат преобразования, а коды цифр. Зачем делать в прерывании то, что можно сделать в основном цикле программы?
Функция вывода на индикатор
void IND_Update(void)
{
static unsigned char count = 0;
//гасим оба разряда
PORT_K &= ~((1<<KAT2)|(1<<KAT1));
//выводим в порт код цифры
PORT_IND = data[count];
//зажигаем нужный разряд
if (count == 0) PORT_K |= (1<<KAT1);
if (count == 1) PORT_K |= (1<<KAT2);
count++;
if (count == 2) count = 0;
}
Эта функция будет вызываться в прерывании таймера. В принципе, для экономии ресурсов ее можно было бы сделать встраиваемой.
Пример 5. Код программы с использованием этих функций
//программный счетчик секунд
unsigned char counterProg = 0;
int main( void )
{
IND_Init();
//инициализация таймера Т0
TIMSK = (1<<TOIE0);
TCCR0 = (1<<CS02)|(0<<CS01)|(1<<CS00);
TCNT0 = 0xb2;
__enable_interrupt();
while(1){
counterProg++;
if (counterProg == 100) counterProg = 0;
IND_Conv(counterProg);
__delay_cycles(8000000);
}
return 0;
}
//прерывания таймера Т0 – вывод на индикатор
#pragma vector = TIMER0_OVF_vect
__interrupt void Timer0_Ovf(void)
{
TCNT0 = 0xb2;
IND_Update();
}
И, наконец – разбиение программы на модули.
Создаем два файла – indicator.h и indicator.c. Сохраняем их в папке проекта.
В хидер файле у нас будут директивы условной компиляции, макроопределения и прототипы функций.
#ifndef INDICATOR_H
#define INDICATOR_H
#include <ioavr.h>
#define PORT_IND PORTB
#define DDR_IND DDRB
#define PORT_K PORTD
#define DDR_K DDRD
#define KAT1 0
#define KAT2 1
void IND_Init(void);
void IND_Conv(unsigned char value);
void IND_Update(void);
#endif //INDICATOR_H
В сишном файле – #include “indicator.h”, объявления переменных и реализация трех функций. Функции описаны выше, поэтому здесь их не привожу.
Подключаем файл indicator.c к проекту.
В файл main.c инклюдим заголовочный файл нашего драйвера семисегментного индикатора - #include “indicator.h”
Все. Теперь мы можем использовать функции для работы с индикатором в файле main.
Полный код драйвера смотрите в проектах.
Продолжение следует… Скоро будет дополнение к этой статье - описание вольтметра на микроконтроллере – в проекте используется 4-ех разрядный семисегментный индикатор.
Файлы
Семисегментный индикатор. Проекты для WinAvr
Семисегментный индикатор. Проекты для CodeVision
Отладочный проект для Proteus`a
Comments
Буду разбираться
7-сегментник с ATmega8 и сдвиговым регистром 74HC164 . Так и не попробовал как
с ним работать . Есть в наличии также 74HC595 . Читал статью "Подключение lcd. Варианты схем" , но хотелось бы посмотреть здесь их применение .
Читал про однопроводное подключение . Но для начала попробовать бы 2-х проводное
подключение , применяя 74HC164 или 74HC595 .
//это часть можно настроить под другой индикатор
Как ими пользоваться?
Quote: Это два катода - наверное добавили лишний, чтобы четное число выводов было.
Quote: В данном случае используется BC547 - биполярный npn транзистор общего назначения, 45В 100мА. Транзистор лучше ставить тот, который ток выдержит;)
Code:
индикатор с общим катодом подключенный напрямую к микроконтроллеру
#define LightOutAll() PORT_TR |= (1<<NUM1)|(1<<NUM2)|(1<<NUM3)|(1<<NUM4)
#define BurnDigit(port, digit) port &= ~(1<<digit)
#define ValueBuf() buf[count]
индикатор с общим катодом подключенный к микроконтроллеру с помощью транзисторов
#define LightOutAll() PORT_TR &= ~((1<<NUM1)|(1<<NUM2)|(1<<NUM3)|(1<<NUM4))
#define BurnDigit(port, digit) port |= (1<<digit)
#define ValueBuf() buf[count]
индикатор с общим анодом подключенный напрямую к микроконтроллеру
#define LightOutAll() PORT_TR &= ~((1<<NUM1)|(1<<NUM2)|(1<<NUM3)|(1<<NUM4))
#define BurnDigit(port, digit) port |= (1<<digit)
#define ValueBuf() ~buf[count]
индикатор с общим анодом подключенный к микроконтроллеру с помощью транзисторов
#define LightOutAll() PORT_TR |= (1<<NUM1)|(1<<NUM2)|(1<<NUM3)|(1<<NUM4)
#define BurnDigit(port, digit) port &= ~(1<<digit)
#define ValueBuf() ~buf[count]
IND_Conv()
лучше не использовать деление,я делаю преобразование вычитанием.Так быстрее,так как МК не умеет делить и при компиляции будет большая и медленная подпрограмма. Я делаю так:
void Decoder(unsigne d int DataForLed)
{
unsigned char Num1=0, Num2=0, Num3=0;
while (DataForLed >= 100)
{
DataForLed -= 100;
Num1++;
}
while (DataForLed >= 10)
{
DataForLed -= 10;
Num2++;
}
Num3 = DataForLed ;
ByDisplay[2] =Num1;
ByDisplay[1] =Num2;
ByDisplay[0] =Num3;
}
применением 74HC164 на 2-х разрядном
7-сегментнике . Но без SPI , без транзисторов . Сэкономлено 5 ножек . С SPI
вас подожду как вы обещали .
Есть ли какое-то преимущество в применении 74HC595 по сравнению с 74HC164 .
Слышал про однопроводное подключение .
Надежно ли это .
Хотелось бы узнать ваше мнение .
Есть, 595 с защелкой и управляя оной можно не бояться за засвечивание негорящих сегментов. На мой взгляд такая индикация выглядит солидно и профессионально . В книге В.Трамперта "Измерение, управление и регулирование с помощью AVR-микроконтро ллеров." я считаю самая правильная реализация однопроводного подключения в случае со сдвиговыми регистрами, стоит ли оно того, решать Вам. Остальные поделки, которые мне встречались содержат простую RC-цепь, а это не надежно.
У меня без SPI потребовалось три ноги МК для 8 разрядной "панели". Со SPI надо 4, четвертый как раз и есть эта защелка.
Quote: Eсть два варианта такого подключения. Первый - с использованием RC цепочки, второй - с использованием двух одновибраторов. Во втором варианте деталей побольше, но мне он внушает больше доверия.
а как быть если у меня 9разрядный индикатор с общим катодом?
auag27 собака mail.ru
0x3f, //0
0x06, //1
0x5b, //2
0x4f, //3 И.Т.Д. я так понял они означают те выводы определённого порта которые нужно включить что бы высветилась определённая цифра. например для единички это будет 15 и 16 ножка то есть порт В1, В2. и катод подключеный к С7. в 16ричной системе сумма этих портов будет записываться как 0x3f.
так вот сама технология перевода портов в 16 систему меня интересует. помогите пожалуйста разобраться! :-)
В зависимости от цифры нужно включать биты(ножки мк)
Например чтобы вкл. цифру 8 необходимо зажечь все сегменты, кроме точки. Тогда:
PORTX = 0b11111110;
Теперь переводим это дело в 16-ную .
Для этого разбиваем наши циферки на две группы по четыре цифры
1111 1110 и заглядываем в таблицу соответствия:
Десятичная
1)Десятичная система
2)Двоичная система
3)Шестнадцатеричная система
0
0
0
1
1
1
2
10
2
3
11
3
4
100
4
5
101
5
6
110
6
7
111
7
8
1000
8
9
1001
9
10
1010
A
11
1011
B
12
1100
C
13
1101
D
14
1110
E
15
1111
F
16
10000
10
Получаем 0xEF;
Знаете, бывает, что удобнее так развестись :-)
Например, так
Code:
#define SEG0 3
#define PORT_SEG0 PORTB
#define SEG1 4
#define PORT_SEG1 PORTC
....
unsigned char data;
....
void OutData(unsigned char data)
{
if (data&(1<<0)) SetBit(PORT_SEG0, SEG0);
else ClearBit(PORT_SEG0, SEG0);
if (data&(1<<1)) SetBit(PORT_SEG1, SEG1);
else ClearBit(PORT_SEG1, SEG1);
...
}
Если посидеть подумать, может быть удастся найти более красивое решение.
Code:
#define SEG0 3
#define PORT_SEG0 PORTB
#define SEG1 4
#define PORT_SEG0 PORTC
Очевидно в 4-й строке PORT_SEG1?
Наваяю - можно выложить код?
Code:
Error[Pe020]: identifier "TOIE1" is undefined C:\project\main.c 37
Error[Pe020]: identifier "TCCR0" is undefined C:\project\main.c 38
Error[Pe020]: identifier "CS02" is undefined C:\project\main.c 38
Error[Pe020]: identifier "CS01" is undefined C:\project\main.c 38
Error[Pe020]: identifier "CS00" is undefined C:\project\main.c 38
и т.п. По даташиту таймеры там есть и буковки типа TOIE1 есть, а в библиотеке iotiny2313.h нет. Простите начинающего,про сто хочу разобраться. Как быть или где почитать?
у меня не получается увидеть устройство... :-(
Для частоты прерываний 100Гц при кварце 8МГц и делителе на 1024 по подсчету нужно 78,125 тактов. У тебя указано 77 (ff-b2).где 78й выполняется?
77й TCNT0 == 0xFF
78й TCNT0 == 0x00
прерывание по переполнению
Последовательно сть такая?
Вопрос не в тему:
Кокой программой пользуетесь для рисования схем на этом сайте?
Пишу в этой ветке вот почему, используя Ваши библиотеки 7-seg столкнулся с проблемкой, нужно было оперативно менять кол-во выводимых разрядов, т.е. гасить старшие, оставляя в работе младшие и наоборот. Пришлось немного подправить Вашу библиотеку :) добавив в функцию void IND_Output ещё одну переменную. теперь это выглядит так:
Code:
/******************************************
* Function name : IND_Output
* Returns : нет
* Parameters : value - число для преобразования, comma - позиция точки на индикаторе
digN - количество разрядов выводимых на индикатор справа на лево
* Purpose : преобразует 16-ти разрядное число, записывает его в буфер индикатора
******************************************/
void IND_Output(unsigned int value, unsigned char comma, unsigned char digN);
и
Code:
//***************************************
void IND_Output(unsigned int value, unsigned char comma, unsigned char digN)
{
unsigned char i;
unsigned char tmp;
for(i = 0; i < digN; i++){
tmp = value % 10;
buf = number[tmp];
value = value/10;
}
if (comma < digN) {
buf[comma] |= 1<<(SEG_DP);
}
}
//**************************************
В примере 4, если использовать индикатор с общим анадом, что нужно исправить в коде?
P.S. желательно исправленный код.
Мне надо подправить драйвер семисегментного индикатора так, чтобы на индикаторе выводить переменную со знаком (signed). Как это правильно сделать. Эта переменная рассчитывается постоянно в программе, и ее со знаком (+ или -)надо выводить на семисегментник.
Спасибо.
http://alex.starspirals.net/2010/02/07/управление-семисегментным-индикатор/
Твой комментарий удалился, потому что я потер комментарии maksim
//инициализация таймера Т0
TIMSK = (1
Code:
//инициализация таймера Т0
TIMSK = (1<<TOIE0);
TCCR0 = (1<<CS02)|(0<<CS01)|(1<<CS00);
TCNT0 = 0xb2;
Почему не указан регистр OCR0A ? В него же записывается значение с которым сравнивается значение TCNT0 и происходит прерывание? В прерывание нужно обнулить TCNT0, и т.д. Поправьте если я не прав.
Индикация работает, но не могу понять почему)) Как работает таймер в Вашем случае?
Заранее благодарен :)
Quoting Юра: Таймер считает от 0xb2 (за магические цифры надо бить по рукам!) до переполения, генерит прерывание, в прерывании в него снова записывается 0xb2 (хотя надо прибавлять 0xb2, см. тут )
С работой таймера разобрался за пару часов со статьей про прерывания и с даташитом.
Большое спасибо за ссыылочку :) Вот про "магические числа" Вы правы))) Путаюсь я в этом пока, что и как считать, а в Вашем примере всё просто и понятно, только поменять в дефайн и всё. Ничего высчитывать не нужно.
Подключение трех разрядного семисегметного индикатора к МК. Использовать индикаторы с общим анодом с динамической способом подключения
Вот E-Mail: skyvip@bk.ru
Also visit my blog: shonaad71.webno de.com (Maryanne: http://shonaad71.webno de.com/news/having-the-best-40th-celebration/)
Code:
PORTD|=((1<<1)|(1<<0));
if (count==0)
{
PORTB=number[data2];
PORTD&=~(1<<1);
}
if (count==1)
{
PORTB=number[data1];
PORTD&=~(1<<0);
}
count++;
if (count==2)count=0;
В приведенных примерах незадействованн ые сегменты индикатора тоже включены но не светятся только из за того что время их включения меньше если уменьшить время сканирования видна засветка на индикаторах. И боюсь что 3 знакоместа уже в примере не работоспособны. Собственно и эта конструкция неудачна достаточно большое время индикаторы погашены.
Boorstysound посмотрите пожалуйста на исходники автора уважаемого тов Pashgan...
программы предельно просты и прозрачны...
А именно Пример 4. Вывод цифр от 0 до 99.
Преобразование двоичных чисел в двоично-десятич ные (BCD).
По вашим критериям программу можно изменить всего в нескольких строках а именно в строке инициализации переменной
//программный счетчик секунд
unsigned char counterProg = 0; установите вместо цифры 0 цифру 99.
далее немного изменить основной цикл программы:
while(1){
data1 = counterProg % 10;
data2 = counterProg/10;
//программный счетчик секунд
if (counterProg ==0) counterProg = 99 else counterProg--;
__delay_cycles( 8000000);
}
RSS feed for comments to this post