Введение
Для одной небольшой задачи понадобилось подружить лазерный датчик расстояния 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