[Home] [< EXTI: контроллер внешних прерываний/событий] [Next: UART и USART. COM-порт. Часть 1 >]

Семисегментный индикатор. Динамическая индикация

Устройство
Статическая индикация
Динамическая индикация
Пример программы



Устройство

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

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

Своё название семисегментные индикаторы получили в связи с тем, что изображение символа формируется с помощью семи отдельно управляемых (подсвечиваемых светодиодом) элементов - сегментов. Эти элементы позволяют отобразить любую цифру 0..9, а также некоторые другие символы, например: '-', 'A', 'b', 'C', 'd', 'E', 'F' и другие. Это даёт возможность использовать индикатор для вывода положительных и отрицательных десятичных и шестнадцатеричных чисел и даже текстовых сообщений. Обычно индикатор имеет также восьмой элемент - точку, используемую при отображении чисел с десятичной точкой. Сегменты индикатора обозначают буквами a, b, ..., g (a - верхний элемент, далее буквы присваиваются сегментам по часовой стрелке; g - центральный сегмент; dp - точка).

8 независимых элементов, каждый из которых может находиться в одном из двух состояний - горит или не горит, дают всего 2**8=256 возможных комбинаций. Или 128 комбинаций, каждая из которых может быть с горящей точкой или без неё.

Семисегментный индикатор

Существует два варианта одноразрядных индикаторов: с общими катодами или общими анодами. Всего для подключения используется 9 выводов - общий и 8 отдельных выводов светодиодов.

Схема подключения светодтодов в индикаторе

Статическая индикация

В том случае, если светодиоды в индикаторе имеют соединённые вместе аноды (схема с общим анодом), общий анод подключается к источнику напряжения +VDD, а катоды светодиодов - сегментов подключаются к схеме управления (например, микроконтроллеру), которая отвечает за формирование изображения на индикаторе. Зажигаются сегменты низким уровнем (логический 0) на выводе схемы управления. По отношению к схеме управления ток светодиодов является втекающим, так что могут использоваться интегральные схемы, которые имеют выходы с открытым стоком. Изменяя величину питающего индикатор напряжения VDD, можно регулировать яркость свечения.

Способы подключения семисегментного индикатора (статическая индикация)

Если в индикаторе соединены вместе катоды (схема с общим катодом), то общий катод подключается к общему проводу схемы, а аноды светодиодов подключаются к схеме управления. В этом случае сегмент зажигается высоким уровнем на выходе схемы управления, для которой ток светодиода является вытекающим, что не позволяет использовать выходы с открытым стоком, необходим выход, выполненный по двухтактной схеме. Регулировать яркость можно, подключив общий вывод индикатора к источнику смещающего напряжения 0..VDD, рассчитанного на втекающий ток, например к эмиттерному повторителю на транзисторе структуры p-n-p. Увеличивая смещение, будем уменьшать яркость свечения.

Регулировка яркости семисегментного индикатора

В спецификации на индикатор указывается потребляемый одним сегментом ток. Обычно это величина порядка нескольких мА и нагрузочной способности выводов большинства микроконтроллеров достаточно для управления индикатором. Если используется микроконтроллер с малым выходным током выходов или если используется индикатор с большим током (например, большого размера или рассчитанный на работу при ярком внешнем освещении), то подключение осуществляется через драйвер - интегральную микросхему, содержащую набор повторителей или инверторов с мощными выходами. Также можно использовать транзисторы в качестве ключей для управления индикатором.

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

Например, для индикаторов FYQ-3641Ax/Bx падение напряжения на светодиоде в зависимости от материала, цвета свечения составляет от 1.6 до 2 В при токе 5 мА и от 1.8 до 2.4 В при токе 30 мА (30 мА - максимально допустимый ток через светодиод для данного индикатора в непрерывном режиме). Так как возможен разброс значений для разных устройств даже одного типа (в меньшей степени, но есть разброс между характеристиками светодиодов и в пределах одного индикатора), а кроме того, падение напряжения зависит от температуры, поэтому параметры схемы должны обеспечивать достаточную стабильность тока при изменении падения напряжения на светодиоде.

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

