同步串行接口除了包含数据线(SPI有两根单向数据线MISO和MOSI,I2C有一根双向数据线SDA)外,还包含时钟线(SPI和I2C的时钟线分别是SCK和SCL)。SPI和I2C都可以连接多个从设备,但两者选择从设备的方法不同:SPI通过硬件(NSS引脚)实现,而I2C通过软件(地址)实现。为了使不同电压输出的器件能够互连,I2C的数据线SDA和时钟线SCL开漏输出。同步串行接口可以用专用接口电路实现,也可以用通用并行接口实现。
串行接口连接串行设备时必须遵循相关的物理接口标准,这些标准规定了接口的机械、电气、功能和过程特性。UART的物理接口标准有RS-232C、RS-449(其中电气标准是RS-422或RS-423)和RS-485等,其中RS-232C和RS-485是最常用的UART物理接口标准。
RS-232C的全称是“数据终端设备(DTE)和数据通信设备(DCE)之间串行二进制数据交换接口技术标准”,其中DTE包括微机、微控制器和打印机等,DCE包括调制解调器MODEM、GSM模块和WiFi模块等。
RS-232C机械特性规定RS-232C使用25针D型连接器,后来简化为9针D型连接器。RS-232C电气特性采用负逻辑:逻辑“1”的电平低于-3V,逻辑“0”的电平高于+3V,这和TTL的正逻辑(逻辑“1”为高电平,逻辑“0”为低电平)不同,因此通过RS-232C和TTL器件通信时必须进行电平转换。
目前微控制器的UART接口采用的是TTL正逻辑,与TTL器件连接不需要电平转换,与采用负逻辑的计算机相连时需要进行电平转换,或使用UART-USB转换器连接。
UART的引脚只有3个:RXD(接收数据)、TXD(发送数据)和GND(地)。用UART连接DTE和DCE时RXD和TXD直接连接。如果用UART连接DTE和DTE(如微机和微控制器),RXD和TXD需交叉连接:
DTE1的TXD(输出)连接DTE2的RXD(输入)
DTE1的RXD(输入)连接DTE2的TXD(输出)
UART的主要指标有两个:数据速率和数据格式。数据速率用波特率表示,数据格式包括1个起始位、5~8个数据位、0~1个校验位和1~2个停止位,如图所示。
通信双方的数据速率和数据格式必须一致,否则无法实现通信。
1、 USART结构及寄存器
USART由收发数据和收发控制两部分组成。
收发数据使用双重数据缓冲:收发数据寄存器和收发移位寄存器,收发移位寄存器在收发时钟作用下完成接收数据的串并转换和发送数据的并串转换。
收发控制包括控制状态寄存器、发送器控制、接收器控制、中断控制和波特率控制等。
USART使用的GPIO引脚:
USART通过7个寄存器进行操作:
USART状态寄存器(SR):
USART控制寄存器1(CR1):
2、USART配置
基本参数:波特率115200 Bits/s,8位字长,无校验,1个停止位
高级参数:接收和发送
波特率的值可以输入,
字长可以选择“8 Bits”或“9 Bits”
校验可以选择“None”、“Even”或“Odd”
停止位可以选择“1”或“2”
数据方向可以选择“Receive and Transmit”、“Receive Only”或“Transmit Only”。
将“Baud Rate”修改为“9600”Bits/s
USART配置完成后生成的相应HAL和LL初始化程序分别在HAL\Core\Src\usart.c和LL\Core\Src\usart.c中,其中主要代码如下:
/* HAL工程 */
huart2.Instance = USART2;
huart2.Init.BaudRate = 9600;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
if (HAL_UART_Init(&huart2) != HAL_OK)
__HAL_RCC_USART2_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_2;
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_3;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* LL工程 */
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_USART2);
LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOA);
GPIO_InitStruct.Pin = LL_GPIO_PIN_2;
GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_USART2);
LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOA);
GPIO_InitStruct.Pin = LL_GPIO_PIN_3;
GPIO_InitStruct.Mode = LL_GPIO_MODE_FLOATING;
LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_USART2);
LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOA);
USART_InitStruct.BaudRate = 9600;
USART_InitStruct.DataWidth = LL_USART_DATAWIDTH_8B;
USART_InitStruct.StopBits = LL_USART_STOPBITS_1;
USART_InitStruct.Parity = LL_USART_PARITY_NONE;
USART_InitStruct.TransferDirection = LL_USART_DIRECTION_TX_RX;
LL_USART_Init(USART2, &USART_InitStruct);
LL_USART_Enable(USART2);
(1)USART HAL库函数
基本的USART HAL库函数在stm32f1xx_hal_usart.h中声明如下:
HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef* huart);
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef* huart,
uint8_t* pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef* huart,
uint8_t* pData, uint16_t Size, uint32_t Timeout);
1)初始化UART
HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef* huart);
参数说明:
★ huart:UART句柄,在stm32f1xx_hal_uart.h中定义如下:
typedef struct __UART_HandleTypeDef
{
USART_TypeDef* Instance; /* UART名称 */
UART_InitTypeDef Init; /* UART初始化参数 */
…………………………………………………………………………………………………
} UART_HandleTypeDef;
HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef* huart);
其中UART_InitTypeDef在stm32f1xx_hal_uart.h中定义如下:
typedef struct
{ uint32_t BaudRate; /* 波特率 */
uint32_t WordLength; /* 字长 */
uint32_t StopBits; /* 停止位数 */
uint32_t Parity; /* 校验方式 */
uint32_t Mode; /* 工作模式 */
…………………………………………………………………………………………………
} UART_InitTypeDef;
HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef* huart);
其中WordLength、StopBits、Parity和Mode分别定义如下:
#define UART_WORDLENGTH_8B 0x00000000U
#define UART_WORDLENGTH_9B ((uint32_t)USART_CR1_M)
#define UART_STOPBITS_1 0x00000000U
#define UART_STOPBITS_2 ((uint32_t)USART_CR2_STOP_1)
HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef* huart);
其中WordLength、StopBits、Parity和Mode分别定义如下:
#define UART_PARITY_NONE 0x00000000U
#define UART_PARITY_EVEN ((uint32_t)USART_CR1_PCE)
#define UART_PARITY_ODD ((uint32_t)(USART_CR1_PCE
| USART_CR1_PS))
#define UART_MODE_RX ((uint32_t)USART_CR1_RE))
#define UART_MODE_TX ((uint32_t)USART_CR1_TE)
#define UART_MODE_TX_RX ((uint32_t)(USART_CR1_TE
| USART_CR1_RE))
2)UART发送
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef* huart,
uint8_t* pData, uint16_t Size, uint32_t Timeout);
参数说明:
★ huart:UART句柄。
★ pData:数据缓存指针。
★ Size:数据长度。
★ Timeout:超时(ms)。
返回值:HAL状态,HAL_OK等。
3)UART接收
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef* huart,
uint8_t* pData, uint16_t Size, uint32_t Timeout);
参数说明:
★ huart:UART句柄。
★ pData:数据缓存指针。
★ Size:数据长度。
★ Timeout:超时(ms)。
返回值:HAL状态,HAL_OK等。
(2)USART LL库函数
基本的USART LL库函数在stm32f1xx_ll_usart.h中声明如下:
ErrorStatus LL_USART_Init(USART_TypeDef* USARTx,
LL_USART_InitTypeDef* USART_InitStruct);
void LL_USART_Enable(USART_TypeDef* USARTx);
uint32_t LL_USART_IsActiveFlag_TXE(USART_TypeDef* USARTx);
uint32_t LL_USART_IsActiveFlag_RXNE(USART_TypeDef* USARTx);
void LL_USART_TransmitData8(USART_TypeDef* USARTx, uint8_t Value);
uint8_t LL_USART_ReceiveData8(USART_TypeDef* USARTx);
1)初始化USART
ErrorStatus LL_USART_Init(USART_TypeDef* USARTx,
LL_USART_InitTypeDef* USART_InitStruct);
参数说明:
★ USARTx:USART名称,取值是USART1或USART2等
★ USART_InitStruct:USART初始化参数结构体指针,初始化参数结构体在stm32f1xx_ll_ usart.h定义如下:
typedefstruct
{ uint32_t USART_BaudRate; /* 波特率 */
uint32_t USART_DataWidth; /* 数据宽度 */
uint32_t USART_StopBits; /* 停止位数 */
uint32_t USART_Parity; /* 校验位 */
…………………………………………………………………………………………………
} USART_InitTypeDef;
其中USART_DataWidth、USART_StopBits和USART_Parity定义如下:
#define LL_USART_DATAWIDTH_8B 0x00000000U
#define LL_USART_DATAWIDTH_9B USART_CR1_M
#define LL_USART_STOPBITS_1 0x00000000U
#define LL_USART_STOPBITS_2 USART_CR2_STOP_1
#define LL_USART_PARITY_NONE 0x00000000U
#define LL_USART_PARITY_EVEN USART_CR1_PCE
#define LL_USART_PARITY_ODD (USART_CR1_PCE | USART_CR1_PS)
2)USART使能
void LL_USART_Enable(USART_TypeDef* USARTx);
参数说明:
★ USARTx:USART名称,取值是USART1或USART2等
3)USART发送寄存器空
uint32_t LL_USART_IsActiveFlag_TXE(USART_TypeDef* USARTx);
参数说明:
★ USARTx:USART名称,取值是USART1或USART2等
返回值:位状态,0或1。
4)USART接收寄存器不空
uint32_t LL_USART_IsActiveFlag_RXNE(USART_TypeDef* USARTx);
参数说明:
★ USARTx:USART名称,取值是USART1或USART2等
返回值:位状态,0或1。
5)USART发送8位数据
void LL_USART_TransmitData8(USART_TypeDef* USARTx, uint8_t Value);
参数说明:
★ USARTx:USART名称,取值是USART1或USART2等
★ Value:发送数据
6)USART接收8位数据
uint8_t LL_USART_ReceiveData8(USART_TypeDef* USARTx);
参数说明:
★ USARTx:USART名称,取值是USART1或USART2等
返回值:接收数据
3、USART设计实例
系统包括:
Cortex-M3 CPU(内嵌SysTick定时器);
按键、LED;
LCD显示屏;
UART2接口:PA2-TX2和PA3-RX2;
UART2经UART-USB转换后通过USB线与PC连接;如下图:
下面编程实现SysTick秒计时,UART2发送秒值到PC(每秒发送1次),并接收PC发送的数据对秒进行设置。
UART的软件设计与实现在LCD实现的基础上完成,包括接口函数和处理函数设计与实现。
1)接口函数设计与实现
接口函数设计与实现的步骤如下:
(1)在usart.h中添加下列代码:
/* USER CODE BEGIN Prototypes */
void UART_Transmit(uint8_t* ucData, uint8_t ucSize);
uint8_t UART_Receive(uint8_t* ucData);
/* USER CODE END Prototypes */
(2)在usart.c的后部添加下列代码:
/* HAL工程代码 */
void UART_Transmit(uint8_t* ucData, uint8_t ucSize)
{
HAL_UART_Transmit(&huart2, ucData, ucSize, 100);
}
uint8_t UART_Receive(uint8_t* ucData)
{
return HAL_UART_Receive(&huart2, ucData, 1, 0);
}
/* LL工程代码 */
void UART_Transmit(uint8_t* ucData, uint8_t ucSize)
{
for (uint8_t i = 0; i < ucSize; ++i) { /* 等待发送寄存器空 */
while (LL_USART_IsActiveFlag_TXE(USART2) == 0) {}
LL_USART_TransmitData8(USART2, *ucData++);
} /* 发送8位数据 */
}
uint8_t UART_Receive(uint8_t* ucData)
{ /* 接收寄存器不空 */
if (LL_USART_IsActiveFlag_RXNE(USART2) == 1) {
*ucData = LL_USART_ReceiveData8(USART2);
return 0; /* 接收数据并返回0 */
} else {
return 1;
}
}
2)处理函数设计与实现
(1)在main.c中定义如下全局变量:
uint8_t ucUrx[20], ucUno, ucSec2; /* UART接收值,接收计数,发送延时 */
(2)在main.c中声明如下函数:
void UART_Proc(void); /* UART处理 */
(3)在while (1)中添加如下代码:
UART_Proc();
(4)在LCD_Proc()后添加如下代码:
void UART_Proc(void) /* UART处理 */
{
if (ucSec2 != ucSec) { /* 1s到 */
ucSec2 = ucSec;
printf("%04u\r\n", ucSec); /* 发送秒值和回车换行 */
}
if (UART_Receive(ucUrx) == 0) { /* 接收到字符 */
ucUrx[++ucUno] = ucUrx[0]; /* 保存字符 */
if (ucUno >= 2) { /* 修改秒值 */
ucSec = (ucUrx[1]-0x30)*10+ucUrx[2]-0x30;
ucUno = 0;
}
}
}
int fputc(int ch, FILE* f) /* printf()实现 */
{
UART_Transmit((uint8_t*)&ch, 1);
return ch;
}
编译下载运行程序,打开串口终端,显示秒值。在串口终端中发送2个数字,秒值应该改变。