• Система автоматизации с открытым исходным кодом на базе esp8266/esp32 микроконтроллеров и приложения IoT Manager. Наша группа в Telegram

I2C на RTL...

pvvx

Активный участник сообщества
Попробовал нативно работать с аппартным I2C.
Удобный аппаратный контроллер.
На примере с INA219 получилось работать так:
1) Конфигурируем i2c в нужный режим (их там куча :) )
2) Забиваем в FIFO команды и данные для передачи.
3) Ожидаем готовности или спустя время транзакции, считываем готовые данные от датчика из FIFO.
Никакой тусовки и ногодрыга.
Пример (только кода) прерывания на аппаратном таймере, непрерывно читающем измеренные ток и напряжение на датчике INA219:
Код:
#define i2c_reg(r) *((volatile uint32 *)(pi2c->base_regs + r))
void ina_tick_handler(void *par) {
    PINA219DRV p = &ina219drv;
    i2c_drv_t *pi2c = &p->i2c;
    DiagPrintf("%d,%d\n", p->buf_i2c.us[0], p->buf_i2c.ss[1]);
    switch(p->status) {
    default:
        // Disable controller.
        i2c_reg(REG_DW_I2C_IC_ENABLE) = 0;
        DiagPrintf(".");
        p->status = 1;
        break;
    case 1:
        // Enable controller.
        i2c_reg(REG_DW_I2C_IC_ENABLE) = BIT_IC_ENABLE;
        DiagPrintf("*");
        p->status = 2;
        break;
    case 3:
        if (i2c_reg(REG_DW_I2C_IC_RAW_INTR_STAT) &    BIT_IC_RAW_INTR_STAT_TX_ABRT) {
            uint32 tmp = i2c_reg(REG_DW_I2C_IC_CLR_INTR);
            p->status = 0;
            break;
        } else {
            // Считаем готовые значения тока и напряжения INA219 из FIFO ic I2C
            p->buf_i2c.uc[1] = i2c_reg(REG_DW_I2C_IC_DATA_CMD);
            p->buf_i2c.uc[0] = i2c_reg(REG_DW_I2C_IC_DATA_CMD);
            p->buf_i2c.uc[3] = i2c_reg(REG_DW_I2C_IC_DATA_CMD);
            p->buf_i2c.uc[2] = i2c_reg(REG_DW_I2C_IC_DATA_CMD);
        }
    case 2:
        // Заполним FIFO ic I2C командами (адрес устройства и прочее он сделает сам, автоматически, согласно начальной конфигурации контроллера IC)
        // Задать номер регистра напряжения для чтения в INA219. Write addr reg + stop.
        i2c_reg(REG_DW_I2C_IC_DATA_CMD) = 2 | BIT_IC_DATA_CMD_STOP;
        // Задать транзакцию чтения регистра напряжения из INA219. Read command 2 bytes + stop.
        i2c_reg(REG_DW_I2C_IC_DATA_CMD) = BIT_IC_DATA_CMD_CMD;
        i2c_reg(REG_DW_I2C_IC_DATA_CMD) = BIT_IC_DATA_CMD_CMD | BIT_IC_DATA_CMD_STOP;
        // Задать номер регистра тока для чтения из INA219. Write addr reg + stop.
        i2c_reg(REG_DW_I2C_IC_DATA_CMD) = 1 | BIT_IC_DATA_CMD_STOP;
        // Задать транзакцию чтения регистра тока из INA219. Read command 2 bytes + stop.
        i2c_reg(REG_DW_I2C_IC_DATA_CMD) = BIT_IC_DATA_CMD_CMD;
        i2c_reg(REG_DW_I2C_IC_DATA_CMD) = BIT_IC_DATA_CMD_CMD | BIT_IC_DATA_CMD_STOP;
        p->status = 3;
        break;
    }
}
Буфера накопления данных к передаче для простоты примера убраны и заменены на вывод в LogUART. Льет примерно такой лог:
Снимок1458.gif
Заполненная командами FIFO I2С исполняет такую пачку передач-приема по I2C, каждое прерывание таймера:
Ina219_int.gif
Частоты I2C CLK разгоняются любые, до нескольких МГц. Можно указать и работу в режиме HS, тогда контроллер I2C будет автоматом вставлять команду перехода в SMBUS режим для повышения частоты за МГц-ы... На чтение данных, для заполнения буферов с типом пинг-понг (один заливается, второй обрабатывается и передается на внешний сервер), можно включить и DMA... Но тут пока простой пример.
Скорость измерения у INA219 к 600 us на 12 бит замеры, чтение значений тока и напряжения по I2C при CLK I2C ~360 кГц (такой вышел для теста - плата с датчиком на длинных проводах и соплях - сделана для Ардуиншиков и более не тянет - щупы осциллографа уже вносят завалы фронтов своей емкостью...) занимает порядка 300 us. Это и выйдет максимальной частотой для опроса по таймеру, но в UART это уже в текстовом виде не вывести - не хватит скорости UART :)...

