RA4M2开发涂鸦模块CBU.6--RA4M2驱动涂鸦CBU模组
- 概述
- 视频教学
- 样品申请
- 参考程序
- 硬件准备
- 接口
- 生成UART
- UART属性配置
- R_SCI_UART_Open()函数原型
- 回调函数user_uart_callback0 ()
- 变量定义
- 按键回调
- 更新按键状态
- DP-LED 同步
- 长按进入配网
- 涂鸦协议解析
- 主循环任务调度
概述
本方案基于瑞萨 RA4M2 MCU 与涂鸦 CBU Wi-Fi & BLE 模组的无缝对接,旨在快速构建智能传感与控制终端。系统架构由 RA4M2 负责业务逻辑和外设驱动,CBU 模组提供网络接入与云端交互能力,两者通过标准串口通协议 (Tuya 通用协议) 完整实现产品功能上报与命令下发。
最近在瑞萨RA的课程,需要样片的可以加qun申请:925643491。
视频教学
https://www.bilibili.com/video/BV1ETMBzrEVt
RA4M2开发涂鸦模块CBU(6)----RA4M2驱动涂鸦CBU模组
样品申请
https://www.wjx.top/vm/rCrkUrz.aspx
参考程序
https://github.com/CoreMaker-lab/RA4M2_TUYA_CBU
https://gitee.com/CoreMaker/RA4M2_TUYA_CBU
硬件准备
首先需要准备一个开发板,这里我准备的是自己绘制的开发板,需要的可以进行申请。
主控为R7FA4M2AD3CFL#AA0
这里使用的无线硬件是涂鸦CBU模组。
接口
模组跳线接线方式如下。
接入RA4M2开发板。
涂鸦模组原理图如下所示。
这里的涂鸦可以接入RA4M2的P100和P101。
生成UART
点击Stacks->New Stack->Connectivity -> UART(r_sci_uart)。
UART属性配置
R_SCI_UART_Open()函数原型
故可以用 R_SCI_UART_Open()函数进行配置,开启和初始化UART。
故可以用 R_SCI_UART_Open()函数进行配置,开启和初始化UART。
/* Open the transfer instance with initial configuration. */err = R_SCI_UART_Open(&g_uart0_ctrl, &g_uart0_cfg);assert(FSP_SUCCESS == err);
回调函数user_uart_callback0 ()
当数据发送的时候,可以查看UART_EVENT_TX_COMPLETE来判断是否发送完毕。
当数据接收的时候,可以查看UART_EVENT_RX_COMPLETE来判断是否发送完毕。
UART_EVENT_RX_CHAR:字符接收事件,当接收到一个字符时触发。
可以检查检查 “p_args” 结构体中的 “event” 字段的值是否等于 “UART_EVENT_TX_COMPLETE"或者"UART_EVENT_RX_COMPLETE"或者"UART_EVENT_RX_CHAR”。
/* UART0——涂鸦模块:单字节接收完成标志 */
volatile bool uart_wifi_RX_flag = false;
/* UART0——涂鸦模块:发送完成标志*/
volatile bool uart_wifi_TX_flag = false;
/* UART0 接收缓存大小*/
#define UART0_LENGTH 255
/* UART0 接收环形缓冲区*/
uint8_t TUYA_wifi_buffer[UART0_LENGTH];
/* 已接收字节计数(环形缓存写指针)*/
uint32_t UART0_TUYA_LENGTH = 0;
/* 解析到一帧有效数据后置位,主循环里解析后需清零 */
uint32_t UART0_TUYA_flag = 0;/****************************************************************** UART0 回调:RX/TX/单字符事件处理*****************************************************************/
void user_uart_callback0(uart_callback_args_t *p_args)
{/* 整帧接收完成(需在 R_SCI_UART_Receive 调用后才会产生)*/if(p_args->event == UART_EVENT_RX_COMPLETE){uart_wifi_RX_flag = true;}/* 发送完成 */else if(p_args->event == UART_EVENT_TX_COMPLETE){uart_wifi_TX_flag = true;}/* 接收到 1 个字符*/else if(p_args->event == UART_EVENT_RX_CHAR){/* 缓冲未满则写入*/if (sizeof(TUYA_wifi_buffer) > UART0_TUYA_LENGTH){/* 仅在数据位 ≥ 8bit 时写入 1 字节 */if (UART_DATA_BITS_8 >= g_uart0_cfg.data_bits){TUYA_wifi_buffer[UART0_TUYA_LENGTH++] = (uint8_t) p_args->data;}/* —— 以下为简单的涂鸦帧快速判定逻辑,可改为状态机 —— *//* 帧头非 0x55 则丢弃当前字节(回溯 1 位)*/if(TUYA_wifi_buffer[00]!=0x55)UART0_TUYA_LENGTH--;/* 普通指令帧长度 ≥ 7 且 CMD ≠ 0x07 时,即可认为一帧完 */if(UART0_TUYA_LENGTH>=7 && TUYA_wifi_buffer[3]!=0x06)UART0_TUYA_flag=1;/* CMD = 0x06(DP 下发)需根据数据点判断长度*/if(TUYA_wifi_buffer[3]==0x06){/* DP-ID 0x65:亮度,数据长度 0x0004 -> 总长 ≥ 15 */if(TUYA_wifi_buffer[6]==0x65 && UART0_TUYA_LENGTH>=15)//亮度值UART0_TUYA_flag=1;/* DP-ID 0x66:开关,数据长度 0x0001 -> 总长 ≥ 12 */else if(TUYA_wifi_buffer[6]==0x66 && UART0_TUYA_LENGTH>=12)//开关UART0_TUYA_flag=1;}}}
}
变量定义
/****************************************************************** 与涂鸦协议相关的工作变量/固定协议帧 * *****************************************************************/uint8_t wifi_first =0;/* 0:第一次心跳;1:第二次心跳 */
uint32_t wifi_num =0;//如果心跳频繁发送,可能是触发了复位,需要重新发送buff1,这里2秒内多次发送心跳指令则认为重启
/* ---- 固定格式下行帧:MCU 主动发送给涂鸦模块 ----------------- */
const uint8_t g_tuya_heartbeat1[8]={0x55,0xAA,0x03,0x00,0x00,0x01,0x00,0x03};//心跳检测,第1次 0x55 aa 00 00 00 01 00 03
const uint8_t g_tuya_heartbeat2[8]={0x55,0xAA,0x03,0x00,0x00,0x01,0x01,0x04};//心跳检测,第2次 0x55 aa 00 00 00 01 01 04
const uint8_t g_tuya_wifi_cfg[8]={0x55,0xAA,0x03,0x05,0x00,0x01,0x01,0x09};//WIFI配网
//0x55, 0xAA, 0x03, 0x01 (帧头)
//0x00, 0x2A (长度)
//0x7B, 0x22, 0x70, 0x22, 0x3A, 0x22 ({"p":")
//0x78, 0x68, 0x6E, 0x7A, 0x74, 0x79, 0x64, 0x67, 0x77, 0x64, 0x63, 0x67, 0x6B, 0x71, 0x78, 0x64 (xhnztydgwdcgkqxd)
//0x22, 0x2C, 0x22, 0x76, 0x22, 0x3A, 0x22 (","v":")
//0x31, 0x2E, 0x30, 0x2E, 0x30 (1.0.0)
//0x22, 0x2C, 0x22, 0x6D, 0x22, 0x3A (","m":)
//0x30 (0)
//0x7D(})
//0xC(校验码)
uint8_t g_tuya_product_info[49]={0x55, 0xAA, 0x03, 0x01,0x00, 0x2A,0x7B, 0x22, 0x70, 0x22, 0x3A, 0x22,0x78, 0x68, 0x6E, 0x7A, 0x74, 0x79, 0x64, 0x67, 0x77, 0x64, 0x63, 0x67, 0x6B, 0x71, 0x78, 0x64,0x22, 0x2C, 0x22, 0x76, 0x22, 0x3A, 0x22,0x31, 0x2E, 0x30, 0x2E, 0x30,0x22, 0x2C, 0x22, 0x6D, 0x22, 0x3A,0x30,0x7D,0xCC};//接收模块发送的查询产品信息请求const uint8_t g_tuya_query_mode[8]={0x55,0xaa,0x03,0x02,0x00,0x00,0x04};//查询工作模式
uint32_t wifi_ap_num =0;//wifi重置计时
const uint8_t g_tuya_rpt_net[8]={0x55,0xaa,0x03,0x03,0x00,0x00,0x05};//报告设备联网状态uint32_t wifi_Update=0;//wifi发送标志位
uint32_t wifi_Rx=0;//wifi接收指令标志位
uint32_t LED_PWM_num=10;//亮度值
/* DP-ID 0x65:亮度值 */
uint8_t g_tuya_dp_pwm[15]={0x55,0xAA,0x03,0x07,0x00,0x08,0x65,0x02,0x00,0x04,0x00,0x00,0x00,0x0A,0x86};//MCU亮度
bool LED_open=0; /* LED 开关:0=OFF,1=ON */
/* DP-ID 0x66:开关 */
uint8_t g_tuya_dp_switch[12]={0x55,0xAA,0x03,0x07,0x00,0x05,0x66,0x01,0x00,0x01,0x00,0x76};//MCU上报开关状态
按键回调
更新按键中断external_irq4_callback,当按下的时候需要告知无线模块,无线模块进行上报,这里定义当按键按下时候button_flag为1,让无线模组进行上报。
/** @brief 由中断回调函数切换的外部中断标志 */
static volatile bool s_ext_irq_flag = false;
bool button_flag =0;
/*** @brief 外部中断 IRQ4 回调函数** 当 ICU 外部中断发生时调用,切换外部中断标志。** @param[in] p_args 中断回调参数(未使用)*/
void external_irq4_callback(external_irq_callback_args_t *p_args)
{(void)p_args;s_ext_irq_flag = !s_ext_irq_flag;button_flag=1;LED_open=!LED_open;
}
更新按键状态
在原有基础上增加wifi_Update变量,这样每次使用按键之后都可以上报到云端。
/*** @brief 根据中断标志更新 LED 输出** 根据 s_ext_irq_flag 状态设置指定 IOPORT 引脚高低电平。*/
static inline void led_update(void)
{
// R_IOPORT_PinWrite(&g_ioport_ctrl,
// BSP_IO_PORT_01_PIN_04,
// s_ext_irq_flag ? BSP_IO_LEVEL_HIGH : BSP_IO_LEVEL_LOW);if(button_flag){button_flag=0;wifi_Update=1;R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_01_PIN_04, LED_open);}
}
DP-LED 同步
该函数先检查 wifi_Update 标志,若需上报,则:
- 本地硬件同步
○ 调整 GPT2 定时器占空比以设置 LED 呼吸灯亮度。 - 构建并发送涂鸦 DP-亮度帧
○ 填充 g_tuya_dp_pwm 数组中的亮度字段并校验。
○ 通过 UART0 向涂鸦模块下发亮度变化数据点。 - 构建并发送涂鸦 DP-开关帧
○ 更新 g_tuya_dp_switch 中的开关字段并校验。
○ 通过 UART0 向涂鸦模块下发 LED 开关状态。
整个流程实现了“本地 LED 状态 → MCU 构造涂鸦数据帧 → Wi-Fi 模块上报至云端”的闭环同步。
/******************************************************************************************************************** @brief 向涂鸦模块上报最新 DP 数据 & 本地硬件同步*******************************************************************************************************************/
void tuya_wifi_Update(void)
{if(wifi_Update){printf("wifi_Update\n");wifi_Update=0;/*更新 GPT 占空比 ---------------------------------------*//* 计数器周期在 FSP 配置为 9000(举例),因此 1% 亮度 = 9000 / 1000 = 9 个计数。 */fsp_err_t err = R_GPT_DutyCycleSet(&g_timer2_ctrl,9000-LED_PWM_num * 9, // 新比较值GPT_IO_PIN_GTIOCA); // 使用 A 通道assert(FSP_SUCCESS == err);/* 更新 DP 帧 ------------------------------------------------------*/g_tuya_dp_pwm[12]=LED_PWM_num>>8;g_tuya_dp_pwm[13]=LED_PWM_num;g_tuya_dp_pwm[14]=0x86-0x0A+g_tuya_dp_pwm[12]+g_tuya_dp_pwm[13];/*发送 DP-亮度 */printf("[MCU] -> TUYA DP_PWM = %d\r\n",LED_PWM_num);err = R_SCI_UART_Write(&g_uart0_ctrl, g_tuya_dp_pwm, 15);if(FSP_SUCCESS != err) __BKPT();while(uart_wifi_TX_flag == false){}uart_wifi_TX_flag = false;/*发送 DP-开关 */printf("[MCU] -> TUYA DP_SW=%d\r\n",LED_open);g_tuya_dp_switch[10]=LED_open;g_tuya_dp_switch[11]=0x76+g_tuya_dp_switch[10];//上报LED开关err = R_SCI_UART_Write(&g_uart0_ctrl, g_tuya_dp_switch, 12);if(FSP_SUCCESS != err) __BKPT();while(uart_wifi_TX_flag == false){}uart_wifi_TX_flag = false;}}
长按进入配网
该函数检测按键引脚状态,累计长按时间。当按键持续按下达到 3 秒(3000 次 1 ms 延时)时,触发一次配网模式:
- 打印日志 [BTN] wifi_ap_mode
- 通过 UART0 向涂鸦模块发送配网指令帧 g_tuya_wifi_cfg(0x05 命令)。
短按或松开时则重置计时,避免误触发。
/******************************************************************************************************************** @brief 长按按键 3 s 进入配网模式 (发送 0x05 命令)*******************************************************************************************************************/
void button_wifi_ap(void)
{// wifi_ap_numbsp_io_level_t p_port_value_pin_111;R_IOPORT_PinRead(&g_ioport_ctrl, BSP_IO_PORT_01_PIN_11, &p_port_value_pin_111);if(p_port_value_pin_111)/* 松开 */wifi_ap_num=0;else//长按3s复位wifi{if(wifi_ap_num<3000)wifi_ap_num++;else if(wifi_ap_num==3000){wifi_ap_num++;printf("[BTN] wifi_ap_mode\r\n");fsp_err_t err = R_SCI_UART_Write(&g_uart0_ctrl, g_tuya_wifi_cfg, 8);if(FSP_SUCCESS != err) __BKPT();while(uart_wifi_TX_flag == false){}uart_wifi_TX_flag = false;}}
}
涂鸦协议解析
该函数在接收到完整一帧涂鸦数据后,
- 校验帧头与版本,确认有效帧;
- 根据 CMD 字段(frame[3])分支:
○ 0x00:心跳请求,打印 [TUYA] 并交替发送两种心跳应答;
○ 0x01:查询产品信息,打印 [TUYA] 并发送产品信息帧;
○ 0x02:查询工作模式,打印 [TUYA] 并发送工作模式帧;
○ 0x03:网络状态上报,打印 [TUYA] <WIFI_MODE=XX> 并回复网络状态帧,同时在已连云时置 wifi_Update;
○ 0x06:DP 下发,打印 [TUYA] <DP-switch=ON/OFF> 或 [TUYA] <DP-PWM=值>,更新本地 LED 状态与亮度,并置 wifi_Update。 - 清空接收缓存,为下一帧解析复位。
/******************************************************************************************************************** @brief 解析完成后,根据 CMD 打印清晰友好的日志*******************************************************************************************************************/
void uart0_tuya(void)
{if(UART0_TUYA_flag ==1)//接收完成标志{fsp_err_t err = FSP_SUCCESS;UART0_TUYA_flag=0;if(TUYA_wifi_buffer[0]==0x55&&TUYA_wifi_buffer[1]==0xAA)//判断帧头和版本{if(TUYA_wifi_buffer[3]==0x00)//判断是否为心跳检测{printf("[TUYA] <heartbeat(SEQ=%u)\r\n", wifi_first);
// if(wifi_num<2000&&wifi_first==1)//频繁发送心跳指令,认为重启
// {
// wifi_first=0;
// }
// wifi_num=0;if(wifi_first==0)//第一次发送心跳数据{wifi_first=1;//心跳检测,向涂鸦模块发送err = R_SCI_UART_Write(&g_uart0_ctrl, g_tuya_heartbeat1, 8);if(FSP_SUCCESS != err) __BKPT();while(uart_wifi_TX_flag == false){}uart_wifi_TX_flag = false;}else{//心跳检测,向涂鸦模块发送err = R_SCI_UART_Write(&g_uart0_ctrl, g_tuya_heartbeat2, 8);if(FSP_SUCCESS != err) __BKPT();while(uart_wifi_TX_flag == false){}uart_wifi_TX_flag = false;}}else if(TUYA_wifi_buffer[3]==0x01)//接收模块发送的查询产品信息请求{printf("[TUYA] <Query Product Information\r\n");//接收模块发送的查询产品信息请求,向涂鸦模块发送err = R_SCI_UART_Write(&g_uart0_ctrl, g_tuya_product_info, 49);if(FSP_SUCCESS != err) __BKPT();while(uart_wifi_TX_flag == false){}uart_wifi_TX_flag = false;}else if(TUYA_wifi_buffer[3]==0x02)//查询工作模式{printf("[TUYA] <Query Work Mode\r\n");//查询工作模式,向涂鸦模块发送err = R_SCI_UART_Write(&g_uart0_ctrl, g_tuya_query_mode, 8);if(FSP_SUCCESS != err) __BKPT();while(uart_wifi_TX_flag == false){}uart_wifi_TX_flag = false;}else if(TUYA_wifi_buffer[3]==0x03)//报告设备联网状态{printf("[TUYA] <WIFI_MODE=%02X\r\n", TUYA_wifi_buffer[6]);//查询工作模式,向涂鸦模块发送err = R_SCI_UART_Write(&g_uart0_ctrl, g_tuya_rpt_net, 8);if(FSP_SUCCESS != err) __BKPT();while(uart_wifi_TX_flag == false){}uart_wifi_TX_flag = false;if(TUYA_wifi_buffer[6]==0x04)//已连上路由器且连接到云端{wifi_Update=1;//wifi跟新标志位}}else if(TUYA_wifi_buffer[3]==0x06)//接收模块DP下发信息{if(TUYA_wifi_buffer[6]==0x66)//LED开关{wifi_Update=1;//wifi跟新标志位LED_open=TUYA_wifi_buffer[10];button_flag=1;printf("[TUYA] <DP-switch=%s\r\n", LED_open ? "ON" : "OFF");}else if(TUYA_wifi_buffer[6]==0x65)//LED亮度值{wifi_Update=1;//wifi跟新标志位LED_PWM_num=0;LED_PWM_num+=TUYA_wifi_buffer[13];LED_PWM_num+=TUYA_wifi_buffer[12]*256;printf("[TUYA] <DP-PWM=%ld\r\n", LED_PWM_num);}}}// 清除数组memset(TUYA_wifi_buffer, 0, UART0_LENGTH);// 同时把当前有效长度归零UART0_TUYA_LENGTH = 0;}
}
主循环任务调度
在主循环中依次执行以下任务:
- LED 更新(led_update())— 处理普通按键翻转 LED;
- 涂鸦数据上报(tuya_wifi_Update())— 若有变更则同步 DP 数据;
- 配网按键检测(button_wifi_ap())— 长按 3 s 触发 Wi-Fi 配网命令;
- 涂鸦协议解析(uart0_tuya())— 解析并响应下行命令;
/* 主循环 */while (true){led_update(); /* 切换普通 LED */
// pwm_breathe_update(); /* 呼吸灯效果 */tuya_wifi_Update(); /* 涂鸦数据上报 */button_wifi_ap(); /* 配网按键检测 */uart0_tuya(); /* 涂鸦协议解析 */R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS);}