STM32工作笔记0054---串口通信实验讲解--自定义发送接收数据协议

 技术交流QQ群【JAVA,C++,Python,.NET,BigData,AI】:170933152

来看一下这个代码后面看UART.C

先回顾上节课的串口配置步骤

首先看


void uart_init(u32 bound){

//1.首先可以看到这里的uart_init(u32 bound){}
//可以看到这里参数以个波特率

usart.h这个文件中定义了

可以看到extern这个,指定了这个变量,在其他文件中也可能用的到.

和这个extern在c语言的时候,提到过,意思是,这个字指定的变量其实是,在外部定义的,也就是别的地方定义的,

这里仅仅是一个声明,比如这个额USART_RX_BUF这个变量

可以看到在.h文件中声明的,但是在

.c文件中进行定义的

u8 USART_RX_BUF 这个变量.

然后看一下个函数,可以看到跟上一讲的基本上是一样的,只不过这个中断函数,写的不一样.

#include "sys.h"
#include "usart.h"	  
// 	 
//如果使用ucos,则包括下面的头文件即可.
#if SYSTEM_SUPPORT_OS
#include "includes.h"					//ucos 使用	  
#endif
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32开发板
//串口1初始化		   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2012/8/18
//版本:V1.5
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved
//********************************************************************************
//V1.3修改说明 
//支持适应不同频率下的串口波特率设置.
//加入了对printf的支持
//增加了串口接收命令功能.
//修正了printf第一个字符丢失的bug
//V1.4修改说明
//1,修改串口初始化IO的bug
//2,修改了USART_RX_STA,使得串口最大接收字节数为2的14次方
//3,增加了USART_REC_LEN,用于定义串口最大允许接收的字节数(不大于2的14次方)
//4,修改了EN_USART1_RX的使能方式
//V1.5修改说明
//1,增加了对UCOSII的支持
// 	  
 

//
//加入以下代码,支持printf函数,而不需要选择use MicroLIB	  
#if 1
#pragma import(__use_no_semihosting)             
//标准库需要的支持函数                 
struct __FILE 
{ 
	int handle; 

}; 

FILE __stdout;       
//定义_sys_exit()以避免使用半主机模式    
void _sys_exit(int x) 
{ 
	x = x; 
} 
//重定义fputc函数 
int fputc(int ch, FILE *f)
{      
	while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
    USART1->DR = (u8) ch;      
	return ch;
}
#endif 

/*使用microLib的方法*/
 /* 
int fputc(int ch, FILE *f)
{
	USART_SendData(USART1, (uint8_t) ch);

	while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) {}	
   
    return ch;
}
int GetKey (void)  { 

    while (!(USART1->SR & USART_FLAG_RXNE));

    return ((int)(USART1->DR & 0x1FF));
}
*/
 
#if EN_USART1_RX   //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误   	
u8 USART_RX_BUF[USART_REC_LEN];     //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15,	接收完成标志
//bit14,	接收到0x0d
//bit13~0,	接收到的有效字节数目
u16 USART_RX_STA=0;       //接收状态标记	  
  
void uart_init(u32 bound){
  //GPIO端口设置
  GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);	//使能USART1,GPIOA时钟
  
	//USART1_TX   GPIOA.9
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
   
  //USART1_RX	  GPIOA.10初始化
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10  

  //Usart1 NVIC 配置
  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器
  
   //USART 初始化设置

	USART_InitStructure.USART_BaudRate = bound;//串口波特率
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式

  USART_Init(USART1, &USART_InitStructure); //初始化串口1
  USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
  USART_Cmd(USART1, ENABLE);                    //使能串口1 

}

void USART1_IRQHandler(void)                	//串口1中断服务程序
	{
	u8 Res;
#if SYSTEM_SUPPORT_OS 		//如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
	OSIntEnter();    
#endif
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
		{
		Res =USART_ReceiveData(USART1);	//读取接收到的数据
		
		if((USART_RX_STA&0x8000)==0)//接收未完成
			{
			if(USART_RX_STA&0x4000)//接收到了0x0d
				{
				if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
				else USART_RX_STA|=0x8000;	//接收完成了 
				}
			else //还没收到0X0D
				{	
				if(Res==0x0d)USART_RX_STA|=0x4000;
				else
					{
					USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
					USART_RX_STA++;
					if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收	  
					}		 
				}
			}   		 
     } 
#if SYSTEM_SUPPORT_OS 	//如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
	OSIntExit();  											 
#endif
} 
#endif	

可以看到这里的中断程序写的跟上次写的有点不一样,那么

这个中断函数,实际上实现了一个小的协议,这个协议是什么意思呢?

他实现了,比如这里首先定义了USART_REC_LEN 200意思就是,我一次接收数据的个数是200个字节.

然后

还定义了一个USART_RX_BUF 一个缓存区,里面是一个长度.

然后又定义了一个USART_RX_STA;存储数据的状态.这个意思是

首先这个USART_RX_STA这个的bit13--0,这个14个位,用来存接收到的数据的个数,比如接收了100个有效的数据位,那么这里就存入100

然后bit14这个是0x0D这个就是asii码的值,指的是回车符号,那么意思是,我读取到这个回车,那就是说明

一次数据的发送要结束了,然后继续看,bit15,他存了0X0A,也就是说我一读取到这个0X0A那么就相当于我知道了

一次数据就要开始发送了,也就是,要开始在0到14位上,每接收到一个数据位就+1

这个中断函数,封装了这样的一个小协议.

下面举个例子再说一下这个实现的协议:

他是这样的比如这里首先接收到0X0A的时候,那么0到14位就开始+1,每接收到一个数据位就+1,然后知道收到0X0D的时候

那么就会把第bit14位设置为1,表示这次接收完成了,那么接着再接收到0x0A的时候,就会把bit15设置为1,这个接收完成标志,

设置为1以后,就相当于这次的接收已经完成了,然后同时把数据会放到缓存里面去.

再看一个具体的例子:

这里比如我首先碰到0X0A以后,就开始给0到14位+1,随着接收,比如接收了50个数据位了,然后

这个时候我收到一个0X0D标志位,那么会把0X0D这个bit14位置1,注意读取到0X0D的时候

这个bit13到bit0,他是不会增加的,然后继续读取,会读取到0X0A,这个时候就会把

bit15这个接收完成标志位设置为1,然后咱们的程序会实时的分析,这个USART_RX_STA这个变量的,

各个位.

读取到bit15,bit14都是1的时候,那么就会去看一下这个时候bit13--0,这几位存了多少个数据位了,比如说是50位

那么这个时候,就会把上面的BUF中的前50位数据拿出来,然后发送出去.

然后再把这里的接收完成标志位,接收0X0D标志位,这个都设置位0

这个就是这个协议的过程.

接下来看看代码是怎么实现的?

接下来一行一行理解一下

void USART1_IRQHandler(void)                	//串口1中断服务程序
	{
	u8 Res;
#if SYSTEM_SUPPORT_OS 		//如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
	OSIntEnter();    
#endif
//1.上面是OS的不用看,可以看到他首先也是
//先判断这个串口是不是被设置为接收模式了
//也就是这里先判断这个中断是不是接收中断
//如果是接收中断,那么就是说明接收到了数据就继续走
//
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
		{
        //2.然后接收到数据以后就是读取数据
        //注意读取的是本次的数据,读取数据以后赋值给一个变量
        //Res.
		Res =USART_ReceiveData(USART1);	//读取接收到的数据
		
        //3.如果这里的最高位,也就是前面说的bit15,接收
        //完成标志位,如果接收完成标志位是0,也就是接收未完成的时候
        //如果这里是1的话,那么说明,这次的接收已经完成了,但是呢
        //数据还没有被发出去,被放到BUF中,如果放到BUF中并且发送出去后,
        //对应的标志位都会被清0的,这里没有清0说明,数据还没有被
        //发送出去.这个时候就不需要做任何操作,等数据获取完发出去就可以了
        //可以看到else也没有写对吧.
		if((USART_RX_STA&0x8000)==0)//接收未完成
			{
        //4.接收没有完成的话,再判断,有没有收到
        //这个是第14位的话,是判断收到的数据是不是是0x0d,
        //如果是的话,说明收到的是个回车.
        //这里还要注意,这里判断的是上一次接收的是不是0x0d
        //也就是说上一次如果是接收的是0x0d的话
			if(USART_RX_STA&0x4000)//接收到了0x0d
				{
                //5.那么这里再判断,这次收到的数据是不是0x0a
                //也就是,因为0x0d是回车,也就是这次发送完了,
                //接下来应该接收一个0x0a
                //如果这里接收到的不是0x0a的话,那么这说明接收错误,需要重新接收
                //数据出了错误就把USART_RX_STA设置为0
				if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
                //6.如果接收的是0x0a这个接收完成标志的话
                //那么说明0x0d是1,0x0a也是1,也就说明数据接收完成了.
                //数据接收完成了以后.
                //就把USART_RX_STA设置为0x8000也就是接收完成标志位是1
                //表示接收完成了.
				else USART_RX_STA|=0x8000;	//接收完成了 
				}
			else //还没收到0X0D
				{
                //7.这个说明上一次接收到的不是0X0D
                //然后再判断这一次接收到的是不是0X0D
                //如果这次接收到的是0x0d,那么需要把
                //bit14,也就是0x0d,这个标志位设置为1	
				if(Res==0x0d)USART_RX_STA|=0x4000;
				else
					{
                //8.如果这次接收到的不是0x0d,那说明
                //接收的还是数据,就需要把数据存入到buf中去
                //那么存入到BUF的哪个下标下呢?
                //USART_RX_STA&0X3FFF 用这个就可以了
                //这里与上3FFF 是因为0到13位是用来表示接收的数据个数的
                //这样,因为后面是USART_RX_STA++ 所以会一个一个下标来获取
					USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
                //9.接收了数据以后,注意下标+1,这个是从低位开始加的,所以不用管.
					USART_RX_STA++;
                //10.那么这里如果判断,已经接收的数据的个数,大于咱们定义的
                //BUF的长度的话,那么这个时候USART_RX_STA设置为0也就是数据过多的时候
                //也显示数据有错误,就可以了,因为这样就超出定义的数据的下标了.
					if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收	  
					}		 
				}
			}   		 
     } 
#if SYSTEM_SUPPORT_OS 	//如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
	OSIntExit();  											 
#endif
} 
#endif

