本帖最后由 小小毛 于 2021-3-23 22:02 编辑

设计背景:
     针对大部分国产低端MCU(ARM-CortexM0)来说,并没有空闲中断,此时就需要一个定时器Timer配合来完成此任务。对于UART接受不定长数据,空闲中断还是非常实用的!
知识点:FreeRTOS的二值信号量 Timer UART
空闲中断的原理:     IDLE中断叫空闲中断,不叫帧中断。那么什么叫空闲,怎么定义空闲呢?在实际发送数据的时候,比如一串字符串,其实发送的两个字符之间间隔非常短,所以在两个字符之间不叫空闲。空闲的定义是总线上在一个字节的时间内没有再接收到数据,空闲中断是检测到有数据被接收后,总线上在一个字节的时间内没有再接收到数据的时候发生的。而总线在什么情况时,会有一个字节时间内没有接收到数据呢?一般就只有一个数据帧发送完成的情况,所以串口的空闲中断也叫帧中断。
开发环境Win10,  MDK5.28,  HC32L136
设计步骤:
    这里不做长篇大论,列举了重要的核心部分讲解,便于大家移植。附件中带有完整的工程代码。
首先定义一个结构体和信号量。
extern SemaphoreHandle_t  AT_RX_Semaphore;
  • /*用于空闲中断判断*/
  • typedef struct
  • {
  •         uint16_t     uart_cnt;  
  •         uint16_t     timer_cnt;
  • }stcUART_Idle;
  • extern stcUART_Idle UART_Idle;
  • 复制代码

    2. 串口部分代码:

    • /**********************************************************************************************
    •     *函数功能:初始化UART
    •     *UARTx:  选择初始化UART端口号
    •     *Parity: 奇偶校验位
    •     *说明IO用使用复位模式2,DMA默认是使能
    • **********************************************************************************************
    • */
    • void BSP_UARTx_Init(M0P_UART_TypeDef *UARTx, uint32_t baud, en_uart_mmdorck_t Parity)
    • {
    •          if(UARTx == M0P_UART0)
    •          {
    •                 Uart0_init(baud,Parity);
    •                 EnableNvic(UART0_IRQn, IrqLevel3, TRUE);       ///<系统中断使能
    •          }
    •          if(UARTx == M0P_UART1)
    •          {
    •                 EnableNvic(UART1_IRQn, IrqLevel3, TRUE);           ///<系统中断使能
    •          }
    • }


    • //串口0模块配置
    • static void Uart0_init(uint32_t baud, en_uart_mmdorck_t Parity)
    • {
    •         stc_gpio_cfg_t stcGpioCfg;
    •     stc_uart_cfg_t  stcCfg;
    •     stc_uart_baud_t stcBaud;

    •     DDL_ZERO_STRUCT(stcGpioCfg);
    •     DDL_ZERO_STRUCT(stcCfg);
    •     DDL_ZERO_STRUCT(stcBaud);

    •     Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio,TRUE); //GPIO外设模块时钟使能

    •     stcGpioCfg.enDir = GpioDirOut;
    •     Gpio_Init(GpioPortA,GpioPin9,&stcGpioCfg);
    •     Gpio_SetAfMode(GpioPortA,GpioPin9,GpioAf1); //配置PA09 为UART0 TX
    •     stcGpioCfg.enDir = GpioDirIn;
    •     Gpio_Init(GpioPortA,GpioPin10,&stcGpioCfg);
    •     Gpio_SetAfMode(GpioPortA,GpioPin10,GpioAf1);//配置PA10 为UART0 RX

    •     Sysctrl_SetPeripheralGate(SysctrlPeripheralUart0,TRUE);//UART0外设模块时钟使能

    •         stcCfg.enRunMode = UartMskMode3;     //模式3
    •     if(Parity == UartMskEven)
    •         {
    •                 stcCfg.enMmdorCk = UartMskEven;      //偶校验
    •         }
    •         else if(Parity == UartMskOdd)
    •         {
    •                 stcCfg.enMmdorCk = UartMskOdd;       //奇校验
    •         }
    •         else
    •         {
    •                 stcCfg.enRunMode = UartMskMode1;     //模式1,奇偶检验无效
    •         }
    •     stcCfg.enStopBit = UartMsk1bit;      //1位停止位
    •     stcCfg.stcBaud.u32Baud = baud;       //波特率9600
    •     stcCfg.stcBaud.enClkDiv = UartMsk8Or16Div;         //通道采样分频配置
    •     stcCfg.stcBaud.u32Pclk = Sysctrl_GetPClkFreq();    //获得外设时钟(PCLK)频率值
    •     Uart_Init(M0P_UART0, &stcCfg);       //串口初始化

    •     Uart_ClrStatus(M0P_UART0,UartRC);    //清接收请求
    •     Uart_ClrStatus(M0P_UART0,UartTC);    //清发送请求
    •     Uart_EnableIrq(M0P_UART0,UartRxIrq); //使能串口接收中断
    •     //Uart_EnableIrq(M0P_UART0,UartTxIrq); //使能串口发送中断
    •         //使能DMA发送, DMA相关通道使能后,如果Tx Buff为空,会立马启动传输
    •         Uart_EnableFunc(M0P_UART0,UartDmaTxFunc);
    • }

    [color=rgb(51, 102, 153) !important]复制代码

    3. 编写UART中断函数
    215642mwepcieci3fdercp.png.thumb.jpg

    在这里采用了循环数组接收,没有使用队列,可以省点资源,效果差不多,数组处理更方便。

    3. Timer定时器,这里选用2ms周期中断,并通过UART中断中启动,在Timer中断中关闭。

    • #include "bsp_timer.h"

    • #include "bsp_uart.h"


    • SemaphoreHandle_t BinSem_UART_Idle;


    • //Timer3 配置,用于uart0 的空闲中断
    • void BSP_Timer3_init(uint16_t u16Period)
    • {
    •     uint16_t                    u16ArrValue;
    •     uint16_t                    u16CntValue;
    •     stc_tim3_mode0_cfg_t     stcTim3BaseCfg;
    •     //结构体初始化清零
    •     DDL_ZERO_STRUCT(stcTim3BaseCfg);

    •     Sysctrl_SetPeripheralGate(SysctrlPeripheralTim3, TRUE); //Base Timer外设时钟使能

    •     stcTim3BaseCfg.enWorkMode = Tim3WorkMode0;              //定时器模式
    •     stcTim3BaseCfg.enCT       = Tim3Timer;                  //定时器功能,计数时钟为内部PCLK
    •     stcTim3BaseCfg.enPRS      = Tim3PCLKDiv32;              //PCLK/32
    •     stcTim3BaseCfg.enCntMode  = Tim316bitArrMode;           //自动重载16位计数器/定时器
    •     stcTim3BaseCfg.bEnTog     = FALSE;
    •     stcTim3BaseCfg.bEnGate    = FALSE;
    •     stcTim3BaseCfg.enGateP    = Tim3GatePositive;

    •     Tim3_Mode0_Init(&stcTim3BaseCfg);                       //TIM3 的模式0功能初始化
    •     u16ArrValue = 0x10000 - u16Period ;
    •     Tim3_M0_ARRSet(u16ArrValue);                            //设置重载值(ARR = 0x10000 - 周期)
    •     u16CntValue = 0x10000 - u16Period;
    •     Tim3_M0_Cnt16Set(u16CntValue);                          //设置计数初值
    •     Tim3_ClearIntFlag(Tim3UevIrq);                          //清中断标志
    •     Tim3_Mode0_EnableIrq();                                 //使能TIM3中断(模式0时只有一个中断)
    •     EnableNvic(TIM3_IRQn, IrqLevel3, TRUE);                 //TIM3 开中断
    •         //Tim3_M0_Run();   //TIM3 运行
    • }

    • /*去初始化,进低功耗功耗前调用此接口*/
    • void BSP_Timer3_Deinit(void)
    • {
    •         stc_tim3_mode0_cfg_t     stcTim3BaseCfg;
    •     DDL_ZERO_STRUCT(stcTim3BaseCfg);                         //结构体初始化清零
    •         Tim3_Mode0_Init(&stcTim3BaseCfg);
    •         Tim3_ClearIntFlag(Tim3UevIrq);                //清中断标志
    •     Tim3_Mode0_DisableIrq();
    •         Tim3_M0_Stop();
    • }

    [color=rgb(51, 102, 153) !important]复制代码

    220626z3akmkaf0moio9a9.png.thumb.jpg

    UART和Timer如何配合使用,上面的函数已经给出了。
    最后,中断中已经给出了信号量,后续如何处理呢?
    用一个任务去接收信号就好了:

    220641fwbwb6q4gcgg0qsj.png.thumb.jpg
    实验效果:
    220659efop7mn64zo6ojol.png.thumb.jpg

    源码整理后的附件: