历史上的今天

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

正在发生

2018年09月01日 | stm32使用两路串口及接收不定长数据的实现

发布者:温馨生活 来源: eefocus关键字:stm32  串口  接收  不定长数据 手机看文章 扫描二维码
随时随地手机看文章

前言:前一段时间需要编写一个使用双路串口的程序采集传感器数据,由于自身能力有限所以遇到了很多坑,后来经过多方学习和调试基本完成了所需功能,现将自己的一些经(踩)验(过)方(的)法(坑),与大家分享。由于本人水平有限文章中有不足之处也欢迎大家指出改正! 

1、串口配置 

本人采用的是stm32F407的串口1和串口3(串口2因为硬件问题让我给烧坏了…尴尬, 在此也提醒大家一定要确保硬件连接无误),配置串口的过程不再赘述,直接上代码


//初始化IO 串口1 

//bound:波特率

void uart_init(u32 bound){

    //GPIO端口设置

    GPIO_InitTypeDef GPIO_InitStructure;

    USART_InitTypeDef USART_InitStructure;

    NVIC_InitTypeDef NVIC_InitStructure;


    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1时钟


    //串口1对应引脚复用映射

    GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIOA9复用为USART1

    GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIOA10复用为USART1


    //USART1端口配置

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9与GPIOA10

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   //速度50MHz

    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出

    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉

    GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10


   //USART1 初始化设置

    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_Cmd(USART1, ENABLE);  //使能串口1 


    //USART_ClearFlag(USART1, USART_FLAG_TC);



    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启相关中断

   //   USART_ITConfig(USART1, USART_IT_TC, ENABLE);


    //Usart1 NVIC 配置

    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口1中断通道

    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//抢占优先级3

    NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;       //子优先级3

    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;         //IRQ通道使能

    NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器



}


void uart3_init(u32 bound)

{

    //GPIO端口设置

    GPIO_InitTypeDef GPIO_InitStructure;

    USART_InitTypeDef USART_InitStructure;

    NVIC_InitTypeDef NVIC_InitStructure;


    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE); //使能GPIOA时钟

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE);//使能USART1时钟


    //串口3对应引脚复用映射

    GPIO_PinAFConfig(GPIOB,GPIO_PinSource10,GPIO_AF_USART3); //GPIOA9复用为USART1

    GPIO_PinAFConfig(GPIOB,GPIO_PinSource11,GPIO_AF_USART3); //GPIOA10复用为USART1


    //USART3端口配置

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11; //GPIOB10与GPIOB11

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   //速度50MHz

    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出

    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉

    GPIO_Init(GPIOB,&GPIO_InitStructure); //初始化PA9,PA10


   //USART3 初始化设置

    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(USART3, &USART_InitStructure); //初始化串口3

    USART_Cmd(USART3, ENABLE);  //使能串口3 


    USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);  //开启接收中断   

    USART_ITConfig(USART3, USART_IT_IDLE, ENABLE);  //开启空闲中断


    //Usart3 NVIC 配置

    NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;//串口1中断通道

    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//抢占优先级3

    NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;       //子优先级3

    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;         //IRQ通道使能

    NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器、



}


这里需要说明的强调的是,在配置串口3的时候开启了空闲中断,这里是为了实现接收不定长数据,会在下文详细说明。


2、接收不定长数据的实现 

首先说明一下个人的思路,如下图。 

这里写图片描述

首先是由上位机通过串口1向stm32发送一条相关指令,stm32再将指令转发给传感器端,传感器接收到相关指令后通过串口3发送相关数据给stm32,最后再由stm32将数据进行相应解析后通过串口1回传给上位机。 

但是无论是上位机指令还是传感器回传的数据,其数据长度都不是固定的,以下给出两种方法分别实现对串口1和串口3的数据接收。 

2.1、使用循环队列的方法接收串口1的数据 
这里写图片描述 
如上图所示,RevBuff1为串口1的接收缓存区;usart1_in为队头,每次接收到一个字节的数据后usart1_in++,用来记录当前入队的位置;usart1_out为队尾,每次出队后队尾记录上次入队的位置。串口1中断函数如下:

uint32_t usart1_in  = 0;

uint32_t usart1_out = 0;

