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

 
 
 

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

📆15.09.16 🙋Electronik83 👀12 435 💬4
Всем коллегам и согражданам привет!
Увлёкся я изучением протоколов. Про реализацию протокола 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
Радиоэлектроника, программирование.
 

Читательское голосование

Нравится

Статью одобрили 29 читателей.

Для участия в голосовании зарегистрируйтесь и войдите на сайт с вашими логином и паролем.
 

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

 

 

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

 

Схема на Датагоре. Новая статья Использование МК ATMega163, ATMega163L, ATMega16 в Arduino IDE... Популярная среда разработки Arduino IDE привлекает большим количеством готовых библиотек и...
Схема на Датагоре. Новая статья Визуализация для микроконтроллера. Часть 4. Android... Вообще то я планировал рассказать сегодня про дисплей на базе ILI9481. Однако, он настолько похож...
Схема на Датагоре. Новая статья Программная реализация протокола I2C на AVR в CodeVisionAVR... Пару лет назад, изучая Atmega8, я захотел программно реализовать работу с устройствами на шине i2c,...
Схема на Датагоре. Новая статья Беспроводной канал связи 2,4 ГГц на базе трансивера nRF24L01+ от Nordic Semiconductor. Часть 1... Доброго вам дня, уважаемые граждане и гости Датагор.ру - этого замечательного сообщества увлечённых...
Схема на Датагоре. Новая статья Несколько функций для програмной реализации протокола I2C на AVR... Добрый день, дорогие друзья! Решил поделиться с вами несколькими функция для работы по протоколу...
Схема на Датагоре. Новая статья Умный дом. Концепция... Что мы имеем в виду, называя дом «умным»? На самом деле, у каждого свое представление об этой...
Схема на Датагоре. Новая статья Немного про шину 1-Wire и цифровой термометр DS18b20... Здравствуйте, друзья. Хочу предложить вашему вниманию несколько простых функций для работы с шиной...
Схема на Датагоре. Новая статья Универсальный контроллер управления 7-сегментными LED индикаторами по двум проводам (Atmega16)... Занялся я конструированием нового устройства и встал вопрос — на чем отображать данные....
Схема на Датагоре. Новая статья Универсальная "прозвонка" на замыкание и размыкание с памятью, звуковой и световой сигнализацией... Известно, что радиотехника – наука о контактах. Неисправность – это наличие контакта там, где его...
Схема на Датагоре. Новая статья Програмирование в AVR Studio 5 с самого начала. Часть 6... Продолжим разбор теоретических основ, без которых невозможно полноценное создание программ....
Схема на Датагоре. Новая статья Грызем микроконтроллеры. Урок 4.... Мигалки – это хорошо, по новогоднему… Но ведь нельзя останавливаться на достигнутом! Пора сделать...
Схема на Датагоре. Новая статья TDA7442D+ATmega8. Микропроцессорный регулятор громкости... Привет ВСЕМ!!! Интересно, кому бы не хотелось, чтобы его усь обладал сервисом промышленных...
 

Общаемся по статье 💬

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

Комментарии, вопросы, ответы, дополнения, отзывы

 

<

StalKer-NightMan

Игорь Читатель Датагора
  • Гражданин
Комментарий # 1 от 15-09-16, 8:51.
Ответить
  • С нами с 15.03.2012
  • 96 комментариев
  • 1 публикация
 
Спасибо за информацию с примерами.

<

galrad

Радик Читатель Датагора
  • Гражданин
Комментарий # 2 от 16-09-16, 11:49.
Ответить
  • С нами с 23.08.2011
  • 95 комментариев
  • 12 публикаций
 
Павел, спасибо за очередную статью! Ради интереса перенес примеры в Atmel Studio и откомпилировал примеры (с некоторыми поправками) все работает! Как я уже говорил, протокол SPI не самый сложный, но наиболее часто используемый из-за высокой скорости передачи данных и небольшого объема кода!
За статью Респект! handshake

<

Electronik83

Павел Сумароков Читатель Датагора
  • Гражданин
Комментарий # 3 от 21-09-16, 4:27.
Ответить
  • С нами с 29.08.2015
  • 4 комментария
  • 2 публикации
 
Спасибо, Радик. Я, и не только я : МЫ (с Игорем) будем делать и стараться делать только качественные статьи дальше, чтобы любой, даже не очень подготовленный читатель смог повторить, что я пишу в коде в своих статьях.

<

Алексей

Читатель Датагора
  • Прохожий
Комментарий # 4 от 07-06-17, 15:43.
Ответить
  • С нами с --
  • 0 комментариев
  • 0 публикаций
 
Спасибо за статью. Все подробно, понятно, а главное работает. Но уже неделю я не могу справится с программным SPI в качестве Slave для attiny2313. Обшарил весь интернет, но примеров на С вообще не нашел. Для мастера - есть много чего, а для раба - нет. Использовать MOSI-MISO не могу, т.к. порт В занят. Свободна младшая половина D. Городил огород на прерывании NIT0 для фиксации синхроимпульса и записи в буфер информации с входа, но так и не добился работы. Нужна реализация одновременной передачи и чтения. Помогите кодом (желательно для Студии) или ткните носом, если такой уже есть. Без общественного разума чувствую не справлюсь!

Добавить комментарий, вопрос, отзыв 💬

Камрады, будьте корректны и вежливы, соблюдайте правила!


Налетай! Паяльники, станции, жала с доставкой

Офигенная миниатюрная рация на литии BaoFeng UV-5R, 5W, 5-15 км!

Тестер универсальный LCR-TC1 для транзисторов, конденсаторов, ESR, MOSFET и т.д.
  • smilelolhellowinkscepticthumbupbored
    crazybadcryingsadirefulsickstraight
    ballooncakegooddrinksmailbombsun
    nightrainstarscolddashpartyhandshake
    musicnegativenowordspardonshoksleepunknown
    wackoyawnsainthelmethashsmokingwhew


Скопируйте текст вашего комментария на случай неверного ответа на контрольный вопрос.