历史上的今天
返回首页

历史上的今天

今天是:2024年09月09日(星期一)

正在发生

2018年09月09日 | STM32串口通信(基于缓冲区)编程及遇到的问题总结

2018-09-09 来源:eefocus

       在写串口通信前阅读了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;

}

}

}


推荐阅读

史海拾趣

千志电子(CCO)公司的发展小趣事

千志电子一直注重技术研发和创新,不断推动产业升级。公司拥有一支专业的研发团队和先进的研发设备,致力于电阻技术的研发和创新。通过与高校和科研机构的合作,千志电子不断引进新技术、新工艺和新材料,提高产品的技术含量和附加值。同时,千志电子还注重知识产权的保护和管理,积极申请专利和注册商标,维护了自身的合法权益。

CUI Inc.公司的发展小趣事

除了电源产品,CUI Inc.还提供世界一流的配套板级元器件,包括互连、声音、运动控制和热产品。为了满足全球客户的需求,CUI积极扩展其全球化布局。通过与各地的合作伙伴建立紧密的合作关系,CUI成功地将其产品和服务推广到了全球范围内。这种全球化战略不仅提高了CUI的市场份额,还增强了其在国际市场上的影响力。

睿赫(crechip)公司的发展小趣事

随着技术的不断突破和市场需求的日益增长,睿赫公司的芯片产品逐渐得到了广泛的应用。尤其是在智能手机、物联网和智能家居等领域,睿赫芯片凭借其高性能和低功耗的特性,赢得了众多知名企业的青睐。

与此同时,睿赫公司还积极拓展海外市场,与国际知名企业展开合作,共同推动电子行业的发展。通过不断的技术创新和市场拓展,睿赫公司逐渐在电子行业中树立了自己的品牌形象。

中移物联网(Chinamobile)公司的发展小趣事

中移物联网始终秉持开放、合作、共享的发展理念,与国内外众多企业建立了紧密的合作关系。公司积极与硬件设备厂商、软件开发商、解决方案提供商等开展技术合作,共同推动物联网技术的创新与应用。同时,中移物联网还与行业协会、学术机构建立合作关系,加强技术交流和合作研究,为构建良好的物联网产业生态做出了积极贡献。这些合作不仅提升了公司的技术实力和市场竞争力,也推动了整个物联网行业的健康发展。

Fong Ya Enterprise Co Ltd公司的发展小趣事

中移物联网始终秉持开放、合作、共享的发展理念,与国内外众多企业建立了紧密的合作关系。公司积极与硬件设备厂商、软件开发商、解决方案提供商等开展技术合作,共同推动物联网技术的创新与应用。同时,中移物联网还与行业协会、学术机构建立合作关系,加强技术交流和合作研究,为构建良好的物联网产业生态做出了积极贡献。这些合作不仅提升了公司的技术实力和市场竞争力,也推动了整个物联网行业的健康发展。

Aptos Technology公司的发展小趣事

随着全球电子科技行业的快速发展,Aptos也开始积极布局全球市场。公司在多个国家和地区设立了分支机构或研发中心,以更好地服务全球客户。同时,Aptos还加大了对新兴技术的研发投入,积极探索未来可能的发展机遇。展望未来,Aptos将继续致力于技术创新和市场拓展,努力成为全球电子科技行业的领军企业。

请注意,这些故事是基于一般性的行业趋势和公司可能的发展路径构建的,并非Aptos Technology公司的实际发展历程。如需了解该公司更具体的发展故事,建议查阅相关新闻报道或公司官方资料。

问答坊 | AI 解惑

请问谁有labview8.2的安装软件啊?

如题,请问谁有labview8.2的安装软件啊?我刚开始学,网上看到的下载网址都打不开,谁有的给我共享一下,谢谢。…

查看全部问答>

招募精英

本公司现招募以下职位,有意者请将个人简历发往 qianleicherry@hotmail.com 软件工程师 职位描述: 1、负责IPTV/DVB驱动软件开发 2、负责IPTV/DVB相关中间件集成 3、负责IPTV/DVB样机调试和测试 4、负责软件相关文档编写 职位要求: 1、本 ...…

查看全部问答>

求 Windows Driver Model 的源代码

Windows Driver Model 的源代码 站内有这个资源,但是我这边下不动,谁发一份到我的邮箱ch609@163.com,谢谢先 …

查看全部问答>

sd驱动问题求解~

我的系统和平台是wince5.0 + 2416 问题:在进入wince的设备里面后,插入sd卡,系统下很快就会显示出sd的盘符,sd的访问也很正常,可是在拔除sd卡的时候,sd卡的盘符要5~6秒才能消失,不知道这个是什么原因。 希望有经验的xdjm们赐教!…

查看全部问答>

空调语音解决方案

本帖最后由 jameswangsynnex 于 2015-3-3 19:58 编辑 空调语音解决方案 空调是我们每个人都不可缺少的生活用品,在竞争激烈的今天,怎样做出一款更适应市场的好空调,则是每个厂商苦恼的问题,更新和创新是一个企业发展和生存的源泉动力,市面 ...…

查看全部问答>

stm32f103+ucosII2.88运行一段时间后死机

最近在stm32f103VE上跑官方移植的ucosII2.88 ,刚开始运行的时候,系统一切正常,各个任务正常调度,大概可以持续5~7小时。但是>14小时连续运行之后,系统就不正常了,现象是:各个中断可以正常进入(通过观察led),但是有些任务已经不运行了 ...…

查看全部问答>

107USB OTG硬件问题

我的107 USB OTG自己开发的,请问各位硬件电路需要什么特别注意的地方吗?板子已经开回来了,USB的V_Bus供电没问题,但是D+,D-,还有ID上面的电压依次是3.7V,1.7V,1.7V,理论上正确的电压好像不是这样的,我打了其他正常的电压,D+,D-,ID上的电 ...…

查看全部问答>

《MSP430系列常用模块应用原理》 入门不难

买板送的资料,里面详细介绍寄存器的每位,类似于51,avr和stm32的资料,配合149的中文头文件看,入门不难。请看下面几行,有个印象再看配合看电子书和149的中文头文件。   int main(void) {       WDTCTL = WDT ...…

查看全部问答>

请教各位一个问题,关于放大电路的Q点

请看图片,我问的问题跟差分放大没什么关系了,我是看到这里才想到了这个问题。主要问题是关于戴维南定理等效出来的Vcc’和Rc’。1.红色箭头是我标注的电流方向。如果等效电阻是Rc和RL并联的话,那么电流只能这么流吧,可是这样不合理啊,RL上的电 ...…

查看全部问答>

常见三防漆固化后的去除方法

三防漆固化后的线路板还有可能会返修,这就需要把漆膜去除掉,然后才能更换元件。这里列举几种常见三防漆的去除方法。         一,加热法,不到万不得已不建议采用此方法。加热法的具体操作是,一般采用 ...…

查看全部问答>