Микроконтроллеры PIC. Архитектура и программирование. Часть 7. Интерфейс SPI микроконтроллеров PIC24F » Программирование устройств на PIC микроконтроллерах


Логин:
Пароль:
О сайте:

Pic.Rkniga.ru - Сайт как для начинающих, так и для опытных радиолюбителей, разрабатывающих свои устройства на популярных PIC микроконтроллерах.
Здесь можно обмениваться сообщениями на форуме, а также добавлять на сайт статьи и схемы своих устройств.

Меню сайта
Главная Форум по PIC микроконтроллерам Форум Статьи по PIC микроконтроллерам Статьи Справочная информаци по PIC микроконтроллерам Справочник Литература по PIC микроконтроллерам Литература Схемотехника Схемотехника устройств на PIC микроконтроллерах Микроконтроллеры Программаторы Все по программированию PIC микроконтроллеров Программы, Софт Программы Ссылки Написать нам
Опрос

На каком языке программирования вы пишите программы?


Ассемблер
Си
Бейсик
Паскаль
Другой


Последние материалы
  • Каршеринг в Москве - это Просто, Удобно и Недорого.
  • Кнопка On/OFF на PIC12F629.
  • Часы с синхронизацией от китайского будильника
  • ШИМ регулятор на PIC16F628A.
  • Счетчики прямого и обратного счета на PIC16F628A.
  • Таймер отключения питания для мультиметра и не только.
  • Программирование на C микроконтроллеров PIC24
  • Измеритель напряжения и тока
  • Маршрутный компьютер для электровелосипеда
  • Простой двухканальный термометр на PIC16F690 и датчиках DS18B20
  • Популярные материалы
    Случайная книга
    Программирование устройств на PIC микроконтроллерах » Справочник » Микроконтроллеры PIC. Архитектура и программирование. Часть 7. Интерфейс SPI микроконтроллеров PIC24F
    Микроконтроллеры PIC. Архитектура и программирование. Часть 7. Интерфейс SPI микроконтроллеров PIC24F
    Автор публикации: alex Просмотров: 11525 Добавлен: 27-09-2012, 14:07 Комментарии: 0

    Содержание
    1. Обзор 16-битных PIC-микроконтроллеров
    2. Архитектура микроконтроллеров PIC24F
    3. Система команд и основы программирования микроконтроллеров PIC24F
       3.1 Программная модель микроконтроллеров PIC24F
       3.2 Режимы адресации и система команд
    4. Программирование портов ввода-вывода
       4.1 Аппаратно-программная архитектура портов ввода/вывода
       4.2 Программирование портов ввода/вывода
       4.3 Модуль регистрации событий
    5. Программирование прерываний
    6. Программирование таймеров
       6.1 Практическое использование 16-битных таймеров
       6.2 Работа таймеров в 32-битном режиме
    7. Интерфейс SPI микроконтроллеров PIC24F
       7.1 Аппаратно-программная реализация SPI в микроконтроллерах PIC24F
       7.2 Практическое программирование обмена данными по SPI
    8. Интерфейс I2C микроконтроллеров PIC24F
       8.1 Принципы функционирования интерфейса I2C
       8.2 Модуль интерфейса I2C микроконтроллеров PIC24F
       8.3 Практическое использование интерфейса I2C
    9.Программирование интерфейса PMP
       9.1 Режимы работы PMP
       9.2 Практические примеры программирования интерфейса PMP
    10. Последовательный интерфейс микроконтроллеров PIC24F
       10.1 Аппаратно$программная архитектура UART
       10.2 Практическое использование последовательного порта
    11. Обработка аналоговых сигналов в микроконтроллере PIC24F
       11.1 Программная модель интегрированного АЦП
       11.2 Практическое использование модуля АЦП
       11.3 Использование внешнего АЦП
    12. Генерация аналоговых и цифровых сигналов
       12.1 Модуль генерации цифровых сигналов
       12.2 Аналоговые компараторы в микроконтроллерах PIC24F

         Многообразие выпускаемых в настоящее время электронных компонентов и устройств, кроме положительных моментов, добавляет и целый ряд проблем разработчику встроенных систем. Одной из таких проблем является интерфейс микроконтроллера с устройством. В микроконтроллере PIC24 имеется достаточное количество портов ввода/вывода, что теоретически позволяет подключать самые разные, сколь угодно сложные устройства. Тем не менее, для стандартизации подключения своих устройств большинство производителей оборудования используют целый ряд аппаратно-программных интерфейсов, которые обеспечивают унифицированные протоколы обмена данными. Наиболее популярными протоколами обмена данными являются SPI, I2C, SMBus, Bluetooth и др. Ведущими производителями электронных компонентов разработаны многочисленные устройства, совместимые с этими популярными протоколами, которые можно использовать в проектах на базе микроконтроллеров.
         В микроконтроллерах большинство этих протоколов можно реализовать «вручную», используя для этого порты ввода/вывода и программное обеспечение. Тем не менее, для упрощения разработки интерфейсов фирмы-производители микроконтроллеров размещают на кристалле специализированные – модули SPI, I2C и др. Не являются исключением и микроконтроллеры семейства PIC24F — все модели семейства имеют как минимум по два, а то и по три интерфейса SPI и I2C. Разработчики микроконтроллеров автоматизируют процесс обмена данными по таким интерфейсам, делая вмешательство программиста минимальным, что значительно экономит время при разработке систем.
         Рассмотрим наиболее популярные интерфейсы SPI и I2C и их реализацию в системах на базе PIC24. Эта глава посвящена интерфейсу SPI, а следующая — интерфейсу I2C.
         Обмен данными по интерфейсу (протоколу) SPI (Serial Peripheral Interface) можно представить упрощенной диаграммой (Рис. 7.1).
         Интерфейс SPI обеспечивает обмен данными в синхронном режиме. Поскольку данные передаются синхронно с тактовой частотой, скорость обмена может быть очень высокой. К преимуществам этого протокола следует отнести и возможность обмена данными в полнодуплексном режиме.

    Рис. 7.1. Схема обмена данными по протоколу SPI

         В большинстве случаев интерфейс связывает два устройства, одно из которых является «ведущим» (Master), а другое – «ведомым» (Slave). Ведущий инициирует обмен данными и синхронизирует их прием и/или передачу. Несколько устройств могут функционировать как ведомые, при этом ведущий должен выбирать ведомого, подавая сигнал выбора (slave select, chip select) на конкретное устройство.
         Сигнал SCLK (Serial Clock) представляет собой последовательность синхронизирующих импульсов, генерируемых ведущим. Передача и прием данных привязаны к передним или задним фронтам синхросигнала. По линии MOSI (Master Output, Slave Input) передача данных осуществляется от ведущего к ведомому, а линия MISO (Master Input, Slave Output) служит для приема данных от ведомого. Сигнал SS (Slave Select) генерируется ведущим для конкретного ведомого, разрешая сеанс обмена данными, при этом активным уровнем такого сигнала является уровень логического нуля.
         Очень часто ведущие производители оборудования используют и альтернативные обозначения сигналов интерфейса. Например, сигнал синхронизации SCLK часто обозначается как SCK, MISO может обозначаться как SDI, DI или SI. Сигнал MOSI имеет альтернативные обозначения SDO, DO и SO. Наконец, сигнал выбора ведомого часто встречается под альтернативными обозначениями nCS и CS.
         Интерфейс SPI является двунаправленным, тем не менее, большинство устройств, выпускаемых промышленностью, обычно используют усеченную (однонаправленную) версию этого протокола. Например, большинство аналогоцифровых преобразователей используют модель, в которой ведущим является устройство, получающее данные (микроконтроллер, компьютер и т. д.). В этих случаях синхронизацию передачи/приема данных выполняет инициатор обмена.

    7.1. Аппаратно-программная реализация SPI в микроконтроллерах PIC24F

         Микроконтроллеры PIC24F, в зависимости от модели устройства, могут включать два или три модуля интерфейса SPI, которые обозначаются как SPI1, SPI2 и SPI3 (три интерфейса имеют устройства с увеличенным объемом памяти программ). Все интерфейсы совершенно идентичны по аппаратно-программной архитектуре, а выводы микроконтроллеров, относящиеся к SPI, имеют следующее назначение:
    • SDIx — ввод последовательных данных;
    • SDOx — вывод последовательных данных;
    • SCKx — вход или выход тактовых импульсов;
    • SSх — вход выбора ведомого.
         Модуль SPI может быть настроен для работы в 2-, 3- или 4-проводной конфигурации. В 3-проводной конфигурации не используется линия SSх, а в двухпроводной — не задействованы SDOx и SSх. Функциональная схема модуля SPIx (x = 1, 2, 3) показана на Рис. 7.2.

    Рис. 7.2. Функциональная схема модуля SPI

         По этой схеме можно понять, как осуществляется обмен данными по интерфейсу SPI. Пусть, например, выполняется передача байта данных из памяти микроконтроллера другому устройству, присоединенному через интерфейс SPI.
         В этом случае байт данных поступает через внутреннюю шину данных в регистр буфера приема/передачи SPIxBUF. При разрешении передачи байт данных помещается в сдвиговый регистр SPIxSR, откуда каждый из восьми битов (от старшего к младшему) выдвигается на линию SDOx синхронно с тактовым сигналом SCKx, который может быть либо внешним, либо формироваться самим микроконтроллером. В зависимости от конфигурации интерфейса выдача битов может выполняться либо по нарастающему, либо по спадающему фронту импульсов тактового сигнала.
         Сам тактовый сигнал может формироваться схемой деления частоты из внутреннего тактового сигнала частотой FCY. Коэффициент деления этой схемы определяется битами 1:0 регистра управления SPIxCON (первый предделитель) и битами 4:2 этого же регистра (второй предделитель). Если устройство работает в роли «ведомого», то сигнал синхронизации приходит от «ведущего».
         При приеме данных по интерфейсу SPI последовательность операций иная.
         В этом случае каждый бит данных по фронту/спаду строба SCKx вдвигается в сдвиговый регистр SPIxSR, откуда сформированный байт (или слово) данных поступает в регистр буфера приема/передачи SPIxBUF. Содержимое буфера далее может быть считано программой.
         Если обмен данными должен синхронизироваться «окном», то в этом случае используется сигнал SS, который разрешает или запрещает прием данных ведомым. Как правило, обмен данными разрешается при НИЗКОМ уровне сигнала SS и запрещается при ВЫСОКОМ уровне этого сигнала. Функционирование интерфейса SPI легче понять, если проанализировать две временные диаграммы (Рис. 7.3 и 7.4).

    Рис. 7.3. Временная диаграмма работы 3-проводного интерфейса SPI

    Рис. 7.4. Временная диаграмма работы 2-проводного интерфейса SPI

         На Рис. 7.3 показана временная диаграмма работы интерфейса SPI в 3-проводном режиме. В данном примере интерфейс SPI работает на передачу, поэтому на линию SDO поразрядно выдвигается байт данных. Работа интерфейса ведомого разрешается при НИЗКОМ уровне сигнала SS и запрещается при ВЫСОКОМ уровне. В изображенной конфигурации каждый бит данных, начиная со старшего, выдвигается на линию SDO по фронту SCK, поэтому ведомое устройство может считывать бит после этого перепада. Если сигнал SS становится неактивным, то даже при дальнейшем поступлении импульсов синхронизации и наличии данных прием их ведомым будет невозможен. При других настройках биты данных могут, например, фиксироваться по спаду синхронизирующего сигнала SCK. Подобный 3-проводной интерфейс чаще всего используется в аналого-цифровых преобразователях и первичных преобразователях физических величин на основе АЦП.
         Окно, генерируемое сигналом SS, фиксирует только определенное число бит данных (8 в данном примере), отсекая остальные, даже при наличии синхронизирующих импульсов на линии SCK. Если сигналы синхронизации (SS и SCK) генерируются микроконтроллером, то он выступает в роли «ведущего».
         Если же синхронизация выполняется другим устройством, а микроконтроллер работает только с линиями SDI/SDO, то он будет выступать в роли «ведомого».
         Интерфейс, состоящий из двух линий — синхронизации (SCK) и входных/выходных данных (SDI/SDO), часто используется для осуществления обмена между самими микроконтроллерами PIC24, поскольку синхронизацию начала/конца обмена данными обеспечить несложно (Рис. 7.4).
         Двухпроводной интерфейс легко реализовать в режиме «ведущий» — «ведомый» для двух микроконтроллеров, когда импульсы синхронизации SCK «ведущего» синхронизируют прием данных «ведомым». При такой конфигурации вывод тактового сигнала интерфейса SCKx «ведущего» соединяется с выводом тактового сигнала «ведомого», а выводы данных SDI/SDO «ведущего» микроконтроллера соединяются с выводами SDO/SDI «ведомого». Естественно, что такую конфигурацию следует настроить соответствующим образом в обоих микроконтроллерах.
         Микроконтроллеры PIC24F могут обмениваться по интерфейсу SPI только 8- или 16-битными данными, поэтому при подключении внешних устройств, требующих иного числа бит (например, 12, 14, 20 или 24, что характерно для микросхем АЦП), необходимо использовать определенные программные ухищрения для обработки данных такой разрядности.
         Для программирования обмена данными по интерфейсу SPI в микроконтроллерах PIC24F имеется несколько регистров, функции и назначение отдельных битов которых мы сейчас рассмотрим. Программный интерфейс SPI включает следующие регистры:
    • SPIxBUF — буфер данных, который может содержать передаваемые или принимаемые данные. Этот буфер является общим «виртуальным» пространством памяти для регистра передачи SPIxTXB, в который поступают передаваемые данные, и регистра приема SPIxRXB, в который поступают принимаемые с линии данные;
    • SPIxCON1 и SPIxCON2 — регистры управления для установки различных параметров обмена;
    • SPIxSTAT — регистр состояния, в котором фиксируются состояния интерфейса;
         SPIxSR — 16-битный сдвиговый регистр, который выполняет поразрядную выдачу данных на выход или поразрядное считывание данных, поступающих на вход.
         Важное замечание: биты конфигурации в регистрах управления SPIxCON1 и SPIxCON2 невозможно установить при работающем интерфейсе, поэтому перед конфигурированием SPI-интерфейса следует его отключить, сбросив бит SPIEN в соответствующем регистре состояния SPIxSTAT.
         Рассмотрим назначение отдельных битов регистров управления и состояния.
         В Табл. 7.1 приведено описание битов регистра SPIxCON1.

    Таблица 7.1. Назначение битов регистра SPIxCON1
    Позиция бита Обозначение Описание
    15-13   Читаются как 0
    12 DISSCK Позволяет отключить вывод SCKx (только если задан режим «ведущего»):
    1 – внутренняя функция тактирования SPI отключена, вывод микроконтроллера работает как цифровой ввод/вывод;
    0 – на выводе генерируются тактовые импульсы для SPI"интерфейса
    11 DISSDO Позволяет отключить вывод SDOx:
    1 – вывод SDOx не используется в модуле SPI и работает как цифровой ввод/вывод;
    0 – вывод SDOx управляется модулем SPI"интерфейса
    10 MODE16 Разрядность данных при обмене по SPI:
    1 – используются 16"битные данные;
    0 – используются 8"битные данные
    9 SMP Определяет режим захвата входных данных. В режиме «ведущего» установки бита имеют такой смысл:
    1 – входные данные читаются в конце интервала времени, в течение которого они выставлены;
    0 – входные данные читаются посредине интервала.
    В режиме «ведомого» бит должен быть сброшен
    8 CKE Определяет, на каком шаге происходит изменение данных на выходе:
    1 – при переходе из активного состояния в режим ожидания;
    0 – при переходе из режима ожидания в активное состояние
    7 SSEN Способ использования бита SSx (в режиме «ведомого»):
    1 – бит SSx используется в режиме «ведомого»;
    0 – бит SSx не используется и управляется как порт ввода/вывода
    6 CKP Выбор полярности импульса синхронизации:
    1 – режим ожидания соответствует высокому, а активный режим – низкому уровню;
    0 – режим ожидания соответствует низкому, а активный режим – высокому уровню
    5 MSTEN Разрешение работы в режиме «ведущего»:
    1 – работа в режиме «ведущего»;
    0 – работа в режиме «ведомого»
    4-2 SPRE2–SPRE0 Установка коэффициента деления второго предделителя(режим «ведущего»):
    111 – соотношение 1:1
    110 – соотношение 2:1
    . . .
    000 – соотношение 8:1
    1-0 PPRE1–PPRE0 Установка коэффициента деления второго предделителя(режим «ведущего»):
    111 – соотношение 1:1
    110 – соотношение 4:1
    001 – соотношение 16:1
    000 – соотношение 64:1

    Таблица 7.2. Назначение битов регистра SPIxCON2
    Позиция бита Обозначение Описание
    15 FRMEN Поддержка оконного интерфейса SPI:
    1 – при обмене данными используется «окно»;
    0 – при обмене данными не используется «окно»
    14 SPIFSD Режим синхронизации окна:
    1 – сигнал «окна» устанавливает «ведомый» при вводе;
    0 – сигнал «окна» устанавливает «ведущий» при выводе
    13 SPIFPOL Установка полярности сигнала «окна»:
    1 – активный уровень «окна» соответствует высокому уровню сигнала;
    0 – активный уровень «окна» соответствует низкому уровню сигнала
    12-2   Читается как 0
    1 SPIFE Смещение сигнала «окна» по отношению к стробу тактовой частоты:
    1 – устанавливается одновременно с первым импульсом частоты синхронизации;
    0 – устанавливается до прихода первого импульса синхронизации
    0 SPIBEN Разрешение работы с расширенным буфером:
    1 – использование расширенного буфера разрешено;
    0 – использование расширенного буфера запрещено (по умолчанию)

    Таблица 7.3. Назначение битов регистра SPIxSTAT
    Позиция бита Обозначение Описание
    15 SPIEN Разрешение работы интерфейса SPI:
    1 – разрешает работу SPI"интерфейса и конфигурирует выводы SCKx, SDOx и SSx для SPI;
    0 – запрещает работу интерфейса SPI
    14   Читаются как 0
    13 SPISIDL Работа в режиме «холостого хода» микроконтроллера:
    1 – прекращаются все операции;
    0 – обмен данными продолжается
    12-11   Читаются как 0
    10-8 SPIBEC2–SPIBEC0 Подсчет элементов данных в буфере SPI. Если задан режим «ведущего», то количество элементов подсчитывается, если задан режим «ведомого», то подсчет не производится
    7 SRMPT Показывает состояние регистра сдвига (SPIxSR) (только в расширенном режиме):
    1 – регистр сдвига пуст и может принимать или отправлять данные;
    0 – регистр сдвига содержит данные
    6 SPIROV Бит переполнения при приеме:
    1 – получен следующий байт/слово. Микроконтроллер не прочитал предыдущие данные;
    5 SRXMPT Бит состояния FIFO"буфера приема (только для расширенного режима):
    1 – FIFO"буфер приема пуст;
    0 – FIFO"буфер приема не пуст
    4-2 SISEL2–SISEL0 Установка режима прерывания буфера SPI (только в расширенном режиме):
    111 – прерывание инициируется при переполнении буфера передачи;
    110 – прерывание инициируется после того, как последний бит сдвинут в регистр сдвига SPIxSR;
    101 – прерывание инициируется после того, как последний бит выдвинут из регистра сдвига SPIxSR;
    . . .
    000 – прерывание инициируется после того, как прочитан последний байт буфера
    1 SPIxTBF Состояние буфера передачи:
    1 – передача данных не началась, буфер SPIxTXB заполнен;
    0 – началась передача данных, буфер передачи SPIxTXB пуст
    0 SPIxRBF Состояние буфера приема:
    1 – прием данных завершен, буфер приема SPIxRXB заполнен;
    0 – прием данных не завершен, буфер приема SPIxRXB пуст


    7.2. Практическое программирование обмена данными по SPI

         Рассмотрим некоторые практические аспекты программирования интерфейса SPI в микроконтроллерах PIC24F. Многие устройства, выпускаемые промышленностью, преобразуют последовательный код на входе в параллельный на выходе с использованием тех или иных модификаций протокола SPI. В нашем первом проекте данные будут выводиться через интерфейс SPI1 микроконтроллера PIC24FJ128GA010 в регистр сдвига на микросхеме CD4094, на выходе которой получим параллельный код, управляющий включением/выключением светодиодов по принципу «бегущей строки». Переключение светодиодов осуществляется с интервалом в 1 секунду. На Рис. 7.5 представлена схема аппаратной части проекта.

    Рис. 7.5. Аппаратная часть проекта

         Для обмена данными в этой схеме используется интерфейс SPI1 микрокон троллера. Данные поступают с вывода SDO1 микроконтроллера на вход D регистра сдвига, а синхронизация передачи выполняется микроконтроллером по линии SCK1. Тактовые импульсы поступают на входы CLK и STB регистра CD4094.
         Программную часть проекта разработаем в среде MPLAB IDE с помощью мастера проектов. Включим в проект Си-файл со следующим исходным текстом:

    #include <p24fj128ga010.h>
    _CONFIG2(FCKSM_CSDCMD&OSCIOFNC_ON&POSCMOD_HS&FNOSC_PRI)
    #define SYSCLK 8000000
    #define t1 1
    #define PREG SYSCLK/2*t1/256
    char c1;
    void __attribute__ ((interrupt)) _T1Interrupt(void)
    {
       _T1IF = 0;
       SPI1BUF = c1;
       while (_SPI1IF == 0);
       _SPI1IF = 0;
       c1 = c1<<1;
       if (c1 == 0x0)
       c1 = 0x1;
    }
    void SPI1Init(void)
    {
       SPI1STATbits.SPIEN = 0;
       SPI1CON1 = 0;
       SPI1CON1bits.DISSCK = 0;
       SPI1CON1bits.DISSDO = 0;
       SPI1CON1bits.MODE16 = 0;
       SPI1CON1bits.MSTEN = 1;
       SPI1STATbits.SPIEN = 1;
    }
    void main(void)
    {
       c1 = 0x1;
       PR1 = PREG;
       TMR1 = 0;
       T1CON = 0x8030;
       _T1IF = 0;
       _T1IP = 4;
       _T1IE = 1;
       _SPI1IF = 0;
       SPI1Init();
       while(1);
    }

         В этой программе определена функция SPI1Init, которая выполняет инициализацию интерфейса SPI1. Непосредственно перед инициализацией нужно отключить интерфейс SPI путем сброса бита SPIEN регистра состояния SPI1STAT:
    SPI1STATbits.SPIEN = 0;

         Поскольку мы будем по отдельности изменять каждый бит конфигурации, то предварительно желательно очистить регистр управления интерфейса SPI1:
    SPI1CON1 = 0;

         В данной конфигурации интерфейса мы будем использовать две сигнальных линии — синхронизации SCK и выходных данных SDO, которые будут управляться самим микроконтроллером. По этой причине биты DISSCK и DISSDO регистра управления SPI1CON должны быть сброшены:

    SPI1CON1bits.DISSCK = 0;
    SPI1CON1bits.DISSDO = 0;

         Далее в нашем проекте используется 8-битный сдвиговый регистр CD4094, поэтому следует указать количество обрабатываемых битов. Для 8-битной посылки данных бит MODE16 регистра управления должен быть сброшен:
    SPI1CON1bits.MODE16 = 0;

         Микроконтроллер в данном проекте будет выполнять синхронизацию обмена данными, т. е. работать в режиме «ведущего», поэтому следует установить бит MSTEN:
    SPI1CON1bits.MSTEN = 1;

         На данный момент все необходимые установки закончены, поэтому включаем SPI-интерфейс:
    SPI1STATbits.SPIEN = 1;

         Это все, что касается функции инициализации SPI-интерфейса SPI1Init.
         В нашей программе не используется режим синхронизации кадра, который задается в регистре SPI1CON2, поэтому содержимое этого регистра можно оставить без изменения (по умолчанию покадровая синхронизация не используется).
         Поскольку программа выполняет периодические действия по пересылке данных по SPI-интерфейсу, то нам понадобится таймер. В данном случае мы используем 16-битный таймер типа А, в качестве которого будет задействован Таймер 1 микроконтроллера. Пересылка байта данных по SPI-интерфейсу выполняется в обработчике прерывания Таймера 1 _T1Interrupt, который будет вызываться каждую секунду.
         Собственно пересылка байта данных по SPI осуществляется командой
    SPI1BUF = c1;

         функции-обработчика. После записи данных в буфер SP1BUF нужно дождаться окончания передачи данных, что и выполняется в цикле while:
    while (_SPI1IF == 0);

         Здесь анализируется флаг прерывания _SPI1IF, который по завершении передачи устанавливается в 1. Затем биты данных (байт c1) сдвигаются влево, обеспечивая во времени эффект «бегущей строки». Флаг прерывания _SPI1IF должен быть очищен программно в обработчике прерывания таймера.
         В основной программе выполняется инициализация Таймера 1 для работы с интервалом времени в 1 с, а также инициализация SPI-интерфейса (функция SPI1Init).
         В библиотеке 16-битных функций управления периферийными устройствами имеется целый ряд функций, позволяющих облегчить разработку программного интерфейса SPI. Чтобы иметь возможность использовать функции библиотеки, разработчик должен включить в проект файл заголовка spi.h и добавить библиотеку libpPIC24Fxxx-elf.a или libpPIC24Fxxx-coff.a (тип файла зависит от принятого в компиляторе формата объектного файла). Библиотечный файл находится в каталоге \...\Program Files\Microchip\MPLAB C для PIC24\lib.
         Можно выполнить небольшую модификацию исходного текста нашей программы, включив туда библиотечные функции для SPI-интерфейса. С учетом таких изменений исходный текст программы будет выглядеть так:

    #include <p24fj128ga010.h>
    #include <spi.h>
    _CONFIG2(FCKSM_CSDCMD&OSCIOFNC_ON&POSCMOD_HS&FNOSC_PRI)
    #define SYSCLK 8000000
    #define t1 1
    #define PREG SYSCLK/2*t1/256
    char c1;
    void __attribute__ ((interrupt)) _T1Interrupt(void)
    {
       _T1IF = 0;
       WriteSPI1(c1);
       while (_SPI1IF == 0);
       _SPI1IF = 0;
       c1 = c1<<2;
       if (c1 == 0x0)
       c1 = 0x1;
    }
    void main(void)
    {
       unsigned int SPICON1Value;
       unsigned int SPICON2Value;
       unsigned int SPISTATValue;
       c1 = 0x1;
       PR1 = PREG;
       TMR1 = 0;
       T1CON = 0x8030; // I?aaaaeeoaeu 1:256
       _T1IF = 0;
       _T1IP = 4;
       _T1IE = 1;
       _SPI1IF = 0;
       CloseSPI1();
       SPICON1Value = ENABLE_SCK_PIN & ENABLE_SDO_PIN & SPI_MODE16_OFF &
       SPI_SMP_ON & SPI_CKE_OFF &
       SLAVE_ENABLE_OFF &
       CLK_POL_ACTIVE_HIGH &
       MASTER_ENABLE_ON &
       SEC_PRESCAL_7_1 &
       PRI_PRESCAL_64_1;
       SPICON2Value = FRAME_ENABLE_OFF & FRAME_SYNC_OUTPUT;
       SPISTATValue = SPI_ENABLE;
       OpenSPI1(SPICON1Value, SPICON2Value, SPISTATValue );
       while(1);
    }

         Мы включили в исходный текст программы файл заголовка:
    #include <spi.h>

         а также несколько библиотечных функций. Вот их назначение:
    • функция CloseSPI1() отключает модуль SPI. Это может понадобиться либо при завершении работы с интерфейсом, либо при необходимости записи новой конфигурации в регистры SPI1CON1 и SPI1CON2; функция OpenSPI1 выполняет запись битов конфигурации в регистры интерфейса. Смысл мнемонических обозначений можно посмотреть в файле spi.h.
         В программе определены три целочисленные переменные (SPICON1Value, SPICON2Value и SPISTATValue), в которые помещается логическая комбинация (AND) всех битов конфигурации для регистров SPI1CON1, SPI1CON2 и SPI1STAT соответственно.
         Наш следующий проект более сложен по сравнению с предыдущим и демонстрирует обмен данными по протоколу SPI. В этом проекте используется два микроконтроллера PIC24F, один из них будет передавать данные в режиме «ведущего», а другой — принимать данные в режиме «ведомого». Оба микроконтроллера работают на тактовой частоте 8 МГц. После приема символов «ведомый» микроконтроллер отправляет их на другое устройство или персональный компьютер по асинхронному последовательному порту 2. Аппаратная часть проекта (без схемы подключения асинхронного интерфейса) показана на Рис. 7.6.

    Рис. 7.6. Схема обмена данными по протоколу SPI

         Здесь используется двухпроводная конфигурация. Оба микроконтроллера для обмена данными используют интерфейс SPI1. Синхронизация передачи данных выполняется сигналом на выводе SCK1 ведущего, а данные передаются с линии SDO1 ведущего на вход SDI1 ведомого. Программная часть проекта разработана в MPLAB IDE и содержит две отдельные программы: одну – для «ведущего» микроконтроллера, а другую — для «ведомого». Исходный текст программы для ведущего показан далее.

    #include <p24fj128ga010.h>
    _CONFIG2(FCKSM_CSDCMD&OSCIOFNC_ON&POSCMOD_HS&FNOSC_PRI)
    void SPI1MasterInit(void)
    {
       SPI1STATbits.SPIEN = 0;
       SPI1CON1 = 0;
       SPI1CON1bits.DISSCK = 0;
       SPI1CON1bits.DISSDO = 0;
       SPI1CON1bits.MODE16 = 0;
       SPI1CON1bits.MSTEN = 1;
       SPI1CON2bits.FRMEN = 0;
       SPI1STATbits.SPIROV = 0;
       SPI1STATbits.SPIEN = 1;
    }
    void main(void)
    {
       char s1[] = "Test string for SPI Master-Slave Configuration";
       char *ps1 = s1;
       _SPI1IF = 0;
       SPI1MasterInit();
       while (*ps1 != 0)
       {
         SPI1BUF = *ps1++;
         while (_SPI1IF == 0);
         _SPI1IF = 0;
       }
       SPI1STATbits.SPIEN = 0;
       while(1);
    }

         Программа просто передает текстовую строку (переменная s1) от ведущего устройства к ведомому. Настройка канала передачи в режиме «ведущего» выполняется функцией SPI1MasterInit. Перед началом конфигурирования необходимо отключить интерфейс SPI, сбросив бит SPIEN. Система сама будет управлять каналом передачи, поэтому в этой функции выбирается режим внутренней синхронизации с помощью следующих установок:

    SPI1CON1bits.DISSCK = 0;
    SPI1CON1bits.DISSDO = 0;

         Программа будет передавать 8-битные символы, поэтому выбирается 8-битный режим работы и функция «ведущего»:

    SPI1CON1bits.MODE16 = 0;
    SPI1CON1bits.MSTEN = 1;

         По окончанию настроек следует разрешить работу интерфейса SPI, установив бит SPIEN. После выполнения функции SPI1MasterInit в основной программе выполняется последовательная, байт за байтом, передача всех символов строки s1 до момента, пока не будет достигнут нулевой символ (строка s1 является стандартной строкой ANSI C с завершающим нулем). Для передачи одного байта достаточно записать его в регистр SPI1BUF, после чего микроконтроллер выполнит все необходимые циклы синхронизации для передачи каждого бита:
    SPI1BUF = *ps1++;

         Одновременно с записью символа в буфер указатель ps1 продвигается по строке к следующему символу.
         О завершении передачи очередного символа свидетельствует установка флага прерывания _SPI1IF, поэтому его значение проверяется в цикле while:
    while (_SPI1IF == 0);

         По завершению передачи строки «ведомому» интерфейс SPI «ведущего» отключается:
    SPI1STATbits.SPIEN = 0;

         Программа управления приемом данных «ведомого» микроконтроллера выглядит следующим образом:

    #include <p24fj128ga010.h>
    _CONFIG2(FCKSM_CSDCMD&OSCIOFNC_ON&POSCMOD_HS&FNOSC_PRI)
    #define SYSCLK 8000000
    #define BAUDRATE2 9600
    #define BAUDRATEREG2 SYSCLK/8/BAUDRATE2-1
    #define UART2_TX_TRIS TRISFbits.TRISF5
    #define UART2_RX_TRIS TRISFbits.TRISF4
    char c1;
    void SPI1SlaveInit(void)
    {
       SPI1STATbits.SPIEN = 0;
       SPI1CON1 = 0;
       SPI1CON1bits.DISSCK = 0;
       SPI1CON1bits.DISSDO = 0;
       SPI1CON1bits.MODE16 = 0;
       SPI1CON1bits.MSTEN = 0;
       SPI1CON1bits.SMP = 0;
       SPI1CON2bits.FRMEN = 0;
       SPI1STATbits.SPIROV = 0;
       SPI1STATbits.SPIEN = 1;
    }
    void UART2Init()
    {
       UART2_TX_TRIS = 0;
       UART2_RX_TRIS = 1;
       U2BRG = BAUDRATEREG2;
       U2MODE = 0;
       U2MODEbits.BRGH = 1;
       U2MODEbits.UARTEN = 1;
       U2STA = 0;
       U2STAbits.UTXEN = 1;
       IFS1bits.U2RXIF = 0;
    }
    void UART2PutChar(char Ch)
    {
       while(U2STAbits.UTXBF == 1);
       U2TXREG = Ch;
    }
    void __attribute__((__interrupt__)) _SPI1Interrupt(void)
    {
       _SPI1IF = 0;
       SPI1STATbits.SPIROV = 0;
    }
    void main(void)
    {
       int c1;
       _SPI1IF = 0;
       _SPI1IE = 1;
       UART2Init();
       SPI1SlaveInit();
       while (1)
       {
         while (_SPI1IF == 0);
         c1 = SPI1BUF;
         if (c1 == 0)
         break;
         UART2PutChar(c1);
       }
       SPI1STATbits.SPIEN = 0;
       while(1);
    }

         В этой программе, как и в предыдущей, перед приемом данных следует сконфигурировать SPI-интерфейс, для чего используется функция SPI1SlaveInit.
         Здесь устанавливаются почти те же биты, что в функции SPI1MasterInit, за исключением бита MSTEN, который, будучи сброшенным, указывает на работу в режиме «ведомого»:
    SPI1CON1bits.MSTEN = 0;

         Для интерфейса SPI1 инициализируется обработчик прерывания, основной функцией которого является очистка флага прерывания и флага переполнения буфера приема SPIROV. Символ, принятый по интерфейсу SPI1, сохраняется в переменной c1, и если он равен 0 (конец строки), то происходит выход из цикла чтения while (1). Признаком того, что в буфере приема имеется байт, служит установка флага прерывания _SPI1IF в 1, после чего байт можно считывать в переменную c1:

    while (_SPI1IF == 0);
    c1 = SPI1BUF;

         В программе объявлены и функции управления работой асинхронного последовательного порта 2, который мы подробно рассмотрим в главе 10, а сейчас просто воспользуемся функциями UART2Init и UART2PutChar. Первая выполняет инициализацию последовательного интерфейса 2, а вторая передает 8-битный символ в асинхронный порт. При выходе из цикла while интерфейс SPI1 отключается.
         Исходный текст программы можно значительно упростить, если обработку принимаемых по интерфейсу SPI данных выполнить полностью в обработчике прерывания _SPI1Interrupt, как показано далее:

    #include <p24fj128ga010.h>
    _CONFIG2(FCKSM_CSDCMD&OSCIOFNC_ON&POSCMOD_HS&FNOSC_PRI)
    #define SYSCLK 8000000
    #define BAUDRATE2 9600
    #define BAUDRATEREG2 SYSCLK/8/BAUDRATE2-1
    #define UART2_TX_TRIS TRISFbits.TRISF5
    #define UART2_RX_TRIS TRISFbits.TRISF4
    char c1;
    void SPI1SlaveInit(void)
    {
       SPI1STATbits.SPIEN = 0;
       SPI1CON1 = 0;
       SPI1CON1bits.DISSCK = 0;
       SPI1CON1bits.DISSDO = 0;
       SPI1CON1bits.MODE16 = 0;
       SPI1CON1bits.MSTEN = 0;
       SPI1CON1bits.SMP = 0;
       SPI1CON2bits.FRMEN = 1;
       SPI1STATbits.SPIROV = 0;
       SPI1STATbits.SPIEN = 1;
    }
    void UART2Init()
    {
       UART2_TX_TRIS = 0;
       UART2_RX_TRIS = 1;
       U2BRG = BAUDRATEREG2;
       U2MODE = 0;
       U2MODEbits.BRGH = 1;
       U2MODEbits.UARTEN = 1;
       U2STA = 0;
       U2STAbits.UTXEN = 1;
       IFS1bits.U2RXIF = 0;
    }
    void UART2PutChar(char Ch)
    {
       while(U2STAbits.UTXBF == 1);
       U2TXREG = Ch;
    }
    void __attribute__((__interrupt__)) _SPI1Interrupt(void)
    {
       IFS0bits.SPI1IF = 0;
       SPI1STATbits.SPIROV = 0;
       c1 = SPI1BUF;
       if (c1 != 0)
       UART2PutChar(c1);
    }
    void main(void)
    {
       int c1;
       _SPI1IF = 0;
       _SPI1IP = 6;
       _SPI1IE = 1;
       UART2Init();
       SPI1SlaveInit();
       while(1);
    }

         Как видно из листинга, исходный текст программы значительно упростился.
         Тем не менее, если система работает в реальном времени, то большие объемы программного кода помещать в обработчик прерывания нежелательно, иначе это скажется на производительности всей системы, особенно если имеются другие обработчики прерываний.
         Если разработчик при создании программ данного проекта захочет воспользоваться библиотечными функциями, то тексты программ изменятся. Исходный текст программы для «ведущего» микроконтроллера может выглядеть так:

    #include <p24fj128ga010.h>
    #include <spi.h>
    _CONFIG2(FCKSM_CSDCMD&OSCIOFNC_ON&POSCMOD_HS&FNOSC_PRI)
    void main(void)
    {
       char s1[] = "LIB SPI-functions Test!";
       char *ps1 = s1;
       unsigned int SPICON1Value;
       unsigned int SPICON2Value;
       unsigned int SPISTATValue;
       CloseSPI1();
       SPICON1Value = ENABLE_SCK_PIN & ENABLE_SDO_PIN & SPI_MODE16_OFF &
       SPI_SMP_ON & SPI_CKE_OFF &
       SLAVE_ENABLE_OFF &
       CLK_POL_ACTIVE_HIGH &
       MASTER_ENABLE_ON &
       SEC_PRESCAL_7_1 &
       PRI_PRESCAL_64_1;
       SPICON2Value = FRAME_ENABLE_OFF & FRAME_SYNC_OUTPUT;
       SPISTATValue = SPI_ENABLE & SPI_IDLE_CON &
       SPI_RX_OVFLOW_CLR;
       OpenSPI1(SPICON1Value,SPICON2Value,SPISTATValue );
       _SPI1IF = 0;
       while (*ps1 != 0)
       {
         WriteSPI1(*ps1++);
         while (_SPI1IF == 0);
         _SPI1IF = 0;
       }
       CloseSPI1();
       while(1);
    }

         В этой программе используются библиотечные функции CloseSPI1, OpenSPI1 и WriteSPI1. С помощью функции OpenSPI1 конфигурируется и включается SPI-интерфейс 1, функция CloseSPI1 отключает интерфейс, а функция WriteSPI1 передает байт (или слово). Исходный текст программы для «ведомого» микроконтроллера выглядит следующим образом:

    #include <p24fj128ga010.h>
    #include <spi.h>
    _CONFIG2(FCKSM_CSDCMD&OSCIOFNC_ON&POSCMOD_HS&FNOSC_PRI)
    #define SYSCLK 8000000
    #define BAUDRATE2 9600
    #define BAUDRATEREG2 SYSCLK/8/BAUDRATE2-1
    #define UART2_TX_TRIS TRISFbits.TRISF5
    #define UART2_RX_TRIS TRISFbits.TRISF4
    char datard;
    void UART2Init()
    {
       UART2_TX_TRIS = 0;
       UART2_RX_TRIS = 1;
       U2BRG = BAUDRATEREG2;
       U2MODE = 0;
       U2MODEbits.BRGH = 1;
       U2MODEbits.UARTEN = 1;
       U2STA = 0;
       U2STAbits.UTXEN = 1;
       IFS1bits.U2RXIF = 0;
    }
    void UART2PutChar(char Ch)
    {
       while(U2STAbits.UTXBF == 1);
       U2TXREG = Ch;
    }
    void main(void)
    {
       unsigned int SPICON1Value;
       unsigned int SPICON2Value;
       unsigned int SPISTATValue;
       UART2Init();
       CloseSPI1();
       SPICON1Value = ENABLE_SCK_PIN & ENABLE_SDO_PIN & SPI_MODE16_OFF &
       SPI_SMP_OFF & SPI_CKE_OFF &
       SLAVE_ENABLE_OFF &
       CLK_POL_ACTIVE_HIGH &
       MASTER_ENABLE_OFF &
       SEC_PRESCAL_7_1 &
       PRI_PRESCAL_64_1;
       SPICON2Value = FRAME_ENABLE_OFF & FRAME_SYNC_OUTPUT;
       SPISTATValue = SPI_ENABLE & SPI_IDLE_CON &
       SPI_RX_OVFLOW_CLR;
       OpenSPI1(SPICON1Value,SPICON2Value,SPISTATValue);
       while(1)
       {
         while (DataRdySPI1() == 0);
         datard = ReadSPI1();
         UART2PutChar(datard);
       }
    }

         Напомню, что при использовании библиотечных функций для работы с интерфейсом SPI в исходный текст программы следует включить объявление заголовочного файла spi.h, а в проект включить один из библиотечных файлов libpPIC24Fxxx-elf.a или libpPIC24Fxxx-coff.a.

    Комментарии