Программирование ARM ESP32: драйвер I2C Sat, April 20 2024  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

ESP32: драйвер I2C Печать
Добавил(а) microsin   

I2C это последовательный, синхронный, полудуплексный коммуникационный протокол, который позволяет существовать на одной шине несколько устройств master и slave. Шина I2C содержит 2 сигнала: последовательные данные (serial data line, SDA) и последовательные такты (serial clock, SCL). Оба сигнала подтянуты к лог. 1 верхними нагрузочными резисторами (pull-up).

У ESP32 есть два контроллера I2C (у ESP32-C3 контроллер I2C только один), которые отвечают за обработку обмена данными по шине I2C. Один контроллер I2C может работать либо как master, либо как slave. Однако не у всех чипов семейства ESP32 контроллер I2C может работать в режиме slave (например так у ESP32-C2).

В составе инструментария разработчика ESP-IDF [2] есть драйвер I2C, который обслуживает коммуникации по шине I2C. Драйвер поддерживает следующие функции:

● Чтение и запись байт в режиме Master.
● Режим Slave.
● Чтение и запись в регистров, которые в свою очередь считываются/записываются устройством master.

[Использование драйвера I2C]

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

1. Конфигурация - на этом шаге устанавливаются инициализационные параметры - режим master или slave, ножки GPIO для сигналов SDA и SCL, тактовая частота, и т. д.

2. Установка драйвера - активация драйвера на одном из двух контроллеров I2C в качестве устройства master или slave.

3. В зависимости от того, как конфигурируется драйвер - если сконфигурирован master, то управление коммуникациями. Если slave - ответ на сообщения от master.

4. Обработка прерываний - конфигурирование и обслуживание прерываний I2C.

5. Опционально дополнительная пользовательская настройка I2C - подстройка параметров по умолчанию (тайминги, порядок бит, и т. п.).

6. Обработка ошибок - как распознать и обработать ошибки конфигурации драйвера и ошибки коммуникации.

7. Удаление драйвера - освобождение ресурсов, используемых драйвером I2C, когда коммуникации завершены.

[Конфигурация]

Для установки обмена по шине I2C начинают с конфигурирования драйвера. Это делается путем установки параметров структуры i2c_config_t:

● Установка режима master или slave (значение перечисления i2c_mode_t).
● Конфигурирование ножек GPIO для SDA и SCL, установка при необходимости внутренней подтяжки pull-up.
● Установка скорости обмена (только для режима master).
● Только для режима slave - разрешение или нет 10-битного режима адресации, установка адреса.

После этого производится инициализация вызовом функции i2c_param_config() с передачей ей в качестве параметров номера контроллера I2C и структуры i2c_config_t.

Пример конфигурации для master:

int i2c_master_port = 0;
i2c_config_t conf = {
   .mode = I2C_MODE_MASTER,
   .sda_io_num = CONFIG_I2C_MASTER_SDA_IO,
   .sda_pullup_en = GPIO_PULLUP_ENABLE,
   .scl_io_num = CONFIG_I2C_MASTER_SCL_IO,
   .scl_pullup_en = GPIO_PULLUP_ENABLE,
   .master.clk_speed = CONFIG_I2C_MASTER_FREQ_HZ,
   // Здесь можно использовать флаги I2C_SCLK_SRC_FLAG_* для выбора
   // источника тактирования I2C:
   .clk_flags = 0,
};

Пример конфигурации для slave:

int i2c_slave_port = I2C_SLAVE_NUM;
i2c_config_t conf_slave = {
   .sda_io_num = CONFIG_I2C_SLAVE_SDA_IO,
   .sda_pullup_en = GPIO_PULLUP_ENABLE,
   .scl_io_num = CONFIG_I2C_SLAVE_SCL_IO,
   .scl_pullup_en = GPIO_PULLUP_ENABLE,
   .mode = I2C_MODE_SLAVE,
   .slave.addr_10bit_en = 0,
   .slave.slave_addr = CONFIG_ESP_SLAVE_ADDR,
   .clk_flags = 0,
};

На этом шаге i2c_param_config() также установит некоторые параметры конфигурации I2C в значения по умолчанию, которые определены спецификацией I2C. Подробнее об этом см. далее врезку "Дополнительная пользовательская настройка I2C".

Конфигурация источника тактирования. Добавляется clock allocator для поддержки различных источников тактирования. Он будет выбирать один из источников тактов, который удовлетворит всем требованиям по частоте и возможностям (как запрашивалось в i2c_config_t::clk_flags).

Когда поле i2c_config_t::clk_flags равно 0, clock allocator выберет только соответствие с желаемой частотой. Если нет необходимости в специальных возможностях, таких как APB, то вы можете конфигурировать clock allocator для выбора источника тактов только в соответствии с желаемой частотой. Для этого установите i2c_config_t::clk_flags в 0. Для характеристик тактов см. таблицу ниже.

Примечание: установленные такты недопустимы, если не соответствуют запрошенным возможностям, т. е. любой бит запрошенных возможностей (clk_flags) равен 0.

Таблица 1. Характеристики источников тактов ESP32.

Имя тактов Частота тактов MAX частота SCL Возможности тактов
APB clock 80 МГц 4 МГц /

Флаги i2c_config_t::clk_flags имеют следующее значение:

I2C_SCLK_SRC_FLAG_AWARE_DFS: скорость тактов I2C не меняется, когда меняется частота тактов APB.
I2C_SCLK_SRC_FLAG_LIGHT_SLEEP: поддерживается режим пониженного энергопотребления Light-sleep, в котором такты APB не работают.

Некоторые флаги могут не поддерживаться на ESP32, поэтому перед их использованием ознакомьтесь с техническим руководством.

Частота тактов SCL в режиме master не должна быть больше максимальной частоты для SCL, упомянутой в таблице выше.

(Максимальная) частота тактов SCL будет зависеть от номинала резисторов pull-up и емкости сигналов шины (или от емкости всех подключенных к шине устройств). Таким образом, пользователям следует выбрать корректный номинал резисторов pull-up, чтобы шина работала на нужной частоте. Обычно для протокола I2C выбирают резисторы pull-up в диапазоне от 1 кОм до 10 кОм, однако для разных частот могут понадобиться разные номиналы резисторов. В общем, чем выше выбранная частота, тем меньше должен быть номинал резисторов (но не меньше 1 кОм). Причина в том, что слишком большой резистор будет сильно ограничивать ток, приводя к увеличению времени нарастания уровня сигнала и уменьшению рабочей частоты шины. Обычно рекомендуется использовать резисторы в интервале 2 .. 5 кОм, однако может понадобиться уточнение номиналов, чтобы подогнать их под требования реального приложения.

[Инсталляция драйвера]

После того, как драйвер I2C был сконфигурирован, установите его путем вызова функции i2c_driver_install() со следующими параметрами:

● Номер порта (контроллера) I2C, тип i2c_port_t.
● Режим master или slave, выбранный из i2c_mode_t.
● (Только для режима slave) размер выделяемых буферов для передачи и приема данных. Поскольку принцип работы шины I2C ориентирован на устройство master, данные могут передаваться от slave к master только по запросу master. Таким образом, устройство slave должно иметь буфер отправки, кода приложение slave записывает данные. Данные остаются в буфере отправки, чтобы их мог прочитать master по своему усмотрению.
● Флаги для выделения прерывания (см. значения ESP_INTR_FLAG_* в esp_hw_support/include/esp_intr_alloc.h).

[Обмен в режиме master]

После установки I2C-драйвера ESP32 готов к обмену с другими устройствами I2C.

Контроллер I2C, работающий в режиме master, отвечает за установку обмена данными с устройствами I2C slave и отправку команд, приводящих в действие slave - например, для проведения измерения температуры и отправки считанных данных обратно к master.

Для улучшения организации этого процесса драйвер предоставляет контейнер, так называемый "command link", он должен быть заполнены последовательностью команд, и затем передан для выполнения контроллеру I2C.

Master Write. Пример ниже показывает, как строится command link для I2C master, чтобы отправить n байт устройству slave.

I2C driver command link master write example fig01

Рис. 1. I2C command link - пример записи master.

Следующее описание показывает, как работает command link для "master write", как он устанавливается, и что происходит внутри:

1. Создается command link вызовом функции i2c_cmd_link_create(). Затем command link заполняется последовательностью данных для отправки в slave:

(a) Start bit - вызовом i2c_master_start().
(b) Slave address - вызовом i2c_master_write_byte(). В качестве аргумента этой функции предоставляется один байт адреса.
(c) Data - один или большее количество байт в качестве аргумента i2c_master_write().
(d) Stop bit - вызовом i2c_master_stop().

