STM32F429第十四篇之串口实验详解

文章目录

  • 摘要
  • 硬件部分
  • 软件部分
    • 主函数
    • UART
      • 配置
        • HAL_UART_Init
        • HAL_UART_MspInit
        • UART_SetConfig
        • HAL_UART_Receive_IT
      • 发送
        • 重构printf
        • HAL_UART_Transmit
      • 接收
        • USART1_IRQHandler
        • HAL_UART_IRQHandler
        • UART_Receive_IT
        • HAL_UART_RxCpltCallback
    • RCC
      • HAL_RCC_GetxCLKxFreq
      • HAL_RCC_GetSysClockFreq

摘要

本文主要以正点原子串口通信实验为基础,讲解其中涉及的HAL库函数的具体实现原理。本实验主要实现的功能是通过阻塞方式,实现串口数据的发送;通过中断方式,实现串口数据的接收。本实验大致结果如下:

  • STM32会循环向PC串口助手发送提示信息。
  • 当STM32接收到来自PC串口助手的串口数据时,会将接收的信息返回发送给串口助手。

本文主要参考文献:

  • 正点原子.STM32F429 开发指南(HAL 库版)
  • ST.RM0090 Reference manual
  • 刘火良,杨森.STM32库开发实战指南.机械工业出版社

硬件部分

STM32F429第十四篇之串口实验详解_第1张图片

这是正点原子开发板关于串口部分硬件原理图。该原理图中TXD与RXD分别与PA10和PA9管脚向量。其电平为TTL电平。所以,该原理图就比较容易理解了。通过CH340芯片,将USB转换为TTL电平的串口。

这样就可以实现电脑中串口助手与ARM串口之间的通信了.

软件部分

主函数

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
/************************************************
 ALIENTEK 阿波罗STM32F429开发板实验3
 串口实验-HAL库函数版
 技术支持:www.openedv.com
 淘宝店铺:http://eboard.taobao.com 
 关注微信公众平台微信号:"正点原子",免费获取STM32资料。
 广州市星翼电子科技有限公司  
 作者:正点原子 @ALIENTEK
************************************************/

int myFlag=0;

int main(void)
{
    u8 len = 0;                      //初始化数据
    u16 times = 0;                   //计数次数
    HAL_Init();                      //初始化HAL库
    Stm32_Clock_Init(360, 25, 2, 8); //设置时钟,180Mhz
    delay_init(180);                 //初始化延时函数
    uart_init(115200);               //初始化USART

    LED_Init(); //初始化LED
    KEY_Init(); //初始化按键

    while (1)
    {

        if (USART_RX_STA & 0x8000)
        {
            len = USART_RX_STA & 0x3fff; //得到此次接收到的数据长度
            printf("\r\n您发送的消息为:\r\n");
            HAL_UART_Transmit(&UART1_Handler, (uint8_t *)USART_RX_BUF, len, 1000); //发送接收到的数据
            while (__HAL_UART_GET_FLAG(&UART1_Handler, UART_FLAG_TC) != SET)
                ;               //等待发送结束
            printf("\r\n\r\n"); //插入换行
            USART_RX_STA = 0;           //接收完成,状态位直接清零
        }
        else
        {
            times++;
            if (times % 5000 == 0)
            {
                printf("\r\nALIENTEK 阿波罗STM32F429开发板 串口实验\r\n");
                printf("正点原子@ALIENTEK\r\n\r\n\r\n");
            }
            if (times % 200 == 0)
                printf("请输入数据,以回车键结束\r\n");
            if (times % 30 == 0)
                LED0 = !LED0; //闪烁LED,提示系统正在运行.
            delay_ms(10);
        }
    }
}

此主函数由正点原子提供,与串口相关的部分可以分成三个部分:

  1. 串口配置,初始化——主要由HAL_UART_Init函数实现。
  2. 串口发送——主要由函数printf和函数HAL_UART_Transmit实现。
  3. 串口接收——主要通过中断服务函数USART1_IRQHandler实现。

下面分条详细讲解。

UART

配置

关于串口的配置部分,主要涉及以下几个重要函数:

  • HAL_UART_Init
  • HAL_UART_MspInit
  • UART_SetConfig
  • HAL_UART_Receive_IT

依次分别介绍如下:

HAL_UART_Init
/**
* @brief  Initializes the UART mode according to the specified parameters in
*         the UART_InitTypeDef and create the associated handle.
* @param  huart: pointer to a UART_HandleTypeDef structure that contains
*                the configuration information for the specified UART module.
* @retval HAL status
*/
HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart)
{
    /*********************************1.检测参数***********************************************/
    /* Check the UART handle allocation */
    if(huart == NULL)//防止空指针
    {
        return HAL_ERROR;
    }

           /* Check the parameters */
    if(huart->Init.HwFlowCtl != UART_HWCONTROL_NONE)
    {
        /* The hardware flow control is available only for USART1, USART2, USART3 and USART6 */
        assert_param(IS_UART_HWFLOW_INSTANCE(huart->Instance));
        assert_param(IS_UART_HARDWARE_FLOW_CONTROL(huart->Init.HwFlowCtl));
    }
    else
    {
        assert_param(IS_UART_INSTANCE(huart->Instance));
    }
    assert_param(IS_UART_WORD_LENGTH(huart->Init.WordLength));
    assert_param(IS_UART_OVERSAMPLING(huart->Init.OverSampling));

    /*********************************2.初始化底层资源***********************************************/
    if(huart->State == HAL_UART_STATE_RESET)//外设并未被初始化
    {
        /* Allocate lock resource and initialize it */
        huart->Lock = HAL_UNLOCKED;	//解开锁以方便配置初始化资源
        /* Init the low level hardware */
        HAL_UART_MspInit(huart);//初始化底层资源
    }

    huart->State = HAL_UART_STATE_BUSY;//切换串口的状态为BUSY

    /*********************************3.改变串口配置参数***********************************************/
    /* Disable the peripheral */
    __HAL_UART_DISABLE(huart);//宏定义,禁用该串口

    /* Set the UART Communication parameters */
    UART_SetConfig(huart);//配置串口参数

    /* In asynchronous mode, the following bits must be kept cleared:
       - LINEN and CLKEN bits in the USART_CR2 register,
       - SCEN, HDSEL and IREN  bits in the USART_CR3 register.*/
    huart->Instance->CR2 &= ~(USART_CR2_LINEN | USART_CR2_CLKEN);//禁止LIN,禁止SCLK管脚
    huart->Instance->CR3 &= ~(USART_CR3_SCEN | USART_CR3_HDSEL | USART_CR3_IREN);//禁止智能卡模式,禁止半双工模式,禁止IrDA模式

    /* Enable the peripheral */
    __HAL_UART_ENABLE(huart);//使能该串口

    /*********************************4.初始化串口状态***********************************************/
    /* Initialize the UART state */
    huart->ErrorCode = HAL_UART_ERROR_NONE;//清理错误信息
    huart->State = HAL_UART_STATE_READY;//状态转为READY

    return HAL_OK;
}