Ограничение тока через светодиод с помощью резистора

При напряжении источника 3.3 В (вариант 1) падение напряжения на резисторе составит 3.3-1.8=1.5 В; значит сопротивление резистора R1=1.5 В/5 мА=300 Ом. Если в результате разброса параметров или в результате изменения температуры, или по иным причинам, возможно отклонение напряжения на светодиоде в пределах 1.6..2.0 В (±0.2 В от расчётного значения 1.8 В), это вызовет отклонение тока от расчётного значения не более ±0.7 мА или не более 14%. В большинстве практических случаев это достаточная точность для питания цепей светодиодных индикаторов, хотя ещё следует учесть нестабильность питающего напряжения, неидеальность цифровых ключей, допуск резистора.

При напряжении источника 5 В (вариант 2) падение напряжения на резисторе составит 5-1.8=3.2 В; значит сопротивление резистора R2=3.2 В/5 мА=640 Ом, выбираем 620 Ом - ближайшее значение из ряда E24. В этом случае отклонение напряжения на светодиоде ±0.2 В вызовет отклонение тока от расчётной величины порядка ±0.3 мА или не более чем ±7%. Получили точность заданного тока лучшую, чем в первом случае. Это вполне ожидаемый результат - увеличивая напряжение источника и его сопротивление, мы делаем его более близким к идеальному источнику тока.

Если задаться предельно допустимой точностью тока ±20%, можем получить, что минимальное питающее напряжение составляет 2.8 В, при этом сопротивление ограничивающего ток резистора равно 200 Ом.

Начертание символов семисегментного индикатора

Для формирования изображения символа на индикаторе используют таблицу, которая ставит в соответствие коду символа набор отображаемых сегментов. Приведём пример такой таблицы. В этой таблице код символа - его порядковый номер в таблице. Набор сегментов, формирующих символ, рассматривается как двоичное число, сегменту A соответствует младший бит числа. Если бит числа равен 0, то соответствующий сегмент не зажигается при отображении символа, а если равен 1, то зажигается. В таблице также приводится запись числа, определяющего набор зажигаемых сегментов, в шестнадцатеричной форме.

# Символ Рис. g f e d c b a HEX
0 0 0 0 1 1 1 1 1 1 0x3F
1 1 1 0 0 0 0 1 1 0 0x06
2 2 2 1 0 1 1 0 1 1 0x5B
3 3 3 1 0 0 1 1 1 1 0x4F
4 4 4 1 1 0 0 1 1 0 0x66
5 5 5 1 1 0 1 1 0 1 0x6D
6 6 6 1 1 1 1 1 0 1 0x7D
7 7 7 0 0(1) 0 0 1 1 1 0x07
(0x27)
8 8 8 1 1 1 1 1 1 1 0x7F
9 9 9 1 1 0 1 1 1 1 0x6F
10 A A 1 1 1 0 1 1 1 0x77
11 b b 1 1 1 1 1 0 0 0x7C
12 C C 0 1 1 1 0 0 1 0x39
13 d d 1 0 1 1 1 1 0 0x5E
14 E E 1 1 1 1 0 0 1 0x79
15 F F 1 1 1 0 0 0 1 0x71
16 <Пробел> space 0 0 0 0 0 0 0 0x00
17 - - (minus) 1 0 0 0 0 0 0 0x40
... можете добавить другие символы

Для символа '7' в таблице даны два возможных варианта отображения.

Динамическая индикация

Обычно требуется отображение чисел, состоящих более чем из одного разряда. Например, для цифрового вольтметра понадобится хотя бы 4..5 разрядов, а для RLC-метра, отображающего две величины или для частотомера, требуемое количество разрядов может составить 8..10. Количество разрядов в калькуляторе может превышать 12. Проблема в том, что если каждым разрядом управлять индивидуально, то с увеличением их количества, рост числа выводов микроконтроллера и количества проводников для подключения индикатора будет просто катастрофическим. Для управления N разрядами требуется 8*N управляющих линий и 1 общий провод. В случае 5 разрядов, количество управляющих линий составит 40, а в 10-разрядном индикаторе - уже 80. А между тем свободных 80 выходов может просто не быть даже у микроконтроллера в 100-выводном корпусе (с учётом выводов питания, отладки, подключения кварцевых резонаторов и реализации важных альтернативных функций). А в 100-выводных корпусах выпускаются далеко не самые дешёвые микроконтроллеры.

