Подключение индикатора CA56-12/CC56-12 к микроконтроллеру AVR

Подключение светодиодных многоразрядных цифровых семисегментных индикаторов CA56-12/CC56-12 к микроконтроллерам AVR (ATtiny/ATmega и другим).


Автор: Погребняк Дмитрий

Самара, 2013.


Индикаторы CA56-12/CC56-12

Каждый цифровой разряд индикатора представляет собой группу светодиодов, соединённых собой одним из выводов. Для индикатора CA – общим является анод светодиодов, для CC – катод.

Вторые выводы (катоды и аноды соответственно) соединены между собой для соответствующих сегментов всех четырёх цифровых разрядов. Нумерация выводов и схема соответствий общих выводов для выбора разряда и выбора сегмента представлена ниже:

CA56-12/CC56-12 – нумерация выводов (распиновка) и соответствие выводов сегментам и разрядам
CA56-12/CC56-12 – нумерация выводов (распиновка) и соответствие выводов сегментам и разрядам


Подключение к микроконтроллеру AVR

Для того, чтобы вывести информацию на индикатор необходимо с большой частотой попеременно зажигать каждый из разрядов. Не принципиально, какой из полюсов использовать для перебора: это может быть как выбор одного из сегментов в семисегментных индикаторах при одновременной подаче напряжения на необходимые вводы, выбирающие разряд. Но куда практичнее и удобнее поступать наоборот: попеременно подавая плюс или минус (соответственно для CA и CC) на один из входов, выбирающих разряд, подавать соответственно минус или плюс одновременно на все входы сегментов, которые необходимо зажечь в выбранном разряде. Удобно подключить все аноды на один из портов микроконтроллера, а катоды – на другой.


Выбор ограничивающих резисторов

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


Расчёт резистора можно провести по формуле:

R = (U0 – ULED) / ILED,

U0 - напряжение на линии питания светодиодов.

ULED - прямое напряжение, рассчитанное для светодиодов (можно взять в спецификации на индикатор)

ILED - расчётный ток для светодиода.

Например, спецификация на мой индикатор зелёного цвета, СС56-12GWA определяет напряжение светодиодов, как 2.2 Вольта, при токе 20 мА. Спецификация допускает использование более высокого значения тока (до 140 мА) в импульсном режиме – импульсами не более 0,1 мс (100 микросекунд), со скважностью не менее 10.

Проведём расчёт резистора для напряжения питания 5В и тока 20мА при напряжении на светодиоде 2,2 В:

R = (5В – 2.2В) / 0.02А = 140Ом.

Не обязательно точно выдерживать сопротивление. Подойдут резисторы ближайшего доступного, но не меньшего номинала, например 150, 160, или 180 Ом, т.к. небольшое снижение тока не сильно заметно сказывается на яркости.


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


Прямое подключение

Прямое подключение индикатора CA56-12/CC56-12 к микроконтроллеру ATmega или ATtiny
Прямое подключение индикатора CA56-12/CC56-12 к микроконтроллеру ATmega или ATtiny

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


Для портов ATmega и ATtiny выдаваемый или принимаемый одним выводом ток не должен превышать 40мА, для подробностей – сверьтесь со спецификацией на микроконтроллер. Так как восемь светодиодов подключаются на один вывод, выбирающий разряд, то ток на каждом из светодиодов не должен превышать 40/8 = 5мА.

Ограничение тока 40мА на один вывод обозначено как предельно допустимое. Превышение этого тока может привести к выходу из строя оборудования. Для некоторых моделей микроконтроллеров AVR предельно допустимый ток может быть меньше. Сверьтесь со спецификацией.

Для грубого расчёта можно воспользоваться вышеприведённой формулой, приняв в качестве значения ILED ток 0,005А. Для более точного расчёта необходимо воспользоваться графиками зависимости напряжений от тока из спецификаций на индикатор и микроконтроллер.

