串口收发接线图

串口初始化流程

第一步,开启时钟,把需要用的USART和GPIO的时钟打开。
第二步,GPIO初始化,把TX配置成复用输出,RX配置成输入。(TX引脚是USART外设控制的输出脚,所以要选择复用推挽输出)
第三步,配置USART,直接使用一个结构体,就可以把图上面所有的参数都配置好了。
第四步,如果你只需要发送的功能,就直接开启USART,初始化就结束了。
第五步,如果你还需要接收的功能,可能还需要配置中断。那就在开启USART之前,再加上ITConfig和NVIC的代码就行了。
初始化完成之后,如果要发送数据,就调用一个发送函数就行了;如果要接收数据,就调用一个接收函数就行了。
如果要获取发送和接收的状态,就调用获取标志位的函数,这就是USART外设的使用思路。
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_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
//中断
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, 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);
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
写DR寄存器。
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);
读DR寄存器。
DR寄存器内部有4个寄存器,控制发送与接收。
void USART_ClockInit(USART_TypeDef* USARTx, USART_ClockInitTypeDef* USART_ClockInitStruct);
void USART_ClockStructInit(USART_ClockInitTypeDef* USART_ClockInitStruct);
void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState NewState);
USART初始化
初始化USART,使得单片机连接至的PA9可以接收到单片机发送的信息:
void Serial_Init()
{
//开启RCC USART1、GPIOA的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//配置PA9(输出)
//一般RX配置是浮空/上拉输入,因为串口波形空闲状态是高电平,所以不使用下拉输入。
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //复用输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure); //PA9 配置成复用推挽输出,供USART1的TX使用。
//初始化USART1
USART_InitTypeDef USART_InitStruture;
//波特率
USART_InitStruture.USART_BaudRate=9600;
//硬件流控制
USART_InitStruture.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
//串口模式
USART_InitStruture.USART_Mode=USART_Mode_Tx;
//校验位
USART_InitStruture.USART_Parity=USART_Parity_No;
//停止位
USART_InitStruture.USART_StopBits=USART_StopBits_1;
//字长
USART_InitStruture.USART_WordLength=USART_WordLength_8b;
USART_Init(USART1,&USART_InitStruture);
//使能USART1
USART_Cmd(USART1,ENABLE);
}
发送单个字节的函数:
对USART_DR的写操作,会将TXE标志位清零,所以标志位置1之后,不需要手动清零。
void Serial_SendByte(uint8_t Byte)
{
USART_SendData(USART1, Byte);
//发送数据寄存器空 标志位
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
//这个标志位不需要清0,再发送下一帧数据的时候,标志位自动清0
}
发送数组的函数:
void Serial_SendArray(uint8_t* Array,uint16_t Length)
{
uint16_t i;
for(i=0;i<Length;i++)
{
Serial_SendByte(Array[i]);
}
}
发送数字的函数:
void Serial_SendNumber(uint32_t Number)
{
int8_t Length = 0;
int32_t Y = 1, Count = Number;
// 计算数字的长度和最高位的除数
for ( ; Count > 0; Count /= 10, Length++) {
Y *= 10; // Y 最终会是 Number 的最高位的10的倍数
}
Y /= 10; // 调整 Y 为最高有效位的除数
// 发送每位数字
for (int i = 0; i < Length; i++) {
int digit = (Number / Y) % 10; // 提取当前最高位
Serial_SendByte(digit+0x30);
Y /= 10; // 移动到下一位
}
//换行
Serial_SendByte('\r');
Serial_SendByte('\n');
}
目前这个状态是已经可以进行单片机串口的发送了。我们来用串口工具软件来进行一下实验:

可以看到STM32可以正常的发送数据。
另外,再介绍一下printf函数移植的方法,使用printf函数之前需要打开工程选项,把Use MicroLIB勾上,MicroLIB是Keil为嵌入式平台优化的一个精简库,我们等会要用到的printf函数就可以用这个MicroLIB;接下来我们需要对printf重定向,将printf打印的东西输出到串口,因为printf默认是输出到屏幕,我们单片机没有屏幕,所以要进行重定向:
方法1(printf只能有1个,比如重定向到串口1,串口2再用就没有了)
1.在串口模块里加上stdio.h头文件。
2.重写fputc函数。
int fputc(int ch,FILE* f)
{
Serial_SendByte(ch);
return ch;
}
重定向fputc跟printf有什么关系呢?这是因为fputc是printf函数的底层,printf函数在打印的时候,就是不断调用fputc函数一个个打印的,我们把fputc函数重定向到了串口,那printf自然就输出到串口了。
方法2(sprintf可以指定打印位置,不涉及重定向,每个串口都可以使用其打印)
1.sprintf可以把格式化字符输出到一个字符串里,所以这里可以先定义一个字符串char,长度给够就行。
char String[100];
sprintf(String,"Num=%d\r\n",666);
Serial_SendString(String);
方法3(封装sprintf)
sprintf每次都得先定义字符串,再打印到字符串,再发送字符串,太麻烦了,我们要是能封装一下这个过程,就再好不过了。
由于spritnf函数比较特殊,它支持可变的参数,像我们之前写的函数,参数的个数都是固定的,可变参数这个执行起来比较复杂。
串口工具数据模式
HEX模式/十六进制模式/二进制模式:以原始数据的形式显示。
文本模式/字符模式:以原始数据编码后的形式显示,下面是ASCII码字符集。

串口的接收
对于串口接收初始化来说,可以使用查询和中断两种方法,如果使用查询,那初始化配置完成之后就结束了;如果使用中断,那还需要再初始化中开启中断,配置NVIC。
查询的流程是,在主函数里不断判断RXNE标志位,如果置1了,就说明收到数据了,那再调用ReceiveData,读取DR寄存器,这样就行了。
串口接收初始化
void Serial_Init()
{
//开启RCC USART1、GPIOA的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//配置PA9(输出)
//一般RX配置是浮空/上拉输入,因为串口波形空闲状态是高电平,所以不使用下拉输入。
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //复用输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure); //PA9 配置成复用推挽输出,供USART1的TX使用。
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //上拉输入
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure); //PA10 配置成上拉输入,供USART1的RX使用。
//初始化USART1
USART_InitTypeDef USART_InitStruture;
//波特率
USART_InitStruture.USART_BaudRate=9600;
//硬件流控制
USART_InitStruture.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
//串口模式
USART_InitStruture.USART_Mode=USART_Mode_Tx | USART_Mode_Rx;
//校验位
USART_InitStruture.USART_Parity=USART_Parity_No;
//停止位
USART_InitStruture.USART_StopBits=USART_StopBits_1;
//字长
USART_InitStruture.USART_WordLength=USART_WordLength_8b;
USART_Init(USART1,&USART_InitStruture);
//使能USART1
USART_Cmd(USART1,ENABLE);
}
串口查询接收
int main(void){
OLED_Init();
Serial_Init();
while(1)
{
if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == SET)
{
//接收到一个字节的数据已经在RxData里面了。
RxData = USART_ReceiveData(USART1);
OLED_ShowHexNum(1,1,RxData,2);
//当RDR移位寄存器中的数据被转移到USART_DR寄存器中,该位被硬件置位。
/*如果USART_CR1寄存器中的RXNEIE为1,则产生中断。对USART_DR的读操作
可以将该位清零。*/
//所以读DR硬件会自动清除标志位,我们就不需要再清除标志位了。
}
}
}
串口中断接收
void USART1_IRQHandler()
{
if(USART_GetFlagStatus(USART1,USART_IT_RXNE) ==SET)
{
Serial_RxData = USART_ReceiveData(USART1);
Serial_RxFlag = 1;
//USART_ClearFlag(USART1,USART_IT_RXNE);
}
}
uint8_t Serial_GetRxFlag(void)
{
if(Serial_RxFlag == 1)
{
Serial_RxFlag = 0;
return 1;
}
return 0;
}
uint8_t Serial_GetRxData(void)
{
return Serial_RxData;
}
主函数
uint8_t RxData;
int main(void){
OLED_Init();
Serial_Init();
OLED_ShowString(1,1,"RxData:");
while(1)
{
if(Serial_GetRxFlag() == 1)
{
RxData = Serial_GetRxData();
Serial_SendByte(RxData);
OLED_ShowHexNum(1,8,RxData,2);
}
}
}