原文链接
HAL_XXX_MspInit 函数是HAL库中的一个回调函数,它会被对应的HAL_XXX_Init 函数调用,所以不需要用户自己调用。它的作用配置外设的底层驱动,包括时钟、引脚、DMA 等。
使用这个函数是为了把和 MCU 相关的初始化操作和和 MCU 无关的初始化操作分开,使代码更清晰和可移植。
stm32代码中的初始化操作可以分为两类:一类是和单片机的硬件资源有关的,比如时钟、引脚、DMA 等,这些资源在不同的单片机上可能会有不同的配置方式和寄存器地址,所以需要根据具体的单片机来编写底层驱动函数;另一类是和单片机的功能有关的,比如分辨率、转换模式、触发方式等,这些功能在不同的单片机上可能会有相同或类似的配置方式和寄存器地址,所以可以使用通用的上层应用函数。这样做的目的是为了提高代码的可移植性,也就是说,如果你要换一个不同的单片机,你只需要修改底层驱动函数,而不需要修改上层应用函数,因为后者是通用的,不会因为单片机的不同而改变。
在I.MX6ULL中,也有类似的函数,比如:
/*
* @description : 初始化串口1,波特率为115200
* @param : 无
* @return : 无
*/
void uart_init(void)
{
/* 1、初始化串口IO */
uart_io_init();
/* 2、初始化UART1 */
uart_disable(UART1); /* 先关闭UART1 */
uart_softreset(UART1); /* 软件复位UART1 */
UART1->
UCR1 = 0; /* 先清除UCR1寄存器 */
/*
* 设置UART的UCR1寄存器,关闭自动波特率
* bit14: 0 关闭自动波特率检测,我们自己设置波特率
*/
UART1->UCR1 &= ~(1<<14);
/*
* 设置UART的UCR2寄存器,设置内容包括字长,停止位,校验模式,关闭RTS硬件流控
* bit14: 1 忽略RTS引脚
* bit8: 0 关闭奇偶校验
* bit6: 0 1位停止位
* bit5: 1 8位数据位
* bit2: 1 打开发送
* bit1: 1 打开接收
*/
UART1->UCR2 |= (1<<14) | (1<<5) | (1<<2) | (1<<1);
/*
* UART1的UCR3寄存器
* bit2: 1 必须设置为1!参考IMX6ULL参考手册3624页
*/
UART1->UCR3 |= 1<<2;
/*
* 设置波特率
* 波特率计算公式:Baud Rate = Ref Freq / (16 * (UBMR + 1)/(UBIR+1))
* 如果要设置波特率为115200,那么可以使用如下参数:
* Ref Freq = 80M 也就是寄存器UFCR的bit9:7=101, 表示1分频
* UBMR = 3124
* UBIR = 71
* 因此波特率= 80000000/(16 * (3124+1)/(71+1))=80000000/(16 * 3125/72) = (80000000*72) / (16*3125) = 115200
*/
UART1->UFCR = 5<<7; //ref freq等于ipg_clk/1=80Mhz
UART1->UBIR = 71;
UART1->UBMR = 3124;
#if 0
uart_setbaudrate(UART1, 115200, 80000000); /* 设置波特率 */
#endif
/* 使能串口 */
uart_enable(UART1);
}
/*
* @description : 初始化串口1所使用的IO引脚
* @param : 无
* @return : 无
*/
void uart_io_init(void)
{
/* 1、初始化IO复用
* UART1_RXD -> UART1_TX_DATA
* UART1_TXD -> UART1_RX_DATA
*/
IOMUXC_SetPinMux(IOMUXC_UART1_TX_DATA_UART1_TX,0); /* 复用为UART1_TX */
IOMUXC_SetPinMux(IOMUXC_UART1_RX_DATA_UART1_RX,0); /* 复用为UART1_RX */
/* 2、配置UART1_TX_DATA、UART1_RX_DATA的IO属性
*bit 16:0 HYS关闭
*bit [15:14]: 00 默认100K下拉
*bit [13]: 0 keeper功能
*bit [12]: 1 pull/keeper使能
*bit [11]: 0 关闭开路输出
*bit [7:6]: 10 速度100Mhz
*bit [5:3]: 110 驱动能力R0/6
*bit [0]: 0 低转换率
*/
IOMUXC_SetPinConfig(IOMUXC_UART1_TX_DATA_UART1_TX,0x10B0);
IOMUXC_SetPinConfig(IOMUXC_UART1_RX_DATA_UART1_RX,0x10B0);
}
这段代码里将uart_init函数和uart_io_init函数分开来的原因有以下几点:
1、uart_init函数是用来配置UART的波特率、数据位、停止位等参数,这些参数是与通信协议相关的,而uart_io_init函数是用来配置UART的IO引脚的复用和属性,这些参数是与硬件平台相关的。将两个函数分开可以提高代码的可移植性和可维护性,如果需要更换通信协议或硬件平台,只需要修改相应的函数即可,而不影响其他部分的代码。
2、uart_init函数和uart_io_init函数的调用时机可能不同,分开来可以提高代码的灵活性和效率。例如,uart_init函数可能需要在每次通信前重新配置UART的参数,而uart_io_init函数可能只需要在系统初始化时配置一次UART的IO引脚。将两个函数分开可以根据实际需求选择合适的时机调用,避免不必要的重复操作。
其中第一点与stm32中的HAL_XXX_MspInit函数与HAL_XXX_Init函数类似,把和 MCU 相关的初始化操作(单片机的硬件资源)和和 MCU 无关的初始化操作(单片机的功能)分开来,从而提高代码的可移植性、模块化和灵活性,不同之处在于HAL_XXX_Init函数可以主动调用HAL_XXX_MspInit函数,而uart_init函数里需要我们手动添加调用uart_io_init函数。
这是因为HAL库是一个通用的库,适用于不同的MCU和外设,所以HAL_XXX_Init函数会自动调用HAL_XXX_MspInit函数来完成与MCU相关的初始化操作。而uart_init函数和uart_io_init函数是我们自己编写的函数,针对特定的UART外设和IO引脚,所以我们需要在uart_init函数里手动调用uart_io_init函数来完成IO引脚的配置。这样做也可以让我们更清楚地控制UART的初始化过程,避免一些潜在的错误或冲突。
关于第二点,在这段代码中uart_init函数里直接调用了uart_io_init函数,也就是每次配置UART的参数的时候都会重新调用一次uart_io_init函数,这样还是会重复操作。但是,我们也可以在uart_init函数里加入一些判断条件,比如:
typedef enum {
UNINITIALIZED = 0,
INITIALIZED = 1
} InitStatus;//定义一个枚举类型(也可使用宏定义),用来表示UART的IO引脚是否已经初始化过
void uart_init(void)
{
static InitStatus uart_io_initialized = UNINITIALIZED; //定义一个静态变量,用来记录UART的IO引脚是否已经初始化过
if (uart_io_initialized == UNINITIALIZED) //如果UART的IO引脚还没有初始化过,就调用uart_io_init函数
{
uart_io_init(); //调用uart_io_init函数
uart_io_initialized = INITIALIZED; //将静态变量置为INITIALIZED,表示UART的IO引脚已经初始化过
}
//以下是配置UART的参数的代码,省略...
}
这样,我们就可以避免每次配置UART的参数的时候都重新调用一次uart_io_init函数,只有在第一次配置UART的参数的时候才会调用一次uart_io_init函数。