В последнее время все чаще натыкаюсь на негативные отзывы о шине I2C
у STM32
, мол работа с ней это танцы с бубном и тд.
За последний месяц мне удалось запустить две микросхемы, работающие по I2C
и ни каких танцев, только вдумчивое чтение даташита.
Модуль I2C у STM32 обладает следующими особенностями:
- может работать в двух режимах Fm (fast mode) и Sm (standart mode), первый работает на частотах до 400KHz, второй до 100KHz
- буфер размером 1 байт с поддержкой DMA
- поддерживает аппаратный подсчет контрольной суммы
- на его основе возможна реализуется SMBus (System Management Bus) и PMBus (Power Management Bus)
- два вектора прерывания, генерируются при успешной передаче и при возникновении ошибки
- фильтр для борьбы с шумами
- может работать в режиме Master или Slave
- генерирует тактирующий сигнал
- генерирует START и STOP
- можно программировать основной и альтернативный адрес на который он будет отзываться
- определяет STOP
По умолчанию модуль находится в режиме Slave
, но он автоматически переключается в режим Master
после генерации состояния START
.
Принципиальное отличие между Master и Slave, в том, что Master
генерирует тактовый сигнал и всегда инициирует передачу данных и заканчивает её
. Slave
же, откликается на свой адрес и широковещательный
, при чем отклик на широковещательный адрес можно отключить. Также Slave
генерирует состояние
ACK
, но его тоже можно отключить.
Такое подробное разъяснение необходимо потому, что в обоих режимах устройство может выступать как передатчиком, так и приемником.
- Slave transmitter
- Slave receiver
- Master transmitter
- Master receiver
Ниже показана структура модуля I2C.
Регистр управления I2C_CR1:
SWRST (Software reset) - единица в этом бите сбрасывает значение всех регистров модуля в дефолтное стояние, может использоваться для сброса при возникновении ошибки.
ALERT (SMBus alert) - установка единицы в этот бит разрешает генерировать сигнал alert в режиме SMBus .
PEC (Packet error checking) - управление этим битом производится программно, но он может быть сброшен аппаратно когда передается PEC, START, STOP или PE=0. Единица в этом бите разрешает передачу CRC .
POS (Acknowledge/PEC Position (for data reception)) - состояние этого бита определяет положение ACK /PEC в двух байтовой конфигурации в режиме Master.
ACK (Acknowledge enable) - единица в этом бите разрешает отправлять ACK /NACK после приема байта адреса или данных.
STOP (Stop generation) - установка единицы в этот бит генерирует сигнал STOP в режиме Master.
START (Start generation) - установка единицы в этот бит генерирует состояние START в режиме Master,
NOSTRETCH (Clock stretching disable (Slave mode)) - если на обработку данных требуется время Slave может остановить передачу мастера, прижав линию SCL к земле, Master будет ждать и не будет ни чего слать, пока линия не будет отпущена. Ноль в этом бите прижимает SCL к земле.
ENGC (General call enable) - если в этом бите установлена единица, модуль отвечает ACK ом на широковещательный адрес 0х00.
ENPEC (PEC enable) - установка единицы в этот бит включает аппаратный подсчет CRC .
ENARP (ARP enable) - установка единицы в этот бит включает ARP .
SMBTYPE (SMBus type) - если в этом бите установлен ноль модуль работает в режиме Slave, если единица в режиме Master.
SMBUS (SMBus mode) - если в этом бите установлен ноль модуль работает в режиме I2C , если единица SMBus .
PE (Peripheral enable) - единица в этом бите включает модуль.
Регистр управления I2C_CR2:
LAST (DMA last transfer) - единица в этом бите разрешает DMA генерировать сигнал окончания передачи EOT (End of Transfer).
DMAEN (DMA requests enable) - единица в этом бите разрешает делать запрос к DMA при установке флагов TxE или RxNE .
ITBUFEN (Buffer interrupt enable) - если этот бит сброшен, разрешены все прерывания, кроме прерываний по приему и передаче.
ITEVTEN (Event interrupt enable) - единица в этом бите разрешает прерывания по событию.
ITERREN (Error interrupt enable) - единица в этом бите разрешает прерывания при возникновении ошибок.
FREQ (Peripheral clock frequency) - в это битовое битовое поле необходимо записать частоту тактирования модуля, она может принимать значение от 2 до 50.
Регистр I2C_OAR1:
ADDMODE (Addressing mode) - этот бит определяет размер адреса Slave, ноль соответствует размеру адреса 7 бит, единица - 10 бит.
ADD (Interface address) - старшие биты адреса, в случае если адрес 10-битный.
ADD (Interface address) - адрес устройства.
ADD0 (Interface address) - младший бит адреса, в случае если адрес 10-битный..
Регистр I2C_OAR2:
ADD2 - альтернативный адрес на который будет отзываться Slave.
ENDUAL (Dual addressing mode enable) - единица в этом бите разрешает Slave отзываться на альтернативный адрес в 7-битном режиме.
I2C_DR - регистр данных, для отправки данных пишем в регистр DR , для приёма читаем его же.
Регистр статуса I2C_SR1:
SMBALERT (SMBus alert) - возникает в случае alert в шине SMBus .
TIMEOUT (Timeout or Tlow error) - возникает если линия SCL прижата к земле. Для master 10mS, для slave 25mS.
PECERR (PEC Error in reception) - возникает при ошибке PEC при приеме.
OVR (Overrun/Underrun) - возникает при переполнении данных.
AF (Acknowledge failure) - устанавливается при получении сигнала NACK . Для сброса нужно записать 0.
ARLO (Arbitration lost (master mode)) - устанавливается при потере арбитража. Для сброса нужно записать 0.
BERR (Bus error) - ошибка шины. Устанавливается в случае возникновения сигнала START или STOP в неправильный момент.
TxE (Data register empty (transmitters)) - устанавливается при опустошении регистра DR, а точнее когда данные из него были перемещены в сдвиговый регистр.
RxNE (Data register not empty (receivers)) - устанавливается при приеме байта данных, кроме адреса.
STOPF (Stop detection (slave mode)) - при работе в режиме slave устанавливается при обнаружении сигнала STOP , если перед этим был сигнал ACK. Для сброса необходимо прочитать SR1 и произвести запись в CR1 .
ADD10 (10-bit header sent (Master mode)) - устанавливается при отправке первого байта 10-битного адреса.
BTF (Byte transfer finished) - флаг устанавливается по окончании приема/передачи байта, работает только при NOSTRETCH равном нулю.
ADDR (Address sent (master mode)/matched (slave mode)) - в режиме master устанавливается после передачи адреса, в режиме slave устанавливается при совпадении адреса. Для сброса нужно прочитать регистр SR1, а затем SR2.
SB (Start bit (Master mode)) - устанавливается при возникновении сигнала START. Для сброса флага необходимо прочитать SR1 и записать данные в регистр DR .
Регистр статуса I2C_SR2:
PEC (Packet error checking register) - в это битовое поле записывается контрольная сумма кадра.
DUALF (Dual flag (Slave mode)) - ноль в этом бите говорит о том, что адрес который принял Slave соответствует OAR1 , иначе OAR2 .
SMBHOST (SMBus host header (Slave mode)) - устанавливается, когда принят заголовок SMBus Host .
SMBDEFAULT
(SMBus device default address (Slave mode)) - устанавливается, если принят адрес по умолчанию
для SMBus
-устройства.
GENCALL (General call address (Slave mode)) - устанавливается, если принят широковещательный адрес в режиме ведомого.
TRA (Transmitter/receiver) - единица в этом бите говорит о том, что модуль работает как передатчик, иначе приемник.
BUSY (Bus busy) - флаг занятости.
MSL (Master/slave) - единица в этом бите говорит о том, что модуль работает в режиме Master, иначе Slave.
Регистр управления частотой I2C_CCR:
F/S (I2C master mode selection) - при установке единицы в этот бит модуль работает в режиме FAST , иначе STANDART .
DUTY (Fm mode duty cycle) - этот бит задает скважность сигнала SCL в режиме FAST . Если установлен ноль tlow/thigh = 2, иначе tlow/thigh = 16/9.
CCR (Clock control register in Fm/Sm mode (Master mode)) - при работе в режиме Master задает тактовую частоту линии SCL.
Sm mode or SMBus
:
Thigh = CCR * TPCLK1
Tlow = CCR * TPCLK1
Fm mode
:
If DUTY = 0:
Thigh = CCR * TPCLK1
Tlow = 2 * CCR * TPCLK1
If DUTY = 1: (to reach 400 kHz)
Thigh = 9 * CCR * TPCLK1
Tlow = 16 * CCR * TPCLK1
Получаем для режима SM
следующее:
CCR * TPCLK1 + CCR * TPCLK1 = 10 000ns
CCR = 10 000/(2* TPCLK1)
Регистр I2C_TRISE:
TRISE
- определяет время нарастания фронта. Рассчитывается по формуле (Tr max/TPCLK1)+1
,
где Tr max
для SM
составляет 1000nS
, а для FM 300nS
,
а TPCLK1
- период который рассчитывается как 1/F
(APB1).
Регистр управления фильтрами I2C_FLTR:
ANOFF (Analog noise filter OFF) - ноль в этом бите включает аналоговый фильтр.
DNF (Digital noise filter) - битовое поле для настройки цифрового фильтра. За подробностями нужно обратиться к документации.
Инициализация модуля из рабочего проекта.
void I2C2_Init(void)
{
/*
SDL -> PB10
SDA -> PB11
RST -> PE15
*/
//включаем тактирование портов и модуля I2C
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN | RCC_AHB1ENR_GPIOEEN;
RCC->APB1ENR |= RCC_APB1ENR_I2C2EN;
//альтернативная ф-ция, выход с открытым стоком, 2 MHz
GPIOB->AFR |= (0x04<<2*4);
GPIOB->AFR |= (0x04<<3*4);
GPIOB->MODER |= GPIO_MODER_MODER10_1;
GPIOB->OTYPER |= GPIO_OTYPER_OT_10;
GPIOB->OSPEEDR &= ~GPIO_OSPEEDER_OSPEEDR10;
GPIOB->MODER |= GPIO_MODER_MODER11_1;
GPIOB->OTYPER |= GPIO_OTYPER_OT_11;
GPIOB->OSPEEDR &= ~GPIO_OSPEEDER_OSPEEDR11;
//PE15 двухтактный выход 50MHz
GPIOE->MODER |= GPIO_MODER_MODER15_0;
GPIOE->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR15;
AU_RST_HIGH
//настраиваем модуль в режим I2C
I2C2->CR1 &= ~2C_CR1_SMBUS;
//указываем частоту тактирования модуля
I2C2->CR2 &= ~I2C_CR2_FREQ;
I2C2->CR2 |= 42; // Fclk1=168/4=42MHz
//конфигурируем I2C, standart mode, 100 KHz duty cycle 1/2
I2C2->CCR &= ~(I2C_CCR_FS | I2C_CCR_DUTY);
//задаем частоту работы модуля SCL по формуле 10 000nS/(2* TPCLK1)
I2C2->CCR |= 208; //10 000ns/48ns = 208
//Standart_Mode = 1000nS, Fast_Mode = 300nS, 1/42MHz = 24nS
I2C2->TRISE = 42; //(1000nS/24nS)+1
//включаем модуль
I2C2->CR1 |= I2C_CR1_PE;
}
void I2C_Write(uint8_t reg_addr, uint8_t data)
{
//стартуем
I2C2->CR1 |= I2C_CR1_START;
while(!(I2C2->SR1 & I2C_SR1_SB)){};
(void) I2C2->SR1;
//передаем адрес устройства
I2C2->DR = I2C_ADDRESS(ADDR,I2C_MODE_WRITE);
while(!(I2C2->SR1 & I2C_SR1_ADDR)){};
(void) I2C2->SR1;
(void) I2C2->SR2;
//передаем адрес регистра
I2C2->DR = reg_addr;
while(!(I2C2->SR1 & I2C_SR1_TXE)){};
//пишем данные
I2C2->DR = data;
while(!(I2C2->SR1 & I2C_SR1_BTF)){};
I2C2->CR1 |= I2C_CR1_STOP;
}
uint8_t I2C_Read(uint8_t reg_addr)
{
uint8_t data;
//стартуем
I2C2->CR1 |= I2C_CR1_START;
while(!(I2C2->SR1 & I2C_SR1_SB)){};
(void) I2C2->SR1;
//передаем адрес устройства
I2C2->DR = I2C_ADDRESS(ADR,I2C_MODE_WRITE);
while(!(I2C2->SR1 & I2C_SR1_ADDR)){};
(void) I2C2->SR1;
(void) I2C2->SR2;
//передаем адрес регистра
I2C2->DR = reg_addr;
while(!(I2C2->SR1 & I2C_SR1_TXE)){};
I2C2->CR1 |= I2C_CR1_STOP;
//рестарт!!!
I2C2->CR1 |= I2C_CR1_START;
while(!(I2C2->SR1 & I2C_SR1_SB)){};
(void) I2C2->SR1;
//передаем адрес устройства, но теперь для чтения
I2C2->DR = I2C_ADDRESS(ADR,I2C_MODE_READ);
while(!(I2C2->SR1 & I2C_SR1_ADDR)){};
(void) I2C2->SR1;
(void) I2C2->SR2;
//читаем
I2C2->CR1 &= ~I2C_CR1_ACK;
while(!(I2C2->SR1 & I2C_SR1_RXNE)){};
data = I2C2->DR;
I2C2->CR1 |= I2C_CR1_STOP;
return data;
}
Опубліковано 26.10.2016
В предыдущей статье мы рассмотрели работу STM32 с шиной I 2 C в качестве Мастера. То есть, он был ведущий и опрашивал датчик. Теперь сделаем так, чтобы STM32 был Slave-ом и отвечал на запросы, то есть сам работал как датчик. Мы выделим 255 байт памяти под регистры с адресами от 0 до 0xFF, и позволим Мастеру в них писать/читать. А чтобы пример был не таким простым, сделаем из нашего STM32, еще и аналого-цифровой преобразователь с интерфейсом I 2 C. ADC будет обрабатывать 8 каналов. Результаты преобразований контроллер будет отдавать Мастеру при чтении из регистров. Поскольку результат преобразования ADC занимает 12 бит, нам потребуется 2 регистра (2 байта) на каждый канал ADC.
i2c_slave.h содержит настройки:
I2CSLAVE_ADDR – адрес нашего устройства;
ADC_ADDR_START – начальный адрес регистров, которые отвечают за результаты преобразований ADC.
В файле i2c_slave.c нас больше всего интересуют функции get_i2c1_ram и set_i2c1_ram . Функция get_i2c1_ram отвечает за считывание данных из регистров. Она возвращает данные с указанного адреса, которые отдаются Мастеру. В нашем случае данные считываются из массива i2c1_ram , но, если Мастер спрашивает адреса регистров из диапазона отведенного для результатов ADC, то отправляются данные преобразований ADC.
get_i2c1_ram :
Uint8_t get_i2c1_ram(uint8_t adr) { //ADC data if ((ADC_ADDR_START <= adr) & (adr < ADC_ADDR_START + ADC_CHANNELS*2)) { return ADCBuffer; } else { // Other addresses return i2c1_ram; } }
Функция set_i2c1_ram – записывает данные принятые от Мастера в регистры с указанным адресом. В нашем случае данные просто записываются в массив i2c1_ram . Но это не обязательно. Вы можете, например, добавить проверку, и, когда на определенный адрес приходит определенное число, выполнить какие-то действия. Таким образом, Вы сможете подавать микроконтроллеру разные команды.
set_i2c1_ram :
Void set_i2c1_ram(uint8_t adr, uint8_t val) { i2c1_ram = val; return; }
Инициализация достаточно проста:
Int main(void) { SetSysClockTo72(); ADC_DMA_init(); I2C1_Slave_init(); while(1) { } }
Сначала мы устанавливаем максимальную частоту работы контроллера. Максимальная скорость необходима, когда нужно избежать любых задержек на шине I 2 C. Затем запускаем работу ADC с использованием DMA. О . О . И, наконец, выполняем инициализацию шины I 2 C как Slave . Как видите, ничего сложного.
Теперь подключим наш модуль STM32 к Raspberry Pi. К каналам ADC подключим потенциометры. И будем считывать с нашего контроллера показатели ADC. Не забываем, что для работы шины I 2 C нужно на каждую линию шины установить подтягивающие резисторы.
В консоли Raspberry проверим видно ли наше устройство на шине I 2 C (о том, ):
I2cdetect -y 1
Как видите, адрес устройства 0x27 , хотя мы указали 0x4E. Когда будет время, подумайте – почему так произошло.
Для считывания из регистров I 2 C-Slave устройства выполняем команду:
I2cget -y 1 0x27 0x00
Где:
0x27
– адрес устройства,
0x00
– адрес регистра (0x00…0xFF).
Для записи в регистры I 2 C-Slave устройства выполняем команду:
I2cset -y 1 0x27 0xA0 0xDD
Де:
0x27
– адрес устройства,
0xA0
– адрес регистра
0xDD
-8-bit данные (0x00…0xFF)
Предыдущая команда записала число 0xDD в регистр 0xA0 (писать в первые 16 регистров можно, и смысла нет, по они отведены под ADC). Теперь прочитаем:
I2cget -y 1 0x27 0xA0
Чтобы упростить процесс считывания данных ADC-каналов я написал скрипт:
#!/usr/bin/env python import smbus import time bus = smbus.SMBus(1) address = 0x27 while (1): ADC = {}; for i in range(0, 8): LBS = bus.read_byte_data(address, 0x00+i*2) MBS = bus.read_byte_data(address, 0x00+i*2+1) ADC[i] = MBS*256 + LBS print ADC time.sleep(0.2)
Он опрашивает и выводит в консоль результаты всех 8-ми ADC-каналов.
Аналогичным образом можно объединить несколько микроконтроллеров. Один из них должен быть Master (), другие Slave.
Желаю успехов!
В статье приведено описание последовательного интерфейса I2С 32-разрядных ARM-микроконтроллеров серии STM32 от компании STMicroelectronics. Рассмотрены архитектура, состав и назначение регистров конфигурирования интерфейса, а также приведены примеры программ его использования.
Введение
Интерфейс I2С, или IIC получил свою аббревиатуру от английских слов Inter-Integrated Circuit и представляет собой последовательную шину, состоящую из двух двунаправленных линий связи с названием SDA и SCL, как сокращение от слов Serial Data Address и Serial Clock. Он обеспечивает обмен данными между микроконтроллером и различными периферийными устройствами, такими как АЦП, ЦАП, микросхемы памяти, другие микроконтроллеры и микросхемы. Схема подключения устройств по интерфейсу I2C показана на рисунке 1.
Рис. 1. Схема подключения устройств по интерфейсу I 2 C
Стандарт на интерфейс I2С был разработан фирмой Philips в начале 1980-х годов. Согласно этому стандарту, интерфейс имел 7-разрядный адрес. Он позволял обращаться к 127 устройствам на скорости до 100 кбит/с. В дальнейшем интерфейс получил своё развитие и стал 10-разрядным, позволяющим обращаться к 1023 устройствам на скорости до 400 кбит/с. Максимальное
допустимое количество микросхем, подсоединённых к одной шине, ограничивается максимальной ёмкостью шины в 400 пФ. Версия стандарта 2.0, выпущенная в 1998 году, представила
высокоскоростной режим работы со скоростью до 3,4 Мбит/с с пониженным энергопотреблением. Версия 2.1 2001 года включает в себя лишь незначительные доработки.
Описание интерфейса I 2 C
Микроконтроллер STM32 включает в свой состав интерфейс I2С, который отличается своей развитостью. Он допускает несколько ведущих устройств на шине и поддерживает высокоскоростной режим. Кроме того, в микроконтроллере STM32 интерфейс I2C можно использовать для широкого спектра приложений, включая генерацию и верификацию контрольной суммы. С ним также можно работать по протоколам SMBus (System Management Bus) и PMBus (Power Management Bus). Большинство моделей STM32 включают в свой состав два интерфейса I2С с именами I2С1 и I2С2. Интерфейс может работать в одном из следующих четырёх режимов:
- Slave transmitter (ведомый передатчик);
- Slave receiver (ведомый приёмник);
- Master transmitter (ведущий передатчик);
- Master receiver (ведущий приёмник).
По умолчанию интерфейс работает в режиме «Ведомый» и автоматически переключается на «Ведущий» после генерирования старт-условия. Переключение с «Ведущего» на «Ведомый» происходит при потере арбитража или после генерирования стоп-условия, что позволяет работать нескольким «Ведущим» микроконтроллерам в одной системе поочередно. В режиме «Ведущий» I2C инициирует обмен данными и генерирует тактовый сигнал. Передаче последовательных данных всегда предшествует старт-условие, а завершается обмен всегда стоп-условием. Оба этих условия генерируются в режиме «Ведущий» программно. В режиме «Ведомый» I2C способен распознать свой собственный адрес (7 или 10 бит) и адрес общего вызова. Определение наличия адреса общего вызова можно включить или отключить программно. Адрес и данные передаются 8-битными посылками, старшим битом вперёд. Первый байт, следующий за стартусловием, содержит адрес (один байт в 7-битном режиме и два байта в 10-битном режиме). Адрес всегда передаётся в режиме «Ведущий».
За 8 тактами передачи байта данных следует 9-й такт, в течение которого приёмник должен послать бит уведомления ACK, получивший своё название от слова ACKnowledge. На рисунке 2 приведена временна′я диаграмма одной посылки интерфейса I2C. Наличие уведомления в ответе можно программно включить или отключить. Размерность адреса интерфейса I2C (7 бит или 10 бит и адрес
общего вызова) можно выбрать программно.
Рис. 2. Временная диаграмма одной посылки интерфейса I
Архитектура блока интерфейса I 2 С
Функциональная схема блока интерфейса I2C для микроконтроллера STM32 приведена на рисунке 3.
Рис. 3. Функциональная схема блока интерфейса I 2 C
Регистр сдвига на этой схеме представляет собой основной регистр, через который передаются и принимаются данные. Передаваемые данные предварительно записываются в регистр данных, после чего через регистр сдвига последовательно транслируются в линию связи SDA. Принимаемые по этой же линии связи данные накапливаются в регистре сдвига, а затем перемещаются в регистр данных. Таким образом, интерфейс может передавать и принимать данные только поочередно. Кроме того, регистр сдвига аппаратно подключён к компаратору, который позволяет сравнивать принятый адрес
с адресными регистрами и, таким образом, определять, для кого предназначен очередной блок данных. Узел управления частотой позволяет формировать сигнал синхронизации SCL в роли ведущего и синхронизироваться от этого сигнала в качестве ведомого устройства. Регистр CCR обеспечивает программную настройку данного узла. Блок интерфейса подключён к выходу PCLK1 шины APB1 через
два предварительных делителя. Микроконтроллер поддерживает два режима обмена: стандартный (Standard Speed) – до 100 кГц, и быстрый (Fast Speed) – до 400 кГц. В зависимости от режима обмена частота тактирования модуля должна быть не менее 2 МГц в стандартном режиме и не менее 4 МГц в быстром режиме. Блок вычисления позволяет аппаратно вычислять контрольную сумму блока данных и сохранять её в регистре PEC. Управление блоком интерфейса I2C, а также формирование флагов событий и прерываний выполняется узлом логики управления. Он же позволяет обслуживать запросы ПДП и формировать сигнал ACK. Связь этого блока с микроконтроллером осуществляется программно с помощью регистров управления CR1, CR2 и регистров состояния SR1, SR2.
Прерывания от I 2 C
Интерфейс I2C имеет аппаратную организацию, способную формировать запросы на прерывание в зависимости от режима работы и текущих событий. В таблице 1 приведены запросы на прерывание от интерфейса I2C.
Таблица 1. Запросы на прерывание от интерфейса I 2 C
Описание регистров
Для работы с интерфейсом I2C в микроконтроллере STM32 имеются специальные регистры. Карта этих регистров с названием входящих в них разрядов представлена в таблице 2. Рассмотрим регистры, необходимые для работы интерфейса I2С. К ним относятся:
- I 2 C_CR1 – управляющий регистр 1;
- I 2 C_CR2 – управляющий регистр 2;
- I 2 C_OAR1 – регистр собственного адреса 1;
- I 2 C_OAR2 – регистр собственного адреса 2;
- I 2 C_DR – регистр данных;
- I 2 C_SR1 – статусный регистр 1;
- I 2 C_SR2 – статусный регистр 2;
- I 2 C_CCR – регистр управления тактовым сигналом;
- I 2 C_TRISE – регистр параметра TRISE.
Некоторые разряды этих регистров используются для работы в режиме SMBus. Регистр I2C_CR1 является первым управляющим регистром интерфейса I2C. Он имеет следующие управляющие разряды:
- разряд 15 SWRST – обеспечивает программный сброс шины I 2 C;
- разряд 14 – зарезервирован;
- разряд 13 SMBus – формирует сигнал тревоги в режиме SMBus;
- разряд 12 PEC – служит для функции проверки ошибки пакета (Packet Error Checking);
- разряд 11 POS – служит для анализа сигналов ACK или PEC при приёме;
- разряд 10 ACK – возвращает бит уведомления ACK после приёма корректного байта адреса или данных;
- разряд 9 STOP – служит для формирования и анализа стоп-условия;
- разряд 8 START – служит для формирования и анализа старт-условия;
- разряд 7 NOSCTETCH – отключает растяжку такта в режиме ведомого;
- разряд 6 ENGC – разрешает общий вызов;
- разряд 5 ENPEC – разрешает сигнал PEC;
- разряд 4 ENARP – разрешает сигнал ARP;
- разряд 3 SMBTYPE – назначает тип интерфейса в качестве ведущего или ведомого для режима SMBus;
- разряд 2 – зарезервирован;
- разряд 1 SMBUS – переключает режимы I 2 C и SMBus;
- разряд 0 PE – разрешает работу интерфейса.
Регистр I2C_CR2 является вторым управляющим регистром интерфейса I2C и имеет следующие управляющие разряды:
- разряды 15…13 – зарезервированы;
- разряд 12 LAST – используется в режиме ведущего приёмника, чтобы позволить генерацию сигнала NACK по последнему принятому байту;
- разряд 11 DMAEN – разрешает запрос DMA;
- разряд 10 ITBUFEN – разрешает прерывания от буфера;
- разряд 9 ITEVTEN – разрешает прерывания от события;
- разряд 8 ITERREN – разрешает прерывания от ошибки;
- разряды 7 и 6 – зарезервированы;
- разряды 5…0 FREQ – задают частоту работы шины.
Регистр I2C_OAR1 – первый регистр собственного адреса, включает в себя следующие разряды:
- разряд 15 ADDMODE – задаёт 7- или 10-разрядный режим адресации в качестве ведомого;
- разряды 14…10 – зарезервированы;
- разряды 9 и 8 ADD – назначают 9 и 8 биты адреса при 10-битной адресации интерфейса;
- разряды 7…1 ADD – назначают 7…1 биты адреса;
- разряд 0 ADD0 – назначает бит 0 адреса при 10-битной адресации интерфейса.
Регистр I2C_OAR2 – второй регистр собственного адреса, включает в себя следующие разряды:
- разряды 15…8 – зарезервированы;
- разряды 7…1 ADD – назначают 7…1 биты адреса в режиме двойной адресации;
- разряд 0 ENDUAL – разрешает режим двойной адресации.
Регистр данных I2C_DR имеет 8 разрядов DR для приёма и передачи данных на шину I2C. В этот регистр данные записываются для передачи и читаются из него при приёме. Разряды 15…9 – зарезервированы. Регистр I2C_SR1 – первый статусный регистр, и включает в себя следующие разряды:
- разряд 15 SMBALERT – сигнализирует о тревоге шины SMBus;
- разряд 13 – зарезервирован;
- разряд 14 TIMEOUT – оповещает об ошибке превышения времени для сигнала SCL;
- разряд 12 PECERR – свидетельствует об ошибке PEC при приёме;
- разряд 11 OVR – формируется при ошибке переполнения данных;
- разряд 10 AF – возникает в случае ошибки уведомления;
- разряд 9 ARLO – указывает на ошибку потери прав на шину;
- разряд 8 BERR – устанавливается при ошибке шины;
- разряд 7 TxE – оповещает, что регистр данных пуст;
- разряд 5 – зарезервирован;
- разряд 6 RxNE – информирует, что регистр данных не пуст;
- разряд 4 STOPF – детектирует стоп-условие в режиме ведомого;
- разряд 3 ADD10 – устанавливается, когда ведущий послал первый байт адреса при 10-битной адресации;
- разряд 2 BTF – оповещает о завершении передачи байта;
- разряд 1 ADDR – устанавливается если послан адрес в режиме ведущего или принят адрес в режиме ведомого;
- разряд 0 SB – устанавливается при генерации старт-условия в режиме ведущего.
Регистр I2C_SR2 – второй статусный регистр, включает в себя следующие разряды:
- разряды 15…8 PEC – содержат контрольную сумму кадра;
- разряд 7 DUALF – является флагом двойной адресации в режиме ведо-мого;
- разряд 6 SMBHOST – устанавливается, когда принят заголовок SMBus Host в режиме ведомого;
- разряд 5 SMBDEFAULT – возникает, если принят адрес по умолчанию для SMBus-устройства в режиме ведомого;
- разряд 4 GENCALL – указывает, что принятадрес общего вызова в режиме ведомого;
- разряд 3 – зарезервирован;
- разряд 2 TRA – оповещает о режиме передачи/приёма;
- разряд 1 BUSY – информирует, что шина занята;
- разряд 0 MSL – детектирует режим «Ведущий»/«Ведомый».
Регистр I2C_CCR – регистр управления тактовым сигналом, который включает в себя разряды:
- разряд 15 F/S – задаёт стандартную или быструю скорость для режима ведущего;
- разряд 14 DUTY – назначает скважность 2 или 16/9 в быстром режиме;
- разряды 13 и 12 – зарезервированы;
- разряды 11…0 CCR – управляют тактовым сигналом для быстрой и стандартной скорости в режиме ведущего.
Регистр I2C_TRISE – регистр параметра TRISE, который включает в себя:
- разряды 15…6 – зарезервированы;
- разряды 5…0 TRISE – определяют максимальное время фронта для быстрой и стандартной скорости в режиме ведущего. Данный пара-метр задаёт момент времени, по которому производятся выборка состояния линий.
Более подробное описание назначения всех регистров I2C и их разрядов можно найти на сайте www.st.com .
П рограммирование интерфейса I 2 С
Рассмотрим практическую реализацию по использованию интерфейса I2С. Для этого можно воспользоваться стандартной библиотекой периферии микроконтроллера STM32. Для интерфейса I2C настройки режима, скорости и всего остального находятся в заголовочном файле и объявлены в виде структуры:
I2C_InitTypeDef:typedef struct{ uint32_t I2C_ClockSpeed; uint16_t I2C_Mode; uint16_t I2C_DutyCycle; uint16_t I2C_OwnAddress1; uint16_t I2C_Ack; uint16_t I2C_ AcknowledgedAddress; }I2C_InitTypeDef;
В этой структуре её элементы имеют следующее назначение:
- uint32_t I 2 C_ClockSpeed – частота тактового сигнала, максимум – 400 КГц;
- uint16_t I 2 C_Mode – режим работы;
- uint16_t I 2 C_DutyCycle – настройки для работы в быстром режиме;
- uint16_t I 2 C_OwnAddress – собственный адрес устройства;
- uint16_t I 2 C_Ack – включено или нет использование бита подтверждения ACK;
- uint16_t I 2 C_AcknowledgedAddress – выбор формата адреса: 7 бит или 10 бит.
Рассмотрим процедуры инициализации и работы с интерфейсом I2C. Для настройки интерфейса I2C в качестве ведущего устройства и передачи данных через него необходимо выполнить следующие действия:
- разрешить тактирование портов;
- инициализировать I 2 C, задав его скорость, адрес и формат адреса;
- назначить выводы микроконтроллера;
- разрешить работу интерфейса;
- сформировать стартовое условие;
- послать адрес адресуемого устройства и данные;
- сформировать стоповое условие.
Для облегчения процесса программирования желательно создать набор основных функций для работы с I2C. В листинге 1 приведена функция инициализации интерфейса I2C в соответствии с описанным выше алгоритмом.
Листинг 1 GPIO_InitTypeDef gpio; // Создание структуры для портов ввода-вывода I2C_InitTypeDef i2c; // Создание структуры для интерфейса I2C void init_I2C1(void) { // Включить тактирование RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); // Инициализировать I2C i2c.I2C_ClockSpeed = 100000; i2c.I2C_Mode = I2C_Mode_I2C; i2c.I2C_DutyCycle = I2C_DutyCycle_2; // Задать адрес=0x12 i2c.I2C_OwnAddress1 = 0x12; i2c.I2C_Ack = I2C_Ack_Disable; i2c.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; I2C_Init(I2C1, &i2c); // Назначить выводы интерфейса gpio.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
Теперь рассмотрим функцию для общения по I2C. Для расширения возможностей эта функция имеет три параметра: номер используемого блока I2C, направление передачи данных и адрес подчинённого устройства. Код данной функции приведён в листинге 2.
Листинг 2 void I2C_StartTransmission(I2C_TypeDef* I2Cx, uint8_t transmissionDirection, uint8_t slaveAddress) { // Ждать освобождения шины while (I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY)); // Сформировать старт-условие I2C_GenerateSTART(I2Cx, ENABLE); // Ждать установки бита while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT)); // Отправить адрес ведомому устройству I2C_Send7bitAddress(I2Cx, slaveAddress, transmissionDirection); // Если передача данных if(transmissionDirection== I2C_Direction_Transmitter) {while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));} // Если прием данных if(transmissionDirection== I2C_Direction_Receiver) {while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));} }
Приведённая функция использует простые функции передачи и приёма данных, приведённые в листинге 3.
Листинг 3 // Функция передачи данных void I2C_WriteData(I2C_TypeDef* I2Cx, uint8_t data) { // Вызвать библиотечную функцию передачи данных I2C_SendData(I2Cx, data); // Ждать окончания передачи данных while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); } // Функция приема данных uint8_t I2C_ReadData(I2C_TypeDef* I2Cx) { // Ждать поступления данных while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED)); data = I2C_ReceiveData(I2Cx); // Считать данные из регистра return data; // Возвратить данные в вызывающую функцию
Закончив обмен данными по I2C, необходимо вызвать функцию формирования стоп-условия
I2C_Generate STOP(I2Cx, ENABLE).
На основе приведённых функций можно создавать программы для работы с множеством разнообразных периферийных устройств.
Заключение
Неоспоримым преимуществом интерфейса I2C является простота подключения устройств с помощью всего лишь двух линий связи и общего провода, благодаря чему данный интерфейс надёжно закрепился в технике и по-прежнему широко применяется в современной аппаратуре.
Кто-то любит пирожки, а кто-то - нет.
Интерфейс i2c широко распространён и используется. В stm32f4 модулей, реализующих данный протокол, аж целых три штуки.
Естественно, с полной поддержкой всего этого дела.
Работа с модулем, в целом, такая же, как и в других контроллерах: даёшь ему команды, он их выполняет и отчитывается о результате:
Я> Шли START.
S> Ок, послал.
Я> Круто, шли адрес теперь. Вот такой: 0xXX.
S> Ок, послал. Мне сказали, что ACK. Давай дальше.
Я> Жив ещё, хорошо. Вот тебе номер регистра: 0xYY, - шли.
S> Послал, получил ACK.
Я> Шли ему теперь данные, вот тебе байт: 0xZZ.
S> Послал, он согласен на большее: ACK.
Я> Фиг ему, а не ещё. Шли STOP.
S> Okay.
И всё примерно в таком духе.
В данном контроллере выводы i2c раскиданы по портам таким образом:
PB6: I2C1_SCL
PB7: I2C1_SDA
PB8: I2C1_SCL
PB9: I2C1_SDA
PB10: I2C2_SCL
PB11: I2C2_SDA
PA8: I2C3_SCL
PC9: I2C3_SDA
Вообще, распиновку периферии удобно смотреть в на 59 странице.
Что удивительно, но для работы с i2c нужны все его регистры, благо их немного:
I2C_CR1
- команды модулю для отправки команд/состояний и выбор режимов работы;
I2C_CR2
- настройка DMA и указание рабочей частоты модуля (2-42 МГц);
I2C_OAR1
- настройка адреса устройства (для slave), размер адреса (7 или 10 бит);
I2C_OAR2
- настройка адреса устройства (если адресов два);
I2C_DR
- регистр данных;
I2C_SR1
- регистр состояния модуля;
I2C_SR2
- регистр статуса (slave, должен читаться, если установлен флаги ADDR или STOPF в SR1);
I2C_CCR
- настройка скорости интерфейса;
I2C_TRISE
- настройка таймингов фронтов.
Впрочем, половина из них типа «записать и забыть».
На плате STM32F4-Discovery уже есть I2C устройство, с коим можно попрактиковаться: CS43L22 , аудиоЦАП. Он подключён к выводам PB6/PB9. Главное, не забыть подать высокий уровень на вывод PD4 (там сидит ~RESET), иначе ЦАП не станет отвечать.
Порядок настройки примерно таков:
1
. Разрешить тактирование портов и самого модуля.
Нам нужны выводы PB6/PB9, потому надо установить бит 1 (GPIOBEN) в регистре RCC_AHB1ENR, чтоб порт завёлся.
И установить бит 21 (I2C1EN) в регистре RCC_APB1ENR, чтоб включить модуль I2C. Для второго и третьего модуля номера битов 22 и 23 соответственно.
2
. Дальше настраиваются выводы: выход Oped Drain (GPIO->OTYPER), режим альтернативной функции (GPIO->MODER), и номер альтренативной функции (GPIO->AFR).
По желанию можно настроить подтяжку (GPIO->PUPDR), если её нет на плате (а подтяжка к питанию обеих линий необходима в любом виде). Номер для I2C всегда один и тот же: 4. Приятно, что для каждого типа периферии заведён отдельный номер.
3
. Указывается текущая частота тактирования периферии Fpclk1 (выраженная в МГц) в регистре CR2. Я так понял, это нужно для расчёта разных таймингов протокола.
Кстати, она должна быть не менее двух для обычного режима и не менее четырёх для быстрого. А если нужна полная скорость в 400 кГц, то она ещё и должна делиться на 10 (10, 20, 30, 40 МГц).
Максимально разрешённая частота тактирования: 42 МГц.
4
. Настраивается скорость интерфейса в регистре CCR, выбирается режим (обычный/быстрый).
Cмысл таков: Tsck = CCR * 2 * Tpckl1, т.е. период SCK пропорционален CCR (для быстрого режима всё несколько хитрее, но в RM расписано).
5
. Настраивается максимальное время нарастания фронта в регистре TRISE. Для стандартного режима это время 1 мкс. В регистр надо записать количество тактов шины, укладывающихся в это время, плюс один:
если такт Tpclk1 длится 125 нс, то записываем (1000 нс / 125 нс) + 1 = 8 + 1 = 9.
6
. По желанию разрешается генерация сигналов прерывания (ошибки, состояние и данных);
7
. Модуль включается: флаг PE в регистре CR1 переводится в 1.
Дальше модуль работает уже как надо. Надо только реализовать правильный порядок команд и проверки результатов. Например, запись регистра:
1
. Сначала нужно отправить START, установив флаг с таким именем в регистре CR1. Если всё ок, то спустя некоторое время выставится флаг SB в регистре SR1.
Хочу заметить один момент, - если нет подтяжки на линии (и они в 0), то этот флаг можно не дождаться вовсе.
2
. Если флаг-таки дождались, то отправляем адрес. Для семибитного адреса просто записываем его в DR прям в таком виде, как он будет на линии (7 бит адреса + бит направления). Для десятибитного более сложный алгоритм.
Если устройство ответит на адрес ACK"ом, то в регистре SR1 появится флаг ADDR. Если нет, то флаг AF (Acknowledge failure).
Если ADDR появился, надо прочитать регистр SR2. Можно ничего там и не смотреть, просто последовательное чтение SR1 и SR2 сбрасывает этот флаг. А пока флаг установлен, SCL удерживается мастером в низком состоянии, что полезно, если надо попросить удалённое устройство подождать с отправкой данных.
Если всё ок, то дальше модуль перейдёт в режим приёма или передачи данных в зависимости от младшего бита отправленного адреса. Для записи он должен быть нулём, для чтения - единицей.
но мы рассматриваем запись, потому примем, что там был ноль.
3
. Дальше отправляем адрес регистра, который нас интересует. Точно так же, записав его в DR. После передачи выставится флаг TXE (буфер передачи пуст) и BTF (передача завершена).
4
. Дальше идут данные, которые можно отправлять, пока устройство отвечает ACK. Если ответом будет NACK, то эти флаги не установятся.
5
. По завершении передачи (или в случае непредвиденного состояния) отправляем STOP: устанавливается одноимённый флаг в регистре CR1.
При чтении всё то же самое. Меняется только после записи адреса регистра.
Вместо записи данных идёт повторная отправка START (повторный старт) и отправка адреса с установленным младшим битом (признак чтения).
Модуль будет ждать данных от устройства. Чтобы поощрать его к отправке следующих байт, надо перед приёмом установить флаг ACK в CR1 (чтобы после приёма модуль посылал этот самый ACK).
Как надоест, флаг снимаем, устройство увидит NACK и замолчит. После чего шлём STOP обычным порядком и радуемся принятым данным.
Вот то же самое в виде кода:
// Инициализация модуля
void i2c_Init(void)
{
uint32_t Clock = 16000000UL; // Частота тактирования модуля (system_stm32f4xx.c не используется)
uint32_t Speed = 100000UL; // 100 кГц
// Включить тактирование порта GPIOB
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;
// Настроим выводы PB6, PB9
// Open drain!
GPIOB->OTYPER |= GPIO_OTYPER_OT_6 | GPIO_OTYPER_OT_9;
// Подтяжка внешняя, потому тут не настраивается!
// если надо, см. регистр GPIOB->PUPDR
// Номер альтернативной функции
GPIOB->AFR &= ~(0x0FUL << (6 * 4)); // 6 очистим
GPIOB->AFR |= (0x04UL << (6 * 4)); // В 6 запишем 4
GPIOB->AFR &= ~(0x0FUL << ((9 - 8) * 4)); // 9 очистим
GPIOB->AFR |= (0x04UL << ((9 - 8) * 4)); // В 9 запишем 4
// Режим: альтернативная функция
GPIOB->MODER &= ~((0x03UL << (6 * 2)) | (0x03UL << (9 * 2))); // 6, 9 очистим
GPIOB->MODER |= ((0x02UL << (6 * 2)) | (0x02UL << (9 * 2))); // В 6, 9 запишем 2
// Включить тактирование модуля I2C1
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
// На данный момент I2C должен быть выключен
// Сбросим всё (SWRST == 1, сброс)
I2C1->CR1 = I2C_CR1_SWRST;
// PE == 0, это главное
I2C1->CR1 = 0;
// Считаем, что запущены от RC (16 МГц)
// Предделителей в системе тактирования нет (все 1)
// По-хорошему, надо бы вычислять это вс из
// реальной частоты тактирования модуля
I2C1->CR2 = Clock / 1000000UL; // 16 МГц
// Настраиваем частоту
{
// Tclk = (1 / Fperiph);
// Thigh = Tclk * CCR;
// Tlow = Thigh;
// Fi2c = 1 / CCR * 2;
// CCR = Fperiph / (Fi2c * 2);
uint16_t Value = (uint16_t)(Clock / (Speed * 2));
// Минимальное значение: 4
if(Value < 4) Value = 4;
I2C1->CCR = Value;
}
// Задаём предельное время фронта
// В стандартном режиме это время 1000 нс
// Просто прибавляем к частоте, выраженной в МГц единицу (см. RM стр. 604).
I2C1->TRISE = (Clock / 1000000UL) + 1;
// Включим модуль
I2C1->CR1 |= (I2C_CR1_PE);
// Теперь можно что-нибудь делать
}
// Отправить байт
bool i2c_SendByte(uint8_t Address, uint8_t Register, uint8_t Data)
{
if(!i2c_SendStart()) return false;
// Адрес микросхемы
if(!i2c_SendAddress(Address)) return i2c_SendStop();
// Адрес регистра
if(!i2c_SendData(Register)) return i2c_SendStop();
// Данные
if(!i2c_SendData(Data)) return i2c_SendStop();
// Стоп!
i2c_SendStop();
return true;
}
// Получить байт
bool i2c_ReceiveByte(uint8_t Address, uint8_t Register, uint8_t * Data)
{
if(!i2c_SendStart()) return false;
// Адрес микросхемы
if(!i2c_SendAddress(Address)) return i2c_SendStop();
// Адрес регистра
if(!i2c_SendData(Register)) return i2c_SendStop();
// Повторный старт
if(!i2c_SendStart()) return false;
// Адрес микросхемы (чтение)
if(!i2c_SendAddress(Address | 1)) return i2c_SendStop();
// Получим байт
if(!i2c_ReceiveData(Data)) return i2c_SendStop();
// Стоп!
i2c_SendStop();
return true;
}
Использование:
{
uint8_t ID = 0;
i2c_Init();
// Считаем, что PD4 выставлен в высокий уровень и ЦАП работает (это надо сделать как-нибудь)
// Отправка байта в устройство с адресом 0x94, в регистр 0x00 со значением 0x00.
i2c_SendByte(0x94, 0x00, 0x00);
// Приём байта из устройства с адресом 0x94 из регистра 0x01 (ID) в переменную buffer
i2c_ReceiveByte(0x94, 0x01, &ID);
}
Конечно, кроме как в учебном примере так делать нельзя. Ожидание окончания действия слишком уж долгое для такого быстрого контроллера.
Кто-то любит пирожки, а кто-то - нет.
Интерфейс i2c широко распространён и используется. В stm32f4 модулей, реализующих данный протокол, аж целых три штуки.
Естественно, с полной поддержкой всего этого дела.
Работа с модулем, в целом, такая же, как и в других контроллерах: даёшь ему команды, он их выполняет и отчитывается о результате:
Я> Шли START.
S> Ок, послал.
Я> Круто, шли адрес теперь. Вот такой: 0xXX.
S> Ок, послал. Мне сказали, что ACK. Давай дальше.
Я> Жив ещё, хорошо. Вот тебе номер регистра: 0xYY, - шли.
S> Послал, получил ACK.
Я> Шли ему теперь данные, вот тебе байт: 0xZZ.
S> Послал, он согласен на большее: ACK.
Я> Фиг ему, а не ещё. Шли STOP.
S> Okay.
И всё примерно в таком духе.
В данном контроллере выводы i2c раскиданы по портам таким образом:
PB6: I2C1_SCL
PB7: I2C1_SDA
PB8: I2C1_SCL
PB9: I2C1_SDA
PB10: I2C2_SCL
PB11: I2C2_SDA
PA8: I2C3_SCL
PC9: I2C3_SDA
Вообще, распиновку периферии удобно смотреть в на 59 странице.
Что удивительно, но для работы с i2c нужны все его регистры, благо их немного:
I2C_CR1
- команды модулю для отправки команд/состояний и выбор режимов работы;
I2C_CR2
- настройка DMA и указание рабочей частоты модуля (2-42 МГц);
I2C_OAR1
- настройка адреса устройства (для slave), размер адреса (7 или 10 бит);
I2C_OAR2
- настройка адреса устройства (если адресов два);
I2C_DR
- регистр данных;
I2C_SR1
- регистр состояния модуля;
I2C_SR2
- регистр статуса (slave, должен читаться, если установлен флаги ADDR или STOPF в SR1);
I2C_CCR
- настройка скорости интерфейса;
I2C_TRISE
- настройка таймингов фронтов.
Впрочем, половина из них типа «записать и забыть».
На плате STM32F4-Discovery уже есть I2C устройство, с коим можно попрактиковаться: CS43L22 , аудиоЦАП. Он подключён к выводам PB6/PB9. Главное, не забыть подать высокий уровень на вывод PD4 (там сидит ~RESET), иначе ЦАП не станет отвечать.
Порядок настройки примерно таков:
1
. Разрешить тактирование портов и самого модуля.
Нам нужны выводы PB6/PB9, потому надо установить бит 1 (GPIOBEN) в регистре RCC_AHB1ENR, чтоб порт завёлся.
И установить бит 21 (I2C1EN) в регистре RCC_APB1ENR, чтоб включить модуль I2C. Для второго и третьего модуля номера битов 22 и 23 соответственно.
2
. Дальше настраиваются выводы: выход Oped Drain (GPIO->OTYPER), режим альтернативной функции (GPIO->MODER), и номер альтренативной функции (GPIO->AFR).
По желанию можно настроить подтяжку (GPIO->PUPDR), если её нет на плате (а подтяжка к питанию обеих линий необходима в любом виде). Номер для I2C всегда один и тот же: 4. Приятно, что для каждого типа периферии заведён отдельный номер.
3
. Указывается текущая частота тактирования периферии Fpclk1 (выраженная в МГц) в регистре CR2. Я так понял, это нужно для расчёта разных таймингов протокола.
Кстати, она должна быть не менее двух для обычного режима и не менее четырёх для быстрого. А если нужна полная скорость в 400 кГц, то она ещё и должна делиться на 10 (10, 20, 30, 40 МГц).
Максимально разрешённая частота тактирования: 42 МГц.
4
. Настраивается скорость интерфейса в регистре CCR, выбирается режим (обычный/быстрый).
Cмысл таков: Tsck = CCR * 2 * Tpckl1, т.е. период SCK пропорционален CCR (для быстрого режима всё несколько хитрее, но в RM расписано).
5
. Настраивается максимальное время нарастания фронта в регистре TRISE. Для стандартного режима это время 1 мкс. В регистр надо записать количество тактов шины, укладывающихся в это время, плюс один:
если такт Tpclk1 длится 125 нс, то записываем (1000 нс / 125 нс) + 1 = 8 + 1 = 9.
6
. По желанию разрешается генерация сигналов прерывания (ошибки, состояние и данных);
7
. Модуль включается: флаг PE в регистре CR1 переводится в 1.
Дальше модуль работает уже как надо. Надо только реализовать правильный порядок команд и проверки результатов. Например, запись регистра:
1
. Сначала нужно отправить START, установив флаг с таким именем в регистре CR1. Если всё ок, то спустя некоторое время выставится флаг SB в регистре SR1.
Хочу заметить один момент, - если нет подтяжки на линии (и они в 0), то этот флаг можно не дождаться вовсе.
2
. Если флаг-таки дождались, то отправляем адрес. Для семибитного адреса просто записываем его в DR прям в таком виде, как он будет на линии (7 бит адреса + бит направления). Для десятибитного более сложный алгоритм.
Если устройство ответит на адрес ACK"ом, то в регистре SR1 появится флаг ADDR. Если нет, то флаг AF (Acknowledge failure).
Если ADDR появился, надо прочитать регистр SR2. Можно ничего там и не смотреть, просто последовательное чтение SR1 и SR2 сбрасывает этот флаг. А пока флаг установлен, SCL удерживается мастером в низком состоянии, что полезно, если надо попросить удалённое устройство подождать с отправкой данных.
Если всё ок, то дальше модуль перейдёт в режим приёма или передачи данных в зависимости от младшего бита отправленного адреса. Для записи он должен быть нулём, для чтения - единицей.
но мы рассматриваем запись, потому примем, что там был ноль.
3
. Дальше отправляем адрес регистра, который нас интересует. Точно так же, записав его в DR. После передачи выставится флаг TXE (буфер передачи пуст) и BTF (передача завершена).
4
. Дальше идут данные, которые можно отправлять, пока устройство отвечает ACK. Если ответом будет NACK, то эти флаги не установятся.
5
. По завершении передачи (или в случае непредвиденного состояния) отправляем STOP: устанавливается одноимённый флаг в регистре CR1.
При чтении всё то же самое. Меняется только после записи адреса регистра.
Вместо записи данных идёт повторная отправка START (повторный старт) и отправка адреса с установленным младшим битом (признак чтения).
Модуль будет ждать данных от устройства. Чтобы поощрать его к отправке следующих байт, надо перед приёмом установить флаг ACK в CR1 (чтобы после приёма модуль посылал этот самый ACK).
Как надоест, флаг снимаем, устройство увидит NACK и замолчит. После чего шлём STOP обычным порядком и радуемся принятым данным.
Вот то же самое в виде кода:
// Инициализация модуля
void i2c_Init(void)
{
uint32_t Clock = 16000000UL; // Частота тактирования модуля (system_stm32f4xx.c не используется)
uint32_t Speed = 100000UL; // 100 кГц
// Включить тактирование порта GPIOB
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;
// Настроим выводы PB6, PB9
// Open drain!
GPIOB->OTYPER |= GPIO_OTYPER_OT_6 | GPIO_OTYPER_OT_9;
// Подтяжка внешняя, потому тут не настраивается!
// если надо, см. регистр GPIOB->PUPDR
// Номер альтернативной функции
GPIOB->AFR &= ~(0x0FUL << (6 * 4)); // 6 очистим
GPIOB->AFR |= (0x04UL << (6 * 4)); // В 6 запишем 4
GPIOB->AFR &= ~(0x0FUL << ((9 - 8) * 4)); // 9 очистим
GPIOB->AFR |= (0x04UL << ((9 - 8) * 4)); // В 9 запишем 4
// Режим: альтернативная функция
GPIOB->MODER &= ~((0x03UL << (6 * 2)) | (0x03UL << (9 * 2))); // 6, 9 очистим
GPIOB->MODER |= ((0x02UL << (6 * 2)) | (0x02UL << (9 * 2))); // В 6, 9 запишем 2
// Включить тактирование модуля I2C1
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
// На данный момент I2C должен быть выключен
// Сбросим всё (SWRST == 1, сброс)
I2C1->CR1 = I2C_CR1_SWRST;
// PE == 0, это главное
I2C1->CR1 = 0;
// Считаем, что запущены от RC (16 МГц)
// Предделителей в системе тактирования нет (все 1)
// По-хорошему, надо бы вычислять это вс из
// реальной частоты тактирования модуля
I2C1->CR2 = Clock / 1000000UL; // 16 МГц
// Настраиваем частоту
{
// Tclk = (1 / Fperiph);
// Thigh = Tclk * CCR;
// Tlow = Thigh;
// Fi2c = 1 / CCR * 2;
// CCR = Fperiph / (Fi2c * 2);
uint16_t Value = (uint16_t)(Clock / (Speed * 2));
// Минимальное значение: 4
if(Value < 4) Value = 4;
I2C1->CCR = Value;
}
// Задаём предельное время фронта
// В стандартном режиме это время 1000 нс
// Просто прибавляем к частоте, выраженной в МГц единицу (см. RM стр. 604).
I2C1->TRISE = (Clock / 1000000UL) + 1;
// Включим модуль
I2C1->CR1 |= (I2C_CR1_PE);
// Теперь можно что-нибудь делать
}
// Отправить байт
bool i2c_SendByte(uint8_t Address, uint8_t Register, uint8_t Data)
{
if(!i2c_SendStart()) return false;
// Адрес микросхемы
if(!i2c_SendAddress(Address)) return i2c_SendStop();
// Адрес регистра
if(!i2c_SendData(Register)) return i2c_SendStop();
// Данные
if(!i2c_SendData(Data)) return i2c_SendStop();
// Стоп!
i2c_SendStop();
return true;
}
// Получить байт
bool i2c_ReceiveByte(uint8_t Address, uint8_t Register, uint8_t * Data)
{
if(!i2c_SendStart()) return false;
// Адрес микросхемы
if(!i2c_SendAddress(Address)) return i2c_SendStop();
// Адрес регистра
if(!i2c_SendData(Register)) return i2c_SendStop();
// Повторный старт
if(!i2c_SendStart()) return false;
// Адрес микросхемы (чтение)
if(!i2c_SendAddress(Address | 1)) return i2c_SendStop();
// Получим байт
if(!i2c_ReceiveData(Data)) return i2c_SendStop();
// Стоп!
i2c_SendStop();
return true;
}
Использование:
{
uint8_t ID = 0;
i2c_Init();
// Считаем, что PD4 выставлен в высокий уровень и ЦАП работает (это надо сделать как-нибудь)
// Отправка байта в устройство с адресом 0x94, в регистр 0x00 со значением 0x00.
i2c_SendByte(0x94, 0x00, 0x00);
// Приём байта из устройства с адресом 0x94 из регистра 0x01 (ID) в переменную buffer
i2c_ReceiveByte(0x94, 0x01, &ID);
}
Конечно, кроме как в учебном примере так делать нельзя. Ожидание окончания действия слишком уж долгое для такого быстрого контроллера.