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

Power Profiler

cheblin

Member
не могет и упирается где-то на
отвечаю...использованые в проекте драйвера полный ацтой.

и таки да, сюрприз, сюрприз, в нормальных драйверах у I2C есть колбэки по прирыванию, более того есть Polling, DMA and Interrupts весь нитернет примерами забит...

я небуду тотально переделывать исходный PowerProfiler
просто напишу AdHoc protocol поверх того что есть, для того чтобы появилось понимание как части взаимосвязаны и работают.

В таком простом примере как PowerProfiler , где всё как на ладони в одной функции,... можно отказаться от динамического выделения памяти.
 

pvvx

Активный участник сообщества
отвечаю...использованые в проекте драйвера полный ацтой.
Драйвера USB-COM на компе и т.д.. При другой usb-булке можно заметно поднять скорость.
Теоретический макс. у USB1.1-COM при передаче блоков по 64 байта составляет 64 байта в 1 ms.
 

pvvx

Активный участник сообщества
и таки да, сюрприз, сюрприз, в нормальных драйверах у I2C есть колбэки по прирыванию, более того есть Polling, DMA and Interrupts весь нитернет примерами забит...
Это хорошо.
У STM32F103 по докам:
Supports different communication speeds:
– Standard Speed (up to 100 kHz),
– Fast Speed (up to 400 kHz)
На чтение регистра INA2xx требуется от 51 клока I2C.
Start + addrI2c(wr) + addr_reg (stop) + Start + addrI2c(rd) + rd_msb + rd_lsb + stop.
В прерываниях это от 8 прерываний на чтение.

Время оцифровки (через которое требуется считывание) INA219 - 84 us
Время оцифровки INA226 - 140 us

Даже если 84 us поделить на 8 прерываний, то получим следование прерываний в 8 us. (125 кГц)

Рассчитывайте свой драйвер. Ждем успеха.
 

pvvx

Активный участник сообщества
STM32F103x8/STM32F103xB
ARM 32-bit Cortex™-M3 CPU Core – 72 MHz maximum frequency
При 400 кГц I2c (максимально возможной для данных MCU по докам) на считывание регистра по I2C необходимо 51/400000=0.000128 сек или 128 us.
Время оцифровки (через которое требуется считывание) INA219 - 84 us
Время оцифровки INA226 - 140 us
Если i2c по прерываниям, то:
72000000*0.0000084 = 604.8 такта в среднем между прерываниями
Вычитая время обработки CPU самого прерывания с сохранением-восстановлением регистров и зарядку калабака в RTOS с переключением задач получим:
1) кол-во тактов на зарядку новой команды контроллеру i2c
2) время на обработку основного треда
3) время на обработку треда USB и её прерываний
Всё это желательно уложить в менее чем 600 тактов CPU, т.к. прерывания i2C идут не с одинаковым интервалом… Задача достаточно сложная.

Но у нас тут есть супер специалист-программист @cheblin. Он нам это решит - даст правильный пример.
 

pvvx

Активный участник сообщества
более того есть Polling, DMA and Interrupts весь нитернет примерами забит...
Что мы можем почерпнуть из указанных доков?

“Запросы DMA генерируются только для передачи данных. … Все остальные события (SB, ADDR и т. д.) Должны управляться опросом или прерываниями.”

У нас для I2C и DMA есть возможность сократить цикл, т.к. есть 2 последовательных чтения регистра данных I2C. Придется ещё повесить прерывание на распределение ошибок от I2C (счас это всё включено в единую команду чтения регистра с устройства).

Т.е. вместо 8 прерываний, с помощью зарядки DMA мы можем получить 7. Учитывая, что CPU затратит время на зарядку DMA более времени считывания по одному прерыванию CPU регистра i2C данный метод уже не дает разгрузки CPU.

Но может проф. программист cheblin укажет как это поправить или какие ошибки или недочеты я допустил в данном описании…
 

pvvx