Инициализацию i2с контроллера тоже написал свою, она очень простая... Выкинул громоздкий SDK-шный Hal... Она оптимизирована под конкретные задачи и использует совсем мало кода и данных. Может позже скину пример в мою сборку SDK.

PS: 4-ре I2С контроллера в RTL871x по регистрам аналогичны Intel D2000 и расписаны в доках от Intel D2000. Код от SDK к Intel D2000 для I2C, после модификации и вставки пару нюансов тоже работает.
 

Вложения

Последнее редактирование:

pvvx

Активный участник сообщества
Кинул почти полный примерчик, попроще, установок I2C в режим мастер для pool опроса и/или для использования как описано выше постом.
Сам типа драйвер I2C: RTL00_WEB/i2c_drv.c at master · pvvx/RTL00_WEB · GitHub
И пример с logUART: RTL00_WEB/ina219buf.c at master · pvvx/RTL00_WEB · GitHub

Если I2C используется с фиксироваными входами/выходами и не требуется переназначений и смены его конфигурации на ходу, то для инициализации достаточно указать данные в его структуре и вызвать int _i2c_init(i2c_drv_t *pi2c) с указателем на неё... Она вызывает только пару коротких процедур _i2c_ic_on(), i2c_disable() и меняет PwrState для PMU, если используются энерго-экономичные режимы. Всё остальное можно тупо описывать через запись и чтение регистра FIFO аппаратных блоков I2C с паузами для ожидания выполнения и результата (опроса регистра статуса на Break на линии I2C - NACK).
Алго ACK задается в специальном регистре:
// General Call Ack
i2c_reg(REG_DW_I2C_IC_ACK_GENERAL_CALL) = BIT_CTRL_IC_ACK_GENERAL_CALL(1);
Остальные процедуры даны ради примера, чтобы было понятно, как работать с данными встроенными аппаратным I2C контролерами...
 
Последнее редактирование:

pvvx

Активный участник сообщества
Кстати hal'овские i2c_read i2c_write блокирующие или нет?
Которые по DMA - нет.
--------
На основе примера, описанном тут, сделан опрос INA219 c передачей потока в websocket для отображения графика с шагом в 1 ms тока и напряжения. Скрипт на клиента ещё не дописан.
Пример работы websocket-а представлен тут https://esp8266.ru/forum/threads/rtl00-mp3-player.1697/page-11#post-37488 , исходники включены в Web-свалка на RTL871x
 
Последнее редактирование:
  • Like
Реакции: Neov

sharikov

