生成的代码是异步模式,波特率115200,接受和发送模式,一个停止位,无校验,带中断。
cubemx生成代码后,主要分为GPIO和USART两部分的驱动代码。
void MX_GPIO_Init(void)
{
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOA_CLK_ENABLE();
}
GPIO文件中只有这一段代码,这个我们都知道是GPIOA的时钟使能了。至于这部分的源码,我们再led中已经解释过代码了。所以今天只有USART的几个函数给大家讲解一下。
/* USER CODE END 0 */
UART_HandleTypeDef huart1;
/* USART1 init function */
void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
}
-----------------------------------------------------------------------
typedef struct
{
USART_TypeDef *Instance; /*!< UART registers base address */
UART_InitTypeDef Init; /*!< UART communication parameters */
uint8_t *pTxBuffPtr; /*!< Pointer to UART Tx transfer Buffer */
uint16_t TxXferSize; /*!< UART Tx Transfer size */
uint16_t TxXferCount; /*!< UART Tx Transfer Counter */
uint8_t *pRxBuffPtr; /*!< Pointer to UART Rx transfer Buffer */
uint16_t RxXferSize; /*!< UART Rx Transfer size */
uint16_t RxXferCount; /*!< UART Rx Transfer Counter */
DMA_HandleTypeDef *hdmatx; /*!< UART Tx DMA Handle parameters */
DMA_HandleTypeDef *hdmarx; /*!< UART Rx DMA Handle parameters */
HAL_LockTypeDef Lock; /*!< Locking object */
__IO HAL_UART_StateTypeDef State; /*!< UART communication state */
__IO uint32_t ErrorCode; /*!< UART Error code */
}UART_HandleTypeDef;
这是第一个函数。整体的感觉告诉我们这是USART1通信参数的配置。接下来开始解释
首先函数外定义了一个全局变量的一个结构体,这个结构体外设的总接口。下划线下也找到了这个结构体的定义,注释也大概解释了下参数的作用。
我们用到哪个说哪个,不用纠结其中的东西。
huart1.Instance = USART1;这行代码为Instance成员赋值,我们根据led那节分析可得USART1是一个宏定义,值是USART1的地址,这里不再赘述过程。即Instance这个成员取得了USART1的地址。
后面几个都是需要通信的参数的赋值,没有什么不同的过程,和上面分析一样,我们不再去分析,直接给出宏定义的值以便我们继续分析。
#define UART_WORDLENGTH_8B ((uint32_t)0x00000000)
#define UART_STOPBITS_1 ((uint32_t)0x00000000)
#define UART_PARITY_NONE ((uint32_t)0x00000000)
#define UART_MODE_TX_RX ((uint32_t)(USART_CR1_TE |USART_CR1_RE))
#define UART_MODE_RX ((uint32_t)USART_CR1_RE)
#define UART_MODE_TX ((uint32_t)USART_CR1_TE)
#define USART_CR1_RE_Pos (2U)
#define USART_CR1_RE_Msk (0x1U << USART_CR1_RE_Pos) /*!< 0x00000004 */
#define USART_CR1_RE USART_CR1_RE_Msk /*!< Receiver Enable */
#define USART_CR1_TE_Pos (3U)
#define USART_CR1_TE_Msk (0x1U << USART_CR1_TE_Pos) /*!< 0x00000008 */
#define USART_CR1_TE USART_CR1_TE_Msk /*!< Transmitter Enable */
#define UART_HWCONTROL_NONE ((uint32_t)0x00000000)
#define UART_OVERSAMPLING_16 ((uint32_t)0x00000000)
接下来是if语句里有一个函数,我们可以知道这是用来将你的通信参数的值写入的寄存器中的一个处理函数,我们贴出代码分析一下。
HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart)
{
/* 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 */
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));
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;
/* 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.*/
CLEAR_BIT(huart->Instance->CR2, (USART_CR2_LINEN | USART_CR2_CLKEN));
CLEAR_BIT(huart->Instance->CR3, (USART_CR3_SCEN | USART_CR3_HDSEL | USART_CR3_IREN));
/* Enable the peripheral */
__HAL_UART_ENABLE(huart);
/* Initialize the UART state */
huart->ErrorCode = HAL_UART_ERROR_NONE;
huart->State= HAL_UART_STATE_READY;
return HAL_OK;
}
进来后首先是一个参数的检查,检查传递的指针是否是一个空指针,空指针说明错误,因为指针没有指向我们所要的内存,所以返回一个错误标志。
下面判断硬件控制流这个参数时候不等于UART_HWCONTROL_NONE。我们当时对结构体此成员赋值正是此值,所以得到结果逻辑0,执行else部分的语句。
这里要检查传入的Instance成员是否符合有效性。我们结合上面赋值的过程得知值是一个USART1的地址,所以我们推测可以知道,这个参数有效性的检查应该是一系列USART的地址或者地址的宏定义。我们跟踪查看一下。
#define IS_UART_INSTANCE(INSTANCE) (((INSTANCE) == USART1) || \
((INSTANCE) == USART2) || \
((INSTANCE) == USART3) || \
((INSTANCE) == UART4) || \
((INSTANCE) == UART5))
可以看到这里是一系列地址的宏定义,检查成员是否等于这里某一个宏定义。我们上面赋值复合(INSTANCE) == USART1。有效性有效接着往下。
下面两个通信参数有效性的检查和上一步的相同直接跳过。
接着是判断USART1的状态,
typedef enum
{
HAL_UART_STATE_RESET = 0x00, /*!< Peripheral is not initialized */
HAL_UART_STATE_READY = 0x01, /*!< Peripheral Initialized and ready for use */
HAL_UART_STATE_BUSY = 0x02, /*!< an internal process is ongoing */
HAL_UART_STATE_BUSY_TX = 0x12, /*!< Data Transmission process is ongoing */
HAL_UART_STATE_BUSY_RX = 0x22, /*!< Data Reception process is ongoing */
HAL_UART_STATE_BUSY_TX_RX = 0x32, /*!< Data Transmission and Reception process is ongoing */
HAL_UART_STATE_TIMEOUT = 0x03, /*!< Timeout state */
HAL_UART_STATE_ERROR = 0x04 /*!< Error */
}HAL_UART_StateTypeDef;
状态是一个枚举的定义,当它是没有启动的状态时,将成员Lock赋值。我们还是给出值,这还是一个枚举。根据下面枚举得出值是0x00
typedef enum
{
HAL_UNLOCKED = 0x00,
HAL_LOCKED = 0x01
} HAL_LockTypeDef;
然后我们看下面一行的代码,根据注释可得出这个函数是初始化硬件的。
__weak void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(huart);
/* NOTE: This function should not be modified, when the callback is needed,
the HAL_UART_MspInit can be implemented in the user file
*/
}
跟踪定义发现函数中只是对传入的变量做了void防止警告的声明。然后NOTE内容告诉我们当我们需要回调的时候这个函数不能被修改,这个函数可以在用户文件中实现。这其实是cubemx生成代码,上一步调用了这个函数来初始化硬件,但是生成代码前没有此函数,所以库里有一个这样的函数防止错误,你需要在你的用户文件中重定义这个函数去实现功能。以下给我生成代码中给出的这个重定义的函数。
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
GPIO_InitTypeDef GPIO_InitStruct;
if(uartHandle->Instance==USART1)
{
/* USER CODE BEGIN USART1_MspInit 0 */
/* USER CODE END USART1_MspInit 0 */
/* Peripheral clock enable */
__HAL_RCC_USART1_CLK_ENABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* Peripheral interrupt init */
HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
/* USER CODE BEGIN USART1_MspInit 1 */
/* USER CODE END USART1_MspInit 1 */
}
}
这个函数就是先判断下是否为USART1,是的话开启它的时钟。然后根据设置的模式去配置GPIO。最后设置了中断优先级并且使能中断。
这样就达到了上面的注释的功能——初始化硬件。
接着开始那个函数往下分析,将State成员更新为HAL_UART_STATE_BUSY。
再往下是一个函数,注释告诉我们是禁用外设。这是一个宏定义,我们给出宏定义并且分析,由于宏定义设计寄存器,我们也贴出相关寄存器。
#define __HAL_UART_DISABLE(__HANDLE__) ((__HANDLE__)->Instance->CR1 &= ~USART_CR1_UE)
#define USART_CR1_UE USART_CR1_UE_Msk
#define USART_CR1_UE_Msk (0x1U << USART_CR1_UE_Pos) /*!< 0x00002000 */
#define USART_CR1_UE_Pos (13U)
我们可以知道这个是讲CR1寄存器第13位写了1,然后取反赋值给Instance成员,即USART分频器和输出被禁止。
再往下这个函数是设置通信参数,我们跟踪定义一下。首先是四个有效性检测,我们同样给出。
#define IS_UART_BAUDRATE(__BAUDRATE__) ((__BAUDRATE__) < 4500001)
#define IS_UART_STOPBITS(STOPBITS) (((STOPBITS) == UART_STOPBITS_1) || \
((STOPBITS) == UART_STOPBITS_2))
#define IS_UART_PARITY(PARITY) (((PARITY) == UART_PARITY_NONE) || \
((PARITY) == UART_PARITY_EVEN) || \
((PARITY) == UART_PARITY_ODD))
#define IS_UART_MODE(MODE) ((((MODE) & (~((uint32_t)UART_MODE_TX_RX))) == 0x00) && \
((MODE) != (uint32_t)0x00000000))
第一个是限制了下大小,第二第三我们上面有过类似的不再分析,我们看一下第四个宏定义。
我们知道我们的MODE参数是接受和发送都选择了,即RE和TE位都是1,所以宏定义第二个条件成立。第一个条件是我们的参数遇上一个判断是否等于一个值,我们的参数正是UART_MODE_TX_RX,即参数&上本身的反,结果是0,第一个条件也满足。
四个有效性都正确。然后下面就是对寄存器的赋值了。
MODIFY_REG(huart->Instance->CR2, USART_CR2_STOP, huart->Init.StopBits);
#define MODIFY_REG(REG, CLEARMASK, SETMASK) WRITE_REG((REG), (((READ_REG(REG)) & (~(CLEARMASK))) | (SETMASK)))
这个WRITE_REG第二个参数比较麻烦,我们先看一下这到底是怎么执行的,
1(READ_REG(REG))
2(~(CLEARMASK))
3(1&2) //此处简写了啊 因为括号太多了 这样有利于我们理清楚逻辑
4(SETMASK)
5( (1&2) | SETMASK)
第一个参数我们在led中就遇到过类似的,这里的值是USART1的CR2寄存器的值。第二个参数是个宏定义我们给出一下。
#define USART_CR2_STOP_Pos (12U)
#define USART_CR2_STOP_Msk (0x3U << USART_CR2_STOP_Pos) /*!< 0x00003000 */
#define USART_CR2_STOP USART_CR2_STOP_Msk /*!< STOP[1:0] bits (STOP bits) */
第二个参数可以得出,第12,13位是1。即0011000000000000,0x00003000
第三个参数是0x00000000。
接下来是看宏定义被替换成什么。这是嵌套的宏定义,我们慢慢分析。
#define WRITE_REG(REG, VAL) ((REG) = (VAL))
这个接受两个参数,第一个参数是USART1的CR2寄存器的值,第二个参数是三个宏定义,我们接着分析。
#define READ_REG(REG) ((REG)),第二和第三个宏都是我么前面MODIFY_REG宏第二第三个的参数。
我们直接把WRITE_REG第二个参数写出来( ( (CR2的值) & 1100111111111111 ) | 0000000000000000 ),这个值的结果是xx00xxxxxxxxxxxx(x代表未知,因为是从寄存器中读出的,并且在这个运算过程中,x的值并没有改变,只是将12,13位变成了00)
分析出来WRITE_REG的第二个参数后,结果就出来了,WRITE_REG的结果就是将xx00xxxxxxxxxxxx赋值给CR2寄存器。给出一下CR2的图看一下是什么功能。
即MODIFY_REG将我们设置的停止位这个通信参数更新到寄存器中。
再往下分析是把字长,校验位,模式三个参数做了按位与的操作后赋值给tmpreg。
#define UART_WORDLENGTH_8B ((uint32_t)0x00000000)
#define UART_PARITY_NONE ((uint32_t)0x00000000)
#define UART_MODE_TX_RX ((uint32_t)(USART_CR1_TE |USART_CR1_RE))
#define UART_MODE_RX ((uint32_t)USART_CR1_RE)
#define UART_MODE_TX ((uint32_t)USART_CR1_TE)
#define USART_CR1_RE_Pos (2U)
#define USART_CR1_RE_Msk (0x1U << USART_CR1_RE_Pos) /*!< 0x00000004 */
#define USART_CR1_RE USART_CR1_RE_Msk
#define USART_CR1_TE_Pos (3U)
#define USART_CR1_TE_Msk (0x1U << USART_CR1_TE_Pos) /*!< 0x00000008 */
#define USART_CR1_TE USART_CR1_TE_Msk
我们可以得到tmpreg的值是0x0000000C,下面的代码又是MODIFY_REG宏定义,又修改了寄存器的值。我们还是先把三个参数列出来
1、CR1寄存器的值
2、0001011000001100b(这里我们直接给出,就是查找宏定义运算,和上面一样。)
3、tepreg
上面我们已经分析过一次这种宏定义,所以这里我们直接按上面的步骤来。
先是读CR1寄存器的值,然后把需要设置的位先清0,之后再把需要的位赋值,最有把值写入CR1寄存器。(没有明白过程的可以看上面的分析过程)
这里我们直接把最后写入CR1的值给出来xxx0x00xxxxx11xx,我们贴出CR1的寄存器看一看这个寄存器操作的结果。
这里可以看出使能了接受发送(通信参数:模式),禁止校验控制(通信参数:检验位),传输的字节长(通信参数:字长)
又更新了三个通信参数到寄存器中,我们接着往下看。
又是一个修改寄存器的操作,我们不再赘述,直接给出结果,这里吧CR3寄存器的第8,9位写了0。
这里禁止了RTS和CTS硬件流控制。代码的最后是比特率的设置和采用问题,和上面一样的道理,我们不再分析源码。