Активный участник сообщества
В Arduino запрос к I2C так-же раскладывается на составляющие:
Start Addr I2c
Write reg
Stop
Start Addr I2c
Read reg 8 bit
Read reg 8 bit
Stop
 

pvvx

Активный участник сообщества
У TLSR826x I2С не сильно лучше. Но кол-во прерываний значительно сокращается, т.к. он тянет такую комбинацию (итерацию) на раз:
Start + addri2c+addr_reg+wr_data1+wr_data2+stop с анализом ACK/NACK (можно ещё чтение 8 бит добавить, но оно не вписывается в такой цикл записи :) )
Т.е. полную транзакцию I2C с 4-мя байтами.
reg_i2c_ctrl = FLD_I2C_CMD_START | FLD_I2C_CMD_ID | FLD_I2C_CMD_ADR | FLD_I2C_CMD_DO | FLD_I2C_CMD_DI | FLD_I2C_CMD_STOP;
Для этого у него 5 регистров:
ID Адрес устройства
ADR Адрес регистра
DO Данные первый байт
DI Данные второй байт
А общей запрос на передачу 2-х байт (запись в регистр INA2xxx) может быть оптимизирован в запись одного dword в регистры i2c контроллера.
Т.е. однотактная команда :)
Мой код дров I2C для TLSR826x:
Код:
int I2CBusWriteWord(unsigned char i2c_addr, unsigned char reg_addr, unsigned short reg_data)
{
    int ret = 0;
//    u8 r = irq_disable();
    while(1) {
        //Start By    Master, write Slave Address & Register Pointer
        reg_i2c_id = i2c_addr;
           reg_i2c_adr = reg_addr; //
        //write data MSByte
        reg_i2c_do = (unsigned char)(reg_data >> 8);
        //write data LSByte
        reg_i2c_di = (unsigned char)(reg_data);
           reg_i2c_ctrl = FLD_I2C_CMD_START | FLD_I2C_CMD_ID | FLD_I2C_CMD_ADR | FLD_I2C_CMD_DO | FLD_I2C_CMD_DI | FLD_I2C_CMD_STOP; // 0x3f
        while(reg_i2c_status & FLD_I2C_CMD_BUSY    );
        if (reg_i2c_status & FLD_I2C_NAK) break;
        ret = 1;
        break;
    }
//    irq_restore(r);
    return ret;
}

_attribute_ram_code_ int I2CBusReadWord(unsigned char i2c_addr, unsigned char reg_addr, void *preg_data)
{
    int ret = 0;
    unsigned char * p = (unsigned char *) preg_data;
//    u8 r = irq_disable();
    while(1) {
        //Start By    Master Write, write Slave Address & Register Pointer
        reg_i2c_id = i2c_addr;
           reg_i2c_adr = reg_addr; //address
           reg_i2c_ctrl = FLD_I2C_CMD_START | FLD_I2C_CMD_ID | FLD_I2C_CMD_ADR | FLD_I2C_CMD_STOP; // 0x33
        while(reg_i2c_status & FLD_I2C_CMD_BUSY    );
        if (reg_i2c_status & FLD_I2C_NAK) break;

        // Start By Master Read, Write Slave Address, Read data MSByte
        reg_i2c_id |= FLD_I2C_WRITE_READ_BIT;  //SlaveID & 0xfe,.i.e write data. Read:High  Write:Low
           reg_i2c_ctrl = FLD_I2C_CMD_START | FLD_I2C_CMD_ID | FLD_I2C_CMD_READ_ID | FLD_I2C_CMD_DI; // 0x59
        while(reg_i2c_status & FLD_I2C_CMD_BUSY    );
        p[1] = reg_i2c_di;

        // Read data LSByte, Stop By Master
        reg_i2c_ctrl = FLD_I2C_CMD_READ_ID | FLD_I2C_CMD_DI | FLD_I2C_CMD_NAK | FLD_I2C_CMD_STOP; // 0xE8
        while(reg_i2c_status & FLD_I2C_CMD_BUSY    );
        p[0] = reg_i2c_di;
        ret = 1;
        break;
    }
//    irq_restore(r);
    return ret;
}

