avr 7 segment display

Январь 30, 2010

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

 7seg_pinouts

Сегменты обозначаются буквами от a до h, причем последняя обозначает точку (в данном случае это буква р). Порядок букв идет справа налево. Если надо зажечь сегмент, то под буквой ставим 1. Как видно, сегмент h во всех цифрах выключен. Однако в процессе мы разсмотрим, как его включить в нужный момент.

Для начала надо подключить индикатор к контроллеру. 
avr_7seg

Для симуляции же схема будет упрощенной и туда добавился еще один индикатор с общим анодом, т.к. не у всех есть точно такой же индикатор, как у автора какой-то схемы, а прошивку подправить не могут. После прочтения этого поста, каждый сможет подправить под себя.

 avr_7seg_proteus

В реале контроллер работает на стандартной частоте в 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];

 ledanode

Получилось все наоборот - сегменты, которые должны гореть - не горят. Зато горят ненужные. Из этого следует, что нам надо все биты в массиве проинвертировать.

Тогда получим 
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.

Tags: ,

категория: учим мк avr

ответить

Авторизация только через loginza.

Yandex Google Вконтакте Mail.ru Twitter Loginza MyOpenID OpenID