К счастью, динамическая индикация позволяет решить проблему, во много раз сократив требуемое для подключения количество выводов. Идея динамической индикации состоит в том, что информация отображается не во всех разрядах индикатора сразу, а поочерёдно, в каждый момент времени только в одном разряде. В связи с тем, что зрение инерционно, необязательно чтобы все элементы изображения светились непрерывно и одновременно. Если с достаточно высокой частотой последовательно переключаться от отображения одного разряда к следующему, а когда будет достигнут последний разряд индикатора, снова переходить к отображению первого и т.д., то глазом это будет восприниматься так, как если бы каждый разряд отображал информацию статично. Этот метод похож на использовавшийся в кинескопах способ формирования изображения с помощью развёртки.

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

Как правило, индикаторы, содержащие несколько разрядов, выпускаются именно в расчёте на динамическую индикацию и все необходимые соединения выполнены внутри устройства. N-разрядный индикатор в этом случае имеет 8 выводов для управления сегментами и N выводов для управления включением разрядов (общий анод или катод разряда). Всего требуется 8+N выводов, что намного лучше, чем 8*N+1 при статической индикации.

Семисегментный четырёхразрядный индикатор FYQ-3641A

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

Предположим, что на изображённом выше индикаторе мы хотим вывести цифру "1" в младшем разряде (зажечь сегменты b, c). Для этого на выводе 6 управления младшим разрядом 4 устанавливаем низкий уровень; на выводах 8, 9, 12 управления остальными разрядами устанавливаем высокий уровень; на выводах 4, 7 управления сегментами c и b устанавливаем высокий уровень, а на выводах управления остальными сегментами - низкий (выводы управления сегментами подключаем через токоограничительные резисторы). В результате только светодиоды B, C разряда 4 будут смещены в прямом направлении, через них будет течь ток и они будут светиться. Сегменты B и С остальных разрядов светиться не будут, так как и на их анодах, и катодах установлен высокий уровень, т.е. напряжение смещения отсутствует. Все сегменты разряда 4, кроме b и c не будут гореть, так как на анодах и катодах соответствующих светодиодов установлен низкий уровень, т.е. напряжение смещение и ток через них отсутствуют. Все остальные светодиоды - светодиоды сегментов a, d, e, f, g, dp разрядов 1..3 не будут светиться так как они вообще смещены в обратном направлении (на катоде установлен высокий уровень, на аноде низкий).

Это очень важная особенность схемы с динамической индикацией - в определённые моменты времени на светодиоды индикаторов неизбежно подаётся обратное напряжение. Максимально допустимое обратное напряжение для светодиодов очень невелико, типичное значение - единицы вольт. Для приводимых здесь в качестве примера индикаторов FYQ-3641Ax/Bx, в соответствии с документацией, допускается обратное напряжение 5 В. Это означает, что напряжение на выходах схемы управления этим индикатором не должно превышать 5 В. В противном случае потребуются меры по преобразованию уровней сигналов. При подключении индикатора к микроконтроллерам с напряжением питания до 5 В проблем не возникает.

Семисегментный четырёхразрядный индикатор FYQ-3641B

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

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

  1. Гасятся все разряды индикатора - для предотвращения появления артефактов на выводимом изображении при смене состояния шины управления сегментами; если используется схема с общими катодами, для этого на общие катоды всех разрядов подаётся высокий уровень (лог. 1); в схеме с общими анодами, на аноды подаётся лог. 0.
  2. На шину управления сегментами выдаются сигналы для отображения символа в очередном разряде.
  3. Зажигается очередной разряд.

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

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

