本篇博客重点在于标准库函数的理解与使用,搭建一个框架便于快速开发
目录
USART简介
USART时钟使能
USART初始化
串口参数
串口数据时序
USART中断配置
USART使能
数据的接收与发送
Serial.h
Serial.c
main.c
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
USART时钟使能
已知USART1在APB2总线;USART2,USART3,USART4,USART5都在APB1总线(如图)
由RCC时钟树,需要使能USART外设对应的时钟
USART1
USART2,USART3,USART4,USART5
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);//USART1时钟使能
引脚模式
GPIO的其它参数的理解可以阅读下方博客,这里不再赘述。
【STM32】GPIO和AFIO标准库使用框架-CSDN博客
STM32F103C8T6的PA9默认复用功能为USART1的Tx,PA10为USART1的Rx
PA9的IO口:片上外设在这里接USART1的Tx
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;//Tx
PA10的IO口:串口协议空闲时为高电平,故选择上拉输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//Rx
USART初始化
串口参数
- 波特率:串口通信的速率,1秒传输多少位
- 起始位:标志一个数据帧的开始,固定为低电平
- 数据位:数据帧的有效载荷,1为高电平,0为低电平,低位先行
- 校验位:用于数据验证,根据数据位计算得来
- 停止位:用于数据帧间隔,固定为高电平
串口数据时序
数据位有8位,无校验位;1位停止位
数据位有9位,最后1位数据位是校验位;1位停止位
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;//1位停止位USART_InitStructure.USART_WordLength = USART_WordLength_8b;//数据长度8位USART_Init(USART1, &USART_InitStructure);
USART中断配置
NVIC的其它参数的理解可以阅读下方博客,这里不再赘述。
【STM32】EXTI与NVIC标准库使用框架-CSDN博客
当USART模块发生中断时,中断信号将被发送到NVIC,并且NVIC将向处理器发出请求处理中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//USART1接收缓冲区非空中断使能NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//使能USART1 全局中断的IRQ通道并配置中断抢占优先级与响应优先级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使能
USART1初始化最后调用即可
USART_Cmd(USART1, ENABLE);
数据的接收与发送
数据的发送
串口发送数据函数是将待发的数据放在TDR寄存器,若发送移位寄存器中没有数据,TDR寄存器就会将数据放在发送寄存器中,发送寄存器中的数据一位一位被发送(低位先被发送),此时TDR为空并会有TXE(发送数据寄存器空)标志位,提示可以将下一个数据发在TDR里了。
USART_SendData(USART1, Byte); //将字节数据写入发送数据寄存器,写入后USART自动生成时序波形while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //等待发送完成/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
数据的接收
串口接收数据一位一位接收(低位先被接收),接收到8位后,硬件将数据放在接收数据寄存器中,接收数据寄存器有数据并会有RNEX(读数据寄存器非空)标志位,提示可以读出数据了。
//USART1的中断服务函数
void USART1_IRQHandler(void)
{if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)//如果接收到了数据{Serial_RxData = USART_ReceiveData(USART1);//取出数据Serial_RxFlag = 1; //接收完成USART_ClearITPendingBit(USART1, USART_IT_RXNE);//清楚接收标志位}
}
Serial.h
#ifndef __SERIAL_H
#define __SERIAL_H#include <stdio.h>void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number, uint8_t Length);
void Serial_Printf(char *format, ...);uint8_t Serial_GetRxFlag(void);
uint8_t Serial_GetRxData(void);#endif
Serial.c
#include "stm32f10x.h" // Device header
#include <stdio.h> //用于fputc重定向printf
#include <stdarg.h> //用于 Serial_Printf()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;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;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;USART_Init(USART1, &USART_InitStructure);USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);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);
}/*** 函 数:串口发送一个字节* 参 数:Byte 要发送的一个字节* 返 回 值:无*/
void Serial_SendByte(uint8_t Byte)
{USART_SendData(USART1, Byte); //将字节数据写入数据寄存器,写入后USART自动生成时序波形while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //等待发送完成/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}/*** 函 数:串口发送一个数组* 参 数:Array 要发送数组的首地址* 参 数:Length 要发送数组的长度* 返 回 值:无*/
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{uint16_t i;for (i = 0; i < Length; i ++) //遍历数组{Serial_SendByte(Array[i]); //依次调用Serial_SendByte发送每个字节数据}
}/*** 函 数:串口发送一个字符串* 参 数:String 要发送字符串的首地址* 返 回 值:无*/
void Serial_SendString(char *String)
{uint8_t i;for (i = 0; String[i] != '\0'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止{Serial_SendByte(String[i]); //依次调用Serial_SendByte发送每个字节数据}
}/*** 函 数:次方函数(内部使用)* 返 回 值:返回值等于X的Y次方*/
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{uint32_t Result = 1; //设置结果初值为1while (Y --) //执行Y次{Result *= X; //将X累乘到结果}return Result;
}/*** 函 数:串口发送数字* 参 数:Number 要发送的数字,范围:0~4294967295* 参 数:Length 要发送数字的长度,范围:0~10* 返 回 值:无*/
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'); //依次调用Serial_SendByte发送每位数字}
}/*** 函 数:使用printf需要重定向的底层函数* 参 数:保持原始格式即可,无需变动* 返 回 值:保持原始格式即可,无需变动*/
int fputc(int ch, FILE *f)
{Serial_SendByte(ch); //将printf的底层重定向到自己的发送字节函数return ch;
}/*** 函 数:自己封装的prinf函数* 参 数:format 格式化字符串* 参 数:... 可变的参数列表* 返 回 值:无*/
void Serial_Printf(char *format, ...)
{char String[100]; //定义字符数组va_list arg; //定义可变参数列表数据类型的变量argva_start(arg, format); //从format开始,接收参数列表到arg变量vsprintf(String, format, arg); //使用vsprintf打印格式化字符串和参数列表到字符数组中va_end(arg); //结束变量argSerial_SendString(String); //串口发送字符数组(字符串)
}/*** 函 数:获取串口接收标志位* 参 数:无* 返 回 值:串口接收标志位,范围:0~1,接收到数据后,标志位置1,读取后标志位自动清零*/
uint8_t Serial_GetRxFlag(void)
{if (Serial_RxFlag == 1){Serial_RxFlag = 0;return 1;}return 0;
}/*** 函 数:获取串口接收的数据* 参 数:无* 返 回 值:接收的数据,范围:0~255*/
uint8_t Serial_GetRxData(void)
{return Serial_RxData;
}void USART1_IRQHandler(void)
{if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET){Serial_RxData = USART_ReceiveData(USART1);Serial_RxFlag = 1;USART_ClearITPendingBit(USART1, USART_IT_RXNE);}
}
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"uint8_t RxData;int main(void)
{OLED_Init();OLED_ShowString(1, 1, "RxData:");Serial_Init();Serial_SendByte(0x41);uint8_t MyArray[] = {0x42, 0x43, 0x44, 0x45};Serial_SendArray(MyArray, 4);Serial_SendString("\r\nNum1=");Serial_SendNumber(111, 3);/*下述3种方法可实现printf的效果*//*方法1:直接重定向printf,但printf函数只有一个,此方法不能在多处使用*/printf("\r\nNum2=%d", 222); //串口发送printf打印的格式化字符串//需要重定向fputc函数,并在工程选项里勾选Use MicroLIB/*方法2:使用sprintf打印到字符数组,再用串口发送字符数组,此方法打印到字符数组,之后想怎么处理都可以,可在多处使用*/char String[100]; //定义字符数组sprintf(String, "\r\nNum3=%d", 333);//使用sprintf,把格式化字符串打印到字符数组Serial_SendString(String); //串口发送字符数组(字符串)/*方法3:将sprintf函数封装起来,实现专用的printf,此方法就是把方法2封装起来,更加简洁实用,可在多处使用*/Serial_Printf("\r\nNum4=%d", 444); //串口打印字符串,使用自己封装的函数实现printf的效果Serial_Printf("\r\n");while (1){if (Serial_GetRxFlag() == 1){RxData = Serial_GetRxData();Serial_SendByte(RxData);OLED_ShowHexNum(1, 8, RxData, 2);}
}