Как стать автором
Обновить
1853.73

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

Уровень сложности Простой
Время на прочтение 18 мин
Количество просмотров 22K
image

В этой статье я хочу продолжить свой цикл о программировании Arduino для начинающих электронщиков. Мы познакомимся с подключением светодиодного семисегментного индикатора к микроконтроллеру через сдвиговый регистр, разберемся с особенностями его программной обработки. Также я продемонстрирую, как с помощью редактора электронных таблиц Excel можно генерировать семисегментные коды. И, как всегда, много внимание будет уделено деталям, которые часто воспринимаются понятными «по умолчанию», но на самом деле для новичка таковыми не являются. В конце, следуя «Arduino way», напишем простой класс для управления семисегментной индикацией.

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

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

image

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

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

image

И действительно, имея под рукой 3D-принтер, вы можете получить светодиодный сегментный индикатор практически любой конфигурации. Например, я использую онлайн кастомайзер на thingiverse. Он позволяет достаточно гибко и без лишних заморочек настраивать модель, которую потом останется всего лишь распечатать и немного доработать напильником.

image

Подключение и управление


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

Сегменты принято обозначать буквами от A до G, как показано на рисунке. Индикаторы могут оснащаться восьмым сегментом – десятичная точка (decimal point), обозначается как DP или H.

image

Как правило, светодиоды в таких индикаторах объединяют катодами или анодами. Получаются индикаторы с общим катодом (ОК) или общим анодом (ОА).

image

Рассмотрим индикатор с общим анодом. Для управления таким индикатором на его общем аноде необходимо обеспечить положительный потенциал. Чтобы получить требуемый символ, на определенную комбинацию катодов необходимо подать отрицательный потенциал. Остальные выводы можно оставить неподключенными.

image

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

Пускай индикатор будет подключен через сдвиговый регистр 74HC595. Как управлять сдвиговым регистром можно почитать в одной из моих прошлых статей. Так как индикатор состоит из светодиодов, необходимо ограничивать их рабочий ток. Самый простым способом является использование токоограничивающих резисторов. В этом кроется основное преимущество светодиодного индикатора перед другими, мы получаем очень просто драйвер, фактически это любая TTL-совместимая микросхема и горстка резисторов. Но тут же кроется и недостаток — большой потребляемый ток. Зато внешняя подсветка для индикатора тоже не нужна.

image

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

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

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

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

image

Давайте попробуем составить семисегментный код для отображения цифры 0 на индикаторе. Для этого необходимо включить светодиоды сегментов A, B, C, D, E, F. Сегменты G и H должны быть выключены. Соответственно, на выводах сдвигового регистра Q0 – Q5 необходимо установить уровни логического нуля, а для выводов Q6 и Q7 – необходимо установить единицы.

image

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

image

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

image

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

image

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

image

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

image

Excel для формирования семисегментных кодов


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

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

image

Для этого можно объединять соседние ячейки, менять их размеры, установить видимые границы и залить ячейки серым цветом. Все это можно сделать через команду меню: Главная / Формат / Формат ячеек.

image

image

Далее немного оживим изображение индикатора. Сделаю так, чтобы сегменты меняли цвет на красный при заполнении. Для этого с зажатой кнопкой "Ctrl" выделю все ячейки, которые представляют собой сегменты индикатора. На вкладке "Главная" необходимо выбрать меню: Условное форматирование / Правила выделения ячеек / Равно

image

В диалоговом окне "Равно" в поле для ввода введу единицу. В принципе, это может быть любой другой удобный для вас символ.

image

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

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

image

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

image

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

image

Далее, умножу вес бита на его двоичное значение, чтобы получить десятичное значение бита.

image

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

image

Преобразуем десятичный код в шестнадцатеричный.

image

В соседней ячейке получу значение в формате шестнадцатеричного числа для программы на Си.

image

Копипастим полученную табличку необходимое количество раз. Далее можно приступать к формированию семисегментных кодов.

image

Мой итоговый вариант выглядит вот так. Я перенес вычисления кодов на отдельную страницу. И в результате получаю готовую строку — инициализатор для программы на Си.

image

Управление сдвиговым регистром 74HC595


Если вам известен принцип работы сдвигового регистра 74HC595, вы можете смело пропустить этот раздел и перейти к следующему, в котором будет рассмотрено непосредственно программирование.

image

Микросхема 74HC595 представляет собой последовательный синхронный сдвиговый регистр, который преобразует последовательный код, поступающий на вход SER, в параллельный код на выходах Q0 – Q7.

