本文主要以正点原子串口通信实验为基础,讲解其中涉及的HAL库函数的具体实现原理。本实验主要实现的功能是通过阻塞方式,实现串口数据的发送;通过中断方式,实现串口数据的接收。本实验大致结果如下:
本文主要参考文献:
这是正点原子开发板关于串口部分硬件原理图。该原理图中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);
}
}
}
此主函数由正点原子提供,与串口相关的部分可以分成三个部分:
HAL_UART_Init
函数实现。printf
和函数HAL_UART_Transmit
实现。USART1_IRQHandler
实现。下面分条详细讲解。
关于串口的配置部分,主要涉及以下几个重要函数:
依次分别介绍如下:
/**
* @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()函数中,功能大致可以分成四个部分:
其中,在第二步中,初始化底层资源需要注意:
HAL_UART_MspInit()
需要自己重新实现。该函数主要配置串口与其对应的GPIO之间的关系。具体详解参考后文。在第三步中,改变串口配置参数需要注意:
#define __HAL_UART_DISABLE(__HANDLE__) ((__HANDLE__)->Instance->CR1 &= ~USART_CR1_UE)
#define __HAL_UART_ENABLE(__HANDLE__) ((__HANDLE__)->Instance->CR1 |= USART_CR1_UE)
UART_SetConfig()
,该函数详解参考后文。/**
* @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
}
}
在底层初始化中,大致可以分成三块内容:
/**
* @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各部分:
其中,第2步到第4步其实都是一样的。对于寄存器中部分位的设置可以分成以下步骤:
在第5步配置过采样中,需要注意:
HAL_RCC_GetPCLK1Freq()
和HAL_RCC_GetPCLK2Freq()
。关于此部分内容,可以参考后文RCC部分。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过采样为例(默认),该功能共分成四个步骤:
根据官方文档,可以获得波特率的求取公式为:
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×(2−OVER8)×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.25≈13。综上,整数部分配置为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(s1−s2×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=(100s1−s2)×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寄存器。
/**
* @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;
}
}
通过阅读源程序,可以知道该函数的主要作用是:
其中,需要注意的是,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函数的重构本文介绍两种方法,分别采用来自于正点原子和野火的源程序。
正点原子的版本源程序如下:
//禁止使用半主机模式
#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
项来避免半主机模式。该资源库代码更少,占用更少的资源。
/**
* @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; //返回发送值
}
在重构代码中,直接对寄存器操作,可以提高代码的效率。
/**
* @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;
}
通过阅读源程序,可以很清楚的理解该函数的意义。其有四个参数,分别为:
即指定串口的某个标志位在某状态下维持的时间,若超过该时间,则判断该串口超时。
其中判定标志位对应的状态,用的宏定义为:
/** @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__))
在本实验中,串口接收用的是中断接收。关于串口的接收,本文的一个重点。在这部分,正点原子使用的是官方库来实现。官方库在此处的处理方式比较复杂,效率比较低,不被推荐使用。但是整个程序的调用流程还是应该了解一下:
HAL_UART_Receive_IT
函数完成串口接收中断相关内容的配置。USART1_IRQHandler
。USART1_IRQHandler
中,调用HAL_UART_IRQHandler
函数,来判断中断源。HAL_UART_IRQHandler
函数,识别出接收中断,从而调用函数UART_Receive_IT
。UART_Receive_IT
中,会将接收到的数据写入接收缓存数组中。当接收数组满之后,该函数会自动关闭接收相关中断。UART_Receive_IT
最后,还会调用HAL_UART_RxCpltCallback
函数。USART1_IRQHandler
函数中,继续执行剩下的语句。在其中,最终的是会调用HAL_UART_Receive_IT
函数,再次使能接收相关的中断。/**
* @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的接收中断服务函数,每次接收到数据,将会触发执行该函数。
在此函数中,共完成三项工作:
需要注意的是:该部分函数由正点原子提供。其超时处理的方法是,跳过此步,不再等待,继续执行后续步骤。
/**
* @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);
}
}
该函数在串口中断响应函数中调用。
因为每个串口只对应一个中断,且只有一个中断响应函数。所以,在进入中断之后,需要首先判断此中断的属性,可以通过查询中断标志位得到。在该函数中,就做该工作。所以,通过此函数可以总结出每一个串口对应的中断源有:
此处忽略其他的中断,下文只重点研究接收器空中断。
涉及到的宏定义有:
/** @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);
;
只有第一句是在受到条件语句约束的,与本意不符。
/**
* @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;
}
}
此函数大致分成两层:
/**
* @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
的状态。
关于RCC更多的内容,可以参考博客 < STM32F429第七篇之RCC(复位与时钟)>
与本实验相关的RCC内容是关于时钟频率的查询。
具体程序如下:
/**
* @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)]);
}
根据博客
根据该时钟图可知:
p c l k = h c l k p r e s c a l e r pclk=\frac{hclk}{prescaler} pclk=prescalerhclk
根据参考手册可知:
所以,关于程序的整体思路已经比较清晰了。就是首先获得HCLK的频率,然后通过PPRE寄存器获取到预分频信息,即可得到PCLK1和PCLK2。下面讲解以下细节:
POSITION_VAL()
宏的含义为:求取该32位二进制数从从最低位开始,共有多少个连续的0。关于该宏更多分析可以参考博客const uint8_t APBAHBPrescTable[16] = {0, 0, 0, 0, 1, 2, 3, 4, 1, 2, 3, 4, 6, 7, 8, 9};
通过查表的方式将寄存器的值转化为需要将频率右移的位数。
HAL_RCC_GetHCLKFreq()
,本质和上文介绍相同,此处不再详细展开。/**
* @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更多内容,详细参考博客
本程序比较简单,大致流程如下: