2024DigiKey创意大赛】【自行车智能灯】汇总提交
[复制链接]
本帖最后由 alanlan86 于 2024-10-30 10:00 编辑
作品名称
自行车智能灯
一、作品简介
(2)简介
自行车智能车灯安装在自行车把手位置,能够根据光传感器自动点亮LED灯,用于做车头照明;能够根据IMU监测车把的左转右转动作,实现方向闪灯提醒;能够根据毫米波雷达探测前方是否有障碍物,发出警告提醒。
ESP32-S3-LCD-EV-Board做为主控板,外接光敏电阻传感器,通过ADC采集光的强度。通过I2C采集ICM-20948的姿态数据,进行车把方向监测。通过读取毫米波传感器的终端输出,监测前方是否有障碍物。
(3)物料清单
物料
|
型号
|
功能
|
1
|
ESP32-S3-LCD-EV-Board
|
主控、LCD显示交互、LED三色灯
|
2
|
ADAFRUIT TDK INVENSENSE ICM-20948
|
9轴传感器
|
3
|
MerryTek MC01-5G
|
毫米波传感器(集成光敏电阻)
|
A、ESP32-S3-LCD-EV-Board
ESP32-S3-LCD-EV-Board 是一款基于 ESP32-S3 芯片的屏幕交互开发板,通过搭配不同类型的 LCD 子板,可以驱动 IIC、SPI、8080 以及 RGB 接口的 LCD 显示屏。同时它还搭载双麦克风阵列,支持语音识别和近/远场语音唤醒,具有触摸屏交互和语音交互功能,满足用户对多种不同分辨率以及接口的触摸屏应用产品的开发需求。
目前从Digikey上采购到的开发板:搭配 480x480 LCD 的 ESP32-S3-LCD-EV-Board,有丰富的外设和强大的显示能力。
该款开发板核心模组型号ESP32-S3-WROOM-1-N16R16V, 该模组是一款通用型 Wi-Fi + 低功耗蓝牙 MCU 模组,搭载 ESP32-S3 系列芯片,内置 16 MB flash 以及 16 MB PSRAM。除具有丰富的外设接口外,模组还拥有强大的神经网络运算能力和信号处理能力,适用于 AIoT 领域的多种应用场景。
B、ICM-20948模组
ICM-20948是TDK(InvenSense)推出的9轴系列的9轴运动跟踪设备,专为电池供电的高性能消费电子产品设计。
9轴产品系列采用了经过市场验证的MotionFusion以及运行时校准,该校准由InvenSense市场的MPU-65xx系列产品支持。此解决方案已售出数百万台,并经过了市场验证。
9轴运动跟踪已成为包括智能手机、平板电脑和可穿戴设备在内的许多消费电子设备的关键功能。与离散解决方案相比,集成9轴设备的尺寸优势对于空间有限的产品(如智能手机和可穿戴传感器)来说是引人注目的。
InvenSense的9轴设备将3轴陀螺仪、3轴加速计和3轴指南针与车载数字运动处理器结合在一起(DMP) 能够处理复杂的运动融合算法。
C、MerryTek MC01-5G
MC01-5G是一款5.8G微波感应模块,它支持5~12VDC宽输入电压,工作电流小,待机功耗低。 产品体积小,易于安装。 采用低阻抗专利天线技术,抗干扰能力强。 内置抗微风、细雨多重数字滤波器算法、稳定输出高低电平、PWM、UART等控制信号。 预留灵敏度,延时时间,光控阀值等参数设置脚位,功能设置灵活。
产品具有感应距离远、可内置、无死区、不受温湿度、噪音、灰尘、气流、环境光影响等优点。广泛 应用于感应灯具、自动门控制、智能家居、智能卫浴、小家电、安防、IOT、智能终端等产品,及走廊、 车库、洗手间、庭院、阳台等场所。
二、系统框图
主控模组
整个系统构成相对简单,采用ESP32-S3-WROOM-1-N16R16V模块作为主控,它是一款拥有2.4 GHz WiFi (802.11 b/g/n) + Bluetooth® 5 (LE)的无线高集成度模组,内置 ESP32S3 系列芯片,Xtensa® 双核 32 位 LX7 处理器 Flash最大可选 16 MB,PSRAM最大大可选 8/16 MB,最多支持36个GPIO,丰富的外设 板上带PCB天线非常便利于集成化设计。开发板上有WS2812,三色LED灯,可用于做用户交互指示。
整个ESP32-S3-LCD-EV-Board开发板的功能框图如下所示:
板载带有的Audio部分,暂时未有使用到,后续可作为扩展使用。
传感器ICM20948
ICM20948是一款来自于TDK InvenSense推出的一款9轴运动检测传感器,它与主控板模块连接比较简单,采用I2C方式进行通信,支持400K Hz通信速率。
MC01-5G微波感应模组
微波感应模块MC01-5G支持5~12V的DC直流电源输入,可取开发板扩展电源5V输入作为工作电源,识别到人体或障碍物时,将通过I/O以电平方式进行输出。针对发射功率、感应延时、环境光探测等均可以通过配置I/O进行输入。其中感应模块支持探测4米的半径范围。
如下是模组的典型应用电路图:
三、各部分功能说明
整个软件基于ESP32-S3的idf开发环境进行编写和调试,基于SDK中的lvgl_demo这份示例代码进行的代码添加。
(1)开发环境
IDF开发环境的安装可以从官网下载SDK 和演示|乐鑫科技,在实现一键安装完成。安装完成后有命令行的工具以及IDE的工具,可以根据用户喜好进行选择应用。
(2)环境光采集部分
环境光照强度采集采用了比较简单和原始的做法,就是通过ADC采集毫米波感应模块上的光敏电阻,通过判断光敏电阻上的电压值,来调节照明LED的点亮和熄灭。
如下截图是ESP32-S3的ADC部分配置及执行代码:
void app_light_sensor_task(void *arg)
{
//-------------ADC1 Init---------------//
//ADC转换器初始化
adc_oneshot_unit_handle_t adc1_handle;
adc_oneshot_unit_init_cfg_t init_config1 = {
.unit_id = ADC_UNIT_1,
};
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, &adc1_handle));
//-------------ADC1 Config---------------//
//配置ADC转换通道
adc_oneshot_chan_cfg_t config = {
.bitwidth = ADC_BITWIDTH_DEFAULT,
.atten = EXAMPLE_ADC_ATTEN,
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, EXAMPLE_ADC1_CHAN0, &config));
//-------------ADC1 Calibration Init---------------//
//ADC转换器校准
adc_cali_handle_t adc1_cali_chan0_handle = NULL;
adc_cali_handle_t adc1_cali_chan1_handle = NULL;
bool do_calibration1_chan0 = example_adc_calibration_init(ADC_UNIT_1, EXAMPLE_ADC1_CHAN0, EXAMPLE_ADC_ATTEN, &adc1_cali_chan0_handle);
while (1) {
ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle, EXAMPLE_ADC1_CHAN0, &adc_raw[0][0]));
ESP_LOGI(TAG, "ADC%d Channel[%d] Raw Data: %d", ADC_UNIT_1 + 1, EXAMPLE_ADC1_CHAN0, adc_raw[0][0]);
if (do_calibration1_chan0) {
ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc1_cali_chan0_handle, adc_raw[0][0], &voltage[0][0]));
ESP_LOGI(TAG, "ADC%d Channel[%d] Cali Voltage: %d mV", ADC_UNIT_1 + 1, EXAMPLE_ADC1_CHAN0, voltage[0][0]);
//根据光敏电阻感应电压调节LED的亮度
if (voltage[0][0] > 1.5)
{
LED_PowerON((3.3 - voltage[0][0])/3.3 * 100);
} else {
LED_PowerOFF();
}
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
//Tear Down
ESP_ERROR_CHECK(adc_oneshot_del_unit(adc1_handle));
if (do_calibration1_chan0) {
example_adc_calibration_deinit(adc1_cali_chan0_handle);
}
}
(3)动作检测部分
关于motion sensor -ICM20948的陀螺仪角速度、加速度的数值采集将会复杂一些。通过调用ESP32-S3的I2C驱动API接口实现传感器的数值读取(此处参考了TDK InvenSense MP9250的一些代码)。
/**
* @brief Read a sequence of bytes from a MPU9250 sensor registers
*/
static esp_err_t mpu9250_register_read(uint8_t reg_addr, uint8_t *data, size_t len)
{
return i2c_master_write_read_device(I2C_MASTER_NUM, MPU9250_SENSOR_ADDR, ®_addr, 1, data, len, I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS);
}
/**
* @brief Write a byte to a MPU9250 sensor register
*/
static esp_err_t mpu9250_register_write_byte(uint8_t reg_addr, uint8_t data)
{
int ret;
uint8_t write_buf[2] = {reg_addr, data};
ret = i2c_master_write_to_device(I2C_MASTER_NUM, MPU9250_SENSOR_ADDR, write_buf, sizeof(write_buf), I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS);
return ret;
}
初始化传感器部分配置:
void motion_sensor_init(void)
{
uint8_t data[2];
ESP_ERROR_CHECK(i2c_master_init());
ESP_LOGI(TAG, "I2C initialized successfully");
/* Read the MPU9250 WHO_AM_I register, on power up the register should have the value 0x71 */
ESP_ERROR_CHECK(mpu9250_register_read(MPU9250_WHO_AM_I_REG_ADDR, data, 1));
ESP_LOGI(TAG, "WHO_AM_I = %X", data[0]);
/* Demonstrate writing by reseting the MPU9250 */
ESP_ERROR_CHECK(mpu9250_register_write_byte(MPU9250_PWR_MGMT_1_REG_ADDR, 1 << MPU9250_RESET_BIT));
/*Set DATA RATE of 1KHz by writing SMPLRT_DIV register*/
ESP_ERROR_CHECK(mpu9250_register_write_byte(MPU9250_SMPLRT_DIV_REG, 0x07));
/*Set accelerometer configuration in ACCEL_CONFIG Register*/
/*XA_ST=0,YA_ST=0,ZA_ST=0, FS_SEL=0 ->±2g*/
ESP_ERROR_CHECK(mpu9250_register_write_byte(MPU9250_ACCEL_CONFIG_REG, 0x00));
/*Set Gyroscopic configuration in GYRO_CONFIG Register*/
/*XG_ST=0,YG_ST=0, FS_SEL=0 ->± 250 °/s*/
ESP_ERROR_CHECK(mpu9250_register_write_byte(MPU9250_GYRO_CONFIG_REG, 0x00));
}
读取传感器角速度、加速度和温度值:
/*Read 14 BYTES of data starting from ACCEL_XOUT_H register*/
mpu9250_register_read( MPU9250_ACCEL_XOUT_H_REG, Rec_Data, 14);
DataStruct->Accel_X_RAW = (int16_t) (Rec_Data[0] << 8 | Rec_Data[1]);
DataStruct->Accel_Y_RAW = (int16_t) (Rec_Data[2] << 8 | Rec_Data[3]);
DataStruct->Accel_Z_RAW = (int16_t) (Rec_Data[4] << 8 | Rec_Data[5]);
temp = (int16_t) (Rec_Data[6] << 8 | Rec_Data[7]);
DataStruct->Gyro_X_RAW = (int16_t) (Rec_Data[8] << 8 | Rec_Data[9]);
DataStruct->Gyro_Y_RAW = (int16_t) (Rec_Data[10] << 8 | Rec_Data[11]);
DataStruct->Gyro_Z_RAW = (int16_t) (Rec_Data[12] << 8 | Rec_Data[13]);
DataStruct->Ax = DataStruct->Accel_X_RAW / 16384.0;
DataStruct->Ay = DataStruct->Accel_Y_RAW / 16384.0;
DataStruct->Az = DataStruct->Accel_Z_RAW / ACCEL_Z_CORRECTOR;
DataStruct->Temperature = (float) ((int16_t) temp / (float) 340.0 + (float) 36.53);
DataStruct->Gx = DataStruct->Gyro_X_RAW / 131.0;
DataStruct->Gy = DataStruct->Gyro_Y_RAW / 131.0;
DataStruct->Gz = DataStruct->Gyro_Z_RAW / 131.0;
为确保采集到的角度速度和角速度能够稳定,并且融合成姿态角,增加了卡尔曼滤波器进行处理:
/*Kalman angle solve*/
double dt = (double) (xTaskGetTickCount() - timer) / configTICK_RATE_HZ;
timer = xTaskGetTickCount();
double roll;
/*勾股算出重力加速度在X-Z平面的投影长度*/
double roll_sqrt = sqrt(DataStruct->Accel_X_RAW * DataStruct->Accel_X_RAW + DataStruct->Accel_Z_RAW * DataStruct->Accel_Z_RAW);
if(roll_sqrt != 0.0)
{
roll = atan(DataStruct->Accel_Y_RAW / roll_sqrt) * RAD_TO_DEG;
}
else
{
roll = 0.0;
}
double pitch = atan2(-DataStruct->Accel_X_RAW, DataStruct->Accel_Z_RAW) * RAD_TO_DEG;
if((pitch < -90 && DataStruct->KalmanAngleY > 90) || (pitch > 90 && DataStruct->KalmanAngleY < -90))
{
KalmanY.angle = pitch;
DataStruct->KalmanAngleY = pitch;
}
else
{
DataStruct->KalmanAngleY = Kalman_getAngle(&KalmanY, pitch, DataStruct->Gy, dt);
}
if(fabs(DataStruct->KalmanAngleY) > 90)
{
DataStruct->Gx = -DataStruct->Gx;
}
DataStruct->KalmanAngleX = Kalman_getAngle(&KalmanX, roll, DataStruct->Gy, dt);
(4)微波感应部分
微波感应模块探测到障碍物将以I/O信号进行输出,引出配置ESP32的GPIO中断进行事件上报。
void app_radar_sensor_task(void)
{
gpio_input_init();
//change gpio interrupt type for one pin
gpio_set_intr_type(GPIO_INPUT_IO_0, GPIO_INTR_ANYEDGE);
//create a queue to handle gpio event from isr
gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t));
//start gpio task
xTaskCreate(radar_gpio_int_task, "gpio_task_example", 2048, NULL, 10, NULL);
//install gpio isr service
gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
//remove isr handler for gpio number.
gpio_isr_handler_remove(GPIO_INPUT_IO_0);
//hook isr handler for specific gpio pin again
gpio_isr_handler_add(GPIO_INPUT_IO_0, gpio_isr_handler, (void*) GPIO_INPUT_IO_0);
}
检测到I/O中断后以Queue队列的方式从中断发出到任务端,使得任务部分可以作出对应处理。
static QueueHandle_t gpio_evt_queue = NULL;
static void IRAM_ATTR gpio_isr_handler(void* arg)
{
uint32_t gpio_num = (uint32_t) arg;
xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);
}
static void radar_gpio_int_task(void* arg)
{
uint32_t io_num;
for(;;) {
if(xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) {
printf("GPIO[%"PRIu32"] intr, val: %d\n", io_num, gpio_get_level(io_num));
}
}
}
(2)、LVGL图形部分
关于ESP32-S3-LCD这个开发板,乐鑫原厂官方原有SDK即有移植好支持LVGL的实例。需要执行UI的开发可以采用调用LVGL的API逐个控件进行布局和显示调试,但是难免效率太低了,所以需要借助UI开发助手Square Line Studio和PC模拟器进行帮助。
有PC模拟器的加持将会减少反复编译下载到嵌入式端芯片的时间,提高开发效率,直接可实现所见即所得。
通过Square Line Studio可以将图形UI的界面进行设计完成。
整个UI的代码文件结构:
Square Line Studio自动生成以上代码,他们的功能作用分别是:
Component
|
功能控件
|
自动生成,无需修改
|
Fonts
|
字体资源
|
自动生成,无需修改
|
Image
|
图片资源
|
自动生成,无需修改
|
Screen
|
屏幕资源
|
自动生成,无需修改
|
Ui.c/ui_event.c/ui_helpers.c
|
界面交互事件
|
用户交互与硬件逻辑交互的代码添加位置
|
代码准备好后,可以在idf的集成环境下编译通过,并烧录到开发板上。
四、作品源码
五、作品功能演示视频
【2024DigiKey创意大赛】【自行车智能灯】开箱贴ESP32-S3-LCD-EV-BOARD&ICM2094 - DigiKey得捷技术专区 - 电子工程世界-论坛
演示视频
六、项目总结
本次活动对于本人来说,最大的遗憾是由于工作的原因,并不能在预定的时间内,完全按照最初的功能设想把所有功能和代码调试完整,导致整个功能的演示完整性不高。但是,也有所收获——最大的收获是本次通过ESP32-S3带彩屏LCD的开发板,对于LVGL进行了一些探究,虽然现在离做出美轮美奂的UI界面还有一些距离(之前疑惑:如果嵌入式工程人员,如果用一款好用的切图软件,把UI做得简洁漂亮?B站上,大家探讨和分享的比较多都是一些移植、LVGL的一些控件的一些普通用法。探讨到实操把square line studio用好的比较少干货!)
最后,总结一句,还是非常感谢Digikey和EEWorld给那么好的学习机会和提供了一个大家交流的平台!
七、其他
题外话,出于工作因素考虑,可能稍后一段很长时间的Follow Me或DIY创新活动都没法参加了~~~静待闲暇之时!
|