Если разряды переключаются с частотой f, то время отображения одного разряда составит максимум 1/f. Максимум - потому что время горения разряда может быть и меньше периода переключения. Мы можем изменять время горения от 0 до 1/f, и тем самым регулировать яркость разряда за счёт эффекта от широтно-импульсной модуляции.

При количестве разрядов N, полное время регенерации изображения на индикаторе в целом составит N*1/f, соответственно частота регенерации F=1/T=f/N. Для того, чтобы не было заметно мерцания изображения, частота регенерации F должна быть не менее 50 Гц, а лучше не менее 100 Гц.

Каждый разряд горит в течении не более чем 1/N от периода регенерации. При быстром переключении разрядов глаз не будет замечать мерцания, но воспринимать он будет усреднённую яркость. Усреднённая за период регенерации, она составит 1/N от величины в случае статической индикации при тех же токах через светодиоды. Поэтому силу тока во время импульсов при динамической индикации потребуется увеличивать по сравнению с силой тока при статической. Естественно, предназначенные для динамической индикации индикаторы рассчитаны на это - они имеют достаточно большой максимально допустимый импульсный ток, в несколько раз превышающий максимальный средний ток. Сложнее обстоит дело с управляющей индикатором схемой. Не каждый микроконтроллер сможет обеспечить достаточный ток для управления сегментами, и тем более не каждый сможет непосредственно управлять включением разрядов - ток через общий вывод разряда может превышать ток сегмента в 8 раз, если горят все элементы разряда. Но это не большая проблема - подключить индикатор к микроконтроллеру можно через микросхему-драйвер с мощными выходами или можно использовать ключи на транзисторах.

Вместо обычных повторителей или инверторов для подключения выводов управления разрядами индикатора может использоваться дешифратор n x N или демультиплексор. Помимо увеличения нагрузочной способности, это даёт возможность ещё уменьшить количество занятых управлением индикатором выводов микроконтроллера. На входы дешифратора подаётся двоичный код, и только на одном выходе, определяемом этим двоичным кодом, будет лог. 1, а на всех остальных будет лог. 0 (или, если выходы инверсные, то наоборот). Дешифратор с трёхбитовым входом имеет до 2**3=8 выходов и может использоваться до 8-разрядных индикаторов включительно, а с 4-битовым входом может переключать до 16 разрядов. Демультиплексор осуществляет коммутирование входного сигнала E на один из выходов, задаваемых адресными входами и полностью эквивалентен дешифратору при E=1, а при E=0 на всех выходах будет лог. 0 (или 1, если выходы инверсные).

В некоторых случаях, особенно если количество разрядов у индикатора небольшое, при подключении удаётся обойтись без дополнительных микросхем. Современные светодиоды имеют высокий КПД и обеспечивают достаточно высокую яркость при малых токах, в то время как при токах, близких к максимальным яркость становится чрезмерно, некомфортно высокой. Имеет смысл экспериментально подобрать режим работы индикатора.

Например, FYQ-3641A8 (четырёхразрядный, красный цвет свечения) обеспечивает хорошую видимость символов при токе в импульсе всего 2 мА на сегмент. Это позволяет использовать индикатор совместно с микроконтроллерами STM32F100xx непосредственно, без драйверов (максимальный ток вывода микроконтроллера составляет 25 мА, этого более чем достаточно даже для управления включением разрядов, так как максимальный ток общего вывода для разряда в данном случае не превышает 8*2 мА=16 мА).

Индикаторы с большим количеством разрядов (существенно большим, чем 8) используются реже, но следует иметь в виду существование второго варианта динамической индикации, который выгодно использовать для таких многоразрядных устройств. Можно осуществлять "развёртку" не по разрядам индикатора, а по сегментам. Это означает, что сначала зажигаются сегменты 'A' во всех разрядах, где они должны гореть. В следующий интервал времени зажигаются сегменты 'B' в нужных разрядах, и т.д. по всем 8 элементам. При таком способе соотношение между периодом отображения одного элемента и периодом полной регенерации индикатора всегда 1/8, независимо от "длины" индикатора. В этом случае ток одного разряда будет либо нулевым, либо равным току одного элемента I0. Ток в линиях управления сегментами может достигать N*I0, где N - количество разрядов, I0 - ток одного сегмента; максимальной величины ток достигает, когда один и тот же сегмент горит во всех разрядах.

