Учебный курс AVR. Работа с SPI модулем. Чтение и запись данных. Ч2

22/10/2012 - 17:23 Павел Бобков

   Теперь вы имеете общее представление о последовательном периферийном интерфейсе и можно перейти к рассмотрению SPI модуля. 
   SPI модуль микроконтроллера AVR atmega16 использует для своей работы 4 вывода - MOSI, MISO, SCK и SS. Когда модуль не задействован, эти выводы являются линиями портов ввода/вывода общего назначения. Когда модуль включен, режим работы этих выводов переопределяются согласно следующей таблице.

 
   Если к микроконтроллеру подключено больше одного периферийного устройства, в качестве дополнительных выводов выбора (SS), можно использовать любые выводы общего назначения. При этом штатный вывод SS должен быть всегда правильно сконфигурирован, даже если он не используется.

Регистры SPI модуля

   В микроконтроллере atmega16 для работы с модулем SPI используются три регистра:

- управляющий регистр SPCR,
- статусный регистр SPSR, 
- регистр данных SPDR.

   Все три регистра восьмиразрядные.

   Кофигурация модуля SPI устанавливается с помощью регистра SPCR (SPI Control Register).




SPIE – разрешает /запрещает прерывания от модуля SPI. Если бит установлен в 1, прерывания от SPI разрешены. 

SPE – включает/выключает модуль SPI. Если бит установлен в 1, модуль SPI включен.

DORD – определяет порядок передачи данных. Когда бит установлен в 1, содержимое регистра данных передается младшим битом вперед. Когда бит сброшен, то старшим битом вперед.

MSTR – определяет режим работы микроконтроллера. Если бит установлен в 1, микроконтроллер работает в режиме Master (ведущий). Если бит сброшен – в режиме Slave (ведомый). Обычно микроконтроллер работает в режиме master.

CPOL и CPHA – определяют в каком режиме работает SPI модуль. Требуемый режим работы зависит от используемого периферийного устройства.


SPR1
и SPR0 – определяют частоту тактового сигнала SPI модуля, то есть скорость обмена. Максимально возможная скорость обмена всегда указывается в спецификации периферийного устройства.


   Статусный регистр SPSR (SPI Status Register) предназначен для контроля состояния SPI модуля
, кроме того он содержит дополнительный бит управления скоростью обмена.


SPIF
– флаг прерывания от SPI. Он устанавливается в 1 по окончании передачи байта данных. Если разрешены прерывания модуля, одновременно с установкой этого флага генерируется прерывание от SPI. Также этот флаг устанавливается в 1 при переводе микроконтроллера из режима master в режим slave с помощью вывода SS.
   Сброс флага происходит аппаратно, при вызове подпрограммы обработки прерывания или после чтения регистра SPSR с последующим обращением к регистру данных SPDR.

WCOL- флаг конфликта записи. Флаг устанавливается в 1, если во время передачи данных выполняется попытка записи в регистр данных SPDR. Флаг сбрасывается аппаратно после чтения регистра SPSR с последующим обращением к регистру данных SPDR.

SPI2X — бит удвоения скорости обмена. Установка этого разряда в 1 удваивает частоту тактового сигнала SCK. Микроконтроллер при этом должен работать в режиме master.

Взаимосвязь между битами SPR0, SPR1, SPI2X и частотой тактового сигнала SCK показана в таблице. 


Где Fosc — тактовая частота микроконтроллера AVR.


   Для передачи и приема данных предназначен регистр SPDR (SPI Data Register)
. Запись данных в этот регистр инициирует передачу данных SPI модулем. При чтении этого регистра, считывается содержимое буфера сдвигового регистра SPI модуля.

   Программный код

   Минимальный программный код для работы с SPI модулем состоит из двух функций:

- функции инициализации.
- функции передачи/приема байта данных

   Инициализация SPI модуля

   Инициализация включает в себя конфигурирование выводов SPI модуля и управляющего регистра SPCR.


#define SPI_PORTX PORTB
#define SPI_DDRX DDRB

#define SPI_MISO 6 
#define SPI_MOSI 5
#define SPI_SCK 7
#define SPI_SS 4