Синхронизация записи последовательного кода осуществляется подачей стробирующих импульсов на вход SCK. А вход RCK управляет обновлением выходов Q0-Q7. Это позволяет изменять состояние выходных сигналов Q0 – Q7 одновременно.

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

image

Для экспериментов традиционно буду использовать Proteus. Схема эксперимента представлена на рисунке.

image

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

//--------------------------------------------------
//Выходы для управления регистром
#define DATA    11
#define CLK     13
#define UPDATE  10

В функции "loop()" настрою эти выводы для работы на выход.

//--------------------------------------------------
//настройка периферии микроконтроллера
void setup() {
  pinMode(DATA,OUTPUT);
  pinMode(CLK,OUTPUT);
  pinMode(UPDATE,OUTPUT);
}

Для управления сдвиговым регистром перед функцией "loop()" объявим функцию "shift()". Во входных параметрах функции будем передавать данные, для записи в регистр.

//--------------------------------------------------
//Запись данных в регистр
void shift(uint8_t data){

}

Для последовательной передачи данных в теле функции организуем цикл "for". Счетчик "bitNum" в цикле будет определять номер бита переменной "data", отправляемый на вход регистра. Передачу данных начнем со старшего бита, чтобы он оказался на выходе Q7.

//--------------------------------------------------
//Запись данных в регистр
void shift(uint8_t data){
  //последовательный сдвиг 8 бит 
  for(uint8_t bitNum = 7; bitNum < 8; --bitNum){
    
  }
}

Счетчик "bitNum" объявлен как целое без знака. Счет ведется в обратную сторону. При очередном вычитании из счетчика единицы, когда его значение было равным нулю, произойдет его переполнение. Новое значение счетчика станет равным 255. Это больше 8. Условие цикла не выполнятся, и цикл будет завершен.

Для записи бита на вход регистра в тело цикла поместим оператор проверки условия "if". Он будет проверять состояние бита по номеру в переменной "bitNum", и устанавливать соответствующее значение на входе регистра.

//--------------------------------------------------
//Запись данных в регистр
void shift(uint8_t data){
  //последовательный сдвиг 8 бит 
  for(uint8_t bitNum = 7; bitNum < 8; --bitNum){
    //проверка бита
    if(data & (1<<bitNum))digitalWrite(DATA,HIGH);
    else                  digitalWrite(DATA,LOW);
  }
}

Немного сократим эту запись. Дело в том, что функция "digitalWrite()" уже содержит внутри проверку истинности своего второго входного параметра. Это позволяет обойтись без внешней проверки, а условие из нее сразу передать как входной параметр. Получается, на выводе микроконтроллера будет установлен низкий логический уровень, если в результате выполнения команды "data & (1<<bitNum)" получается ноль. При любом ненулевом результате, функция "digitalWrite()" запишет в порт логическую единицу.

//--------------------------------------------------
//Запись данных в регистр
void shift(uint8_t data){
  //последовательный сдвиг 8 бит 
  for(uint8_t bitNum = 7; bitNum < 8; --bitNum){
    //проверка бита
    digitalWrite(DATA,data & (1<<bitNum));
  }
}

После установки значения на входе данных регистра, в теле цикла необходимо сформировать один стробирующий импульс записи. А после 8-ми бит данных подадим одиночный импульс для переключения выходной защелки.

//--------------------------------------------------
//Запись данных в регистр
void shift(uint8_t data){
  //последовательный сдвиг 8 бит 
  for(uint8_t bitNum = 7; bitNum < 8; --bitNum){
    //проверка бита
    digitalWrite(DATA,data & (1<<bitNum));
    //строб записи
    digitalWrite(CLK,HIGH);
    digitalWrite(CLK,LOW);
  }
  //обновление выходов регистра
  digitalWrite(UPDATE,HIGH);
  digitalWrite(UPDATE,LOW);
}

На этом наша функция завершена. Остается проверить ее работоспособность. Вызовем функцию "shift()" из функции "loop()". В качестве входного параметра передадим семисегментный код семерки… на удачу.

//--------------------------------------------------
//супер цикл
void loop() {
  shift(0xF8);
}

Под спойлером вы можете найти полный скетч.
//--------------------------------------------------
//Выходы для управления регистром
#define DATA    11
#define CLK     13
#define UPDATE  10