Например, из спецификации на СС56-12GWA видно, что ток 5мА протекает при падении напряжения на светодиоде 1,9В. А из спецификации на микроконтроллер ATmega8A видно что при напряжении питания 5В и температуре 25 градусов, при токе 5мА напряжение на выдающем выводе составит около 4,88 В, а на принимающем выводе около 0,12В. Значит в вышеприведённой формуле следует взять разницу 4,88В-0,12В за U0, 1,9В за ULED, а в качестве ILED – рассчитываемый ток 5мА.

В итоге получается ((4,88В - 0,12В) - 1,9В) / 0,005А = 572 Ома.


Конфигурирование портов в ATtiny и ATmega при прямом подключении

Порты ввода-вывода микроконтроллеров, в зависимости от значений соответствующих разрядов регистров DDRx и PORTx могут работать в разных режимах.

DDRx.nPORTx.nНаправлениеСостояние вывода
00ВводВывод находится в свободном состоянии (tri-state)
01ВводВывод подключен на VCC(линию питания) через встроенный подтягивающий резистор 20-50 килоОм
10ВыводВывод подключен напрямую на «землю»
11ВыводВывод подключен напрямую на VCC(линию питания)

Итак, зажигание светодиода будет производиться следующим образом:

Со стороны анода (для CC56-это порт выбора сегментов, для CA56-это порт выбора разрядов) порт должен быть сконфигурирован всегда на вывод: DDRx.n = 1. Выставляя соответствующий бит регистра PORTx, данный вывод будет либо подключаться на минус («землю»), либо на плюс.

Со стороны катода (для CC56-это порт выбора разрядов, для CA56-это порт выбора сегментов) вывод должен быть сконфигурирован в ноль: PORTx.n = 0. Выбирая соответствующим битом регистра DDRx направление порта, данный вывод будет либо оставаться в свободном состоянии, либо замыкаться на минус («землю»).


Подключение общим выводом через транзистор

Подключение индикаторов с общим катодом (CC56-12)
Подключение индикаторов с общим катодом (CC56-12)
Подключение индикаторов с общим анодом (CA56-12)
Подключение индикаторов с общим анодом (CA56-12)

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

Спецификация на микроконтроллер, как правило, указывает максимально допустимый суммарный ток с группы выводов, который не рекомендуется превышать (см. спецификацию на микроконтроллер, раздел Electrical Characteristics, DC Characteristics, комментарии под таблицей). Это ограничение зависит от типа микроконтроллера и варианта исполнения (типа корпуса). Например, для ATmega8 ток с выводов C0-C5 в сумме не должен превышать 100мА, а с остальных выводов – 200мА. Для ATmega32 в корпусе DIP ток с выводов порта A не должен превышать 100мА, и с выводов остальных портов – также 100мА. В корпусах TQFP, QFN/MLF ограничения для ATmega32 A0..A7 – 100 мА, B0..B4 – 100мА, B3..B7 + D0..D2 + XTAML – 100мА, D3..D7 – 100мА, C0-C7 – 100мА, но в сумме со всех выводов – не более 400мА.

В любом случае, рекомендую ограничиться током в 100мА на порт, то есть по 12,5 мА на каждый вывод. В этом случае номинал резисторов R1-R8 составит 224Ома.


Общий вывод (выбирающий разряд) подключается через транзисторы. Это NPN транзисторы замыкающие вывод на землю для индикаторов с общим катодом (CC56-12), или PNP транзисторы, замыкающие на линию питания для индикаторов с общим анодом (CA56-12). Во втором случае можно использовать отдельные линии питания для индикатора и для микроконтроллера.

Транзисторы должны быть рассчитаны на соответствующий ток и напряжение. Например, подойдут КТ503Б – NPN, для CC56-12, или КТ502Б – PNP, для CA56-12, рассчитанные на ток до 150мА и напряжение 25Вольт.


Зная минимальный статический коэффициент передачи тока h21э, можно вычислить номинал резисторов R9-R12. Например, если ток, проходящий через эмиттер-коллектор к индикатору, составит 100мА, а h21э для нашего транзистора равен 80, то ток, проходящий через базу-эмиттер должен быть 100/80 = 1,25мА. Значит, при напряжении на выводе микроконтроллера 5В, номинал резистора должен быть не более 4кОм.


