23.12.2008

Семисегментный индикатор для Arduino (3)

Часть III

(продолжение, см. Часть II и Часть I)


Очень удобно экспериментировать с макеткой без пайки, например:



Воткните в нее LED-ы и нагрузочные резисторы, затем соедините проводами по принципиальной схеме из второй части. Подключите все это к Arduino, на котором будет моделироваться программа отображения через 11 проводов: 8 для шины, 3 для общих катодов. Получится некий колтун из проводов (для простоты надо брать кроссировку - одним концом в плату, другим - в колодку на Arduino).

Готово, самое время написать sketch для ATMEGA8, который будет у нас исполнять роль последовательного приемника, защелки, знакогенератора, декодера и отображателя ;)

Для начала надо определить массив знакогенератора. Будем хранить в нем карту отображения сегментов a,b,c,d,e,f,g побитно (я выбрал старший бит с весом 0x80 для сегмента a, 0x40 для сегмента b и так далее):

byte d_map[MAX_SYMBOLS];

Также потребуется защелка - для хранения кодов символов, которые мы отображаем в настоящий момент:

byte latch[SEG_NUM];

Чтобы ввести немного универсальности, я сделал промежуточные массивы, в которых задаются pin-ы Arduino, к которым подключены сегменты шины:

byte l_pin[8];

(восемь - так как есть еще сегмент DP - десятичная точка). Ну и тогда уж пины общих катодов каждого индикатора:

byte s_pin[SEG_NUM];

Теперь надо выбрать способ отображения. На ум приходит самый тривиальный - как в калькуляторе. Принятый по TWI-шине код символа помещается в самый младший разряд (справа), остальные сдвигаются влево, самый старший навсегда покидает индикатор. Но что же делать с точкой? В описанную схему она никак не монтируется, введение точки в код символа а) удваивает знакогенератор (с точкой и без точки - отдельные символы) б) делает менее интуитивным кодирование и передачу символов из "старшего" Arduino.

Ну и ладно, давайте сделаем специальный код символа, который не надо хранить в знакогенераторе - код точек. При его получении ничего никуда не сдвигается, а просто определяется, на каких местах зажигаются точки (да хоть на всех). Будем хранить точки в отдельном массиве:

boolean point[SEG_NUM];

Массив обновляется только при поступлении кода символа с включенным старшим битом (0x80), после чего остальные биты трактуются как включение или выключение точки на соответствующем знакоместе и переписываются в массив point.

Самое время определиться с символами, которые мы собрались загнать в знакогенератор (содержимое массива d_map и значение константы MAX_SYMBOLS). Для простоты (а чего экономить-то?) определяем каждому символу 8 бит (старший занят на признак позиций точек, см. выше, так что максимум 128 символов). У меня хватило фантазии на такой набор:





Последним угадывается истинно пелевинский символ-пустота.

Самое время - показать программу по мотивам вышеизложенного:

#define CPU_FREQ 8000000L
#include <Wire.h>

#define ADDR 0x69 // I2C device address (change if need, note: 7-bit)
#define SEG_NUM 3 // total digits in a display
#define delayms 1 // time for displaying one digit, ms

byte latch[SEG_NUM]; // защелка состояний
boolean point[SEG_NUM]; // десятичные точки
byte s_pin[SEG_NUM]; // пины общих катодов
byte l_pin[8]; // мап пинов на сегменты знакоместа
byte d_map[24]; // память знакогенератора

void receive_handler(int numbytes) {
for (unsigned int i=0;i<numbytes;i++) {
byte r = Wire.receive();
if (r<0x80) {
// ordinary symbol
for (byte k=(SEG_NUM-1);k!=0;k--) latch[k] = latch[k-1];
latch[0] = r;
} else {
// DP code
for (byte k=0;k<SEG_NUM;k++) point[k] = (r >> k) & 0x1;
}
}
}

void setup() {
s_pin[0] = 4; // 2;
s_pin[1] = 12; //12;
s_pin[2] = 2; // 4;

l_pin[0] = 5;
l_pin[1] = 6;
l_pin[2] = 7;
l_pin[3] = 8;
l_pin[4] = 9;
l_pin[5] = 10;
l_pin[6] = 11;
l_pin[7] = 3;

for (byte i=0;i<SEG_NUM;i++) {
point[i] = false;
pinMode(s_pin[i],OUTPUT);
digitalWrite(s_pin[i],LOW);
}
for (byte i=0;i<8;i++) pinMode(l_pin[i],OUTPUT);

//
d_map[0] = 0b00000011; // 0
d_map[1] = 0b10011111; // 1
d_map[2] = 0b00100101; // 2
d_map[3] = 0b00001101; // 3
d_map[4] = 0b10011001; // 4
d_map[5] = 0b01001001; // 5
d_map[6] = 0b01000001; // 6
d_map[7] = 0b00011111; // 7
d_map[8] = 0b00000001; // 8
d_map[9] = 0b00001001; // 9
d_map[10] = 0b00010001; // A
d_map[11] = 0b11000001; // b
d_map[12] = 0b01100011; // C
d_map[13] = 0b10000101; // d
d_map[14] = 0b01100001; // E
d_map[15] = 0b01110001; // f
d_map[16] = 0b10010001; // H
d_map[17] = 0b11110111; // i
d_map[18] = 0b11100011; // L
d_map[19] = 0b00110001; // P
d_map[20] = 0b10000011; // U
d_map[21] = 0b00111001; // градус
d_map[22] = 0b11111101; // минус
d_map[23] = 0xff; // пустой

// initial display state - empty
latch[0] = 22;
latch[1] = 22;
latch[2] = 22;

// start wiring library
Wire.begin(ADDR);
Wire.onReceive(receive_handler);
}

//
// Set LED bus in order to display symbol 'd'
//

void set_digit(byte d) {
byte i=0;
for (byte mask=0x80;mask!=1;mask>>=1)
digitalWrite(l_pin[i++],(d_map[d]&mask) ? HIGH : LOW );
}

void indicate() {
for (byte k=0;k<SEG_NUM;k++) {
digitalWrite(s_pin[k],HIGH);
set_digit(latch[k]);
digitalWrite(l_pin[7],point[k] ? LOW : HIGH); // DP segment
delay(delayms);
digitalWrite(s_pin[k],LOW);
}
}

void loop() {
indicate();
}


Теперь рассмотрим программу на передающем "конце". Эксперименты с TWI показывают, что для стабильной работы надо снабдить окончание передачи небольшими задержками. Соответственно, получаем две функции:

void od(byte d) {
Wire.beginTransmission(ADDR);
Wire.send(d);
Wire.endTransmission();
delayMicroseconds(100);
}

void od3(byte d1, byte d2, byte d3) {
Wire.beginTransmission(ADDR);
Wire.send(d1);
Wire.send(d2);
Wire.send(d3);
Wire.endTransmission();
delayMicroseconds(100);
}


Первая выводит один символ, вторая - сразу три, заполняя весь наш дисплей за один прием.
Теперь макетирование окончено, и можно брать в руки паяльник...

(окончание см. в Части IV)

Комментариев нет:

Отправить комментарий