Active member
Кинул почти полный примерчик, попроще, установок I2C в режим мастер для pool опроса и/или для использования как описано выше постом.
Сам типа драйвер I2C: RTL00_WEB/i2c_drv.c at master · pvvx/RTL00_WEB · GitHub
С обработкой ошибок все плохо. На тесте "отверткой по плате" виснет.
Добавил
Код:
if (_i2c_read(&i2cmaster, MBED_I2C_SLAVE_ADDR0, (char*)&i2cdata_read[0], 3, 1) !=DRV_I2C_OK) {
    rtl_printf("[%s] i2c_read error!\n", __FUNCTION__);
    _i2c_break(&i2cmaster);
    _i2c_ic_off(&i2cmaster);
    _i2c_init(&i2cmaster);
    return I2C_ERROR;
}
перестало виснуть если замыкать sda на gnd но виснет намертво после замыкания scl на 0. Почему переинициализация контроллера не лечит второй случай непонятно.
Еще в драйвере нужна функция развешивания слэйвов типа такой:
(это из AVR)
Код:
void I2C_Reset(void)
{
  for (char i = 0; i < 9; i++)
  {
    I2C_DATA_HI();
    delay(1);
    if ((PINB & SDA_MSK)==0)
    {
      I2C_CLOCK_HI();
      I2C_CLOCK_LO();
      I2C_CLOCK_HI();
    }
  }
  I2C_Stop();
}
Драйвер mbed так же виснет. В нем таймаут в функции чтения не работает.

Итого: для "боевого" кода без допиливания непригодно.
В SDK хоть что-то вообще работает "из коробки" ???

И это я еще не дошел до теста "рашпилем по ЛАТР-у".
 

pvvx

Активный участник сообщества
В SDK хоть что-то вообще работает "из коробки" ???
Современные SDK сделаны как DEMO.
Да и вообще не бывает SDK, в которых всё работает (особенно одновременно :))
Описание контроллеров есть - пишите свои драйвера. Я так и сделал, для примера и вышло меньше объемом и так как мне надо было... Тем более I2С не профессиональная шина, а для игрушек...
 
Последнее редактирование:

Simon

Member
pvvx, подскажите пожалуйста.
Мне нужно выполнить чтение слейва через repeated start. Должно выглядеть вот так(картинка из документации на датчик):
0000.JPG

- Первые два байта - команда для слейва на чтение
- Следующий байт запуск чтения
- 3 байта ответа передает слейв.

i2c_write без стопа в конце проходит нормально.
Но i2c_read перед командой чтения рестартует шину, посылая "стоп-старт". Стоп сбрасывает датчик и тот не отвечает.
Нужно отменить рестарт шины или включить repeated start.
 

Simon

Member
Нашел сам. Помогло:
i2c_restart_enable(i2c_t *obj);
что бы это не значило :)
 

АндрейМ

New member
1) Конфигурируем i2c в нужный режим (их там куча :) )
смотрю на твой драйвер под INA, не хватает понимания, как настраиваются i2c режимы.
Например:

// Master Target Address
i2c_reg(REG_DW_I2C_IC_TAR) = p->addr;
устанавливается адрес слейва, в которого дальше кидаем команды

У BMP180 таких адресов два - выбор регистра и чтение, в оригинальном ногодрыгателе идет посыл 24 бита с отловом ACK:

Код:
bool BMP180_select_reg(uint32_t addr) { // write 24 bits w/ add i2c_start
    int i = 0;
    for (i = 0; i < 24; i++) {
        i2c_step_scl_sda(addr & 0x800000);
        addr <<= 1;
        if ((i & 7) == 7 && i2c_step_scl_sda(1) != I2C_ACKS) { //check ack
            i2c_stop();
            return false;
        }
        if (i == 15) {
            i2c_start();
        }
    }
    return true;
}
Посоветуй, куда копать?
 

pvvx

Активный участник сообщества
Посоветуй, куда копать?
PS: 4-ре I2С контроллера в RTL871x по регистрам аналогичны Intel D2000 и расписаны в доках от Intel D2000. Код от SDK к Intel D2000 для I2C, после модификации и вставки пару нюансов тоже работает.
https://esp8266.ru/forum/attachments/rtl871x_i2c-docx.4665/

Для MLX90614 и прочих SMB это выглядит так:

В console при использовании i2c_drv.c:
project.mk:
ADD_SRC_C += project/src/driver/i2c_drv.c
CFLAGS += -DUSE_I2C_CONSOLE=1
Код:
>ati2c ?
I2C Init:
        ati2c i [sda_pin [scl_pin [mode [speed]]]]
I2C Deinit:
        ati2c d
I2C Write:
        ati2c W address data1 [data2 ... [data8]...]
I2C write + stop:
        ati2c w address data1 [data2 ... [data8]...]
I2C Read:
        ati2c R address count
I2C read + stop:
        ati2c r address count
I2C get:
        ati2c g address wrcount wrdata1 [..wrdata6] rdcount
>ati2c i // инициализируем I2С  с параметрами по умолчанию
I2C1 Init: 24 25 02 0000c350
I2C1 Status = 1
>ati2c g 5a 1 6 3 // По адресу устройства 0x5a  запрашиваем smb чтение регистра 6 и читаем 3 байта
I2C1 get[3]: 5a 5d 3a 53
I2C1 Status = 1
>ati2c g 5a 1 7 3 // По адресу устройства 0x5a  запрашиваем smb чтение регистра 7 и читаем 3 байта
I2C1 get[3]: 5a 4a 3a 79
I2C1 Status = 1
>ati2c d
I2C1 DeInit
>
Из PDF для MLX90614
Снимок1610.gif
На ногах RTL:
Снимок1611.gif
 
Последнее редактирование:

pvvx

Активный участник сообщества
В web-свалку добавлен пример работы по I2C с MLX90614 (Infra Red Thermometer)
Снимок1615.jpg

Регистрация остывания ложки (websocket, 100 точек Ta и To в сек), вынутой из кружки чая:
Снимок1614.gif
Ta - Ambient temperature
To - Object temperature


Криво, ступенями, т.к. высыхает чай на ложке :)
Температура самого датчика (Ta) падает - до замера нагрел рукой...

Опрашивать быстрее 10 ms MLX90614 нет смысла...
Снимок1616.gif
При опросе To и Ta в 1 ms он вообще гонит халтуру :)
 
Последнее редактирование:
  • Like
Реакции: A_D

pvvx

Активный участник сообщества
С обработкой ошибок все плохо. На тесте "отверткой по плате" виснет.
...
И это я еще не дошел до теста "рашпилем по ЛАТР-у".
Не виснет с webcsocket c данными MLX90614 в последней версии - как угодно замыкайте SCL, SDA, GND и всё равно заработает снова...
 

pvvx

Активный участник сообщества
>ati2c g 76 1 d0 1 // адрес 0x76, передать 1 байт 0xd0 (задать номер регистра), прочитать 1 байт.
I2C1 get[1]: 76 58 // 0x58 - ID BMP280
I2C1 Status = 1 // =1 ok - шина I2c свободна, =2 stop ещё не передан, ...
-------
ati2c g 68 1 75 10 // адрес 0x68, передать 1 байт 0x75 (задать номер регистра), прочитать 16 байт.
I2C1 get[16]: 68 71 00 1a b4 00 ed 44 00 24 18 00 b8 c0 dd f7 a8 // 0x71 - ID MPU9250
I2C1 Status = 1
 
Последнее редактирование:

АндрейМ

New member
ADD_SRC_C += project/src/driver/i2c_drv.c
Потребовалось на одной шине два устройства. Принцип - отдельная инициализация i2c, драйверам периферии передается готовая структура.
Но из-за невозможности менять IC_TAR при IC_ENABLE==1 неправильно срабатывают _i2c_write() для второго и далее устройств.
Предлагаю текущий адрес слейва держать в структуре i2c_drv_t и в процедурах _i2c_read и write добавить проверку и при необходимости
i2c_reg(REG_DW_I2C_IC_ENABLE) = 0;
перед
i2c_reg(REG_DW_I2C_IC_TAR) = p->addr;

Короче фьюче реквест :). В остальном драйвер отличный.
 

pvvx

Активный участник сообщества
Потребовалось на одной шине два устройства. Принцип - отдельная инициализация i2c, драйверам периферии передается готовая структура.
Но из-за невозможности менять IC_TAR при IC_ENABLE==1 неправильно срабатывают _i2c_write() для второго и далее устройств.
Предлагаю текущий адрес слейва держать в структуре i2c_drv_t и в процедурах _i2c_read и write добавить проверку и при необходимости
i2c_reg(REG_DW_I2C_IC_ENABLE) = 0;
перед
i2c_reg(REG_DW_I2C_IC_TAR) = p->addr;

Короче фьюче реквест :). В остальном драйвер отличный.
Это не "драйвер", а упрошенный пример использования оборудования без громоздкого оф. HAL и рассчитывался на одно устройство. Там ещё много "но" ради упрощения.
А цель достигнута - вы как-то смогли разобраться и можете теперь сами модифицировать и строить варианты с I2C под свои нужды...
 

АндрейМ

New member
А цель достигнута - вы как-то смогли разобраться и можете теперь сами модифицировать и строить варианты с I2C под свои нужды...
Не нужно скромничать - для примера Ваш драйвер слишком работоспособный и отлично вписывается в иерархию вместо куска HAL. Моя идеология позволяет переписывать уёжища, но тут форкать рабочую систему для пары бантиков считаю неправильным. Но Вам, конечно, виднее.

ЗЫ: предлагаю перейти на "ТЫ" )))
 

pvvx

Активный участник сообщества
Ну с BMPx80 уже не I2C, а SMBus.
Контроллер тоже работает по сигналам в "стандарте" SMBus -> http://smbus.org/specs/SMBus_3_0_20141220.pdf
Еще в драйвере нужна функция развешивания слэйвов типа такой:
(это из AVR)
Код:
void I2C_Reset(void)
{
  for (char i = 0; i < 9; i++)
  {
    I2C_DATA_HI();
    delay(1);
    if ((PINB & SDA_MSK)==0)
    {
      I2C_CLOCK_HI();
      I2C_CLOCK_LO();
      I2C_CLOCK_HI();
    }
  }
  I2C_Stop();
}
Последний раз не удалось заставить его гнать 11 бит "1" подряд. Он всё стремится передать команду перехода на другую скорость и прочие SMBus-ные коды переключения и, обнаружив что устройств на шине нет, не хочет на шину выводить произвольные байты...
Может есть какое решение, а то ногодрыг GPIO не катит - слишком много переключений - надо включить "Открытый коллектор" на GPIO SDA и CLKи т.д...
Тем более такое "развешивание" не подходит для SMBus устройств.
Совместимость SMBus с устройством I2C » Все о РадиоЭлектроТехнике
 
Последнее редактирование:

АндрейМ

New member
Немного сумбурно получается - разобрался, почему не проходили изменения TAR. Контроллер не позволяет менять его, если очередь не пуста:
Код:
    if (pbmp->addr != i2c_reg(REG_DW_I2C_IC_TAR)) {
        int cnt = 0;
        while (!(i2c_reg(REG_DW_I2C_IC_STATUS) & BIT_IC_STATUS_TFE)) {
            rtw_udelay_os(100);
            if (cnt++ > 100) {
                return 0;
            }
        }
    }
после выхода из цикла чтение/запись нормально проходят на новый адрес.
 
Последнее редактирование:

Ксения

New member
Всем здравствуйте!
Подскажите, а каким образом конфигурируются пины GPIO (PC_4 и PC_5)? Из кода этого непонятно...
Имеем плату RAK473. Пытаемся прочитать несколько байт от устройства по I2C: функции _i2c_setup, _i2c_init, _i2c_set_speed проходят без ошибок, но _i2c_read возвращает либо I2C abort либо I2C Timeout. Посмотрели осциллографом, сигналов на выводах нет вообще... Единственная версия - что они неправильно сконфигурированы.

Заранее спасибо за ответ!
 
Сверху Снизу