Управление выводами, выбирающими разряд, в этом случае аналогично описанному для прямого подключения, но наоборот:

- для индикаторов с общим анодом (CA56-12) необходимо задать уровень сигнала 0 (PORTx.n = 0). Управление осуществляется регистром направления порта (DDRx). При записи в DDRx.n единицы соответствующий вывод будет замыкаться на землю, и через подключенный транзистор пойдёт ток.

- для индикаторов с общим катодом (CC56-12) порт должен быть сконфигурирован на вывход (DDRx.n = 1), а открываться подключенный транзистор будет при подаче на порт высокого уровня сигнала (PORTx.n = 1)


Подлкючение всех выводов через транзисторы

Наконец, третий вариант – подключить все выводы через транзисторы. Со стороны анодов это будет PNP-транзисторы, эмиттерами подключенные к линии питания, со стороны катодов индикатора – это NPN-транзисторы, подключенные эмиттерами к «земле». Соответствующим образом меняется управление выводами как выбирающими сегмент, так и выбирающими разряд. Для индикатора с общими катодами вывод через транзисторы будет аналогичен описанному в варианте прямого подключения для индикатора с общими анодами, и наоборот.


Такой вариант подключения хорош если требуется провести большой ток через индикатор (например, при подключении множества индикаторов), или снизить нагрузку на микроконтроллер.


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


Использование прерывания таймера

Удобным вариантом является периодическая смена разрядов в прерывании таймера. Выбор делителя таймера желательно производить таким образом, чтобы частота обновления всего индикатора была не менее 50 Гц, а желательно - более 100. Т.к. на нашем индикаторе 4 разряда то вызов прерывания таймера должен происходить не менее 400 раз в секунду. Например, если микроконтроллер работает с частотой 8 МГц, то хорошим вариантом будет обработка по прерыванию 8-битного таймера, работающего в нормальном режиме с делителем частоты 1/64. Тогда переполнение таймера будет происходить 8000000/64/256 = 488,3 раза в секунду, что вполне удовлетворяет описанным выше требованиям.

TCCR0 = 0b00000011; // 1/64 prescaler;
TIMSK |= _BV(TOIE0);

Название векторов прерываний можно подсмотреть здесь. Так, например, для большинства микроконтроллеров ATmega вектор прерывания по переполнению таймера 0 будет TIMER0_OVF_vect.


Пример кода

Ниже приведён пример кода для работы с 4хразрядным индикатором. Код легко может быть сконфигурирован и на другие типы индикаторов.

Код написан на языке C для avr-gcc.


Весь код является свободным для любого, включая коммерческое, использования, при условии ссылки на автора (Погребняк Дмитрий, http://aterlux.ru/).


LEDind.h

Все макросы ниже определены для прямой схемы подключения как на схеме выше. Количество выводов (4) и режим подключения (общий катод) выбраны для индикатора CC56-12. С другими индикаторами код не испытывался.

Изменяя определения макросов в заголовочном файле данный код может быть сконфигурирован на любой вариант подключения из описанных выше, а также на индикаторы, или группу индикаторов в сумме от 1 до 8ми разрядов.

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


#ifndef LEDIND_H_
#define LEDIND_H_

#include <avr/io.h>

// Количество разрядов, от 1 до 8
#define LEDIND_NUM_DIGITS 4

// Если определено - то общий катод, иначе - общий анод
#define LEDIND_COMMON_CATHODE 

// Если определено - то выводы выбирающие разряд (общие для сегментов) подключены через транзисторы 
//#define LEDIND_POSITION_TRANSISTORS

// Если определено - то выводы выбирающие сегмент подключены через транзисторы
//#define LEDIND_DIGIT_TRANSISTORS

// PORTx для порта, формирующего разряд
#define LEDIND_DIGIT_PORT PORTC
// DDRx для порта, формирующего разряд
#define LEDIND_DIGIT_DDR DDRC

// PORTx для порта, выбирающего разряд (общий)
#define LEDIND_POSITION_PORT PORTD
// DDRx для порта, выбирающего разряд (общий)
#define LEDIND_POSITION_DDR DDRD

// Пины, используемые для вывода в порт соответсвующего разряда (от 1 до LEDIND_NUM_DIGITS, слева-направо)
#define LEDIND_POSITION_PIN1 PD3
#define LEDIND_POSITION_PIN2 PD4
#define LEDIND_POSITION_PIN3 PD5
#define LEDIND_POSITION_PIN4 PD6


#if (LEDIND_NUM_DIGITS == 1 )
#define LEDIND_POSITION_MASK _BV(LEDIND_POSITION_PIN1)
#elif (LEDIND_NUM_DIGITS == 2)
#define LEDIND_POSITION_MASK (_BV(LEDIND_POSITION_PIN1) | _BV(LEDIND_POSITION_PIN2))
#elif (LEDIND_NUM_DIGITS == 3)
#define LEDIND_POSITION_MASK (_BV(LEDIND_POSITION_PIN1) | _BV(LEDIND_POSITION_PIN2) | \
    _BV(LEDIND_POSITION_PIN3))