通过上文源代码,可以知道,在HAL_UART_Init()函数中,功能大致可以分成四个部分:

  1. 参数的检测
  2. 初始化底层资源
  3. 改变串口配置参数
  4. 初始化串口状态

其中,在第二步中,初始化底层资源需要注意:

  1. 只有当该串口未被初始化过,才会配置底层资源。
  2. 配置底层资源使用的函数 HAL_UART_MspInit()需要自己重新实现。该函数主要配置串口与其对应的GPIO之间的关系。具体详解参考后文。

在第三步中,改变串口配置参数需要注意:

  1. 改变串口的配置参数之前和之后需要需要分别使用对应的宏定义禁用该串口,和使能该串口。具体的宏定义如下:
#define __HAL_UART_DISABLE(__HANDLE__)              ((__HANDLE__)->Instance->CR1 &=  ~USART_CR1_UE)
#define __HAL_UART_ENABLE(__HANDLE__)               ((__HANDLE__)->Instance->CR1 |=  USART_CR1_UE)
  1. 配置串口参数调用函数 UART_SetConfig() ,该函数详解参考后文。
  2. 配置完串口参数之后,在异步通信中,需要禁用CR2和CR3中的一些寄存器。
HAL_UART_MspInit
/**
 * @brief 配置串口底层资源
 * @note  此函数将会被HAL_UART_Init()调用
 * @param {UART_HandleTypeDef} *huart 串口句柄
 * @retval 无
 **/
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
    //GPIO端口设置
    GPIO_InitTypeDef GPIO_Initure;

    if (huart->Instance == USART1) //如果是串口1,进行串口1 MSP初始化
    {
        __HAL_RCC_GPIOA_CLK_ENABLE();  //使能GPIOA时钟
        __HAL_RCC_USART1_CLK_ENABLE(); //使能USART1时钟

        GPIO_Initure.Pin = GPIO_PIN_9;            //PA9
        GPIO_Initure.Mode = GPIO_MODE_AF_PP;      //复用推挽输出
        GPIO_Initure.Pull = GPIO_PULLUP;          //上拉
        GPIO_Initure.Speed = GPIO_SPEED_FAST;     //高速
        GPIO_Initure.Alternate = GPIO_AF7_USART1; //复用为USART1
        HAL_GPIO_Init(GPIOA, &GPIO_Initure);      //初始化PA9

        GPIO_Initure.Pin = GPIO_PIN_10;      //PA10
        HAL_GPIO_Init(GPIOA, &GPIO_Initure); //初始化PA10

#if EN_USART1_RX
        HAL_NVIC_EnableIRQ(USART1_IRQn);         //使能USART1中断通道
        HAL_NVIC_SetPriority(USART1_IRQn, 3, 3); //抢占优先级3,子优先级3
#endif
    }
}

在底层初始化中,大致可以分成三块内容:

  1. 使能时钟
  2. 配置GPIO信息
  3. 使能中断与配置优先级
UART_SetConfig
/**
  * @brief  Configures the UART peripheral.
  * @param  huart: pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval None
  */
static void UART_SetConfig(UART_HandleTypeDef *huart)
{
    uint32_t tmpreg = 0x00;
    /*********************************1.参数检测***********************************************/
    /* Check the parameters */
    assert_param(IS_UART_BAUDRATE(huart->Init.BaudRate));
    assert_param(IS_UART_STOPBITS(huart->Init.StopBits));
    assert_param(IS_UART_PARITY(huart->Init.Parity));
    assert_param(IS_UART_MODE(huart->Init.Mode));

    /*********************************2.CR2配置***********************************************/
    /*-------------------------- USART CR2 Configuration -----------------------*/
    tmpreg = huart->Instance->CR2;//获取原寄存器数据

    /* Clear STOP[13:12] bits */
    tmpreg &= (uint32_t)~((uint32_t)USART_CR2_STOP);//将值转换为32位且取反,再通过与运算进行清零

    /* Configure the UART Stop Bits: Set STOP[13:12] bits according to huart->Init.StopBits value */
    tmpreg |= (uint32_t)huart->Init.StopBits;//将清零后的停止位相关寄存器写入现在数值

    /* Write to USART CR2 */
    huart->Instance->CR2 = (uint32_t)tmpreg;//最后,将临时值写入到寄存器中

    /*********************************3.CR1配置***********************************************/
    /*-------------------------- USART CR1 Configuration -----------------------*/
    tmpreg = huart->Instance->CR1;//获取寄存器原有值

    /* Clear M, PCE, PS, TE and RE bits */
    tmpreg &= (uint32_t)~((uint32_t)(USART_CR1_M | USART_CR1_PCE | USART_CR1_PS | USART_CR1_TE | \
                                     USART_CR1_RE | USART_CR1_OVER8));//清除对应的数据位

    /* Configure the UART Word Length, Parity and mode:
       Set the M bits according to huart->Init.WordLength value(字长)
       Set PCE and PS bits according to huart->Init.Parity value(校验)
       Set TE and RE bits according to huart->Init.Mode value(收发模式)
       Set OVER8 bit according to huart->Init.OverSampling value (过采样)*/
    tmpreg |= (uint32_t)huart->Init.WordLength | huart->Init.Parity | huart->Init.Mode | huart->Init.OverSampling;//在将新设的数据写入寄存器临时值

    /* Write to USART CR1 */
    huart->Instance->CR1 = (uint32_t)tmpreg;//最后将临时值写入寄存器

    /*********************************4.CR3配置***********************************************/
    /*-------------------------- USART CR3 Configuration -----------------------*/
    tmpreg = huart->Instance->CR3;//获取寄存器原有值

    /* Clear CTSE and RTSE bits */
    tmpreg &= (uint32_t)~((uint32_t)(USART_CR3_RTSE | USART_CR3_CTSE));//清除需要配置的寄存器位

    /* Configure the UART HFC: Set CTSE and RTSE bits according to huart->Init.HwFlowCtl value */
    tmpreg |= huart->Init.HwFlowCtl;//设置硬件控制

    /* Write to USART CR3 */
    huart->Instance->CR3 = (uint32_t)tmpreg;//将临时值写入寄存器

    /*********************************5.设置波特率***********************************************/
    /* Check the Over Sampling */
    if(huart->Init.OverSampling == UART_OVERSAMPLING_8)
    {
        /*-------------------------- USART BRR Configuration ---------------------*/
        if((huart->Instance == USART1) || (huart->Instance == USART6))
        {
            huart->Instance->BRR = UART_BRR_SAMPLING8(HAL_RCC_GetPCLK2Freq(), huart->Init.BaudRate);
        }
        else
        {
            huart->Instance->BRR = UART_BRR_SAMPLING8(HAL_RCC_GetPCLK1Freq(), huart->Init.BaudRate);
        }
    }
    else
    {
        /*-------------------------- USART BRR Configuration ---------------------*/
        if((huart->Instance == USART1) || (huart->Instance == USART6))
        {
            huart->Instance->BRR = UART_BRR_SAMPLING16(HAL_RCC_GetPCLK2Freq(), huart->Init.BaudRate);
        }
        else
        {
            huart->Instance->BRR = UART_BRR_SAMPLING16(HAL_RCC_GetPCLK1Freq(), huart->Init.BaudRate);
        }
    }
}