void I2CBusDeInit(void) {
#if (MCU_CORE_TYPE == MCU_CORE_8266)
    BM_CLR(reg_gpio_ie(GPIO_PF1), GPIO_PF1 & 0xff);    // disable input
    BM_SET(reg_gpio_config_func(GPIO_PE7), GPIO_PE7 & 0xff);
    BM_CLR(reg_gpio_ie(GPIO_PE7), GPIO_PE7 & 0xff);    // disable input
    BM_SET(reg_gpio_oen(GPIO_PE7), GPIO_PE7 & 0xff); // disable output
    analog_write(0x14, (analog_read(0x14) & (~((3 << 2) | (3 <<6))))
                |  (GPIO_PULL_UP_1M << 2) // PE7
                |  (GPIO_PULL_UP_1M << 6) // PF1
                );
#endif
    BM_CLR(reg_rst_clk0, FLD_CLK_I2C_EN);
}

void I2CBusInit(unsigned int clk_hz) {
//    int ret = 0;
#if (MCU_CORE_TYPE == MCU_CORE_8266)
    u32 gpio_sda = GPIO_PE7;
    u32 gpio_scl = GPIO_PF1;

    gpio_set_func(gpio_sda, AS_I2C);  //disable gpio function
    gpio_set_func(gpio_scl, AS_I2C);  //disable gpio function

    gpio_setup_up_down_resistor(gpio_sda, PM_PIN_PULLUP_10K);
    gpio_setup_up_down_resistor(gpio_scl, PM_PIN_PULLUP_10K);

    gpio_set_input_en(gpio_sda, 1);
    gpio_set_input_en(gpio_scl, 1);
#else
    u32 gpio_sda = GPIO_PA3;
    u32 gpio_scl = GPIO_PA4;
    gpio_setup_up_down_resistor(gpio_sda, PM_PIN_PULLUP_10K);       // 10k pull_up resistor
    gpio_setup_up_down_resistor(gpio_scl, PM_PIN_PULLUP_10K);       // 10k pull_up resistor

    gpio_set_func(gpio_sda, AS_I2C);  // disable gpio function
    gpio_set_func(gpio_scl, AS_I2C);  // disable gpio function
#endif
    // FI2C = (System Clock/(address 0x73[7:4]+1)) / (4 *clock speed configured in address 0x00)
    // System Clock / (8 * address 0x00)
    // default reg_i2c_speed = 0x13
    reg_i2c_speed = (CLOCK_SYS_CLOCK_HZ/4)/clk_hz;

    BM_SET(reg_i2c_mode, FLD_I2C_MODE_MASTER);  // enable master mode.
    BM_SET(reg_rst_clk0, FLD_CLK_I2C_EN);       // enable i2c clock
    BM_CLR(reg_spi_sp, FLD_SPI_ENABLE);        // force PADs act as I2C; i2c and spi share the hardware of IC

    //Start/Stop By Master, SDA & SCL = "1"
    reg_i2c_ctrl = FLD_I2C_CMD_START | FLD_I2C_CMD_STOP; // launch stop cycle
    while(reg_i2c_status & FLD_I2C_CMD_BUSY);
}
 

pvvx

Активный участник сообщества
В итого за счет более правильной организации встроенных контроллеров на TLSR имеем:
трансфер в USB-COM с шины I2C более 400 килобайт в сек при вдвое меньшей частоте CPU чем у STM.
У STM - до 20 килобайт.
О размере кода вопрос вообще...
 

pvvx

Активный участник сообщества
И не забываем главного - TC32 в TLSR выполняет код из обычной встроенной SPI-Flash c масенькой кэщ :) А монстр STM из параллельной Flash с доступом < 70 нс.
Т.е. различие производительности CPU этих чипов различаются в тысячи раз.
 

pvvx

Активный участник сообщества
я ж говорб там рядом лежит код который собственно документ обсуждает
У вас что регистрации там нет? Почты тоже нет?
Вот кусок с DMA, от туда :) :)
Код:
Status I2C_Master_BufferRead(I2C_TypeDef* I2Cx, uint8_t* pBuffer,  uint32_t NumByteToRead, I2C_ProgrammingModel Mode, uint8_t SlaveAddress)

{
    __IO uint32_t temp = 0;
    __IO uint32_t Timeout = 0;

    /* Enable I2C errors interrupts (used in all modes: Polling, DMA and Interrupts */
    I2Cx->CR2 |= I2C_IT_ERR;

    if (Mode == DMA) /* I2Cx Master Reception using DMA */
    {
        /* Configure I2Cx DMA channel */
        I2C_DMAConfig(I2Cx, pBuffer, NumByteToRead, I2C_DIRECTION_RX);
        /* Set Last bit to have a NACK on the last received byte */
        I2Cx->CR2 |= CR2_LAST_Set;
        /* Enable I2C DMA requests */
        I2Cx->CR2 |= CR2_DMAEN_Set;
        Timeout = 0xFFFF;
        /* Send START condition */
        I2Cx->CR1 |= CR1_START_Set;
        /* Wait until SB flag is set: EV5  */
        while ((I2Cx->SR1&0x0001) != 0x0001)
        {
            if (Timeout-- == 0)
                return Error;
        }
        Timeout = 0xFFFF;
        /* Send slave address */
        /* Set the address bit0 for read */
        SlaveAddress |= OAR1_ADD0_Set;
        Address = SlaveAddress;
        /* Send the slave address */
        I2Cx->DR = Address;
        /* Wait until ADDR is set: EV6 */
        while ((I2Cx->SR1&0x0002) != 0x0002)
        {
            if (Timeout-- == 0)
                return Error;
        }
        /* Clear ADDR flag by reading SR2 register */
        temp = I2Cx->SR2;
        if (I2Cx == I2C1)
        {
            /* Wait until DMA end of transfer */
            while (!DMA_GetFlagStatus(DMA1_FLAG_TC7));
            /* Disable DMA Channel */
            DMA_Cmd(I2C1_DMA_CHANNEL_RX, DISABLE);
            /* Clear the DMA Transfer Complete flag */
            DMA_ClearFlag(DMA1_FLAG_TC7);

        }

        else /* I2Cx = I2C2*/
        {
            /* Wait until DMA end of transfer */
            while (!DMA_GetFlagStatus(DMA1_FLAG_TC5));
            /* Disable DMA Channel */
            DMA_Cmd(I2C2_DMA_CHANNEL_RX, DISABLE);
            /* Clear the DMA Transfer Complete flag */
            DMA_ClearFlag(DMA1_FLAG_TC5);
        }
        /* Program the STOP */
        I2Cx->CR1 |= CR1_STOP_Set;
        /* Make sure that the STOP bit is cleared by Hardware before CR1 write access */
        while ((I2Cx->CR1&0x200) == 0x200);
    }
Сплошной [inline]while ((I2Cx->SR1&0x0001) != 0x0001)[/inline] [inline] if (Timeout-- == 0) return Error;[/inline]
Сколько тактов уходит на [inline]I2C_DMAConfig(I2Cx, pBuffer, NumByteToRead, I2C_DIRECTION_RX);[/inline]?
Не знаете? :)
А смысл его, если как уже выше сообщением вам написал, что оно считывает всего 2 байта из 5 в нужной транзакции. А байты идут со скоростью 9 us (при 1MHz CLK I2С, желательно >2MHz).
Остальные 3 - будут с [inline]while ((I2Cx->SR1&0x0001) != 0x0001)[/inline] [inline] if (Timeout-- == 0) return Error;[/inline] и 4-й с ожиданием DMA :) Было 5 - стало 4 + куча кода с временем выполнения более чем требуется писать/читать байтики по I2C у INA2xxx.

И этот бессмысленный DMA на сколько ускорит, в % ?
Это вам домашнее задание :p
 