/*инициализация SPI модуля в режиме master*/
void SPI_Init(void)
{

   /*настройка портов ввода-вывода
   все выводы, кроме MISO выходы*/
   SPI_DDRX |= (1<<SPI_MOSI)|(1<<SPI_SCK)|(1<<SPI_SS)|(0<<SPI_MISO);
   SPI_PORTX |= (1<<SPI_MOSI)|(1<<SPI_SCK)|(1<<SPI_SS)|(1<<SPI_MISO);

   /*разрешение spi,старший бит вперед,мастер, режим 0*/
   SPCR = (1<<SPE)|(0<<DORD)|(1<<MSTR)|(0<<CPOL)|(0<<CPHA)|(1<<SPR1)|(0<<SPR0);
   SPSR = (0<<SPI2X);
}

   Передача/прием данных

   Процесс передачи/приема данных с помощью SPI модуля, работающего в режиме Master, состоит из следующей последовательности действий:

1. установка низкого логического уровня на линии SS
2. загрузка данных в регистр SPDR
3. ожидание окончания передачи (проверка флага SPIF)
4. сохранение принятых данных (чтение SPDR), если требуется
5. возврат на 2-ой шаг, если переданы не все данные
6. установка высокого логического уровня на линии SS

  Ниже приведено несколько вариантов функции передачи/приема данных.

  Передача одного байта данных по SPI


void SPI_WriteByte(uint8_t data)
{
   SPI_PORTX &= ~(1<<SPI_SS);
   SPDR = data;
   while(!(SPSR & (1<<SPIF)));
   SPI_PORTX |= (1<<SPI_SS); 
}


   Передача и прием одного байта данных по SPI


uint8_t SPI_ReadByte(uint8_t data)
{
   uint8_t report;
   SPI_PORTX &= ~(1<<SPI_SS);
   SPDR = data;
   while(!(SPSR & (1<<SPIF)));
   report = SPDR;
   SPI_PORTX |= (1<<SPI_SS); 
   return report; 
}


   Передача нескольких байтов данных по SPI 
*data – указатель на массив передаваемых данных, а num – размерность массива


void SPI_WriteArray(uint8_t num, uint8_t *data)
{
   SPI_PORTX &= ~(1<<SPI_SS); 
   while(num--){
      SPDR = *data++;
      while(!(SPSR & (1<<SPIF)));
   }
   SPI_PORTX |= (1<<SPI_SS); 
}

...

//Пример использования:
uint8_t buf[3] = {12, 43, 98};


SPI_WriteArray(3, buf);


   Передачи и прием нескольких байтов данных по SPI
*data – указатель на массив передаваемых данных, а num – размерность массива.
Принятые данные будут сохраняться в том же массиве.


void SPI_ReadArray(uint8_t num, uint8_t *data)
{
   SPI_PORTX &= ~(1<<SPI_SS); 
   while(num--){
      SPDR = *data;
      while(!(SPSR & (1<<SPIF)));
      *data++ = SPDR; 
   }
   SPI_PORTX |= (1<<SPI_SS); 
}

Файлы

   Простой SPI драйвер, в котором сведены все описанные выше функции - AVR_SPI_driver.rar

Остальные части

   Учебный курс AVR. Работа с модулем SPI. Ч1
   Учебный курс AVR. Работа с SPI. Управление сдвиговым регистром. Ч3

Comments   

# Сергей 2012-10-23 06:50
Респект! Ждем продолжения, желательно с каким-нибудь дисплеем от мобильного.
# Pashgan 2012-10-23 18:38
Так на сайте есть пример для Nokia 3310 и Motorola C350. Я планирую написать про сдвиговый регистр, цап (или ацп), а потом еще про flash память at45db.
# Сергей 2012-10-24 04:34
Точно, нашел. А i2c есть в планах?
# Pashgan 2012-10-24 21:43
Да, есть.
# foxit 2012-10-25 03:56
Жду продолжения!
# megannnn98 2012-11-02 09:06
спасибо большое
# Zliva 2012-11-10 13:27
Очень нужна связка двух АВР через СПИ с контролем получения/отпра вки данных. Срочно!!!
# foxit 2012-11-11 16:09
Можно помочь разве что ссылками.
http://www.ermicro.com/blog/?p=1050

