zoukankan      html  css  js  c++  java
  • STM32串口中断的一些资料

    在研究STM32串口接收发送中断的时候找到不少不错的资料,现在备份在这里。以供自己查阅,以及方便其他人。

    TC ====TXE

    顺便预告下最近会写个有关串口处理数据的帖子,从查询和中断方面以及数据处理的方式,从队列以及FIFO方面写起。

    SECTION 1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    /*
    调试STM32串口过程中发现一个奇怪的问题,初始化串口1口,使能串口发送完成中断后,立刻就进入了发送完成中断。
    仔细的查阅了STM32手册中的串口部分的介绍:
              
    以下是字符发送的配置过程,注意第6点,在设置USART_CR1中的TE位时,会发送一个空闲帧作为第一次数据发送,所以即便你执行了USART_ClearFlag(USART1, USART_FLAG_TC); (这个函数肯定在空闲帧数据发送完成前执行),所以当空闲帧发送完后,就进入发送完成中断。
              
    配置步骤:
    1.  通过在USART_CR1寄存器上置位UE位来激活USART
    2.  编程USART_CR1的M位来定义字长。
    3.  在USART_CR2中编程停止位的位数。
    4.  如果采用多缓冲器通信,配置USART_CR3中的DMA使能位(DMAT)。按多缓冲器通信中
    的描述配置DMA寄存器。
    5.  利用USART_BRR寄存器选择要求的波特率。
    6.  设置USART_CR1中的TE位,发送一个空闲帧作为第一次数据发送。
    7.  把要发送的数据写进USART_DR寄存器(此动作清除TXE位)。在只有一个缓冲器的情况
    下,对每个待发送的数据重复步骤7。
    8.  在USART_DR寄存器中写入最后一个数据字后,要等待TC=1,它表示最后一个数据帧的
    传输结束。当需要关闭USART或需要进入停机模式之前,需要确认传输结束,避免破坏
    最后一次传输。
    */
    //解决的办法:
    //方法一
    //在执行
    USART_ITConfig(USART1, USART_IT_TC, ENABLE); 
    //之前,先延时一段时间,基本上比一个字符发送的时间长一点就可以了,然后再执行
    USART_ClearFlag(USART1, USART_FLAG_TC);
              
    //方法二:
    //在执行
    USART_ITConfig(USART1, USART_IT_TC, ENABLE);
    while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET)
    {
            //等待空闲帧发送完成后  再清零发送标志
    }
    USART_ClearFlag(USART1,USART_FLAG_TC);

    SECTION 2

     

    先说TC。即Transmission Complete。发送一个字节后才进入中断,这里称为“发送后中断”。和原来8051的TI方式一样,都是发送后才进中断,需要在发送函数中先发送一个字节触发中断。发送函数如下

    /*******
    功能:中断方式发送字符串.采用判断TC的方式.即 判断 发送后中断 位.
    输入:字符串的首地址
    输出:无
    *******/
    void USART_SendDataString( u8 *pData )
    {
        pDataByte = pData;
      
        USART_ClearFlag(USART1, USART_FLAG_TC);//清除传输完成标志位,否则可能会丢失第1个字节的数据.网友提供.
        
        USART_SendData(USART1, *(pDataByte++) ); //必须要++,不然会把第一个字符t发送两次
    }


    中断处理函数如下
    /********
    * Function Name  : USART1_IRQHandler
    * Description    : This function handles USART1 global interrupt request.
    * Input          : None
    * Output         : None
    * Return         : None
    *********/
    void USART1_IRQHandler(void)
    {
        if( USART_GetITStatus(USART1, USART_IT_TC) == SET  )
        {
            if( *pDataByte == '' )//TC需要 读SR+写DR 方可清0,当发送到最后,到''的时候用个if判断关掉
                USART_ClearFlag(USART1, USART_FLAG_TC);//不然TC一直是set, TCIE也是打开的,导致会不停进入中断. clear掉即可,不用关掉TCIE
            else
                USART_SendData(USART1, *pDataByte++ );
        }

    }

    其中u8 *pDataByte;是一个外部指针变量

    在中断处理程序中,发送完该字符串后,不用关闭TC的中断使能TCIE,只需要清掉标志位TC;这样就能避免TC == SET 导致反复进入中断了。

    串口初始化函数如下

    /*********
    名称:  USART_Config
    功能:  设置串口参数
    输入:  无
    输出:  无
    返回:  无
    **********/
    void USART_Config()
    {
      USART_InitTypeDef USART_InitStructure;//定义一个包含串口参数的结构体
      
      USART_InitStructure.USART_BaudRate = 9600; //波特率9600
      USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8位数据位
      USART_InitStructure.USART_StopBits = USART_StopBits_1;//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_InitStructure.USART_Clock = USART_Clock_Disable;//时钟关闭
      USART_InitStructure.USART_CPOL = USART_CPOL_Low;
      USART_InitStructure.USART_CPHA = USART_CPHA_2Edge;
      USART_InitStructure.USART_LastBit = USART_LastBit_Disable;
      USART_Init(USART1, &USART_InitStructure);//设置到USART1
      
      USART_ITConfig(USART1, USART_IT_TC, ENABLE);//Tramsimssion Complete后,才产生中断. 开TC中断必须放在这里,否则还是会丢失第一字节

      USART_Cmd(USART1, ENABLE); //使能USART1
    }
    这里请问一个问题:开TC中断USART_ITConfig()如果放在我的USART_SendDataString()中再开,会丢失字符串的第一字节。必须放在串口初始化函数中才不会丢。不知道为什么??

    这里笔者可以给出解释,你看下SECTION1 就可以知道为什么呢,你这样做的原理和SECTION1讲解的差不多,就相当于延时,而你后面没有丢失数据的主要原因就是你代码中有这么一句 USART_ClearFlag(USART1, USART_FLAG_TC);//清除传输完成标志位,否则可能会丢失第1个字节的数据.网友提供.

    再说判断TXE。即Tx DR Empty,发送寄存器空。当使能TXEIE后,只要Tx DR空了,就会产生中断。所以,发送完字符串后必须关掉,否则会导致重复进入中断。这也是和TC不同之处。

    发送函数如下:
    /*******
    功能:中断方式发送字符串.采用判断TC的方式.即 判断 发送后中断 位.
    输入:字符串的首地址
    输出:无
    *******/
    void USART_SendDataString( u8 *pData )
    {
        pDataByte = pData;
        USART_ITConfig(USART1, USART_IT_TXE, ENABLE);//只要发送寄存器为空,就会一直有中断,因此,要是不发送数据时,把发送中断关闭,只在开始发送时,才打开。 
        
    }

    中断处理函数如下:

    /********
    * Function Name  : USART1_IRQHandler
    * Description    : This function handles USART1 global interrupt request.
    * Input          : None
    * Output         : None
    * Return         : None
    ********/
    void USART1_IRQHandler(void)
    {
        if( USART_GetITStatus(USART1, USART_IT_TXE) == SET  )
        {
            if( *pDataByte == '' )//待发送的字节发到末尾NULL了
                USART_ITConfig(USART1, USART_IT_TXE, DISABLE);//因为是 发送寄存器空 的中断,所以发完字符串后必须关掉,否则只要空了,就会进中断
            else
                USART_SendData(USART1, *pDataByte++ );
        }

    }

    在串口初始化函数中就不用打开TXE的中断了(是在发送函数中打开的)如下:
    /************
    名称:  USART_Config
    功能:  设置串口参数
    输入:  无
    输出:  无
    返回:  无
    ************/
    void USART_Config()
    {
      USART_InitTypeDef USART_InitStructure;//定义一个包含串口参数的结构体
      
      USART_InitStructure.USART_BaudRate = 9600; //波特率9600
      USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8位数据位
      USART_InitStructure.USART_StopBits = USART_StopBits_1;//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_InitStructure.USART_Clock = USART_Clock_Disable;//时钟关闭
      USART_InitStructure.USART_CPOL = USART_CPOL_Low;
      USART_InitStructure.USART_CPHA = USART_CPHA_2Edge;
      USART_InitStructure.USART_LastBit = USART_LastBit_Disable;

      USART_Init(USART1, &USART_InitStructure);//设置到USART1
      
      USART_Cmd(USART1, ENABLE); //使能USART1

    }

    SECTION 3

    在USART的发送端有2个寄存器,一个是程序可以看到的USART_DR寄存器(下图中阴影部分的TDR),另一个是程序看不到的移位寄存器(下图中阴影部分Transmit Shift Register)。

    对应USART数据发送有两个标志,一个是TXE=发送数据寄存器空,另一个是TC=发送结束;对照下图,当TDR中的数据传送到移位寄存器后,TXE被设置,此时移位寄存器开始向TX信号线按位传输数据,但因为TDR已经变空,程序可以把下一个要发送的字节(操作USART_DR)写入TDR中,而不必等到移位寄存器中所有位发送结束,所有位发送结束时(送出停止位后)硬件会设置TC标志。

    另一方面,在刚刚初始化好USART还没有发送任何数据时,也会有TXE标志,因为这时发送数据寄存器是空的。

    TXEIE和TCIE的意义很简单,TXEIE允许在TXE标志为'1'时产生中断,而TCIE允许在TC标志为'1'时产生中断。

    至于什么时候使用哪个标志,需要根据你的需要自己决定。但我认为TXE允许程序有更充裕的时间填写TDR寄存器,保证发送的数据流不间断。TC可以让程序知道发送结束的确切时间,有利于程序控制外部数据流的时序。

    SECTION 4

            总的来说,STM32单片机的串口还是很好理解的,编程也不算复杂。当然我更愿意希望其中断系统和51单片机一样的简单。

            对于接收终端,就是RXNE了,这只在接收完成后才产生,在执行USART_ITConfig(USART1, USART_IT_RXNE, ENABLE)代码时不会进入ISR。但麻烦的就是发送有关的中断了:TXE或者TC,根据资料和测试的结果,TXE在复位后就是置1的,即在执行USART_ITConfig(USART1, USART_IT_TXE,  ENABLE)后会立即产生中断请求。因此这造成一个麻烦的问题:如果没有真正的发送数据,TXE中断都会发生,而且没有休止,这将占用很大部分的CPU时间,甚至影响其他程序的运行!

            因此建议的是在初始化时不好启用TXE中断,只在要发送数据(尤其是字符串、数组这样的系列数据)时才启用TXE。在发送完成后立即将其关闭,以免引起不必要的麻烦。

            对于发送,需要注意TXE和TC的差别——这里简单描述一下,假设串口数据寄存器是DR、串口移位寄存器是SR以及TXD引脚TXDpin,其关系是DR->SR->TXDpin。当DR中的数据转移到SR中时TXE置1,如果有数据写入DR时就能将TXE置0;如果SR中的数据全部通过TXDpin移出并且没有数据进入DR,则TC置1。并且需要注意TXE只能通过写DR来置0,不能直接将其清零,而TC可以直接将其写1清零。

            对于发送单个字符可以考虑不用中断,直接以查询方式完成。

            对于发送字符串/数组类的数据,唯一要考虑的是只在最后一个字符发送后关闭发送中断,这里可以分为两种情况:对于发送可显示的字符串,其用0x00作为结尾的,因此在ISR中就用0x00作为关闭发送中断(TXE或者TC)的条件;第二种情况就是发送二进制数据,那就是0x00~0xFF中间的任意数据,就不能用0x00来判断结束了,这时必须知道数据的具体长度。

           这里简单分析上面代码的执行过程:TXE中断产生于前一个字符从DR送入SR,执行效果是后一个字符送入DR。对于第一种情况,如果是可显示字符,就执行USART_SendData来写DR(也就清零了TXE),当最后一个可显示的字符从DR送入SR之后,产生的TXE中断发现要送入DR的是字符是0x00——这当然不行——此时就关闭TXE中断,字符串发送过程就算结束了。当然这时不能忽略一个隐含的结果:那就是最后一个可显示字符从DR转入SR后TXE是置1的,但关闭了TXE中断,因此只要下次再开启TXE中断就会立即进入ISR。对于第二种情况,其结果和第一种的相同。

             对于第一种情况,其程序可以这么写:其中TXS是保存了要发送数据的字符串,TxCounter1是索引值:

    extern __IO uint8_t TxCounter1;
    extern uint8_t *TXS;
    extern __IO uint8_t TxLen; 

    void USART1_IRQHandler(void)
        {
            if(USART_GetITStatus(USART1, USART_IT_TXE) != RESET)
                {                                               
                    if(TXS[TxCounter1]) //如果是可显示字符
                        { USART_SendData(USART1,TXS[TxCounter1++]);}
                    else   //发送完成后关闭TXE中断,
                        { USART_ITConfig(USART1,USART_IT_TXE,DISABLE);}                                                        
                }                   
        }

            对于第二种情况,和上面的大同小异,其中TXLen表示要发送的二进制数据长度:

    void USART1_IRQHandler(void)
        {
            if(USART_GetITStatus(USART1, USART_IT_TXE) != RESET) //对USART_DR的写操作,将该位清零。
                {                                              
                    if(TxCounter1<TxLen)
                        { USART_SendData(USART1,TXS[TxCounter1++]);}
                    else   //发送完成后关闭TXE中断
                        { USART_ITConfig(USART1,USART_IT_TXE,DISABLE);}                                                         
                }                    
        }

            事实上第一种情况是第二种的特殊形式,就是说可以用第二种情况去发送可显示的字符——当然没人有闲心去数一句话里有多少个字母空格和标点符号!

            在使用时,只要将TXS指向要发送的字符串或者数组,设置TxLen为要发送的数据长度,然后执行USART_ITConfig(USART1, USART_IT_TXE,ENABLE)就立即开始发送过程。用户可以检查TxCounter1来确定发送了多少字节。比如以第二种情况为例:

    uint32_t *TXS;
    uint8_t TxBuffer1[]="0123456789ABCDEF";
    uint8_t DST2[]="ASDFGHJKL";
    __IO uint8_t TxLen = 0x00;

         TxLen=8;                               //发送8个字符,最终发送的是01234567
        TXS=(uint32_t *)TxBuffer1;   //将TXS指向字符串TxBuffer1
        TxCounter1=0;                     //复位索引值
        USART_ITConfig(USART1, USART_IT_TXE,ENABLE);   //启用TXE中断,即开始发送过程
        while(TxCounter1!=TxLen);   //等待发送完成

        TXS=(uint32_t *)TxBuffer2;   //同上,最终发送的是ASDFGHJK
        TxCounter1=0;
        USART_ITConfig(USART1, USART_IT_TXE,ENABLE);
        while(TxCounter1!=TxLen);

            以上就是我认为的最佳方案,但串口中断方式数据有多长就中断多少次,我认为还是占用不少CPU时间,相比之下DMA方式就好多了,因为DMA发送字符串时最多中断两次(半传输完成,全传输完成),并且将串口变成类似16C550的器件。

  • 相关阅读:
    网络协议-应用层协议-HTTP协议简介
    网络协议-应用层协议-电子邮件协议
    网络协议-应用层协议-文件传输协议
    网络协议-应用层协议-远程登陆协议
    网络协议-应用层协议-概述
    网络协议-传输层协议-Socket编程
    网络协议-传输层协议-TCP协议
    网络协议-传输层协议-UDP协议
    北华大学网络赛题
    哈尔滨网络热身赛
  • 原文地址:https://www.cnblogs.com/liufang/p/3990206.html
Copyright © 2011-2022 走看看