//--------------------------------------------------
//Запись данных в регистр
void shift(uint8_t data){
  //последовательный сдвиг 8 бит 
  for(uint8_t bitNum = 7; bitNum < 8; --bitNum){
    //проверка бита
    digitalWrite(DATA,data & (1<<bitNum));
    //строб записи
    digitalWrite(CLK,HIGH);
    digitalWrite(CLK,LOW);
  }
  //обновление выходов регистра
  digitalWrite(UPDATE,HIGH);
  digitalWrite(UPDATE,LOW);
}

//--------------------------------------------------
//настройка периферии микроконтроллера
void setup() {
  pinMode(DATA,OUTPUT);
  pinMode(CLK,OUTPUT);
  pinMode(UPDATE,OUTPUT);
}

//--------------------------------------------------
//супер цикл
void loop() {
  shift(0xF8);
}

Вывод семисегментного кода


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

image

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

Для работы с SPI интерфейсом в программе необходимо подключить встроенную библиотеку <SPI.h>.

//--------------------------------------------------
//для работы с SPI интерфейсом
#include <SPI.h>

Семисегментный код, который был сгенерирован в Excel, разместим в массиве так, чтобы его нулевой элемент содержал код нуля, а старший элемент — код девятки.

//--------------------------------------------------
//массив семисегментных кодов
uint8_t segmCode[]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90};

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

//--------------------------------------------------
//настройка периферии микроконтроллера
void setup() {
  SPI.begin();
  digitalWrite(SS,LOW);
}

Фоновая программа будет содержать один цикл "for", который с интервалом в одну секунду поочередно запишет в регистр очередной семисегментный код.

//--------------------------------------------------
//супер цикл
void loop() {
  //считаем от нуля до десяти
  for(uint8_t i = 0; i < 10; ++i){  
    //запись в регистр кода по номеру i
    SPI.transfer(segmCode[i]);
    //обновляем выходы сдвигового регистра
    digitalWrite(SS,HIGH);
    digitalWrite(SS,LOW);
    //пауза для отображения цифры
    delay(1000);
  }
}

Полный скетч под спойлером.
//--------------------------------------------------
//для работы с SPI интерфейсом
#include <SPI.h>

//--------------------------------------------------
//массив семисегментных кодов
uint8_t segmCode[]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90};																																															

//--------------------------------------------------
//настройка периферии микроконтроллера
void setup() {
  SPI.begin();
  digitalWrite(SS,LOW);
}

//--------------------------------------------------
//супер цикл
void loop() {
  //считаем от нуля до десяти
  for(uint8_t i = 0; i < 10; ++i){  
    //запись в регистр кода по номеру i
    SPI.transfer(segmCode[i]);
    //обновляем выходы сдвигового регистра
    digitalWrite(SS,HIGH);
    digitalWrite(SS,LOW);
    //пауза для отображения цифры
    delay(1000);
  }
}


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

Вывод десятичного числа


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

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

image

Пускай верхний индикатор будет отображать десятки секунд, а нижний — единицы секунд.

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

Сперва, изменим диапазон счета для цикла "for", теперь он будет считать до 60 секунд.

  //считаем от нуля до 60 секунд
  for(uint8_t i = 0; i < 60; ++i){ 

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

//запись в регистр единицы секунд
    SPI.transfer(segmCode[i % 10]);
    //запись в регистр десятка секунд
    SPI.transfer(segmCode[i /10 % 10]);

Операция "i % 10" возвращает остаток от деления счетчика i на основание десятичной системы счисления, таким образом производится выделение младшего десятичного разряда. Для выделения десятков мы сперва выполняем сдвиг счетчика влево путем деления на основание системы счисления "i /10", и затем выделяем десятки секунд из младшего разряда получением остатка от деления.

Для примера возьмём i=34. Тогда для "i%10" получится, что 10 помещается в 34 целиком 3 раза, в остатке остается 4. Затем выполним операцию "34/10". При целочисленном делении получаем результат, аналогичный сдвигу вправо на один десятичный разряд. То есть тройка «переезжает» в разряд единиц, дробная часть откидывается. Операцию "34/10%10" целиком можно не выполнять, если вы гарантируете, что значение переменной никогда не будет иметь значимый разряд сотен.

Обратите внимание на то, как происходит преобразование десятичного числа в эквивалентный семисегментный код. Команда типа "segmCode[i % 10]" фактически производит табличную подстановку. Массив "segmCode" заполнен таким образом, что в его нулевом элементе размещен семисегментный код нуля, в первом — единицы, во втором — двойки, и так далее. Получается, что на сам индикатор мы выводим не десятичное число, а элемент массива перекодировки по индексу, который определяется этим десятичным числом.
Полный скетч под спойлером.
//--------------------------------------------------
//для работы с SPI интерфейсом
#include <SPI.h>

//--------------------------------------------------
//массив семисегментных кодов
uint8_t segmCode[]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90};																																															