У обоих функций i2c_master_write_byte() и i2c_master_write() есть дополнительный аргумент, указывающий, должен ли master убедиться, что получил бит ACK.

2. Запускается выполнение command link контроллером I2C путем вызова i2c_master_cmd_begin(). Как только выполнение началось, command link не может быть изменен.

3. После передачи команд освобождаются ресурсы, используемые для command link, путем вызова i2c_cmd_link_delete().

Master Read. Пример ниже показывает, как построить command link для I2C master, чтобы прочитать n байт из устройства slave.

I2C driver command link master read example fig02

Рис. 2. I2C command link - пример чтения master.

В сравнении с записью данных, command link заполняется на шаге 4 не вызовом функций i2c_master_write..., а i2c_master_read_byte() и/или i2c_master_read(). Также последнее чтение на шаге 5 конфигурируется так, что master не предоставляет бит ACK.

Индикация Write или Read. После отправки адреса slave-устройства (шаг 3 на обоих диаграммах рис. 1 и рис. 2), устройство master либо записывает в устройство slave, либо читает из него. Информация о том, какая из этих операций происходит, скрыта в самом младшем бите адреса slave-устройства.

По этой причине command link, посылаемый устройством master для записи данных в slave, содержит адрес (ESP_SLAVE_ADDR << 1) | I2C_MASTER_WRITE, и это выглядит следующим образом:

i2c_master_write_byte(cmd, (ESP_SLAVE_ADDR << 1) | I2C_MASTER_WRITE, ACK_EN);

Для чтения из slave вызов будет выглядеть так:

i2c_master_write_byte(cmd, (ESP_SLAVE_ADDR << 1) | I2C_MASTER_READ, ACK_EN);

[Обмен в режиме slave]

После инсталляции I2C-драйвера ESP32 готов к обмену с другими устройствами I2C. Для режима slave предоставляются следующие API-функции:

i2c_slave_read_buffer(). Всякий раз, когда master записывает данные в устройство slave, slave будет автоматически сохранять записанные данные в буфер приема. Это позволяет приложению slave вызвать функцию i2c_slave_read_buffer(), когда это необходимо. У этой функции также есть параметр, указывающий время блокировки, если в буфере приема нет данных. Это позволяет приложению slave ждать в течение указанного таймаута момента поступления данных в буфер.

i2c_slave_write_buffer(). Буфер отправки используется для хранения данных, которые устройство slave хочет отправить устройству master, в порядке следования FIFO. Данные остаются здесь, пока master не запросит их. У функции i2c_slave_write_buffer() есть параметр для указания времени блокировки, если буфер отправки уже заполнен. Это позволит приложению slave application ждать в течение указанного таймаута появления в буфере отправки адекватного пространства.

Пример кода, где показано использование этих функций, можно найти в $ESPIDF_PATH/examples/peripherals/i2c.

Обработка прерываний. Во время инсталляции драйвера по умолчанию также устанавливается обработчик прерывания I2C.

Как упоминалось выше в конце секции "Конфигурация", когда функция i2c_param_config() инициализирует конфигурацию драйвера для порта I2C, она также установит параметры коммуникации I2C в значения по умолчанию, определенные спецификацией I2C. Некоторые другие связанные параметры предварительно конфигурируются в регистрах контроллера I2C.

Все эти параметры можно поменять на значения, определяемые пользователем, с помощью вызова специальных функций, показанных в таблице ниже. Обратите внимание, что значения тайминга указаны в тактах шины APB. Частота APB указана в I2C_APB_CLK_FREQ.

Таблица 2. Другие конфигурируемые параметры I2C.

Функция Конфигурируемый параметр
i2c_set_period() Время лог. 1 и лог. 0 для импульсов SCL.
i2c_set_start_timing() Тайминг сигналов SCL и SDA, используемый для генерации сигналов START.
i2c_set_stop_timing() Тайминг сигналов SCL и SDA, используемый для генерации сигналов STOP.
i2c_set_data_timing() Временная взаимосвязь между сигналами SCL и SDA, когда slave их оцифровывает, а также когда master их переключает.
i2c_set_timeout() Таймаут I2C.
i2c_set_data_mode() Выбор, какой передавать бит первым при передаче / приеме, LSB или MSB, выбор одного из режимов, определенных в перечислении i2c_trans_mode_t.

