uart
串口简介
串口(Serial Port),也称为串行通信接口,是一种用于设备间按位顺序传输数据的通信方式。在嵌入式系统,特别是STM32微控制器中,串口是非常常见的通信接口,用于实现设备间的命令交互、数据传输、调试信息输出等任务。
基本概念
串行通信:数据一位接一位地按顺序进行发送和接收,相对于并行通信而言,减少了物理线路数量,降低了成本和复杂性,适用于远距离、低速率或需要节省引脚资源的场合。
异步通信:STM32串口通常采用异步通信方式,数据包由起始位、数据位、校验位(可选)和停止位组成,收发双方无需共享同一时钟信号,而是通过约定的波特率(比特率)和帧格式保持同步。
接口标准
最常用的串口标准是RS-232、RS-485和TTL电平:
- RS-232:传统的串口标准,电气特性为负逻辑(逻辑“1”对应-12V至-3V,逻辑“0”对应+3V至+12V),适用于短距离(<15米)点对点通信。
- RS-485:改进的半双工多点通信标准,具有更好的抗干扰能力和更远的传输距离(>1200米)。电气特性为差分信号,允许连接多个设备。
- TTL电平:适用于嵌入式系统内部或设备间的近距离通信,逻辑“1”对应3.3V或5V,逻辑“0”对应0V。STM32的USART(Universal Synchronous Asynchronous Receiver Transmitter)接口通常直接输出TTL电平,与外部设备通信时可能需要通过电平转换芯片与RS-232或RS-485设备连接。
STM32串口功能特性
STM32微控制器内置多个USART(通用同步异步收发器)或UART(通用异步收发器)接口,支持以下主要功能:
- 数据传输:支持8位或9位数据位长度,奇偶校验(无、奇、偶、标记、空间),1位或2位停止位。
- 波特率设置:通过预分频器和小数波特率发生器配置任意波特率。
- 流控:支持硬件流控(RTS/CTS)和软件流控(XON/XOFF),用于在接收端缓冲区满时暂停发送端数据。
- 多模式支持:除异步通信外,还支持同步通信(如SPI、I2S模式)和单线半双工通信(如LIN模式)。
- 中断与DMA:提供多种中断源(如发送完成、接收完成、接收超时等),并支持DMA传输以减轻CPU负担。
- 多实例:STM32型号各异,但通常提供至少2到5个独立的USART或UART接口。
应用示例
- 设备控制:通过串口发送命令控制外部设备(如电机驱动器、传感器模块等)工作,或接收设备状态反馈。
- 数据采集:传感器或其他数据源通过串口将数据发送给STM32进行处理或存储。
- 调试输出:在开发过程中,通过串口打印日志信息,便于监控程序运行状态。
- 无线模块接口:一些无线模块(如蓝牙、Wi-Fi、LoRa等)提供串口转无线通信的透明传输,STM32通过串口与这些模块通信,实现远程数据交换。
- 多机通信:在RS-485网络中,一个STM32作为主站,其他设备作为从站,通过串口实现多点通信。
编程与配置
STM32串口可通过直接操作寄存器或使用STM32官方提供的HAL库、LL库等进行配置和编程。编程步骤通常包括:
时钟使能:通过RCC寄存器或库函数启用USART/UART接口所需的时钟源。
串口初始化:配置串口工作参数(波特率、数据位、校验位、停止位、流控等),设置中断/DMA相关参数(如使能中断源、配置DMA通道等)。
GPIO配置:将USART/UART相关的TX、RX引脚配置为复用开漏(或推挽)输出和浮空输入模式,并连接到对应的USART/UART外设。
启动串口:使能USART/UART接收和/或发送,并在必要时启动DMA传输。
数据收发:编写发送数据函数(通过USART_SendData等)和接收数据回调函数(中断服务程序或DMA传输完成回调)。
错误处理:监测并处理通信错误,如帧错误、噪声错误、溢出错误等。
综上所述,STM32的串口接口提供了灵活且功能丰富的通信手段,广泛应用于各种嵌入式系统中。根据实际项目需求,选择合适的串口标准、配置相应参数,并编写相应的数据收发代码,即可实现设备间的稳定通信。如果您在实际应用中有具体问题或需要编程指导,欢迎继续提问。
示例代码
#include "reg_uart.h"
// 映射关系:
// PA9 -> TX PA10 -> RX
void reg_uart_init(void)
{
//GPIO时钟开启
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
//配置速度
GPIOA->CRH &= ~(GPIO_CRH_MODE9 | GPIO_CRH_MODE10);
GPIOA->CRH |= GPIO_CRH_MODE9_1 ;//| GPIO_CRH_MODE10_1; //RX->Input,TX->OUT 2MHz
//复用,推挽输出
GPIOA->CRH &= ~(GPIO_CRH_CNF9 | GPIO_CRH_CNF10);
GPIOA->CRH |= GPIO_CRH_CNF9_1 | GPIO_CRH_CNF10_0;//RX->Floating input, TX->AF_OUT Push-pull
//AFIO 时钟开启
RCC->APB2ENR |=RCC_APB2ENR_AFIOEN;
//默认 PA9/PA10
AFIO->MAPR &= ~AFIO_MAPR_USART1_REMAP;
//串口时钟开启
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
//8 Data bits,无奇偶校验
USART1->CR1 &= ~(USART_CR1_M | USART_CR1_PCE);
//TE,RE
USART1->CR1 |= USART_CR1_TE | USART_CR1_RE;
//default: 1 Stop bit
USART1->CR2 &= ~USART_CR2_STOP;
//配置bps=115200,则72MHz,查表得39.0625
USART1->BRR = 0x0270|0x0001;
//开启串口
USART1->CR1 |= USART_CR1_UE;
}
uint32_t reg_uart_sendstr(const uint8_t * str,uint32_t len)
{
uint32_t i = 0;
if(0 == len)
{
while(*(str+i) != '\0')
{
//发送一个字节
while(!(USART1->SR & USART_SR_TXE));
USART1->DR = *(str+i);
i++;
}
}
else
{
for(i = 0; i < len && *(str+i) != '\0'; i++)
{
//发送一个字节
while(!(USART1->SR & USART_SR_TXE));
USART1->DR = *(str+i);
}
}
return i;
}
uint32_t reg_uart_sendhex(const uint8_t * str,uint32_t len)
{
uint32_t i = 0;
uint8_t temp = 0x00;
while(i < len)
{
//发送一个字节
temp = "0123456789ABCDEF"[(*(str+i) & 0xf0) >> 4];
while(!(USART1->SR & USART_SR_TXE));
USART1->DR = temp;
temp = "0123456789ABCDEF"[(*(str+i) & 0x0f)];
while(!(USART1->SR & USART_SR_TXE));
USART1->DR = temp;
while(!(USART1->SR & USART_SR_TXE));
USART1->DR = 0x20;
i++;
}
while(!(USART1->SR & USART_SR_TXE));
USART1->DR = '\r';
while(!(USART1->SR & USART_SR_TXE));
USART1->DR = '\n';
return i;
}
void reg_uart_sendbyte(const uint8_t data)
{
//发送一个字节
while(!(USART1->SR & USART_SR_TXE));
USART1->DR = data;
}
uint8_t reg_uart_recvbyte(void)
{
uint8_t temp;
while(!(USART1->SR & USART_SR_RXNE));
temp = (uint8_t)USART1->DR;
return temp;
}
/**
* @brief printf,scanf需要重定义的函数
*
* 需要注意的是,若此种方式,则需要在MDK中target中勾选
* USE MicroLIB选项
*/
int fputc(int ch, FILE *f)
{
reg_uart_sendbyte(ch);
return ch;
}
int fgetc(FILE *f)
{
return reg_uart_recvbyte();
}
void uart1_demo(void)
{
uint8_t sBuf[9] = "\x1f\x2e\x3d\x4c\x5b\x6a\x79\x80";
reg_uart_init();
while(1)
{
reg_gpio_delay(100);
if(10 != reg_uart_sendstr((const uint8_t *)"UART OK!\r\n",12))
{
reg_uart_sendstr((const uint8_t *)"retLen != needLen!\r\n",0);
}
reg_uart_sendhex(sBuf,8);
}
}
#define RBUF_SIZE 0x400
void uart1_shell_demo(void)
{
static uint8_t tabLen = 8; //默认tab长度等价于8个space
uint8_t rBuf[RBUF_SIZE];
uint32_t rLen = 0;
uint8_t temp = 0x55;
uint32_t i = 0;
led_mode = 0;
reg_gpio_init();
reg_uart_init();
reg_uart_sendstr((const uint8_t *)"uart1_shell_demo\r\n",0);
reg_tim2_init();
reg_time2_intr_init();
//刚刚开始 起始2字符头
reg_uart_sendbyte('>');
reg_uart_sendbyte(' ');
while(1)
{
temp = reg_uart_recvbyte();
if(temp == '\b')
{
if(rLen > 0)
{
reg_uart_sendbyte('\b');
reg_uart_sendbyte(' ');
reg_uart_sendbyte('\b');
rLen --;
}
}
else if(temp == '\r')
{
reg_uart_sendbyte('\r');
reg_uart_sendbyte('\n');
//-----------------------------
// 接收到数据之后,再做处理判断
//-----------------------------
rBuf[rLen] = '\0';
if(0 == strcmp((const char *)rBuf,"led on"))
{
reg_uart_sendstr((uint8_t *)"[Succeed] Led ON !\r\n",0);
led_mode = 1;
}
else if(0 == strcmp((const char *)rBuf,"led off"))
{
reg_uart_sendstr((uint8_t *)"[Succeed] Led OFF !\r\n",0);
led_mode = 0;
}
else if(0 == strcmp((const char *)rBuf,"led flip"))
{
reg_uart_sendstr((uint8_t *)"[Succeed] Led Flip !\r\n",0);
led_mode = 2;
}
else
{
if(rLen != 0)
{
reg_uart_sendstr((uint8_t *)"[Error] unknow command!\r\n",0);
}
}
//-----------------------------
// 数据处理完毕之后,清空buf
//-----------------------------
rLen = 0;
//新一轮开始
reg_uart_sendbyte('>');
reg_uart_sendbyte(' ');
}
else if(temp == '\t')
{
unsigned char taboffset = 0;
taboffset = tabLen - (2+rLen)%tabLen;
for(i = 0; i < taboffset; i++)
{
rBuf[rLen] = ' '; //把tab当成空格存储
reg_uart_sendbyte(' '); //返回也返回空格
}
rLen += taboffset;
}
else if(temp >= 0x20 && temp <= 0x7F)
{
reg_uart_sendbyte(temp);
rBuf[rLen] = temp;
rLen++;
}
else
{
#if 0
//调试信息
reg_uart_sendbyte('\\');
reg_uart_sendbyte('x');
reg_uart_sendbyte("0123456789ABCDEF"[(temp>>4) & 0x0f]);
reg_uart_sendbyte("0123456789ABCDEF"[temp & 0x0f]);
#endif
}
#if 0
switch(led_mode)
{
case 0:
{
LED1_OFF;
}break;
case 1:
{
LED1_ON;
}break;
case 2:
{
//翻转电平
LED1_FLIP;
}break;
}
#endif
}
}
void uart_init(void)
{
USART_InitTypeDef uartConfig;
//----------------------------------------
// 先配置好需要使用的UART1相应的引脚
// 并使能串口时钟
//----------------------------------------
GPIO_InitTypeDef gpioConfig;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
//TX
gpioConfig.GPIO_Pin = GPIO_Pin_9;
gpioConfig.GPIO_Mode = GPIO_Mode_AF_PP;
gpioConfig.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpioConfig);
//RX
gpioConfig.GPIO_Pin = GPIO_Pin_10;
gpioConfig.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &gpioConfig);
//----------------------------------------
// 再配置UART1相应功能
//----------------------------------------
// 1.串口配置
USART_StructInit(&uartConfig);
uartConfig.USART_BaudRate = 115200;
USART_Init(USART1, &uartConfig);
// 2.使能UART
USART_Cmd(USART1, ENABLE);
// 注意:STM32 上电第一个字节丢失问题
// 在写第一个字节之前,需要先清除TC标志
// 1.读取SR,然后写入DR(复位的时候,直接写入DR,没有读取SR过程,若没有手动清除TC,
// 会出现上述情况)
USART_GetFlagStatus(USART1, USART_FLAG_TC);
}
void uart_send_string(const uint8_t *str, uint32_t strlen)
{
uint32_t i = 0;
if (0 == strlen)
{
for (i = 0; *str != '\0'; i++)
{
USART_SendData(USART1, *str);
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) != SET)
; //等待数据发送完,需要放在USART_SendData前面,否则丢失上电的第一个字节
str++;
}
}
else
{
for (i = 0; i < strlen; i++)
{
USART_SendData(USART1, *str);
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) != SET)
; //等待数据发送完,需要放在USART_SendData前面,否则丢失上电的第一个字节
str++;
}
}
}
void uart_send_hex(const uint8_t *str, uint32_t strlen)
{
uint32_t i = 0;
uint8_t temp = 0;
uint8_t enter[3] = "\r\n";
for (i = 0; i < strlen; i++)
{
temp = "0123456789ABCDEF"[(*(str + i) >> 4) & 0x0f];
USART_SendData(USART1, temp);
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) != SET)
; //等待数据发送完
temp = "0123456789ABCDEF"[*(str + i) & 0x0f];
USART_SendData(USART1, temp);
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) != SET)
; //等待数据发送完
temp = 0x20;
USART_SendData(USART1, temp);
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) != SET)
; //等待数据发送完
}
for (i = 0; i < 2; i++)
{
USART_SendData(USART1, enter[i]);
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) != SET)
; //等待数据发送完
}
}