» » » Программная реализация протокола SPI на AVR в CodeVisionAVR

 
 
 
3

Программная реализация протокола SPI на AVR в CodeVisionAVR

Разместил Electronik83 15 сентября 2016. Просмотров: 3 659

Всем коллегам и согражданам привет!
Увлёкся я изучением протоколов. Про реализацию протокола I2C у меня уже была статья на Датагоре. Сегодня поговорим о протоколе SPI. Сразу к делу. Заглянем в Wiki за ёмким определением.
SPI (англ. Serial Peripheral Interface, SPI bus — последовательный периферийный интерфейс, шина SPI) — последовательный синхронный стандарт передачи данных в режиме полного дуплекса, предназначенный для обеспечения простого и недорогого высокоскоростного сопряжения микроконтроллеров и периферии. SPI также иногда называют четырёхпроводным (англ. four-wire) интерфейсом.

В отличие от стандартного последовательного порта (англ. standard serial port), SPI является синхронным интерфейсом, в котором любая передача синхронизирована с общим тактовым сигналом, генерируемым ведущим устройством (процессором). Принимающая (ведомая) периферия синхронизирует получение битовой последовательности с тактовым сигналом. К одному последовательному периферийному интерфейсу ведущего устройства-микросхемы может присоединяться несколько микросхем. Ведущее устройство выбирает ведомое для передачи, активируя сигнал «выбор кристалла» (англ. chip select) на ведомой микросхеме. Периферия, не выбранная процессором, не принимает участия в передаче по SPI.
В реализации протокола SPI используют четыре цифровых линии для передачи сигналов (в скобках даны вариации обозначений):

CLK (CLOCK, SCLK) — по этой линии передаются тактовые импульсы для ведомых устройств.
DO (MISO, SDO, DO, DOUT, SO) — вход данных МК, эта линия нужна для приема данных от периферии.
DI (MOSI, SDI, DI, DIN, SI) — выход данных МК, передаём данные к ведомому устройству.
CS (SS, nCS, CS, CSB, CSN) — выбор микросхемы, выбор ведомого, в народе «чипселект».
Выводов CS может быть несколько, и количество их соответствует тому количеству устройств, с которыми мы хотим общаться на шине.
Хочу особо отметить, что в большинстве случаев этот сигнал (CS) инвертирован. Т.е. если на нем логический «0» — устройство выбрано и с ним можно работать. И наоборот, «1» говорит, что мы не хотим общаться с этим устройством.

С теорией вроде как всё, переходим к практике. Далее раcсказ пойдет только об одном ведомом устройстве на шине SPI. Подключать сразу несколько устройств пока не было необходимости.
Забегая вперед, скажу: для каждого нового устройства на шине нужно будет добавить две небольшие процедуры начала/конца общения.

Я, как обычно, для тестов использую Arduino Nano. Под неё и пишу.

