(该文章主要基于UART串口通信)
Goal
实现UART串口的自发自收,即PC发送数据给STM32然后再传输,将CubeMX代码移植到CubeIDE中。
Background
UART(蓝牙模块) vs SPI(W25Q16 flash) vs IIC(MPU6050,OLED)
逻辑分析仪(抓波形,用于协议解码、调试)vs示波器(运算、模拟)
通信协议目的:协议双方进行数据交互,先编码,以电平形式在电线上传输,再解码
通信协议三种模式:1. 半双工(IIC)2. 全双工(UART, SPI)3. 单工(只能收或者只能发)
UART(异步通信):
异步通信没有时钟,协定好每bit多少时间然后去采集,协定的时间叫做波特率,单片机和PC双方需协同一样的波特率。
TX发送端 RX接收端 GND标准口(作为电平高低参考值)
默认高电平,拉低电平为起始信号,传出8bits数据(人为设定数据位),采集到停止信号则拉高电平
图 1 UART异步通信
IIC(同步通信):
SCL时钟 SDA传出数据、信号(这根线一个时间段只能一个从机使用)
SCL默认高电平,不像系统时钟一直有脉冲,SCL需要被触发。SDA默认高电平,当SDA先拉低作为起始信号,然后触发SCL也拉低,产生八个脉冲(人为设定8个数据位),也即上升沿采样,传出8bit数据,然后有第九个脉冲作为应答信号,第九个脉冲后有继续和停止两个选项,停止的话SCL先拉高,SDA随即拉高产生停止信号。继续和停止使用的编解码方式不一样。
可以实现一主多从,主机上的SCL和SDA对应不同的从机。每个从机有特定地址,发送地址锁定从机而对从机进行操作。
图 2 IIC
SPI:
片选CS MOSI MISO SCK
由一条数据线发送一条接受实现全双工
SCK,MISO,MOSI主机的三根线连到从机,用不同片选信号选中从机,即多根CS线,e.g. CS1控制从机1,CS2控制从机2,CS3控制从机3,如此类推。
CS拉低则选中对应从机,作为起始信号,CS拉高为停止信号。SCK在这之间产生时钟脉冲,SPI不一定上沿采样,可选择。一些重要参数的配置:CPHA=0用于设置第一个上升沿开始进行周期采样,CPHA=1为第一个下降沿开始进行周期采样;CPOL=0则SCK信号默认低电平,CPOL=1则SCK信号默认高电平。
图 3 SPI
通用同步异步收发器USART
UART vs USART:USART的信号线和时钟为同步的,而UART为异步通信。
图 4 USART框图
跨时钟域处理
STM32单片机通过CPU或DMA传到发送数据寄存器TDR,再发送到移位寄存器,在通过TX信号线将数据传输出去。
STM32接收数据则从RX信号线接收,先到移位寄存器,再到接收数据寄存器RDR,再传输到CPU。CPU工作时钟168MHz,USART1挂载在APB2上即84MHz,RX为一周期115200波特率,即11.52MHz和84MHz不匹配,因此引入寄存器,先让信号放入FIFO中缓存,为第一个时钟域,慢信号被缓存再被快速采集。
图 5 USART1挂载在APB2时钟上
中断事件的一些事件标志位很重要,用于代码的理解。
图 6 USART中断请求的一些事件标志位
单片机传输发送数据可以直接发送。
单片机接收数据则要用中断,因为要做好准备接收,如果在while里面写接收程序,因为while里面除了接收程序还有其他程序,当运行到其他程序而数据传入时会无法接收,因此需要将接收程序写入中断,而中断可以看作是程序中有最高优先级的,所以将接收程序写进中断。
Experiment Steps
在芯片手册找到USART1_TX和USART1_RX对应的接口为PA9和PA10。将其设置为推挽输出,其他参数为默认值。
图 7 USART1对应接口
参数设置115200波特率 8bit数据位。
需要打开STM32的中断,使能USART1 global interrupt。
初始化串口设置中断。
RXNE寄存器判断是否接收到信息,调库USART_ReceiveData(USART1),8bits接收数据,若采集到16bits数据,截取低8bits,因为人为设定数据位为8bits。高的8bits为零,低的8bits为接收数据。
通过检测回车加换行\r\n判断发送数据的结尾。
位宽:认为定义数据位为8bit,因为还有\r\n占两位,所以8+2=10bit,需要16bits接收。
编写程序时用debug一步步走,看到当接收1的时候对应了0x31(为1的ASCII码),它为ASCII码编码,若检测到\r则第14位(用16bits接收,0-15,所以倒数第二位为第14位)接收位设置为1,检测到\n则第15位接收位设置为1。
高位数据对应地址低位01000000... \r对应0x0d \n对应0x0a
传输的数值放到BUF中缓存
8000即\n位为1标志结束,将其通过串口发回来。
Code: (该代码部分参考了已有文章)
#include "main.h"
UART_HandleTypeDef huart1;
char RxBuf[4];
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
HAL_UART_Receive_IT(&huart1,(uint8_t *)RxBuf,1);
while (1)
{
}
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *UartHandle){
if(UartHandle->Instance == USART1){
HAL_UART_Transmit(&huart1, (uint8_t *)RxBuf, 1, 0XFFFF);
HAL_UART_Receive_IT(&huart1, (uint8_t *)RxBuf, 1);
}
}
Result
能够成功收发数据。
图 8 Result