根据源代码可知,该函数大体可以分成5各部分:

  1. 参数判断
  2. CR2寄存器配置
  3. CR1寄存器配置
  4. CR3寄存器配置
  5. 设置波特率

其中,第2步到第4步其实都是一样的。对于寄存器中部分位的设置可以分成以下步骤:

  1. 获取原有寄存器值。
  2. 将寄存器值中需要配置的数据位清零。
  3. 将新配置的数据写入。
  4. 将临时值写入寄存器。

在第5步配置过采样中,需要注意:

  1. 因为串口1和串口6和其他的串口并不是挂在同一个总线上,所以,要分成两种情况配置。
  2. 使用到函数为HAL_RCC_GetPCLK1Freq()HAL_RCC_GetPCLK2Freq()。关于此部分内容,可以参考后文RCC部分。
  3. 使用到宏为UART_BRR_SAMPLING8()UART_BRR_SAMPLING16()
#define UART_DIV_SAMPLING16(_PCLK_, _BAUD_)            (((_PCLK_)*25)/(4*(_BAUD_)))
#define UART_DIVMANT_SAMPLING16(_PCLK_, _BAUD_)        (UART_DIV_SAMPLING16((_PCLK_), (_BAUD_))/100)
#define UART_DIVFRAQ_SAMPLING16(_PCLK_, _BAUD_)        (((UART_DIV_SAMPLING16((_PCLK_), (_BAUD_)) - (UART_DIVMANT_SAMPLING16((_PCLK_), (_BAUD_)) * 100)) * 16 + 50) / 100)
#define UART_BRR_SAMPLING16(_PCLK_, _BAUD_)            ((UART_DIVMANT_SAMPLING16((_PCLK_), (_BAUD_)) << 4)|(UART_DIVFRAQ_SAMPLING16((_PCLK_), (_BAUD_)) & 0x0F))

#define UART_DIV_SAMPLING8(_PCLK_, _BAUD_)             (((_PCLK_)*25)/(2*(_BAUD_)))
#define UART_DIVMANT_SAMPLING8(_PCLK_, _BAUD_)         (UART_DIV_SAMPLING8((_PCLK_), (_BAUD_))/100)
#define UART_DIVFRAQ_SAMPLING8(_PCLK_, _BAUD_)         (((UART_DIV_SAMPLING8((_PCLK_), (_BAUD_)) - (UART_DIVMANT_SAMPLING8((_PCLK_), (_BAUD_)) * 100)) * 16 + 50) / 100)
#define UART_BRR_SAMPLING8(_PCLK_, _BAUD_)             ((UART_DIVMANT_SAMPLING8((_PCLK_), (_BAUD_)) << 4)|(UART_DIVFRAQ_SAMPLING8((_PCLK_), (_BAUD_)) & 0x0F))

上述宏主要作用将根据波特率计算分频数,且将波特率写入对应的寄存器。以16过采样为例(默认),该功能共分成四个步骤:

  1. 计算分频数
  2. 获取分频数的整数部分
  3. 获取分频数的小数部分
  4. 将分频数写入对应的寄存器

根据官方文档,可以获得波特率的求取公式为:
B U A D = f c k 8 × ( 2 − O V E R 8 ) × D I V BUAD = \frac {f_{ck}} {8\times (2 - OVER8) \times DIV} BUAD=8×(2OVER8)×DIVfck
其中:

B U A D BUAD BUAD ——波特率。
f c k f_{ck} fck——总线频率。
O V E R 8 OVER8 OVER8——寄存器标志。
D I V DIV DIV——分频数。

所以,当采用16倍过采样时,可以得到分频数计算公式为:
D I V = f c k 8 × 2 × B U A D DIV = \frac {f_{ck}} {8\times 2 \times BUAD} DIV=8×2×BUADfck

以F429的USART1为例,采用16倍过采样,取波特率为115200,则可以获取分频数为:

D I V = 90 × 1 0 6 8 × 2 × 115200 = 48.828125 DIV = \frac{90\times 10^6} {8\times 2 \times 115200}=48.828125 DIV=8×2×11520090×106=48.828125

因此,在配置BRR寄存器时,整数部分配置为48。小数部分配置为 0.828125 × 16 = 13.25 ≈ 13 0.828125\times16=13.25\approx13 0.828125×16=13.2513。综上,整数部分配置为48,小数部分配置为13。

而官方的宏定义的计算方法为:

第一步:

#define UART_DIV_SAMPLING16(_PCLK_, _BAUD_)            (((_PCLK_)*25)/(4*(_BAUD_)))

先计算分频数的100倍取整,也就是保留分频数的2位小数。其公式表达形式为:
s 1 = 100 × f c k 16 × B U A D = 25 × f c k 4 × B U A D = 48.82 × 100 = 4882 s_1=100\times \frac{f_{ck}}{16\times BUAD}=\frac{25\times f_{ck}}{4\times BUAD}=48.82\times100=4882 s1=100×16×BUADfck=4×BUAD25×fck=48.82×100=4882

第二步:

#define UART_DIVMANT_SAMPLING16(_PCLK_, _BAUD_)        (UART_DIV_SAMPLING16((_PCLK_), (_BAUD_))/100)

获取分频数整数部分。其公式表达形式为:
s 2 = s 1 100 = 48 s_2=\frac {s_1}{100}=48 s2=100s1=48

第三步:

#define UART_DIVFRAQ_SAMPLING16(_PCLK_, _BAUD_)        (((UART_DIV_SAMPLING16((_PCLK_), (_BAUD_)) - (UART_DIVMANT_SAMPLING16((_PCLK_), (_BAUD_)) * 100)) * 16 + 50) / 100)

获取分频数小数部分。其公式表达形式为:
s 3 = ( s 1 − s 2 × 100 ) × 16 + 50 100 = 13 s_3=\frac{(s_1-s_2\times100)\times 16 +50}{100}=13 s3=100(s1s2×100)×16+50=13
其本质等于
s 3 = ( s 1 100 − s 2 ) × 16 + 0.5 = 13 s_3=(\frac{s_1}{100}-s_2)\times 16 +0.5=13 s3=(100s1s2)×16+0.5=13
这样看上去就比较明确了,先获得小数部分,在将其×16倍获取应该填写到寄存器内部的值,最后+0.5是为了保持四舍五入。

第四步:

#define UART_BRR_SAMPLING16(_PCLK_, _BAUD_)            ((UART_DIVMANT_SAMPLING16((_PCLK_), (_BAUD_)) << 4)|(UART_DIVFRAQ_SAMPLING16((_PCLK_), (_BAUD_)) & 0x0F))

分别将整数部分和小数部分写入BRR寄存器。