Пример программы

В качестве примера здесь будет приведена программа, выводящая информацию на 4-разрядный семисегментный индикатор FYQ-3641A (имеет общий катод). Для управления индикатором используется микроконтроллер STM32F100RBT6B в составе оценочной платы STM32VLDISCOVERY.

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

Для предельного упрощения программы было бы удобно подключить индикатор к микроконтроллеру так, чтобы и линии управления сегментами, и линии управления разрядами индикатора образовывали непрерывные диапазоны битов в пределах своего порта ввода-вывода. Например, можно было бы подключить линии управления сегментами A, B, ..., G, DP к выводам PC0, PC1, ..., PC6, PC7, а линии DIG1, DIG2, DIG3, DIG4 для управления разрядами, допустим, к PA0, PA1, PA2, PA3. Тогда код управления индикатором получается действительно очень простым:

// Отключить все разряды индикатора - подать на
// общие катоды лог. 1 (PA0, PA1, PA2, PA3).
void digs_off()
{
    GPIOA->BSRR=0xF;
}

// Включить разряд d, 0<=d<=3 - подать на общий
// катод разряда лог. 0.
void dig_on(uint32_t d)
{
    GPIOA->BRR=1<<(d&0x3);
}

// Выдать на шину управления сегментами байт b
// (PC0 - a, PC1 - b, ..., PC7 - dp)
// Нулевые биты отключают соответствующие
// сегменты, единичные - включают.
void set_seg_bus(char b)
{
    GPIOC->BRR=0xFF;
    GPIOC->BSRR=b;
}

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

Кроме того, немаловажен такой вопрос, как разводка печатной платы. Если выбирают между простотой разводки и простотой программы, то практически всегда делается выбор в пользу простой разводки. А простая разводка обычно требует подключения к выходам портов "вразнобой". Здесь можно посмотреть иллюстрирующий это пример.

Здесь индикатор будет подключён в соответствии с приведённой ниже схемой. Резисторы в цепях управления разрядами R9..R12 используются только для защиты выводов микроконтроллера от перегрузки в случае ошибочного подключения, короткого замыкания и т.д. в макетируем устройстве. В реальном устройстве их быть не должно (подключение должно быть непосредственным), так как они вызывают изменение яркости свечения при изменении количества включённых сегментов в разряде. При этом номиналы резисторов R1..R8 следует увеличить до 820 Ом.

Схема подключения индикатора к микроконтроллеру

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

Скачать проект
// Определяйте символ препроцессора
// VECT_TAB_SRAM для отладки в RAM!

#include <stm32f10x.h>

// Массивы DIGS_CONTROL, SEGS_CONTROL описывают
// конфигурацию подключения индикатора к микроконтроллеру.
// Элементы массивов - байты; старшая половина байта
// задаёт порт ввода-вывода микроконтроллера (0 - GPIOA, и т.д.);
// младшая половина задаёт бит этого порта, к которому
// подключён вывод микроконтроллера.

// Схема подключения выводов для управления
// включением разрядов индикаторов. В данном случае:
// DIG1 - PC6, DIG2 - PC7, DIG3 - PC8, DIG4 - PC9.
const char DIGS_CONTROL[]={0x26, 0x27, 0x28, 0x29};

const uint32_t DIGS_COUNT=
        sizeof DIGS_CONTROL/sizeof DIGS_CONTROL[0];

// Схема подключения выводов для управления
// сегментами индикатора. В данном случае:
// A - PA8, B - PA9, C - PA10, D - PA11,
// E - PA12, F - PC0, G - PC1, DP - PC2.
const char SEGS_CONTROL[]={0x08, 0x09, 0x0A, 0x0B,
        0x0C, 0x20, 0x21, 0x22};

// Таблица портов ввода-вывода общего назначения.
GPIO_TypeDef* const PORTS[]={GPIOA, GPIOB,
            GPIOC, GPIOD};