//--------------------------------------------------
//настройка периферии микроконтроллера
void setup() {
  SPI.begin();
  digitalWrite(SS,LOW);
}

//--------------------------------------------------
//супер цикл
void loop() {
  //считаем от нуля до 60 секунд
  for(uint8_t i = 0; i < 60; ++i){  
    //запись в регистр единицы секунд
    SPI.transfer(segmCode[i % 10]);
    //запись в регистр десятка секунд
    SPI.transfer(segmCode[i /10 % 10]);
    //обновляем выходы сдвигового регистра
    digitalWrite(SS,HIGH);
    digitalWrite(SS,LOW);
    //пауза для отображения цифры
    delay(1000);
  }

  while(1);
}

Немного унификации


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

image

Увидев такое количество микросхем, вы можете спросить, почему бы не перейти на динамическую индикацию. Конечно, динамический способ имеет ряд неоспоримых плюсов. Но от динамической развертки может рябить в глазах. Если устройство индикации подвижно, в том числе испытывает вибрации, динамическая развертка может «рассыпаться». Тот же эффект возможен и в случае, если подвижно не устройство индикации, а сам наблюдатель. Поэтому в важных приложениях я стараюсь динамическую развертку избегать. А если вы переживаете за площадь печатной платы, используйте корпуса типа TSSOP или QFN. Они прекрасно помещаются под семисегментным индикатором и не занимают дополнительную площадь.

image

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

Для вывода десятичного числа на индикаторы напишем функцию "decimalTo7segm()".

//--------------------------------------------------
//вывод десятичного числа произвольной разрядности
void decimalTo7segm(uint32_t dec, uint8_t numberOfDigits){
  for(; numberOfDigits; --numberOfDigits){
    //запись очередного семисегментного кода
    SPI.transfer(segmCode[dec%10]);
    //десятичный сдвиг вправо
    dec /= 10;
  }
  //обновляем выходы сдвигового регистра
  digitalWrite(SS,HIGH);
  digitalWrite(SS,LOW);
}

Через входные параметры функция "decimalTo7segm()" получает непосредственно само десятичное число "uint32_t dec". Входная переменная не обязательно должна быть 32-х разрядной, это будет зависеть от ваших потребностей. Если вам не нужно выводить слишком большие числа, ограничьте ее 16-ю или 8-ю разрядами, применив соответствующие типы данных. Количество доступных для отображения цифр индикаторов в функцию будет передавать входная переменная "numberOfDigits". Ну а логика работы функции должна быть понятна из комментариев к коду.

Остается вызвать функцию "decimalTo7segm()" из "loop()". Для удобства проверки работоспособности, передам значение 1234 и укажу количество индикаторов как 4.

//--------------------------------------------------
//супер цикл
void loop() {
  //вывод десятичного числа
  decimalTo7segm(1234, 4);
  while(1);
}

Полный скетч под спойлером.
//--------------------------------------------------
//для работы с SPI интерфейсом
#include <SPI.h>

//--------------------------------------------------
//массив семисегментных кодов
uint8_t segmCode[]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90};																																															

//--------------------------------------------------
//вывод десятичного числа произвольной разрядности
void decimalTo7segm(uint32_t dec, uint8_t numberOfDigits){
  for(; numberOfDigits; --numberOfDigits){
    //запись очередного семисегментного кода
    SPI.transfer(segmCode[dec%10]);
    //десятичный сдвиг вправо
    dec /= 10;
  }
  //обновляем выходы сдвигового регистра
  digitalWrite(SS,HIGH);
  digitalWrite(SS,LOW);
}

//--------------------------------------------------
//настройка периферии микроконтроллера
void setup() {
  SPI.begin();
  digitalWrite(SS,LOW);
}

//--------------------------------------------------
//супер цикл
void loop() {
  //вывод десятичного числа
  decimalTo7segm(1234, 4);
  while(1);
}

Улучшения в стиле «Arduino way»


Я лично не являюсь сторонником использования классов для программирования микроконтроллеров, и, наверное, когда-то посвящу этому отдельную статью. Но кого интересует оптимизация кода, если мы используем Arduino? Все-таки Arduino — это в первую очередь про удобство. Поэтому предлагаю оформить обработчик индикатора в виде отдельного класса.

Создадим класс "SevenSegmIndicator". Этот класс должен будет описывать свойства и алгоритм работы семисегментного индикатора.