http://www.rocketnumbernine.com/2009/04/26/using-spi-on-an-avr-1
http://www.rocketnumbernine.com/2009/05/12/using-spi-on-an-avr-2
http://www.rocketnumbernine.com/2009/07/03/using-spi-on-an-avr-3
# Pashgan 2012-11-17 18:11
Готового кода у меня нет. Ни разу не решал такой задачи.
# skullhead 2012-11-14 07:16
позвольте высказать пожелание в продолжение темы-не могли бы вы в качестве примера реального применения SPI привести работу управляемой светодиодной ленты.и пример простой,и весьма прикладной)
# Pashgan 2012-11-17 18:09
Я уже написал код для управления по SPI 7-ми сегментным индикатором подключенным через сдвиговые регистры.
# vovanxp 2012-11-16 12:05
Когда будет третья часть? Жду с нетерпением.
# Pashgan 2012-11-17 18:10
Код примеров написал, а до текста никак руки не дойдут. В ближайшее время напишу. Могу проекты на форум пока выложить, если нужно.
# vovanxp 2012-11-19 10:29
Quoting Pashgan:
Код примеров написал, а до текста никак руки не дойдут. В ближайшее время напишу. Могу проекты на форум пока выложить, если нужно.

Пожалуйста, выложите пока на форум.
# shs 2012-11-20 17:03
Паша подскажи как передать структуру а не массив?

typedef struct{
unsigned char sec;
unsigned char min;
unsigned char hour;
} time_t;
time_t t;