// Установка бита порта ввода-вывода, заданного
// байтом (как в массивах XXXX_CONTROL) в состояние state.
void line_set(char port_pin, bool state)
{
    if(state)
        PORTS[(port_pin>>4)&0xF]->BSRR=1<<(port_pin&0xF);
    else
        PORTS[(port_pin>>4)&0xF]->BRR=1<<(port_pin&0xF);

}
// Активный уровень (т.е. включающий) для вывода
// управления разрядом; лог. 0 (false) - для схемы
// с общим катодом; лог. 1 (true) - для схемы с общим андом.
// Использование инвертирующего драйвера для этих выводов
// изменяет активный уровень на противоположный.
#define DIG_ACTIVE_LEVEL false

// Активный (включающий) уровень для сегментов;
// лог. 1 для индикатора с общим катодом;
// лог. 0 для индикатора с общим андом.
// Если шина управления сегментами подключатся
// через инвертирующий драйвер, активный уровень
// меняется на противоположный.
#define SEG_ACTIVE_LEVEL true

// Отключить все разряды индикатора.
void digs_off()
{
    for(uint32_t i=0; i<DIGS_COUNT; i++)
        line_set(DIGS_CONTROL[i], !DIG_ACTIVE_LEVEL);
}

// Включить разряд d, индексация начинается с 0;
// d=0 соответствует крайнему левому разряду.
void dig_on(uint32_t d)
{
    line_set(DIGS_CONTROL[d], DIG_ACTIVE_LEVEL);
}

// Вывести на шину управления сегментами байт b;
// младший бит управляет сегментом A, следующий - B
// и т.д., старший бит управляет десятичной точкой DP.
// Единичный бит включает сегмент, нулевой выключает.
void set_seg_bus(char b)
{
    for(uint32_t i=0; i<8; i++, b>>=1)
        line_set(SEGS_CONTROL[i], (b&1)^!SEG_ACTIVE_LEVEL);
}

// Конфигурирование выводов микроконтроллера,
// управляющих индикатором.
void pin_config(char pp, bool level)
{
    uint32_t port=(pp>>4)&0xF;
    uint32_t pin=pp&0xF;
    // Включить тактирование используемого порта
    // (предполагается, что управляющие портами биты
    // расположены последовательно).
    RCC->APB2ENR|=RCC_APB2ENR_IOPAEN<<port;
    if(level)
        PORTS[port]->BSRR=1<<pin;
    else
        PORTS[port]->BRR=1<<pin;
    uint32_t mask, fnmode;
    volatile uint32_t *mode_reg=(pin<8)?&PORTS[port]->CRL:
            &PORTS[port]->CRH;
    if(pin>=8)
        pin-=8;
    mask=~(0xF<<(4*pin));
    fnmode=2<<(4*pin);
    *mode_reg&=mask;
    *mode_reg|=fnmode;
}

// Конфигурирование всех выводов, используемых
// для управления индикатором.
void display_config()
{
    for(uint32_t i=0; i<DIGS_COUNT; i++)
        pin_config(DIGS_CONTROL[i], !DIG_ACTIVE_LEVEL);
    for(uint32_t i=0; i<8; i++)
            pin_config(SEGS_CONTROL[i], !SEG_ACTIVE_LEVEL);
}

// Таблица знакогенератора, ставит в соответствие
// коду символа байт для управления сегментами.
// Используются не ASCII коды символов!
// В данной таблице определены символы с кодами
// 0..9, соответствующие десятичным цифрам '0'..'9';
// 10..15 для шестнадцатеричных 'A', 'b', 'C', 'd', 'E', 'F';
// 16 - пробел (все сегменты погашены);
// 17 - '-'.
// При необходиммости, таблица может быть расширена
// или сокращена.
const char CG_TABLE[]=
    {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, // '0'..'5'
     0x7D, 0x07, 0x7F, 0x6F,             // '6'..'9'
     0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, // 'A'..'F'
     0x00, // SPACE
     0x40  // MINUS
    };
#define SPACE 16
#define MINUS 17

