Семисегментный дисплей
Первым делом нам нужно получить какое-то средство отладки – чтобы можно было искать ошибки (а они неизбежно появятся) и получать какой-то отзыв от контроллера.
Поэтому займемся сперва дисплеем на семисегментных индикаторах.
Рассмотрим сначала, как работает отдельный семисегментный индикатор:
Рисунок 1. Схема семисегментного индикатора с точкой.
Вот так схематично выглядит семисегментный индикатор с точкой.
Устроен он очень просто: есть восемь выводов (по одному на каждый сегмент + точка) и один (или два, которые объединяются вместе перемычкой – на рисунке это 3 и 8) общий вывод, который подключается либо на ноль (семисегментный индикатор с общим катодом), либо к питанию (семисегментный индикатор с общим анодом).
Вот так выглядит внутренняя схема индикатора:
Рисунок 2. Внутреннее устройство семисегментного индикатора с точкой.
Вот как располагаются сегменты:
Рисунок 3. Расположение сегментов.
Иными словами – семисегментный индикатор – не что иное, как семь (восемь, если есть точка) светодиодов, у которых один из выводов подключен к общей точке. В зависимости от того, катоды объединены в общую точку, или аноды, выделяют индикаторы, соответственно, с общим катодом или общим анодом. Общая точка подключается либо к «-» (общий катод), либо к «+» (общий анод).
Чтобы загорелся какой-то из сегментов, нужно подать напряжение между общей точкой и соответствующим выводом. Таким образом, и мы подаём напряжение на выводы A, B и C, то на индикаторе загорится "7".
Отдельные семисегментные индикаторы, как видим, подключаются достаточно просто.
Теперь рассмотрим, как подключить несколько индикаторов.
Здесь есть варианты:
Во-первых, можно найти семисегментные индикаторы на 2-3 и больше разрядов, которые будут выглядеть примерно вот так:
Рисунок 4. Схема трёхразрядного семисегментного дисплея.
У таких дисплеев есть восемь выводов для определения индикации (назовем их шиной данных), и несколько (по количеству разрядов) выводов для выбора разряда (шина адреса). Выводы для выбора разряда являются общими катодами или анодами.
Например, у нас трехразрядный индикатор. Попробуем вывести на нём "123"
- Чтобы вывести "1" в старшем (крайнем левом) разряде, нам нужно сначала погасить индикатор (отключаем все общие точки), затем выставить на шине данных нужную комбинацию – в данном случае, выводы B и C подключаем к "+", если индикатор с общим катодом, или к "-", если индикатор с общим анодом - и только потом подключить соответствующую общую точку. После этого необходимо сделать маленькую паузу, чтобы светодиоды успели зажечься.
- Для того, чтобы вывести "2", необходимо снова погасить индикатор; выставить на выводах данных обозначение "2" (выводы A, B, D, E, G), а затем подключить нужную общую точку - вывод CA2. И снова пауза.
- Аналогично для "3".
- И так до бесконечности
Если мы будем достаточно быстро циклически выполнять 1) - 3), то будет сложно заметить мерцание (выключили все разряды – включили первый – маленькая пауза – выключили все разряды – включили второй – маленькая пауза – выключили все разряды – включили третий – маленькая пауза – выключили все разряды – включили первый - …) – будет казаться, что весь дисплей горит ровно.
Вариант второй – у дисплея есть отдельные выводы a-g и DP для каждого разряда – или мы сами составляем дисплей из отдельных индикаторов.
Наша задача – по сути, реализовать первый вариант из подручных средств: будем использовать общие для всех разрядов восемь выводов данных и выводы адреса для выбора разряда.
Теперь о подключении - у нас индикаторы с общим анодом (на шине данных светодиоды включаются "минусом", а на шине адресации разряд выбирается "плюсом"):
Рисунок 5. Схема подключения нескольких отдельных семисегментных индикаторов.
Упрощенная схема представлена на рисунке.
Так, мы просто объединяем выводы a-g, dp для всех индикаторов; «общими» выводами же управляем выбором конкретного индикатора – а дальше можем работать по алгоритму выше.
Работать будет, но дисплей будет светиться тускловато – выводы микроконтроллера, связанные с общим входом, обеспечивают довольно большой ток, когда подключены к «земле» (логический 0), и маленький при подтяжке к питанию (логическая 1) - поэтому добавим в схему транзисторы, которые будут работать в режиме насыщения (он же режим ключа):
Рисунок 6. Схема подключения нескольких отдельных семисегментных индикаторов с общим анодом с помощью транзисторов.
Так, теперь можно приступать к программе.
Рассмотрим, что должен уметь «кирпичик» работы с дисплеем, что он получает на вход и на выход.
Во-первых, «кирпичик» должен знать выводы, связанные с дисплеем: нам нужно 8 выводов для шины данных (будем использовать порт D) и 6 выводов для шины адреса (выводы B0 – B5) – требуется функция для инициализации дисплея. При этом нужно учитывать, что наш дисплей – индикаторы с общим анодом.
На вход должна поступать строка, которую мы сможем перевести в символы для семисегментного дисплея; соответственно, нужна функция записи строки в «память дисплея» (в роли памяти будет выступать массив на шесть байтовых элементов).
Ещё мы помним, что для того, чтобы наш дисплей постоянно показывал какую-либо строку, нам нужно часто-часто отображать каждый разряд дисплея – значит, нам потребуется функция, которая будет постоянно вызываться в прерывании.
Для начала попробуем просто выводить какие-то символы на дисплей. При этом у нас и шина данных, и шина адресации «включаются» нулем: шина данных потому, что индикаторы с общим анодом, а шина адресации – из-за транзисторов. Шина данных будет размещаться на выводах порта D, адресная шина – на выводах B0-B5; при этом к выводу B0 подключен самый старший разряд, к выводу B5 – самый младший.
Нужно понять, что «единица», «символ единицы» и «код единицы» - три совершенно разные вещи. Если мы проводим какие-то математические вычисления, то мы используем «единицу» - число или цифру; если мы работаем со строкой – то используем «символ единицы» - некий код из таблицы ACSII; а вот «код единицы» - это то значение, которое мы должны подать на шину данных, чтобы на семисегментном индикаторе загорелся символ единицы (хо-хо, мы снова всех запутаем!)
Вот код для самой простой программки, которая будет просто отображать «012345» на дисплее (используем среду IAR):
//выводим на экран "012345" #include "ioavr.h" #include "inavr.h" #include "stdint.h" //ddr и port для работы с уже выбранным индикатором #define DispDdrDatabus DDRD #define DispPortDatabus PORTD //ddr и port для выбора индикатора #define DispDdrChoose DDRB #define DispPortChoose PORTB #define DISP_CHOOSE_MASK 0x3F #define DISP_SEGMENT_COUNT 6 uint8_t DispCurrSegment, DispCurrSegmentMask; //переменные для хранения для номера текущего индикатора и его маски uint8_t DispMemory[DISP_SEGMENT_COUNT] = {0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92}; //непосредственно сама память дисплея //функция для перерисовки дисплея void RedrawDisplay(void) { DispPortChoose |= DISP_CHOOSE_MASK; //не выбираем ни одного индикатора DispPortDatabus = DispMemory[DispCurrSegment]; //настраиваем шину данных DispPortChoose = (DispPortChoose | DISP_CHOOSE_MASK) & (˜DispCurrSegmentMask); //выбираем нужный индикатор //определяем следующий сегмент и его маску if (DispCurrSegment < DISP_SEGMENT_COUNT - 1) { DispCurrSegment++; DispCurrSegmentMask = DispCurrSegmentMask << 1; } else //если переходим на первый сегмент - обнуляем счётчик и меняем маску { DispCurrSegment = 0; DispCurrSegmentMask = 1; } } //функция инициализации дисплея void DisplayInit() { DispDdrChoose |= DISP_CHOOSE_MASK; //все выводы, относящиеся к дисплею - на выход DispPortChoose = 0xFF; //не выбираем ни одного индикатора DispDdrDatabus = 0xFF; DispPortDatabus = 0xFF; //и не выводим ничего на шину данных DispCurrSegment = 0; //определяем, что текущий элемент - нулевой DispCurrSegmentMask = 1; //соответственно, маска для нулевого элемента } void main( void ) { DDRB = 0; PORTB = 0; DDRD = 0; PORTD = 0; DDRC = 0; PORTC = 0; DisplayInit(); while(1) { RedrawDisplay(); } }
Итак, у нас есть память – массив DispMemory; в нем по умолчанию записана строка с кодами символов 012345 (не путать с самими символами!). Чтобы получить эти коды, можно просто вычислять значения с помощью «распиновки» - см. рисунок … - или можно, например, использовать простенькие программки типа приложения «Seven Segment Editor» в среде MikroC.
Также есть переменные DispCurrSegment – показывает номер текущего работающего индикатора (он же номер текущего отображаемого элемента в массиве DispMemory), и DispCurrSegmentMask – соответственно, маска для текущего индикатора для шины адресации.
Для работы используются две функции – функция инициализации (ну, тут все понятно) и функция перерисовки дисплея. Последняя делает следующие действия:
- выключает дисплей через шину адресации
- выставляет на шине данных нужное значение
- включает нужный индикатор
- переходит на следующий индикатор и изменяет маску
В принципе, все как мы и планировали выше.
Но есть несколько проблем:
Самое главное – это то, что нельзя вызывать функцию перерисовки дисплея в главной функции, так как в main-е в дальнейшем у нас будет вызываться множество функций, а перерисовка дисплея критична ко времени (чтобы убедиться в этом, просто добавьте задержку после вызова функции RedrawDisplay – на 5 мс изображение заметно дрожит, на 10 – раздражающе перемигивает)
Что ж, делать нечего – используем прерывание!
//добавляем прерывание //выводим на экран "012345" #include "ioavr.h" #include "inavr.h" #include "stdint.h" //ddr и port для работы с уже выбранным индикатором #define DispDdrDatabus DDRD #define DispPortDatabus PORTD //ddr и port для выбора индикатора #define DispDdrChoose DDRB #define DispPortChoose PORTB #define DISP_CHOOSE_MASK 0x3F #define DISP_SEGMENT_COUNT 6 uint8_t DispCurrSegment, DispCurrSegmentMask; //переменные для хранения для номера текущего индикатора и его маски uint8_t DispMemory[DISP_SEGMENT_COUNT] = {0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92}; //непосредственно сама память дисплея //прерывание для перерисовки дисплея void RedrawDispInterrupt(void) { DispPortChoose |= DISP_CHOOSE_MASK; //не выбираем ни одного индикатора DispPortDatabus = DispMemory[DispCurrSegment]; //настраиваем шину данных DispPortChoose = (DispPortChoose | DISP_CHOOSE_MASK) & (~DispCurrSegmentMask); //выбираем нужный индикатор //определяем следующий сегмент и его маску if (DispCurrSegment < DISP_SEGMENT_COUNT - 1) { DispCurrSegment++; DispCurrSegmentMask = DispCurrSegmentMask << 1; } else //если переходим на первый сегмент - обнуляем счётчик и меняем маску { DispCurrSegment = 0; DispCurrSegmentMask = 1; } } //функция инициализирует дисплей void DisplayInit() { DispDdrChoose |= DISP_CHOOSE_MASK; //все выводы, относящиеся к дисплею - на выход DispPortChoose = 0xFF; //не выбираем ни одного индикатора DispDdrDatabus = 0xFF; DispPortDatabus = 0xFF; //и не выводим ничего на шину данных DispCurrSegment = 0; //определяем, что текущий элемент - нулевой DispCurrSegmentMask = 1; //соответственно, маска для нулевого элемента } //инициализация таймера Т0 void FastTimerInit() { __disable_interrupt(); TCCR0A = 0; TCCR0B = (1 << CS01); //таймер 0 включён с предделителем 8 в нормальном режиме; TIMSK0 |= (1 << TOIE0); //разрешили прерывание по переполнению Т0 __enable_interrupt(); } //прерывание таймера Т0 #pragma vector = TIMER0_OVF_vect __interrupt void FastTimerInterrupt(void) { RedrawDispInterrupt(); //перерисовка дисплея } void main( void ) { DDRB = 0; PORTB = 0; DDRD = 0; PORTD = 0; DDRC = 0; PORTC = 0; DisplayInit(); FastTimerInit(); while(1) { } }
Смотрим, что изменилось, а что нет: функция RedrawDisplay просто поменяла название и стала вызываться в прерывании. Добавились функции для таймера – инициализация (таймер 0 с предделителем 8 и прерыванием по переполнению) и, соответственно, сама функция прерывания.
Так, отлично. Теперь у нас есть возможность делать что-то в основном цикле, и это никак не будет влиять на работу дисплея.
Следующая задача – это писать на дисплее то, что нам хочется.
Есть несколько вариантов реализации: во-первых, можно сделать просто – вызываем функцию, передаём туда код для дисплея, записываем его в массив DispMemory. Но! Работать с кодами очень, очень неудобно – совершенно непонятно, что мы передаём в данный момент.
Гораздо удобней передавать в функцию строку того, что мы хотим отобразить, внутри функции перекодировать эту строку в код и переписывать массив DispMemory. Но тогда нам понадобится хранить «алфавит дисплея» - пары «символ-код». Алфавит наш, конечно, будет несколько… своеобразным: часть букв будет, естественно, пропущена – не все можно отобразить через семисегментный дисплей – также будут присутствовать цифры, точка, пробел и так далее.
Так, пусть мы определили наш алфавит:
//массив "символ-код" //по "договорённости" размещаем сначала цифры от 0 до F #define DISP_SYMBOL_COUNT 37 uint8_t DispSymbolCodeArr[2][DISP_SYMBOL_COUNT] = {{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', ' ', '.', 'T', 'N', '-', 'S', 'U', 'V', 'L', 'I', 'R', 'O', 'H', 'Y', 'N', 'R', 'P', 'I', 'c', '<', '>'}, {0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E, 0xFF, 0x7F, 0x87, 0xAB, 0xBF, 0x92, 0xC1, 0xC1, 0xC7, 0xF9, 0xAF, 0xA3, 0x89, 0x91, 0xAB, 0x08, 0x8C, 0xFB, 0x46, 0x23, 0x1C}};
На вход мы получили некоторую строку – будем надеяться, что она содержит символы только из нашего алфавита, но это не факт. Что же делать дальше?
Вот алгоритм:
Рисунок 7. Алгоритм функции записи символов в память дисплея.
Вот код новой функции:
//массив "символ-код" //по "договорённости" размещаем сначала цифры от 0 до F #define DISP_SYMBOL_COUNT 37 uint8_t DispSymbolCodeArr[2][DISP_SYMBOL_COUNT] = {{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', ' ', '.', 'T', 'N', '-', 'S', 'U', 'V', 'L', 'I', 'R', 'O', 'H', 'Y', 'N', 'R', 'P', 'I', 'c', '<', '>'}, {0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E, 0xFF, 0x7F, 0x87, 0xAB, 0xBF, 0x92, 0xC1, 0xC1, 0xC7, 0xF9, 0xAF, 0xA3, 0x89, 0x91, 0xAB, 0x08, 0x8C, 0xFB, 0x46, 0x23, 0x1C}}; const uint8_t DispBlankSymbol = ' '; const uint8_t DispBlankCodeIndex = 17; const uint8_t DispPointSymbol = '.'; const uint8_t DispPointCodeIndex = 18; … //изменение строки, которая будет записываться на дисплей; строка должна иметь SegmentCount символов минимум или заканчиваться 0 //ключевое слово "__monitor" запрещает прерывания при выполнении функции __monitor void DisplayShowStr(char* Str) { int8_t currDispMemoryIndex, currSymbolIndex; uint8_t* currStrSymbol; currDispMemoryIndex = 0; if (Str) //если строка существует { currStrSymbol = (uint8_t*) Str; //определяем текущий символ - первый while ((currDispMemoryIndex < DISP_SEGMENT_COUNT) && (*currStrSymbol != 0)) //до конца строки или пока есть непереписанные сегменты { //ищем текущий символ в таблице соответствий "Символ-код" currSymbolIndex = 0; while ((*currStrSymbol != DispSymbolCodeArr[0][currSymbolIndex]) && (currSymbolIndex < DISP_SYMBOL_COUNT)) currSymbolIndex++; if (currSymbolIndex < DISP_SYMBOL_COUNT) //если нашли символ DispMemory[currDispMemoryIndex] = DispSymbolCodeArr[1][currSymbolIndex]; else //если не нашли - записываем пробел DispMemory[currDispMemoryIndex] = DispSymbolCodeArr[1][DispBlankCodeIndex]; currDispMemoryIndex++; //перешли на следующее знакоместо currStrSymbol++; //перешли на следующий записываемый символ } } //если строка оказалась меньше 6 символов - дописываем пробелы for (; currDispMemoryIndex < DISP_SEGMENT_COUNT; currDispMemoryIndex++) //до конца памяти дисплея DispMemory[currDispMemoryIndex] = DispSymbolCodeArr[1][DispBlankCodeIndex]; //записываем пробел } void main( void ) { DDRB = 0; PORTB = 0; DDRD = 0; PORTD = 0; DDRC = 0; PORTC = 0; DisplayInit(); FastTimerInit(); while(1) { DisplayShowStr("123456.7"); __delay_cycles(16000000); DisplayShowStr("AB.CD"); __delay_cycles(16000000); } }
Для того, чтобы во время выполнения функции прерывание с перерисовыванием дисплея не вызывалось, используем перед функцией ключевое слово «__monitor» - оно запрещает прерывания при выполнении функции.
Также для упрощения записи пробелов мы отдельно вынесли индекс символа пробела из массива.
Вот результат:
Рисунок 8. Отображение строки с отдельно стоящей точкой.
Получилась вот какая проблема: точка стала занимать отдельное место, хотя логичней будет приписать её к предыдущему символу.
Тогда нам добавить в функцию два случая: первый – это когда встречается точка в середине строки – тогда мы дописываем к предыдущему символу код точки (так, код символа «С» без точки – 0хС6 = 11000110; код символа «С» с точкой – 0x46 = 01000110) – иными словами, к предыдущему символу нужно нужно применить побитовое И с кодом точки 0x7F = 01111111.
Второй случай – когда точку нужно добавить в конце строки – то есть когда точка – символ № DISP_SYMBOL_COUNT + 1.
//изменение строки, которая будет записываться на дисплей; строка должна иметь SegmentCount символов минимум или заканчиваться 0 //ключевое слово "__monitor" запрещает прерывания при выполнении функции __monitor void DisplayShowStr(char* Str) { int8_t currDispMemoryIndex, currSymbolIndex; uint8_t* currStrSymbol; currDispMemoryIndex = 0; if (Str) //если строка существует { currStrSymbol = (uint8_t*) Str; //определяем текущий символ - первый while ((currDispMemoryIndex < DISP_SEGMENT_COUNT) && (*currStrSymbol != 0)) //до конца строки или пока есть непереписанные сегменты { //если наш символ - точка, и она стоит не в начале строки, то к предыдущему значению приписываем '.' if ((*currStrSymbol == DispPointSymbol) && (currStrSymbol != (uint8_t*)Str)) { DispMemory[currDispMemoryIndex - 1] &= DispSymbolCodeArr[1][DispPointCodeIndex]; } else //иначе ищем текущий символ в таблице соответствий "Символ-код" { //ищем текущий символ в таблице соответствий "Символ-код" currSymbolIndex = 0; while ((*currStrSymbol != DispSymbolCodeArr[0][currSymbolIndex]) && (currSymbolIndex < DISP_SYMBOL_COUNT)) currSymbolIndex++; if (currSymbolIndex < DISP_SYMBOL_COUNT) //если нашли символ DispMemory[currDispMemoryIndex] = DispSymbolCodeArr[1][currSymbolIndex]; else //если не нашли - записываем пробел DispMemory[currDispMemoryIndex] = DispSymbolCodeArr[1][DispBlankCodeIndex]; currDispMemoryIndex++; //перешли на следующее знакоместо } currStrSymbol++; //перешли на следующий записываемый символ } } //свободные сегменты вроде кончились, но вдруг нам нужно записать ещё только точку? if((*currStrSymbol == DispPointSymbol) && (currDispMemoryIndex == DISP_SEGMENT_COUNT)) DispMemory[currDispMemoryIndex - 1] &= DispSymbolCodeArr[1][DispPointCodeIndex]; //если строка оказалась меньше 6 символов - дописываем пробелы for (; currDispMemoryIndex < DISP_SEGMENT_COUNT; currDispMemoryIndex++) //до конца памяти дисплея DispMemory[currDispMemoryIndex] = DispSymbolCodeArr[1][DispBlankCodeIndex]; //записываем пробел }
Рисунок 9. Отображение строки с точкой, присоединяющейся к предыдущему символу.
Итак, теперь у нас уже есть дисплей, который будет отображать произвольные строки из символов алфавита!
Что нужно ещё для счастья?
Возвращаемся к нашему моделированию – шла речь о том, чтобы при настройке желаемой температуры или таймера значение на дисплее мигало.
Что есть мигание – это попеременное включение и выключение дисплея. Так, за включение-выключение и отображение информации на дисплее отвечает функция, вызываемая в прерывании – её-то и попробуем модифицировать!
Так, прерывание у нас вызывается очень часто – раз в 256 мкс, а мигание должно происходить так, чтобы это было не раздражающе для глаза. Ищем в прерывании место, которое вызывается реже всего:
Рисунок 10. Поиск наиболее редко вызываемого места в прерывании.
Итак, смена адреса текущего индикатора на нулевой (окончание очередной полной отрисовки дисплея) происходит реже всего, а именно:
КОЛИЧЕСТВО_ИНДИКАТОРОВ * ЧАСТОТА_ВЫЗОВА_ПРЕРЫВАНИЯ = 6 * 256 мкс = 1536 мкс ˜ 1.5 мс
Уже неплохо, но все ещё слишком часто для того, чтобы вставить в это место включение-выключение дисплея – при такой частоте мигание будет просто-напросто незаметно, дисплей будет просто тускло светиться.
Что же делать в случаях, когда таймер вызывается слишком часто? Счётчик в счётчике – введем переменную, которая будет увеличиваться каждый раз при попадании в переход на нулевой индикатор. По достижению максимума мы и будем работать с миганием.
Теперь посмотрим, как реализовать это самое мигание, то есть включать-выключать дисплей в нужный момент – и так, чтобы потратить на это минимум времени и изменить минимум кода в основной функции. Пляшем от того, что мы будем редактировать ту часть функции, где происходит переход на нулевой индикатор – именно это и определяет «включенность» дисплея! В этом месте мы задаём маску для текущего индикатора, а дальше весь цикл отображения памяти мы просто эту маску сдвигаем.
А теперь представим, что вместо маски для нулевого индикатора мы не выбираем индикатора вообще – тогда в дальнейшем все сдвиги будут «пустые», и дисплей будет выключен! Вот то, что нам нужно!
Рисунок 11. Алгоритм реализации мигания в прерывании.
Получили следующий код:
//переменные для работы с миганием: флаг включенности режима мигания, текущая фаза мигания (дисплей включён-выключен), счётчик для смены фазы мигания uint8_t DispBlinkEnabled, DispBlinkCurrMode, dispBlinkModeSwitchCounter; #define DISP_BLINK_MODE_SWITCH_MAX_COUNTER 255 //длительность мигания дисплея //прерывание для перерисовки дисплея void RedrawDispInterrupt(void) { DispPortChoose |= DISP_CHOOSE_MASK; //не выбираем ни одного индикатора DispPortDatabus = DispMemory[DispCurrSegment]; //настраиваем шину данных DispPortChoose = (DispPortChoose | DISP_CHOOSE_MASK) & (˜DispCurrSegmentMask); //выбираем нужный индикатор //определяем следующий сегмент и его маску if (DispCurrSegment < DISP_SEGMENT_COUNT - 1) { DispCurrSegment++; DispCurrSegmentMask = DispCurrSegmentMask << 1; } else //если переходим на первый сегмент - обнуляем счётчик и меняем маску { DispCurrSegment = 0; if(DispBlinkEnabled) //если включён режим мигания { dispBlinkModeSwitchCounter++; //увеличиваем счётчик if (dispBlinkModeSwitchCounter == DISP_BLINK_MODE_SWITCH_MAX_COUNTER) //если выждали время, когда дисплей был включен или выключен - пора интвертироваться { dispBlinkModeSwitchCounter = 0; DispBlinkCurrMode = !DispBlinkCurrMode; //первый бит будет либо 1 (тогда ниже маска будет равняться первому сегменту), либо 0 - тогда будет пройден цикл без отображения на дисплей } DispCurrSegmentMask = DispBlinkCurrMode & 1; } else DispCurrSegmentMask = 1; //если выключен режим мигания, то маска просто для первого сегмента } }
Отлично!
По сути своей, мы сделали то, что хотели – теперь осталось навести красоту: написать отдельные функции для включения-выключения мигания, также впоследствии мы напишем специальные функции для отображения температуры, времени, различных настроек и т.д.
Затем нужно будет вынести функции и переменные, связанные с дисплеем, в отдельную библиотеку (выделить файл заголовка ‘.h’ и файл ‘.c’).
Итак, вот версия с выделенной отдельно библиотекой дисплея – проект сделан в среде IAR Embedded Workbench IDE.
А вот отдельно прошивка.
Пример работы библиотеки
Как видно, мы перевернули последние два индикатора - это сделано для того, чтобы корректно отображать температуру, а именно точку для градуса Цельсия: "°C"; при этом сегменты подключены к основной шине таким образом, чтобы можно было выводить на них символы без искажений, как и для остальных индикаторов.
Наверх
Автор - Moriam =ˆˆ=