Урок 31
Часть 1
Связь ATtiny2313 и Atmega8 по SPI
Продолжаем разговор о шине SPI и, как я уже обещал, начнём дружить между собой два контроллера — ATtiny2313 и Atmega8.
Во-первых, я ещё вот что хочу сказать.
Я изучил даташит ATtiniy2313 и увидел то, что у данного МК как таковой шины SPI нет. Есть подобие данной шины — её заменитель — USI (Universal Serial Interface).
Ну в принципе-то по большому счёту это одно и тоже, но различия всё же есть.
Ну давайте для полного прикола объявим нашу ATTiny ведущим устройством и будем каким-то образом всё это дело сочинять.
Посмотрим схему модуля USI
Вообщем, очевидная разница хотя бы в том, что как аппаратного тактирования в модуле нет, так как там нет даже генератора, есть только модуль, управляющий тактированием.
В качестве генератора тактовых импульсов можно использовать нулевой таймер, либо применить подключенный по определённой ножке внешний осцилятор, либо, как мы с вами поступим, мы будем весь этот процесс проводить в цикле.
Также можно здесь настроить режим SPI с помощью определённого бита
Вообщем-то здесь мы видим, что процесс не сильно отличается от того же процесса на Atmega, единстенное нет ещё одного бита. Но это всё равно не отнимает у нас права пользоваться данной шиной для приёма и передачи, просто будут только 2 режима вместо 4.
А все регистры мы будем смотреть уже по мере написания кода.
Теперь схема в протеусе (нажмите на картинку для увеличения изображения)
Здесь стандартная схема на ATtiny и стандартная схема на ATmega с подключенными дисплеями (смотреть урок 12 и урок 30). Единственное различие, что все информационные входы, отвечающие за интерфейс SPI между собою соединены следующим образом
ATTiny2313 ATmega8
MOSI MOSI
MISO MISO
USCK SCK
PB4 SS
Ну и ещё подключен виртуальный осциллограф, чтобы смотреть, как передаются биты.
Сначала создадим проект для ATtiny. Имя мы ему присвоим ATTINY_SPI, а код весь возьмём с прошлого занятия.
Затем также создадим проект для Atmega8 и назовём его ATMEGA_SPI. Код для данного проекта использован будет из проекта урока 12. А библиотека для дисплея была взята с урока по часам, так как она там уже более дополненная. А код функции setpos в lcd.c был взят из проекта по подключению дисплея через переходник, так как дисплей мы подключили 4-строчный, чтобы у нас работали все 4 строки. Ну и допишем код для теста нижних строк в main()
setpos(2,1);
str_lcd(«String 2»);
setpos(4,2);
str_lcd(«String 3»);
setpos(6,3);
str_lcd(«String 4»);
Соберём коды обоих проектов и посмотрим в протеусе, у нас должны работать оба дисплея
Пока начнём писать код в проекте ATTINY_SPI.
Первым делом добавим переменную в функции main() для того, чтобы где-то хранить наш байт
int main(void)
{
unsigned char n=0;//переменная для случайного числа
Начнём писать теперь функцию для инициализации шины SPI. Мы её не будем называть USI, а будем использовать стандартное привычное название
//—————————————-
void SPI_init(void)
{
DDRB|=((1<<PORTB4)|(1<<PORTB6)|(1<<PORTB7));//Ножки USI на выход
DDRB&=~(1<<PORTB5);//Ножка DI на выход
PORTB&=~((1<<PORTB4)|(1<<PORTB6)|(1<<PORTB7));//Ножки USI в низкий уровень
}
//—————————————-
Из кода функции и комментариев мы видим, что для инициализации шины мы не используем вообще никакие регистры, кроме регистров портов общего назначения, настроив в них определённые направления и значения.
Вызовем данную функцию в main()
LCD_ini(); //Инициализируем дисплей
SPI_init(); //Инициализируем шину
Также для красоты между выводом строк, а также в бесконечный цикл вставим небольшие задержки
str_lcd(«Hello World!»);
_delay_ms(1000);
setpos(2,1);
str_lcd(«String 2»);
_delay_ms(1000);
while(1)
{
_delay_ms(1000);
Передавать мы будем случайное число, поэтому сгенерируем его в бесконечном цикле, используя функцию rand(), предварительно очистив дисплей и установив указатель на начало координат
while(1)
{
clearlcd();
setpos(0,0);
n= (unsigned char) rand() % 256;
_delay_ms(1000);
То есть у нас будет генерироваться случайное число от 0 до 255, что с успехом влезет в передаваемый байт.
Создадим функцию для передачи байта в SPI над функцией main(), чтобы не писать прототип
//—————————————-
void SPI_SendByte(char byte)
{
}
//—————————————-
Теперь идём в даташит и смотрим, куда же мы должны положить байт для передачи по SPI или USI. Оказывается для этого есть вот такой регистр
Ну и давайте туда и положим байт
void SPI_SendByte(char byte)
{
USIDR = byte; //данные в регистр
Теперь нам нужно как то заставить контроллер начать передавать данные
Для этого есть вот такой статусный регистр
А в данном регистре есть байт USIOIF, который является флагом начала передачи, не взирая на то, что регистр статусный
Поэтому напишем следующий код в нашу функцию
USIDR = byte; //данные в регистр
USISR |= (1<<USIOIF);//установим флаг начала передачи
Только данное действие не начнёт само собой передавать данные, так как нет тактирования. Здесь будет условный цикл
while(!(USISR & (1<<USIOIF)))
{
}
В данном цикле мы будем отслеживать сброс в ноль того же бита в том же регистре.
Теперь самое интересное — тело данного цикла. Тут не всё так просто.
Нужно будет немного ознакомиться со следующим регистром
Это уже полноправный регистр управления. В нём существуют следующие биты
USISIE (Start Condition Interrupt Enable) — бит разрешения прерываний.
USIOIE (Counter Overflow Interrupt Enable) — флаг для прерываний по переполнению счётчика.
USIWM1..0: (Wire Mode) — биты режима. За счёт того, что этих бита два, то мы можем включить один из четырёх режимов:
То есть так как шина USI она универсальная, может выступать не только как SPI, а и как I2C, но нас будет интересовать именно двухпроводной SPI, поэтому мы выберем режим второй, то есть включать будем бит USIWM0.
USICS1..0 (Clock Source Select) — биты установки варианта тактирования шины. Работают совместно со следующим битом.
USICLK (Clock Strobe) — бит стробирования регистра сдвига.
Вот таблица вариантов трёх вышеуказанных битов
Нас будет интересовать вариант шестой — программное тактирование и программное управление регистром сдвига.
USITC (Toggle Clock Port Pin) — бит переключения порта. В случае если контроллер является ведущим устройством, то включается в 1.
Таким образом, в нашей функции мы установим следующие биты управляющего регистра
while(!(USISR & (1<<USIOIF)))
{
USICR |= ((1<<USIWM0)|(1<<USICS1)|(1<<USICLK)|(1<<USITC));//постепенно передаем байт
Так как у нас всё происходит в цикле, то для обеспечения не слишком большой частоты нам нужно будет установить задержку
USICR |= ((1<<USIWM0)|(1<<USICS1)|(1<<USICLK)|(1<<USITC));//постепенно передаем байт
_delay_us(10);
}
Может быть ATmega примет и с большей скоростью и задержка нам будет не нужна, но, во-первых, мы тогда очень сильно загрузим контроллер, а также нам тяжело будет с такой скоростью отследить процесс в протеусе на нашем виртуально осциллографе.
Вернёмся в бесконечный цикл и вызовем нашу новую функцию для того, чтобы передать байт ведомому устройству
n= (unsigned char) rand() % 256;
SPI_SendByte(n);
Затем нам нужно будет отобразить наш отправляемый байт на дисплее, для этого преобразуем его сначала в строку. Для этого кроме функции sprinf существует функция itoa.
SPI_SendByte(n);
itoa(n,str,10);
Первым входным аргументом в данной функции является преобразовываемая целочисленная величина, вторым — указатель на строку, а третьим — система исчисления. Так как мы будем выводить показания в десятичном виде, то у нас будет 10.
Затем мы этот байт непосредственно отобразим на дисплее
itoa(n,str,10);
str_lcd(str);
Соберём код и запустим его в протеусе. Посмотрим сначала отображение на дисплее
Байты нормально отображаются.
Теперь смотрим на осциллограмму и видим, что там также всё нормально
Посмотрим как именно передаётся байт. Зелёная осциллограмма показывает тактовые импульсы, а розовая — непосредственно шину передачи байта. Я дождался, когда будет байт попроще. У нас значение 5, если перевести в двоичный формат, то это 00000101. Так оно и есть на осциллограмме. Когда идут первые пять тактовых импульса, то шина передачи у нас находится в нуле, как только пятый импульс у нас заканчивается, то по его спадающему фронту у нас устанавливается шина передачи в единицу, чтобы во время возрастающего фронта шестого импулься она в этой единице находилась, так оно и есть. Затем по спаду шестого импульса шина передачи сбрасывается в ноль, а по спаду седьмого импульса, она поднимается.
Вообщем, я думаю теперь нам стал более понятен протокол передачи данных по шине SPI.
Теперь посмотрим все соединения на макетной плате, касающиеся контроллера ATtiny2313
Всё у нас практически как в прошлом занятии, только мы ещё можем наблюдать, что у нас от платы куда-то отходят четыре провода. Это не что иное, как провода шины SPI для соединеия с другим контроллером, и также общий провод. Общие провода должны быть соединены. Хотя, возможно, будет работать и без этого, так как у нас шина USB подключена к программаторам от одного ПК. А вообще будет не так-то это всё просто, так как avrdude не будет знать, какой именно контроллер ей прошивать.
Попробуем прошить наш контроллер. Но прежде чем это делать, мы выберем в avrdude проект, считаем конфигурационные биты. Но главное не в этом. Ещё немаловажная информация! Перейдём на вкладку с фьюзами, считаем их и снимем вот этот вот бит, иначе у нас будет слишком маленькая скрость
Всё-таки он для 8 МГц не нужен, мы зря его установили на прошлом занятии.
Прошьём контроллер, и посмотрим
У нас всё нормально работает, только работу шины SPI мы сейчас не оценим, так как нам это отследить негде. Мы ещё не подключали другой контроллер, а настоящего осциллографа у меня, к сожалению нет, также нет даже логического анализатора. Возможно, в недалёком будущем всё это будет. Но пока вот так. Мы только видим, что переданные байты отображаются у нас на дисплее. А другим контроллером, а также дружбой между первым и вторым контроллером мы займёмся уже в следующей части нашего такого вот интересного занятия.
Предыдущий урок Программирование МК AVR Следующая часть
Программатор и дисплеи можно приобрести здесь:
Программатор USBASP USBISP с адаптером USBASP USBISP 3.3 с адаптером
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
В тексте урока опечатка.
Должно быть как на схеме:
ATTiny2313 Atmega8
MOSI MISO
MISO MOSI