技术交流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.
可以看到,启动后,程序会提示输入数据,然后输入以后点击发送
接下来,屏幕就会显示发送的数据.