【WCH蓝牙系列芯片】-基于CH582开发板—利用定时器加DMA方式模拟串口输出
------------------------------------------------------------------------------------------------------------------------------------
在使用CH582芯片开发测试中,有个实际的用途是利用串口输出日志的方式,来进行程序的调试。CH582芯片一共提供了 4 组全双工的异步串口 UART0/1/2/3;但是如果在应用中四个串口都用完了,没有单独空的串口作为日志输出,此时,可以利用芯片的定时器加DMA方式模拟串口输出的方式解决这一问题。
在CH582芯片上,只有TMR1和TMR2支持DMA功能,所以使用PA11所在的定时器2引脚作为模拟串口的输出脚。
先初始化定时器和DMA,配置PA11为推挽输出模式,设置主频和波特率参数
static uint32_t timer_dma_buf[11]; //存储DMA(直接内存访问)传输的数据 static uint32_t bit_cnt; //用于存储每个位的计数值 //接收主频(sys_frq)和波特率(baudrate) void ch5xx_timer_uart_init(uint32_t sys_frq,uint32_t baudrate) //初始化UART接口的函数 { bit_cnt = sys_frq/baudrate; //计算每个位的计数值 timer_dma_buf[0] = bit_cnt; //将计算出的位计数值存储在DMA缓冲区的第一个元素中 timer_dma_buf[9] = 0; //将DMA缓冲区的第十个元素设置为0 timer_dma_buf[10] = 0; //将DMA缓冲区的第十一个元素设置为0 timer_dma_buf[11] = 0; //将DMA缓冲区的第十二个元素设置为0 GPIOA_SetBits(GPIO_Pin_11); GPIOA_ModeCfg(GPIO_Pin_11, GPIO_ModeOut_PP_5mA); //推挽输出 #if 0 //LED闪烁 uint32_t i=100; while(i--) { GPIOB_InverseBits(GPIO_Pin_22); DelayMs(1); } GPIOB_ResetBits(GPIO_Pin_22); DelayMs(10); GPIOB_SetBits(GPIO_Pin_22); #endif TMR2_PWMInit(Low_Level, PWM_Times_1); //初始化定时器2为PWM(脉冲宽度调制)模式 TMR2_PWMCycleCfg(bit_cnt); //配置PWM周期为bit_cnt TMR2_Disable(); //禁用定时器2 TMR2_PWMActDataWidth(0); // 占空比 50%, 修改占空比必须暂时关闭定时器 TMR2_PWMEnable(); //启用PWM TMR2_Enable(); //启用定时器2 TMR2_ITCfg(ENABLE, RB_TMR_IE_DMA_END); //启用定时器2的DMA结束中断 R32_TMR2_DMA_BEG = (uint32_t)&timer_dma_buf[0]; //设置DMA传输的起始地址为timer_dma_buf数组的开始 R32_TMR2_DMA_END = (uint32_t)&timer_dma_buf[10]; //设置DMA传输的结束地址为timer_dma_buf数组的第十个元素 ch5xx_timer_printf("ch5xx_timer uart init done\r\n"); //调用ch5xx_timer_printf函数打印初始化完成的消息 }
//用于通过定时器发送一系列字节数据 //uint8_t *data:指向要发送数据的指针 //uint32_t len:要发送的数据长度 __HIGH_CODE void ch5xx_timer_send(uint8_t *data,uint32_t len) { while(len) { ch5xx_timer_send_byte(*data); //传入当前指向的数据字节 data ++; len --; } } //通过定时器和DMA(直接内存访问)发送单个字节的数据。将一个字节的数据位逐个发送出去 __HIGH_CODE void ch5xx_timer_send_byte(uint8_t byte) { uint32_t shift_data = byte; //用于存储要发送的字节数据 uint32_t *p = &timer_dma_buf[1]; //定义一个指向timer_dma_buf数组第二个元素的指针p。timer_dma_buf是一个预先定义的数组,用于存储DMA传输的数据 //一个字节有8位 for(uint8_t j=0;j<8;j++) { if(shift_data & 0x01u) //检查shift_data的最低位是否为1,通过与操作0x01u(即二进制的00000001) { *p = 0; //如果最低位为1,则将指针p指向的数组元素设置为0,表示在UART信号中发送一个逻辑高电平 } else { *p = bit_cnt; // 将指针p指向的数组元素设置为bit_cnt,用于存储定时器的计数值,表示在UART信号中发送一个逻辑低电平。 } shift_data >>=1; //将shift_data右移一位,这样下一次循环将检查下一个位 p++; //指针p向前移动 } R32_TMR2_DMA_BEG = (uint32_t)&timer_dma_buf[0]; // 设置DMA传输的起始地址为timer_dma_buf数组的开始 TMR2_ClearITFlag(RB_TMR_IE_DMA_END); //清除定时器2的DMA结束中断标志 R8_TMR2_CTRL_DMA = RB_TMR_DMA_ENABLE; //启用定时器2的DMA //wait dma end 等待DMA传输结束 while(!TMR2_GetITFlag(RB_TMR_IF_DMA_END)); //使用一个while循环来轮询DMA结束中断标志,直到它被设置,表示DMA传输已经完成 R8_TMR2_CTRL_DMA = 0; //禁用定时器2的DMA } //将格式化的字符串发送到一个串行接口 void ch5xx_timer_printf(char *fmt,...) { char buffer[128]; //一个字符数组buffer,大小为128字节,用于存储格式化后的字符串 unsigned char i=0; va_list arg_ptr; // 定义了一个变量参数列表arg_ptr va_start(arg_ptr,fmt); //初始化变量参数列表arg_ptr uint32_t len = vsnprintf(buffer,128,fmt,arg_ptr); // 调用vsnprintf函数将格式化的字符串写入buffer va_end(arg_ptr); //结束对变量参数列表arg_ptr的使用 ch5xx_timer_send(buffer,len); //调用ch5xx_timer_send函数发送buffer中的数据,发送的长度由len指定 }
在主函数中,只要调试初始化函数,传入对应的主频和波特率参数,这里设置主频时60M,波特率在115200,再调用ch5xx_timer_printf函数直接就可以输出日志。2M波特率测试也是可以。
通过串口助手设置好对应的波特率,可以看到正确打印的数据参数。