В этой статье рассматривается пример программной реализации на микроконтроллерах PIC и AVR функций ведущего шины SPI для разных режимов (mode0, mode1, mode2, mode3). Чтобы понимать что происходит — для начала, как всегда, читаем теорию (что такое SPI и как он работает). Если с теорией разобрались, тогда можно приступать к практической реализации.
Итак, что должен уметь делать SPI-мастер? Собственно говоря, всего четыре вещи:
Как вы знаете (вы же с теорией знакомы?) — чтение и установку данных и «Мастер», и «Слэйв» производят по противоположным фронтам сигнала тактирования, причём для приёма и передачи может использоваться один и тот же сдвиговый регистр. Для наглядности давайте нарисуем для всех четырёх режимов диаграммы сигналов, соответствующие передаче по SPI двух бит, а уже по ним распишем действия контроллера:
Диаграммы нарисованы для случая, когда передача осуществляется младшим битом вперёд.
Биты, не выделенные жирным, — не имеют для нас значения и могут быть на следующем этапе перезаписаны (это, например, те биты, которые мы уже установили на шину данных, или, наоборот, — уже считали с шины данных).
По диаграммам видно, что в результате обмена данными сдвиговые регистры «Мастера» и «Слэйва» поменялись содержимым (помните, в теоретической части я писал, что у SPI есть такая особенность, — для приёма и передачи можно использовать один и тот же регистр).
Прежде чем переходить к написанию кода, — давайте ещё подумаем вот о чём. Архитектура контроллеров у нас 8-ми битная, а для SPI часто может потребоваться бОльшая размерность сдвигового регистра.
Пусть в нашем примере размер сдвигового регистра можно будет выбрать от 1-го до 32-х бит.
То есть максимально под сдвиговый регистр мы будем выделять 4 байта (4 восьмибитных регистра, расположенных в памяти так, как на рисунке справа). Для случаев, когда размер регистра превышает 1 байт (то есть равен 2, 3 или 4 байта) мы будем программно имитировать работу с несколькими 8-ми битными регистрами как с одним большим. Используемые для организации сдвигового регистра восьмибитные регистры в дальнейшем будем называть просто байтами, чтобы не путаться.Что представляет собой чтение с точки зрения «Мастера»? Да ничего особенного, — нужно просто прочитать сигнал на входе (MISO) и записать его в младший или старший бит сдвигового регистра (в зависимости от того — младшим или старшим битом вперёд передаются данные и от значения CPHA). Вот так это выглядит в коде:
Для контроллеров PIC | Для контроллеров AVR | ||||
2) Для передачи старшим битом вперёд при CPHA=0 и младшим битом вперёд при CPHA=1
Для случая 1 в регистр FSR должен быть предварительно загружен адрес младшего используемого байта нашего сдвигового регистра, а для случая 2 — адрес старшего используемого байта этого регистра. PORT_SPI — адрес порта ввода /вывода, к которому подключена линия MISO, MISO_Line — номер канала порта. | 1) Для передачи младшим битом вперёд при CPHA=0 и старшим битом вперёд при CPHA=1
2) Для передачи старшим битом вперёд при CPHA=0 и младшим битом вперёд при CPHA=1
Для случая 1 в регистр X должен быть предварительно загружен адрес младшего используемого байта нашего сдвигового регистра, а для случая 2 — адрес старшего используемого байта этого регистра. PIN_SPI — адрес регистра, отображающего состояния входов порта ввода/вывода, к которому подключена линия MISO, MISO_Line — номер канала порта. |
Здесь и далее, temp — просто некий вспомогательный регистр.
Кроме того, перед началом передачи, предназначенные для передачи биты, должны быть выровнены по старшему биту старшего используемого нами для организации сдвигового регистра байта при передаче старшим битом вперёд, и по младшему биту младшего используемого байта при передаче младшим битом вперёд. Картинка внизу иллюстрирует, что имеется ввиду на примере 12-ти битного пакета SPI (то есть, когда мы реализуем 12-ти битный сдвиговый регистр; при этом из наших зарезервированных четырёх байт используются только два).
Идём дальше. «Сдвиг», как видно по диаграммам, состоит для «Мастера» (впрочем как и для «Слэйва», но мы в этой статье занимаемся только «Мастером») из двух действий: это,
во-первых, собственно сдвиг регистра и, во-вторых, установка очередного бита на шину MOSI.
Начнём мы с отдельной процедуры, реализующей сам сдвиг, поскольку если наш сдвиговый регистр состоит из нескольких байт, то при сдвиге нужно определённым образом переносить биты из одного байта в другой.
Кроме того, сдвиг нам нужен не простой, а циклический, то есть при сдвиге вправо младший бит нашего большого регистра должен стать старшим, а при сдвиге влево — старший бит должен стать младшим. Код, реализующий сдвиг будет выглядеть следующим образом:Для контроллеров PIC | Для контроллеров AVR | ||||
1) Сдвиг вправо
2) Сдвиг влево
| 1) Сдвиг вправо
2) Сдвиг влево
|
Ну вот, со сдвигом регистра разобрались, теперь можно приступать и к этапу передачи, именуемому «Сдвиг». Как я ранее уже писал, — этот этап передачи состоит из двух действий: сдвиг регистра и установка очередного бита на шину MOSI. В коде это будет выглядеть так:
Для контроллеров PIC | Для контроллеров AVR | ||||||||
1) Для передачи младшим битом вперёд при CPHA=0
2) Для передачи старшим битом вперёд при CPHA=0
3) Для передачи младшим битом вперёд при CPHA=1
4) Для передачи старшим битом вперёд при CPHA=1
| 1) Для передачи младшим битом вперёд при CPHA=0
2) Для передачи старшим битом вперёд при CPHA=0
3) Для передачи младшим битом вперёд при CPHA=1
4) Для передачи старшим битом вперёд при CPHA=1
|
Хотелось бы обратить внимание вот на что: поскольку при CPHA=0 по первому фронту на шине тактирования происходит чтение, то первый передаваемый бит в этот момент уже должен быть установлен на шине. Лучше устанавливать его сразу при загрузке передаваемых данных в регистр или это можно делать, например, по сигналу SS, в любом случае — он должен быть установлен ещё до начала тактирования. Для CPHA=1 никакие вспомогательные действия до начала тактирования не нужны.
И ещё одно. После обмена пакетами с помощью приведённых выше процедур изменится граница выравнивания данных. То есть, если посылаемые данные были выровнены к младшему биту младшего байта, то принятые будут выровнены к старшему биту старшего байта, а если посылаемые данные были выровнены к старшему биту старшего байта, то принятые будут выровнены к младшему биту младшего байта.
Далее давайте подумаем как управлять линией SCLK и какие при этом надо решить задачи.
Во-первых, перед началом передачи (а точнее как только сконфигурировали SPI), нужно установить на линии SCLK уровень, соответствующий выбранной полярности (CPOL).
Во-вторых, нужно как-то определять моменты переключения линии SCKL. Очевидно, что частота этих переключений определяет скорость передачи.
Надо сказать, что вообще-то интерфейс SPI не предъявляет каких-либо требований к стабильности частоты импульсов на SCLK, поэтому переключения можно формировать когда угодно, например, как выполнили все действия по установке, чтению и сдвигу данных — так и переключайте себе SCLK. Понижать скорость можно добавлением пустых циклов перед каждым переключением. Это первый метод для управления шиной SCLK.
Второй метод заключается в том, чтобы использовать для отсчёта моментов переключения таймеры. Тут вообще красота, — очень легко можно сделать переключения со стабильной частотой, которая полностью будет определяться параметрами таймера. Алгоритм примерно такой: как только выполнили все подготовительные действия — запускаете таймер, а далее по прерыванию от таймера инвертируете линию SCLK, сбрасываете и перезапускаете таймер.
Чтобы знать когда остановиться (когда передали все биты) — специальным счётчиком нужно отсчитывать количество переданных бит или количество переключений линии SCLK. Естественно лучше считать количество бит, поскольку переключений линии SCLK будет в два раза больше.
Код никакой писать не буду, — тут всё просто (если что — на форум).
Ну и последнее, что нам осталось — управлять линией SS. Естественно, что моменты, когда нужно начинать и заканчивать сеанс — это полностью ваше личное дело, специфичное для каждой конкретной задачи, так что вам и флаг в руки. Скажу только, что обычно с точки зрения мастера есть два варианта управления этой линией:
И ещё одно. Все приведённые выше коды предполагают, что используются пушпульные выходы. Если выходы предполагается сделать с открытым стоком, то нужно записать в соответствующие защёлки нули, и работать не с регистром порта, а с регистром выбора направления работы порта (тогда сигналы, формируемые контроллером на соответствующих выводах, будут меняться не между нулём и единицей, а между нулём и Z-состоянием).
Вот здесь можно посмотреть пример использования описанных функций.
Еще один интерфейс, которым можно без особых проблем связать устройство с электронным проектом — это SPI интерфейс.
SPI (Serial Peripheral Interface — последовательный периферийный интерфейс) как и UART довольно старый, простой и популярный интерфейс. Существует множество устройств работающие по этому интерфейсу (микроконтроллеры, EEPROM, ADC, DAC …). Будем и мы его использовать для наших устройств.
В SPI интерфейсе для передачи данных используется три линии: MOSI – передача; MISO – прием; SCK – тактирование. Дополнительно используется линия SS (Slave Select ?) – выбор устройства (если их несколько). SPI — полнодуплексный синхронный интерфейс — передача и прием осуществляется параллельно. Инициирует и тактирует обмен ведущее устройство – master (может быть только одно). Ведомое — slave-устройство (может быть несколько) принимает/отдает байт под действием тактового сигнала.
В микроконтроллерах AVR SPI интерфейс используется двойственно, как интерфейс связи и как интерфейс для последовательного внутрисхемного программирования. В подавляющем большинстве контроллеров SPI интерфейс присутствует (есть исключения в моделях Tiny), но даже если его нет, программно реализовать его не составит труда.
Итак, как устроен SPI интерфейс?
А устроен он до безобразия просто (его то и интерфейсом назвать сложно) — два сдвиговых регистра (master и slave), плюс генератор на стороне master – все! Сдвиговые регистры замкнуты в кольцо по линиям MOSI и MISO, тактовый сигнал от генератора подается на оба сдвиговых регистра (на slave через линию SCK).
До начала обмена данные помещаются в сдвиговые регистры, мастер запускает генератор, генератор «отщелкивает» 8 тактов, за эти 8 тактов сдвиговые регистры «меняются» содержимым (мастер получает байт слэйва, а слэйв – мастера) – все!
Если ведомых устройств несколько – обмен осуществляется только с одним, у которого сигнал на SS низкого уровня.
SPI – самый быстрый последовательный интерфейс. При частоте задающего генератора микроконтроллера 20МГц он может осуществлять обмен со скоростью 10 000 000 Бод
(1.2 мегабайта за секунду)!
Недостатком интерфейса есть то, что инициатором обмена всегда является мастер. Это значит, что если на периферии что-то произошло, мастер об этом узнает только тогда когда сам «надумает» провести сеанс связи (теряется оперативность). Производители AVR нашли способ обойти этот недостаток. Если SPI микроконтроллера сконфигурирован как master ножку SS можно сконфигурировать как на вход, так и на выход. Если ножка сконфигурирована как выход, то она используется для выбора slave-устройства. Но если ножка SS сконфигурирована на вход, то при появлении на этой ножке низкого уровня микроконтроллер автоматически переключит SPI в режим slave и сможет принять данные от другого мастера. После сеанса обмена SPI остается в режиме slave, для перевода его в режим master нужно повторно конфигурировать SPI.
Я думаю, для устройств блога, данный алгоритм переключения master- slave редко будет нужен, поэтому мы будем использовать SPI только как master или только как slave.
Если устройство блога – устройство вывода (например клавиатура), то SPI будет master, а в Вашем проекте SPI будет slave.
Если устройство блога – устройство ввода (например цифровой индикатор), то SPI будет slave, а в Вашем проекте SPI будет master.
Для связи устройства с Вашим проектом через интерфейс SPI нужно:
1 Подключить устройство блога к соответствующим ножкам микроконтроллера.
2 Настроить SPI Вашего контроллера (как master или как slave). Для этого в соответствующие порта ввода/вывода записать определенные значения.
3 Иметь (написать) процедуры приема/передачи сообщений по SPI в Вашей программе.
Теперь рассмотрим подробно каждый пункт:
1 СОЕДИНЕНИЕ УСТРОЙСТВ ЧЕРЕЗ SPI.
Просто соединяем одноименные выводы MOSI-MOSI, MISO-MISO, SCK-SCK, SS-SS. Если соединение только между двумя устройствами, то SS-SS, в принципе, не обязательно, но нужно позаботится о том, чтобы на ножке SS slave-устройства был низкий уровень (притянуть к «земле») иначе устройство не будет участвовать в обмене.
2 НАСТРОЙКА SPI.
У SPI есть четыре режима работы (0, 1, 2, 3). Режим работы зависит от того, по какому фронту тактового сигнала осуществляется обмен. Кроме того можно настроить какой бит (старший или младший) передается первым. Для обмена это не имеет никакого значения – результат будет одинаковым (режимы работы нужны для подстройки SPI к устройствам разных производителей). Master и slave должны работать в одном режиме.
Для устройств блога будем применять следующие настройки SPI:
Режим – 0
Старший бит передается первым
Частота тактирования — по обстоятельствам (для SPI частота не столь важна, так как тактирование происходит от одного источника).
Для примера возьмем микроконтроллер ATmega8 с внутренним задающим генератором на 8МГц. Настроим управляющие порта ввода-вывода контроллера и в различных средах программирования.
Algorithm Builder.
Создаем проект (Файл/Новый). Выбираем микроконтроллер и частоту задающего генератора в Опции/Опции проекта… (ATmega8, внутренний задающий генератор на 8МГц). В панели инструментов жмем кнопочку «S» – настройщик управляющих регистров» выбираем SPI.
Настройки для master (для связи с устройством вывода):
Настройки для slave (для связи с устройством ввода):
Жмем «ОК». Готово – SPI проинициализирован и готов к работе.
Ассемблер. Можно поковыряться в даташите на контроллер, можно взять кусок асма из скомпиленного проекта на С, можно перевести мнемокоманды Algorithm Builder — выбираем, что нравится.
Настройки для master (для связи с устройством вывода):
init-SPI-master: sbi 0x17,0x02 sbi 0x17,0x05 sbi 0x17,0x03 ldi r16,0x00 out 0x0E,r16 ldi r16,0xD0 out 0x0D,r16 |
Настройки для slave (для связи с устройством ввода):
init-SPI-slave: sbi 0x17,0x04 ldi r16,0x00 out 0x0E,r16 ldi r16,0xC0 out 0x0D,r16 |
CodeVisionAVR. Для генерации настроек SPI, нажимаем на значок шестеренку (CodeWisardAVR) на панели инструментов. В открывшемся окошке сначала выбираем вкладку Chip в ней выбираем микроконтроллер и устанавливаем частоту, с которой будет работать задающий генератор. Далее выбираем и заполняем вкладку SPI в соответствии с нужными характеристиками.
Настройки для master (для связи с устройством вывода):
// Port B initialization // Func7=In Func6=In Func5=Out Func4=In Func3=Out Func2=Out Func1=In Func0=In // State7=T State6=T State5=0 State4=T State3=0 State2=0 State1=T State0=T PORTB=0x00; DDRB=0x2C; // SPI initialization // SPI Type: Master // SPI Clock Rate: 2000,000 kHz // SPI Clock Phase: Cycle Half // SPI Clock Polarity: Low // SPI Data Order: MSB First SPCR=0xD0; SPSR=0x00; |
Настройки для slave (для связи с устройством ввода):
// Port B initialization // Func7=In Func6=In Func5=In Func4=Out Func3=In Func2=In Func1=In Func0=In // State7=T State6=T State5=T State4=0 State3=T State2=T State1=T State0=T PORTB=0x00; DDRB=0x10; // SPI initialization // SPI Type: Slave // SPI Clock Rate: 2000,000 kHz // SPI Clock Phase: Cycle Half // SPI Clock Polarity: Low // SPI Data Order: MSB First SPCR=0xC0; SPSR=0x00; |
Сохраняем сгенерированный проект (File\Generate, Save and Exit). Создан проект со всеми нужными установками для SPI. В проекте инициализируется и другая периферия (зачастую не нужная). После создания проекта его можно подкорректировать – удалить все не нужное.
3 СОЗДАНИЕ ПРОЦЕДУР ОБРАБОТКИ SPI.
Работа с SPI в микроконтроллере организована очень просто. Чтобы начать обмен данными в мастер-контроллере достаточно записать передаваемый байт в регистр SPDR – обмен начнется автоматически. По завершению обмена вызовется процедура обработки прерывания, в ней считываем из SPDR полученный байт. Со слэйв-контроллером еще проще по окончании обмена вызовется процедура обработки прерывания, в ней считываем из SPDR полученный байт.
Algorithm Builder.
Передача. Как для master’a, так и для slave передаваемый байт нужно записать в регистр SPDR. Для master’a запись байта в SPDR инициирует обмен.
Прием. Как для master’a, так и для slave после принятия байта вызовется прерывание, в котором и обрабатывается посылка.
Если во время передачи байта будет попытка записи в регистр SPDR — возникнет конфликт записи. Поэтому в программе нужно позаботится о том, чтобы следующий байт передавался по окончании приема предыдущего. Наилучшим решением этой проблемы будет следующий алгоритм: первый байт-начало пакета байт передается из тела программы, а остальные передаются из прерывания окончании передачи по SPI. Таким образом основная программа «освобождается» от работы по передаче строки байт и в тоже время не может возникнуть конфликт записи, так как прерывание вызывается о окончании обмена.
Так как мы планируем передавать единичные байты по SPI, приведенный выше алгоритм мы использовать не будем.
Ассемблер.
Код предельно прост. Для передачи пишем в SPDR, полученный байт читаем из SPDR в теле прерывания.
CodeVisionAVR. CodeWisardAVR кроме инициализации создаст и процедуру обработки прерывания. Ничего дополнительного не потребуется.
// прием/передача данных по SPI в теле прерывания interrupt [SPI_STC] void spi_isr(void) { unsigned char data; data=SPDR; } // подготовка/передача данных по SPI в теле программы unsigned char data; SPDR=data; |
Вот, вроде бы, и все, что хотелось сказать по SPI.
Интерфейс довольно простой и не должен вызывать проблем при его использовании.
(Visited 6 938 times, 1 visits today)
спросил
Изменено 5 лет, 8 месяцев назад
Просмотрено 2к раз
\$\начало группы\$
Microchip предлагает ряд последовательных микросхем SRAM с коммуникационным протоколом SQI, который похож на SPI, но с 4 параллельными битами, записанными при каждом переключении CLK.
Если вы можете потратить дополнительные 3 булавки, это имеет смысл. Вы можете достичь пропускной способности, в 4 раза превышающей скорость SPI.
Однако есть одна загвоздка — SPI поддерживается всеми современными микроконтроллерами как аппаратный модуль, поэтому он работает автоматически и очень быстро, а частоты CLK в несколько МГц не являются проблемой.
SQI не поддерживается аппаратным обеспечением, поэтому вам нужно немного взломать протокол. Если мой MCU, например, работает на системных часах 8 МГц и выполняет большинство инструкций за один цикл (AVR), я могу ожидать, что скорость SQI CLK будет ниже 1 МГц. Это основано на программной реализации SPI, которая также работает на частоте менее 1 МГц и имеет немного меньше кода.
Итак, допустим, я могу передавать 4 бита за раз, используя SQI, с CLK 800 кГц (реальные числа), то есть 3,2 Мбит в секунду. Однако я могу запустить SPI на частоте 5 МГц и получить 5 Мбит данных с меньшим количеством кода и без ожидания кода моего приложения, пока данные отправляются или принимаются по линиям связи.
Так в чем здесь смысл? Что мне не хватает?
\$\конечная группа\$
\$\начало группы\$
SQI не поддерживается аппаратно…
Ваша предпосылка, хотя и верна для вашего конкретного MCU, ошибочна в общем случае. Микроконтроллеры, на которые вы смотрели, могут не иметь SQI (который, я считаю, такой же, как Quad SPI или QSPI), есть много устройств с аппаратной поддержкой QSPI. (Обратите внимание, что «QSPI» — это сбивающий с толку аббревиатура, поскольку иногда он также используется для «Queued SPI», что отличается.)
Например: многие микроконтроллеры ARM (например, STM32 и Atmel SAM) имеют аппаратное периферийное устройство, которое может делать x1, x2 или x4 SPI, а некоторые выполняются из хранилища QSPI в дополнение к использованию таких периферийных устройств для хранения данных.
Большинство ПЛИС поддерживают настройку по основному последовательному порту (SPI) с шириной x1, x2 или x4. Некоторые могут даже использовать двойные флэш-чипы QSPI для полных восьми битов за такт. См. конфигурацию серии Xilinx 7 UG, ctrl-F «quad».
В этих приложениях QSPI не является битовой заглушкой и поэтому работает в четыре раза быстрее. Для вашего конкретного микроконтроллера (и многих небольших 8-битных микроконтроллеров) вы правы, и вам будет лучше использовать обычную флэш-память x1 SPI. Во многих микроконтроллерах, прикладных процессорах и FPGA имеется аппаратная поддержка x4 SPI, и это намного быстрее.
\$\конечная группа\$
6
Зарегистрируйтесь с помощью Google
Зарегистрироваться через Facebook
Зарегистрируйтесь, используя электронную почту и пароль
Электронная почта
Требуется, но никогда не отображается
Электронная почта
Требуется, но не отображается
Нажимая «Опубликовать свой ответ», вы соглашаетесь с нашими условиями обслуживания, политикой конфиденциальности и политикой использования файлов cookie
.\$\начало группы\$
Я пытаюсь понять, как провайдер может записывать машинный код в память микроконтроллера.
В случае с микроконтроллерами AVR (такими как ATMega) вы можете использовать специальный внутрисхемный программатор, который «включит» целевой микроконтроллер через шину SPI. (Я получаю эту информацию из этого документа)
Меня интересует вот что: если бы у меня был новый AVR без каких-либо программ, написанных для него, как он мог бы «слушать» шину SPI? Разве программа не должна быть запущена?
\$\конечная группа\$
2
\$\начало группы\$
Свежий AVR, даже если он не имеет кода, запрограммированного в пространстве пользователя, не означает, что у него нет возможностей.
Вы заметите, что линия RESET используется как часть протокола ISP. Хотя линия RESET, очевидно, делает то, что вы ожидаете от большей части чипа, она также включает механизм, который обрабатывает ISP. Это также четко разграничивает «нормальную» работу от работы интернет-провайдера, вы находитесь в одной или другой. Вы также заметите, что для программиста используется рукопожатие «магических байтов», чтобы установить, что этот механизм работает.
Делает ли этот дополнительный механизм всю работу по обработке протокола и управлению шинами памяти, или он просто переворачивает адресную строку, позволяя основному процессору запускать код ISP из ПЗУ (звучит более разумно?), на самом деле не имеет значения. вопрос с точки зрения программиста.
\$\конечная группа\$
\$\начало группы\$
Не менее важный вопрос: как мне перепрограммировать мой AVR, когда мой пользовательский код зависает в чипе (вскоре после перезагрузки). .. Приятно иметь функцию, которая работает независимо от того, что в нее запрограммировано или не запрограммировано.
Как уже упоминалось, это либо случай чистой логики, либо некоторая логика и код в роме, используемом для реализации этой функции. Достаточно просто для чистой логики.
То, что вывод помечен как сброс, не означает, что весь дизайн должен быть сброшен по этой логике. Посмотрите, например, на JTAG (на чипах, которые его имеют), как правило, имеет свой собственный сброс, отдельный от сброса основного чипа, и является или может быть полностью отдельным доменом сброса от сброса основного чипа. Сброс — это просто еще один вход, который используется, как того хотят разработчики, а не автоматическая причина, по которой каждый бит логики должен реагировать на него.
Очевидно, существует логика и, возможно, какой-то код, которого нет в этом домене сброса (по существу, в домене сброса питания и/или инвертированном сбросе, когда вы отпускаете контакт сброса, эта логика сама может перейти в сброс, и когда вы утверждаете контакт сброса, эта логика может быть освобождена).