Распишем для начала все дефайны (#define):
// Chip type ATmega328P at 16 MHz
// Пример реализации протокола SPI
// Для примера использована память MX25L8005
#include   // нужно для атмегушки
#include      // нужно для задержек
// пропишем пины
#define spi_cs      PORTB.0 // это D8  ардуино
#define DDR_spi_cs  DDRB. 0
#define spi_do      PINB. 1 // это D9  ардуино
#define DDR_spi_do  DDRB. 1
#define spi_di      PORTB.2 // это D10 ардуино
#define DDR_spi_di  DDRB. 2
#define spi_clk     PORTB.3 // это D11 ардуино
#define DDR_spi_clk DDRB. 3
// задержка
#define SPI_time 10


Сразу можно прописать инициализацию портов:
// главная функция типа:)
void main () {
  DDRB.5=1; // для тестовой лампочки
    // инициализация линий.....
  DDR_spi_clk = 1; // на выход
  DDR_spi_do  = 0; // на вход
  DDR_spi_di  = 1; // на выход
  DDR_spi_cs  = 1; // на выход
  spi_cs=1;        // задираем cs
  delay_us(100); // ждем устаканивания питания

  // тут напишем потом

}

Далее про тактирование и передачу данных. Приведу для примеру картинку, которая раскрывает некоторые понятия, чтоб можно было проще воспринимать коментарии в коде.

На картинке изображена форма тактирующего сигнала CLK. Теперь нужно передать один байт в шину. Тут ничего сложного нет:
// пишем байт в шину
void SPI_wr(char byte) {
  char i;                 // счетчик для цикла
  for (i=0; i<8; i++) {   // цикл на 8 бит
    delay_us(SPI_time/2); // формируем половину паузы
    if (byte & 0x80) spi_di=1; else spi_di=0; // выставляем один бит
    byte<<=1;                                 // двигаем байт 
    delay_us(SPI_time/2); // завершаем паузу
    spi_clk = 1;          // тактовый фронт
    delay_us(SPI_time);   // формируем импульс
    spi_clk = 0;          // тактовый спад
  }
}


Дальше надо научиться принимать байт:
// читаем байт из шины
unsigned char SPI_rd() {
  char i, byte=0;         // переменные для счетчика и байта
  for (i=0; i<8; i++) {   // цикл на 8 бит
    delay_us(SPI_time);   // формируем паузу
    spi_clk = 1;          // формируем фронт
    delay_us(SPI_time/2); // половина импульса    
    if (spi_do) byte++;   // читаем бит и записываем его
    if (i!=7) byte<<=1;   // двигаем байт
    delay_us(SPI_time/2); // завершаем импульс
    spi_clk = 0;
  }
  return byte;            // вернули, что прочли
}


Линии CLK, DI и DO теперь у нас работают нормально. Не стоит забывать и про линию CS. Многие устройства по этой линии начинают и заканчивают процедуру обмена данными. Пропишем:
// начинаем общение
void spi_go() {
  delay_us(SPI_time);  
  spi_clk=0;
  spi_cs =0; 
  delay_us(SPI_time);  
}
// заканчиваем общение
void spi_end() {
  delay_us(SPI_time);
  spi_cs = 1;
  delay_us(SPI_time);
}


У нас готовы основные функции работы с протоколом. Теперь, чтобы передать пару байт в шину, необходимо выполнить четыре функции. Пишу чисто для примера, в основной код это не включается:
  spi_go();     // говорим устройству, что начинаем общаться с ним
  spi_wr(0xAA); // записали в шину байт 0xAA
  spi_wr(0x55); // записали в шину байт 0x55
  spi_end();    // завершили процедуру обмена данными

Как оказалось, ничего сложного нет.

Теперь надо придумать, на чём всё это дело проверить. Порывшись в своих запасах, нашёл микросхему памяти MX25L8005, да ещё и в удобном DIP8 корпусе. Данный чип представляет собой память с интерфейсом SPI на целый мегабайт.

Тест будем проводить по светодиоду, припаянному к D13 ножке Arduino. Алгоритм такой: мы просто считаем ID данной микросхемы и если он совпадет с тем, что указано в даташите, мы включаем тестовый светодиод.
Т.е. передав четыре байта 0×90, 0×00, 0×00 и 0×00 мы должны прочитать с шины два байта, а именно 0xC2 и 0×13. Данный алгоритм охватит все процедуры и функции, описанные ранее. И так, приступим:

  while(1) {
    char t=0;
    delay_ms(100); // ждем немного
    PORTB.5=0; // гасим тестовую лампочку
    // читаем ID микросхемы памяти
    spi_go(); spi_wr(0x90); spi_wr(0x00); spi_wr(0x00); spi_wr(0x00); 
    if (spi_rd()==0xC2) t++; // сразу сравниваем с нужным
    if (spi_rd()==0x13) t++; // сразу сравниваем с нужным
    spi_end();
    if (t==2) PORTB.5=1;  // включаем тестовую лампочку
  } 


Далее закидываем наш исходный код в CodeVision, компилируем и смотрим возможные ошибки. Сразу без ошибок у меня редко выходит написать, но с опытом их становится всё меньше. Вышло три ошибки/опечатки.

Теперь тестим это дело в Протеусе (ISIS 7 Proteus). К сожалению, не оказалось в Протеусе модели/прототипа моей микросхемы памяти. Просто подключил виртуальный осциллограф, чтобы форму сигналов посмотреть:

На картинке видно, что все сигналы около дела.

Обнаружил один недочет — забыл задрать в самом начале чипселект CS. Он ведь инверсный. Поправил.

Теперь настала пора подумать над схемой включения. Из дефайнов видно, что CLK — D8, DO — D9, DI — D10, CS — D11. Нарисовал схему:


Переходим к тесту в железе. Воткнул в беспаечную макетную плату (breadboard) платку ARDUINO NANO (всё ещё пахнет недавним горелым диодом) и микросхему MX25L8005, соединил всё проводками. Теперь долгожданный момент — включаем и… И забыл прошить!

Открываю мой любимый Xloader, заливаю прошивку. И вижу, что светодиод сразу загорелся.


Для проверки менял значения 0xC2 и 0×13 в коде на другие. Результат совпал с ожидаемым — светодиод не загорался.
Вывод. Данный код можно считать полностью рабочим и положить в папку примеров.

Эх, даже ничего не сломалось и не сгорело в этот раз! smile
Спасибо за внимание!
Павел Сумароков (Electronik83)
РФ, Северодвинск
Профиль Electronik83
Радиоэлектроника, программирование.
 

Понравилось? Палец вверх!

  • всего лайков: 25

Поделись с друзьями!

Связанные материалы:


Схема на Датагоре. Новая статья Программная реализация протокола I2C на AVR в CodeVisionAVR... Пару лет назад, изучая Atmega8, я захотел программно реализовать работу с устройствами на шине i2c,...
Схема на Датагоре. Новая статья Беспроводной канал связи 2,4 ГГц на базе трансивера nRF24L01+ от Nordic Semiconductor. Часть 1... Доброго вам дня, уважаемые граждане и гости Датагор.ру - этого замечательного сообщества...
Схема на Датагоре. Новая статья Предварительный усилитель на аудиопроцессоре TDA7318 (TDA7313) и Arduino. Часть 1... Благодарим Тимофея Носова за идею проекта на TDA7318. При построении усилителя звуковой частоты,...
Схема на Датагоре. Новая статья Несколько функций для програмной реализации протокола I2C на AVR... Добрый день, дорогие друзья! Решил поделиться с вами несколькими функция для работы по протоколу...
Схема на Датагоре. Новая статья Умный дом. Концепция... Что мы имеем в виду, называя дом «умным»? На самом деле, у каждого свое представление об этой...
Схема на Датагоре. Новая статья Немного про шину 1-Wire и цифровой термометр DS18b20... Фотка от www.150cc.ru Здравствуйте, друзья. Хочу предложить вашему вниманию несколько простых...
Схема на Датагоре. Новая статья Универсальный контроллер управления 7-сегментными LED индикаторами по двум проводам (Atmega16)... Занялся я конструированием нового устройства и встал вопрос — на чем отображать данные....
Схема на Датагоре. Новая статья Универсальная "прозвонка" на замыкание и размыкание с памятью, звуковой и световой сигнализацией... Известно, что радиотехника – наука о контактах. Неисправность – это наличие контакта там, где его...
Схема на Датагоре. Новая статья Програмирование в AVR Studio 5 с самого начала. Часть 6... Продолжим разбор теоретических основ, без которых невозможно полноценное создание программ....
Схема на Датагоре. Новая статья Грызем микроконтроллеры. Урок 4.... Мигалки – это хорошо, по новогоднему… Но ведь нельзя останавливаться на достигнутом! Пора сделать...
Схема на Датагоре. Новая статья Грызем микроконтроллеры. Урок 2.... Предлагаю продолжить изучение микроконтроллеров… Второй урок будет посвящен по большей части...
Схема на Датагоре. Новая статья TDA7442D+ATmega8. Микропроцессорный регулятор громкости... Привет ВСЕМ!!! Интересно, кому бы не хотелось, чтобы его усь обладал сервисом промышленных...
<
  • Подписчик
15 сентября 2016 08:51

Игорь / StalKer-NightMan

  • С нами с 15.03.2012
  • Ушёл в реал Пользователь offline
  • 90 комментариев
  • 1 публикация
 
  • 0
Спасибо за информацию с примерами.

<
  • Гражданин
16 сентября 2016 11:49

Радик / galrad

  • С нами с 23.08.2011
  • Ушёл в реал Пользователь offline
  • 84 комментария
  • 12 публикаций
 
  • 0
Павел, спасибо за очередную статью! Ради интереса перенес примеры в Atmel Studio и откомпилировал примеры (с некоторыми поправками) все работает! Как я уже говорил, протокол SPI не самый сложный, но наиболее часто используемый из-за высокой скорости передачи данных и небольшого объема кода!
За статью Респект! handshake

<
  • Кандидат
21 сентября 2016 04:27

Павел Сумароков / Electronik83

  • С нами с 29.08.2015
  • Ушёл в реал Пользователь offline
  • 4 комментария
  • 2 публикации
 
  • 0
Спасибо, Радик. Я, и не только я : МЫ (с Игорем) будем делать и стараться делать только качественные статьи дальше, чтобы любой, даже не очень подготовленный читатель смог повторить, что я пишу в коде в своих статьях.

Информация
Вы не можете участвовать в комментировании. Вероятные причины:
— Администратор остановил комментирование этой статьи.
— Вы не авторизовались на сайте. Войдите с паролем.
— Вы не зарегистрированы у нас. Зарегистрируйтесь.
— Вы зарегистрированы, но имеете низкий уровень доступа. Получите полный доступ.