pvvx

Активный участник сообщества
Ущё надо прибавить 2 стартика
Код:
Timeout = 0xFFFF;
        /* Send START condition */
        I2Cx->CR1 |= CR1_START_Set;
        /* Wait until SB flag is set: EV5  */
        while ((I2Cx->SR1&0x0001) != 0x0001)
        {
            if (Timeout-- == 0)
                return Error;
        }
и стопик.
Вот вам и выйдет 7 while (что выше) + 2 байтика по DMA с его зарядкой, остановом и прерыванием по ошибке от I2C что тама не тот ACK :) Ещё таймер на ожидание что байтики не пошли...
Килобайта от 1.5 кода вместо пару сотни байт :)
 

pvvx

Активный участник сообщества
Короче лепите и проверим, что нам выдаст проф.программер...

А я пока совмещенку на JDY-10 слепил - BLE или USB.
upload_2020-1-20_15-45-10.png upload_2020-1-20_15-45-41.png
Следующий вариант влепить тот-же код в nRF52840 в Arduino... :)
 

pvvx

Активный участник сообщества
@cheblin У Nordic жадность поболее китайцев, нормальных ip моделей они не купят для встроенных контролеров (платить же надо за лицензию), а поставят какой обрезок... Там I2C аналогичная STM - c while(готово?) на каждый бит ввода вывода на i2c. И чтобы это хотя-бы как-то работало поставили Cortex M4F и прибавили RAM. Память и дурь CPU на это вся и уходит в их чипе...
Пример чтения в USB-COM на nRF52840 я вам уже давал.
А вот один их кусков самой либы I2C для nRF52840 , с теми-же While на каждый бит/байт в шину i2с :
Код:
  // Start I2C transmission
  _p_twim->ADDRESS = txAddress;

  _p_twim->TASKS_RESUME = 0x1UL;

  _p_twim->TXD.PTR = (uint32_t)txBuffer._aucBuffer;
  _p_twim->TXD.MAXCNT = txBuffer.available();

  _p_twim->TASKS_STARTTX = 0x1UL; // шлепнули старт

  while(!_p_twim->EVENTS_TXSTARTED && !_p_twim->EVENTS_ERROR); // ждем когда выполнится 
  _p_twim->EVENTS_TXSTARTED = 0x0UL;

  if (txBuffer.available()) { // шлепнули байтики
    while(!_p_twim->EVENTS_LASTTX && !_p_twim->EVENTS_ERROR); // ждем когда вылезут
  }
  _p_twim->EVENTS_LASTTX = 0x0UL;

  if (stopBit || _p_twim->EVENTS_ERROR) // шлепнули стоп
  {
    _p_twim->TASKS_STOP = 0x1UL;
    while(!_p_twim->EVENTS_STOPPED); // ждем когда выполнится 
    _p_twim->EVENTS_STOPPED = 0x0UL;
  }
  else
  {
    _p_twim->TASKS_SUSPEND = 0x1UL;
    while(!_p_twim->EVENTS_SUSPENDED);
    _p_twim->EVENTS_SUSPENDED = 0x0UL;
  }

  if (_p_twim->EVENTS_ERROR)
  {
    _p_twim->EVENTS_ERROR = 0x0UL;

    uint32_t error = _p_twim->ERRORSRC;

    _p_twim->ERRORSRC = error;

    if (error == TWIM_ERRORSRC_ANACK_Msk)
    {
      return 2;
    }
    else if (error == TWIM_ERRORSRC_DNACK_Msk)
    {
      return 3;
    }
    else
    {
      return 4;
    }
  }
 

cheblin

Member
While на каждый бит/байт в шину i2с :
ох как раздухарился...
самое врема апноуты начать сочинять...

// ждем когда выполнится
чем теорией по древу растекаться , да тактики подчитывать не проще ли взять и на практике это всё подтвердить...
лично я ИМ больше доверяю, ибо производители чипов не такие дураки %username%
 
Сверху Снизу