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

 
 
 

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

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

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

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

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); // ждем устаканивания питания

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

}

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

На картинке изображена форма тактирующего сигнала 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). К сожалению, не оказалось в Протеусе модели/прототипа моей микросхемы памяти. Просто подключил виртуальный осциллограф, чтобы форму сигналов посмотреть:
Программная реализация протокола SPI на AVR в CodeVisionAVR

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

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

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


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

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


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

Эх, даже ничего не сломалось и не сгорело в этот раз! smile
Спасибо за внимание!

Об авторе

Павел Сумароков (Electronik83)
РФ, Северодвинск
Радиоэлектроника, программирование.
 

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

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

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


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


Kaspersky FREE. Бесплатная годовая лицензия для России, Белоруссии и Украины

Привет, друзья! Очередной подарок от kaspersky.ru — антивирус «Kaspersky FREE». Спасибо...

Блок управления вытяжным вентилятором. Наладка, результаты

Всем здоровья! Утюг починен. Хочу коротко отчитаться в проделанной работе и результатах...

Пусть всегда будет солнце!

Сегодня День Победы. Мы так долго живём без войны, что начали забывать, что это...

Доработанный вариант малошумящего двухполярного источника питания

Здравствуйте, коллеги! Размещаю дополнение к статье «Малошумящий двухполярный блок питания...

17/01 киберсубботник на портале

Привет, дрУги! В ближайщую субботу на нашем портале и в магазине будет...

Библиотеки Diptrace +3D. Разъёмы PBS (розетки)

Продолжая тему библиотек для Diptrace, хочу предложить розетки однорядные PBS для пайки...

Схемка в блокнот. Индикатор разрядки аккумуляторной батареи

При выездах и эксплуатации радиоаппаратуры от аккумуляторных батарей в полевых...

Toshiba - это хорошо! Новейший каталог 4Q2012

Предлагаю вниманию сограждан новейший каталог Toshiba Bipolar Power Transistors — 4 квартал...

Датагорскiя Вѣдомости №1 (2012)

Здравствуйте, уважаемые сограждане-датагорцы и гости нашего электронного города! Спешу...

Transformer: программа расчета маломощного (до 500 Вт) силового трансформатора на

Предлагаю начинающим очень простую программулину для расчета силовых трансформаторов. Автор...

Датагорская Ярмарка электроники v.2 открылась!

Уважаемые граждане Датагории и гости нашего кибер-города! После обновления открылась...

Узел коммутации обмоток выходных трансформаторов ламповых усилителей

Последнее время все мучил меня вопрос о быстрой и безболезненной коммутации вторичных...
<
  • Подписчик
15 сентября 2016 08:51

Игорь / StalKer-NightMan

  • Регистрация: 15.03.2012
  • Публикаций: 1
  • Комментариев: 84
 
  • 0
Спасибо за информацию с примерами.

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

Радик / galrad

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

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

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

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

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