X
首页
最能打的中国芯评选
技术
模拟电子
单片机
半导体
电源管理
嵌入式
传感器
应用
汽车电子
工业控制
家用电子
手机便携
安防电子
医疗电子
网络通信
测试测量
物联网
大学堂
首页
直播
专题
TI 培训
论坛
汽车电子
国产芯片
电机驱动控制
电源技术
单片机
模拟电子
PCB设计
电子竞赛
DIY/开源
嵌入式系统
医疗电子
颁奖专区
【厂商专区】
【电子技术】
【创意与实践】
【行业应用】
【休息一下】
活动中心
直播
发现活动
颁奖区
电子头条
参考设计
下载中心
分类资源
文集
排行榜
电路图
Datasheet
语言
中文
EN
瑞萨MCU/MPU
[原创] 【瑞萨RA8D1 LVGL评测】基于 RA8D1+LVGL9.3+FreeRTOS 的环境监测系统
高山迷彩
2026-2-9 00:22
楼主
# 【瑞萨RA8D1 LVGL评测】基于 RA8D1+LVGL9.3+FreeRTOS 的环境监测系统(MQTT 对接 Home Assistant) 有幸申请到RA生态工作室的CPKRA8D1-Core和CPKRA8D1-EXP两块板子。 ,结合 ENS160(空气质量 / CO)和 AHT2X(温湿度)传感器,在 256×480 分辨率 MIPI LCD 上实现环境数据的本地可视化,并通过 LWIP+MQTT 协议将数据上报至 Home Assistant 智能家居平台,同时支持本地 UI 的实时数据显示与历史趋势折线图查看。 ### 开发板介绍: 核心板介绍 :https://gitee.com/ramcu/cpk_examples/blob/main/cpkcor_ra8d1b/docs/01_overview.md 扩展板介绍:https://gitee.com/ramcu/cpk_examples/blob/main/cpkexp_ekra8x1/docs/01_overview.adoc
### 硬件连接 传感器ENS160+AHT2X传感器接口连接
按键
网口连接
ENS160模组
### 开发平台使用与FSP配置 这里我使用的是Rensas 官方的e2 stdio,结合官方的F SP6.1进行项目开发。下载地址https://www.renesas.cn/zh/software-tool/e2-studio#downloads 由于之前使用过rensas的其他单片机开发过一些东西,这回就没有继续更新系统. 安装例程一步一步配置
接下来配置freertos thread,LWIP以及 LVGL 等相关配置项。这个也是参照例程还有感谢位测评人的帮助 https://blog.csdn.net/EPTmachine/article/details/157579744?spm=1001.2014.3001.5502 还有这个测评人的帮助 https://forum.eepw.com.cn/thread/398951/1
更改LVGL与LWIP thread的动态内存分配
添加二个信号量与msg buff由于页面切换与传感器向其他任务发布数据
### 程序处理流程
流程图关键说明 | 任务名称 | 优先级 | 动作 | 交互 | ---- | ---- | ----| ----| | ENS160_Thread | 5 | 读取 ENS160/AHT2X 数据,格式化后写入消息缓冲区 | 写入g_sensor_mssage_buffer_mqtt与g_sensor_mssage_buffer| | LWIP_thread | 4 | 从消息缓冲区读取数据,通过 LWIP 发布到 MQTT 服务器 | 读取 g_sensor_mssage_buffer_mqtt | LVGL_thread | 3 |从消息缓冲区读取数据,更新 LVGL 界面(数值 + 折线图)| 读取 g_sensor_mssage_buffer MsgBuffer(消息缓冲区)设计逻辑 为 UI 和 MQTT 分别定义 MsgBuffer,避免数据读取冲突(如 UI 高频读取不影响 MQTT 低频上报); 数据格式统一:缓冲区中存储标准化的传感器数据结构体(包含温度、湿度、AQI、CO、时间戳); 线程安全保障:MsgBuffer 内置互斥锁 / 信号量,确保采集任务写入、UI/MQTT 读取时无数据竞争。 采集: RA8D1 通过 I2C 总线读取 ENS160(AQI/CO)和 AHT2X(温湿度)原始数据 → 格式化为统一结构体 → 分别写入MsgBuffer-UI和MsgBuffer-MQTT; 显示: UI 任务周期性从MsgBuffer-UI读取数据 → 更新 LVGL 界面(实时数值页 / 历史折线图页) → 输出到 256×480 MIPI LCD; 传输: MQTT 任务周期性从MsgBuffer-MQTT读取数据 → 封装为 JSON 格式 → 通过 RA8D1 的 RMII 以太网接口(LWIP 协议栈)发布到 Home Assistant 的 MQTT 服务器 lvgl_thread 主要程序 首先从CPKCOR-RA8D1B core board的LVGL DEMO 例程中拷贝 如下文件。 board_init.c ,board_init.h,dis_configuration_data.c,dsi_layer.h,dsi_layer.c,lv_conf_user.h 到src文件中 ```c lv_ui guider_ui; uint8_t btn_id=0; static const lv_point_t points_array[] = { {140,450}, /* First button is assigned to x=140; y=450 */ {50,450} /* Second button is assigned to x=50; y=450 */ }; void button_read(lv_indev_t * indev, lv_indev_data_t * data) { /* Get the ID (0,1,2...) of the pressed button. * Let's say it returns -1 if no button was pressed */ FSP_PARAMETER_NOT_USED (indev); BaseType_t xResult=pdFAIL; xResult=xSemaphoreTake(g_corebutton_binary_semaphore,0); // xResult=xSemaphoreTake(g_expbutton_binary_semaphore,0); /* Is there a button press? */ if(xResult == pdTRUE) { data->btn_id = btn_id; /* Save the ID of the pressed button */ data->state = LV_INDEV_STATE_PRESSED; /* Set the pressed state */ } else { data->state = LV_INDEV_STATE_RELEASED; /* Set the released state */ } } /* Blinky Thread entry function */ void LVGL_thread_entry (void * pvParameters) { // FSP_PARAMETER_NOT_USED(pvParameters); FSP_PARAMETER_NOT_USED (pvParameters); lv_obj_t *screen_active; char buffer[50]; char temputer[20] = {0}; // 温度 char rh[20] = {0}; // 湿度 char airQ[20] = {0}; // 空气质量 char eCO2[20] = {0}; // 二氧化碳 /* TODO: add your own code here */ lv_init(); board_init(); //ui_init(); lv_indev_t * indev = lv_indev_create(); lv_indev_set_type(indev, LV_INDEV_TYPE_BUTTON); lv_indev_set_button_points(indev, points_array); lv_indev_set_read_cb(indev, button_read); setup_ui(&guider_ui); custom_init(&guider_ui); while (1) { lv_timer_handler(); vTaskDelay(5); size_t bytesReceived = xMessageBufferReceive(g_sensor_message_buffer, buffer, sizeof(buffer), portMAX_DELAY); if (bytesReceived > 0) { // 步骤1:拷贝原始字符串(strtok会修改原字符串,避免破坏原始数据) char str_copy[100] = {0}; strncpy(str_copy, buffer, sizeof(str_copy) - 1); ------------ // 步骤2:使用strtok按逗号分割字符串 char *token = strtok(str_copy, ","); int token_count = 0; // 统计分割出的子串数量 // 步骤3:依次提取四个子串并存储 while (token != NULL && token_count < 4) { switch (token_count) { case 0: strncpy(temputer, token, sizeof(temputer) - 1); // 温度 break; case 1: strncpy(rh, token, sizeof(rh) - 1); // 湿度 break; case 2: strncpy(airQ, token, sizeof(airQ) - 1); // 空气质量 break; case 3: strncpy(eCO2, token, sizeof(eCO2) - 1); // 二氧化碳 break; } token = strtok(NULL, ","); // 继续分割下一个子串 token_count++; } // 步骤4:异常检查(确保分割出4个有效子串) if (token_count != 4) { LOG_E("错误:字符串分割结果数量异常,期望4个,实际%d个\n", token_count); return -1; } screen_active=lv_scr_act(); if(screen_active ==guider_ui.screen){ lv_label_set_text(guider_ui.screen_Ltemp, temputer); lv_label_set_text(guider_ui.screen_Lrh, rh); lv_label_set_text(guider_ui.screen_label_2, airQ); lv_label_set_text(guider_ui.screen_label_3, eCO2); memset(buffer,0,50); } else if(screen_active ==guider_ui.screen_1){ lv_chart_set_next_value(guider_ui.screen_1_chart_1_temp,guider_ui.screen_1_chart_1_temp_0, atoi(temputer)); lv_chart_set_next_value(guider_ui.screen_1_chart_2, guider_ui.screen_1_chart_2_0, atoi(rh)); lv_chart_set_next_value(guider_ui.screen_1_chart_3, guider_ui.screen_1_chart_3_0, atoi(eCO2)); } // buffer[bytesReceived] = '\0'; // 确保字符串以 NULL 结尾 LOG_I("Received: \n"); // LOG_I("%s",buffer); } else { LOG_E("Failed to receive message"); } } } ``` 参考https://docs.lvgl.io/master/main-modules/indev/button.html 由于没有触摸驱动,添加一个硬件按键来模拟触摸屏效果。用来切换屏幕显示 ENS160传感器任务 ```c //g_sensor_message_buffer fsp_err_t err = FSP_SUCCESS; int timeout_ms = 100; i2c_master_event_t i2c_event = I2C_MASTER_EVENT_ABORTED; void sau_i2c_master_callback(i2c_master_callback_args_t *p_args) { i2c_event = I2C_MASTER_EVENT_ABORTED; if (NULL != p_args) { /* capture callback event for validating the i2c transfer event*/ i2c_event = p_args->event; } } /* ENS160_thread entry function */ extern char msg[256]; /* pvParameters contains TaskHandle_t */ void ENS160_thread_entry(void *pvParameters) { FSP_PARAMETER_NOT_USED (pvParameters); const char *message = "Hello, FreeRTOS!"; R_IIC_MASTER_Open(&g_i2c0_ctrl, &g_i2c0_cfg); aht21_init(); ens160_init(); /* TODO: add your own code here */ while (1) { ENS160_Read(); size_t bytesSent = xMessageBufferSend(g_sensor_message_buffer, msg, strlen(msg), pdMS_TO_TICKS(1000)); if (bytesSent < strlen(msg)) { LOG_E("\r\nFailed to send complete message"); } size_t bytesSent2 = xMessageBufferSend(g_sensor_message_buffer_mqtt, msg2, strlen(msg2), pdMS_TO_TICKS(1000)); if (bytesSent < strlen(msg)) { LOG_E("\r\nFailed to send complete message"); } vTaskDelay (100); } } ``` 对于 ENS160 读取部分驱动 ```c void ENS160_Read(void) { uint8_t eco2_raw[2] = {0}; uint16_t eco2_val = 0; uint8_t temperature_raw[2] = {0}; uint16_t temperature_val = 0; uint8_t rh_raw[2]={0}; float temperature_c = 0; uint16_t tempUint16; uint8_t air_quality = 0; float aht_temp = 0.0f; float aht_humi = 0.0f; uint16_t ahtu16; uint16_t ens_temp; uint16_t ens_rh ; aht21_read_data(&aht_temp, &aht_humi); // Format as ENS160 expects ens_temp = (uint16_t)((aht_temp + 273.15f) * 64.0f); ens_rh = (uint16_t)(aht_humi * 512.0f); uint8_t cmd_code[3] ={ENS160_TEMP_IN,ens_temp>>8,ens_temp&0xFF}; wk_delay_ms(10); g_i2c0_ctrl.slave = ENS160_ADDR; // i2c_master_transmit(&hi2cx, ENS160_ADDR<<1,cmd_code, 2, I2C_TIMEOUT); err = R_IIC_MASTER_Write(&g_i2c0_ctrl, cmd_code, 3, false); assert(FSP_SUCCESS == err); /* Since there is nothing else to do, block until Callback triggers*/ while ((I2C_MASTER_EVENT_TX_COMPLETE != i2c_event) && timeout_ms>0) { R_BSP_SoftwareDelay(1U, BSP_DELAY_UNITS_MICROSECONDS); timeout_ms--; } if (I2C_MASTER_EVENT_ABORTED == i2c_event) { __BKPT(0); } /* Read data back from the I2C slave */ i2c_event = I2C_MASTER_EVENT_ABORTED; timeout_ms = I2C_TIMEOUT; wk_delay_ms(30); uint8_t cmd_code2[3] ={ENS160_RH_IN,ens_rh>>8,ens_rh&0xFF}; // LOG_I("ens_rh = %x,ens_rl =%x\n",ens_rh>>8,ens_rh&0xFF); g_i2c0_ctrl.slave = ENS160_ADDR; // cmd_code[0] =ENS160_RH_IN; // cmd_code[1]=(ens_rh>>8); // cmd_code[2] =(ens_rh&0xff); err = R_IIC_MASTER_Write(&g_i2c0_ctrl, cmd_code2, 3, false); assert(FSP_SUCCESS == err); /* Since there is nothing else to do, block until Callback triggers*/ while ((I2C_MASTER_EVENT_TX_COMPLETE != i2c_event) && timeout_ms>0) { R_BSP_SoftwareDelay(1U, BSP_DELAY_UNITS_MICROSECONDS); timeout_ms--; } if (I2C_MASTER_EVENT_ABORTED == i2c_event) { //__BKPT(0); } /* Read data back from the I2C slave */ i2c_event = I2C_MASTER_EVENT_ABORTED; timeout_ms = I2C_TIMEOUT; wk_delay_ms(10); // Read eCO2 ENS160_mem_read(&g_i2c0_ctrl, ENS160_ADDR, ENS160_REG_ECO2, eco2_raw, 2, I2C_TIMEOUT); //HAL_I2C_Mem_Read(&hi2c1, ENS160_I2C_ADDR, ENS160_REG_ECO2, I2C_MEMADD_SIZE_8BIT, eco2_raw, 2, HAL_MAX_DELAY); eco2_val = (eco2_raw[1]<<8)|eco2_raw[0]; // Read temperature (actual, from 0x30) ENS160_mem_read(&g_i2c0_ctrl, ENS160_ADDR, ENS160_REG_TEMPERATURE, temperature_raw, 2, I2C_TIMEOUT); // HAL_I2C_Mem_Read(&hi2c1, ENS160_I2C_ADDR, ENS160_REG_TEMPERATURE, I2C_MEMADD_SIZE_8BIT, temperature_raw, 2, HAL_MAX_DELAY); temperature_val = (temperature_raw[1]<<8)|temperature_raw[0]; temperature_c = ((float)temperature_val / 64.0) - 273.15; // Read air quality index ENS160_mem_read(&g_i2c0_ctrl, ENS160_ADDR, ENS160_REG_QUALITY, &air_quality, 1, I2C_TIMEOUT); ENS160_mem_read(&g_i2c0_ctrl, ENS160_ADDR, ENS160_REG_RH, rh_raw, 2, I2C_TIMEOUT); aht_humi=(uint16_t)((rh_raw[0] << 8) | rh_raw[1]) / 512.0f; snprintf(msg,sizeof(msg),"%.2f,%.2f,%d,%d", aht_temp-5, aht_humi , air_quality, eco2_val); snprintf(msg2, sizeof(msg2), "{\r\n \"temperature\": %.2f,\r\n \"humidity\": %.2f,\r\n \"AirQuality\": %d,\r\n \"eCO2\": %d\r\n}", aht_temp, aht_humi , air_quality, eco2_val); // HAL_UART_Transmit(&huart2, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY); LOG_I("%s",msg); } ``` LWIP_Thread 任务程序 ```c #define TOPIC "office/sensor1" void LWIP_Thread_entry(void *pvParameters) { FSP_PARAMETER_NOT_USED (pvParameters); /* TODO: add your own code here */ // start_mqtt_task(pvParameters); // (void)argument; #ifdef LWIP_APP uint32_t notified_value; mqtt_client_t *client = mqtt_client_new(); struct mqtt_connect_client_info_t ci; memset(&ci, 0, sizeof(ci)); ci.client_id = "lwip_mqtt_test"; ci.client_user = USER_NAME; ci.client_pass = PASSWD; ci.keep_alive =60; ci.will_topic ="office/sensor1"; ci.will_qos =0; ci.will_retain =0; // 创建计数信号量:最大值 MQTT_REQ_MAX_IN_FLIGHT,初始值相同 publish_semaphore_id = xSemaphoreCreateCounting(4, 5); IP4_ADDR(&host_ip, 192, 168, 3, 57); //homeassistant IP地址 // 假设此处已通过 DNS 获取了 host_ip,或手动填入 IP mqtt_client_connect(client, &host_ip, PORT, mqtt_connection_cb, NULL, &ci); char payload[100]; u8_t qos = 1; u8_t retain = 0; // 等待任务通知 // 参数:进入前不清除位,退出时清除所有位,指向通知值的指针,永久等待 if (xTaskNotifyWait(0, 0xFFFFFFFF, ¬ified_value, portMAX_DELAY) == pdTRUE) { if (notified_value & SIGNAL_CONNECTED) { for(int i = 0; ; i++) { // 获取信号量 (代替 osSemaphoreWait) if (xSemaphoreTake(publish_semaphore_id, portMAX_DELAY) == pdPASS) { size_t bytesReceived = xMessageBufferReceive(g_sensor_message_buffer_mqtt, payload, sizeof(payload), portMAX_DELAY); mqtt_publish(client, TOPIC, payload, bytesReceived, qos, retain, mqtt_pub_request_cb, NULL); } vTaskDelay(pdMS_TO_TICKS(1000)); } } } #endif } ``` HomeAssistant 配置(configuration.yaml) ```yaml mqtt sensor: - name: "温度" state_topic: "office/sensor1" suggested_display_precision: 1 unit_of_measurement: "°C" value_template: "{{ value_json.temperature }}" - name: "湿度" state_topic: "office/sensor1" unit_of_measurement: "%" value_template: "{{ value_json.humidity }}" - name: "空气质量" state_topic: "office/sensor1" suggested_display_precision: 1 unit_of_measurement: "Q" value_template: "{{ value_json.AirQuality }}" - name: "二氧化碳" state_topic: "office/sensor1" unit_of_measurement: "ppm" value_template: "{{ value_json.eCO2 }}" ``` 使用File edit保存此ymal文件 然后在设置——>开发者工具中应用YAML配置,然后重启mqtt服务器
### LVGL页面设计 借助与NXP的Guibuilder,设计了2个页面
然后把Guibuilder 生成的代码文件夹 加入到源位置中
一个显示实时数据,一个才有折线图显示温湿度与eCO2的趋势变化 。通过在核心板的按键来切换两个页面 下图是在homeassistant上显示温湿度等相关参数信息
视频演示LVGL 显示实时传感器数据,与历史数据
lvglDisp
视频演示LWIP mqtt接收传感器数据
Video_2026-02-01_234246
### 改进与不足 测试后续改进,在实际过程中LWIP任务在运行一会儿后有时出现 不给服务器发送数据包的情况。LVGL任务的第二个图形界面,当按键按下后有时不会返回到主界面。还带继续修正 ### 总结 瑞萨提供的 FSP(Flexible Software Package)开发包对新手非常友好:外设驱动(I2C/MIPI/ETH)均有标准化 API,无需从零编写寄存器配置代码;FSP 直接支持 FreeRTOS 集成,任务创建、互斥锁 / 消息缓冲区等组件可通过配置工具可视化配置,减少了手动移植 FreeRTOS 的踩坑成本;与 LVGL 的适配无兼容性问题,仅需实现 LCD 刷新回调函数即可完成 UI 底层对接。对比部分小众 MCU“资料少、驱动残缺” 的问题,RA8D1 的开发体验接近 STM32,但在 MIPI、以太网等外设的原生支持上更具优势。RA8D1 的测评让我看到:中高端 MCU 的 “高集成度” 正在降低嵌入式产品的开发门槛,开发者无需再花费大量精力做外设扩展,可更聚焦于业务逻辑和用户体验。
本帖最后由 高山迷彩 于 2026-2-9 00:24 编辑
点赞
回复评论
暂无评论,赶紧抢沙发吧
最新活动
罗德与施瓦茨—从天到地 从测试者角度 解读NTN技术
你晒单我买单2026第1期报名中,DigiKey得捷带您畅享好物!
有奖直播:AI基础设施技术测试周
免费申请《一本书讲透汽车功能安全:标准详解与应用实践》,挑战《ISO26262标准》共读,赢好礼
装备焕新月:e络盟Multicomp Pro系列产品,小投入,大升级 —— 装备焕新惊喜体验
MPS 机器人模块设计大赛报名重磅开启,锻造硬实力,让想法照进现实!
随便看看
后台服务程序
FPGA-spi问题
东芝PCIM在线展会开始啦!精彩享不停!
stm32能通过硬件停止PWM输出吗
感谢有你+谢谢坚持
嵌入式的OTA空中下载技术
FSEZ1317的变压器参数取值
关于MSP430F149的多路捕获问题。
stm31f1000系列用什么调试环境
请教高人算法问题(移频信号计算)
什么样的帖子才算精华贴啊?
TCPMP界面设计,有偿求助……
wince 下调用动态连接库问题
如何通过元件摆放来改善电路板的EMI?
八月秒杀:是德科技 N9000A信号分析仪
传个单片机快速上手
一款功能强大的STM32F103ZE开发板(第2版,功能更强大)原理图和用户手册
altera公司IP核使用手册
电源设计
LAUNCHXL-F28027 - C2000 Piccolo LaunchPad已收!
往年今日
verilog书上投币器代码,不懂请教
为什么交流电压成分会变小????
初学DSP,用ccs4运行的时候总是出现直接关闭,希望前辈们帮助,多谢
晒设计方案+STM32F429i之看看温度对晶振有多大影响
各位大神,初学者请教!
430BOOST-SHARP96 显示效果不错!
随便聊聊……
三极管的选择
晒设计方案+STM32F429i开发板之触摸屏
金钢狼!MSP-BNDL-FR5969LCD开箱
电子工程世界版权所有
京B2-20211791
京ICP备10001474号-1
京公网安备 11010802033920号
回复
写回复
收藏
回复