HAL_UART_Receive_IT
/**
  * @brief  Receives an amount of data in non blocking mode
  * @param  huart: pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @param  pData: Pointer to data buffer
  * @param  Size: Amount of data to be received
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
    uint32_t tmp = 0;

    tmp = huart->State;
    if((tmp == HAL_UART_STATE_READY) || (tmp == HAL_UART_STATE_BUSY_TX))
    {
        if((pData == NULL ) || (Size == 0))//若接收数组为空,则返回错误
        {
            return HAL_ERROR;
        }

        /* Process Locked */
        __HAL_LOCK(huart);//将串口句柄上锁,即很简单的将huart中的成员LOCK值修改为HAL_LOCKED

		/*通过结构体中的指针修改参数传入的变量*/
        huart->pRxBuffPtr = pData;
        huart->RxXferSize = Size;
        huart->RxXferCount = Size;

        huart->ErrorCode = HAL_UART_ERROR_NONE;//清除错误
        /* Check if a transmit process is ongoing or not */
        if(huart->State == HAL_UART_STATE_BUSY_TX)//状态转换
        {
            huart->State = HAL_UART_STATE_BUSY_TX_RX;
        }
        else
        {
            huart->State = HAL_UART_STATE_BUSY_RX;
        }

        /* Enable the UART Parity Error Interrupt */
        __HAL_UART_ENABLE_IT(huart, UART_IT_PE);//使能校验错误中断

        /* Enable the UART Error Interrupt: (Frame error, noise error, overrun error) */
        __HAL_UART_ENABLE_IT(huart, UART_IT_ERR);//使能错误中断

        /* Process Unlocked */
        __HAL_UNLOCK(huart);//释放资源

        /* Enable the UART Data Register not empty Interrupt */
        __HAL_UART_ENABLE_IT(huart, UART_IT_RXNE);//使能接收中断

        return HAL_OK;
    }
    else
    {
        return HAL_BUSY;
    }
}

通过阅读源程序,可以知道该函数的主要作用是:

  1. 初始化huart中的接收数组的指针,大小,与当前计数值。
  2. 将串口的接收状态转化为BUSY。
  3. 使能与串口接收相关的中断。
  4. 初始化串口接收错误为无。

其中,需要注意的是,HAL的上锁方法也就是将句柄的锁成员变量的值转化为LOCKED。其具体的实现过程如下所示:

 #define __HAL_LOCK(__HANDLE__)                                           \
                                do{                                        \
                                    if((__HANDLE__)->Lock == HAL_LOCKED)   \
                                    {                                      \
                                       return HAL_BUSY;                    \
                                    }                                      \
                                    else                                   \
                                    {                                      \
                                       (__HANDLE__)->Lock = HAL_LOCKED;    \
                                    }                                      \
                                  }while (0)

  #define __HAL_UNLOCK(__HANDLE__)                                          \
                                  do{                                       \
                                      (__HANDLE__)->Lock = HAL_UNLOCKED;    \
                                    }while (0)

发送

关于数据发送部分,本例程主要涉及两个方面:

  • printf()函数的重构。
  • HAL_UART_Transmit()函数的使用。

下面就重点介绍这两个部分.

重构printf

printf函数的重构本文介绍两种方法,分别采用来自于正点原子和野火的源程序。

正点原子的版本源程序如下:

//禁止使用半主机模式
#pragma import(__use_no_semihosting)

//以下三处定义必须有,否则编译器会报错,具体含义并不清楚
struct __FILE
{
    int handle;
};
FILE __stdout;
void _sys_exit(int x)
{
    x = x;
}

/**
 * @brief   fputc重构函数
 * @note    该函数会被printf()自动调用
 * @param {int} ch  打印值
 * @param {FILE} *f 在函数内部并没有使用
 * @retval 
 **/
int fputc(int ch, FILE *f)
{
    while ((USART1->SR & 0X40) == 0)
        ;                //判断上一个数据是否发送完
    USART1->DR = (u8)ch; //发送现有数据
    return ch;           //返回发送值
}

而在野火的书中,前面禁止半主机模式内容全部省略,而是通过配置keil使用Use MicroLIB项来避免半主机模式。该资源库代码更少,占用更少的资源。

STM32F429第十四篇之串口实验详解_第2张图片
然后直接重构fputc函数即可,如下所示:

/**
 * @brief   fputc重构函数
 * @note    该函数会被printf()自动调用
 * @param {int} ch  打印值
 * @param {FILE} *f 在函数内部并没有使用
 * @retval 
 **/
int fputc(int ch, FILE *f)
{
    while ((USART1->SR & 0X40) == 0)
        ;                //判断上一个数据是否发送完
    USART1->DR = (u8)ch; //发送现有数据
    return ch;           //返回发送值
}

在重构代码中,直接对寄存器操作,可以提高代码的效率。

HAL_UART_Transmit
/**
  * @brief  Sends an amount of data in blocking mode. 
  * @param  huart: pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @param  pData: Pointer to data buffer
  * @param  Size: Amount of data to be sent
  * @param  Timeout: Timeout duration  
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
  uint16_t* tmp;
  uint32_t tmp1 = 0;
  
  tmp1 = huart->State;//获取串口状态
  if((tmp1 == HAL_UART_STATE_READY) || (tmp1 == HAL_UART_STATE_BUSY_RX))//判断串口发送是否空闲
  {
    if((pData == NULL ) || (Size == 0)) //若发送内容为空,则返回错误
    {
      return  HAL_ERROR;
    }
    
    /* Process Locked */
    __HAL_LOCK(huart);//将串口句柄上锁
    
    huart->ErrorCode = HAL_UART_ERROR_NONE;//清除串口错误
    /* 改变串口发送的状态为BUSY */
	/* Check if a non-blocking receive process is ongoing or not */
    if(huart->State == HAL_UART_STATE_BUSY_RX) 
    {
      huart->State = HAL_UART_STATE_BUSY_TX_RX;
    }
    else
    {
      huart->State = HAL_UART_STATE_BUSY_TX;
    }

	/* 重构句柄的发送缓存的大小 */
    huart->TxXferSize = Size;
    huart->TxXferCount = Size;



    while(huart->TxXferCount > 0)//循环发送
    {
      huart->TxXferCount--;//先自减,再发送
      if(huart->Init.WordLength == UART_WORDLENGTH_9B)//若字长为9
      {
        if(UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, Timeout) != HAL_OK)//判断是否超时
        { 
          return HAL_TIMEOUT;
        }
        tmp = (uint16_t*) pData;//获得发送数组的指针
        huart->Instance->DR = (*tmp & (uint16_t)0x01FF);

        if(huart->Init.Parity == UART_PARITY_NONE)//若是无校验,则发送的数据为9位。
        {
          pData +=2;
        }
        else//若有校验,发送的数据位8位。
        { 
          pData +=1;
        }

      } 
      else//若字长为8
      {
        if(UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, Timeout) != HAL_OK)
        {
          return HAL_TIMEOUT;
        }
        huart->Instance->DR = (*pData++ & (uint8_t)0xFF);
      } 
    }
    
    if(UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TC, RESET, Timeout) != HAL_OK)
    { 
      return HAL_TIMEOUT;
    }
    
	/* 串口发送的状态恢复 */
    /* Check if a non-blocking receive process is ongoing or not */
    if(huart->State == HAL_UART_STATE_BUSY_TX_RX) 
    {
      huart->State = HAL_UART_STATE_BUSY_RX;
    }
    else
    {
      huart->State = HAL_UART_STATE_READY;
    }
    
    /* Process Unlocked */
    __HAL_UNLOCK(huart);//解锁
    
    return HAL_OK;
  }
  else
  {
    return HAL_BUSY;   
  }
}

