uart

串口简介

串口(Serial Port),也称为串行通信接口,是一种用于设备间按位顺序传输数据的通信方式。在嵌入式系统,特别是STM32微控制器中,串口是非常常见的通信接口,用于实现设备间的命令交互、数据传输、调试信息输出等任务。

基本概念

串行通信:数据一位接一位地按顺序进行发送和接收,相对于并行通信而言,减少了物理线路数量,降低了成本和复杂性,适用于远距离、低速率或需要节省引脚资源的场合。

异步通信:STM32串口通常采用异步通信方式,数据包由起始位、数据位、校验位(可选)和停止位组成,收发双方无需共享同一时钟信号,而是通过约定的波特率(比特率)和帧格式保持同步。

接口标准

最常用的串口标准是RS-232RS-485TTL电平

  • 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库等进行配置和编程。编程步骤通常包括:

  1. 时钟使能:通过RCC寄存器或库函数启用USART/UART接口所需的时钟源。

  2. 串口初始化:配置串口工作参数(波特率、数据位、校验位、停止位、流控等),设置中断/DMA相关参数(如使能中断源、配置DMA通道等)。

  3. GPIO配置:将USART/UART相关的TX、RX引脚配置为复用开漏(或推挽)输出和浮空输入模式,并连接到对应的USART/UART外设。

  4. 启动串口:使能USART/UART接收和/或发送,并在必要时启动DMA传输。

  5. 数据收发:编写发送数据函数(通过USART_SendData等)和接收数据回调函数(中断服务程序或DMA传输完成回调)。

  6. 错误处理:监测并处理通信错误,如帧错误、噪声错误、溢出错误等。

综上所述,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)
            ; //等待数据发送完
    }
}