u8 RevBuff1[BUFFSIZE] = {0};

u8 flag = 0;


void USART1_IRQHandler(void)                    //串口1中断服务程序

{

    u8 res1 = 0;


    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)//接收到数据

    {


        res1 = USART_ReceiveData(USART1);

        USART_ClearITPendingBit(USART1, USART_IT_RXNE);

        RevBuff1[usart1_in++] = res1;


        if (usart1_in >= BUFFSIZE) {

            usart1_in = 0;

        }


        flag = 1;

    }


按照这个思路,要想将串口1的接收到的数据发送给串口3时,只需要判断队头和队尾的位置,然后发送队尾至队头之间的数据就可以实现不定长数据的发送了,具体代码实现如下:


//把串口1接收到的数据透传发送给串口3

if (flag == 1) {    

   if (usart1_out != usart1_in) {       //在队头队尾不相等的情况下,即有数据存入RecBuff1

       if (usart1_out < usart1_in) {    //若队尾小于队头,直接发送队尾至队头之间的数据

            for (t = usart1_out; t < usart1_in; t++) {

                while(USART_GetFlagStatus(USART3,USART_FLAG_TXE)!=SET);

                USART_SendData(USART3, RevBuff1[t]);        

            }  

            usart1_out = usart1_in;     //发送完成后队尾移至当前队头的位置

            flag = 0;

       } 

       else {

           //若队尾大于队头,先发送队尾至RecBuff1[BUFFSIZE]之间的数据

           //然后发送RecBuff1[0]至队头之间的元素

           for (t = usart1_out; t < BUFFSIZE; t++) {

                while(USART_GetFlagStatus(USART3,USART_FLAG_TXE)!=SET);

                USART_SendData(USART3, RevBuff1[t]);        

            }  

            for (t = 0; t < usart1_in; t++) {

               while(USART_GetFlagStatus(USART3,USART_FLAG_TXE)!=SET);

               USART_SendData(USART3, RevBuff1[t]);  

            }

            usart1_out = 0;           //发送完成后队尾移至RecBuff1[0]的位置

            flag = 0;

       }

    }   

 }


2.2、利用串口的RXNE和IDLE中断接收串口3的数据 

首先说明,IDLE中断就是串口收到一帧数据后,发生的中断;所谓一帧数据是指给单片机一次发来1个字节,或者一次发来若干个字节,这些一次发来的数据,就称为一帧数据。 而当接收到1个字节,就会产生RXNE中断。

这里写图片描述

上图为状态寄存器描述图,当串口接收到数据时,bit5就会自动变成1,当接收完一帧数据后,bit4就会变成1。 
需要注意的是,在中断函数里面,需要把对应的位清0,否则会影响下一次数据的接收。比如RXNE接收数据中断,只要把接收到的一个字节读出来,就会清除这个中断。IDLE中断,如果是F0系列的单片机,需要用ICR寄存器来清除,如果是F1系列的单片机,清除方法是“先读SR寄存器,再读DR寄存器”,经测试,F4系列与F1系列的方法相同。在使用IDLE中断之前先要在串口3配置时开启相应中断(上文已经提到过),如下图: 
这里写图片描述
接着来看串口3的中断程序:

void USART3_IRQHandler(void)     //串口3中断服务程序

{

    u8 res3  = 0;

    u8 Clear = 0;


    //如果接收到一个字节

    if (USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) {

        res3 = USART_ReceiveData(USART3);

        RevBuff3[usart3_in++] = res3;


        if (usart3_in >= BUFFSIZE) {

            usart3_in = 0;

        }


        flag3 = 1;

    }

    //如果接收到一帧数据

    else if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET) {

        Clear = USART3->SR;     //读SR寄存器

        Clear = USART3->DR;     //读DR寄存器(先读SR再度DR,可以清除IDLE中断)

        ReceiveStave = 1;       //标记接收到1帧的数据

    }


}


串口3中断函数中,分别来判断是否接收到1个字节,以及判断是否接收到1帧数据,这样不仅能接收不定长数据,同时usart3_in中也记录了RevBuff3的大小。此时再进行其他的控制就容易多了,以下是我自己控制部分的代码:


         //当串口3接收到一个完整数据帧,处理数据帧,将处理好结果发回串口1

         if (ReceiveStave == 1) {  

            ReceiveStave = 0;

            t = 0;


            //复制RevBuff3到tmp

            for (t = 0, tmpLen = 0; t < usart3_in; t++,tmpLen++) {

                tmp[tmpLen] = RevBuff3[t];

            }


            //得到处理后的数据

            p = ReadRealTimeData(tmp, tmpLen);



            //发送处理后的数据

            for (i = 0; i < strlen( p ); i++) {

                while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)!=SET);               

                USART_SendData(USART1, p[i]);

               // while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);

            }


             usart3_in = 0;

         }


3、串口发送数据的TC/TXE行为 

先来看看串口数据发送的过程是什么样的:


先写数据到DR寄存器->移位寄存器->TX管脚。当数据从DR寄存器移出到移位寄存器(即DR寄存器空)时TXE就置位,优点是能保持发送数据的连续性;而当一帧数据全部发送完成(”\0”结束符)则触发TC中断,优点是可确定发送完成的时间多用于数据的流控。

也就是说,TXE=1代表发送数据寄存器空,可以往里存放数据 ;TC=1代表该寄存器中的数据已全部发送完成。借用openedv论坛中一句形象的描述就是:


你写数据到串口时,是装入弹仓,硬件会将数据移到枪膛,这时,TXE为1,TC为0,STM32硬件的TX脚正在发送数据,但你还可以装入数据到弹仓,装入后,TXE为0,TC为0. 

TX发送完一个数据后,立即将数据从弹仓移入枪膛,这时,TXE为1,TC为0. 

最后TX发送完数据,你又没有装入新数据,这时。TXE为1,TC为1.

那么了解这两个有什么具体的用处呢? 

常见的使用方式有以下两种,在向串口写数据时需要知道上一次数据是否发送完成,既然TXE=1代表发送数据寄存器空,可以往里存放数据,那么我们在写数据之前可以先进行TXE中断判断,代码如下:


  //发送处理后的数据

  for (i = 0; i < strlen( p ); i++) {

      while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)!=SET);               

      USART_SendData(USART1, p[i]);  

  }


而TC=1代表该寄存器中的数据已全部发送完成,可以理解为每发送一次数据后,判断此次发送是否完成,代码如下:


for(t=0;t

    USART_SendData(USART1, USART_RX_BUF[t]);       

    while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);

}


4、注意硬件接线及标号是否正确!!! 

注意硬件接线及标号是否正确!!! 

注意硬件接线及标号是否正确!!! 

注意硬件接线及标号是否正确!!! 

解决了数据发送和接收的大问题,程序基本就应该可以完成了吧,想想还有点小激动呢!连接线路,RX接TX,TX接RX,一切看起来十分完美,然而出现了尴尬的情况,程序怎么也读不到数据……刚开始我觉得一定是程序写的有问题,找了好久后无果,请大神来看看,大神看完程序也说没问题,思考片刻后交换了一下接线,然后就好了!!!好了!!!(我还是太年轻,可是我也试过啊,怎么就不行呢?!被大神深深鄙视了一次…),最后发现是传感器上的标号标错了(RX,TX标反了!!!)。这大概是这个项目中最大的一个坑吧。所以再次提醒大家一定要注意硬件接线及标号是否正确!!!


总结


跌跌撞撞地总算是实现了基本的功能,由于自己也是第一次入手串口相关项目(都怪自己学的不扎实),所以遇到了许多大大小小的问题,项目至今也还有一些BUG,比如说,采集数据时还会出现丢包现象,用两种不同方式实现接收不定长数据是因为只用第一种就会出现数据丢失… 但是也算有所收获吧,写下来和大家分享交流。

关键字:stm32  串口  接收  不定长数据 引用地址:stm32使用两路串口及接收不定长数据的实现

上一篇:STM32库函数实现USART发送数据
下一篇:STM32F103程序设计-9-USB转TTL串口(收发)

推荐阅读