通过源程序可知,本函数的主要功能就是将一个缓存数组中的数据发送出去。在无校验的时候,该串口支持发送9位二进制数。在此函数中,使用了一个函数判断是否超时,该函数如下所示:

UART_WaitOnFlagUntilTimeout

/**
  * @brief  This function handles UART Communication Timeout.
  * @param  huart: pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @param  Flag: specifies the UART flag to check.
  * @param  Status: The new Flag status (SET or RESET).
  * @param  Timeout: Timeout duration
  * @retval HAL status
  */
static HAL_StatusTypeDef UART_WaitOnFlagUntilTimeout(UART_HandleTypeDef *huart, uint32_t Flag, FlagStatus Status, uint32_t Timeout)
{
  uint32_t tickstart = 0;

  /* Get tick */ 
  tickstart = HAL_GetTick();

  /* Wait until flag is set */
  if(Status == RESET)
  {
    while(__HAL_UART_GET_FLAG(huart, Flag) == RESET)//标志位没有置位
    {
      /* Check for the Timeout */
      if(Timeout != HAL_MAX_DELAY)
      {
        if((Timeout == 0)||((HAL_GetTick() - tickstart ) > Timeout))//已经超时
        {
          /* Disable TXE, RXNE, PE and ERR (Frame error, noise error, overrun error) interrupts for the interrupt process */
          __HAL_UART_DISABLE_IT(huart, UART_IT_TXE);
          __HAL_UART_DISABLE_IT(huart, UART_IT_RXNE);
          __HAL_UART_DISABLE_IT(huart, UART_IT_PE);
          __HAL_UART_DISABLE_IT(huart, UART_IT_ERR);

          huart->State= HAL_UART_STATE_READY;

          /* Process Unlocked */
          __HAL_UNLOCK(huart);

          return HAL_TIMEOUT;
        }
      }
    }
  }
  else//若状态为SET
  {
    while(__HAL_UART_GET_FLAG(huart, Flag) != RESET)//获得标志位已经置位
    {
      /* Check for the Timeout */
      if(Timeout != HAL_MAX_DELAY)
      {
        if((Timeout == 0)||((HAL_GetTick() - tickstart ) > Timeout))
        {
          /* Disable TXE, RXNE, PE and ERR (Frame error, noise error, overrun error) interrupts for the interrupt process */
          __HAL_UART_DISABLE_IT(huart, UART_IT_TXE);
          __HAL_UART_DISABLE_IT(huart, UART_IT_RXNE);
          __HAL_UART_DISABLE_IT(huart, UART_IT_PE);
          __HAL_UART_DISABLE_IT(huart, UART_IT_ERR);

          huart->State= HAL_UART_STATE_READY;

          /* Process Unlocked */
          __HAL_UNLOCK(huart);
        
          return HAL_TIMEOUT;
        }
      }
    }
  }
  return HAL_OK;
}

通过阅读源程序,可以很清楚的理解该函数的意义。其有四个参数,分别为:

  • huart:指定串口的句柄
  • Flag:指定需要判断的标志位。
  • Status:指定需要判断标志位的状态。
  • Timeout:指定标志位在状态下最大维持时间。

即指定串口的某个标志位在某状态下维持的时间,若超过该时间,则判断该串口超时。

其中判定标志位对应的状态,用的宏定义为:

/** @brief  Checks whether the specified UART flag is set or not.
  * @param  __HANDLE__: specifies the UART Handle.
  *         This parameter can be UARTx where x: 1, 2, 3, 4, 5, 6, 7 or 8 to select the USART or 
  *         UART peripheral.
  * @param  __FLAG__: specifies the flag to check.
  *        This parameter can be one of the following values:
  *            @arg UART_FLAG_CTS:  CTS Change flag (not available for UART4 and UART5)
  *            @arg UART_FLAG_LBD:  LIN Break detection flag
  *            @arg UART_FLAG_TXE:  Transmit data register empty flag
  *            @arg UART_FLAG_TC:   Transmission Complete flag
  *            @arg UART_FLAG_RXNE: Receive data register not empty flag
  *            @arg UART_FLAG_IDLE: Idle Line detection flag
  *            @arg UART_FLAG_ORE:  Overrun Error flag
  *            @arg UART_FLAG_NE:   Noise Error flag
  *            @arg UART_FLAG_FE:   Framing Error flag
  *            @arg UART_FLAG_PE:   Parity Error flag
  * @retval The new state of __FLAG__ (TRUE or FALSE).
  */

#define __HAL_UART_GET_FLAG(__HANDLE__, __FLAG__) (((__HANDLE__)->Instance->SR & (__FLAG__)) == (__FLAG__))   

接收

在本实验中,串口接收用的是中断接收。关于串口的接收,本文的一个重点。在这部分,正点原子使用的是官方库来实现。官方库在此处的处理方式比较复杂,效率比较低,不被推荐使用。但是整个程序的调用流程还是应该了解一下:

  1. 在串口的初始化部分,通过调用HAL_UART_Receive_IT函数完成串口接收中断相关内容的配置。
  2. 若ARM接收到字符,就会触发中断服务函数USART1_IRQHandler
  3. USART1_IRQHandler中,调用HAL_UART_IRQHandler函数,来判断中断源。
  4. 通过HAL_UART_IRQHandler函数,识别出接收中断,从而调用函数UART_Receive_IT
  5. 在调用函数UART_Receive_IT中,会将接收到的数据写入接收缓存数组中。当接收数组满之后,该函数会自动关闭接收相关中断
  6. 在函数UART_Receive_IT最后,还会调用HAL_UART_RxCpltCallback函数。
  7. 最后,返回USART1_IRQHandler函数中,继续执行剩下的语句。在其中,最终的是会调用HAL_UART_Receive_IT函数,再次使能接收相关的中断
USART1_IRQHandler
/**
 * @brief   串口1中断接收程序 
 * @note    无
 * @param {*}无
 * @retval  无
 **/
void USART1_IRQHandler(void)
{
    u32 timeout = 0;
    u32 maxDelay = 0x1FFFF;

    HAL_UART_IRQHandler(&UART1_Handler); //调用HAL库串口中断处理函数

    timeout = 0;
    while (HAL_UART_GetState(&UART1_Handler) != HAL_UART_STATE_READY) //等待串口就绪
    {
        timeout++; //超时处理
        if (timeout > maxDelay)//如果超时,不再等待
            break;
    }

    timeout = 0;
    while (HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, RXBUFFERSIZE) != HAL_OK) //重新配置串口接收中断参数
    {
        timeout++; //超时处理
        if (timeout > maxDelay)//若超时,不再等待
            break;
    }
}

这是串口1的接收中断服务函数,每次接收到数据,将会触发执行该函数。

在此函数中,共完成三项工作:

  1. 调用HAL库公用中断处理函数。
  2. 等待串口就绪。此处个人认为有点问题,因为在接收就绪时,发送不就绪时,此时的串口并非为HAL_UART_STATE_READY
  3. 重新配置串口接收中断参数。关于配置中断接收数据,使用的是函数HAL_UART_Receive_IT,在前文已经分析。

需要注意的是:该部分函数由正点原子提供。其超时处理的方法是,跳过此步,不再等待,继续执行后续步骤。

HAL_UART_IRQHandler
/**
  * @brief  This function handles UART interrupt request.
  * @param  huart: pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval None
  */
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
{

    uint32_t tmp1 = 0, tmp2 = 0;

    tmp1 = __HAL_UART_GET_FLAG(huart, UART_FLAG_PE);//奇偶校验错误标志(SR)
    tmp2 = __HAL_UART_GET_IT_SOURCE(huart, UART_IT_PE);//PE中断使能
    /* UART parity error interrupt occurred ------------------------------------*/
    if((tmp1 != RESET) && (tmp2 != RESET))//奇偶校验使能且发生中断
    {
        __HAL_UART_CLEAR_PEFLAG(huart);//清除错误标志位

        huart->ErrorCode |= HAL_UART_ERROR_PE;//改变句柄状态
    }

    tmp1 = __HAL_UART_GET_FLAG(huart, UART_FLAG_FE);//帧错误标志
    tmp2 = __HAL_UART_GET_IT_SOURCE(huart, UART_IT_ERR);//EIE错误中断
    /* UART frame error interrupt occurred -------------------------------------*/
    if((tmp1 != RESET) && (tmp2 != RESET))//帧错误发生
    {
        __HAL_UART_CLEAR_FEFLAG(huart);

        huart->ErrorCode |= HAL_UART_ERROR_FE;
    }

    tmp1 = __HAL_UART_GET_FLAG(huart, UART_FLAG_NE);//操作错误标志
    tmp2 = __HAL_UART_GET_IT_SOURCE(huart, UART_IT_ERR);//EIE错误中断
    /* UART noise error interrupt occurred -------------------------------------*/
    if((tmp1 != RESET) && (tmp2 != RESET))
    {
        __HAL_UART_CLEAR_NEFLAG(huart);

        huart->ErrorCode |= HAL_UART_ERROR_NE;
    }

    tmp1 = __HAL_UART_GET_FLAG(huart, UART_FLAG_ORE);//溢出错误
    tmp2 = __HAL_UART_GET_IT_SOURCE(huart, UART_IT_ERR);//EIE错误中断
    /* UART Over-Run interrupt occurred ----------------------------------------*/
    if((tmp1 != RESET) && (tmp2 != RESET))
    {
        __HAL_UART_CLEAR_OREFLAG(huart);

        huart->ErrorCode |= HAL_UART_ERROR_ORE;
    }

    tmp1 = __HAL_UART_GET_FLAG(huart, UART_FLAG_RXNE);//接收到数据标志置位
    tmp2 = __HAL_UART_GET_IT_SOURCE(huart, UART_IT_RXNE);//中断标志位使能
    /* UART in mode Receiver ---------------------------------------------------*/
    if((tmp1 != RESET) && (tmp2 != RESET))
    {
        UART_Receive_IT(huart);
    }

    tmp1 = __HAL_UART_GET_FLAG(huart, UART_FLAG_TXE);//发送数据标志
    tmp2 = __HAL_UART_GET_IT_SOURCE(huart, UART_IT_TXE);//发送数据中断使能
    /* UART in mode Transmitter ------------------------------------------------*/
    if((tmp1 != RESET) && (tmp2 != RESET))
    {
        UART_Transmit_IT(huart);
    }

    tmp1 = __HAL_UART_GET_FLAG(huart, UART_FLAG_TC);//传输完成
    tmp2 = __HAL_UART_GET_IT_SOURCE(huart, UART_IT_TC);
    /* UART in mode Transmitter end --------------------------------------------*/
    if((tmp1 != RESET) && (tmp2 != RESET))
    {
        UART_EndTransmit_IT(huart);
    }

    if(huart->ErrorCode != HAL_UART_ERROR_NONE)
    {
        /* Set the UART state ready to be able to start again the process */
        huart->State = HAL_UART_STATE_READY;

        HAL_UART_ErrorCallback(huart);
    }
}

该函数在串口中断响应函数中调用。

因为每个串口只对应一个中断,且只有一个中断响应函数。所以,在进入中断之后,需要首先判断此中断的属性,可以通过查询中断标志位得到。在该函数中,就做该工作。所以,通过此函数可以总结出每一个串口对应的中断源有:
STM32F429第十四篇之串口实验详解_第3张图片
此处忽略其他的中断,下文只重点研究接收器空中断。

涉及到的宏定义有:

/** @brief  Checks whether the specified UART interrupt has occurred or not.
  * @param  __HANDLE__: specifies the UART Handle.
  *         This parameter can be UARTx where x: 1, 2, 3, 4, 5, 6, 7 or 8 to select the USART or 
  *         UART peripheral.
  * @param  __IT__: specifies the UART interrupt source to check.
  *          This parameter can be one of the following values:
  *            @arg UART_IT_CTS: CTS change interrupt (not available for UART4 and UART5)
  *            @arg UART_IT_LBD: LIN Break detection interrupt
  *            @arg UART_IT_TXE: Transmit Data Register empty interrupt
  *            @arg UART_IT_TC:  Transmission complete interrupt
  *            @arg UART_IT_RXNE: Receive Data register not empty interrupt
  *            @arg UART_IT_IDLE: Idle line detection interrupt
  *            @arg USART_IT_ERR: Error interrupt
  * @retval The new state of __IT__ (TRUE or FALSE).
  */
#define __HAL_UART_GET_IT_SOURCE(__HANDLE__, __IT__) (((((__IT__) >> 28) == 1)? (__HANDLE__)->Instance->CR1:(((((uint32_t)(__IT__)) >> 28) == 2)? \
                                                      (__HANDLE__)->Instance->CR2 : (__HANDLE__)->Instance->CR3)) & (((uint32_t)(__IT__)) & UART_IT_MASK))

该宏定义是为了获得中断标志位的中断源是否使能。因为中断源的使能与否分布在三个标志位中,所以,参数__IT__的28位之上数据用来指示该中断源对应使能寄存器是哪个。而其低16位则对应着该中断使能位在寄存器上的位置。通过低位与运算,就可以知道该中断源是否使能。

/** @brief  Clear the UART PE pending flag.
  * @param  __HANDLE__: specifies the UART Handle.
  *         This parameter can be UARTx where x: 1, 2, 3, 4, 5, 6, 7 or 8 to select the USART or 
  *         UART peripheral.
  * @retval None
  */
