在stm32编程时,最常用的通讯方式就是串口通讯。
一般使用HAL库来实现串口通讯。
但有时,我们不满足于HAL库的代码,或者出现“玄学bug”需要了解具体原理来debug。
下面将通过详解HAL库函数来解释uart原理
一、初始化
uart的初始化比较简单(至少比gpio简单一点)。
只需要解释几个概念:
比特率——传输速率
字长——一次传输数据的长度,8-9位
停止位——每次接收数据时,检测数据是否接受完成所用的位
校验位——检测数据是否正确
STM32CubeMX配置如下:
几个重要的寄存器:
DR寄存器——接受或传输数据
SR寄存器——用于控制,其中FLAG位在第7位
二、发送数据
HAL库的大致思路是:
检测是否空闲->判断发送8位数据还是16位数据->按位将要发送的数据放到DR寄存器
而具体的发送流程,形象的说,这就像两把手枪
数据就像子弹,而枪管的左端就像TDR寄存器。
第一步:将数据放入TDR寄存器
第二步:击发子弹,发送端flag位置1
但这个过程是自动发生的,而且我们也看不到火光,只能看到弹壳。
发送完毕后flag位会自动置1,就像弹出的弹壳可以被我们看到。
此时我们便知道发送完毕了。
第三步:数据发送到接收端RDR寄存器
第四步:发送新的数据,注意接收端取走数据
循环下去,就能把数据全部发送到接收端。
注意:一定要看flag位是否置1,如果不管它,就会出事
接下来,看一下代码:
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
uint8_t *pdata8bits;
uint16_t *pdata16bits;
uint32_t tickstart = 0U;
/* Check that a Tx process is not already ongoing */
if (huart->gState == HAL_UART_STATE_READY)
{
if ((pData == NULL) || (Size == 0U))
{
return HAL_ERROR;
}
/* Process Locked */
__HAL_LOCK(huart);
huart->ErrorCode = HAL_UART_ERROR_NONE;
huart->gState = HAL_UART_STATE_BUSY_TX;
/* Init tickstart for timeout management */
tickstart = HAL_GetTick();
huart->TxXferSize = Size;
huart->TxXferCount = Size;
/* In case of 9bits/No Parity transfer, pData needs to be handled as a uint16_t pointer */
if ((huart->Init.WordLength == UART_WORDLENGTH_9B) && (huart->Init.Parity == UART_PARITY_NONE))
{
pdata8bits = NULL;
pdata16bits = (uint16_t *) pData;
}
else
{
pdata8bits = pData;
pdata16bits = NULL;
}
/* Process Unlocked */
__HAL_UNLOCK(huart);
while (huart->TxXferCount > 0U)
{
if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK)
{
return HAL_TIMEOUT;
}
if (pdata8bits == NULL)
{
huart->Instance->DR = (uint16_t)(*pdata16bits & 0x01FFU);
pdata16bits++;
}
else
{
huart->Instance->DR = (uint8_t)(*pdata8bits & 0xFFU);
pdata8bits++;
}
huart->TxXferCount--;
}
if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TC, RESET, tickstart, Timeout) != HAL_OK)
{
return HAL_TIMEOUT;
}
/* At end of Tx process, restore huart->gState to Ready */
huart->gState = HAL_UART_STATE_READY;
return HAL_OK;
}
else
{
return HAL_BUSY;
}
}
注意tickstart,从此刻开始计时,超时就结束。
注意“超时”不是每个步骤一个超时时间,是整个发送过程不能超时,因此在自己写代码时,要么超时时间设的很长很长,要么尽量分段发送那些长数据。
另外,设置超时0xFFFFFFFFU不是说真的等这么长时间再超时,程序默认0xFFFFFFFFU代表无限大,即永不超时。
按位将数据放到DR寄存器,uart就会把数据发送出去。
一个小细节,&0xFF(0x1FF)可以只保留8(9)位,将溢出的直接滤去。
每发送一个数据,会等待发送完毕再发下一个。通过检测SR寄存器的FLAG位(第7位)来判断是否发送完毕。
三、接收
HAL库的大致思路是:
检测接收端是否空闲->判断接收的是8位数据还是16位数据->按位将DR寄存器中的数据读出。
核心就是:
while (huart->RxXferCount > 0U)
{
if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK)
{
return HAL_TIMEOUT;
}
if (pdata8bits == NULL)
{
*pdata16bits = (uint16_t)(huart->Instance->DR & 0x01FF);
pdata16bits++;
}
else
{
if ((huart->Init.WordLength == UART_WORDLENGTH_9B) || ((huart->Init.WordLength == UART_WORDLENGTH_8B) && (huart->Init.Parity == UART_PARITY_NONE)))
{
*pdata8bits = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);
}
else
{
*pdata8bits = (uint8_t)(huart->Instance->DR & (uint8_t)0x007F);
}
pdata8bits++;
}
huart->RxXferCount--;
}
基本上就是发送反过来。
四、中断
提供了一个中断回调函数
void USER_UART_IRQHandler(UART_HandleTypeDef* huart)
{
if (USART1 == huart->Instance)
{
if (RESET != __HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE)) // 确认是否为空闲中断
{
__HAL_UART_CLEAR_IDLEFLAG(huart); // 清除空闲中断标志
USER_UART_IDLECallback(huart); // 调用中断回调函数
}
}
}
在USER_UART_IDLECallback()中写要运行的程序即可。
五、总结
uart通讯几乎是stm32中最简单且稳定的一部分了,基本上配置一遍就再也不用改了。但如果对原理不了解,配置的过程还是有挺多问题的。
HAL库中的代码还算简介高效,写自己的发送接收函数实在是没必要了。学习HAL库也只是为了了解而已。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)