文章目录
- 串口简介
- 串口的数据传输
- 奇偶校验
- STM32的串口特点
- STM32串口工作原理
- STM32的串口中断
- 串口1和串口6互相通信(实验)
- 配置外设
- 代码编写
- 时钟树
- 参考资料
串口简介
- 通用异步收发传输器
- Universal Asynchronous Receiver/Transmitter,UART
- 具有TX和RX俩个信号线
- 可以将数据串行地发送给接收方
- 串口的数据是按照每一位依次发送的,同时只有一条信号线用于发送
- 大多数通信协议都是串口的
- 串口没有时钟线,接收方根据波特率确定采样时间(异步通信)
- 波特率:每秒串口发送的位速率
- 常用115200bps,等效的数据传输速率一般为11.25KB/s
串口的数据传输
- TX接RX,RX接TX,两条数据线上的信号格式相同但是方向不同
- 可以同时发送和接收数据(全双工)
- 一个数据包由起始位,结束位,数据位,和校验位(校验位可有可无)组成
- 每一位的时长由波特率决定,停止位可以是1~2个时钟
- 串口空闲时(即没有数据传输时)保持高电平
- 数据可以是5~9位,最低位(LSB)先发送,最高位(MSB)后发送
奇偶校验
- 串口的最后一个数据位和结束位之间可以插入校验位
- 校验位将这一字节数据中1的个数补齐至偶数个
- 例如,原来8位中数据有5个1,如果是奇校验则第9位校验位为0,偶校验则校验位为1
- 如果通信中有一位发生错误校验会不通过,数据就被舍弃
STM32的串口特点
-
UART,USART和LPUART(三种)
-
UART,和USART基本相同
-
但USART可以当SPI主机用(不推荐)
-
LPUART可以使用32KHz时钟,这时功耗很低,常用于一些低功耗芯片
-
-
可以自由设置波特率
-
具有多种高级功能(很少用)
- 支持全双工,单线半双工模式
- 具有硬件RS232流控
- 第二代串口支持硬件RS485流控,自动波特率等
- 第三代USART增加了对SPI从机的支持
STM32串口工作原理
- 发送:数据被写入串口外设,然后串口按照波特率依次发送数据并添加起始位和停止位
- 接收:串口外设可被配置为以波特率的8倍或16倍的频率采样RX引脚上的电平,然后串口会自动分析数据并将接收到的数据提取出来
- 串口波特率的最大值取决于串口外设的时钟和接收采样倍数
STM32的串口中断
- 每个串口都有一个中断向量
- 当串口发生特定事件即可触发中断,例如
- 一个数据包的数据发送完成
- 一个数据包的数据接收完成
- 串口通信错误(如奇偶校验错误)
- 串口处于空闲状态(连续一个字节的时间RX处于高电平)
- …
- 软件需判定触发中断的具体事件
串口1和串口6互相通信(实验)
- UART1使用中断发送数据
- UART6使用中断接收数据
- 使用按键触发发送
- 尝试同时收发数据
配置外设
打开Cube,首先把PA0设置为GPIO-EXTI0在Connectivity中把串口1和串口6打开(即USART1和USART6),在mode里选择Asynchronous即打开异步模式
STM32的所有外设都可以被配置在不同引脚上(可以切换引脚)
按住ctrl键后点击引脚就可以显示可以被切换的引脚位置
按住ctrl键不动,即可以把原来设置好的引脚拖到可以替换的引脚位置
在USART1的Parameter Settings中的Baud Rate(比特率),Word Length(一个数据包有多少个数据位,包括校验位),Parity校验选项(不校验,偶校验,奇校验),Stop Bits停止位,Over Sampling超采样(采用频率的倍数,决定了串口波特率的上限,把16Samples改为8Samples可以提高上限)
在NVIC中把EXTI0的中断勾选上
在GPIO中把GPIO设置为上升沿或者下降沿触发(下降沿触发即按下按键后触发中断)
然后生成代码
代码编写
在main.c文件中有提供系统运行时间的函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* USER CODE BEGIN Callback 0 */
/* USER CODE END Callback 0 */
if (htim->Instance == TIM7) {
HAL_IncTick();
}
/* USER CODE BEGIN Callback 1 */
/* USER CODE END Callback 1 */
}
我们来看一下里面调用的HAL_IncTick()函数有什么作用
__weak void HAL_IncTick(void)
{
uwTick += uwTickFreq;
}
在这个函数下面有一个获得系统运行时间的函数
__weak uint32_t HAL_GetTick(void)
{
return uwTick;
}
该函数的作用就是获取系统运行时间
首先我们传输数据就需要建立一个发送缓冲区和接收缓冲区(是数组,即一个内存空间,用来存放发送和接收的数据),在 USER CODE BEGIN PV/ END PV里写两个数组充当发送缓冲区以及接收缓冲区,代码如下
/* USER CODE BEGIN PV */
uint8_t txDateBuffer[4];
uint8_t rxDateBuffer[4];
/* USER CODE END PV */
要使串口1传送给串口6,我们需要先设置串口1的中断,在 USER CODE BEGIN 4 / END 4 之间写外部中断回调函数的代码
/* USER CODE BEGIN 4 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_PIN)
{
uint32_t temp=HAL_GetTick;
*((uint32_t *)txDateBuffer)=temp;//把txDateBuffer的地址强制类型转换为一个uint32_t *的指针
//由于txDateBuffer是uint8_t类型,因此我们需要借助指针来进行强制类型转换为uint32_t类型
if (GPIO_PIN==GPIO_PIN_0)
{
HAL_UART_Transmit_IT(&huart1,txDateBuffer,sizeof(txDateBuffer));//其中的IT代表中断
}
}
/* USER CODE END 4 */
这样我们就成功地发送了数据,接下来就需要在 USER CODE BEGIN 2 / END 2 之间写一个接收数据的函数
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart6,rxDateBuffer,sizeof(rxDateBuffer));
/* USER CODE END 2 */
然后编写接收的回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef*huart)
{
if (huart==&huart6)//判断条件也可以写成huart->Instance==USART6
{
HAL_UART_Receive_IT(&huart6,rxDateBuffer,sizeof(rxDateBuffer));//这样就可以执行一个继续接收的程序
//由于串口接收到的数据是uint32_t存放在rxDateBuffer(但是它是一个uint8_t的数组)
}
}
由于这样在rxDateBuffer中的数据不方便观察,因此我们需要在BEGIN PV/END PV之间再定义一个uint32_t的数组rxDate,更改后代码如下:
/* USER CODE BEGIN PV */
uint8_t txDateBuffer[4];
uint8_t rxDateBuffer[4];
uint32_t rxDate;
/* USER CODE END PV */
然后用这个rxDate去接收rxDateBuffer强制类型转换后的数据
然后把它写到接收函数的前面
void HAL_UART_RxCpltCallback(UART_HandleTypeDef*huart)
{
if (huart==&huart6)//(huart->Instance==USART6)
{
rxDate=*((uint32_t*)rxDateBuffer);
HAL_UART_Receive_IT(&huart6,rxDateBuffer,sizeof(rxDateBuffer));
}
}
然后我们考虑到如果按键产生中断速度过快,就可能使得上一次发送数据后串口还没接收完毕,这时再次传送数据过去,会由于接收串口正在被占用导致下一次传送的数据出现错误,因此需要写一个回调函数来判断上一次传输是否已经完成
void HAL_UART_TxCpltCallback(UART_HandleTypeDef*huart)
或者我们可以在按键回调函数里写一个循环,让它判断是否传输完毕,如果未完成就将程序锁死
while (huart1.gState!=HAL_UART_STATE_READY);
这样就完成了串口传输的功能。
时钟树
- STM32的所有外设都是可以配置时钟的
- 大部分外设挂载在APB总线上,它们的时钟频率等于总线频率
- 部分外设可以单独配置时钟频率