#define __HAL_UART_CLEAR_PEFLAG(__HANDLE__)     \
  do{                                           \
    __IO uint32_t tmpreg = 0x00;                \
    tmpreg = (__HANDLE__)->Instance->SR;        \
    tmpreg = (__HANDLE__)->Instance->DR;        \
    UNUSED(tmpreg);                             \
  } while(0)									//注意此处无分号

根据参考手册可知,清除标志位的方法就是依次读取SR寄存器和DR寄存器。其中这段代码比较有意思的地方是其使用了do{}while(0)。这是因为:我们在编程的时候,习惯在语句后加引号。例如:

__HAL_UART_CLEAR_PEFLAG(__HANDLE__);

该句替换为:

do{                                           \
    __IO uint32_t tmpreg = 0x00;                \
    tmpreg = (__HANDLE__)->Instance->SR;        \
    tmpreg = (__HANDLE__)->Instance->DR;        \
    UNUSED(tmpreg);                             \
  } while(0);								//注意此处分号正好合适

保证了C语言的统一性。若缺少do while{0},改宏定义为:

#define __HAL_UART_CLEAR_PEFLAG(__HANDLE__)     \
 {                                           \
    __IO uint32_t tmpreg = 0x00;                \
    tmpreg = (__HANDLE__)->Instance->SR;        \
    tmpreg = (__HANDLE__)->Instance->DR;        \
    UNUSED(tmpreg);                             \
}									//注意此处无分号

则调用此宏定义就变成:

{                                           \
    __IO uint32_t tmpreg = 0x00;                \
    tmpreg = (__HANDLE__)->Instance->SR;        \
    tmpreg = (__HANDLE__)->Instance->DR;        \
    UNUSED(tmpreg);                             \
};							//此处分号多余错误		

那为什么不能不加中括号,直接将宏定义为4个单独语句不行吗?如下所示:

#define __HAL_UART_CLEAR_PEFLAG(__HANDLE__)     \
    __IO uint32_t tmpreg = 0x00;                \
    tmpreg = (__HANDLE__)->Instance->SR;        \
    tmpreg = (__HANDLE__)->Instance->DR;        \
    UNUSED(tmpreg);                             \	

答案是不行,若出现如下调用宏:

if(x==1)
	__HAL_UART_CLEAR_PEFLAG(__HANDLE__);

则编译器会解释为:

if(x==1)
	 __IO uint32_t tmpreg = 0x00;                \
tmpreg = (__HANDLE__)->Instance->SR;        \
tmpreg = (__HANDLE__)->Instance->DR;        \
UNUSED(tmpreg);  
;     

只有第一句是在受到条件语句约束的,与本意不符。

UART_Receive_IT
/**
  * @brief  Receives an amount of data in non blocking mode
  * @param  huart: pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval HAL status
  */
static HAL_StatusTypeDef UART_Receive_IT(UART_HandleTypeDef *huart)
{
    uint16_t* tmp;
    uint32_t tmp1 = 0;

    tmp1 = huart->State; //获取串口的状态
    if((tmp1 == HAL_UART_STATE_BUSY_RX) || (tmp1 == HAL_UART_STATE_BUSY_TX_RX))//接收状态正在进行,此处状态在HAL_UART_Receive_IT()中已经置位
    {
        /*****************************1.接收数据*********************************************/
        if(huart->Init.WordLength == UART_WORDLENGTH_9B)//字长为9位
        {
            tmp = (uint16_t*) huart->pRxBuffPtr;//接收数据位置在HAL_UART_Receive_IT()中置位
            if(huart->Init.Parity == UART_PARITY_NONE)//无校验
            {
                *tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x01FF);
                huart->pRxBuffPtr += 2;
            }
            else
            {
                *tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x00FF);
                huart->pRxBuffPtr += 1;
            }
        }
        else//字长为8位
        {
            if(huart->Init.Parity == UART_PARITY_NONE)//无校验
            {
                *huart->pRxBuffPtr++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);
            }
            else//有校验
            {
                *huart->pRxBuffPtr++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x007F);
            }
        }
        /*****************************2.判断接收缓存*********************************************/
        if(--huart->RxXferCount == 0)//接收缓存耗尽
        {
            __HAL_UART_DISABLE_IT(huart, UART_IT_RXNE);//停止接收中断

            /* Check if a transmit process is ongoing or not */
            if(huart->State == HAL_UART_STATE_BUSY_TX_RX) //若发送和接收同时进行
            {
                huart->State = HAL_UART_STATE_BUSY_TX;//不再接收
            }
            else//若只是在接收
            {
                /* Disable the UART Parity Error Interrupt */
                __HAL_UART_DISABLE_IT(huart, UART_IT_PE);//禁用校验错误中断

                /* Disable the UART Error Interrupt: (Frame error, noise error, overrun error) */
                __HAL_UART_DISABLE_IT(huart, UART_IT_ERR);//禁用错误中断

                huart->State = HAL_UART_STATE_READY;//状态转变
            }
            HAL_UART_RxCpltCallback(huart);//调用接收回调函数

            return HAL_OK;
        }
        return HAL_OK;
    }
    else
    {
        return HAL_BUSY;
    }
}

此函数大致分成两层:

  1. 接收数据且放入缓冲区。
  2. 判断缓冲区是否已经满了。若缓冲区已满,则关闭接收相关终端,且调用回调函数。
HAL_UART_RxCpltCallback
/**
 * @brief 串口接收回调函数
 * @note  在函数UART_Receive_IT中被调用
 * @param {UART_HandleTypeDef} *huart 串口句柄
 * @retval 无
 **/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART1) //如果是串口1
    {
        if ((USART_RX_STA & 0x8000) == 0) //接收未完成
        {
            if (USART_RX_STA & 0x4000) //接收到了0x0d(回车符)
            {
                if (aRxBuffer[0] != 0x0a) //换行符
                    USART_RX_STA = 0;     //接收错误,重新开始
                else
                    USART_RX_STA |= 0x8000; //接收完成了
            }
            else //还没收到0X0D
            {
                if (aRxBuffer[0] == 0x0d)
                    USART_RX_STA |= 0x4000;
                else
                {
                    USART_RX_BUF[USART_RX_STA & 0X3FFF] = aRxBuffer[0];
                    USART_RX_STA++;
                    if (USART_RX_STA > (USART_REC_LEN - 1))
                        USART_RX_STA = 0; //接收数据错误,重新开始接收
                }
            }
        }
    }
}

该函数主要是处理自定义的标志位USART_RX_STA的状态。

  • 若接收完成,也就是接收到\r\n,则会将最高位(16位)置位。
  • 若接收到\r,则将次高位(15位)置位。
  • 若接收出错,则将标志位清0。

RCC

关于RCC更多的内容,可以参考博客 < STM32F429第七篇之RCC(复位与时钟)>

与本实验相关的RCC内容是关于时钟频率的查询。

具体程序如下:

HAL_RCC_GetxCLKxFreq

/**
  * @brief  Returns the HCLK frequency     
  * @note   Each time HCLK changes, this function must be called to update the
  *         right HCLK value. Otherwise, any configuration based on this function will be incorrect.
  * 
  * @note   The SystemCoreClock CMSIS variable is used to store System Clock Frequency 
  *         and updated within this function
  * @retval HCLK frequency
  */
