stm32使用两路串口及接收不定长数据的实现

发布者:温馨生活最新更新时间:2018-09-01 来源: 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串口(收发)

推荐阅读最新更新时间:2024-03-16 16:12

东芝推出业界首款符合Qi v1.2标准的15W无线充电接收器IC
东京 东芝公司(TOKYO:6502)今日宣布推出业界首款 无线充电接收器IC TC7766WBG ,其功率接收能力为15W并且符合无线充电联盟(WPC)定义的Qi v1.2标准。东芝将于今年年底开始提供产品样品,并将于2016年春季开始批量生产。 TC7766WBG的无线充电速度堪比甚至超过有线充电速度,因为在没有改变封装尺寸的情况下,其通过增加整流电压实现三倍于东芝现有产品 的功率接收能力。该系列产品适用于广泛的应用,包括智能手机和平板电脑等移动设备和工业设备。 这一全新IC包含各种所需的功能,包括构建独立无线能量传输RX系统所需的整流电路和控制器。东芝原创的尖端CD-0.13工艺实现小封装和高效率,简化系
[手机便携]
东芝推出业界首款符合Qi v1.2标准的15W无线充电<font color='red'>接收</font>器IC
利用光纤发射/接收器对实现远距离高速数据采集
    摘要: 应用串行同步传输原理,用一种小巧的光发射/接收器对设计成光纤通讯系统,接A/D采样数据直接进行远距离高速传输。实际证明该方法简单、可靠,具有一定的实用价值。     关键词: 高速数据 远距离采集 光纤通讯 多路同步 电力变压器局部放电在线定位监测系统的关键环节之一是准确、可靠的数据采集与传输。根据电力变压器局部放电产生的超声波和电脉冲信号频率高、频谱宽、幅度变化大,环境电磁干扰严重,以及电力变压器与中心控制室距离一般在500m范围以内,而多路检测信号集中在变压器周期等特点,电力变压器局部放电在线定位监测系统的信号检测采用现场集中数据采集方案,数据传输直接利用一种低价、高速、中远距离多模光纤HF
[电源管理]
单片机虚拟串口及界面设置
虚拟串口 虚拟串口是计算机通过软件模拟的串口,当其它设计软件使用到串口的时候,可以通过调用虚拟串口仿真模拟,以查看所设计的正确性。首先要安装虚拟串口设置的软件,网上有很多设置虚拟串口的软件,我用的是VSPD,可试用1个月,试用期过后,该软件将不能使用,但不用担心,所设置的虚拟串口不会消失,可以继续使用。 设置界面: 我设置的是COM3和COM4,软件只能成对设置,主要是因为通信时,一方可以监视另一方,如果仅设置1个虚拟串口的话,如你用COM3发送接收数据,但发送了什么接收到什么,你无法验证其正确与来源。这是人家编写软件的高明之处。软件设置时将所设置的两个虚拟串口对接,这样就可实现发送与接收的监视。从设备管理器中可以看到
[单片机]
单片机虚拟<font color='red'>串口</font>及界面设置
STM32中断NVIC、EXTI外部中断
eg:STM32F407ZGT6 1:NVIC(嵌套向量中断控制器) 在参考手册的描述中(我就直接Copy了哈): 嵌套向量中断控制器 NVIC 包含以下特性: ● STM32F405xx/07xx 和 STM32F415xx/17xx 具有 82 个可屏蔽中断通道, STM32F42xxx和 STM32F43xxx 具有多达 86 个可屏蔽中断通道(不包括 Cortex™-M4F 的 16 根中 断线) ● 16 个可编程优先级( 使用了 4 位中断优先级) ● 低延迟异常和中断处理 ● 电源管理控制 ● 系统控制寄存器的实现 嵌套向量中断控制器 (NVIC) 和处理器内核接口紧密配合,可以实现低延迟的中断处理和晚
[单片机]
<font color='red'>STM32</font>中断NVIC、EXTI外部中断
STM32 TALK | 无感FOC方案原理机器控制难点分析
1、电机控制方案的分析与选择 在永磁电机的无感控制策略中,主要有两大类:(1)无感方波控制;(2)无感FOC控制。 先谈谈无感方波控制。在无感方波控制中,主要是利用反电动势过零点的方式来得到换相信号(反电动势过零点的信号与电机的换相信号在相位上相差30°电角度)。为了得到反电动势过零的信号,通常采用两种方式:(1)硬件比较器法;(2)软件端电压采样法。这两种方法的原理大致是相同的,都是将检测得到的端电压的值与电机中性点电压进行比较来得到反电动势过零点的信号。在无感方波控制中,该检测手段的好坏将决定了控制性能的好坏。但无感方波控制通常会伴随着噪声大、转矩脉动大等缺点,因此仅在一些对电机无感控制要求不是很高的场合中较为适用。
[单片机]
STM32 USB学习笔记9
主机环境:Windows 7 SP1 开发环境:MDK5.14 目标板:STM32F103C8T6 开发库:STM32F1Cube库和STM32_USB_Device_Library 现在我们来分析VCP例程的最后一个文件USB设备类的usbd_cdc文件,该文件跟CDC类紧密相关,看下其头文件的一些定义: /** @defgroup usbd_cdc_Exported_Defines * @{ */ #define CDC_IN_EP 0x81 /* EP1 for data IN */ #define CDC_OUT_EP 0x01
[单片机]
<font color='red'>STM32</font> USB学习笔记9
GPRS无线终端测试系统电路设计
基于高性能单片机STM32($20.3400)和GPRS无线通信方案实现了对测试点CMMB网络覆盖情况的实时监测,并利用GPS接收器将测试终端的地理位子信息上传到服务器端,完成了对监测终端的精准定位。终端板卡供电方式采用太阳能供电系统,保障其在无电源和人员看守的情况下长期稳定的工作。最后通过综合测试,能实现所有要求的功能,完全满足本次设计的要求。 系统分为测试终端和服务器端,服务器端只需要一台性能良好的个人计算机,而测试终端主要由以下几个模块构成:射频前端模块模块、功率测量与存储模块、GPS接收器、太阳能供电模块、处理器模块及GPRS无线通信模块。各个模块主要是通过STM32($20.3400)微处理器的GPIO口连接与通信。
[单片机]
GPRS无线终端测试系统电路设计
stm32定时器优先级
什么是优先级   优先级是具有高抢占式优先级的中断可以在具有低抢占式优先级的中断处理过程中被响应,即中断嵌套,或者说高抢占式优先级的中断可以嵌套低抢占式优先级的中断。   当两个中断源的抢占式优先级相同时,这两个中断将没有嵌套关系,当一个中断到来后,如果正在处理另一个中断,这个后到来的中断就要等到前一个中断处理完之后才能被处理。如果这两个中断同时到达,则中断控制器根据他们的响应优先级高低来决定先处理哪一个;如果他们的抢占式优先级和响应优先级都相等,则根据他们在中断表中的排位顺序决定先处理哪一个。 stm32定时器优先级   STM32 可以支持的 68 个外部中断通道,已经固定的分配给相应的外部设备。每个中断通道都具备自己
[单片机]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

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

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

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved