Семисегментные индикаторы - наиболее простой способ для представления цифровой информации в виде арабских цифр и даже некоторых букв (для полноценного отображения букв используются более сложные многосегментные и матричные индикаторы). Светодиодные семисегментные индикаторы имеют высокую яркость, широкий диапазон рабочих температур, низкую стоимость и очень просты в управлении. Главными недостатками светодиодных индикаторов являются относительно высокое энергопотребление и слабая видимость при ярком освещении, хотя на нашем модном цветном мобильном телефоне тоже ничего не видно на солнышке днём :)
Конструктивно светодиодный семисегментный индикатор представляет собой группу из семи светодиодов, расположенных в определенном порядке. Включая их в различных комбинациях можно составить упрощённые изображения цифр 0..9. Индикаторы различаются по размеру, типу соединения светодиодов - общий анод, общий катод, по количеству отображаемых разрядов - одноразрядные, двухразрядные и более, по яркости свечения и по цвету - красные, желтые, зеленые и т.д.
display7.c
#include <stdint.h>
static uint8_t display7(uint8_t value) {
switch(value) {
case 0:
return 0xEE;
case 1:
return 0x28;
case 2:
return 0xCD;
case 3:
return 0x6D;
case 4:
return 0x2B;
case 5:
return 0x67;
case 6:
return 0xE7;
case 7:
return 0x2C;
case 8:
return 0xEF;
case 9:
return 0x6F;
case 10:
return 0xAF;
case 11:
return 0xE3;
case 12:
return 0xC6;
case 13:
return 0xE9;
case 14:
return 0xC7;
case 15:
return 0x87;
default:
return 0;
}
}
main.c
/*
xc8 --chip=16f648A main.c
*/
#include <xc.h>
#include <stdint.h>
#include "display7.c"
#pragma config WDTE = OFF
const uint8_t porta_btn_mask = 0b11110;
int main() {
uint8_t display_data = 0,
last_buttons = 0;
PORTB = TRISB = 0;
while(1) {
uint8_t buttons = PORTA & porta_btn_mask;
if (last_buttons != buttons) {
last_buttons = buttons;
if (!RA1) {
display_data += 2;
} else if (!RA2) {
display_data += 1;
} else if (!RA3) {
display_data -= 1;
} else if (!RA4) {
display_data -= 2;
}
PORTB = display7(display_data & 0xF);
}
}
return 0;
}
В данном примере используется наиболее простой вид индикации - статическая индикация, в таком режиме каждый сегмент индикатора постоянно находится в одном из двух состояний - включен или выключен. Функция display7
приводит двоичное число в код семисегментного индикатора и позволяет отображать шестнадцатиричное число 0..F.
Для одновременной работы нескольких разрядов семисегментного индикаторов часто используют динамическую индикацию (динамическое управление), которое подразумевает поочередное переключение разрядов с частотой, не воспринимаемой человеческим глазом. Для динамической индикации требуется меньшее количество ног микроконтроллера и меньшее количество внешних элементов. На исследуемой плате нога RB4 как раз и занимается поочерёдным переключением двух разрядов - при нуле на RB4 данные с PORTB попадают на левый индикатор, при единице соответственно на правый.
Задача переключения разрядов семисегментных довольно критична ко времени - при его неравномерном распределении один разряд будет ярче другого, что приведёт к неприятным для человеческого глаза эффектам, мерцаниям. Для задач, критичных ко времени реакции на возникшие события в микроконтроллерах предусмотрены прерывания - основной цикл main()
приостанавливается (прерывается), сохраняя свой контекст выполнения, отрабатывает логика обработчика прерывания, после чего управление снова передаётся main()
, при этом сохранённый ранее контекст выполнения восстанавливается.
isr.c
#include <xc.h>
#include <stdint.h>
#include <stdbool.h>
#include "display7.c"
// C89, section 6.5.7 Initialization.
// If an object that has static storage duration is not initialized explicitly, then:
// - if it has arithmetic type, it is initialized to (positive or unsigned) zero;
// interface
volatile uint8_t display_data;
// http://microchip.wikidot.com/faq:31
void interrupt _int_7(void) // interrupt function
{
static bool current_7;
if(INTCONbits.T0IF && INTCONbits.T0IE)
{ // if timer flag is set & interrupt enabled
INTCONbits.T0IF = 0; // clear the interrupt flag
uint8_t data = display7(current_7 ? display_data & 0xF : display_data >> 4);
PORTB = current_7 ? data | 0b10000 /* RB4 */ : data;
current_7 = !current_7;
}
}
main.c
/*
xc8 --chip=16f648A main.c isr.c
*/
#include <xc.h>
#include <stdint.h>
#pragma config WDTE = OFF
extern uint8_t display_data;
int main() {
const uint8_t porta_btn_mask = 0b11110;
uint8_t last_buttons = 0;
OPTION_REGbits.T0CS = 0; // Timer0 increments on instruction clock
OPTION_REGbits.PSA = 0; // Prescaler is assigned to the Timer0 module
OPTION_REGbits.PS0 = 1;
OPTION_REGbits.PS1 = 0;
OPTION_REGbits.PS2 = 0; // Prescaler 1:4; T0IF each 256*4 cycle
INTCONbits.T0IE = 1; // Enable interrupt on TMR0 overflow
INTCONbits.GIE = 1; // Global interrupt enable
PORTB = TRISB = 0;
while(1) {
uint8_t buttons = PORTA & porta_btn_mask;
if (last_buttons != buttons) {
last_buttons = buttons;
if (!RA1) {
display_data += 10;
} else if (!RA2) {
display_data += 1;
} else if (!RA3) {
display_data -= 1;
} else if (!RA4) {
display_data -= 10;
}
}
}
return 0;
}
В файле isr.c
реализована логика, отвечающая за отображение информации на семисегментном дисплее. Интерфейс взаимодействия isr.c
с другими программными модулями, такими как main.c, только через переменную display_data
. Функция display7
не входит в этот интерфейс и поэтому объявлена как static
, т.е. доступна только в пределах своего модуля. Грамотное проектирование интерфейсов позволяет распределить для модулей зоны ответственности и инкапсулировать их реализацию.
Модули, полученные на этапе проектирования системы, должны быть минимально связанны друг с другом (Слабая Связанность, Low Coupling). При таком проектировании в случае изменения одного модуля не придется править другие или эти изменения будут минимальными. Чем слабее связанность, тем легче писать/понимать/расширять/чинить любую программу на любом языке программирования.
Так как скважность импульсов на RB4 равна двум яркость свечения каждого из разрядов семисегментного распределяется поровну от максимальной.
И напоследок одно замечание по коду extern uint8_t display_data;
, который указывает на переменную в другом файле. К сожалению в данном случае Си компилятор тут не проверяет типы и поэтому оставит без внимания какую-нибудь ерунду типа extern uint16_t display_data;
при том что реальная переменная uint8_t
. Поэтому для лучшего контроля типов декларации extern переменных / функций принято описывать в отдельных файлах.
display_data.h
extern volatile uint8_t display_data;
Этот файл нужно включить #include "display_data.h"
в main.c и isr.c.
Далее сторожевой таймер.