avr 7 segment display
Январь 30, 2010
В последнее время на форумах видел много тем по подключению светодиодных индикаторов к контроллеру, но не все знают, как управлять индикатором. Типичная ошибка - это непонимание принципа организации сегментов. На самом деле не надо конвертировать десятичные числа в формат bcd и не надо цеплять внешние декодеры. Контроллер много умнее.
Чтобы зажигать цифры, надо представить, что мы подключили к контроллеру обычные светодиоды. В определенные моменты нам надо одновременно зажигать определенные светодиоды. Для этого есть некая договоренность, как распологать эти диоды.
Сегменты обозначаются буквами от a до h, причем последняя обозначает точку (в данном случае это буква р). Порядок букв идет справа налево. Если надо зажечь сегмент, то под буквой ставим 1. Как видно, сегмент h во всех цифрах выключен. Однако в процессе мы разсмотрим, как его включить в нужный момент.
Для начала надо подключить индикатор к контроллеру.
Для симуляции же схема будет упрощенной и туда добавился еще один индикатор с общим анодом, т.к. не у всех есть точно такой же индикатор, как у автора какой-то схемы, а прошивку подправить не могут. После прочтения этого поста, каждый сможет подправить под себя.
В реале контроллер работает на стандартной частоте в 8мгц с делителем на 8, так что вам не надо трогать фьюзы. Реальная частота - 1мгц.
Для начала нам надо перевести наборы нулей и единиц в удобоваримый шестнадцатиричный вид, хотя можно оставить и двоичный, добавив перед этим 0b, чтобы компилятор понял формат.
Я перевел все в шестнадцатиричный. А для хранения чисел я использую массив. В конечном счете подготовительный код будет выглядеть так.
1: #include <avr/io.h>
2: #include <util/delay.h>
3:
4: #define LEDC_PORT PORTD // LEDC с общим катодом
5: #define LEDC_DDR DDRD
6:
7:
8: const unsigned char digs[10]={0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F}; // сathode
9:
10:
11: int main(void){
12:
13: LEDC_DDR = 0xFF;
14: LEDC_PORT = 0x00;
15:
16: return 0;
17:
18: }
Таким образом мы можем обращаться к элементам массива как к цифрам.
Для зажигания 0 достаточно сделать LEDC_PORT = digs[0]; в 15й строке.
После компиляции должно быть так:
Device: atmega8
Program: 108 bytes (1.3% Full)
(.text + .data + .bootloader)
Data: 10 bytes (1.0% Full)
(.data + .bss + .noinit)
Теперь разберемся с точкой.
За точку у нас отвечает бит h, который стоит слева и имеет самый больший вес. В двоичном коде точку надо задать так 0b10000000 или 0x80.
Теперь перед выводом данных нам надо поместить эту единицу к нужной цифре или другими словами - выставить тот пустой бит в 1. Для этого служит маскИрование или операции с маской. А маской является точка. Для включения любого бита применяется операция логического ИЛИ со сдвигом на нужное место. В нашем случае сдвиг не нужен, т.к. маска заготовлена заранее.
Вся операция маскирования выглядит так:
LEDC_PORT = digs[0] | 0x80;
Что происходит?
Число 0x3F соединяется при помощи ИЛИ с 0x80 и получается 0xBF. При таком варианте записи рассмотреть весь механизм нельзя. Поэтому запишем это в двоичном режиме.
0 0111111 -> 0x3F
1 0000000 -> 0x80
----------------------
1 0111111 -> 0xBF
При логическом ИЛИ если бит в одном из чисел был 1, то и в конечном результате этот бит будет 1, даже если во втором числе он был 0.
Чтобы не думать о числах, надо придать этому числу некий образ.
#define DOT 0x80 и тогда можно писать просто LEDC_PORT = digs[0] | DOT;
Как видно из схемы в протеусе, там еще один индикатор, который подключен немного не так. Его общий вывод является анодом и подключается к плюсу.
Что будет, если в этот индикатор вывести наши данные из массива?
Для этого добавим вверху
#define LEDA_PORT PORTB // LEDC с общим анодом
#define LEDA_DDR DDRB
а в функции main еще сконфигурируем порты на выход и выведем уже в порт LEDA_PORT.
LEDA_DDR = 0xFF;
LEDA_PORT = 0x00;
LEDA_PORT = digs[0];
Получилось все наоборот - сегменты, которые должны гореть - не горят. Зато горят ненужные. Из этого следует, что нам надо все биты в массиве проинвертировать.
Тогда получим
unsigned char digs[10]={0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90}; //anode
Т.к. 2 одинаковые переменные не могут существовать, то мы переименуем 2 массива соответственно.
unsigned char cathode[10]={0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F}; // cathode
unsigned char anode[10]={0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90}; //anode
Если у вас 2 панели индикаторов с катодом и анодом в каждой, тогда этот код можно сократить, етм самым оптимизировав расход памяти, ибо все 20 байт будут загружены в ОЗУ. Инвертирование делается при помощи логической операции XOR или исключающее ИЛИ.
А чтобы программа приобрела некий смысл, то мы напишем простой счетчик от 00 до 99 с зажиганием точек. По окончанию счета он останавливается на 99 и горит точка на первом индикаторе единиц.
Для ведения счета нам потребуется 1 цикл с переменной i и переменная j для хранения десятков. Так же нужна переменная для работы цикла counter.
Весь код выглядит так.
1: #include <avr/io.h>
2: #include <util/delay.h>
3:
4: #define LEDC_PORT PORTD // LEDC с общим катодом
5: #define LEDC_DDR DDRD
6:
7: #define LEDA_PORT PORTB // LEDA с общим анодом
8: #define LEDA_DDR DDRB
9:
10: #define DOT 0x80
11:
12: const unsigned char cathode[10]={0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F}; // katode
13: const unsigned char anode[10]={0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90}; //anode
14:
15: int main(void){
16: char counter,i, j;
17:
18: LEDC_DDR = 0xFF;
19: LEDC_PORT = 0x00;
20:
21: LEDA_DDR = 0xFF;
22: LEDA_PORT = 0x00;
23:
24: while(counter!=100){
25:
26: if(i==10){ i=0; j++; } // тут же заканчивается if
27:
28: LEDC_PORT = cathode[i];
29: LEDA_PORT = anode[j];
30:
31: _delay_ms(100); // макрос задержки для частоты в 1мгц
32:
33: i++;
34: counter++;
35: }
36:
37: LEDC_PORT = cathode[9]|DOT; // зажигаем точку
38:
39: return 0;
40:
41: }
Считаем до 100 потому как начинаем счет с нуля. Можно было бы в while написать и до 99, но тогда пришлось бы присваивать counter значение 1, а это трата лишней памяти.
Не забываем, что и точка для индикаторов с общим анодом должна быть инвертирована. 0b01111111. Точно так же и операция OR инвертируется и заменяется на AND.
категория: учим мк avr