#elif (LEDIND_NUM_DIGITS == 4)
#define LEDIND_POSITION_MASK (_BV(LEDIND_POSITION_PIN1) | _BV(LEDIND_POSITION_PIN2) | \
    _BV(LEDIND_POSITION_PIN3) | _BV(LEDIND_POSITION_PIN4))
#elif (LEDIND_NUM_DIGITS == 5)
#define LEDIND_POSITION_MASK (_BV(LEDIND_POSITION_PIN1) | _BV(LEDIND_POSITION_PIN2) | \
    _BV(LEDIND_POSITION_PIN3) | _BV(LEDIND_POSITION_PIN4) | _BV(LEDIND_POSITION_PIN5))
#elif (LEDIND_NUM_DIGITS == 6)
#define LEDIND_POSITION_MASK (_BV(LEDIND_POSITION_PIN1) | _BV(LEDIND_POSITION_PIN2) | \
    _BV(LEDIND_POSITION_PIN3) | _BV(LEDIND_POSITION_PIN4) | _BV(LEDIND_POSITION_PIN5) | \
    _BV(LEDIND_POSITION_PIN6))
#elif (LEDIND_NUM_DIGITS == 7)
#define LEDIND_POSITION_MASK (_BV(LEDIND_POSITION_PIN1) | _BV(LEDIND_POSITION_PIN2) | \
    _BV(LEDIND_POSITION_PIN3) | _BV(LEDIND_POSITION_PIN4) | _BV(LEDIND_POSITION_PIN5) | \
    _BV(LEDIND_POSITION_PIN6) | _BV(LEDIND_POSITION_PIN7))
#elif (LEDIND_NUM_DIGITS == 8)
#define LEDIND_POSITION_MASK (_BV(LEDIND_POSITION_PIN1) | _BV(LEDIND_POSITION_PIN2) | \
    _BV(LEDIND_POSITION_PIN3) | _BV(LEDIND_POSITION_PIN4) | _BV(LEDIND_POSITION_PIN5) | \
    _BV(LEDIND_POSITION_PIN6) | _BV(LEDIND_POSITION_PIN7)  | _BV(LEDIND_POSITION_PIN8))
#endif


// Если обновление цифр происходит по таймеру то здесь следует указать прерывание, используемое для этого
#define LEDIND_USE_INTERRUPT TIMER0_OVF_vect 
// Иначе необходимо периодически вызывать ledind_display_update() вручную

// Минус
#define LEDIND_MINUS 0b00010000
// Точка после цифры (комбинируется с цифрой)
#define LEDIND_POINT 0b00000100
// Символ переполнения (выводится в левой позиции)
#define LEDIND_OVERFLOW 0b11010011

