Skip to content

串口通讯

通讯原理

  • 传送方式: 并行串行

image-20230630185444953

串行: USART, I2C, SPI只有一两根数据线

并行: SDIO, FSMC

image-20230630190029879

  • 通信的方向

全双工: 同时收发数据USART, SPI

半双工: 不可同时收发数据, 分时收发数据SPI

单工: 任何时候都是固定一个方向传输

  • 同步方式

同步: 有时钟信号, 效率比较高, 对时钟允许误差比较小

异步: 没有时钟信号, 加上辅助标志符

  • 速率

比特率bit/s, 每秒二进制数的位数

波特率: 每秒传输的码元个数

0V-3.3V一个二进制位为一个码元

用多种不同的电压代表不同的信息, 码元和位数就不同了

比特率 = 波特率*log2 M, M表示每一个码元常在的信息量

image-20231128092521226

串口

串行通信接口, 指的是按照位进行发送和接收的接口, 比如RS-232/422/485等, 232是标准的接口

RS-232接口(DB9): 简化以后的接口

image-20231128093037099

异步通讯的话只需要上面标红的三个引脚

通信协议

物理层

使用的RS232电平, 逻辑1 -15V ~ -3V 逻辑0: +3V ~ +15V

COMS电平: 逻辑1: 3.3V, 逻辑0: 0V

TTL电平: 逻辑1: 5V, 逻辑0: 0V

image-20230630195705940

3.3V的时候可以使用 MAX3232, 或者SP3232

5V的时候可以使用的转换芯片 MAX232

image-20230630200043103

  • TTL

芯片里面出来的都是TTL电平

  • USB转串口

主要芯片有CH340, PL2303, CP2102, FT232

image-20231128093750475

协议层

image-20230630200659430

image-20231128093853663

起始位是一个逻辑0

结束位是0.5、1、1.5或2个逻辑1表示

有效数据: 经常被约定为5, 6, 7, 8, 9位长度, LSP在前面(低位在前), MSP在后

校验位:

  • 奇校验, 加上校验位之后1的个数为奇数
  • 偶校验, 加上校验位之后1的个数为偶数
  • 无校验
  • 零校验
  • 一校验

STM的实现

USART: Universal syschronous asynchronous receive transmitter 通用数据同步异步收发器

UART: Universal asynchronous receive transmitter 通用数据异步收发器

这两种模式都可以与外部设备进行全双工异步通讯

可以配置使用DMA多缓冲器通信

有多个标志位的中断源

image-20230630201454008

  • H7

image-20231128100606231

主要是时钟, 有两个时钟域, 一个用于控制, 另一个用于实际的工作, 还多了一个FIFO

image-20231128100904897

实际使用

  • 发送

UE, TE打开发送时钟和通道

数据放到TDR, 之后传到发送移位寄存器, 最后发送到TX

之后TXE位为1表示发送数据寄存器数据空了, 这时候不能写

之后TC寄存器表示发送移位寄存器空了

  • 接收

首先使能寄存器, UE,RE

之后数据进来, 进入移位寄存器, 设置RXNE寄存器为1

可以从RDR读取

image-20230630201541935

是使用有5根线

  • TX: 数据发送
  • RX: 数据接收
  • SCLK: 时钟, 可选
  • nRTX: 请求发送, 可选, n=>低电平有效, 硬件控制
  • nCTX: 允许发送
  • 其他引脚: 红外编码模块

image-20230701135847759

注: 实际在使用的时候注意时钟

寄存器

  • USART_SR: 状态寄存器

image-20230701142540187

image-20230701143758579

主要使用到的是位5, 6

  • USART_BRR: 波特比率寄存器, 设置分频因子

image-20230701151341333

image-20230701151356780

fck是串口的时钟, 根据总线的不同变化, 串口1是PCLK2对应72MHz, 其他的对应的是32MHz

算出来的这一个值分为整数和小数部分, 分别保存在不同的位置

image-20231128101459759

image-20231128101611218

image-20230701151822599

image-20231128102014721

加0.5是为了四舍五入

统一的公式

由于左移四位相当于乘了一个16, 所以可以和小数部分合起来

image-20231128193528619

  • 其他的F4F7H7系列

image-20231128194031886

OVER8这个位可以设置为1或者0, F1只有16位

image-20231128194424041

过采样是一种信号处理技术,其目的是提高采样率以增加信号分辨率并提高信噪比。在过采样的过程中,信号被多次采样,每次采样之间的间隔非常小。这样做是因为在采样率低的情况下,信号中的某些细节会被忽略,从而降低信号的精度。通过过采样,可以增加采样点的数量,使信号的细节更加清晰。同时,过采样还可以减少噪声对信号的影响,提高信号质量。

image-20231128194636264

使用16倍的时候没有区别, 使用8位的时候有一位不被使用

image-20231128194818412

image-20231128195019450

使用USART_CR1寄存器的位15设置使用几位过采样

image-20231128195131851

image-20231128195223838

  • USART_DR: 数据寄存器

包含了发送或接收的数据。由于它是由两个寄存器组成的,一个给发送用(TDR),一个给接收 用(RDR),该寄存器兼具读和写的功能。TDR寄存器提供了内部总线和输出移位寄存器之间的 并行接口(参见图248)。RDR寄存器提供了输入移位寄存器和内部总线之间的并行接口。 当使能校验位(USART_CR1中PCE位被置位)进行发送时,写到MSB的值(根据数据的长度不 同,MSB是第7位或者第8位)会被后来的校验位该取代。 当使能校验位进行接收时,读到的MSB位是接收到的校验位。

一个地址对应两个实际的物理地址,九位有效

  • USART_CR1: 控制寄存器

