В предыдущей статье мы познакомились с шиной SPI и узнали, что для подключения ведомого устройства к ведущему нужно 4 провода. Однако, если ведомых устройств больше одного, у нас уже возникают интересные варианты.
Параллельное подключение устройств к шине SPI
При параллельном подключении несколько ведомых устройств используют общие провода SCLK, MOSI и MISO, при этом каждый ведомый имеет свою линию SS. Ведущий определяет устройство, с которым осуществляется обмен, путем формирования низкого сигнала на его SS.
Видно, что для подключения n устройств требуется n линий SS, то есть для функционирования SPI-среды с n ведомыми нужно выделить под это n+3 ноги микроконтроллера.
Последовательное подключение устройств к шине SPI
При последовательном подключении устройств они используют общие провода SCLK и SS, а выход одного подсоединяется во вход другого. MOSI ведущего подключается к первому устройству, а MISO - к последнему. То есть для ведущего на шине SPI это как бы одно устройство.
Остается отметить прелесть такого подключения: подключи хоть 3, хоть 8 устройств, это займет всего 4 ноги на контроллере.
Последовательное соединение двух сдвиговых регистров
Мы помним, что DS - есть пин последовательного ввода, а Q0-Q7 пины последовательного вывода. Q7S, который мы не использовали, когда у нас был всего один регистр в схеме, - это последовательный вывод регистра. Он находит свое применение, когда мы передаем больше 1 байта в регистры. Через этот пин последовательно протолкнутся все байты, предназначенные для последующих регистров, а последний передаваемый байт останется в первом регистре.
Подсоединяя пин Q7S одного первого регистра к пину DS второго (и так далее, если это необходимо), получаем двойной (тройной и т.д.) регистр.
Подключение двойного 7-сегментного дисплея
Двойной 7-семисегментный дисплей это, как правило, устройство с 18-ю ногами, по 9 на каждый символ. Взглянем на схему (мой дисплей имеет маркировку LIN-5622SR и есть большая вероятность того, что его схема подключения окажется уникальна):
Это дисплей с общим анодом, что говорит о необходимости подачи на com1 и com2 высокого уровня ТТЛ, а для зажигания диода - низкий уровень на соответствующей ноге. Если у вас дисплей с общим катодом, делать нужно наоборот!
Подключим дисплей, как показано на схеме:
Схема будет выглядеть не так, как на картинке, если расположение выводов у дисплея отличается от моего, здесь просто нужно быть внимательным при подключении. Если дисплей с общим катодом, то com1 и com2 подключаются на питание!
Простая змейка на двойном 7-сегментном дисплее
Итак, зажигать циферки на односимвольном дисплее мы научились в прошлый раз, а сегодня мы будем рисовать змейку на двухсимвольном. Для начала сделаем простую змейку, которая состоит из трех сегментов и бегает по кругу.
Наш цикл будет состоять из восьми кадров, на каждом из которых будет зажигаться определенные три светодиода. На первом кадре будут гореть 1E, 1F, 1A (см. схему), на втором - 1F, 1A, 2A, на третьем - 1A, 2A, 2B и так далее, на восьмом - 1D, 1E, 1F.
Снова, для удобства, составим табличку байтов, помня, что по умолчанию биты передаются, начиная со старшего, т.е. 2h.
Кадр
|
1 abcd efgh
|
2 abcd efgh
|
hex
|
1
|
0111 0011
|
1111 1111
|
EC FF
|
2
|
0111 1011
|
0111 1111
|
ED EF
|
3
|
0111 1111
|
0011 1111
|
EF CF
|
4
|
1111 1111
|
0001 1111
|
FF 8F
|
5
|
1111 1111
|
1000 1111
|
FF 1F
|
6
|
1110 1111
|
1100 1111
|
7F 3F
|
7
|
1110 0111
|
1110 1111
|
7E 7F
|
8
|
1110 0011
|
1111 1111
|
7C FF
|
Ведущий должен выставить низкий (активный) уровень на проводе SS, совершить передачу двух байт, и отпустить провод. В этот момент произойдет защелкивание, в каждый регистр запишется по байту, загорятся два знака.
#include <SPI.h> //подключаем библиотеку SPI
enum { reg = 9 }; //выбираем линию SS регистра на 9-м пине Arduino
void setup(){
SPI.begin(); //инициализируем SPI
//переводим выбранный для передачи пин в режим вывода
pinMode(reg, OUTPUT);
}
void loop(){
//Заполняем массив байтами, которые будем передавать
static uint8_t digit[16] =
{0xFF,0xCE,0xFF,0xDE,0xFC,0xFE,0xF8,0xFF,
0xF1,0xFF,0xF3,0xF7,0xF7,0xE7,0xFF,0xC7};
//передаем по два байта из массива и защелкиваем регистры
for (int i=0;i<16;i+=2){
digitalWrite(reg, LOW);
SPI.transfer(digit[i]);
SPI.transfer(digit[i+1]);
digitalWrite(reg, HIGH);
delay(80); //пауза между кадрами
}
}
Видео работы программы:
Параллельные процессы в Arduino
Почему разработчики Arduino уделяют особое внимание примеру Blink without delay?
Обычно программа Arduino линейна - сначала делает одно, потом другое. В примере выше мы использовали функцию delay(80), чтобы каждый кадр рисовался через 80 миллисекунд после предыдущего. Однако ведь эти 80 миллисекунд процессор ничего не делает и никому не дает ничего делать! Для запуска двух и более параллельных процессов нам нужно поменять концепцию построения программы, отказавшись от delay().
Стержнем нашей новой конструкции станет таймер. Таймер будет считать время, а мы заставим происходить то или иное событие через определенные промежутки времени. Например, каждую секунду будет тикать дисплей с часами, а каждые 0,86 секунды будет мигать светодиод.
В Arduino есть штука, которая отсчитывает время с начала работы программы, называется она millis(). С ее-то помощью и организуется "распараллеливание" задач.
Итоговый проект: часы и хитрая змейка
Соберем такую схему:
Левый и средний регистры у нас работают с точки зрения ведущего как одно устройство, а правый регистр - как другое. Видно, что эти два устройства используют один и тот же провод SCLK (13-й пин Arduino, провод показан оранжевым) и MOSI (11-й пин, желтый цвет), SS используются разные (пины 8 и 9, зеленый цвет). Подключение 7-сегментных дисплеев к регистрам показано для моих конкретных моделей и, вероятно, не будет совпадать с вашим.
В этот раз сделаем нашу змейку более хитрой: она будет пробегать по всем сегментам так же, как ездил на мотоцикле по дорожной развязке волк в серии "Ну Погоди!", которая начинается с того, что он этот самый мотоцикл выкатывает из гаража и надевает каску.
Последовательность байтов для этой змейки будет такая:
static uint8_t snake[32] =
{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};
Теперь суть: функция millis() сидит и считает миллисекунды от начала начал. В начале каждого цикла loop мы запоминаем значение millis() в переменную timer. Заводим переменные snakeTimerPrev и digitTimerPrev, которые будут хранить в себе момент предыдущего события: для snakeTimerPrev - это включение предыдущего кадра анимации змейки, для digitTimerPrev - включение предыдущей цифры. Как только разница текущего времени (timer) и предыдущего (snakeTimerPrev или digitTimerPrev) становится равна заданному периоду (в нашем случае - 80 и 1000 мс, соответственно), мы производим передачу следующего кадра/байта.
Таким образом,
- каждые 80 мс контроллер будет опускать сигнал на линии SS двойного дисплея, передавать два байта и отпускать линию.
- каждую секунду контроллер будет опускать сигнал на линии SS одиночного дисплея, передавать один байт и отпускать линию.
Реализуем это на Arduino. Я уже все подробно описывал до этого, думаю, нет смысла комментировать.
#include <SPI.h>
enum { snakePin = 9, digitPin = 8 };
unsigned long timer=0, snakeTimerPrev=0, digitTimerPrev=0;
int i=0, j=0;
void setup(){
SPI.begin();
pinMode(digitPin, OUTPUT);
pinMode(snakePin, OUTPUT);
}
void loop(){
static uint8_t digit[16] =
{0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,
0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E};
static uint8_t snake[32] =
{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};
timer=millis();
if (timer-snakeTimerPrev>80){
digitalWrite(snakePin, LOW);
SPI.transfer(snake[j]);
SPI.transfer(snake[j+1]);
digitalWrite(snakePin, HIGH);
j<30 ? j+=2 : j=0;
snakeTimerPrev=timer;
}
if (timer-digitTimerPrev>1000){
digitalWrite(digitPin, LOW);
SPI.transfer(digit[i]);
digitalWrite(digitPin, HIGH);
i<9 ? i++ : i=0;
digitTimerPrev=timer;
}
}
enum { snakePin = 9, digitPin = 8 };
unsigned long timer=0, snakeTimerPrev=0, digitTimerPrev=0;
int i=0, j=0;
void setup(){
SPI.begin();
pinMode(digitPin, OUTPUT);
pinMode(snakePin, OUTPUT);
}
void loop(){
static uint8_t digit[16] =
{0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,
0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E};
static uint8_t snake[32] =
{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};
timer=millis();
if (timer-snakeTimerPrev>80){
digitalWrite(snakePin, LOW);
SPI.transfer(snake[j]);
SPI.transfer(snake[j+1]);
digitalWrite(snakePin, HIGH);
j<30 ? j+=2 : j=0;
snakeTimerPrev=timer;
}
if (timer-digitTimerPrev>1000){
digitalWrite(digitPin, LOW);
SPI.transfer(digit[i]);
digitalWrite(digitPin, HIGH);
i<9 ? i++ : i=0;
digitTimerPrev=timer;
}
}
Вот я использовал выражение j<30 ? j+=2 : j=0; Кто забыл, это логическое выражение типа А ? B : C, оно означает: ЕСЛИ А, ТО B, ИНАЧЕ C. Крайне полезная штука!
Ну и вкусное виде о том, что получилось:
Далее по теме Arduino и SPI
Комментариев нет:
Отправить комментарий