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


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

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

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

Какой средой программирования вы пользуетесь?


MPLab/MPLabX
MicroC
MicroBasic
MicroPascal
Другой


Последние материалы
  • Тестовая плата для отладки программ на микроконтроллере PIC18F4550
  • Кнопка On/OFF на PIC12F629.
  • Часы с синхронизацией от китайского будильника
  • ШИМ регулятор на PIC16F628A.
  • Счетчики прямого и обратного счета на PIC16F628A.
  • Таймер отключения питания для мультиметра и не только.
  • Измеритель напряжения и тока
  • Маршрутный компьютер для электровелосипеда
  • Простой двухканальный термометр на PIC16F690 и датчиках DS18B20
  • Электронная "Незабудка" для забывчивых
  • Популярные материалы
    Случайная книга
    Программирование устройств на PIC микроконтроллерах » Справочник » Микроконтроллеры PIC. Архитектура и программирование. Часть 8. Интерфейс I2C микроконтроллеров PIC24F
    Микроконтроллеры PIC. Архитектура и программирование. Часть 8. Интерфейс I2C микроконтроллеров PIC24F
    Автор публикации: alex Просмотров: 15197 Добавлен: 27-09-2012, 14:08 Комментарии: 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

         Интерфейс I2C является одним из наиболее популярных среди разработчиков оборудования (читается как «I—square—C»). В настоящее время выпускается огромное число микросхем, использующих этот интерфейс. Микроконтроллеры семейства PIC24F имеют развитые аппаратно-программные средства для организации обмена данными с периферийными устройствами по шине I2C. Многие разработчики недолюбливают работу с этим интерфейсом из-за его кажущейся сложности. В отличие от интерфейса SPI, который кажется более простым при изучении, I2C требует более серьезных усилий, но программировать его несложно.

    8.1. Принципы функционирования интерфейса I2C

         Рассмотрим принципы обмена данными по протоколу I2C. Стандарт I2C реализован как двухпроводной последовательный интерфейс, разработанный компанией «Philips Corp.» для работы c максимальной скоростью передачи данных 100 Кбит/с. Впоследствии стандарт стал поддерживать более скоростные режимы работы шины (400 Кбит/с и 1Мбит/с). При этом к одной шине I2C могут быть подключены устройства с различными скоростями доступа, если скорость передачи данных будет удовлетворять требованиям самого низкоскоростного устройства.
         Протокол передачи данных по шине I2C разработан таким образом, чтобы гарантировать надежный качественный прием/передачу данных. При обмене данными одно устройство является «ведущим» и инициирует такой обмен, а также формирует сигналы синхронизации. Другое устройство, «ведомое», может начать передачу/прием данных только по команде ведущего шины.
         Протокол I2C использует две сигнальные линии, по одной из которых подается сигнал синхронизации, обычно обозначаемый как SCL, а по другой, обозначаемой обычно как SDA, передаются или принимаются данные. К шине I2C можно подключать несколько устройств, при этом линии SCL и SDA являются общими (Рис. 8.1).

    Рис. 8.1. Схема подключения устройств по интерфейсу I2C

         К сигнальным линиям необходимо подключить так называемые подтягивающие резисторы, обозначенные на схеме как Rp, присоединив их к источнику питания (для стандартной TTL-логики это +5В). Подтягивающие резисторы (их значение может находиться в диапазоне 1-10 K) нужны для фиксации уровня сигналов, поскольку спецификация I2C предусматривает использование устройств, имеющих выходные каскады с открытым коллектором или стоком (open drain). Кроме того, что более существенно, поскольку линии SCL и SDA являются двунаправленными, то такая аппаратная конфигурация обеспечивает функцию «монтажного» И (AND), что позволяет передавать сигналы в обоих направлениях.
         Конфигурация шины I2C позволяет работать с одним или несколькими ведущими, что дает возможность создавать разветвленные высокоскоростные сети обмена данными. В подавляющем большинстве случаев на шине находится только один ведущий и несколько ведомых устройств. «Ведущий» обеспечивает синхронизацию обмена данными, генерируя синхроимпульсы по линии SCL.
         Обмен данными по шине I2C инициируется «ведущим» устройством, которое должно обеспечить формирование стартовой (в начале обмена) и стоповой (в конце обмена) последовательностей сигналов. Эти последовательности показаны на Рис. 8.2.

    Рис. 8.2. Старт-стоповая последовательность сигналов интерфейса I2C

         Протокол I2C требует, чтобы сигнал на линии данных SDA оставался неизменным при ВЫСОКОМ уровне сигнала SCL. Перепад сигнала на линии SDA из ВЫСОКОГО уровня в НИЗКИЙ при ВЫСОКОМ уровне на линии SCL
         формируется «ведущим» и указывает на начало обмена данными. Если при ВЫСОКОМ уровне сигнала на линии SCL уровень сигнала на линии SDA меняется с НИЗКОГО на ВЫСОКИЙ, то эта последовательность сигналов интерпретируется как завершение операции обмена данными на шине.
         Данные передаются по шине в виде последовательности из 8 бит (хотя в некоторых конфигурациях допускается передача и большего числа бит), причем первым в последовательности идет старший значащий бит (MSB).
         В практическом плане каждый бит удобно передавать по нарастающему фронту сигнала SCL. Напомню, что при ВЫСОКОМ уровне сигнала SCL сигнал на линии SDA должен оставаться неизменным, если только это не стартовая или стоповая последовательность. После того как все 8 бит переданы, устройство, передающее данные, ожидает от устройства, принимающего данные, подтверждения приема (acknowledge) по линии SDA. Бит ответа фиксируется по нарастающему фронту 9-го импульса SCL. В момент передачи бита подтверждения отвечающее устройство принимает управление линией SDA на себя, а затем возвращает управление инициатору обмена.
         Поскольку к шине I2C можно подключать несколько устройств, то для обмена данными с конкретным устройством вначале нужно указать его адрес.
         В простейшем случае, например, для передачи данных в устройство вначале нужно передать адрес, а затем и сам байт данных. Эта последовательность показана на Рис. 8.3.

    Рис. 8.3. Запись байта данных в устройство на шине I2C

         Цикл записи данных в устройство начинается со стартовой последовательности. Затем передаются первые 8 бит, содержащие 7-разрядный адрес устройства (биты A7…A1) и команду чтения-записи (обозначена как ). Обычно команда записи передается НИЗКИМ уровнем, а команда чтения — ВЫСОКИМ. Бит 9 — это ответ устройства (обозначен как ACK), который фиксируется по фронту 9-го синхроимпульса и анализируется «ведущим». Следующие 8 бит являются битами данных. Как и при передаче адреса, 9-й бит также содержит ответ устройства. Цикл записи оканчивается стоповой последовательностью.
         Эта временная диаграмма нам очень пригодится, когда мы будем разрабатывать аппаратно-программные интерфейсы микроконтроллера PIC24F и расширителя ввода/вывода, работающего по I2C протоколу PCF8574A. Это устройство работает практически так, как показано на Рис. 8.3.
         Перейдем к рассмотрению возможностей микроконтроллеров PIC24F в части работы с интерфейсом I2C.

    8.2. Модуль интерфейса I2C микроконтроллеров PIC24F

         Микроконтроллеры семейства PIC24F предоставляют разработчику удобный аппаратно-программный интерфейс, позволяющий относительно легко разрабатывать системы обмена данными по шине I2C. Далее показана упрощенная функциональная схема одного из модулей I2C, интегрированных на кристалле микроконтроллера (Рис. 8.4).
         Модуль I2C-интерфейса состоит из передающей части, приемной части и логики управления. Для упрощения анализа на рисунке не показан ряд узлов и программных регистров. Передача данных осуществляется посредством параллельно-последовательного регистра I2CxTRN (x = 1, 2, …). Данные в регистр записываются программно через интерфейс внутренней шины, после чего побитово выдвигаются на линию SDAx синхронно с импульсами тактового сигнала на линии SCLx. Скорость передачи задается в регистре I2CxBRG и синхронизирована с тактовой частотой шины TCY/2.

    Рис. 8.4. Упрощенная функциональная схема модуля I2C

         Прием данных по интерфейсу I2C осуществляется через регистр сдвига I2CxRSR, куда данные вдвигаются побитово с линии SDAx синхронно с тактовыми импульсами SCLx. Принятые данные в параллельном коде помещаются в регистр I2CxRCV, откуда их может прочитать программа. Как правило, данные по шине I2C передаются и принимаются побайтово.
         Логика управления включает схемы синхронизации, а также регистры управления и состояния I2CxCON и I2CxSTAT, которые доступны программно и позволяют управлять процессом обмена по шине. Кроме указанных, модуль интерфейса содержит и другие регистры, которые мы при первоначальном знакомстве с функционированием шины I2C рассматривать не будем. Заинтересованным в более глубоком изучении материала читателям можно обратиться к фирменным руководствам фирмы Microchip для конкретных моделей микроконтроллеров.
         Далее в таблицах приводятся назначения отдельных битов программно-доступных регистров модуля интерфейса I2C. В Табл. 8.1 приводится описание битов регистра управления I2CxCON.

    Таблица 8.1. Назначение битов регистра управления I2CxCON
    Позиция бита Обозначение Назначение
    10 I2CEN Разрешение работы интерфейса:
    1 – разрешает функционирование I2Cx модуля и устанавливает выводы SDAx и SCLx в режим работы с интерфейсом I2C;
    0 – отключает модуль I2Cx. Все выводы интерфейса I2C могут использоваться в качестве дискретных портов ввода/вывода
    14   Читается как 0
    13 I2CSIDL Разрешение функционирования I2C интерфейса в режиме «холостого хода» микроконтроллера:
    1 – все операции на интерфейсе в режиме «холостого хода» прекращаются;
    0 – операции продолжаются при работе процессора в режиме «холостого хода»
    12 SCLREL Определяет, нужно ли передать управление тактовой частотой на выводе SCLx при функционировании в режиме «ведомого»:
    1 – управление передается;
    0 – линия SCLx удерживается в состоянии низкого уровня
    11 IPMIEN Разрешение режима расширенного управления:
    1 – режим расширенного управления разрешен. В этом режиме «ведущий» получает подтверждения о достоверности каждого адреса, по которому идет обращение;
    0 – режим расширенного управления запрещен
    10 A10M Разрешение работы с 10"битным адресом «ведомого»:
    1 – регистр I2CxADD будет сконфигурирован для работы с 10-битными адресами;
    0 – регистр I2CxADD работает с 7-битными адресами
    9 DISSLW Разрешение управления скоростью нарастания тактовых импульсов:
    1 – управление запрещено;
    0 – управление разрешено
    8 SMEN Разрешение работы с уровнями напряжения шины SMBus:
    1 – порты ввода/вывода переключаются уровнями напряжений SMBus;
    0 – работа с такими уровнями сигналов запрещена
    7-6   Специальное назначение, здесь не рассматриваются
    5 ACKDT Подтверждение приема данных (только при чтении данных в режиме «ведущего»):
    1 – послать сигнал NACK;
    0 – послать сигнал ACK
    4 ACKEN Разрешение подтверждения (только при чтении данных в режиме «ведущего»):
    1 – посылать подтверждение;
    0 – не посылать подтверждение
    3 RCEN Разрешение чтения (только в режиме «ведущего»):
    1 – разрешить работу в режиме чтения;
    0 – чтение не разрешено
    2 PEN Разрешение стоповой последовательности (только в режиме «ведущего»):
    1 – инициализировать стоповую последовательность на линиях SDAx и SCLx;
    0 – не разрешать стоповую последовательность
    1 RSEN Разрешение повторного старта (только в режиме «ведущего»):
    1 – инициировать повторный старт на линиях SDAx и SCLx;
    0 – не инициировать повторный старт
    0 SEN Разрешение стартовой последовательности (только в режиме «ведущего»):
    1 – инициировать стартовую последовательность на линиях SDAx и SCLx;
    0 – не инициировать стартовую последовательность

    В Табл. 8.2 описано назначение битов регистра состояния I2CxSTAT.

    Таблица 8.2. Назначение битов регистра состояния I2CxSTAT
    Позиция бита Обозначение Назначение
    15 ACKSTAT Подтверждение ответа (операции обмена данными в режимах «ведущий» и «ведомый»):
    1 – получен сигнал NACK от «ведомого»;
    0 – получен сигнал ACK от «ведомого»
    14 TRSTAT Состояние операции передачи данных (только при передаче данных в режиме «ведущего»):
    1 – выполняется передача (8 бит + ACK);
    0 – передача не выполняется
    13-11   Читается как 0
    10-9   Специальное назначение, здесь не рассматривается
    8 ADD10 Подтверждение 10"битного адреса:
    1 – адрес подтвержден;
    0 – адрес не подтвержден
    7 IWCOL Возникновение конфликта записи:
    1 – попытка записи в регистр I2CxTRN была неудачной, поскольку шина I2C занята;
    6 I2COV Бит переполнения:
    1 – байт был получен, когда в регистре I2CxRCV находятся предыдущие данные;
    0 – переполнение отсутствует
    5 D/A Индикация типа данных (только в режиме «ведомого»):
    1 – указывает, что последнее полученное значение является байтом данных;
    0 – указывает, что последнее полученное значение является адресом устройства
    4 P Стоповый бит:
    1 – стоповый бит является последним обнаруженным битом;
    0 – стоповый бит не является последним
    3 S Стартовый бит:
    1 – стартовый бит является последним обнаруженным битом;
    0 – стартовый бит не является последним
    2 R/W Направление передачи данных (только в режиме «ведомого»):
    1 – передача данных идет от «ведомого»;
    0 – передача данных идет «ведомому»
    1 RBF Состояние буфера приема:
    1 – прием завершен, в буфере I2CxRCV находятся данные;
    0 – прием не завершен, буфер I2CxRCV пуст
    0 TBF Состояние буфера передачи:
    1 – передача продолжается, в буфере I2CxTRN имеются данные;
    0 – передача завершена, буфер I2CxTRN пуст

         При работе в режиме «ведущего» модуль интерфейса I2C должен обеспечить генерацию тактового сигнала для шины. Как правило, шина I2C работает на одной из трех тактовых частот: 100 кГц, 400 кГц или 1МГц. Период тактовой частоты определяется как сумма минимального интервала времени пребывания сигнала SCLx в состоянии НИЗКОГО уровня и минимального времени пребывания сигнала в состоянии ВЫСОКОГО уровня. Для расчета тактовой частоты шины можно воспользоваться следующим выражением:
    FSCL = FCY/(I2CxBRG + (FCY/10000000) + 1).

         Тогда значение, которое нужно записать в регистр установки скорости обмена I2CxBRG, будет равно:
    I2CxBRG = (FCY/FSCL) – (FCY/10000000) – 1.

         В этих формулах FCY = FOSC/2. Значение, записанное в регистр I2CxBRG, не должно быть меньше 2.
         Можно облегчить себе задачу, не рассчитывая значение регистра I2CxBRG, а взяв его из Табл. 8.3.

    Таблица 8.3. Тактовые частоты шины I2C и их соответствие значениям регистра скорости обмена I2CxBRG
    Требуемая тактовая частота шины I2C FCY
    Значение регистра I2CxBRG
    Фактическая частота шины I2C
    Десятичное Шестнадцатеричное
    100 кГц 16 МГц 157 9D 100 кГц
    100 кГц 8 МГц 78 4E 100 кГц
    100 кГц 4 МГц 39 27 99 кГц
    400 кГц 16 МГц 37 25 404 кГц
    400 кГц 8 МГц 18 12 404 кГц
    400 кГц 4 МГц 9 9 385 кГц
    400 кГц 2 МГц 4 4 385 кГц
    1 МГц 16 МГц 13 D 1026 кГц
    1 МГц 8 МГц 6 6 1026 кГц
    1 МГц 4 МГц 3 3 909 кГц

         В следующем разделе мы рассмотрим на практике методы программирования обмена данными по интерфейсу I2C.

    8.3. Практическое использование интерфейса I2C

         Перейдем к практическим аспектам работы с интерфейсом I2C. В нашем первом проекте мы будем использовать микросхему популярного расширителя ввода/вывода PCF8574A, который позволяет преобразовать последовательный сигнал, поступающий по 2-проводному интерфейсу I2C, в параллельный 8-битный выходной код. Аппаратная часть проекта показана на Рис. 8.5.
         По этой схеме расширитель интерфейса PCF8574A по интерфейсу I2C подключен к микроконтроллеру PIC24FJ128GA010. Сигнальная линия SCL расширителя подключена к линии строба SCL1 первого интерфейса I2C микроконтроллера, а линия данных SDA — к линии данных SDA1. В этом проекте микроконтроллер выступает в роли «ведущего», передающего 8-битные данные «ведомому», в качестве которого выступает расширитель интерфейса PCF8574A.
         Данные в расширитель передаются каждые 0.5 секунды. На выходах P0…P7 параллельный код включает/выключает светодиоды, создавая эффект «бегущей строки». Резисторы R9 и R10 подключены к выходным каскадам с открытыми стоками микроконтроллера и расширителя, создавая таким образом эффект «монтажного И». Расширитель интерфейса PCF8574F имеет три бита адреса (A0…A2), которые установлены в 1.

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

         Полный 8-битный адрес устройства включает, кроме этих бит, оставшиеся старшие биты (они зафиксированы производителем) плюс самый младший бит, значение которого определяется типом операции. Этот бит обычно обозначается , он равен 0 при выполнении операции записи и единице — при выполнении операции чтения.
         Комбинированный адрес I2C-устройства, сформированный таким образом, будет разным для операций чтения и записи. Например, в нашем случае если выполняется запись в устройство, то его «комбинированный» адрес равен 0x7E.
         Если выполняется операция чтения, то в цикле передачи адреса на шину будет выставлено значение 0x7F.
         Этот пример приведен для конкретного устройства. Для других устройств, работающих с шиной I2C, принцип формирования адреса может отличаться, хотя, как правило, самый младший бит комбинированного адреса всегда определяется типом операции (чтение или запись).
         Обратимся к программной части проекта. Она разработана в среде MPLAB IDE с помощью мастера проектов по стандартной процедуре, которая нам известна. В созданный проект добавим файл программы со следующим текстом:

    #include <p24fj128ga010.h>
    _CONFIG2(FCKSM_CSDCMD&OSCIOFNC_ON&POSCMOD_HS&FNOSC_PRI)
    #define SYSCLK 8000000
    #define t1 0.5
    #define PREG SYSCLK/2*t1/256
    unsigned char AddrW = 0x7e;
    unsigned char ByteW = 0x1;
    void __attribute__ ((interrupt)) _T1Interrupt(void)
    {
       _T1IF = 0;
       if (ByteW == 0x0)
       ByteW = 0x1;
       I2C1CONbits.I2CEN = 1;
       I2C1BRG = 0x4E;
       I2C1CONbits.SEN = 1;
       while (I2C1CONbits.SEN != 0);
       I2C1TRN = AddrW;
       while (I2C1STATbits.TRSTAT == 1);
       I2C1TRN = ByteW;
       while (I2C1STATbits.TRSTAT == 1);
       I2C1CONbits.PEN = 1;
       I2C1CONbits.I2CEN = 0;
       ByteW = ByteW << 1;
    }
    void main(void)
    {
       _T1IF = 0;
       _T1IP = 4;
       _T1IE = 1;
       PR1 = PREG;
       TMR1 = 0;
       T1CON = 0x8030;
       while(1);
    }

         В этой программе для передачи байта данных от микроконтроллера к расширителю по интерфейсу I2C используется функция-обработчик _T1Interrupt прерывания Таймера 1, которое вызывается каждые 0.5 с. Рассмотрим более детально программный код обработчика, касающийся передачи байта данных по интерфейсу I2C.
         Напомню, для обмена данными с устройством, работающим с протоколом I2C, необходимо сначала передать адрес устройства, а затем данные. В нашей программе адрес расширителя PCF8574A хранится в переменной AddrW (0x7E), а байт данных для передачи — в переменной ByteW (его начальное значение равно 0x1). После очередного цикла передачи содержимое байта ByteW будет сдвигаться влево (оператор ByteW = ByteW << 1) до тех пор, пока не станет равным 0. После этого переменной ByteW будет вновь присвоено значение 0x1 (оператор if обработчика) и т. д.
         Полный цикл передачи данных устройству начинается с оператора
    I2C1CONbits.I2CEN = 1;

         Установленный бит I2CEN разрешает работу первого модуля интерфейса I2C. При выполнении этой команды на обеих линиях шины I2C устанавливается ВЫСОКИЙ уровень, что свидетельствует о готовности шины к обмену данными. Следующий оператор
    I2C1BRG = 0x4E;

         устанавливает частоту синхронизации обмена данными по I2C, равную 100 кГц, для тактовой частоты микроконтроллера, равной 8 МГц. Это значение можно рассчитать вручную, либо воспользоваться данными из Табл. 8.3. Новое значение загружается в тактовый генератор модуля интерфейса I2C, как только начинается процесс обмена данными.
         Следующая команда инициирует стартовую последовательность:
    I2C1CONbits.SEN = 1;

         Если установлен бит SEN регистра I2C1CON, то модуль интерфейса I2C формирует последовательность сигналов SCL и SDA, соответствующую стартовой последовательности. Этот бит сбрасывается аппаратно по завершении старта, поэтому программа может отследить завершение этого этапа обмена с помощью оператора
    while (I2C1CONbits.SEN != 0);

         После этого можно передавать адрес устройства, записав в регистр передачи содержимое переменной AddrW:
    I2C1TRN = AddrW;

         Для определения завершения передачи служит бит TRSTAT регистра состояния I2C1STAT, который устанавливается в 1 в начале обмена данными и сбрасывается в 0 по окончании этой процедуры. Для определения конца передачи байта состояние бита TRSTAT проверяется в цикле while:
    while (I2C1STATbits.TRSTAT == 1);

         Важное замечание: бит TRSTAT указывает, что обмен данными завершен и получен сигнал подтверждения ACK от «ведомого». Это означает, что никаких дополнительных проверок делать больше не нужно.
         Следующий шаг — передача собственно байта данных с проверкой завершения – выполняется двумя операторами:

    I2C1TRN = ByteW;
    while (I2C1STATbits.TRSTAT == 1);

         По завершении процесса обмена следует сформировать стоповую последовательность сигналов на шине I2C, что выполняется установкой бита PEN регистра управления I2C1CON:
    I2C1CONbits.PEN = 1;

         Затем можно отключить устройство от шины I2C до следующего цикла с помощью оператора:
    I2C1CONbits.I2CEN = 0;

         Собственно, это и есть основная часть работы, выполняемая нашей программой. В основной программе нужно только инициализировать и запустить Таймер 1 в режиме 16-битного таймера с прерыванием:

    _T1IF = 0;
    _T1IP = 4;
    _T1IE = 1;
    PR1 = PREG;
    TMR1 = 0;
    T1CON = 0x8030;

         Здесь в регистр периода PR1 Таймера 1 загружается значение PREG, необходимое для срабатывания Таймера 1 каждые 0.5 c. В регистр управления T1CON записывается значение, соответствующее коэффициенту деления 1:256, и устанавливается бит запуска Таймера 1.
         Чтобы облегчить программирование модуля I2C, для него, как и для других периферийных устройств, имеется библиотека 16-битных функций. Для ее использования в проект следует включить файл библиотеки libpPIC24Fxxx-elf.a или libpPIC24Fxxx-coff (в зависимости от того, какой объектный файл генерируется компилятором), а в исходный текст программы на языке Си — объявление заголовочного файла i2c.h. Модифицируем исходный текст предыдущей программы так, чтобы в нем использовались функции библиотеки периферийных модулей. Кроме того, для разнообразия изменим интервал срабатывания Таймера 1, установив его равным 2 с. В этом случае исходный текст программы может выглядеть так:

    #include <p24fj128ga010.h>
    #include <i2c.h>
    _CONFIG2(FCKSM_CSDCMD&OSCIOFNC_ON&POSCMOD_HS&FNOSC_PRI)
    #define SYSCLK 8000000
    #define t1 2
    #define PREG SYSCLK/2*t1/256
    unsigned char Done;
    void __attribute__ ((interrupt)) _T1Interrupt(void)
    {
       _T1IF = 0;
       Done = 1;
    }
    void main(void)
    {
       unsigned int config2, config1;
       unsigned char AddrW = 0x7e;
       unsigned char wByte = 0x1;
       config2 = 0x4E;
       _T1IF = 0;
       _T1IP = 4;
       _T1IE = 1;
       PR1 = PREG;
       TMR1 = 0;
       T1CON = 0x8030;
       config1 = (I2C_ON & I2C_IDLE_CON & I2C_CLK_HLD &
       I2C_IPMI_DIS & I2C_7BIT_ADD &
       I2C_SLW_DIS & I2C_SM_DIS &
       I2C_GCALL_DIS & I2C_STR_DIS &
       I2C_NACK & I2C_ACK_DIS & I2C_RCV_DIS &
       I2C_STOP_DIS & I2C_RESTART_DIS &
       I2C_START_DIS);
       Done = 0;
       while (1)
       {
         if (Done == 1)
         {
           Done = 0;
           OpenI2C1(config1,config2);
           IdleI2C1();
           StartI2C1();
           while(I2C1CONbits.SEN );
           MasterWriteI2C1(AddrW);
           while (I2C1STATbits.TRSTAT);
           MasterWriteI2C1(wByte);
           while (I2C1STATbits.TRSTAT);
           StopI2C1();
           while(I2C1CONbits.PEN);
           CloseI2C1();
           if (wByte == 0x0)
           wByte = 0x1;
           else
           wByte = wByte << 1;
         }
       }
    }

         Программа записывает данные на шину I2C в цикле while (1) основной программы при значении переменной Done, равном 1. Установка этой переменной выполняется в обработчике прерывания _T1Interrupt, который вызывается каждые 2 с.
         В этой программе используется несколько библиотечных функций для работы с модулями интерфейса I2C. Функция OpenI2C1 конфигурирует и включает первый модуль интерфейса I2C. В качестве параметров функция принимает значение, определяющее скорость обмена данными по шине (переменная config2), и требуемые настройки регистра управления I2C1CON (переменная config1). В данном случае интерфейс настроен на скорость обмена, соответствующую частоте SCL 100 кГц при тактовой частоте микроконтроллера 8 МГц.
         Смысл макросов переменной config1 будет понятен, если вы посмотрите содержимое заголовочного файла i2c.h
         После включения модуля интерфейса он переводит шину в свободное состояние (состояние ожидания) при помощи функции IdleI2C1. В этом состоянии на линиях SCL1 и SDA1 будет присутствовать ВЫСОКИЙ уровень. Из этого состояния можно начинать цикл обмена данными, что и выполняет следующая в листинге за IdleI2C1 функция StartI2C1. Фактически функция StartI2C1 устанавливает бит SEN регистра управления I2C1CON. Программа должна обязательно дождаться окончания формирования стартовой последовательности (цикл while), по завершении которой микроконтроллер сбрасывает бит SEN.
         Затем на шину I2C нужно выставить адрес устройства, к которому происходит обращение. В данном случае 7-битный адрес устройства объединяется с битом операции записи (равен 0), и это 8-битное значение (переменная AddrW) выставляется на шину при помощи библиотечной функции MasterWriteI2C1 (AddrW).
         Завершение передачи адреса фиксируется битом TRSTAT, который сбрасывается по завершении операции. После записи адреса устройства можно передать в устройство байт данных, что и выполняется функцией MasterWriteI2C1 (wByte).
         Момент завершения передачи байта определяется по сбросу бита TRSTAT.
         Операцию обмена следует завершить функцией StopI2C1. О завершении обмена данными можно судить по значению флага PEN, который, в случае успешного завершения, должен быть сброшен в 0. Отключение интерфейса выполняется библиотечной функцией CloseI2C1.
         Чтение данных с шины I2C микроконтроллер выполняет по несколько иному программному алгоритму. Это обусловлено тем, что «ведущий», в роли которого выступает PIC24, в цикле чтения данных с шины I2C должен передавать управление линией данных SDA «ведомому».
         Чтение данных по интерфейсу I2C я проиллюстрирую в проекте, схема которого показана на Рис. 8.6.
         Здесь 8-битный параллельный код на входах P0…P7 микросхемы PCF8574A, который формируется при различных комбинациях нажатых/отжатых кнопок, считывается по интерфейсу I2C в микроконтроллер. Линия SCL1 микроконтроллера соединена с линией SCL расширителя ввода/вывода, а линия SDA — с линией SDA расширителя PCF8574A. Двоичный код на входах расширителя каждые 2 секунды передается в микроконтроллер, а затем — на выход порта А, к которому подсоединены светодиоды. Для реализации периодического опроса входов используется Таймер 1 микроконтроллера. Адрес расширителя интерфейса PCF8574A изменился и теперь равен 0x7F (последний бит «комбинированного» адреса стал равен 1, поскольку выполняется чтение).

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

         Теперь о программной части. Программный проект реализован в среде MPLAB IDE и содержит программу на Си, исходный текст которой показан далее:

    #include <p24fj128ga010.h>
    #include <i2c.h>
    _CONFIG2(FCKSM_CSDCMD&OSCIOFNC_ON&POSCMOD_HS&FNOSC_PRI)
    #define SYSCLK 8000000
    #define t1 2
    #define PREG SYSCLK/2*t1/256
    unsigned char AddrR = 0x7f;
    unsigned char ByteR = 0;
    void __attribute__ ((interrupt)) _T1Interrupt(void)
    {
       _T1IF = 0;
       I2C1BRG = 0x4E;
       I2C1CONbits.I2CEN = 1;
       IdleI2C1();
       I2C1CONbits.SEN = 1;
       while (I2C1CONbits.SEN != 0);
       I2C1TRN = AddrR;
       while (I2C1STATbits.TRSTAT == 1);
       I2C1CONbits.RCEN = 1;
       while (I2C1CONbits.RCEN);
       ByteR = I2C1RCV;
       StopI2C1();
       while(I2C1CONbits.PEN);
       CloseI2C1();
       PORTA = ByteR;
    }
    void main(void)
    {
       TRISA = 0x0;
       _T1IF = 0;
       _T1IP = 4;
       _T1IE = 1;
       TMR1 = 0;
       PR1 = PREG;
       T1CON = 0x8030;
       while(1);
    }

         В этой программе используются некоторые функции 16-битной библиотеки для периферийных устройств, поэтому в проект нужно добавить один из библиотечных файлов libpPIC24Fxxx-elf.a или libpPIC24Fxxx-coff (в зависимости от того, какой объектный файл генерируется компилятором), а в текст программы включить объявление заголовочного файла i2c.h.
         Чтение байта данных выполняется в обработчике прерывания Таймера 1 _T1Interrupt. После сброса флага прерывания _T1IF включается модуль интерфейса I2C, задается тактовая частота шины, которая выбрана равной 100 кГц при тактовой частоте микроконтроллера 8МГц, а сама шина переводится в состояние ожидания:

    I2C1BRG = 0x4E;
    I2C1CONbits.I2CEN = 1;
    IdleI2C1();

         Цикл чтения данных с шины I2C начинаем с формирования стартовой последовательности:

    I2C1CONbits.SEN = 1;
    while (I2C1CONbits.SEN != 0);

         Теперь нужно выставить на шину адрес устройства, к которому происходит обращение (с соответствующим значением бита чтения/записи). Для этого в регистр передачи записывается значение переменной AddrR, что автоматически инициирует передачу байта адреса по шине. Чтобы обнаружить конец передачи, нужно дождаться сброса бита TRSTAT. Нулевое значение этого бита свидетельствует о завершении передачи байта адреса в устройство. Эти действия реализованы в двух операторах:

    I2C1TRN = AddrR;
    while (I2C1STATbits.TRSTAT == 1);

         Далее нужно прочитать байт данных из устройства в микроконтроллер. Для этого следует передать управление линией данных SDA расширителю ввода/вывода, установив бит RCEN:
    I2C1CONbits.RCEN = 1;
    while (I2C1CONbits.RCEN);

         Бит RCEN будет оставаться в состоянии 1 до тех пор, пока не закончится прием данных. Как только байт данных окажется в регистре буфера I2C1RCV, бит RCEN аппаратно сбросится в 0, а содержимое регистра I2C1RCV можно будет считать в переменную ByteR:
    ByteR = I2C1RCV;

         На этом цикл чтения данных по шине I2C можно закончить:

    StopI2C1();
    while(I2C1CONbits.PEN);
    CloseI2C1();

         Наконец, данные из переменной ByteR записываются в порт А
    PORTA = ByteR;

         Это все, что касается функции обработчика. В основной программе выполняется инициализация Таймера 1 и настройка его прерывания.
         Рассмотрим последний проект этой главы, в котором будет показано взаимодействие трех устройств на шине I2C. В этом проекте будут использованы два расширителя ввода/вывода PCF8574A, работающие на одной общей шине I2C в роли «ведомых», и микроконтроллер, выполняющий роль «ведущего» шины. Данные с одного расширителя с базовым адресом 0x7f будут читаться в микроконтроллер, который, в свою очередь, будет отправлять их другому «ведомому» с базовым адресом 0x7c. Таким образом, данные в параллельном коде со входа микросхемы D2 будут переключать светодиоды, подключенные к выходам микросхемы D1. Таймер 1 обеспечивает обмен данными каждые 2 секунды. Схема аппаратной части проекта представлена на Рис. 8.7.

    Рис. 8.7. Совместное использование шины I2C несколькими устройствами

         Обратите внимание на подключение выводов адреса A0…A2 микросхем D1 и D2 — оно должно соответствовать указанному в программе с учетом бита чтения/записи, который передается после базового 7-битного адреса каждого устройства. Для данной схемы тактовая частота микроконтроллера PIC24FJ128GA010 выбрана равной 8 МГц, а частота синхронизации шины I2C — 100 кГц.
         Исходный текст программы на языке Си для данного проекта представлен далее:

    #include <p24fj128ga010.h>
    #include <i2c.h>
    _CONFIG2(FCKSM_CSDCMD&OSCIOFNC_ON&POSCMOD_HS&FNOSC_PRI)
    #define SYSCLK 8000000
    #define t1 2
    #define PREG SYSCLK/2*t1/256
    unsigned char AddrR = 0x7f;
    unsigned char AddrW = 0x7c;
    unsigned char dat = 0;
    char ReadByteI2C(void)
    {
       char ByteRead;
       I2C1BRG = 0x4E;
       I2C1CONbits.I2CEN = 1;
       IdleI2C1();
       I2C1CONbits.SEN = 1;
       while (I2C1CONbits.SEN != 0);
       I2C1TRN = AddrR;
       while (I2C1STATbits.TRSTAT == 1);
       I2C1CONbits.RCEN = 1;
       while (I2C1CONbits.RCEN);
       ByteRead = I2C1RCV;
       StopI2C1();
       while(I2C1CONbits.PEN);
       CloseI2C1();
       return ByteRead;
    }
    void WriteByteI2C(char bRead)
    {
       I2C1BRG = 0x4E;
       I2C1CONbits.I2CEN = 1;
       IdleI2C1();
       I2C1CONbits.SEN = 1;
       while (I2C1CONbits.SEN != 0);
       I2C1TRN = AddrW;
       while (I2C1STATbits.TRSTAT == 1);
       I2C1TRN = bRead;
       while (I2C1STATbits.TRSTAT == 1);
       StopI2C1();
       while(I2C1CONbits.PEN);
       CloseI2C1();
    }
    void __attribute__ ((interrupt)) _T1Interrupt(void)
    {
       _T1IF = 0;
       dat = ReadByteI2C();
       WriteByteI2C(dat);
    }
    void main(void)
    {
       _T1IF = 0;
       _T1IP = 4;
       _T1IE = 1;
       TMR1 = 0;
       PR1 = PREG;
       T1CON = 0x8030;
       while(1);
    }

         В программе используются 16-битные библиотечные функции, поэтому в проект следует включить файл библиотек libpPIC24Fxxx-elf.a или libpPIC24Fxxx-coff(в зависимости от того, какой объектный файл генерируется компилятором), а в текст программы — объявление заголовочного файла i2c.h.
         В этой программе чтение байта из одного устройства выполняется функцией
         ReadByteI2C, а запись байта в другое устройство производит функция WriteByteI2C. Обе функции вызываются в обработчике прерывания Таймера 1 _T1Interrupt.
         Мы уже знакомы с теми действиями, которые выполняются при записи/чтении данных на шине I2C, тем более что почти все операторы этой программы мы уже анализировали в предыдущих примерах. Думаю, читатели сумеют самостоятельно проанализировать исходный текст программы.
         Несколько заключительных слов о программировании интерфейса I2C. Приведенные примеры являются относительно простыми — в одном цикле обмена данными выполняется только одна операция записи или чтения данных. Для более сложных устройств, например микросхем памяти, в одном цикле могут выполняться как чтение, так и запись данных. В таких операциях линия данных SDA будет управляться поочередно «ведущим» и «ведомым», поэтому может понадобиться так называемая процедура «рестарта» шины. В любом случае, перед использованием I2C-совместимого устройства следует тщательно изучить техническую документацию производителя.

    Комментарии