Appearance
串口通讯
通讯原理
- 传送方式: 并行串行
串行: USART, I2C, SPI只有一两根数据线
并行: SDIO, FSMC
- 通信的方向
全双工: 同时收发数据USART, SPI
半双工: 不可同时收发数据, 分时收发数据SPI
单工: 任何时候都是固定一个方向传输
- 同步方式
同步: 有时钟信号, 效率比较高, 对时钟允许误差比较小
异步: 没有时钟信号, 加上辅助标志符
- 速率
比特率bit/s, 每秒二进制数的位数
波特率: 每秒传输的码元个数
0V-3.3V一个二进制位为一个码元
用多种不同的电压代表不同的信息, 码元和位数就不同了
比特率 = 波特率*log2 M, M表示每一个码元常在的信息量
串口
串行通信接口, 指的是按照位进行发送和接收的接口, 比如RS-232/422/485等, 232是标准的接口
RS-232接口(DB9): 简化以后的接口
异步通讯的话只需要上面标红的三个引脚
通信协议
物理层
使用的RS232电平, 逻辑1 -15V ~ -3V 逻辑0: +3V ~ +15V
COMS电平: 逻辑1: 3.3V, 逻辑0: 0V
TTL电平: 逻辑1: 5V, 逻辑0: 0V
3.3V的时候可以使用 MAX3232, 或者SP3232
5V的时候可以使用的转换芯片 MAX232
- TTL
芯片里面出来的都是TTL电平
- USB转串口
主要芯片有CH340, PL2303, CP2102, FT232
协议层
起始位是一个逻辑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多缓冲器通信
有多个标志位的中断源
- H7
主要是时钟, 有两个时钟域, 一个用于控制, 另一个用于实际的工作, 还多了一个FIFO
实际使用
- 发送
UE, TE打开发送时钟和通道
数据放到TDR, 之后传到发送移位寄存器, 最后发送到TX
之后TXE位为1表示发送数据寄存器数据空了, 这时候不能写
之后TC寄存器表示发送移位寄存器空了
- 接收
首先使能寄存器, UE,RE
之后数据进来, 进入移位寄存器, 设置RXNE寄存器为1
可以从RDR读取
是使用有5根线
- TX: 数据发送
- RX: 数据接收
- SCLK: 时钟, 可选
- nRTX: 请求发送, 可选, n=>低电平有效, 硬件控制
- nCTX: 允许发送
- 其他引脚: 红外编码模块
注: 实际在使用的时候注意时钟
寄存器
- USART_SR: 状态寄存器
主要使用到的是位5, 6
- USART_BRR: 波特比率寄存器, 设置分频因子
fck是串口的时钟, 根据总线的不同变化, 串口1是PCLK2对应72MHz, 其他的对应的是32MHz
算出来的这一个值分为整数和小数部分, 分别保存在不同的位置
加0.5是为了四舍五入
统一的公式
由于左移四位相当于乘了一个16, 所以可以和小数部分合起来
- 其他的F4F7H7系列
OVER8这个位可以设置为1或者0, F1只有16位
过采样是一种信号处理技术,其目的是提高采样率以增加信号分辨率并提高信噪比。在过采样的过程中,信号被多次采样,每次采样之间的间隔非常小。这样做是因为在采样率低的情况下,信号中的某些细节会被忽略,从而降低信号的精度。通过过采样,可以增加采样点的数量,使信号的细节更加清晰。同时,过采样还可以减少噪声对信号的影响,提高信号质量。
使用16倍的时候没有区别, 使用8位的时候有一位不被使用
使用USART_CR1寄存器的位15设置使用几位过采样
- USART_DR: 数据寄存器
包含了发送或接收的数据。由于它是由两个寄存器组成的,一个给发送用(TDR),一个给接收 用(RDR),该寄存器兼具读和写的功能。TDR寄存器提供了内部总线和输出移位寄存器之间的 并行接口(参见图248)。RDR寄存器提供了输入移位寄存器和内部总线之间的并行接口。 当使能校验位(USART_CR1中PCE位被置位)进行发送时,写到MSB的值(根据数据的长度不 同,MSB是第7位或者第8位)会被后来的校验位该取代。 当使能校验位进行接收时,读到的MSB位是接收到的校验位。
一个地址对应两个实际的物理地址,九位有效
- USART_CR1: 控制寄存器
M位(12): 控制数据的长度, 1: 一个起始位, 9个数据位, n个停止位, 0: 八个数据位
一般会禁止校验位
可以通过读取状态寄存器知道实际的问题
打开串口, 实际控制的是时钟
控制发送接收使能需要设置为1
其他各种中断, 一般会使能位5的中断(接收中断)
- USART_CR2: 控制寄存器
- USART_CR3: 控制硬件
- 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;
实际实现
- 初始化GPIO引脚, RX设置为浮空输入, TX设置为复用推挽输出
- 之后初始化串口, 初始化之后使能
- 初始化NVIC
- 初始化中断, 如果读取寄存器不为空的时候就产生中断
- 在传递的时候判断标志位, 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
在连接的时候A和A连接, B和B连接