Каждая из перечисленных в таблице 2 функций имеет комплементарную функцию _get_, чтобы можно было проверить текущее установленное значение. Например, для проверки таймаута I2C вызовите i2c_get_timeout().

Для проверки значений параметров по умолчанию, устанавливаемых в процессе конфигурации драйвера, см. файл driver/i2c.c и определения с суффиксом _DEFAULT.

Также можно использовать различные ножки GPIO для сигналов SDA и SCL, и после этого сконфигурировать резисторы pull-up вызовом функции i2c_set_pin(). Если вы хотите изменить уже введенные значения, используйте функцию i2c_param_config().

Примечание: внутренние pull-up резисторы ESP32 имеют номинал в диапазоне десятков килоом, сопротивление которых в большинстве случаев слишком большое, чтобы можно было обойтись без внешних резисторов I2C pull-up. Рекомендуется использовать внешние резисторы pull-up с номиналами, соответствующими спецификации I2C.

[Обработка ошибок]

Основные функции драйвера I2C либо возвратят ESP_OK при успешном завершении, либо определенный код ошибки в случае неудачи. Хорошая практика - всегда проверять возвращаемые из функций значения и предусмотреть обработку ошибок. Драйвер также печатает в лог сообщения, которые детализируют ошибку, например при проверке допустимости введенной конфигурации. Для подробностей см. файл driver/i2c.c и определения с суффиксом _ERR_STR.

Используйте выделенные прерывания для захвата отказов коммуникаций. Например, если slave растягивает сигнал тактов на слишком большое время, когда подготавливает отправку данных обратно к master, сработает прерывание I2C_TIME_OUT_INT.

В случае ошибки обмена вы можете сбросить внутренние аппаратные буферы вызовом функций i2c_reset_tx_fifo() и i2c_reset_rx_fifo() для буферов отправки и приема соответственно.

[Пример приложения]

См. $ESPIDF_PATH/examples/peripherals/i2c.

Ниже в таблице приведено общее описание функций. Полное описание функций, типов данных и определений см. в документации [1].

Примечание: не все функции Espressif поддерживают режим slave, например ESP32C2. Функции с корнем _master_ должны вызваться только в режиме I2C master, функции с корнем _slave_ должны вызываться только в режиме I2C slave.