// Выводимые данные, формирующие соответствующую цифру в одном разряде
#define LEDIND_DIGIT0 0b11101011
#define LEDIND_DIGIT1 0b00101000
#define LEDIND_DIGIT2 0b10110011
#define LEDIND_DIGIT3 0b10111010
#define LEDIND_DIGIT4 0b01111000
#define LEDIND_DIGIT5 0b11011010
#define LEDIND_DIGIT6 0b11011011
#define LEDIND_DIGIT7 0b10101000
#define LEDIND_DIGIT8 0b11111011
#define LEDIND_DIGIT9 0b11111010
// Буквы A..F для шестнадцатиричного вывода
#define LEDIND_DIGITA 0b11111001
#define LEDIND_DIGITB 0b01011011
#define LEDIND_DIGITC 0b11000011
#define LEDIND_DIGITD 0b00111011
#define LEDIND_DIGITE 0b11010011
#define LEDIND_DIGITF 0b11010001


/*
  Инициализирует порты для вывода на семисегментные индикаторы.
*/
void ledind_init();

/*
 Зажигает очередную цифру на дисплее, если не задно LEDIND_USE_INTERRUPT, то этот метод должен
 периодически вызываться. С частотой LEDIND_NUM_DIGITS * частота обновления. Для комфортного отображения,
 частота обновления должна быть не менее 100 Гц
*/
void ledind_display_update();

/*
  Выводит данные на указанной позиции.
  position 0..(LEDIND_NUM_DIGITS - 1)
  data - двоичные данные, выводимые в порт при зажигании соответствующей позиции.
*/
void ledind_set_data(uint8_t position, uint8_t data);

/*
  Вывод десятичного числа со знаком. Если число не помещается на экране, то отображается символ переполнения
  num - число
  decpoint - позиция десятичной точки. Если число не помещается на экране, то разряды правее точки 
     выталкиваются, а само число округляется.
  first_symbol - символ, выводимый в самой левой позиции. Если не указан (0), то левая позиция также 
     используется для вывода числа.
*/
void ledind_num(int16_t num, uint8_t decpoint, uint8_t first_symbol);

/*
  Вывод шестнадцатиричного представления числа
*/
void ledind_hex(uint16_t hex);


#endif /* LEDIND_H_ */

LEDind.c

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include "LEDind.h"

uint8_t ledind_current_digit = 0;
uint8_t ledind_digits[LEDIND_NUM_DIGITS];


// Информация выводимая в регистр, выбирающий разряд (LEDIND_POSITION_DDR или LEDIND_POSITION_PORT)
const uint8_t PROGMEM ledind_position_out[] = {
  _BV(LEDIND_POSITION_PIN1)
  #if (LEDIND_NUM_DIGITS > 1)
  , _BV(LEDIND_POSITION_PIN2)
  #endif
  #if (LEDIND_NUM_DIGITS > 2)
  , _BV(LEDIND_POSITION_PIN3)
  #endif
  #if (LEDIND_NUM_DIGITS > 3)
  , _BV(LEDIND_POSITION_PIN4)
  #endif
  #if (LEDIND_NUM_DIGITS > 4)
  , _BV(LEDIND_POSITION_PIN5)
  #endif
  #if (LEDIND_NUM_DIGITS > 5)
  , _BV(LEDIND_POSITION_PIN6)
  #endif
  #if (LEDIND_NUM_DIGITS > 6)
  , _BV(LEDIND_POSITION_PIN7)
  #endif
  #if (LEDIND_NUM_DIGITS > 7)
  , _BV(LEDIND_POSITION_PIN8)
  #endif
};

// Информация выводимая в LEDIND_DIGIT_OUT_REGISTER для отображения соответствующей цифры от 0 до 9
const uint8_t PROGMEM ledind_digits_pattern[] = {
  LEDIND_DIGIT0, LEDIND_DIGIT1, LEDIND_DIGIT2, LEDIND_DIGIT3, LEDIND_DIGIT4,
  LEDIND_DIGIT5, LEDIND_DIGIT6, LEDIND_DIGIT7, LEDIND_DIGIT8, LEDIND_DIGIT9,
  LEDIND_DIGITA, LEDIND_DIGITB, LEDIND_DIGITC, LEDIND_DIGITD, LEDIND_DIGITE, LEDIND_DIGITF
};

