Микроконтроллеры PIC благодаря своей универсальности и относительно невысокой цене находят широкое применение в различных электронных проектах. Но как бы не был универсален микроконтроллер, в больших системах он все равно не может выполнять все операции в одиночку – ему приходится обмениваться данными с другими устройствами с помощью какого-нибудь коммуникационного протокола. К наиболее популярным подобным протоколам (интерфейсам), находящих широкое применение в современной встраиваемой электронике, относятся протоколы USART, IIC (I2C), SPI и CAN. Использование последовательного протокола связи USART в микроконтроллерах PIC мы уже рассматривали на нашем сайте в этой статье, а в данной статье мы рассмотрим использование протокола I2C в микроконтроллере PIC16F877.
Ранее на нашем сайте мы рассматривали использование интерфейса I2C в плате Arduino и в плате STM32F103C8 (Blue Pill).
Принципы работы протокола I2C
IIC (Inter Integrated Circuits), часто обозначаемый также как I2C, представляет собой двухпроводный синхронный коммуникационный протокол. Протокол I2C имеет две линии – линию данных (Serial Data Line, SDL) и линию синхронизации (Serial Clock Line, SCL)
Первоначально протокол I2C был разработан компанией Phillips. Протокол использует модель ведущий (Master) – ведомый (Slave). Связь в данном протоколе может осуществляться только между ведущим и ведомым. Достоинством протокола I2C по сравнению с ранее существовавшими до него аналогичными протоколами является то, что в нем к ведущему может быть подключен более чем один ведомый. Всего же протокол I2C поддерживает сеть размером до 128 устройств, каждое из которых имеет в сети уникальный адрес.
Структурная схема интерфейса I2C представлена на следующем рисунке.
Обмен данными в протоколе I2C осуществляется с помощью двух линий – линии данных (SDL) и линии синхронизации (SCL).
В любой момент времени только ведущий (master) может инициировать связь (обмен данными). Поскольку в сети протокола может быть несколько ведомых (slave), то каждый из них должен иметь свой уникальный адрес, чтобы с его помощью ведущий мог указать какому ведомому предназначаются передаваемые им данные. В конкретный момент времени данные передает только тот ведомый, к которому обратился ведущий (по адресу), все остальные ведомые в это время "молчат". Таким образом, с использованием всего 2-х линий осуществляется связь между многочисленными устройствами.
Где применяется протокол I2C
Протокол I2C используется только для связи на короткие расстояния. Протокол отличается высокой надежностью работы благодаря наличию линии синхронизации. В большинстве случаев протокол I2C используется в датчиках или других устройствах, которые должны передавать данные ведущему (master). При этом очень удобно то, что микроконтроллер с помощью всего двух контактов (линий) может взаимодействовать с большим количеством ведомых устройств.
Если вам нужна связь на большие расстояния чем те, которые может обеспечить протокол I2C, то вам необходимо использовать протокол RS232 или RS485, если же вам необходима более надежная связь на короткие расстояния, тогда вам целесообразно использовать протокол SPI.
I2C в микроконтроллере PIC16F877a с использованием компилятора XC8
В данном проекте мы будем рассматривать связь по протоколу I2C в микроконтроллере PIC16F877a только с использованием компилятора XC8. Данный процесс похож на аналогичные процессы и в других микроконтроллерах, но с небольшими отличиями. При этом стоит иметь ввиду, что "продвинутые" серии микроконтроллеров PIC, например, серия PIC18F в своем компиляторе уже содержит встроенную библиотеку для работы с протоколом I2C, но для микроконтроллера PIC16F877A такой библиотеки нет, поэтому мы создадим ее самостоятельно. Код данной библиотеки доступен для скачивания в конце данной статьи.
При изучении чего то нового в микроконтроллере всегда желательно посмотреть даташит на него. В нашем случае нам необходимо в этом даташите посмотреть раздел про интерфейс I2C и выяснить какие регистры микроконтроллера используются для настройки его работы. Здесь мы не будем приводить эти регистры, их настройка представлена далее в приведенных фрагментах программы.
Далее рассмотрим функции, которые мы будем использовать в нашей библиотеке для работы с протоколом I2C в микроконтроллере PIC16F877A.
void I2C_Initialize()
Данная функция инициализации будет сообщать микроконтроллеру что мы будем использовать протокол I2C. Это осуществляется с помощью установки соответствующих битов в регистрах SSPCON и SSPCON2. Вначале нам необходимо задать режимы работы контактов, на которых мы будем использовать интерфейс IIC (у нас это контакты RC3 и RC4) на ввод данных. Затем установить необходимые биты в регистрах SSPCON и SSPCON2. Мы будем использовать режим ведущего в интерфейсе IIC с частотой синхронизации FOSC/(4 * (SSPADD + 1)). В комментариях к этим строкам программы указаны страницы в даташите на микроконтроллер, на которых подробно описаны эти настройки.
Далее нам необходимо установить частоту синхронизации (clock frequency), она может отличаться для различных приложений. Ее мы будем задавать с помощью переменной feq_k – изменяя в программе ее значение можно изменять частоту синхронизации.
1 2 3 4 5 6 7 8 9 10 |
void I2C_Initialize(const unsigned long feq_K) //Begin IIC as master { TRISC3 = 1; TRISC4 = 1; //Set SDA and SCL pins as input pins SSPCON = 0b00101000; //pg84/234 SSPCON2 = 0b00000000; //pg85/234 SSPADD = (_XTAL_FREQ/(4*feq_K*100))-1; //Setting Clock Speed pg99/234 SSPSTAT = 0b00000000; //pg83/234 } |
Void I2C_Hold()
Данная функция используется для приостановки работы устройства до тех пор пока не завершится выполнение текущей операции I2C. Мы должны проверить завершение ее выполнения прежде чем переходить к новой операции. Это осуществляется с помощью проверки регистров SSPSTAT и SSPCON2. Регистр SSPSTAT содержит информацию о состоянии шины I2C. Указанные функции выполняются с помощью следующих команд:
1 2 |
SSPSTAT & 0b00000100 SSPCON2 & 0b00011111 |
С помощью данных команд мы убеждаемся в том, что 2-й бит регистра SSPSTAT установлен в 0, а биты с 0 по 4 регистра SSPCON2 также установлены в 0. Затем мы объединяем эти две команды в одну и проверяем результат получившейся операции. Если он равен 0, то программа продолжит выполнение, если он не равен 0, то выполнение программы задерживается до тех пор пока ее результат не будет равен 0.
1 2 3 4 |
void I2C_Hold() { while ( (SSPCON2 & 0b00011111) || (SSPSTAT & 0b00000100) ) ; //check the this on registers to make sure the IIC is not in progress } |
Void I2C_Begin() и void I2C_End()
Каждый раз, когда мы будем передавать или принимать данные по шине I2C, мы должны открывать и закрывать соединение I2C. Для открытия соединения I2C нам необходимо установить бит SEN, а для закрытия соединения I2C нам необходимо установить бит PEN. Перед переключением данных битов мы должны проверить занята ли шина I2C или нет с помощью функции I2C_Hold, описанной ранее.
1 2 3 4 5 6 7 8 9 10 |
void I2C_Begin() { I2C_Hold(); //Hold the program is I2C is busy SEN = 1; //Begin IIC pg85/234 } void I2C_End() { I2C_Hold(); //Hold the program is I2C is busy PEN = 1; //End IIC pg85/234 } |
Void I2C_Write()
Данная функция используется для передачи данных от ведущего (master) к ведомому (slave). Эту функцию в большинстве случаев следует использовать после функции I2C_Begin(), а после ее выполнения вызывать функцию I2C_End(). Данные, которые будут передаваться по шине I2C, будут проходить через переменную data. Затем из этой переменной они будут перемещаться в буфер регистра SSPBUF чтобы затем передаться по шине I2C.
Но перед передачей данных нам необходимо передать адрес ведомого устройства, которому будут передаваться эти данные. Поэтому функцию I2C_Write нам необходимо будет использовать дважды: первый раз для передачи адреса, а второй – непосредственно для передачи данных.
1 2 3 4 5 |
void I2C_Write(unsigned data) { I2C_Hold(); //Hold the program is I2C is busy SSPBUF = data; //pg82/234 } |
unsigned short I2C_Read()
Данная функция будет использоваться для считывания данных, которые в данный момент находятся на шине I2C. Она используется после запроса ведомому чтобы он передал данные на шину. Принимаемые данные будут записываться в регистр SSPBUF, откуда мы их можем переместить в любую переменную.
Во время обмена данными по протоколу I2C ведомый после передачи данных, запрошенных ведущим, должен также передать бит подтверждения (acknowledgment bit). Этот бит должен быть проверен ведущим чтобы убедиться в том, что процесс обмена данными был успешным. После проверки бита подтверждения ACKDT мы будем устанавливать бит ACKEN.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
unsigned short I2C_Read(unsigned short ack) { unsigned short incoming; I2C_Hold(); RCEN = 1; I2C_Hold(); incoming = SSPBUF; //get the data saved in SSPBUF I2C_Hold(); ACKDT = (ack)?0:1; //check if ack bit received ACKEN = 1; //pg 85/234 return incoming; } |
Описанных нами функций вполне хватит для большинства операций обмена данными по интерфейсу I2C. Хотя функционал протокола I2C значительно больше чем в представленных нами функциях, мы не рассматриваем их здесь из-за ограниченного объема статьи. Для их более подробного изучения вы всегда можете обратиться к даташиту на микроконтроллер.
Полный код программы для работы с протоколом I2C в микроконтроллере PIC вы можете скачать по этой ссылке.
Начало программы для работы с протоколом I2C
Теперь, когда у нас запрограммированы функции для работы с протоколом I2C, мы можем написать небольшую программу для работы с данным протоколом. Затем мы можем смоделировать работу нашей программы.
Первым делом в программе установим биты конфигурации и установим тактовую частоту микроконтроллера равную 20 MHz.
1 2 3 4 5 6 7 8 9 10 |
#pragma config FOSC = HS // Oscillator Selection bits (HS oscillator) #pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled) #pragma config PWRTE = ON // Power-up Timer Enable bit (PWRT enabled) #pragma config BOREN = ON // Brown-out Reset Enable bit (BOR enabled) #pragma config LVP = OFF // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming) #pragma config CPD = OFF // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off) #pragma config WRT = OFF // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control) #pragma config CP = OFF // Flash Program Memory Code Protection bit (Code protection off) #define _XTAL_FREQ 20000000 |
Затем нам необходимо подключить заголовочный файл PIC16F877a_I2C.h, который можно скачать по ссылке, приведенной выше. Убедитесь в том, что заголовочный файл добавлен в заголовок вашего проекта. Структура файлов вашего проекта должна выглядеть как на следующем рисунке:
После этого подключите заголовочный файл в программе.
1 2 |
#include <xc.h> #include "PIC16F877a_I2C.h" |
Далее внутри цикла while мы будем открывать соединение I2C и передавать по шине I2C несколько случайных данных, после этого мы будем закрывать соединение I2C. В качестве случайных значений мы выбрали D0, 88 и FF. Вы можете изменить их по своему усмотрению.
1 2 3 4 5 6 7 8 9 |
while(1) { I2C_Begin(); I2C_Write(0xD0); I2C_Write(0x88); I2C_Write(0xFF); I2C_End(); __delay_ms(1000); } |
Моделирование работы проекта в Proteus
Симулятор Proteus имеет замечательный инструмент под названием I2C debugger, который можно использовать для считывания данных с шины I2C. Таким образом, с его помощью мы можем проверить передаваемые нами в шину I2C значения. Нарисованная в симуляторе Proteus схема нашего проекта приведена на следующем рисунке.
Загрузите hex файл нашей программы в микроконтроллер, два раза нажав на его изображение на схеме. Затем запустите выполнение программы. Вы должны увидеть всплывающее окно, в котором отобразится вся необходимая информация о шине I2C. В нашем случае это окно выглядит следующим образом:
Если вы внимательно посмотрите на данные в этом окне, вы увидите что они те же самые, которые мы передавали в программе для микроконтроллера – D0, 88 и FF. Эти данные передаются каждую секунду, поэтому и время в данном окне будет обновляться соответствующим образом. Синяя стрелка показывает что данные передаются от ведущего к ведомому. Если данные будут передаваться в противоположном направлении, то стрелка будет направлена в другую сторону. Вид отдельно взятой строчки в этом окне выглядит следующим образом:
Исходный код программы
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
/* * File: IIC with PIC16F * Author: Aswinth Raj * * Created on 7 May, 2018, 6:42 PM */ #pragma config FOSC = HS // Oscillator Selection bits (HS oscillator) #pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled) #pragma config PWRTE = ON // Power-up Timer Enable bit (PWRT enabled) #pragma config BOREN = ON // Brown-out Reset Enable bit (BOR enabled) #pragma config LVP = OFF // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming) #pragma config CPD = OFF // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off) #pragma config WRT = OFF // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control) #pragma config CP = OFF // Flash Program Memory Code Protection bit (Code protection off) #define _XTAL_FREQ 20000000 #include <xc.h> #include "PIC16F877a_I2C.h" int main() { I2C_Initialize(100); //Initialize I2C Master with 100KHz clock while(1) { I2C_Begin(); I2C_Write(0xD0); I2C_Write(0x88); I2C_Write(0xFF); I2C_End(); __delay_ms(1000); } |
Все файлы для данного проекта вы можете скачать по следующей ссылке.
686 просмотров
Добрый день!Вы случайно не знаете, почему в i2c дебаггере вместо такой картинки, как у вас, появляется нечто подобное:
324.601us 409.603us S Sr P
409.603us 525.606us ? ? S P
525.606us 645.404us ? ? ? ? S P
645.404us 738.206us ? ? S P
738.206us 1.001 s ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? S Sr P
1.001 s 1.001 s ? ? S P
1.001 s 1.001 s ? ? ? ? S P
1.001 s 1.001 s ? ? S P
1.001 s 1.001 s ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? S Sr P
и т.д.?
Добрый день, точно не знаю. Но у вас вначале процесс идет нормально, а потом зацикливается почему то на 1 секунде. У вас конфигурация регистров правильно выставлена? Нет ли случайно где опечатки?
SDA=SDL=SDI?
На схеме и в тексте всяко-разно...
Да, случаются иногда небольшие изменения в терминологии. А SDI вы где увидели? Подскажите