Функция Описание
i2c_driver_install Устанавливает драйвер I2C. В режиме master, если кэш желательно запретить (как например при записи flash) и slave чувствительно к интервалам времени, рекомендуется использовать ESP_INTR_FLAG_IRAM. В таком случае используйте память, выделенную во внутреннем RAM для функций чтения и записи i2c, потому что мы не можем обращаться к psram (если psram разрашена) в функции обработки прерывания, когда кэш запрещена.
i2c_driver_delete Удалит драйвер. Эта функция не потокобезопасна. Убедитесь перед вызовом этой функции, что ни один из потоков не будет постоянно удерживать семафоры.
i2c_param_config Конфигурирует шину I2C с указанными параметрами.
i2c_reset_tx_fifo Сбросит аппаратный FIFO передачи I2C.
i2c_reset_rx_fifo Сбросит FIFO приема I2C.
i2c_set_pin Конфигурирует выводы GPIO для сигналов SCK и SDA.
i2c_master_write_to_device Выполняет запись в устройство, подключенное к определенному порту I2C. Эта функция является оберткой над i2c_master_start(), i2c_master_write(), i2c_master_read(), и т. д.
i2c_master_read_from_device Выполнит чтение устройства, подключенного к определенному порту I2C. Это функция является оберткой над i2c_master_start(), i2c_master_write(), i2c_master_read(), и т. д.
i2c_master_write_read_device Выполняет запись, за которым идет чтение. Между записью и чтением используется сигнал REPEATED START I2C, шина не освобождается, пока эти две транзакции не завершатся. Эта функция является оберткой над i2c_master_start(), i2c_master_write(), i2c_master_read(), и т. д.
i2c_cmd_link_create_static Создает и инициализирует список команд I2C с указанным буфером. Все выделения для данных или сигналов (START, STOP, ACK, ...) будут выполняться в этом буфере. Этот буфер должен оставаться достоверным в течение всего времени выполнения транзакции. После завершения всех транзакций I2C требуется вызвать i2c_cmd_link_delete_static(). Настоятельно рекомендуется не выделять этот буфер в стеке (т. е. в локальных переменных функции). Используемый размер данных может увеличиться в будущем, в результате может быть переполнение стека, поскольку макрос I2C_LINK_RECOMMENDED_SIZE также возвращает большее значение. Лучше использовать буфер, распределенный статически или динамически (через malloc).
i2c_cmd_link_delete_static Освободит список команд I2C, выделенный статически вызовом i2c_cmd_link_create_static.
i2c_cmd_link_create Создает и инициализирует список команд I2C с указанным буфером. После завершения всех транзакций I2C требуется вызвать i2c_cmd_link_delete() для освобождения ресурсов. Требуемое количество байт выделяется автоматически.
i2c_cmd_link_delete Освободит список команд I2C, созданный ранее вызовом i2c_cmd_link_create.
i2c_master_start Поместит в очередь сигнал START для указанного списка команд. Вызовите i2c_master_cmd_begin() для отправки всех поставленных в очередь команд.
i2c_master_write_byte Поместит в очередь команду записи байта для списка команд. Через порт I2C будет отправлен 1 байт. Вызовите i2c_master_cmd_begin() для отправки всех поставленных в очередь команд.
i2c_master_write Поместит в очередь команду записи нескольких байт для списка команд. Вызовите i2c_master_cmd_begin() для отправки всех поставленных в очередь команд.
i2c_master_read_byte Поместит в очередь команду чтения байта для списка команд. По шине I2C будет прочитан 1 байт. Вызовите i2c_master_cmd_begin() для отправки всех поставленных в очередь команд.
i2c_master_read Поместит в очередь команду чтения нескольких байт для списка команд. По шине I2C будет прочитано несколько байт. Вызовите i2c_master_cmd_begin() для отправки всех поставленных в очередь команд.
i2c_master_stop Поместит в очередь команду сигнала STOP для списка команд. Вызовите i2c_master_cmd_begin() для отправки всех поставленных в очередь команд.
i2c_master_cmd_begin Посылает все поставленные в очередь команды по шине I2C, в режиме master. Задача (вызывающий код) будет заблокирован, пока все команды не будут отправлены. Порт I2C защищен мьютексом, так что эта функция потокобезопасная.
i2c_slave_write_buffer Записывает байты во внутренний кольцевой буфер данных устройства slave. Когда буфер TX FIFO пуст, ISR заполнит аппаратный FIFO данными из внутреннего кольцевого буфера.
i2c_slave_read_buffer Прочитает байты из внутреннего буфера I2C. Когда шина I2C принимает данные, ISR будет копировать эти данные из аппаратного RX FIFO в внутренний кольцевой буфер. Вызов этой функции будет тогда копировать байты из внутреннего кольцевого буфера в буфер данных пользователя.
i2c_set_period Установит период тактов I2C master.
i2c_get_period Прочитает текущий период тактов I2C master.
i2c_filter_enable Разрешает аппаратный фильтр на шине I2C. Иногда на шину I2C влияют высокочастотные помехи (например импульсы длительностью порядка 20 нс), или фронт нарастания уровня тактов SCL очень пологий, это может нарушить поведение машины состояний master. Разрешение функции аппаратного фильтра позволяет отфильтровывать высокочастотные помехи, что делает работу master более стабильной.
i2c_filter_disable Запрещает фильтр шины I2C.
i2c_set_start_timing Установит тайминг сигнала START I2C master.
i2c_get_start_timing Прочитает текущий тайминг сигнала START I2C master.
i2c_set_stop_timing Установит тайминг сигнала STOP I2C master.
i2c_get_stop_timing Прочитает текущий тайминг сигнала STOP I2C master.
i2c_set_data_timing Установит тайминг сигнала данных I2C.
i2c_get_data_timing Прочитает тайминг сигнала данных I2C.
i2c_set_timeout Установит значение таймаута I2C.
i2c_get_timeout Прочитает значение таймаута I2C.
i2c_set_data_mode Установит режим транзакции данных I2C.
i2c_get_data_mode Прочитает режим транзакции данных I2C.

[Ссылки]

1. ESP32 Inter-Integrated Circuit (I2C) site:espressif.com.
2. Установка среды разработки ESP-IDF для ESP32.

 

Добавить комментарий


Защитный код
Обновить

Top of Page