关于即将到来的“机器人末日”传闻,人们各执一词,因为这与就业市场有极大关联。机器人技术与人工智能技术正以惊人的速度进步和发展。目前,从建筑到金融领域,部分就业者已经被同类机械化或数字化技术所取代。但在未来十年,更多的行业将受到影响。根据2017年麦肯锡全球研究所对46个国家中800个职业进行的一项研究发现,到2030年,将有8亿人的工作被自动...
工业机器人是在上个世纪40年代诞生出来的,最初的工业机器人是在固定的环境下进行重复作业,后来工业机器人用在了许多的行业,像是汽车、、金属、物流、医药、甚至是航空航天等等。随着科技的发展,人们需要的不是传统的不会变化的机器人,而是可以应用到更多场景的智能化的工业机器人,在功能上可以兼顾传感和智能化进行决策和判断,达到发掘工业数据的...
据外媒报道,总部位于澳大利亚墨尔本的Rectifier Technologies公司推出了首款双向电动汽车充电器——壁挂式Highbury直流(DC)双向充电器,不仅可以利用家庭和企业的电力给电动汽车充电,还能够将电动汽车的剩余电力卖回给电网。图片来源:Rectifier此种称作“车到电网”(V2G)的技术可以让具有双向充电功能的电动汽车(如日产聆风)车主在用电高峰期时...
本节主要内容:完成Lora模块单片机STM8L101F3标准库的移植STM8l101F3单片机属于是8位单片机,但和传统的51有很大的区别:1.STM8的内核不一样: 51单片机的内核是传统的8051,STM8是ST独有的8位单片机内核。2.STM8单片机内部的寄存器更多,资源更丰富,功能更强大,价格相比8051更贵一点。3.ST公司提供了STM8单片机对应的标准库,程序开发一般基于标准库来开...

史海拾趣

问答坊 | AI 解惑

bf561 汇编指令集(一)

做个记录 只列出几个例子 Load / Store Load Immediate p3 = 12 (z) ; r0 = -344 (x) ; Load Pointer Register p5 = [ p0 ++ ] ; p2 = [ sp -- ] ; Load Data Register r7 = [i3 ++ m0] ; r1 = [ p0 ++ p1 ] ; r0 = [ i0 ++ ] ; ...…

查看全部问答∨

嵌入式设计在变频调速领域的应用

1. 目前国家提倡节能减排,发展低碳经济。变频调速,改善工艺是目前工艺领域节能减排的主要方向。开发制作这些大型的工况设备,嵌入式设计技术应用不可缺少。欢迎大家讨论嵌入式技术在工业领域应用的心得和技巧。…

查看全部问答∨

谁对LPC1766芯片有所了解

    最近做毕业设计,要在主芯片为LPC1766的板子上实现以太网通信的功能,谁对这芯片有所了解啊,有中文版的资料就更好了。谢谢…

查看全部问答∨

VHDL的一个小小问题

--有如下语句: ... ... GENERIC(CONSTANT BUS_WIDTH: INTEGER := 15);        --总线宽度定义 ... ... po1: INOUT         STD_LOGIC_VECTOR(BUS_WIDTH DOWNTO 0); ... ... po1 <= "1111111111 ...…

查看全部问答∨

高手来帮忙呀!--关于EVC蓝牙开发

最近在最一个关于蓝牙的程序,包括打开蓝牙、查找周围蓝牙设备并列举等,还有匹配。 以前没做过相关的,对蓝牙通讯也不是很熟悉,哪位高手有蓝牙开发的经验啊?请指教呀。谢谢! 请各位大侠不吝赐教啊!感激!!!…

查看全部问答∨

#####哪位大侠有SHT11(瑞士盛世瑞恩温湿度传感器)的MSP430程序呢???####

最近找这个找疯了,求求哪位大侠帮个忙! 小弟邮箱:zhanliang198109@126.com 先谢谢了!…

查看全部问答∨

WindowsXP作为POWERLINK主站的配置过程

WindowsXP作为POWERLINK主站的配置过程…

查看全部问答∨

【设计工具】user.manual.lpc17xx

user.manual.lpc17xx FPGA设计指南:器件、工具和流程 systemc user.manual.lpc17xx 数字逻辑与数字集成电路…

查看全部问答∨

MSP430-JTAG原理图和物料清单

上传找到的一个,MSP430-JTAG原理图和物料清单。供大家参考啊…

查看全部问答∨
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
随便看看

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved