28 ноября 2011 г.

Arudino + SPI + 7-segment: добавляем фичи

В этой статье мы узнаем, как работать с АЦП Arduino и как обработать нажатие кнопки, напридумываем новых последовательностей для анимации на двойном 7-сегментном дисплее и вспомним замечательную штуку - рандомизатор. Собрав все эти и полученные ранее знания, повеселимся по полной!


Новая анимация для 7-сегментника

Нагенерим новых веселых анимаций. Для нас это означает, что мы включаем воображение, продумываем кадры и переводим 7-сегментные знаки в байтовые последовательности, после чего собираем знакомую нам схему:
и пишем код:


#include <SPI.h>
enum { reg = 9 };
void setup(){
  SPI.begin();     
  pinMode(reg, OUTPUT);
}


void loop(){
  static uint8_t seq[хх] = {вот тут будет наша последовательность};
    for (int i=0;i<хх;i+=2){
      digitalWrite(reg, LOW);
      SPI.transfer(seq[i]);
      SPI.transfer(seq[i+1]);
      digitalWrite(reg, HIGH);
      delay(80); 
  }
}
Код и схему я уже комментировал в предыдущей статье.

Например, последовательность
{0xFC,0xDE,0xFE,0xCE,0xFF,0xC6,0xFF,0xC3,0xFF,0xE1,0xFF,0xF0,0xFF,0xD8,0xFF,0xCC,
 0xFF,0xC6,0xF7,0xC7,0xF3,0xE7,0xF1,0xF7,0xF0,0xFF,0xD8,0xFF,0xCC,0xFF,0xC6,0xFF,
 0xC3,0xFF,0xE1,0xFF,0xF0,0xFF,0xF8,0xFE}
порождает следующую анимацию:



а последовательность
{0xFF,0xFF,0xFF,0xCF,0xFF,0x86,0xFF,0x80,0xCF,0x80,0x86,0x80,0x80,0x80,0x80,0xB0,
 0x80,0xF9,0x80,0xFF,0xB0,0xFF,0xF9,0xFF,0xFF,0xFF}
делает вот так:


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

АЦП Arduino

Плата Arduino имеет на борту 6-канальный 10-разрядный АЦП. Это означает, что мы можем одновременно дискретизовать 6 входных аналоговых сигналов по 1024 уровням. Одно измерение занимает примерно 100 мкс, что дает сэмплрейт всего порядка 10кГц. Этого вполне достаточно, пока мы не соберемся использовать Arduino в качестве осциллографа...

Входы АЦП обозначаются А0-А5 и в англоязычной литературе обзываются Analog Inputs. Ниже на картинке они обведены красным прямоугольником. Кстати, их можно использовать в качестве дополнительных цифровых пинов, если вам не хватило обычных 13ти.


Потенциометр как источник аналогового сигнала

Все со школы помнят потенциометр - эта такой переменный резистор с тремя ногами. Не знаю как у вас, а у меня в школе потенциометр представлял из себя железную дуру длиной 30 сантиметров, толщиной с мою детскую ручонку, со скользящим (читай: еле двигающимся, и то, если со всей силы давить на ручку) по обмотке контактом. Много, тяжело, но наглядно.

Запомнив навсегда принцип его работы, можем позволить себе взять потенциометр поменьше и прилепить его одной ногой на +5В, другой ногой на землю, третьей ногой на вход А0 Arduino.



Вращая ручку потенциометра, мы задаем напряжение, подаваемое на вход, от 0 до 5В. АЦП преобразует это напряжение в код от 0 до 1023.

Напряжение измеряется простой функцией analogRead(pinNumber). Предварительно ничего инициализировать не нужно.

Самое время рассказать про очень интересную функцию map, которая нам потом пригодится. Эта функция занимается тем, что пересчитывает входное значение из старых пределов в выходное значение в новых пределах, сохраняя пропорции. Функция имеет такой синтаксис:

outputValue = map (inputValue, inputMin, inputMax, outputMin, outputMax).

Например, мы считали с АЦП значение 850 в пределах 0...1023, а хотим получить значение в пределах 10...100. Используем map (850, 0, 1023, 10, 100), получаем 84. BINGO!


Тактовая кнопка

Для добавления еще большей интерактивности нашему проекту посмотрим, как работать с кнопками. Есть вот такая кнопка:

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

Пусть при отпущенной кнопке на 5-м пине Arudiuno будет низким, а при нажатой - высоким. Подключаем кнопку так, чтобы пин был притянут к земле через резистор, а нажатие кнопки подтягивало бы его к +5В:


Код для обработки кнопки довольно простой: сначала в теле setup() указываем режим пина, к которому подключена кнопка, INPUT, а в цикле считываем его состояние функцией digitalRead(pinNumber).


void setup() {
  pinMode(5, INPUT);    
}

void loop(){

  if (digitalRead(5) == HIGH) {обрабатываем нажатие кнопки тут}
}
Рандомизатор

Функция для генерации псевдослучайных чисел - это random(). Ей обязательно нужно указывать максимальное значение и необязательно - минимальное. Например, random(30) будет выдавать всякий раз "случайное" число от 0 до 30, а random(15, 100) - настолько же "случайное" число от 15 до 100.

Чтобы случайность была более случайной, используется функция randomSeed(), которую нужно запихать в тело void setup(). Так мы задаем отправную точку для random(). Если в качестве аргумента randomSeed() мы укажем постоянное число, то random() все время будет генерировать одну и ту же последовательность, однако написав randomSeed(analogRead(0)), мы будет брать за стартовую точку некий шум.


Веселье начинается!

Соберем такую схему:


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

Запрограммируем эту штуку так, чтобы ручкой потенциометра регулировалась частота кадров, а по нажатию кнопки рандомно менялась анимация. Ну и конечно, нафаршируем программу разными байтовыми последовательностями (да, сейчас будет махровое полотенце).