//--------------------------------------------------
//класс для работы с индикаторами
class SevenSegmIndicator{
public:

private:
  //количество индикаторов (минимальное количество 1)
  uint8_t numberOfDigits = 1;
  //семисегментный код
  uint8_t segmCode[10] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90}; 
};

Наш класс будет иметь два свойства: "numberOfDigits" и массив "segmCode[]". Переменная "numberOfDigits" будет использоваться для хранения в программе количества индикаторов, размещенных на схеме. По умолчанию этому полю будет присваиваться минимальное количество индикаторов, которое может обрабатывать наша программа, т.е. один. Массив "segmCode[]" хранит семисегментные коды. Массив заполним сразу.

С помощью спецификатора доступа "private" эти переменные будут скрыты от остального кода, доступ к ним возможен только для методов класса.

После спецификатора доступа "public" разместим методы нашего класса, которые будут реализовывать основную логику его работы. Эти методы будут доступны в остальной программе.

  //основной метод для вывода данных в индикаторы
  void update(uint32_t dec){
    for(uint8_t num = numberOfDigits; num; --num){
      //запись очередного семисегментного кода
      SPI.transfer(segmCode[dec%10]);
      //обновляем выходы сдвигового регистра
      digitalWrite(SS,HIGH);
      digitalWrite(SS,LOW);
      //десятичный сдвиг вправо
      dec /= 10;
    }
  }

Метод "update()" фактически будет повторят написанную нами ранее функцию "decimalTo7segm()".

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

  //конструктор
  SevenSegmIndicator(uint8_t n){
    //запуск аппаратного SPI
    SPI.begin();
    digitalWrite(SS,LOW);
    //запоминаем количество индикаторов
    numberOfDigits = n;
  }

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

//объект для работы с 4-мя индикаторами
SevenSegmIndicator myIndicator = 4;

Теперь в функции "setup()" больше не останется кода, так как он теперь будет выполняться конструктором класса.

Нам осталось воспользоваться методом класса "updae()" для вывода на индикатор десятичного числа. Разместим его вызов в функции "loop()".

  //вывод десятичного числа
  myIndicator.update(1234);

Полный скетч под спойлером.
//--------------------------------------------------
//для работы с SPI интерфейсом
#include <SPI.h>

//--------------------------------------------------
//класс для работы с индикаторами
class SevenSegmIndicator{
public:
  //конструктор
  SevenSegmIndicator(uint8_t n){
    //запуск аппаратного SPI
    SPI.begin();
    digitalWrite(SS,LOW);
    //запоминаем количество индикаторов
    numberOfDigits = n;
  }

  //основной метод для вывода данных в индикаторы
  void update(uint32_t dec){
    for(uint8_t num = numberOfDigits; num; --num){
      //запись очередного семисегментного кода
      SPI.transfer(segmCode[dec%10]);
      //обновляем выходы сдвигового регистра
      digitalWrite(SS,HIGH);
      digitalWrite(SS,LOW);
      //десятичный сдвиг вправо
      dec /= 10;
    }
  }
private:
  //количество индикаторов (минимальное количество 1)
  uint8_t numberOfDigits = 1;
  //семисегментный код
  uint8_t segmCode[10] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90};  
};

//объект для работы с 4-мя индикаторами
SevenSegmIndicator myIndicator = 4;

//--------------------------------------------------
void setup() {
  
}

//--------------------------------------------------
//супер цикл
void loop() {
  //вывод десятичного числа
  myIndicator.update(1234);
  while(1);
}

Послесловие


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

На этом, пожалуй, можно заканчивать. Но а вы можете посмотреть другие мои статьи на тему программирования Arduino:
1. Тактовая кнопка, как подключить правильно к "+" или "-"
2. Экономим выводы для Arduino. Управление сдвиговым регистром 74HC595 по одному проводу
3. Блокирующая обработка тактовой кнопки для Arduino. Настолько полный гайд, что ты устанешь его читать
4. Неблокирующая обработка тактовой кнопки для Arduino. Как использовать прерывание таймера «в два клика» в стиле ардуино
5. С чем едят конечный автомат
6. На пол пути к конечному автомату для Arduino. Однопроходные функции и фиксация событий программы с помощью флагов

Теги:
Хабы:
+34
Комментарии 108
Комментарии Комментарии 108

Публикации

Информация

Сайт
timeweb.cloud
Дата регистрации
Дата основания
Численность
201–500 человек
Местоположение
Россия
Представитель
Timeweb Cloud