// Строка символов для вывода на индикатор.
// Здесь используются не ASCII коды, а индексы
// символов в таблице знакогенератора
// (код 0 - символ '0' и т.д.).
// Для вывода данных на индикатор следует просто
// поместить коды выводимых символов в эту строку.
// Установка старшего бита отображает точку вместе с символом.
char OUT_STR[]={SPACE, SPACE, SPACE, SPACE};

// Функция для обновления изображения на индикаторе;
// должна вызываться периодически с частотой f=N*F,
// где N - количество разрядов индикатора,
// F - желаемая частота обновления индикатора.
void display_refresh()
{
    const uint32_t str_len=
            sizeof OUT_STR/sizeof OUT_STR[0];
    static uint32_t d=0;
    char ch=OUT_STR[d];
    char dot=ch&0x80;
    ch&=0x7F;
    ch=CG_TABLE[ch]|dot;
    digs_off();
    set_seg_bus(ch);
    dig_on(d);
    if(++d>=str_len)
        d=0;
}

// Счётчик миллисекунд.
volatile uint32_t ms_counter=0;

// Обработчик исключений от системного таймера.
extern "C" void SysTick_Handler()
{
    // Переключение разрядов с частотой f=500 Гц.
    if(ms_counter&1)
        display_refresh();
    ms_counter++;
}

// Функция для формирования задержки
// длительностью ms миллисекунд.
void delay(uint32_t ms)
{
    uint32_t t0=ms_counter;
    while(ms_counter-t0<ms);
}

// Вывод на индикатор целого со знаком.
// Если индикатор имеет недостаточную разрядность,
// выводится сообщение об ошибке: 'E' или '-E'
// и функция возвращает false.
bool display(int n)
{
    char sign=SPACE;
    if(n<0)
    {
        n=-n;
        sign=MINUS;
    }
    char d=0;
    for(uint32_t i=0; i<DIGS_COUNT; i++)
    {
        if(n!=0)
        {
            d=n%10;
            n/=10;
        }
        if(i==0)
            d|=0x80; // Точка в конце числа.
        else if(n==0&&d==0)
        {
            // Вывод знака, если есть;
            // пропуск ведущих нулей.
            d=sign;
            sign=SPACE;
        }
        OUT_STR[DIGS_COUNT-1-i]=d;
        d=0;
    }
    if(n!=0||sign!=SPACE)
    {
        for(uint32_t i=0; i<DIGS_COUNT-1; i++)
            OUT_STR[i]=SPACE;
        OUT_STR[DIGS_COUNT-1]=0xE;
        if(sign==MINUS&&DIGS_COUNT>=2)
            OUT_STR[DIGS_COUNT-2]=MINUS;
        return false;
    }
    return true;
}

int main()
{
    // Конфигурирование выводов микроконтроллера,
    // управляющих индикатором.
    display_config();
    // Конфигурирование системного таймера.
    SysTick_Config(SystemCoreClock/1000);

    // Включаем все сегменты всех разрядов и все точки.
    OUT_STR[0]=0x88;
    OUT_STR[1]=0x88;
    OUT_STR[2]=0x88;
    OUT_STR[3]=0x88;
    delay(1500);

    // Гасим индикатор.
    OUT_STR[0]=SPACE;
    OUT_STR[1]=SPACE;
    OUT_STR[2]=SPACE;
    OUT_STR[3]=SPACE;
    delay(500);

    // Демонстрируем все символы
    // из нашего знакогенератора (с точкой и без).
    const uint32_t N=sizeof CG_TABLE/sizeof CG_TABLE[0];
    for(uint32_t i=0; i<2*N; i++)
    {
        char c=i/2;
        if(i&1) c|=0x80;
        OUT_STR[DIGS_COUNT-1]=c;
        delay(1000);
    }

    // Переполнение 4-разрядного индикатора.
    display(-1000);
    delay(2000);
    display(10000);
    delay(2000);

    // Запускаем секундомер от начального значения -123.
    uint32_t t=-123;
    while(true)
    {
        display(t++);
        delay(1000);
    }
}
author: hamper; date: 2016-01-12; modified: 2016-03-16
  @Mail.ru