串口通信的介绍在上次介绍了,传送门
本文使用单片机内部自带的外设硬件串口通信来与其他设备通信
使用异步全双工的串口通信与计算机通信,需要使用3根线,一个USB转TTL的模块
这是模块图片,需要注意的是stm32使用3.3v的ttl电平
需要将3v3和vcc用跳线帽短接起来,这个模块没有对外供电的功能,所以单片机需要外接其他电源
连线如下示意图
单片机的通信管脚是根据编程使用的串口决定的,详情见这个表中的USARTx_TX(USARTx_RX)
名称 | USART1 | USART2 | USART6 |
---|---|---|---|
总线 | APB2 | APB1 | APB2 |
TXD | PA9 / PB6 | PA2 | PA11 |
RXD | PA10 / PB7 | PA3 | PA12 |
TXD和RXD都使用 复用 推挽 浮空 即可
GPIO的初始化之前介绍过了,传送门
复用模式其实就是使用来自片内外设的数据的一种模式
这里给个例子
复用选择是因为一个管脚可能可以连接多个外设,因此我们需要选出需要的那个外设
使用这个函数选择
void GPIO_PinAFConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_PinSource, uint8_t GPIO_AF)
第一个参数是GPIO组
第二个参数是选择那个管脚,为GPIO_PinSource0 到 GPIO_PinSource15
第三个参数是连接到哪个外设,具体取值请看标准库的函数定义
这里将GPIO初始化和复用选择的例子,我使用的是USRAT1
void Usart_GPIO_init(void)
{
GPIO_InitTypeDef GPIO_InitStruct; //声明结构体
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); //开启GPIO时钟
GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1); //设置复用
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1); //设置复用
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; //复用模式
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; //推挽
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //管脚
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; //浮空
GPIO_InitStruct.GPIO_Speed = GPIO_High_Speed; //高速
GPIO_Init(GPIOA, &GPIO_InitStruct); //初始化
}
硬件串口挂载在APB1或APB2总线下
使用这两个函数启用时钟
void RCC_AHB1PeriphClockCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState)
void RCC_AHB2PeriphClockCmd(uint32_t RCC_AHB2Periph, FunctionalState NewState)
第一个参数是时钟名称
第二个是使能或失能
具体名称请在标准库源码中查看
typedef struct
{
uint32_t USART_BaudRate; //比特率
uint16_t USART_WordLength; //数据长度
uint16_t USART_StopBits; //停止位长度
uint16_t USART_Parity; //校验位
uint16_t USART_Mode; //输入输出模式
uint16_t USART_HardwareFlowControl; //硬件流控制,一般写无即可
} USART_InitTypeDef;
波特率,具体解释之前文章说过了,传送门
需要注意的是需要通信双方的波特率相同,可以查询串口助手来选择需要的波特率
字节宽度
一般选择8bit字宽
也就是这个宏定义
USART_WordLength_8b
停止位宽度
可以是0.5,1,1.5,2,按照宏定义选择即可
#define USART_StopBits_1 ((uint16_t)0x0000)
#define USART_StopBits_0_5 ((uint16_t)0x1000)
#define USART_StopBits_2 ((uint16_t)0x2000)
#define USART_StopBits_1_5 ((uint16_t)0x3000)
校验位,可以是奇校验,偶校验,无校验
按宏定义选择
#define USART_Parity_No ((uint16_t)0x0000)//无校验
#define USART_Parity_Even ((uint16_t)0x0400)//偶校验
#define USART_Parity_Odd ((uint16_t)0x0600)//奇校验
输入输出模式,这个可以使用或命令输入多个(|)
按这个宏定义选择即可
USART_Mode_Rx //接收
USART_Mode_Tx //发送
在本文章中无需使用
按这个宏定义配置即可
USART_HardwareFlowControl_None
使用这个函数初始化串口
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct)
传入串口号和上步配置的结构体地址即可,请使用取址符(&)
stm32f401ccu6有1,2,6串口
因此第一个参数取值可以是
USART1
USART2
USART6
例子
USART_InitTypeDef USART_InitStruct; //声明结构体
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启时钟
USART_InitStruct.USART_BaudRate = 9600; //波特率
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //不使用硬件流
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//接收和发送模式
USART_InitStruct.USART_Parity = USART_Parity_No; //无校验位
USART_InitStruct.USART_StopBits = USART_StopBits_1; //停止位位为1位
USART_InitStruct.USART_WordLength = USART_WordLength_8b; //字宽8位长度
USART_Init(USART1, &USART_InitStruct); //初始化
关于NVIC的设置之前说过了,传送门
这里只说明下中断名称
USART1_IRQn
USART2_IRQn
USART6_IRQn
例子
void Usart_NVIC_init(void)
{
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = Usart_IRQChannelPreemptionPriority;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = Usart_IRQChannelSubPriority;
NVIC_Init(&NVIC_InitStruct);
}
使用这个函数打开中断
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState)
第一个输入串口号,第二个输入中断类型,第三个输入使能或使能
中断类型常用如下
USART_IT_RXNE //接收数据中断
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState)
输入串口号和使能或失能即可
中断函数名如下
USART1_IRQHandler
USART2_IRQHandler
USART6_IRQHandler
例子
void Usart_IRQHandler(void)
{
if (USART_GetITStatus(Usart_USARTx, USART_IT_RXNE) != RESET)
{
/*自己的内容*/
USART_ClearITPendingBit(Usart_USARTx, USART_IT_RXNE);
}
}
原型
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT)
名称 | 描述 |
---|---|
输入1 | 串口号 |
输入2 | 中断类型 |
输出 | 设置或未设置 |
功能描述:检测指定的中断类型的置位状态
原型
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT)
名称 | 描述 |
---|---|
输入1 | 串口号 |
输入2 | 中断类型 |
输出 | 无 |
功能描述:清除指定串口的指定中断标志
原型
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG)
名称 | 描述 |
---|---|
输入1 | 串口号 |
输入2 | 标志类型 |
输出 | 设置或未设置 |
常用标志
USART_FLAG_TXE //发送完成标志
USART_FLAG_RXNE //接收完成标志
功能描述:读取指定串口的指定标志
原型
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
名称 | 描述 |
---|---|
输入1 | 串口号 |
输入2 | 发送的数据 |
输入 | 无 |
功能描述:将数据通过指定串口发送
原型
uint16_t USART_ReceiveData(USART_TypeDef* USARTx)
名称 | 描述 |
---|---|
输入1 | 串口号 |
输出 | 接收的数据 |
功能描述:接收来自指定串口的数据
USART_SendData(USART1, (uint8_t)ch); //发送数据
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET) ;//等待发送完成
while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET); //等待接收到数据
void Usart_IRQHandler(void)
{
u8 Dat;
if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
Dat = USART_ReceiveData(USART1);
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
还记得初学c语言时使用的标准输入输出函数
printf();和scanf();吗
输入函数的流程为 获取来自控制台数据->解析数据
输出函数的流程为 解析数据->发送到控制台
获取和发送的程序是使用这两个函数实现的
int fgetc(FILE *f)
int fputc(int ch, FILE *f)
因此我们可以使用一个新的函数覆写原来的函数,将数据通过串口输入输出,这个过程被称之为重定向
int fputc(int ch, FILE *f)
{
USART_SendData(Usart_USARTx, (uint8_t)ch); //发送数据
while (USART_GetFlagStatus(Usart_USARTx, USART_FLAG_TXE) == RESET) //等待发送完成
;
return (ch);
}
将Usart_USARTx
改成自己的串口号即可使用printf输出到串口
int fgetc(FILE *f)
{
while (USART_GetFlagStatus(Usart_USARTx, USART_FLAG_RXNE) == RESET) //等待接收到数据
;
return (int)USART_ReceiveData(Usart_USARTx); //返回数据
}
将Usart_USARTx
改成自己的串口号即可使用scanf从串口获取数据
这是因为格式问题,需要将使用了printf的文件的编码方式更改为ASCLL
这里是如何更改的传送门
以及使用VScode时乱码问题
进入vscode的设置
搜索编码,将files.autoGuessEncoding选项打上勾即可
在windows系统中的串口助手如果要用printf实现换行,需要使用\r\n
而非\n
如图
CSDN
链接:百度网盘
提取码:742k
这里放我常用的驱动和串口助手
链接:百度网盘
提取码:040l