[嵌入式开发模块]单片机串口模块:串口+定时器+环形缓冲区 实现无串口IDLE中断接收不定长串口数据
Posted 雾里赏花
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[嵌入式开发模块]单片机串口模块:串口+定时器+环形缓冲区 实现无串口IDLE中断接收不定长串口数据相关的知识,希望对你有一定的参考价值。
前言
本周看了些代码模块化和代码框架抽象分层的知识,现在尝试将手里代码重新整理成模块,方便以后业务开发。现在摸索了两天,在网上看了些别人的文章和代码,初步整理好了第一版(2019.12.6)。
MCU: 华大的MCU HC32F030K8TA,其内核是Cortex-M0+,8KRAM,64KROM。
编程环境:keil5
模块
分析介绍
这款MCU的性能一般,功能较少,目前想要让其外接一个通信模块进行联网,通过串口指令控制配置通信模块,之前使用它接收串口数据都是有协议指定帧头和帧尾,连上通信模块以后,模块发送的串口数据可能没有固定帧尾,在网上查了些资料,很多都是用stm32的串口IDLE中断功能实现的,但是这款MCU没有IDLE寄存器,没有IDLE中断,无IDLE中断,在参考这篇文章《从uart到serial》后,最终实现了串口模块化和串口接收不定长数据。具体实现方式是使用定时器判断一帧数据是否结束,如果结束,会将接受标志置1且触发回调函数。
mcu_uart.h
/*
******************************************************************************************
*
example:
//建议在使用例程前调用mcu_Init_SYSCLOCK(48)初始化系统时钟,使用外部晶振
//否则误码率很大
void test_mcu_uart(void)
char str[200] = '\\0';
int length = 0;
mcu_uart uart0 = mcu_PortA,mcu_Pin10,mcu_Pin9,9600u,callback1;
mcu_uart uart1 = mcu_PortA,mcu_Pin3,mcu_Pin2,9600u;
// mcu_Init_SYSCLOCK(48);
mcu_Init_UART0(&uart0);
mcu_Init_UART1(&uart1);
while(1)
if(mcu_get_uart0_Rx_flag())
memset(str,0,200);
length = mcu_uart0_read(str);
mcu_uart0_send(str,length);
mcu_reset_uart0_Rx_flag();
printf("Jason\\n");
******************************************************************************************
*/
#ifndef __MCU_UART_H
#define __MCU_UART_H
/*
******************************************************************************************
* INCLUDES
******************************************************************************************
*/
#include "mcu_portpin.h"
/*
******************************************************************************************
* CONSTANT
******************************************************************************************
*/
/* 串口环形存储区的最大长度 */
#define RINGBUFF_LEN 200
/*
*******************************************************************************************
* TYPE DEFINITION
*******************************************************************************************
*/
typedef struct _mcu_uart
mcu_port_t uart_port; /* 串口端口 */
mcu_pin_t uart_rxpin; /* 串口接收引脚 */
mcu_pin_t uart_txpin; /* 串口发送引脚 */
unsigned int uart_bortrate; /* 串口波特率 */
void (*callback)(void); /* 接收帧结束回调函数 */
mcu_uart;
/*
*******************************************************************************************
* INTERFACES
*******************************************************************************************
*/
/* 串口0初始化 */
void mcu_Init_UART0(mcu_uart *uart);
/* 通过串口0发送数据 */
int mcu_uart0_send(char *buf,unsigned int length);
/* 通过串口0接收数据 */
int mcu_uart0_read(char *buf);
/* 获取接收帧标志 */
uint8_t mcu_get_uart0_Rx_flag(void);
/* 清除接收帧标志 */
void mcu_reset_uart0_Rx_flag(void);
/* 串口1初始化 */
void mcu_Init_UART1(mcu_uart *uart);
#endif
mcu_uart.c
/*
******************************************************************************************
* INCLUDES
******************************************************************************************
*/
#include "mcu_config.h"
#include "mcu_uart.h"
/**/
#include "uart.h"
#include "gpio.h"
#include "bt.h"
/*
******************************************************************************************
* CONSTANT
******************************************************************************************
*/
/*
*******************************************************************************************
* TYPE DEFINITION
*******************************************************************************************
*/
typedef struct
uint16_t Head;
uint16_t Tail;
uint16_t Length;
uint8_t Ring_Buff[RINGBUFF_LEN];
RingBuff_t;
/*
*******************************************************************************************
* LOCAL VARIABLE
*******************************************************************************************
*/
static RingBuff_t ringBuff;//创建一个ringBuff的缓冲区
static uint8_t uart0_Rx_start_flag= 0;
static uint8_t uart0_Rx_end_flag = 0;
static uint8_t uart0_Rx_flag = 0;
static void (*uart0_end_callback)(void) = NULL;
/*
*******************************************************************************************
* LOCAL FUNCTION DECLARATIONS
*******************************************************************************************
*/
static uint8_t Write_RingBuff(uint8_t data);
static uint8_t Read_RingBuff(uint8_t *rdata);
static void Uart0TimerIsr(void);
static void Uart0RxCallback(void);
static void mcu_Init_Timer0(void);
/*
*******************************************************************************************
* PUBLIC INTERFACES DECLARATIONS
*******************************************************************************************
*/
void mcu_Init_UART0(mcu_uart *uart);
void mcu_Init_UART1(mcu_uart *uart);
int mcu_uart0_send(char *buf,unsigned int length);
int mcu_uart0_read(char *buf);
uint8_t mcu_get_uart0_Rx_flag(void);
void mcu_reset_uart0_Rx_flag(void);
/*
*******************************************************************************************
* PUBLIC INTERFACES IMPLEMENTATIONS
*******************************************************************************************
*/
/*************************************************
Function: mcu_Init_UART0
Description: 串口0初始化(其内部初始化了一个定时器Timer0用于IDLE检测)
Calls: mcu_Init_Timer0
Called By: void
Input: uart: mcu_uart结构体指针(回调函数会在接收完一整帧数据后触发)
Output: void
Return: void
Other: 注意查看用户手册,波特率与系统时钟的误码率。此封装的函数只支持通用UART,
如要求其他诸如:奇偶校验,停止位等,请参照此处重新设计一个函数。
*************************************************/
void mcu_Init_UART0(mcu_uart *uart)
uint16_t u16Scnt = 0;//SCNT 为 16-Bit 波特率计数器 UARTx.SCNT 的计数值
uint32_t u32Baud = uart->uart_bortrate;//波特率
en_gpio_port_t enPort = (en_gpio_port_t)uart->uart_port;
en_gpio_pin_t enRxPin = (en_gpio_pin_t)uart->uart_rxpin;
en_gpio_pin_t enTxPin = (en_gpio_pin_t)uart->uart_txpin;
stc_uart_config_t stcConfig;//uart 总体配置
stc_uart_irq_cb_t stcUartIrqCb;//uart收发中断处理函数接口
stc_uart_multimode_t stcMulti;//uart多主机模式以及从机地址以及地址掩码配置
stc_uart_baud_t stcBaud;//uart波特率配置
stc_gpio_config_t stcGpioCfg;//GPIO 端口配置结构体
mcu_Init_Timer0();//* 初始化定时器,此处关键 *//
DDL_ZERO_STRUCT(stcGpioCfg);
DDL_ZERO_STRUCT(stcConfig);//清空结构体的内存
DDL_ZERO_STRUCT(stcUartIrqCb);
DDL_ZERO_STRUCT(stcMulti);
DDL_ZERO_STRUCT(stcBaud);
//使能基础时钟
Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio,TRUE);//使能GPIO模块时钟
//使能串口0 Uart0时钟
Sysctrl_SetPeripheralGate(SysctrlPeripheralUart0,TRUE);//使能uart0模块时钟
stcGpioCfg.enDir = GpioDirOut;//为输出
Gpio_Init(enPort,enTxPin,&stcGpioCfg);//为输出
Gpio_SetAfMode(enPort,enTxPin,GpioAf1);//TX
stcGpioCfg.enDir = GpioDirIn;
Gpio_Init(enPort,enRxPin,&stcGpioCfg);
Gpio_SetAfMode(enPort,enRxPin,GpioAf1);//RX
stcUartIrqCb.pfnRxIrqCb = Uart0RxCallback;//中断入口地址
stcConfig.pstcIrqCb = &stcUartIrqCb;//uart的所有的中断服务函数为stcUartIrqCb结构体
stcConfig.bTouchNvic = TRUE;//NVIC中断使能
if(NULL != uart->callback)
uart0_end_callback = uart->callback ;//* 回调赋值,此处关键 *//
if(TRUE == stcConfig.bTouchNvic)
EnableNvic(UART0_IRQn,IrqLevel1,TRUE);
stcConfig.enRunMode = UartMode1;//模式1
stcConfig.enStopBit = Uart1bit; //1bit停止位
stcMulti.enMulti_mode = UartNormal;//正常工作模式
Uart_SetMultiMode(UARTCH0,&stcMulti);//多主机单独配置
Uart_Init(UARTCH0, &stcConfig);//串口初始化
Uart_SetClkDiv(UARTCH0,Uart8Or16Div);//采样分频,模式1为8分频
stcBaud.u32Pclk = Sysctrl_GetPClkFreq();//获得外设时钟(PCLK)频率值
stcBaud.enRunMode = UartMode1;
stcBaud.u32Baud = u32Baud;
u16Scnt = Uart_CalScnt(UARTCH0,&stcBaud);//波特率计算,返回定时器配置值
Uart_SetBaud(UARTCH0,u16Scnt);//波特率设置
Uart_ClrStatus(UARTCH0,UartRC);//清接收请求
Uart_EnableIrq(UARTCH0,UartRxIrq);//使能串口中断
Uart_EnableFunc(UARTCH0,UartRx);//使能收发
/*************************************************
Function: mcu_Init_UART1
Description: 串口1初始化(只初始化了串口输出,用于printf调试)
Calls: mcu_Init_Timer0
Called By: void
Input: uart: mcu_uart结构体指针(回调函数无效)
Output: void
Return: void
Other: 注意查看用户手册,波特率与系统时钟的误码率。此封装的函数只支持通用UART,
如要求其他诸如:奇偶校验,停止位等,请参照此处重新设计一个函数。
*************************************************/
void mcu_Init_UART1(mcu_uart *uart)
uint16_t u16Scnt = 0;//SCNT 为 16-Bit 波特率计数器 UARTx.SCNT 的计数值
uint32_t u32Baud = uart->uart_bortrate;//波特率
en_gpio_port_t enPort = (en_gpio_port_t)uart->uart_port;
en_gpio_pin_t enRxPin = (en_gpio_pin_t)uart->uart_rxpin;
en_gpio_pin_t enTxPin = (en_gpio_pin_t)uart->uart_txpin;
stc_uart_config_t stcConfig;//uart 总体配置
stc_uart_irq_cb_t stcUartIrqCb;//uart收发中断处理函数接口
stc_uart_multimode_t stcMulti;//uart多主机模式以及从机地址以及地址掩码配置
stc_uart_baud_t stcBaud;//uart波特率配置
stc_gpio_config_t stcGpioCfg;//GPIO 端口配置结构体
DDL_ZERO_STRUCT(stcGpioCfg);
DDL_ZERO_STRUCT(stcConfig);//清空结构体的内存
DDL_ZERO_STRUCT(stcUartIrqCb);
DDL_ZERO_STRUCT(stcMulti);
DDL_ZERO_STRUCT(stcBaud);
//使能基础时钟
Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio,TRUE);//使能GPIO模块时钟
//使能串口1 Uart1时钟
Sysctrl_SetPeripheralGate(SysctrlPeripheralUart1,TRUE);//使能uart0模块时钟
stcGpioCfg.enDir = GpioDirOut;//为输出
Gpio_Init(enPort,enTxPin,&stcGpioCfg);//为输出
Gpio_SetAfMode(enPort,enTxPin,GpioAf1);//TX
stcGpioCfg.enDir = GpioDirIn;
Gpio_Init(enPort,enRxPin,&stcGpioCfg);
Gpio_SetAfMode(enPort,enRxPin,GpioAf1);//RX
// stcUartIrqCb.pfnRxIrqCb = UART1RxIntCallback;//中断入口地址
// stcUartIrqCb.pfnTxIrqCb = TxIntCallback;
// stcUartIrqCb.pfnRxFEIrqCb = ErrIntCallback;//接收帧错误中断服务函数
// stcUartIrqCb.pfnPEIrqCb = PErrIntCallBack;//CTS信号翻转中断服务函数
// stcUartIrqCb.pfnCtsIrqCb = CtsIntCallBack;//奇偶校验错误中断服务函数
stcConfig.pstcIrqCb = &stcUartIrqCb;//uart的所有的中断服务函数为stcUartIrqCb结构体
stcConfig.bTouchNvic = TRUE;//NVIC中断使能
if(TRUE == stcConfig.bTouchNvic)
EnableNvic(UART1_IRQn,IrqLevel3,TRUE);
stcConfig.enRunMode = UartMode1;//模式1
stcConfig.enStopBit = Uart1bit; //1bit停止位
stcMulti.enMulti_mode = UartNormal;//正常工作模式
Uart_SetMultiMode(UARTCH1,&stcMulti);//多主机单独配置
Uart_Init(UARTCH1, &stcConfig);//串口初始化
Uart_SetClkDiv(UARTCH1,Uart8Or16Div);//采样分频,<模式0无效,模式1/3为8分频,模式2为16分频
stcBaud.u32Pclk = Sysctrl_GetPClkFreq();//获得外设时钟(PCLK)频率值 //判断此处是否为24M
stcBaud.enRunMode = UartMode1;
stcBaud.u32Baud = u32Baud;
u16Scnt = Uart_CalScnt(UARTCH1,&stcBaud);//波特率计算,返回定时器配置值 //判断此处是否为26
Uart_SetBaud(UARTCH1,u16Scnt);//波特率设置
Uart_ClrStatus(UARTCH1,UartRC);//清接收请求
Uart_EnableIrq(UARTCH1,UartRxIrq);//使能串口中断
Uart_EnableFunc(UARTCH1,UartRx);//使能收发
/*************************************************
Function: mcu_uart0_send
Description: 通过串口0发送数据
Calls: Uart_SendData
Called By: void
Input:
buf: 字符串首地址指针
length: 字符串长度
Output: void
Return: 0成功,其他失败
Other:
*************************************************/
int mcu_uart0_send(char *buf,unsigned int length)
int i=0;
unsigned int len=length;
if(0 == len)
return 1;
for(i=0;i<len;i++)
Uart_SendData(UARTCH0,(uint8_t)buf[i]);
return 0;
/*************************************************
Function: mcu_uart0_read
Description: 通过串口0接收数据
Calls: Read_RingBuff
Called By: void
Input:
buf: 接收字符串首地址指针
Output:
buf: 接收字符串首地址指针
Return: 上一次接收的帧的长度
Other:
*************************************************/
int mcu_uart0_read(char *buf)
char *point=buf;
int lenght = ringBuff.Length;
while(1 != Read_RingBuff((uint8_t*)point))
point++;
return lenght;
/*************************************************
Function: mcu_get_uart0_Rx_flag
Description: 获取接收帧标志,判断uart0是否有串口消息在缓冲区中未处理
Calls: Read_RingBuff
Called By: void
Input:
Output:
Return: 0无串口消息,1有串口消息在缓冲区中未处理
Other: 当uart0接收完一帧时,uart0_Rx_flag置1,需要调用mcu_reset_uart0_Rx_flag()清零
*************************************************/
uint8_t mcu_get_uart0_Rx_flag(void)
return uart0_Rx_flag;
/*************************************************
Function: mcu_reset_uart0_Rx_flag
Description: 清除接收帧标志,处理完uart0中串口消息后可以清除标志
Calls: Read_RingBuff
Called By: void
Input:
Output:
Return:
Other: 与mcu_get_uart0_Rx_flag()一起调用
*************************************************/
void mcu_reset_uart0_Rx_flag(void)
uart0_Rx_flag = 0;
/*
*******************************************************************************************
* LOCAL FUNCTIONS IMPLEMENTATIONS
*******************************************************************************************
*/
/*************************************************
Function: Write_RingBuff
Description: 向环形缓冲区尾插入一个字节
Calls:
Called By: void
Input:
data:写入的一个字节
Output:
Return:
Other:
*************************************************/
static uint8_t Write_RingBuff(uint8_t data)
if(ringBuff.Length >= RINGBUFF_LEN)
return 1;
ringBuff.Ring_Buff[ringBuff.Tail] = data;
ringBuff.Tail = (ringBuff.Tail+1) % RINGBUFF_LEN;//防止越界非法访问
ringBuff.Length++;
return 0;
/*************************************************
Function: Read_RingBuff
Description: 从环形缓冲区头部读出一个字节
Calls:
Called By: void
Input:
rdata:读出的字节的存放地址
Output:
Return:
Other:
*************************************************/
static uint8_t Read_RingBuff(uint8_t *rdata)
if(0 == ringBuff.Length)
return 1;
*rdata = ringBuff.Ring_Buff[ringBuff.Head];
ringBuff.Head = (ringBuff.Head+1) % RINGBUFF_LEN;//防止越界非法访问
ringBuff.Length--;
return 0;
/*************************************************
Function: Uart0TimerIsr
Description: 硬件定时器Timer0的中断处理函数
Calls:
Called By: void
Input:
Output:
Return:
Other: Timer0专门用于UART0的IDLE检测,目前设定为,5ms没有收到数据,判定该帧结束
*************************************************/
static void Uart0TimerIsr(void)
static uint8_t count=0;
if(TRUE == Bt_GetIntFlag(TIM0,BtUevIrq))
if(1 == uart0_Rx_start_flag)/* 如果串口接收到了数据 */
if(0 == count)
uart0_Rx_end_flag = 1;/* 计时 */
count++;
if(count>5)//帧IDLE检测时间 单位(ms)
count = 0;
if(1 == uart0_Rx_end_flag)/* 如果一定时间间隔没有收到串口数据 */
uart0_Rx_start_flag = 0;
uart0_Rx_end_flag = 0;
uart0_Rx_flag = 1;/* 说明接收此帧数据结束 */
if(NULL != uart0_end_callback)
uart0_end_callback();
Bt_ClearIntFlag(TIM0,BtUevIrq); //清除中断标志
/*************************************************
Function: Uart0RxCallback
Description: UART0的接收回调函数
Calls:
Called By: void
Input:
Output:
Return:
Other: 当接收到一个字节的数据时,会触发此回调,将该字节写入RingBuff
*************************************************/
static void Uart0RxCallback(void)
uint8_t temp = Uart_ReceiveData(UARTCH0);
uart0_Rx_start_flag = 1;
uart0_Rx_end_flag = 0;
Write_RingBuff(temp);/* 接收到一个字节,写入回形缓存区 */
/*************************************************
Function: mcu_Init_Timer0
Description: 定时器0初始化
Calls:
Called By: void
Input:
Output:
Return:
Other: Timer0专门用于UART0的IDLE检测,每1ms触发一次
*************************************************/
static void mcu_Init_Timer0(void)
uint32_t TempValue;
uint16_t u16ArrValue;
uint16_t u16CntValue;
stc_bt_mode0_config_t Timer0;
DDL_ZERO_STRUCT(Timer0);
Sysctrl_SetPeripheralGate(SysctrlPeripheralBTim, TRUE); //Base Timer外设时钟使能
Timer0.enWorkMode = BtWorkMode0; //模式0,定时器模式
Timer0.enCT = BtTimer; //定时器功能
Timer0.pfnTim0Cb = Uart0TimerIsr; //中断回调
Timer0.enCntMode = Bt16bitArrMode; //自动重载16位计数器/定时器
Timer0.enPRS = BtPCLKDiv1; //不分频
Timer0.bEnGate = BtGatePositive;
Timer0.bEnTog = FALSE;
Timer0.enGateP = BtGatePositive;
Bt_Mode0_Init(TIM0,&Timer0);
TempValue=MCU_FREQUENCY; //系统频率
// TempValue/=64; //分频系数
TempValue=TempValue/1000; //定时器触发时间为1ms=0.001s
TempValue=0x10000-TempValue; //装载值
u16ArrValue = (uint16_t)TempValue;
u16CntValue = (uint16_t)TempValue;
// Value = u16CntValue;
Bt_M0_ARRSet(TIM0,u16ArrValue);
Bt_M0_Cnt16Set(TIM0,u16CntValue);
Bt_ClearIntFlag(TIM0,BtUevIrq); //清除中断标志
Bt_Mode0_EnableIrq(TIM0); //使能中断
EnableNvic(TIM0_IRQn, IrqLevel3, TRUE);
Bt_M0_Run(TIM0);
/*************************************************
Function: fputc
Description: printf函数重定向到UART1,用于调试
Calls:
Called By: void
Input:
Output:
Return:
Other:
*************************************************/
int fputc(int ch, FILE *f)
Uart_SendData(UARTCH1, (uint8_t) ch);
// while (Uart_GetStatus(UARTCH1, UartTC) == TRUE);
return ch;
样例
MCU通过串口uart0接收到一帧数据,再将原数据通过发送uart0发送出去。
#include "mcu_sysclock.h"
#include "mcu_uart.h"
#include <stdio.h>
#include <string.h>
void callback1(void)
printf("callback\\n");
void test_mcu_uart(void)
char str[200] = '\\0';
char str1[10]= "\\nhello!\\n";
int length = 0;
mcu_uart uart0 = mcu_PortA,mcu_Pin10,mcu_Pin9,9600u,callback1;
mcu_uart uart1 = mcu_PortA,mcu_Pin3,mcu_Pin2,9600u以上是关于[嵌入式开发模块]单片机串口模块:串口+定时器+环形缓冲区 实现无串口IDLE中断接收不定长串口数据的主要内容,如果未能解决你的问题,请参考以下文章