完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
相关推荐
1个回答
|
|
一、什么是USART
1. USART简介 USART(Universal Synchronous/Asynchronous Receiver/Transmitter)的全称为通用同步/异步串行接收/发送器,它与普通串口UART的不同在于,USART有同步、异步两种工作模式;而UART是经过裁剪后的USART,只有异步工作模式。 2. STM32F4中的USART 2.1 USART的发送/接收引脚 STM32F4中有两个USART(USART1、USART2),其中,我们以USART1为例,它的发送、接收与PA9、PA10引脚相连,从如下的GPIO引脚复用图中可以看出,PA9可以复用为USART1的发送(TX)功能,而PA10可以复用为USART1的接收(RX)功能。 2.2 USART转为USB接口 单片机通常需要与电脑互相传输数据,但是电脑没有USART接口,这该怎么办呢?设计者通常通过一个芯片把USART接口转换为USB接口,这样就可以与电脑通信了。 能实现USART转USB的芯片有很多,STM32F4中使用的是CH340G。 STM32F4中USART1转USB的原理图如下所示: 其中,TXD/RXD 是相对于 CH340G 来说的,也就是 USB 串口的发送和接受脚。而 USART1_RX/USART1_TX 则是相对于 STM32F407ZGT6 来说的。这样,通过对接,就可以实现 USB 串口和 STM32F407ZGT6 的串口通信了。 注: 在本文的实验中,就是用USB串口把USB信号转换为串口信号,进而通过PA9和PA10的复用功能来与单片机进行通信。 二、常用的串口相关寄存器 常用的串口相关寄存器有三个:
在上篇博文【STM32F4】四、串口通信1——硬件部分中我们在第三部分列出过,下面我们只把波特率发生器的硬件部分展示在下图中: 首先由USART_BRR寄存器产生初始的时钟信号,假设频率为f;输入到分频系数为USARTDIV的分频器后,输出信号频率变为** f / USARTDIV**;在经过采样除法器后,最终输出信号的波特率为** f / USARTDIV / [8 x (2 - OVER8)] **,其中,OVER8可人为设置。 其中,初始频率 f 通常是固定的,OVER8 通常设为0;那么公式就简化为波特率 = f / USARTDIV / 16。而为了得到最终的波特率,我们要求的其实就是唯一的可变参数USARTDIV,实际上它也是由USART_BRR寄存器决定的。 但在程序中,我们不需自己计算USART_BRR的配置。我们只要把想要的波特率(如115200)直接写入程序、传给相应函数即可,STM32F4提供的库函数会帮我们计算并对USART_BRR进行配置。 三、程序编写 1. 串口配置的一般步骤 根据正点原子的课程,列出串口配置(带中断响应)的一般过程如下: ① 必要的时钟使能下面我们也将按照上述步骤,一一编写程序。 2. 编写程序 下面我们把经过详细注释的代码放上来,全都是按照上述九个步骤来写的: #include "stm32f4xx.h" #include "usart.h" #include "delay.h" void My_USART1_Init(void) //配置和初始化的程序,除中断处理函数外,其他的配置都在这里面 { GPIO_InitTypeDef GPIO_InitStructure; //用于GPIO配置的结构体 USART_InitTypeDef* USART_InitStruct; //用于USART配置的结构体 NVIC_InitTypeDef* NVIC_InitStruct; //用于NVIC配置的结构体 //===============================一、串口时钟使能=================================== //使能USART1,由于USART1挂载在APB2总线下,所以要去RCC相关的库函数中搜索APB2的时钟使能函数 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //=================================GPIO时钟使能===================================== //因为要通过PA9和PA10的复用功能来使用UART1,所以也要使能GPIOA的时钟,GPIOA挂载在AHB1总线下 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); //===============================二、引脚复用映射=================================== //通过下面这个函数,把PA9配置为复用功能——USART1_TX,PA10配置为复用功能——USART1_RX GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1); GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1); //=============================三、端口模式设置================================== //下面要配置PA9和PA10配置为复用模式 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //GPIO_Mode_OUT; //AF即复用模式 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOA, &GPIO_InitStructure); //================================四、串口参数初始化================================= //初始化USART1的配置 USART_InitStruct->USART_BaudRate = 115200; //波特率 USART_InitStruct->USART_HardwareFlowControl = USART_HardwareFlowControl_None;//不使用硬件流控制 USART_InitStruct->USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //把发送和接收功能都进行使能 USART_InitStruct->USART_Parity = USART_Parity_No;//不使用奇偶校验 USART_InitStruct->USART_StopBits = USART_StopBits_1;//使用1个停止位 USART_InitStruct->USART_WordLength = USART_WordLength_8b;//因为没有奇偶检验,所以可以使用8位字长 USART_Init(USART1, USART_InitStruct); //==================================================================== //=============如果不使用中断,那么这个程序到这里就可以结束了============= //==================================================================== //=============================五、开启中断并且初始化NVIC============================ //配置NVIC //首先设置中断优先级分组 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //再初始化NVIC NVIC_InitStruct->NVIC_IRQChannel = USART1_IRQn;//不同的通道定义在顶层头文件stm32f4xx.h中 //设置NVIC通道为USART1的通道 NVIC_InitStruct->NVIC_IRQChannelCmd = ENABLE; NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority = 1;//设置抢占优先级为1 NVIC_InitStruct->NVIC_IRQChannelSubPriority = 1;//设置响应优先级为1 NVIC_Init(NVIC_InitStruct); //==================================六、使能串口==================================== //使能USART1 USART_Cmd(USART1, ENABLE); //使能USART1的某种中断 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//把接收非空中断USART_IT_RXNE使能,即一旦接收到了信息,就引发中断,且执行相应的中断函数 //下一步就要在下面定义USART1的中断服务函数,函数名是固定的,在官方的系统系统文件中startup_stm32f40_41xxx.s已经给出:USART1_IRQHandler } //================================七、编写中断处理函数=============================== void USART1_IRQHandler(void) { u8 res; //用来记录接收到的数据,因为我们在上面设置的8位字长代表一个数据,所以这里可以用u8来记录? //==============================九、串口传输状态获取================================= if(USART_GetITStatus(USART1, USART_IT_RXNE)) //读取USART_IT_RXNE标志位的状态) { //================================八、串口数据收发================================== res = USART_ReceiveData(USART1); //读取接收到的数据 USART_SendData(USART1, res); //把接收到的数据发出去 } } //主函数 int main(void) { My_USART1_Init(); while(1); //在这里无限循环即可,程序会自动执行 中断 和 中断处理函数 return 0; } 注: 如果编译时程序出错,可能是因为在官方库文件usart.c里已经定义过一个中断处理函数USART1_IRQHandler(),把它注释掉或者把这个文件删掉即可。 四、更复杂的程序(USART_RX_STA寄存器的应用) 我们在上面第三部分写的程序中,在中断处理函数里我们只是简单地把每个接收到的字符发送出去,没有做其他处理。但是,这往往不能满足工程师们的需求。 工程师们在使用串口时,通常要求在写完一段话后才进行发送(而不是每收到一个字符就发送出去),而且一定要有一个结束标志(如回车键Enter),当遇到结束标志时才进行发送,而不是像第三部分中机械地每收到一个字符就发送一个。 这就要求在以上程序的基础上,加入如下功能:
1. USART_RX_STA 寄存器简介 USART_RX_STA寄存器共有16位,即0 ~ 15,每个位的作用如下图所示: (下图源自正点原子《STM32F4开发指南——库函数版本》 5.3.3小节——USART1_IRQHandler 函数) 其中,0 ~ 13位用来表示接收到的有效数据个数,由于其有14个二进制位,因此最大的可保存数据为 2 ^14 -1,也就是说我们一段话最多可以包含 2 ^14 -1 个字符; 第 14 位用来表示是否接收到了某个标志符(图中的 0X0D 表示回车符,也就是说当接收到回车符时,要把第** 14** 位置 1),我们通常将回车符或者是回车符和某个字符的组合设为结束标志。 第 15 位用来表示是否接收完成,也就是要根据我们人为设计的结束标志(如回车+ c) 来判断,如果遇到了结束标志,则需要把这一位置1,对接收的信息处理完成后再把这一位置0。 2. 程序编写 2.1 程序思路 从上面的描述中可以看出,要使用USART_RX_STA寄存器,我们需要始终对 此寄存器的几个标志位 及 串口接收到的字符 进行监控和更新。 现在假设我们把标志为设为 ** 0X0D + 0X0A**,即回车键 + 换行键(其实只要在键盘上按下一次回车键就够了),那么我们要做的监控和更新操作如下:
上述所有工作,都与我们的初始化函数My_USART1_Init没有关系,初始化只是一个初始化功能,没有监督和更新的作用; 而上述所有监督和更新的工作,我们都在中断处理函数和主(main)函数中完成,如下,先展示出中断处理函数: 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) //根据USART_IT_RXNE标志位来确认是否接收到了新字符 { Res =USART_ReceiveData(USART1);//(USART1->DR); //读取接收到的新字符 if((USART_RX_STA&0x8000)==0)//USART_RX_STA&0x8000,即读取USART_RX_STA的第15位(1000 0000 0000 0000,其实是第16位,但是用 0 ~ 15 来编号的话,我们把它叫做第15位) { if(USART_RX_STA&0x4000)//USART_RX_STA&0x4000,即读取USART_RX_STA的第14位,判断上一次接收到的字符是否是0x0d { if(Res!=0x0a)USART_RX_STA=0;//如果上一次是0x0d,但这次不是0x0a,则表示接收错误,USART_RX_STA全部清0,重新开始 else USART_RX_STA|=0x8000; //如果上一次是0x0d,且这一次是0x0a,表示接收到了结束标志位,当前的句子接收完成,把第15位置1 } else //还没收到0X0D { if(Res==0x0d)USART_RX_STA|=0x4000;//如果这次接收到的是0x0d,则把USART_RX_STA的第14位置1 else { USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;//如果之前没有接收到0x0d,且这一个接收到的字符也不是0x0d,则表示当前这个字符是普通消息,则把它存在buffer(USART_RX_BUF)的第USART_RX_STA&0X3FFF中。其中,USART_RX_STA&0X3FFF是我们当前记录的字符个数,即USART_RX_STA的0 ~ 13位(0011 1111 1111 1111) USART_RX_STA++; //当没有接收到0x0d时,USART_RX_STA的第14、15位都是0,只有前13位有数据,用以存储接收到的字符个数,因此这里直接把USART_RX_STA加1,用来记录字符个数,且不会影响到14、15位(因为它们都是0) if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//如果接收到的字符个数比可接收的最多数量USART_REC_LEN多了,则表示溢出、接收错误,重新开始接收 } } } } #if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS. OSIntExit(); #endif } #endif 从以上程序中可以看到,中断处理函数主要做了监督并更新第14、15位,以及保存字符、且监督并更新 0 ~ 13 位。 另外,关于程序中的#if SYSTEM_SUPPORT_OS OS,我在这篇帖子中找到了相关的解释: 我感觉它的作用是保护传输数据不被打断。那么,主(main)函数中需要做什么工作呢? int main(void) { u8 t; u8 len; u16 times=0; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2 delay_init(168); //延时初始化 uart_init(115200); //串口初始化波特率为115200 LED_Init(); //初始化与LED连接的硬件接口 while(1) { if(USART_RX_STA&0x8000) //若检测到USART_RX_STA的第15位、即结束标志位为1,则表示当前信息传输结束 { len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度 printf("rn您发送的消息为:rn"); //把这句话通过串口打印到屏幕上 for(t=0;t USART_SendData(USART1, USART_RX_BUF[t]); //向串口1发送数据 while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束 } printf("rnrn");//插入换行 USART_RX_STA=0; //把当前信息处理完后,将USART_RX_STA清0,一切重新开始 }else //如果结束标志位为0.则表示当前信息传输还未结束 { times++; if(times%5000==0) { //打印信息以提示工程师继续输入信息 printf("rnALIENTEK 探索者STM32F407开发板 串口实验rn"); printf("正点原子@ALIENTEKrnrnrn"); } if(times%200==0)printf("请输入数据,以回车键结束rn"); if(times%30==0)LED0=!LED0;//闪烁LED,提示系统正在运行. delay_ms(10); } } } 整个主函数中,最主要的就是while(1)循环,它负责监督USART_RX_STA寄存器的结束标志位,如果结束标志位被置1,则表示当前信息传输结束,主函数就把从串口USART1接收到的信息再通过USART1发送出去; 如果当前USART_RX_STA的结束标志位不是1,则表示传输还未结束,主函数则打印提示信息,以提示工程师继续输入信息。 |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1617 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1543 浏览 1 评论
977 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
683 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1595 浏览 2 评论
1863浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
644浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
515浏览 3评论
531浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
504浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-22 04:47 , Processed in 0.871793 second(s), Total 77, Slave 60 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号