image-20231128195848700

M位(12): 控制数据的长度, 1: 一个起始位, 9个数据位, n个停止位, 0: 八个数据位

image-20230701142324741

一般会禁止校验位

image-20230701142340297

image-20230701142434024

可以通过读取状态寄存器知道实际的问题

image-20230701142643055

打开串口, 实际控制的是时钟

image-20230701142757448

控制发送接收使能需要设置为1

image-20230701143557806

其他各种中断, 一般会使能位5的中断(接收中断)

  • USART_CR2: 控制寄存器

image-20230701142246978

  • USART_CR3: 控制硬件

image-20231128200951893

  • USART_GTPR: 保护时间, 预分频寄存器

固件库实现

c
typedef struct
{
  uint32_t USART_BaudRate;            /*!< This member configures the USART communication baud rate.
                                           设置波特率 */

  uint16_t USART_WordLength;          /*!< Specifies the number of data bits transmitted or received in a frame.
                                           This parameter can be a value of @ref USART_Word_Length设置数据长度 */

  uint16_t USART_StopBits;            /*!< Specifies the number of stop bits transmitted.
                                           This parameter can be a value of @ref USART_Stop_Bits 设置停止位长度*/

  uint16_t USART_Parity;              /*!< Specifies the parity mode.
                                           This parameter can be a value of @ref USART_Parity
                                           @note When parity is enabled, the computed parity is inserted
                                                 at the MSB position of the transmitted data (9th bit when
                                                 the word length is set to 9 data bits; 8th bit when the
                                                 word length is set to 8 data bits). 设置校验, 奇校验, 偶校验*/
 
  uint16_t USART_Mode;                /*!< Specifies wether the Receive or Transmit mode is enabled or disabled.
                                           This parameter can be a value of @ref USART_Mode 设置模式, 接收还是发送*/

  uint16_t USART_HardwareFlowControl; /*!< Specifies wether the hardware flow control mode is enabled
                                           or disabled.
                                           This parameter can be a value of @ref USART_Hardware_Flow_Control
                                           硬件流控制器, 是否使用后面的两根线 */
} USART_InitTypeDef;
c
typedef struct
{
  //设置时钟
  uint16_t USART_Clock;   /*!< Specifies whether the USART clock is enabled or disabled.
                               This parameter can be a value of @ref USART_Clock 设置使能不使能*/

  uint16_t USART_CPOL;    /*!< Specifies the steady state value of the serial clock.
                               This parameter can be a value of @ref USART_Clock_Polarity 极性, 空闲时候电平*/

  uint16_t USART_CPHA;    /*!< Specifies the clock transition on which the bit capture is made.
                               This parameter can be a value of @ref USART_Clock_Phase 相位, 读取的边沿*/
						//上面的两种相互组合有四种不同的效果
  uint16_t USART_LastBit; /*!< Specifies whether the clock pulse corresponding to the last transmitted
                               data bit (MSB) has to be output on the SCLK pin in synchronous mode.
                               This parameter can be a value of @ref USART_Last_Bit 最后一位的时钟是否输出*/
} USART_ClockInitTypeDef;

实际实现

image-20230701154521695

  1. 初始化GPIO引脚, RX设置为浮空输入, TX设置为复用推挽输出
  2. 之后初始化串口, 初始化之后使能
  3. 初始化NVIC
  4. 初始化中断, 如果读取寄存器不为空的时候就产生中断
  5. 在传递的时候判断标志位, TXE是数据移入移位寄存器, TC是移位寄存器为空的时候
c
void USART_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	//初始化时钟
	DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
	DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);
	
	
	//设置GPIO
	GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
	
	GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
	
	//配置波特率
	USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
	//数据长度
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	//停止位
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	//校验位
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	//初始化串口
	USART_Init(DEBUG_USARTx, &USART_InitStructure);
	//初始化NVIC
	NVIC_Configuration();
	//初始化中断,在接受的时候产生中断
	USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);
	//使能串口
	USART_Cmd(DEBUG_USARTx, ENABLE);
	
}


void Usart_SendByte(USART_TypeDef* pUSARTx, uint8_t data)
{
	
	USART_SendData(pUSARTx, data);
	while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}

void Usart_sendHalfWord(USART_TypeDef* pUSARTx, uint16_t data)
{
	uint8_t temp_h, temp_l;
	temp_h = (data & 0xff00)>>8;
	temp_l = data & 0xff;
	USART_SendData(pUSARTx, temp_h);
	while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
	USART_SendData(pUSARTx, temp_l);
	while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}


void Usart_sendString(USART_TypeDef* pUSARTx, uint8_t* data, uint8_t num)
{
	int i;
	for(i=0 ; i<num ; i++)
	{
		Usart_SendByte(pUSARTx, data[i]);
	}
    //这里设置为TC为发送完成的时候循环结束
    while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TC) == RESET);
}

: 不能在循环过程中判断USART_FLOG_TC, 因为在初始化的时候TC为1, 所以这时候不会进入循环, 直接发送第二字节的内容, 会覆盖第一字节导致出错

初始化printf函数

重写fputc函数

c
//重定义C库函数
int fputc(int ch, FILE *f)
{
	USART_SendData(DEBUG_USARTx, (uint8_t)ch);
	while(USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);
	return ch;
}

int fgetc(FILE *f)
{
	while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);
	return (int)USART_ReceiveData(DEBUG_USARTx);
}

在使用后一个函数的时候最好不初始化中断, 或者重写中断函数, 否则会卡住

RS485

image-20240103214008958

image-20240103214408382

image-20240103214619337

image-20240103215319958

image-20240103215414833

在连接的时候A和A连接, B和B连接