uint32_t HAL_RCC_GetHCLKFreq(void)
{
  SystemCoreClock = HAL_RCC_GetSysClockFreq() >> APBAHBPrescTable[(RCC->CFGR & RCC_CFGR_HPRE)>> POSITION_VAL(RCC_CFGR_HPRE)];
  return SystemCoreClock;
}

/**
  * @brief  Returns the PCLK1 frequency     
  * @note   Each time PCLK1 changes, this function must be called to update the
  *         right PCLK1 value. Otherwise, any configuration based on this function will be incorrect.
  * @retval PCLK1 frequency
  */
uint32_t HAL_RCC_GetPCLK1Freq(void)
{  
  /* Get HCLK source and Compute PCLK1 frequency ---------------------------*/
  return (HAL_RCC_GetHCLKFreq() >> APBAHBPrescTable[(RCC->CFGR & RCC_CFGR_PPRE1)>> POSITION_VAL(RCC_CFGR_PPRE1)]);
}

/**
  * @brief  Returns the PCLK2 frequency     
  * @note   Each time PCLK2 changes, this function must be called to update the
  *         right PCLK2 value. Otherwise, any configuration based on this function will be incorrect.
  * @retval PCLK2 frequency
  */
uint32_t HAL_RCC_GetPCLK2Freq(void)
{
  /* Get HCLK source and Compute PCLK2 frequency ---------------------------*/
  return (HAL_RCC_GetHCLKFreq()>> APBAHBPrescTable[(RCC->CFGR & RCC_CFGR_PPRE2)>> POSITION_VAL(RCC_CFGR_PPRE2)]);
} 

根据博客。可以获得下图:
STM32F429第十四篇之串口实验详解_第4张图片
根据该时钟图可知:
p c l k = h c l k p r e s c a l e r pclk=\frac{hclk}{prescaler} pclk=prescalerhclk
根据参考手册可知:

STM32F429第十四篇之串口实验详解_第5张图片

所以,关于程序的整体思路已经比较清晰了。就是首先获得HCLK的频率,然后通过PPRE寄存器获取到预分频信息,即可得到PCLK1和PCLK2。下面讲解以下细节:

  1. POSITION_VAL()宏的含义为:求取该32位二进制数从从最低位开始,共有多少个连续的0。关于该宏更多分析可以参考博客
  2. APBAHBPrescTable[]查询表的具体定义为:
const uint8_t APBAHBPrescTable[16] = {0, 0, 0, 0, 1, 2, 3, 4, 1, 2, 3, 4, 6, 7, 8, 9};

通过查表的方式将寄存器的值转化为需要将频率右移的位数。

  1. 关于获取HCLK频率值的函数HAL_RCC_GetHCLKFreq(),本质和上文介绍相同,此处不再详细展开。

HAL_RCC_GetSysClockFreq

/**
  * @brief  Returns the SYSCLK frequency
  *        
  * @note   The system frequency computed by this function is not the real 
  *         frequency in the chip. It is calculated based on the predefined 
  *         constant and the selected clock source:
  * @note     If SYSCLK source is HSI, function returns values based on HSI_VALUE(*)
  * @note     If SYSCLK source is HSE, function returns values based on HSE_VALUE(**)
  * @note     If SYSCLK source is PLL, function returns values based on HSE_VALUE(**) 
  *           or HSI_VALUE(*) multiplied/divided by the PLL factors.         
  * @note     (*) HSI_VALUE is a constant defined in stm32f4xx_hal_conf.h file (default value
  *               16 MHz) but the real value may vary depending on the variations
  *               in voltage and temperature.
  * @note     (**) HSE_VALUE is a constant defined in stm32f4xx_hal_conf.h file (default value
  *                25 MHz), user has to ensure that HSE_VALUE is same as the real
  *                frequency of the crystal used. Otherwise, this function may
  *                have wrong result.
  *                  
  * @note   The result of this function could be not correct when using fractional
  *         value for HSE crystal.
  *           
  * @note   This function can be used by the user application to compute the 
  *         baudrate for the communication peripherals or configure other parameters.
  *           
  * @note   Each time SYSCLK changes, this function must be called to update the
  *         right SYSCLK value. Otherwise, any configuration based on this function will be incorrect.
  *         
  *               
  * @retval SYSCLK frequency
  */
__weak uint32_t HAL_RCC_GetSysClockFreq(void)
{
  uint32_t pllm = 0, pllvco = 0, pllp = 0;
  uint32_t sysclockfreq = 0;

  /* Get SYSCLK source -------------------------------------------------------*/
  switch (RCC->CFGR & RCC_CFGR_SWS)
  {
    case RCC_CFGR_SWS_HSI:  /* HSI used as system clock source */
    {
      sysclockfreq = HSI_VALUE;
       break;
    }
    case RCC_CFGR_SWS_HSE:  /* HSE used as system clock  source */
    {
      sysclockfreq = HSE_VALUE;
      break;
    }
    case RCC_CFGR_SWS_PLL:  /* PLL used as system clock  source */
    {
      /* PLL_VCO = (HSE_VALUE or HSI_VALUE / PLLM) * PLLN
      SYSCLK = PLL_VCO / PLLP */
      pllm = RCC->PLLCFGR & RCC_PLLCFGR_PLLM;
      if(__HAL_RCC_GET_PLL_OSCSOURCE() != RCC_PLLSOURCE_HSI)
      {
        /* HSE used as PLL clock source */
        pllvco = ((HSE_VALUE / pllm) * ((RCC->PLLCFGR & RCC_PLLCFGR_PLLN) >> POSITION_VAL(RCC_PLLCFGR_PLLN)));
      }
      else
      {
        /* HSI used as PLL clock source */
        pllvco = ((HSI_VALUE / pllm) * ((RCC->PLLCFGR & RCC_PLLCFGR_PLLN) >> POSITION_VAL(RCC_PLLCFGR_PLLN)));    
      }
      pllp = ((((RCC->PLLCFGR & RCC_PLLCFGR_PLLP) >> POSITION_VAL(RCC_PLLCFGR_PLLP)) + 1 ) *2);
      
      sysclockfreq = pllvco/pllp;
      break;
    }
    default:
    {
      sysclockfreq = HSI_VALUE;
      break;
    }
  }
  return sysclockfreq;
}

关于RCC更多内容,详细参考博客

本程序比较简单,大致流程如下:

  1. 根据寄存器中的值判断系统时钟为HSE,HSI或者PLLCLK。
  2. 若系统时钟为HSE或者HSI,可以直接通过宏定义获取该值为多少。
  3. 若系统时钟为PLLCLK,则需要判断PLLCLK的输入时钟为HSI或者HSE。
  4. 然后将PLLCLK输入时钟乘以N,且除以P,即获得PLLCLK。
  5. 获得的PLLCLK即为SYSCLK。

你可能感兴趣的:(ARM,ARM,STM32,串口,UART,HAL库)