17 января 2012 г.

Arduino и тройной 7-сегментный дисплей на SPI

Мы уже научились управлять одинарным и двойным 7-сегментником (и даже ими одновременно) по SPI. Таким образом, двигаясь step-by-step, мы подобрались к тройному 7-сегментному дисплею. Здесь нас ожидает принципиально новый способ получения "картинки" на дисплее - нам придется делать своеобразную развертку. Стоит называть это динамической индикацией.




Тройной 7-сегментный дисплей

Если бы тройной 7-сегментник имел по 9 ног для каждой цифры, как одинарный и двойной, у него бы было 27 ног. Это слишком много, поэтому разработчики решили оставить только 11, соединив катоды одинаковых сегменов трех цифр вместе и оставив болтаться три анода отдельно.

Так выглядит этот дисплей:



и его схема:


Он может быть и не хромым как у меня - у него может быть 12 ног, а не 11. В этом случае, скорее всего, одна из ног будет дополнительным анодом.

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

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

Собираем схему

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

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

Принципиальная схема нашего изобразительного устройства будет такова:


Если вы внимательно разбирались с предыдущими схемами подключения регистров 74HC595, то у вас не должно возникнуть проблем. В кратце:
  1. красный провод - +5В;
  2. черный провод - земля;
  3. желтый провод - клок;
  4. оранжевые провода - последовательные данные (проходят через правый регистр "насквозь");
  5. коричневый провод - защелкивание регистров;
  6. зеленые провода - параллельные данные для анодов 7-сегментника;
  7. синие и серые (рисовал - не подумал) - параллельные данные для катодов 7-сегментника.
На схеме параллельные данные для анодов у меня берутся с ног 2, 3 и 4 (QC, QD и QE), но вы можете использовать любые другие, для этого вам придется изменить код - пересчитать посылаемые байты. А сделал я так, потому что мне было удобнее припаять провода именно к этим ногам:



Вообще с лицевой стороны получилось довольно симпатично:



Пустой левый сокет является "system reserved" - я впаял его для дальнейшего расширения функционала, а сейчас он останется пустым.

Первый тест

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

Катодный байт во время сеанса SPI-передачи проползает сквозь правый (см. схему) регистр и заползает в левый. Анодный байт заползает в правый регистр, да там и остается. В результате показываются циферки, и все радуются.

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

static uint8_t digit[16] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E};

Тут у нас лежат коды цифр от 0 до F. Выкинем ABCDF (они нафиг не нужны) и добавим в конце под десятым номером пустышку (она понадобится нам, когда мы будем отображать двузначные и однозначные числа). Не забываем сменить объявляемый размер массива:

static uint8_t digit[11] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90,0xFF};
В качестве анодного байта просто зафигачим 0xFF - чтобы на всех анодах были единицы, и зажигались одинаковые циферки.

Стряпаем код:


// подключаем библиотеку SPI
#include <SPI.h>
// провод CS подсоединяем к 8-му пину Arduino
enum { reg = 8 };

void setup()
{
// инициализируем SPI
  SPI.begin();
// настраиваем 8-й пин как выход 
  pinMode(reg, OUTPUT);
}

// храним в массиве все цифры
static uint8_t digit[11] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90,0xFF};

void loop()
{
  for (int i=0;i<11;i++){
    // притягиваем CS к земле - начало передачи
    digitalWrite(reg, LOW);
    // передаем катодный байт
    SPI.transfer(digit[i]);
    // передаем анодный байт
    SPI.transfer(0xFF);
    // отпускаем  CS - конец передачи
    digitalWrite(reg, HIGH);
    // ждем секунду
    delay(1000);
  }
}


Все работает:



Отображение трехзначного числа

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

Практика показала, что если при переключении цифры каждые 8 миллисекунд еще можно различить мерцание, то при значении в 7 миллисекунд глаз уже не видит дискретизацию.

Однако сначала нам нужно разобрать число на косточки. Пусть есть число int f, которое в десятичной системе имеет число разрядов не более трех, то есть число меньше 1000. Действуем так:
  1. Делим f на 100, оставаясь в рамках int - мы аккуратно получаем количество сотен в нашем числе.
  2. Отнимаем от f число сотен, помноженное на 100, делим остаток на 10 -получаем число десятков.
  3. Отнимаем от f число сотен, помноженное на 100 и число десятков, помноженное на 10 -получаем количество единиц.
  4. Если число сотен равно нулю, то ничего не отображаем в левом разряде.
  5. Если число сотен и десятков равно нулю, то ничего не отображаем в левом и среднем разрядах.
Потом фигачим нашу пару байт по SPI для каждого разряда нашего числа по очереди. Анодный байт рассчитываем так, чтобы 11100111 зажигало левый разряд, 11101011 - средний, 11110011 - правый. То есть у нас единица в битах 2,3 и 4 ответственна за зажигание соответствующего разряда.

Код для Arduino получился такой:


// подключаем библиотеку SPI 

#include <SPI.h> 
// провод CS подсоединяем к 8-му пину Arduino
enum { reg = 8 }; 


void setup()
{
// инициализируем SPI
  SPI.begin();   
// определяем 8-й пин Arduino как выход        
  pinMode(reg, OUTPUT); 
}
//мы разобъем число на сотни, десятки и единицы, объявляем их здесь
int hundreds=0, tens=0, ones=0; 
//время отображения каждой цифры       
int delayTime=7;        
//коды цифр на семисегментнике (0-9 и пустота)   
static uint8_t digit[11] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90,0xFF}; 
// коды позиций зажигаемого семисегментника (левый, центральный, правый)
static uint8_t pos[3]= {0xE7,0xEB,0xF3};             


void loop()
{
//число, которое будем выводить
  int f=707;                          
//выделяем сотни   
  hundreds=f/100;    
//выделяем десятки                   
  tens=(f-hundreds*100)/10; 
//выделяем единицы            
  ones=f-hundreds*100-tens*10;   
//если сотен нет, не отображаем ничего в 3м разряде       
  if (hundreds==0) hundreds=10;   
//если сотен и десятков нет, не отображаем ничего во 2м разряде      
  if (hundreds==10 && tens==0)tens=10; 


//начинаем передачу по SPI
  digitalWrite(reg, LOW);       
//передаем код цифры, соответствующей разряду сотен        
  SPI.transfer(digit[hundreds]);
//выбираем левый 7-сегментник        
  SPI.transfer(pos[0]);      
//заканчиваем передачу            
  digitalWrite(reg, HIGH);
//пауза, равная delayTime              
  delay(delayTime);                    


//передаем код цифры, соответствующей разряду десятков         
  digitalWrite(reg, LOW); 
  SPI.transfer(digit[tens]);
//выбираем центральный 7-сегментник            
  SPI.transfer(pos[1]);                
  digitalWrite(reg, HIGH); 
  delay(delayTime); 
        
//передаем код цифры, соответствующей разряду единиц
  digitalWrite(reg, LOW); 
  SPI.transfer(digit[ones]);  
//выбираем правый 7-сегментник          
  SPI.transfer(pos[2]);                
  digitalWrite(reg, HIGH); 
  delay(delayTime); 
  
}


Наслаждаемся вот таким зрелищем:




Если у вас что-то работает неправильно, для дебага измените delayTime на, например, 500 - тогда вам станет видно, что именно в устройстве происходит не так.

Динамическая индикация

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

Мы заводим переменные timer и timerPrev:

  • timer хранит время с начала работы программы в миллисекундах,
  • timerPrev хранит время после совершения последнего переключения. 

Как только разница между ними достигнет периода переключения (обзовем его thinkingTime), производим новое переключение и запоминаем значение timer в timerPrev.

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

Запихнем разбиение числа на разряды и их вывод в функцию void output (int f), чтобы это нам не мешалось в void loop(). А в главном цикле мы будем ждать, пока не пройдет  thinkingTime после последнего переключения и тогда вычислять очередной элемент последовательности Фибоначчи и отображать его. Код получился таким:



#include <SPI.h> 
enum { reg = 8 }; 


void setup()
{
  SPI.begin();   
  pinMode(reg, OUTPUT); 
}


int hundreds=0, tens=0, ones=0, fnn=0, fn=1, f=0
int delayTime=7;        
static uint8_t digit[11] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90,0xFF}; 
static uint8_t pos[3]= {0xE7,0xEB,0xF3};
unsigned long timer=0, timerPrev=0, thinkingTime=1000;             


void output(int f)


  hundreds=f/100;    
  tens=(f-hundreds*100)/10; 
  ones=f-hundreds*100-tens*10;   
  if (hundreds==0) hundreds=10;   
  if (hundreds==10 && tens==0)tens=10; 


  digitalWrite(reg, LOW);       
  SPI.transfer(digit[hundreds]);
  SPI.transfer(pos[0]);      
  digitalWrite(reg, HIGH);
  delay(delayTime);                    


  digitalWrite(reg, LOW); 
  SPI.transfer(digit[tens]);
  SPI.transfer(pos[1]);                
  digitalWrite(reg, HIGH); 
  delay(delayTime); 
        
  digitalWrite(reg, LOW); 
  SPI.transfer(digit[ones]);  
  SPI.transfer(pos[2]);                
  digitalWrite(reg, HIGH); 
  delay(delayTime); 
}



void loop()
{

 timer=millis();
   if(timer-timerPrev>thinkingTime){
      //вычисляем элемент последовательности Фибоначчи номер n      
      f=fnn+fn;
      //элементу номер n-2 присваиваем значение элемента n-1
      fnn=fn;
      //элементу номер n-1 присваиваем значение элемента n
      fn=f;
      //запоминаем время 
      timerPrev=timer;
      //увеличиваем время "раздумий"
      thinkingTime+=f;
      //если f дорос до 1000, возвращаем все как было
      if (f>1000){f=0;fn=1;fnn=0;thinkingTime=1000;}
  }
  //выводим число
  output(f);

  
}

Наслаждаемся:



Далее по теме Arduino и SPI