Микрочип написал примечания к приложению по этому поводу:
- AN734 по внедрению ведомого I2C
- AN735 по реализации мастера I2C
- Существует также более теоретический AN736 по настройке сетевого протокола для мониторинга окружающей среды, но он не нужен для этого проекта.
Замечания по применению работают с ASM, но их легко перенести на C.
Бесплатные компиляторы Microchip C18 и XC8 имеют функции I2C. Подробнее о них вы можете прочитать в документации к библиотекам компилятора , раздел 2.4. Вот небольшая информация о начале:
Настройка
У вас уже есть компилятор Microchip C18 или XC8. Они оба имеют встроенные функции I2C. Чтобы использовать их, вам необходимо включить i2c.h
:
#include i2c.h
Если вы хотите взглянуть на исходный код, вы можете найти его здесь:
- Заголовок C18:
installation_path
/v
x.xx
/h/i2c.h
- C18 источник:
installation_path
/v
x.xx
/src/pmc_common/i2c/
- Заголовок XC8:
installation_path
/v
x.xx
/include/plib/i2c.h
- Источник XC8:
installation_path
/v
x.xx
/sources/pic18/plib/i2c/
В документации вы можете найти, в каком файле в /i2c/
папке находится функция.
Открытие соединения
Если вы знакомы с MSSP-модулями Microchip, вы будете знать, что их сначала нужно инициализировать. Вы можете открыть соединение I2C через порт MSSP, используя OpenI2C
функцию. Вот как это определяется:
void OpenI2C (unsigned char sync_mode, unsigned char slew);
С помощью sync_mode
вы можете выбрать, является ли устройство ведущим или ведомым, и, если это ведомое устройство, должно ли оно использовать 10-битный или 7-битный адрес. Большую часть времени используется 7-бит, особенно в небольших приложениях. Варианты для sync_mode
:
SLAVE_7
- ведомый режим, 7-битный адрес
SLAVE_10
- ведомый режим, 10-битный адрес
MASTER
- Мастер режим
С помощью slew
вы можете выбрать, будет ли устройство использовать скорость нарастания. Подробнее о том, что здесь: Какова скорость нарастания для I2C?
Два модуля MSSP
В устройствах с двумя модулями MSSP есть что-то особенное, например, PIC18F46K22 . У них есть два набора функций, один для модуля 1 и один для модуля 2. Например, вместо того OpenI2C()
, они имеют OpenI2C1()
иopenI2C2()
.
Итак, вы все настроили и открыли соединение. Теперь давайте сделаем несколько примеров:
Примеры
Мастер написать пример
Если вы знакомы с протоколом I2C, вы будете знать, что типичная основная последовательность записи выглядит следующим образом:
Master : START | ADDR+W | | DATA | | DATA | | ... | DATA | | STOP
Slave : | | ACK | | ACK | | ACK | ... | | ACK |
Сначала мы отправляем условие START. Подумайте об этом, поднимая трубку. Затем адрес с битом записи - набор номера. На этом этапе раб с отправленным адресом знает, что его вызывают. Он отправляет подтверждение («Привет»). Теперь ведущее устройство может отправлять данные - он начинает говорить. Он отправляет любое количество байтов. После каждого байта ведомый должен подтверждать полученные данные («да, я вас слышу»). Когда ведущее устройство завершило разговор, он вешает трубку в состоянии STOP.
В C последовательность записи мастера будет выглядеть следующим образом:
IdleI2C(); // Wait until the bus is idle
StartI2C(); // Send START condition
IdleI2C(); // Wait for the end of the START condition
WriteI2C( slave_address & 0xfe ); // Send address with R/W cleared for write
IdleI2C(); // Wait for ACK
WriteI2C( data[0] ); // Write first byte of data
IdleI2C(); // Wait for ACK
// ...
WriteI2C( data[n] ); // Write nth byte of data
IdleI2C(); // Wait for ACK
StopI2C(); // Hang up, send STOP condition
Мастер прочитал пример
Последовательность основного чтения немного отличается от последовательности записи:
Master : START | ADDR+R | | | ACK | | ACK | ... | | NACK | STOP
Slave : | | ACK | DATA | | DATA | | ... | DATA | |
Опять же, мастер инициирует вызов и набирает номер. Однако сейчас он хочет получить информацию. Сначала подчиненный отвечает на вызов, затем начинает говорить (отправляя данные). Мастер подтверждает каждый байт, пока у него не будет достаточно информации. Затем он отправляет Not-ACK и кладет трубку с условием STOP.
В Си это будет выглядеть так для основной части:
IdleI2C(); // Wait until the bus is idle
StartI2C(); // Send START condition
IdleI2C(); // Wait for the end of the START condition
WriteI2C( slave_address | 0x01 ); // Send address with R/W set for read
IdleI2C(); // Wait for ACK
data[0] = ReadI2C(); // Read first byte of data
AckI2C(); // Send ACK
// ...
data[n] = ReadI2C(); // Read nth byte of data
NotAckI2C(); // Send NACK
StopI2C(); // Hang up, send STOP condition
Ведомый код
Для ведомого лучше всего использовать программу обработки прерываний или ISR. Вы можете настроить свой микроконтроллер на получение прерывания при вызове вашего адреса. Таким образом, вам не нужно постоянно проверять автобус.
Во-первых, давайте настроим основы для прерываний. Вам нужно будет включить прерывания и добавить ISR. Важно, чтобы PIC18 имели два уровня прерываний: высокий и низкий. Мы собираемся установить I2C как прерывание с высоким приоритетом, потому что очень важно отвечать на вызов I2C. Что мы собираемся сделать, это следующее:
- Напишите ISR SSP для случая, когда прерывание является прерыванием SSP (а не другим прерыванием)
- Напишите общий ISR с высоким приоритетом, для которого прерывание имеет высокий приоритет. Эта функция должна проверять, какой тип прерывания сработал, и вызывать правильный подчиненный ISR (например, ISP SSP).
- Добавьте
GOTO
инструкцию к общему ISR для вектора прерывания с высоким приоритетом. Мы не можем поместить общий ISR непосредственно в вектор, потому что во многих случаях он слишком велик.
Вот пример кода:
// Function prototypes for the high priority ISRs
void highPriorityISR(void);
// Function prototype for the SSP ISR
void SSPISR(void);
// This is the code for at the high priority vector
#pragma code high_vector=0x08
void interrupt_at_high_vector(void) { _asm GOTO highPriorityISR _endasm }
#pragma code
// The actual high priority ISR
#pragma interrupt highPriorityISR
void highPriorityISR() {
if (PIR1bits.SSPIF) { // Check for SSP interrupt
SSPISR(); // It is an SSP interrupt, call the SSP ISR
PIR1bits.SSPIF = 0; // Clear the interrupt flag
}
return;
}
// This is the actual SSP ISR
void SSPISR(void) {
// We'll add code later on
}
Следующее, что нужно сделать, - включить высокоприоритетное прерывание при инициализации чипа. Это можно сделать несколькими простыми манипуляциями с регистром:
RCONbits.IPEN = 1; // Enable interrupt priorities
INTCON &= 0x3f; // Globally enable interrupts
PIE1bits.SSPIE = 1; // Enable SSP interrupt
IPR1bits.SSPIP = 1; // Set SSP interrupt priority to high
Теперь у нас работают прерывания. Если вы реализуете это, я бы проверил это сейчас. Напишите основную информацию, SSPISR()
чтобы начать мигать светодиодом, когда происходит прерывание SSP.
Ладно, значит, ваши прерывания работают. Теперь давайте напишем реальный код для SSPISR()
функции. Но сначала немного теории. Мы выделяем пять различных типов прерываний I2C:
- Мастер пишет, последний байт был адресом
- Мастер пишет, последний байт был данными
- Мастер читает, последний байт был адресом
- Мастер читает, последний байт был данными
- NACK: конец передачи
Вы можете проверить, в каком состоянии вы находитесь, проверив биты в SSPSTAT
регистре. Этот регистр выглядит следующим образом в режиме I2C (неиспользуемые или нерелевантные биты опускаются):
- Бит 5: D / NOT A: Адрес / Не адрес: устанавливается, если последний байт был данными, очищается, если последний байт был адресом
- Бит 4: P: стоповый бит: устанавливается, если условие STOP возникло последним (нет активной операции)
- Бит 3: S: стартовый бит: устанавливается, если условие START возникло последним (активная операция)
- Бит 2: R / NOT W: чтение / не запись: устанавливается, если операция является мастер-чтением, сбрасывается, если операция является мастер-записью
- Бит 0: BF: Buffer Full: устанавливается, если в регистре SSPBUFF есть данные, очищается, если нет
С помощью этих данных легко увидеть, в каком состоянии находится модуль I2C:
State | Operation | Last byte | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 0
------+-----------+-----------+-------+-------+-------+-------+-------
1 | M write | address | 0 | 0 | 1 | 0 | 1
2 | M write | data | 1 | 0 | 1 | 0 | 1
3 | M read | address | 0 | 0 | 1 | 1 | 0
4 | M read | data | 1 | 0 | 1 | 1 | 0
5 | none | - | ? | ? | ? | ? | ?
В программном обеспечении лучше использовать состояние 5 по умолчанию, которое предполагается, когда не соблюдаются требования для других состояний. Таким образом, вы не отвечаете, когда не знаете, что происходит, потому что раб не отвечает на NACK.
В любом случае, давайте посмотрим на код:
void SSPISR(void) {
unsigned char temp, data;
temp = SSPSTAT & 0x2d;
if ((temp ^ 0x09) == 0x00) { // 1: write operation, last byte was address
data = ReadI2C();
// Do something with data, or just return
} else if ((temp ^ 0x29) == 0x00) { // 2: write operation, last byte was data
data = ReadI2C();
// Do something with data, or just return
} else if ((temp ^ 0x0c) == 0x00) { // 3: read operation, last byte was address
// Do something, then write something to I2C
WriteI2C(0x00);
} else if ((temp ^ 0x2c) == 0x00) { // 4: read operation, last byte was data
// Do something, then write something to I2C
WriteI2C(0x00);
} else { // 5: slave logic reset by NACK from master
// Don't do anything, clear a buffer, reset, whatever
}
}
Вы можете увидеть, как вы можете проверить SSPSTAT
регистр (сначала с AND, 0x2d
чтобы у нас были только полезные биты), используя битовые маски, чтобы увидеть, какой у нас тип прерывания.
Ваша задача выяснить, что вы должны отправить или сделать, когда отвечаете на прерывание: это зависит от вашего приложения.
Ссылки
Опять же, я хотел бы упомянуть замечания по применению, которые Микрочип написал о I2C:
- AN734 по внедрению ведомого I2C
- AN735 по реализации мастера I2C
- AN736 по настройке сетевого протокола для мониторинга окружающей среды
Есть документация для библиотек компилятора: Документация для библиотек компилятора
При настройке чего-либо самостоятельно проверьте таблицу данных вашего чипа в разделе (M) SSP для связи I2C. Я использовал PIC18F46K22 для основной части и PIC18F4620 для подчиненной части.