再来看一下main.c

#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "usart.h"

/************************************************
 ALIENTEK精英STM32开发板实验4
 串口 实验   
 技术支持:www.openedv.com
 淘宝店铺:http://eboard.taobao.com 
 关注微信公众平台微信号:"正点原子",免费获取STM32资料。
 广州市星翼电子科技有限公司  
 作者:正点原子 @ALIENTEK
************************************************/
int main(void)
{
	u16 t;
	u16 len;
	u16 times = 0;
	delay_init();									//延时函数初始化

    //1.首先这里是配置中断优先级分组
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
	uart_init(115200);								//串口初始化为115200
	LED_Init();										//LED端口初始化
	KEY_Init();										//初始化与按键连接的硬件接口
	while (1)
	{
       //2.先判断是否已经接收完成,也就是最高位是否是1
		if (USART_RX_STA & 0x8000)
		{
            //3.是的话,获取到接收的数据长度
			len = USART_RX_STA & 0x3fff; //得到此次接收到的数据长度
			printf("\r\n您发送的消息为:\r\n\r\n");
			for (t = 0; t < len; t++)
			{
                //4.然后循环发送一位一位的发送
				USART_SendData(USART1, USART_RX_BUF[t]); //向串口1发送数据
                //5.等待本次发送结束.也就是这一位数据发送结束,然后再发送下一位
				while (USART_GetFlagStatus(USART1, USART_FLAG_TC) != SET)
					; //等待发送结束
			}

			printf("\r\n\r\n"); //插入换行

            //6.发送完成以后,这个标志位设置为0,所有的标志位都设置为0
			USART_RX_STA = 0;
		}
		else
		{
            //7.数据还没有接收完成的情况,数据继续接收
            //就times++数据
			times++;
			if (times % 5000 == 0)
			{
               //8.每5000次,这里打印一次商标
               //
				printf("\r\n精英STM32开发板 串口实验\r\n");
				printf("正点原子@ALIENTEK\r\n\r\n");
			}
            //9.每200次,就是接收满了一个buf的数据了,就提示要再输入数据了
			if (times % 200 == 0)
				printf("请输入数据,以回车键结束\n");
			if (times % 30 == 0)
            //10.每30次就闪烁一下LED灯
				LED0 = !LED0; //闪烁LED,提示系统正在运行.
			delay_ms(10);
		}
	}
}

然后编译一下,来调试一下.

刚开始的时候开发版没有反应

然后打开xocm

这里下载代码到开发版以后,可以看到灯就再闪烁了.

然后xcom,需要勾选上发送新行.

这样,数据发送完以后,会自动的给数据添加一个回车0x0d.

可以看到,启动后,程序会提示输入数据,然后输入以后点击发送

接下来,屏幕就会显示发送的数据.

猜你喜欢

转载自blog.csdn.net/lidew521/article/details/108220300