[嵌入式开发模块]单片机串口模块:串口+定时器+环形缓冲区 实现无串口IDLE中断接收不定长串口数据

前言

本周看了些代码模块化和代码框架抽象分层的知识,现在尝试将手里代码重新整理成模块,方便以后业务开发。现在摸索了两天,在网上看了些别人的文章和代码,初步整理好了第一版(2019.12.6)。

MCU: 华大的MCU HC32F030K8TA,其内核是Cortex-M0+,8KRAM,64KROM。
编程环境:keil5

模块

分析介绍

这款MCU的性能一般,功能较少,目前想要让其外接一个通信模块进行联网,通过串口指令控制配置通信模块,之前使用它接收串口数据都是有协议指定帧头和帧尾,连上通信模块以后,模块发送的串口数据可能没有固定帧尾,在网上查了些资料,很多都是用stm32的串口IDLE中断功能实现的,但是这款MCU没有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 
#include 
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};
	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_uart0_send(str1,8);
			mcu_reset_uart0_Rx_flag();
			printf("Length is %d\n",length); 
		}
	}
}

int main()
{
	test_mcu_uart();
	return 0;
}

现象

在PC端接uart0发送后的结果
[嵌入式开发模块]单片机串口模块:串口+定时器+环形缓冲区 实现无串口IDLE中断接收不定长串口数据_第1张图片
PC端通过uart1接收结果
[嵌入式开发模块]单片机串口模块:串口+定时器+环形缓冲区 实现无串口IDLE中断接收不定长串口数据_第2张图片

你可能感兴趣的:(单片机)