void SPI_Send(time_t *time)
{
unsigned char num = 3;

RTC_SELECT();
while(num--){
SPDR = *time++;//error : incompatible types in assignment
// понятно что тип не тот
//а как быть не догоняю

while(!(SPSR & (1
# Pashgan 2012-11-20 20:31
Например так.
Code:
void SPI_WriteStr(time_t *time)
{
SPI_PORTX &= ~(1<<SPI_SS);

SPDR = time->sec;
while(!(SPSR & (1<<SPIF)));

SPDR = time->min;
while(!(SPSR & (1<<SPIF)));

SPDR = time->hour;
while(!(SPSR & (1<<SPIF)));

SPI_PORTX |= (1<<SPI_SS);
}

А вообще.. у тебя же данные одного типа. Зачем тебе структура? В этом случае можно использовать и массив.
# Артём 2012-11-25 14:17
Или так
SPI_ReadArray((unsigned char *)&t, sizeof(t));
# Mikhail 2012-11-21 16:05
Большое спасибо за такие труды. У вас есть какой-то стиль написания, плюс оформление, которые читать и понимать очень легко. Прошу не изменяйтесь :). И вопрос: у вас планируется разобрать интерфейс I2C и прошивку МК через бутлоадер?
# Pashgan 2012-11-21 21:18
Спасибо. Да, планирую. Вопрос времени.. а его как всегда не хватает..
# Артём 2012-11-25 14:19
Смотрите AVR112 - бутлоадер через i2c
# Ренат 2012-12-04 18:47
Блин, я похоже после армии совсем отупел... Есть материал какой нибудь, чтоб все сначала начать?((
# Pashgan 2012-12-04 19:09
Есть... "первая программа на си"
http://chipenable.ru/index.php/programming-avr/item/2-uchebnyy-kurs-iar-avr-pervaya-programma-na-si.html
# Иван 2013-04-11 19:16
Привет например я подключил два радио модуля NRF24L01 настроил SPI, записываю данные в SPDR они должны появится в приемнике в том самом регистре, и делай сними все что угодно. Или с радио связью там посложнее?
# Pashgan 2013-04-12 11:55
Нужно читать даташит на эту микросхему. Думаю там есть какой-то формат сообщения. В интернете есть примеры использования этого модуля.
# den2life 2013-05-02 21:09
привет всем. я прошил мк-просто инициализировал spi, почему на 5 ножке mosi наблюдается 5 вольт. там ведь должно быть 0.
# den2life 2013-05-02 23:42
Вопрос закрыт
# zloiia 2014-02-20 12:16
Исправьте, пожалуйста опечатку в описании регистра SPCR. Бит MTSR называется MSTR
# Pashgan 2014-03-18 20:02
Исправил. Спасибо.
# SAnEK_Bo 2014-02-23 16:02
Здравствуйте. помогите, пожалуйста. Мне нужно Соединить микроконтроллер ATmega8A с пятью драйверами MAX7221 по интерфейсу SPI. Работаю в CVAVR, Proteus ну и на реальном железе. Несколько дней гуглил, сам разбирался, но смог подключить только один такой драйвер.


#include
#include
#define MOSI 3
#define SCK 5
#define SS 2
void Port_init()
{
DDRB |= (1
# Слава 2014-05-05 12:25
Добрый день!!! Не могу понять, а для чего применены "Пользовательск ие типы" uint8_t, почему нельзя было оставить unsigned char???
# Pashgan 2014-05-08 09:53
http://chipenable.ru/index.php/programming-avr/item/62-10-pravil-programmirovaniya-na-si-predotvraschayuschih-oshibki.html
# Слава 2014-05-09 15:05
Спасибо.... Понял...
# Dmitri256 2014-10-26 12:58
в инициализации не совсем правильно, вместо
SPI_DDRX |= (1
# Dmitri256 2014-10-26 13:00
вместо
Code:SPI_DDRX |= (1<<SPI_MOSI)|(1<<SPI_SCK)|(1<<SPI_SS)|(0<<SPI_MISO);
правильнее так
Code:
SPI_DDRX |= (1<<SPI_MOSI)|(1<<SPI_SCK)|(1<<SPI_SS);
SPI_DDRX &= ~(1<<SPI_MISO);
# Master_Gril 2014-11-10 10:28
Добрый день!
Подскажите пожалуйста, если использовать приведённый тут код в авр студио 6, какие библиотеки нужно подключать?
# alex6441161 2014-12-03 13:10
у меня AT90USB162 и ATtiny2313A общаются по SPI. В ATtiny2313A реализован программный SPI. При передаче одного байта все передается норм, а вот при передаче двух байт передается какая-то фигня. Но заметил, если постоянно передавать один и тот же байт, то возвращаться будут все время одни и те же данные, но не те которые послал. Если соединить MOSI и MISO у мастера, то данные отправленные и пришедшие совпадают. Я так понимаю что проблема с ведомым, но в чем именно не могу понять. Может кто подскажет?
Code:void USI_init_SPI_S (void)
{
DDR_SPI |= (1<<DO);
DDR_SPI &= ~(1<<UCSK);
DDR_SPI &= ~(1<<SS);
DDR_SPI &= ~(1<<DI);
USICR |= (1<<USIWM0) | (1<<USICS1);
}

unsigned char SPI_WR_S (unsigned char data_w)
{
USIDR = data_w;
USISR |= (1<<USIOIF);
while(!(USISR & (1<<USIOIF)));
return (USIDR);
}

int main (void)
{
unsigned char data_r[2];
unsigned char i=0;
USI_init_SPI_S ();
for(;;)
{
if(bit_is_clear(PIN_SPI, SS))
{
data_r = SPI_WR_S(data_r);
if(i>=2) i=0;
}
# kript0n 2015-02-15 09:31
Я кое-чего не понимаю.
Запись данных ведется через MOSI. В коде его определили, как 5 выход на порту B.
Code:#define SPI_MOSI 5
Вообще это стандартный MOSI вывод на atmega16. А если я хочу использовать другой вывод, как модуль узнает через какой выход слать данные? Явно это нигде не задается.
# Александр Бабаков 2016-01-13 15:55
Добрый вечер ! Спасибо большое за ваши труды !!! Я недавно только начал изучать программировани е и мне сложновато понять объявление типа uint8_t . Могу ли я записать например (char data) вместо (uint8_t data)? И как это может повлиять на работу ?
# Vladsh 2016-01-20 13:33
Всем доброго дня! Имеется устройство на мега16! Питание подал на схему, и на выводах MOSI, MISO, SCK. т.е. с 5-9 ножки есть постоянно+5в, разъясните так должно быть или нет не могу понять! устройство(тайм ер) не включается, признаков жизни не подает, хотя работало раньше!!!

У вас недостаточно прав для комментирования.