inline void ledind_display_update() {
  uint8_t cd = ledind_current_digit;
  uint8_t pd = pgm_read_byte(&ledind_position_out[cd]);
  uint8_t d = ledind_digits[cd];
  // Сначала снимем вывод
  #if (defined(LEDIND_COMMON_CATHODE) ^ defined(LEDIND_POSITION_TRANSISTORS))
    LEDIND_POSITION_DDR &= ~LEDIND_POSITION_MASK;
  #else
    LEDIND_POSITION_PORT &= ~LEDIND_POSITION_MASK;
  #endif
  // Меняем уровни на выводах, выбирающих сегменты и формирующих цифру
  #if (defined(LEDIND_COMMON_CATHODE) ^ defined(LEDIND_DIGIT_TRANSISTORS))
    LEDIND_DIGIT_PORT = d;
  #else
    LEDIND_DIGIT_DDR = d;
  #endif
  // Выбираем нужный разряд
  #if (defined(LEDIND_COMMON_CATHODE) ^ defined(LEDIND_POSITION_TRANSISTORS))
    LEDIND_POSITION_DDR |= pd;
  #else
    LEDIND_POSITION_PORT |= pd;
  #endif
  cd++;
  if (cd >= LEDIND_NUM_DIGITS)
    cd = 0;
  ledind_current_digit = cd;
}

#ifdef LEDIND_USE_INTERRUPT
ISR(LEDIND_USE_INTERRUPT) {
  ledind_display_update();
}
#endif

void ledind_init() {
  #if (defined(LEDIND_COMMON_CATHODE) ^ defined(LEDIND_DIGIT_TRANSISTORS))
    LEDIND_DIGIT_PORT = 0x00;
    LEDIND_DIGIT_DDR = 0xFF;
  #else
    LEDIND_DIGIT_DDR = 0x00;
    LEDIND_DIGIT_PORT = 0x00;
  #endif
  #if (defined(LEDIND_COMMON_CATHODE) ^ defined(LEDIND_POSITION_TRANSISTORS))
    LEDIND_POSITION_PORT &= ~LEDIND_POSITION_MASK;
    LEDIND_POSITION_DDR &= ~LEDIND_POSITION_MASK;
  #else
    LEDIND_POSITION_PORT &= ~LEDIND_POSITION_MASK;
    LEDIND_POSITION_DDR |= LEDIND_POSITION_MASK;
  #endif
  for (uint8_t i = 0; i < LEDIND_NUM_DIGITS; i++) {
    ledind_digits[i] = 0;
  }
}

const uint16_t PROGMEM power10[] = {1, 10, 100, 1000, 10000};

void ledind_set_data(uint8_t position, uint8_t data) {
  if (position >= LEDIND_NUM_DIGITS)
    return;
  ledind_digits[position] = data;
}

void ledind_hex(uint16_t hex) {
  for (int8_t i = LEDIND_NUM_DIGITS - 1; i >= 0; i--) {
    ledind_digits[i] =  pgm_read_byte(&ledind_digits_pattern[hex & 0x0F]);
    hex >>= 4;
  }
}

void _ledind_output_error(uint8_t first_symbol) {
  #if (LEDIND_NUM_DIGITS > 1)
    ledind_digits[0] = first_symbol;
    #if (LEDIND_NUM_DIGITS > 2)
      for (uint8_t i = 1; i < (LEDIND_NUM_DIGITS - 1); i++)
      ledind_digits[i] = 0;
    #endif
  #endif
  ledind_digits[LEDIND_NUM_DIGITS - 1] = LEDIND_OVERFLOW;
}

