Введение

Для одной небольшой задачи понадобилось подружить лазерный датчик расстояния VL53L1X с микроконтроллером CH32V003. Датчик работает по I2C, и изначально я рассчитывал, что в его даташите будут описаны регистры, чтобы быстро написать драйвер «с нуля». Но у STMicroelectronics подход другой: они не публикуют описание регистров, а распространяют готовый драйвер с API только для STM32 (см. UM2356 "VL53L1X API user manual").

Для моего проекта нужен был простой и дешёвый МК с минимальными ресурсами, и выбор пал на CH32V003F4P6. У него нет HAL и CubeMX, только CMSIS, «урезанный StdPeriph», reference manual и среда MounRiver. Впрочем, для простых проектов этого достаточно.

Выбор драйвера

Чтобы не тащить тяжёлый оригинальный драйвер, я взял облегчённый вариант — VL53L1X ultra lite driver (ULD), также выпущенный ST (описан в UM2510 "A guide to using the VL53L1X ultra lite driver"). На GitHub легко нашёл пример для STM32, скопировал к себе папку с исходниками VL53L1X_ULD_API и подключил в проект:

Дополнительно добавил свои файлы vl53l1x.c и vl53l1x.h, где сделал адаптацию под CH32.

Инициализация I2C

Весь порт свёлся к тому, чтобы переписать низкоуровневые функции передачи данных по I2C. Для начала я добавил инициализацию I2C в vl53l1x.c:

static void vl53l1x_LL_Init(void)
{
    GPIO_InitTypeDef MyGPIO = {0};
    I2C_InitTypeDef MyI2C = {0};

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1  , ENABLE);

    MyGPIO.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2; // sda&scl
    MyGPIO.GPIO_Mode = GPIO_Mode_AF_OD;
    MyGPIO.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_Init(GPIOC, &MyGPIO);

    MyGPIO.GPIO_Pin = GPIO_Pin_3 ; // xshut pin
    MyGPIO.GPIO_Mode = GPIO_Mode_Out_PP;
    MyGPIO.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_Init(GPIOC, &MyGPIO);

    MyI2C.I2C_ClockSpeed = 100000;
    MyI2C.I2C_DutyCycle = I2C_DutyCycle_16_9;
    MyI2C.I2C_OwnAddress1 = 0;
    MyI2C.I2C_Mode = I2C_Mode_I2C;
    MyI2C.I2C_Ack = I2C_Ack_Enable;
    MyI2C.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    I2C_Init(I2C1, &MyI2C);

    I2C_Cmd(I2C1, ENABLE);
    I2C_AcknowledgeConfig(I2C1, ENABLE);

}

Замена HAL-функций

В файле vl53l1_platform.c драйвер использует только две функции:

  • int I2CWrite(uint16_t Dev, uint8_t *pdata, uint32_t count)

  • int I2CRead(uint16_t Dev, uint8_t *pdata, uint32_t count)

В оригинале они опираются на HAL_I2C_Master_Receive и HAL_I2C_Master_Transmit. Я оставил названия теми же, но сделал собственную реализацию в файлах HAL_I2C.c и HAL_I2C.h.

Так драйвер остался полностью совместимым по интерфейсам. Плюс реализовал рабочие таймауты, чтобы избежать зависания при ошибках на шине I2C.

Системный таймер

Для работы таймаутов и функции HAL_Delay нужен системный таймер. В STM32 это делает CubeMX, но здесь пришлось поднять руками.

В system_ch32v00x.c я включил SysTick, настроил прерывание каждые 1 мс и добавил счётчик:

void SystemInit (void)
{
  RCC->CTLR |= (uint32_t)0x00000001;
  RCC->CFGR0 &= (uint32_t)0xFCFF0000;
  RCC->CTLR &= (uint32_t)0xFEF6FFFF;
  RCC->CTLR &= (uint32_t)0xFFFBFFFF;
  RCC->CFGR0 &= (uint32_t)0xFFFEFFFF;
  RCC->INTR = 0x009F0000;

  RCC_AdjustHSICalibrationValue(0x10);

  SetSysClock();

  NVIC_EnableIRQ(SysTicK_IRQn);
  SysTick->SR &= ~(1 << 0);
  SysTick->CMP = SystemCoreClock/1000;
  SysTick->CNT = 0;
  SysTick->CTLR = 0xF;
}
void SysTick_Handler(void)
{
    SysCounter++;
    SysTick->SR = 0;
}

uint32_t GetSysTick(void)
{
    return SysCounter;
}
void HAL_Delay(uint16_t ms)
{
    uint32_t StartTick = GetSysTick();

    while((GetSysTick()-StartTick)<ms);
}

Пример работы

В итоговом примере для среды MounRiver датчик каждые 1 секунду отправляет актуальное расстояние через UART (пин PD5). Всё работает стабильно. API драйвера остаётся без изменений, поэтому его легко подключать к проекту.

Исходники проекта я выложил на GitHub: KorolDenis/Driver-VL53L1X-for-CH32V003

Комментарии (0)