//Подключаем библиотеку SPI
#include <SPI.h>
//вешаем кнопку на 5-й пин, MOSI регистра - на 9-й
enum { regPin = 9, buttonPin = 5};
//переменная для счета времени
unsigned long timer=0;
//переменная для фиксации времени предыдущей смены кадра анимации
unsigned long timerPrev=0;
//простая бегущая переменная
int i=0;
//номер отрисовываемой последовательности
int seqNumber=0;
//период смены кадров
int period=10;
//длины последовательностей
int seqMax[14]={16,16,16,16,24,24,24,24,22,22,32,32,40,40} ;
//сами последовательности
static uint8_t seq[14][40]={
{0xFF,0x9C,0xFF,0x9C,0x9C,0xFF,0x9C,0xFF,0xA3,0xFF,0xA3,0xFF,0xFF,0xA3,0xFF,0xA3},
{0xFF,0xA3,0xFF,0xA3,0xA3,0xFF,0xA3,0xFF,0x9C,0xFF,0x9C,0xFF,0xFF,0x9C,0xFF,0x9C},  
{0xFF,0xCE,0xFF,0xDE,0xFC,0xFE,0xF8,0xFF,0xF1,0xFF,0xF3,0xF7,0xF7,0xE7,0xFF,0xC7}, {0xFF,0xC7,0xF7,0xE7,0xF3,0xF7,0xF1,0xFF,0xF8,0xFF,0xFC,0xFE,0xFF,0xDE,0xFF,0xCE},
{0xFC,0xFE,0xDC,0xFF,0xBD,0xBF,0xBF,0xAF,0xFF,0xA7,0xF7,0xE7,0xF3,0xF7,0xB3,0xFF,
 0xBB,0xFF,0xBF,0x9F,0xFF,0x9E,0xFE,0xDE},
{0xFE,0xDE,0xFF,0x9E,0xBF,0x9F,0xBB,0xFF,0xB3,0xFF,0xF3,0xF7,0xF7,0xE7,0xFF,0xA7,
 0xBF,0xAF,0xBD,0xBF,0xDC,0xFF,0xFC,0xFE}, 
{0xFF,0xFF,0xFF,0xCF,0xFF,0x86,0xFF,0x80,0xCF,0x80,0x86,0x80,0x80,0x80,0x80,0xB0,
 0x80,0xF9,0x80,0xFF,0xB0,0xFF,0xF9,0xFF,0xFF,0xFF},
{0xFF,0xFF,0xF9,0xFF,0xB0,0xFF,0x80,0xFF,0x80,0xF9,0x80,0xB0,0x80,0x80,0x86,0x80,
 0xCF,0x80,0xFF,0x80,0xFF,0x86,0xFF,0xCF,0xFF,0xFF},
{0xFF,0xFE,0xF7,0xDC,0xE3,0x9C,0xA3,0x88,0x81,0x80,0x80,0x81,0x88,0xA3,0x9C,0xE3,
 0xDC,0xF7,0xFE,0xFF,0xFF,0xFF},
{0xFF,0xFF,0xFE,0xFF,0xDC,0xF7,0x9C,0xE3,0x88,0xA3,0x80,0x81,0x81,0x80,0xA3,0x88,
 0xE3,0x9C,0xF7,0xDC,0xFF,0xFE},
{0xFF,0x9E,0xFF,0xDC,0xFF,0xF8,0xFF,0xF1,0xFF,0xE3,0xFF,0xA7,0xBF,0xAF,0xBD,0xBF,
 0xBC,0xFF,0xDC,0xFF,0xCE,0xFF,0xC7,0xFF,0xE3,0xFF,0xB3,0xFF,0xBB,0xBF,0xBF,0x9F},
{0xBF,0x9F,0xBB,0xBF,0xB3,0xFF,0xE3,0xFF,0xC7,0xFF,0xCE,0xFF,0xDC,0xFF,0xBC,0xFF,
 0xBD,0xBF,0xBF,0xAF,0xFF,0xA7,0xFF,0xE3,0xFF,0xF1,0xFF,0xF8,0xFF,0xDC,0xFF,0x9E},
{0xF8,0xFE,0xF0,0xFF,0xE1,0xFF,0xC3,0xFF,0xC6,0xFF,0xCC,0xFF,0xD8,0xFF,0xF0,0xFF,
 0xF1,0xF7,0xF3,0xE7,0xF7,0xC7,0xFF,0xC6,0xFF,0xCC,0xFF,0xD8,0xFF,0xF0,0xFF,0xE1,
 0xFF,0xC3,0xFF,0xC6,0xFE,0xCE,0xFC,0xDE},
{0xFC,0xDE,0xFE,0xCE,0xFF,0xC6,0xFF,0xC3,0xFF,0xE1,0xFF,0xF0,0xFF,0xD8,0xFF,0xCC,
 0xFF,0xC6,0xF7,0xC7,0xF3,0xE7,0xF1,0xF7,0xF0,0xFF,0xD8,0xFF,0xCC,0xFF,0xC6,0xFF,
 0xC3,0xFF,0xE1,0xFF,0xF0,0xFF,0xF8,0xFE},
};
           
void setup(){
//Инициализируем SPI
  SPI.begin();
//пин MOSI ставим в режим вывода
  pinMode(regPin, OUTPUT);
//пин кнопки ставим в режим ввода
  pinMode(buttonPin, INPUT);
//сидер для рандомизатора
  randomSeed(analogRead(1));
}
void loop(){
//если нажата кнопка, случайно выбираем номер последовательности
  if (digitalRead(buttonPin)==HIGH)seqNumber = random(12);
//запоминаем прошедшее время с начала работы программы
  timer = millis();
//вводим период смены кадров анимации с потенциометра
  period = map (analogRead(0), 0, 1023, 10, 100);
//когда прошло period времени после предыдущей смены кадра
  if (timer-timerPrev>period){
//начинаем передачу по SPI
    digitalWrite(regPin, LOW);
//передаем первый байт
    SPI.transfer(seq[seqNumber][i]);
//передаем второй байт
    SPI.transfer(seq[seqNumber][i+1]);
//заканчиваем передачу по SPI
    digitalWrite(regPin, HIGH);
//если анимация закончилась, запускаем ее снова
    i<(seqMax[seqNumber]-2) ? i+=2 : i=0;
//запоминаем время выставления текущего кадра
    timerPrev=timer;
  }
}
Вышла очень милая программка. Посмотрим, как она работает:


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