完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
相关推荐
1个回答
|
|
在写串口通信前阅读了STM32中文参考手册,然后满心澎湃地写代码。在这个过程中遇一些让人郁闷的事情,目前这些问题目前已经解决了(嘿嘿嘿),特此来总结一番。串口的使用步骤大概如下(51单片机、STM32、QT或VS编写PC串口上位机都是如此)
1、初始化串口参数(波特率、数据位、停止位、校验位、流控制、开启接收/发送) 2、配置串口中断 3、数据传输 那么STM32怎么使用串口呢?上面已经说了嘛,所以按照以上步骤即可。可是由于STM32的引脚是可以复用的,我们还需要设置串口通信引脚(GPIO)的工作方式,然后再设置串口参数、串口中断。 下文函数基于STM32固件库V3.5,以STM32F103RC的串口1的使用为例。 一、串口的初始化和中断设置 1、初始化GPIO: 根据手册的8.1.11节,我们可以找到下表: 在全双工的模式下,发送引脚需要设置为推挽复用输出,接收引脚则设置为浮空输入或带上拉的输入。因为一般不用同步和流量控制的方式,所以CK、RST、CTS引脚不作配置。当然啦,在使用STM32外设的时候不要忘记打开外设时钟(GPIO和USART的RCC)。 GPIO_InitTypeDef GPIO_InitStructure; //开启串口和GPIO的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); //配置发送引脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //发送引脚设置为推挽复用 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); //配置接收引脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //接收引脚设置为浮空输入 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); 2、配置串口参数 有专门用于初始化串口的库函数(USART_Init)和对应的结构体(USART_InitTypeDef),好像每个外设都有这样的配套,具体内容可参看《STM32F10xxx固件库_3.xx.pdf》。 USART_InitTypeDef USART_InitStructure; //波特率 USART_InitStructure.USART_BaudRate = 9600; //数据长度 USART_InitStructure.USART_WordLength = USART_WordLength_8b; //停止位 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; //初始化串口1 USART_Init(USART1, &USART_InitStructure); 3、中断配置 在使用STM32的中断前,要对NVIC中断控制器进行配置,设置中断的优先级。 //配置中断优先级 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); 4、使能串口及串口中断 注意1:初始化时不要随意打开TXE中断!只要TX-DR寄存器为空,TX和TXE标志都会马上被置位而立即会产生中断(参考《STM32中文参考手册》的25.3.2节),即使中断标志被清除,也会被重新置位。因此,我采用的是TC中断而不是采用TXE中断。 注意2:不要采用在一个中断配置函数中同时打开两个中断!例如:USART_ITConfig(USART1, USART_IT_TC | USART_IT_RXNE, ENABLE); 咋眼一看,明明只打开TC中断和RX中断,然而却会同时把TXE中断也打开。 //串口1使能 USART_Cmd(USART1, ENABLE); //清除接收中断标记 USART_ClearITPendingBit(USART1, USART_IT_RXNE); //清除发送完成中断标记 USART_ClearITPendingBit(USART1, USART_IT_TC); //打开串口1发送完中断 USART_ITConfig(USART1, USART_IT_TC, ENABLE); //打开串口1接收中断 两个中断不能在一个函数中同时打开!!!太坑了T_T USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); 这样,串口1配置好了。但代码一运行就会发现不妥!为什么每次初始化完成就马上进入中断了呢???遇到这种现象千万不要大惊小怪,我很淡(dan)定(teng)地做了个实验,发现处理器复位后,串口的SR寄存器中的TC标志会被置位。而根《STM32中文参考手册》25.3.2节,在串口使能后会自动发送一个空闲帧,发送完毕后TC也会置位,所以初始化将导致串口初始化完毕后马上进入TC中断。为了避免这种情况,可以在串口使能后等待空闲帧发送完毕,再打开TC中断。 具体看下面完整的初始化代码: //配置串口1 void USART1_Config() { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); //配置发送引脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //发送引脚设置为推挽复用 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); //配置接收引脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //接收引脚设置为浮空输入 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); //波特率 USART_InitStructure.USART_BaudRate = 9600; //数据长度 USART_InitStructure.USART_WordLength = USART_WordLength_8b; //停止位 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; //初始化串口1 USART_Init(USART1, &USART_InitStructure); //USART1->SR寄存器复位后TC位为1,在此清零 USART_ClearFlag(USART1, USART_FLAG_TC); //串口1使能 USART_Cmd(USART1, ENABLE); //使能后串口发送一个空闲帧,等待空闲帧发送完毕后将TC标记位清零 while(USART_GetFlagStatus(USART1, USART_FLAG_TC) != SET); //否则开启TC中断后会马上中断 USART_ClearFlag(USART1, USART_FLAG_TC); //配置中断优先级 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); /*************************************************************** 注意:初始化时不要随意打开TXE中断, 因为只要TX-DR寄存器为空,TX和TXE都会马上被置位而立即会产生中断 ***************************************************************/ //清除接收中断标记 USART_ClearITPendingBit(USART1, USART_IT_RXNE); //清除发送完成中断标记 USART_ClearITPendingBit(USART1, USART_IT_TC); //打开串口1发送完中断 USART_ITConfig(USART1, USART_IT_TC, ENABLE); //打开串口1接收中断 两个中断不能在一个函数中同时打开!!!太坑了T_T USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); } 二、数据的发送和接收(注:以下代码有bug,博主至今还未找到原因T-T,仅供思路以参考!) 为了提高代码的效率,我使用基于环形缓冲的串口通信方式。 发送数据原理:把要发送的数据全部加入到缓冲区中,让处理器开始发送。一个数据发送结束后,即会产生TC中断,此时在中断服务程序中发送下一个数据。像吃饭看电视,在夹菜(发数据)的时候才要把注意力放到菜盘子上,嚼饭的时候(数据发送中)可以看电视,在开始发送数据到数据发送完毕触发中断的这段时间里,处理器可以去做别的事情。 接收数据原理:当一个数据接收完毕后,将数据立存入缓冲区而不处理,并在未处理数据的计数器上加1。等到处理器空闲,再从缓冲区读取这些数据做并处理(不在中断函数中)。 如此一来,串口的收发速率并不受影响,还能保证处理器在数据收发的过程中并行执行其他任务。 #include "usart1.h" #include "string.h" //发送缓冲区 u8 Usart1_SendBuffer[USART_SendBufferSize]; //接收缓冲区 u8 Usart1_RecvBuffer[USART_RecvBufferSize]; //发送缓冲区指针 int Usart1_SendPointer = 0; //接收缓冲区指针 int Usart1_RecvPointer = 0; //发送字符队列的长度 int Usart1_SendDataSize = 0; //接收未处理字符数 int Usart1_RecvDataSize = 0; //串口1发送状态 int Usart1_SendStatus = USART_Status_Idle; //生成字符串的缓冲区 char StringBuffer[100]; //发送字符串 void USART1_SendString(char *str) { // while(*str) // { // USART1_SendByte(*str++); // } USART1_SendArray((u8*)str, strlen(str)); } //发送字节队列 void USART1_SendArray(u8 *DataArray, int count) { if(count <= 0) { return; } while(count) { USART1_SendByte(*DataArray++); count --; } } //发送一个字节 void USART1_SendByte(u8 data) { int pos; //如果缓冲区满了,要等待 while(Usart1_SendDataSize >= USART_SendBufferSize); //计算数据在缓冲区的位置 pos = Usart1_SendPointer + Usart1_SendDataSize; //数据位置超过缓冲区尾地址 if(pos >= USART_SendBufferSize) { //重新计算位置 pos = pos - USART_SendBufferSize; } Usart1_SendBuffer[pos] = data; Usart1_SendDataSize ++; //如果串口空闲,立即发送 if(Usart1_SendStatus == USART_Status_Idle) { Usart1_SendStatus = USART_Status_Busy; USART_SendData(USART1, Usart1_SendBuffer[Usart1_SendPointer++]); //指针移动到缓冲区尾地址后,循环到缓冲区首地址 if(Usart1_SendPointer == USART_SendBufferSize) { Usart1_SendPointer = 0; } Usart1_SendDataSize --; } } //串口1中断服务程序 void USART1_IRQHandler() { //判断发送完成中断 if(USART_GetITStatus(USART1, USART_IT_TC) == SET) { //清空发送完成TC标记位 USART_ClearFlag(USART1, USART_FLAG_TC); //清空串口发送完成中断TCIE标记 USART_ClearITPendingBit(USART1, USART_IT_TC); if(Usart1_SendDataSize > 0) { //发送下一个数据 USART_SendData(USART1, Usart1_SendBuffer[Usart1_SendPointer++]); //指针移动到缓冲区尾地址后,循环到缓冲区首地址 if(Usart1_SendPointer == USART_SendBufferSize) { Usart1_SendPointer = 0; } //待发送数据减1 Usart1_SendDataSize --; } else { //发送完毕,串口1发送状态:空闲 Usart1_SendStatus = USART_Status_Idle; } } //接收中断 if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET) { //清空串口接收标记 USART_ClearITPendingBit(USART1, USART_IT_RXNE); //获取缓冲区数据 Usart1_RecvBuffer[Usart1_RecvPointer++] = USART_ReceiveData(USART1); //如果没有溢出,待处理数据+1。否则丢弃该数据 if(Usart1_RecvDataSize < USART_RecvBufferSize) { Usart1_RecvDataSize ++; } //指针移动到缓冲区尾地址后,循环到缓冲区首地址 if(Usart1_RecvPointer == USART_RecvBufferSize) { Usart1_RecvPointer = 0; } } } |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1771 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1619 浏览 1 评论
1070 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
724 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1673 浏览 2 评论
1935浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
728浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
567浏览 3评论
593浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
551浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-22 12:12 , Processed in 0.716576 second(s), Total 77, Slave 60 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号