void ledind_num(int16_t num, uint8_t decpoint, uint8_t first_symbol) {
  uint16_t unum;
  uint8_t neg;
  if (num < 0) {
    unum = -num;
    neg = 1;
  } else {
    unum = num;
    neg = 0;
  }
  // Ищем самый левый значимый разряд
  int8_t hid = 4;
  while ((hid > decpoint) && (pgm_read_word(&power10[hid]) > unum)) {
    hid--;
  }
  // Вычисляем самый номер правого разряда
  int8_t lod = hid - LEDIND_NUM_DIGITS + 1;
  if (first_symbol)
    lod++;
  if (lod > decpoint) {
    _ledind_output_error(first_symbol);
    return;
  }
  uint8_t minus_overlapped = 0; // Признак минуса, наложенного на единицу
  if (neg) {
    if (lod == decpoint) {
      if (unum < (pgm_read_word(&power10[hid]) * 2)) { // Если у нас слева единица, то можно наложить минус
        minus_overlapped = 1;
      } else { // иначе - переполнение.
        _ledind_output_error(first_symbol);
        return;
      }
    } else { // Если не в притык, то сдвинем число вправо, выталкивая разряды меньше точки
      lod++;
    }
  }
  if (lod > 0) { // Если число у нас обрезается, то округлим его
    unum += pgm_read_word(&power10[lod - 1]) * 5 - neg;
    if (minus_overlapped && (unum >= (pgm_read_word(&power10[hid]) * 2))) {
      // Если в результате округления левая единица стала двойкой, то - переполнение
      _ledind_output_error(first_symbol);
      return;
    } else if ((hid < 4) && (unum >= pgm_read_word(&power10[hid + 1]))) { // Если появился ещё один разряд
      hid++;
      if (neg && (lod == decpoint)) {
        minus_overlapped = 1;
      } else {
        lod++;
        if (lod > decpoint) {
          _ledind_output_error(first_symbol);
          return;
        }
        // Выполняем округление ещё раз. Это округление уже не должно добавлять разрядов.
        // Случаи для дисплеев с малым количеством разрядов - не рассматриваем.
        unum += pgm_read_word(&power10[lod - 1]) * 5 - neg;
      }
    }
  }
  uint8_t p = 0;
  if (first_symbol) {
    ledind_digits[0] = first_symbol;
    p++;
  }
  while (lod < 0) {
    ledind_digits[p++] = 0;
    lod++;
  }
  if (neg & !minus_overlapped) {
    ledind_digits[p++] = LEDIND_MINUS;
  }
  for (int8_t r = hid; r >= lod; r--) {
    uint16_t d = pgm_read_word(&power10[r]);
    uint8_t c = 0;
    while (unum >= d) {
      unum -= d;
      c++;
    }
    uint8_t data = pgm_read_byte(&ledind_digits_pattern[c]);
    if (minus_overlapped && (r == hid)) {
      data |= LEDIND_MINUS;
    }
    if (r && (r == decpoint)) {
      data |= LEDIND_POINT;
    }
    ledind_digits[p++] = data;
  }
}

Пример использования

Вывод значения на СС56-12GWA
Вывод значения на СС56-12GWA

Особый интерес представляет функция void ledind_num(int16_t num, uint8_t decpoint, uint8_t first_symbol). Она предназначена для вывода числа в десятичной записи на индикатор. Если число больше чем необходимое число разрядов, то на индикатор будет выведен символ ошибки.

-num - выводимое число со знаком. Если знак минуса не помещается на индикаторе и первая цифра числа – единица, то знак выводится в том же разряде, вместе с единицей.

-decpoit - позиция десятичной точки от правого края, от 0 до 4. Если число не помещается на индикаторе, то разряды правее точки не будут отображаться, а само число будет соответствующим образом округлено.

-first_symbol - значение выводимое в самой левой позиции индикатора. Если это значение не задано (ноль), то левая позиция используется также для вывода числа.


#define F_CPU 8000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include "LEDind.h"

int main(void) {
  TCCR0 = 0b00000011; // Настройка таймера
  TIMSK |= _BV(TOIE0); // Прерывание по переполнению

  ledind_init(); //Подготовка к работе с индикатором

  sei(); //Включение прерываний

  while(1)  {
    for (uint16_t a = 0; a < 10000; a++) {
      ledind_num(a, 2, 0);
      _delay_ms(100);
    }
  }
}



8 ms; mod: Mon, 11 Jan 2021 09:39:36 GMT; gen: Fri, 19 Apr 2024 16:49:12 GMT