串口介绍
串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信
单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大地扩展了单片机的应用范围,增强了单片机系统的硬件实力
硬件电路:
简单双向串口通信有两根通信线(发送端TX和接收端RX),其中TX与RX要交叉连接,当只需单向的数据传输时,可以只接一根通信线;当电平标准不一致时,需要加电平转换芯片
电平标准:
电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:
- TTL电平:+3.3V或+5V表示1,0V表示0
- RS232电平:-3~-15V表示1,+3~+15V表示0
- RS485电平:两线压差+2~+6V表示1,-2~-6V表示0(差分信号)
串口参数及时序:
无校验位:
有校验位:
- 波特率:串口通信的速率
- 起始位:标志一个数据帧的开始,固定为低电平
- 数据位:数据帧的有效载荷,1为高电平,0为低电平,低位先行
- 校验位:用于数据验证,根据数据位计算得来
- 停止位:用于数据帧间隔,固定为高电平
USART
简介
USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步收发器
- USART是STM32内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可自动接收RX引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器里
- 自带波特率发生器,最高达4.5Mbits/s
- 可配置数据位长度(8/9)、停止位长度(0.5/1/1.5/2)
- 可选校验位(无校验/奇校验/偶校验)
- 支持同步模式、硬件流控制、DMA、智能卡、IrDA、LIN
框图
左上角的TX和RX为发送、接受引脚,SW_RX、IRDA_OUT、IRDA_IN为智能卡和IrDA通信的引脚;
TX发送引脚连接发送移位寄存器,RX接受引脚连接接收移位寄存器;两个数据寄存器:发送数据寄存器(TDR)、接受数据寄存器(RDR)占用同一个地址,程序上表现为数据寄存器DR;
发送移位寄存器的作用是将一个字节的数据一位一位的移出,对应串口协议中的数据位。当往发送数据寄存器中写入数据时,发送数据寄存器会检查移位寄存器是否有正在移动的数据,若无,则发送数据寄存器中的数据将全部写入发送移位寄存器,准备发送,并将标志位TXE置1(此时数据未必已经发送),此时,即可在发送数据寄存器中写入下一个数据;发送移位寄存器会在发送器控制的驱动下向右移位(低位先行),一位一位的将数据输出到TX引脚;在发送数据寄存器和发送移位寄存器双重缓存的作用下,能够在连续发送数据的情况下,数据帧之间没有空闲,提高效率;
接收端同理,不过方向相反,数据在接收器控制的驱动下由RX引脚传输到接受移位寄存器中,再传输到接收数据寄存器中,并置标志位RXNE为1;
硬件数据流控,可用于避免发送设备太快时接收设备来不及处理而出现数据丢失或者覆盖现象;硬件数据流控有两个引脚:nRTS为请求发送引脚,为输出脚,低电平有效,用于传递“当前是否能接收数据”的信息;nCTS为清除发送引脚,为输入脚,低电平有效,用于接受其他设备的nRTS信号。在使用时,通常为nRTS连接其他设备的nCTS,当RX能接受数据时,nRTS置低电平,请求对方发送,当无法接收新数据时,如接受数据寄存器一直未读,则nRTS置高电平,其他设备的nRTS接收到后 ,其TX引脚就会暂停发送数据;
右侧的SCLK模块用于配合发送移位寄存器产生同步的时钟信号,只能用于输出,无法输入,故两个USART之间无法实现同步通信;该模块常用于兼容其他通讯协议,如spi,或用于自适应波特率;
中间的唤醒单元可用于实现串口挂载多设备,在USART地址中可给串口分配特定地址,当发送指定地址时,设备唤醒单元开始工作;
USART中断控制用于控制中断输出,TXE发送寄存器空、RXNE接收寄存器非空用于判断发送状态和接收状态;
波特率发生器:对APB时钟分频,得到发送和接收的时钟,f_PCLKx(x=1,2),USART1挂载在APB2,故使用PCLK2的时钟,一般为72MHZ,其他的USART挂载在APB1,故为PCLK1的时钟,一般为36MHz;之后时钟进行分频,除去USARTDIV的分频系数(存在整数部分和小数部分),再除以16,得到发送器时钟和接收器时钟;TE和RE分别为发送和接收时钟使能
数据帧
字长:
可选择9位字长,有校验/无校验,或8位字长,有校验/无校验,通常为9位有校验,8位无校验
停止位:
起始位:
相较于数据发送,数据接收需要注意的问题更多,包括每一帧输入采样的位置是否靠近中间,避免因过于靠前或靠后导致电平仍在翻转,以及噪声的滤波;
为解决以上问题,输入电路对采样时钟进行了细分,会以波特率的16倍频率进行采样,即,在移位的时间内采样16次;当出现检测到下降沿(即起始信号)时,会在之后的第3次、第3次、第7次进行一批采样,第8次、第9次、第10次再进行一批采样,这两批采样中,都要求每3位中至少含有两个0,若全为0则无噪声,若有1位为1则会在帐塔寄存器中置NE噪声标志位为1,若3位全为1,则不检测为起始位,电路忽略前面的数据,重新开始捕捉下降沿;
当通过起始位侦测时,则接收状态由空闲转变为接受起始位,同时,第8次、第9次、第10次采样的位置恰好为起始位的正中间,往后接收的数据位就都在第8次、第9次、第10次进行采样
数据采样:
基本原理同上,电路在第8次、第9次、第10次采样数据,没有噪声时,三位全为1/0;有噪声时,按照2:1的规则,若两次为1,则认为数据为1,反之亦然,同时将噪声标志位NE置1
波特率发生器:
- 发送器和接收器的波特率由波特率寄存器BRR里的DIV确定
- 计算公式:波特率 = fPCLK2/1 / (16 * DIV)
代码实现
相关库函数
//USART缺省设置、初始化、结构体初始化
void USART_DeInit(USART_TypeDef* USARTx);
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
void USART_StructInit(USART_InitTypeDef* USART_InitStruct);
//配置同步时钟输出
void USART_ClockInit(USART_TypeDef* USARTx, USART_ClockInitTypeDef* USART_ClockInitStruct);
void USART_ClockStructInit(USART_ClockInitTypeDef* USART_ClockInitStruct);
//使能和中断使能
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);
//开启USART到DMA的触发通道
void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState NewState);
//设置地址、唤醒、LIN
void USART_SetAddress(USART_TypeDef* USARTx, uint8_t USART_Address);
void USART_WakeUpConfig(USART_TypeDef* USARTx, uint16_t USART_WakeUp);
void USART_ReceiverWakeUpCmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_LINBreakDetectLengthConfig(USART_TypeDef* USARTx, uint16_t
USART_LINBreakDetectLength);
void USART_LINCmd(USART_TypeDef* USARTx, FunctionalState NewState);
//发送和接收数据
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);
//智能卡、IrDA相关
void USART_SendBreak(USART_TypeDef* USARTx);
void USART_SetGuardTime(USART_TypeDef* USARTx, uint8_t USART_GuardTime);
void USART_SetPrescaler(USART_TypeDef* USARTx, uint8_t USART_Prescaler);
void USART_SmartCardCmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_SmartCardNACKCmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_HalfDuplexCmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_OverSampling8Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_OneBitMethodCmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_IrDAConfig(USART_TypeDef* USARTx, uint16_t USART_IrDAMode);
void USART_IrDACmd(USART_TypeDef* USARTx, FunctionalState NewState);
//标志位读取和清除
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG);
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);
串口发送和单字节接收代码:
Serial.c函数:
//Serial.c
#include "stm32f10x.h" // Device header
#include <stdio.h>
uint8_t Serial_RxData;
uint8_t Serial_RxFlag;
void Serial_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //TX为输出脚,复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //RX为输入脚,上拉输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600; //波特率
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//不适用流控
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_InitStructure.USART_Parity = USART_Parity_No; //无校验
USART_InitStructure.USART_StopBits = USART_StopBits_1; //一位停止位
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //8位字长
USART_Init(USART1, &USART_InitStructure);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //使能USART1中断
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART1, ENABLE);
}
void Serial_SendByte(uint8_t Byte)
{
USART_SendData(USART1, Byte); //在下一次senddata时,TEX会自动清零,故无需手动清零
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}
void Serial_Array(uint8_t *Array, uint16_t Lehgth)
{
uint16_t i;
for(i=0; i<Lehgth; i++)
{
Serial_SendByte(Array[i]);
}
}
void Serial_SendString(char *String)
{
uint8_t i;
for(i=0; String[i] != '\0'; i++)
{
Serial_SendByte(String[i]);
}
}
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1;
while(Y--)
{
Result *= X;
}
return Result;
}
void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
uint8_t i;
for(i=0; i<Length; i++)
{
Serial_SendByte(Number / Serial_Pow(10, Length-i-1) % 10 + '0');
}
}
int fpuc(int ch, FILE *f) //重定向fputc,用于将printf输出到串口
{
Serial_SendByte(ch);
return ch;
}
uint8_t Serial_GetExFlag(void)
{
if(Serial_RxFlag == 1)
{
Serial_RxFlag = 0;
return 1;
}
return 0;
}
uint8_t Serial_GetRxData(void)
{
return Serial_RxData;
}
void USART1_IRQHandler(void)
{
if(USART_GetFlagStatus(USART1, USART_IT_RXNE) == SET)
{
Serial_RxData = USART_ReceiveData(USART1);
Serial_RxFlag = 1;
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
main函数:
#include "stm32f10x.h" // Device header
#include "delay.h"
#include "LED.h"
#include "KEY.h"
#include "OLED.h"
#include "Serial.h"
int main(void)
{
//LED_Init();
//KEY_Init();
delay_init();
OLED_Init();
Serial_Init();
Serial_SendByte(0x41);
uint8_t MyArray[] = {0x42, 0x43, 0x44, 0x45};
Serial_Array(MyArray, 4);
Serial_SendString("Hello World!\r\n");
Serial_SendNumber(12345, 5);
uint8_t RxData;
while(1)
{
RxData = Serial_GetRxData();
Serial_SendByte(RxData);
OLED_ShowHexNum(1, 1, RxData, 2);
}
}