Часто при разработке радио-электронных устройств возникает необходимость выполнения климатических условий с повышенными требованиями, таких как предельно допустимые рабочие температуры -55…+55 ºC. И эти требования становятся проблемой для реализации цифровых панелей взаимодействия с пользователем. Рабочие температуры ЖК индидикаторов в пределах -20...+70 ºC , люминисцентных газоразрядных индикаторов -40..+70 ºC. Поэтому возникает необходимость организовывать панели взаимодействия с оператором-пользователем с помощью цифровых индикаторов, одноцветных и двухцветных светодиодов. Для использования таких органов индикации необходимы схемотехнические и программные решения. Есть разные способы управления системой индикации. В данной статье хочу привести свой опыт использования и организации схемно-программной структуры проекта.
Cхема системы индикации
На рисунке ниже приведена применяемая мной схема подключения цифровых индикаторов, одноцветных и двухцветных светодиодов для вывода информации пользователю на лицевую панель. Все узлы собраны на отечественной комплектации. В качестве цифровых индикаторов используются индикаторы 3ЛС324Б1, которые управляются с контроллера с помощью буферных сдвиговых регистров IN74HC595AD. Особенностью данной схемы является ее масштабируемость. Для управления всеми индикаторами достаточно 5 выводов контроллера (можно уменьшить до трех, если OE выход подключить к минусу, а MR подключить к плюсу), которые используются для организации последовательного интерфейса к микросхемам IN74HC595AD. Как работать с этим интерфейсом описано в даташите на данную микросхему, пример программной реализации алгоритма работы представлен в листинге 1.
![](https://habrastorage.org/getpro/habr/upload_files/c46/4aa/992/c464aa9920881efe9d5a357bc8836282.jpg)
Микросхемы имеют дополнительный выход Q7’, подключение которого ко входу DS следующей микросхемы дает возможность создания целой цепочки сдвиговых регистров для передачи данных от одной микросхемы к другой. Если кол-во переданных бит в первую микросхему превысит 8, то эта микросхема начнет выдавать принятый байт на вход следующей микросхемы через выход Q7’, при этом стробирующие сигналы для всех микросхем запараллелены. Схема, приведенная ниже содержит небольшое кол-во индикаторов, для примера работы. У меня есть проект, где кол-во индикаторов достигает 63 штук и это не предел, причем в этом проекте есть два разных варианта панели))) Перейдем к программной реализации работы данной схемы.
Программа управления системой индикации
Для удобной программной реализации работы с системой индикации используется С++. Иерархия классов системы индикации состоит из абстрактного базового класса Indicator, от которого наследуются классы Led (одноцветный светодиод), LedDual (двухцветный светодиод), Digit (цифровой индикатор). Абстрактный класс Indication, в котором реализован метод Show и который использует массив индикаторов для отображения и дочерние классы Indication_v1, Indication_v2 в которых создаются объекты индикаторы и реализуются методы Update и Test для разных вариантов панели индикации в одном проекте.
В листинге 1 представлена функция Show абстрактного класса Indication. Это основная функция, непосредственно в которой происходит работа со сдвиговыми регистрами и для организации удобной работы с которой строится иерархия классов. Цикл for с индексом j перебирает все объекты Indicator. Цикл for с индексом i занимается перебором всех доступных бит в каждом из объектов Indicator, так как определенному типу индикатору соответствует свое кол-во подключаемых ножек сдвигающих регистров.
Листинг1. Алгоритм работы с буферными сдвиговыми регистрами 74HC595RM13TR.
#define OE_H PORT_WriteBit ( MDR_PORTA, OE, Bit_SET )
#define OE_L PORT_WriteBit ( MDR_PORTA, OE, Bit_RESET )
#define MR_H PORT_WriteBit ( MDR_PORTA, MR, Bit_SET )
#define MR_L PORT_WriteBit ( MDR_PORTA, MR, Bit_RESET )
#define STCP_H PORT_WriteBit ( MDR_PORTA, STCP, Bit_SET )
#define STCP_L PORT_WriteBit ( MDR_PORTA, STCP, Bit_RESET )
#define SHCP_H PORT_WriteBit ( MDR_PORTA, SHCP, Bit_SET )
#define SHCP_L PORT_WriteBit ( MDR_PORTA, SHCP, Bit_RESET )
#define DS_H PORT_WriteBit ( MDR_PORTA, DS, Bit_SET )
#define DS_L PORT_WriteBit ( MDR_PORTA, DS, Bit_RESET )
#define RESET_74HC595 MR_L; STCP_H; STCP_L; MR_H; OE_L;
#define TOUT 10
#define PAUSE Delay(TOUT)
void Indication::Show()
{
// Перебор индикаторов
for(uint8_t j = 0;j < count;j++)
{
// Перебор всех битов значения value индиктора и запись
// каждого бита в сдвиговый регистр.
for(uint8_t i = indicators[j]->Bits();i > 0;i--)
{
SHCP_L;
PAUSE;
(indicators[j]->Value()&(1<<(i-1)))? DS_H:DS_L;
PAUSE;
SHCP_H;
PAUSE;
}
}
// Стробирующий импульс на выдачу значений на параллельный выход
STCP_H;
PAUSE;
STCP_L;
PAUSE;
}
В листинге 2 представлен абстрактный базовый класс Indicator и дочерние классы Led, LedDual и Digit. Член value данного класса – это выводимое значение для индикации, bits – кол-во бит для индикации. Классы Led, LedDual, Digit наследуются от абстрактного базового класса Indicator. Для каждого из этих классов value и bits имеет свое значение. В Led::value задается состояние выключен/включен, а bits = 1. Для LedDual::value задает состояния выключен/включен зеленый/включен красный/мигающий зеленый/мигающий красный, bits = 2. Для Digit::value – это значение выводимое на индикаторе, а bits = 8. Всю конкретику работы мы реализуем в дочерних классах, а использовать объекты этих классов будем через абстрактный интерфейс!
Листинг 2. Класс Indicator.
class Indicator
{
protected:
uint8_t value; // Выводимое значение
uint8_t bits; // Кол-во бит в значении
public:
Indicator(){}
// Получить значение.
uint8_t Value() { return value; }
// Установить значение.
virtual void SetValue(uint8_t val) = 0;
// Получить кол-во бит.
uint8_t Bits() { return bits; }
// Перегрузка оператора равно.
virtual void operator=(uint8_t val) = 0;
};
class Led : public Indicator
{
public:
Led() {
bits = 1;
value = 0;
}
void operator=(uint8_t val) { value = val;}
void SetValue(uint8_t val) { value = val;}
void Off() { value = 0;}
void On() { value = 1;}
};
class LedDual : public Indicator
{
public:
LedDual() {
bits = 2;
value = 0;
}
void operator=(uint8_t val) { SetValue(val); }
// Set val as
// 0 - off
// 1 - on green
// 2 - on red
// 3 - blink green
// 4 - blink red
void SetValue(uint8_t val) {
if ( val == 3 ) value = (value & 1)^ 1;
else if ( val == 4 ) value = (value & 2)^ 2;
else if ( val == 1 ) value = 1;
else if ( val == 2 ) value = 2;
else value = 0;
}
void Off() { value = 0;}
void Green() { value = 1;}
void Red() { value = 2;}
void BlinkGreen() { value = 3;}
void BlinkRed() { value = 4;}
};
class Digit : public Indicator
{
static const uint8_t segCode[];
public:
Digit() {
bits = 8;
value = 0;
}
void operator=(uint8_t val) { value = segCode[val];}
void SetValue(uint8_t val) { value = segCode[val];}
};
#define A_ (uint8_t)(1<<1)
#define B_ (uint8_t)(1<<4)
#define C_ (uint8_t)(1<<5)
#define D_ (uint8_t)(1<<0)
#define E_ (uint8_t)(1<<3)
#define F_ (uint8_t)(1<<6)
#define G_ (uint8_t)(1<<7)
#define H_ (uint8_t)(1<<2)
const uint8_t Digit::segCode[] = {
/* без точки */
/*00*/ /*0 */(uint8_t)(~(A_|B_|C_|D_|E_|F_)),
/*01*/ /*1 */(uint8_t)(~(B_|C_)),
/*02*/ /*2 */(uint8_t)(~(A_|B_|D_|E_|G_)),
/*03*/ /*3 */(uint8_t)(~(A_|B_|C_|D_|G_)),
/*04*/ /*4 */(uint8_t)(~(B_|C_|F_|G_)),
/*05*/ /*5 */(uint8_t)(~(A_|C_|D_|F_|G_)),
/*06*/ /*6 */(uint8_t)(~(A_|C_|D_|E_|F_|G_)),
/*07*/ /*7 */(uint8_t)(~(A_|B_|C_)),
/*08*/ /*8 */(uint8_t)(~(A_|B_|C_|D_|E_|F_|G_)),
/*09*/ /*9 */(uint8_t)(~(A_|B_|C_|D_|F_|G_)),
/* с точкой */
/*10*/ /*0.*/(uint8_t)(~(A_|B_|C_|D_|E_|F_|H_)),
/*11*/ /*1.*/(uint8_t)(~(B_|C_|H_)),
/*12*/ /*2.*/(uint8_t)(~(A_|B_|D_|E_|G_|H_)),
/*13*/ /*3.*/(uint8_t)(~(A_|B_|C_|D_|G_|H_)),
/*14*/ /*4.*/(uint8_t)(~(B_|C_|F_|G_|H_)),
/*15*/ /*5.*/(uint8_t)(~(A_|C_|D_|F_|G_|H_)),
/*16*/ /*6.*/(uint8_t)(~(A_|C_|D_|E_|F_|G_|H_)),
/*17*/ /*7.*/(uint8_t)(~(A_|B_|C_|H_)),
/*18*/ /*8.*/(uint8_t)(~(A_|B_|C_|D_|E_|F_|G_|H_)),
/*19*/ /*9.*/(uint8_t)(~(A_|B_|C_|D_|F_|G_|H_)),
/*20*/ /*_ */(uint8_t)(~(D_)),
/*21*/ /*P */(uint8_t)(~(A_|B_|E_|F_|G_)),
/*22*/ /*t */(uint8_t)(~(E_|D_|F_|G_)),
/*23*/ /*° */(uint8_t)(~(A_|B_|F_|G_)),
/*24*/ /*П */(uint8_t)(~(A_|B_|C_|E_|F_)),
/*25*/ /*с */(uint8_t)(~(D_|E_|G_)),
/*26*/ /*S */(uint8_t)(~(A_|C_|D_|F_|G_)),
/*27*/ /*С */(uint8_t)(~(A_|D_|F_|E_)),
/*28*/ /*E */(uint8_t)(~(A_|E_|D_|F_|G_)),
/*29*/ /*U */(uint8_t)(~(B_|C_|D_|E_|F_)),
/*30*/ /* */(uint8_t)(0xFF),
/*31*/ /*- */(uint8_t)(~(G_)),
/*32*/ /*H */(uint8_t)(~(B_|C_|E_|F_|G_))
};
Класс Indication является абстрактным и используется для наследования от него классов с различными вариантами наборов индикаторов и реализаций функций Update и Test. В функцию Update передаются данные, на основании которых выполняется обновление индицируемых значений.
Листинг 3. Абстрактный класс Indication.
#define SHCP PORT_Pin_0
#define DS PORT_Pin_1
#define STCP PORT_Pin_2
#define MR PORT_Pin_3
#define OE PORT_Pin_4
class Indication
{
int count; // кол-во индикаторов
Indicator** indicators; // указатель на массив указателей
// объектов Indicator
public:
Indication();
// Обновить состояние членов класса.
virtual void Update(const state_t & state){}
// Засветить все светодиоды, для визуальной проверки исправности.
virtual void Test(){}
// Непосредственный вывод значений в сдвиговые регистры.
void Show();
// Сброс-очистка регистров.
void Reset() { RESET_74HC595;}
// Установить кол-во индикаторов
void SetCount(int n) { count = n; }
// Установить указатель массива указателей
void SetIndicators(Indicator** ind) { indicators = ind;}
void HGgroup(uint32_t v, Digit & d1, Digit & d2, Digit & d3, uint8_t isDigit)
{
d1 = v / 100;
d2 = (v % 100)/10 + (isDigit ? 10 : 0);
d3 = v % 10;
}
};
Листинг 4. Дочерний класс Indication_v1.
class Indication_v1 : public Indication
{
Led HL1;
Led HL2;
LedDual HL3;
LedDual HL4;
LedDual HL5;
Digit HG3;
Digit HG2;
Digit HG1;
enum{n = 8};
Indicator* indicators[n];
public:
Indication_v1();
void Update(const state_t & arg);
void Test();
};
Indication_v1::Indication_v1()
:Indication()
{
indicators[0] = &HL5;
indicators[1] = &HL4;
indicators[2] = &HL3;
indicators[3] = &HL2;
indicators[4] = &HL1;
indicators[5] = &HG3;
indicators[6] = &HG2;
indicators[7] = &HG1;
Indication::SetCount(n);
Indication::SetIndicators(indicators);
}
void Indication_v1::Update(const state_t & arg)
{
int voltage = 285;
HGgroup(voltage, HG1, HG2, HG3, 1);
HL1.On();
HL2.On();
HL3.Red();
HL4.Green();
HL5.BlinkGreen();}
void Indication_v1::Test()
{
// Зажигаем на некоторое время все светодиоды.
// Для проверки не работающих.
// Зажигаем красные
HL3.Red();
HL4.Red();
HL5.Red();
HG1 = 18;
HG2 = 18;
HG3 = 18;
Show();
Delay (0x1FFFFFF);
// Зажигаем зеленые
HL3.Green();
HL4.Green();
HL5.Green();
HL1.On();
HL2.On();
Show();
Delay (0x1FFFFFF);
}
Для использования класса создаем объект класса в следующем виде:
#if ( VERS == 0 )
Indication_v1 indication;
#elif ( VERS == 1)
Indication_v2 indication;
#endif
indication.Reset();
while(1)
{
indication.Update(arg);
indication.Show();
sleep()
}
Данная структура проекта позволяет управлять индикаторами, используя их позиционные имена с помощью простых операторов (HL1 = 1 или HL1.Red()), т.е. ваша программа будет визуально соответствовать схеме и разрабатываться используя позиционные обозначения, представленные на схеме. Что очень удобно. Изменяемая часть, такая как версия панели инкапсулирована в отдельный класс, что дает возможность создавать новые классы панелей Indication_v2, Indication_v3, не портя алгоритмов уже разработанных панелей.