【STM32标准库】【自制库】硬件串口通信和标准输入输出函数的重定向


文章基于适用于STM32F4系列,作者使用STM32F401CCU6开发板。
本文章基于此系列和开发板展开讨论。

硬件串口通信

串口通信的介绍在上次介绍了,传送门

本文使用单片机内部自带的外设硬件串口通信来与其他设备通信

电气连接

使用异步全双工的串口通信与计算机通信,需要使用3根线,一个USB转TTL的模块
请添加图片描述
请添加图片描述
这是模块图片,需要注意的是stm32使用3.3v的ttl电平
需要将3v3和vcc用跳线帽短接起来,这个模块没有对外供电的功能,所以单片机需要外接其他电源

连线如下示意图
在这里插入图片描述
单片机的通信管脚是根据编程使用的串口决定的,详情见这个表中的USARTx_TX(USARTx_RX)

名称USART1USART2USART6
总线APB2APB1APB2
TXDPA9 / PB6PA2PA11
RXDPA10 / PB7PA3PA12

初始化思路

  1. GPIO初始化
  2. 设置GPIO复用选择
  3. 打开时钟
  4. 定义初始化结构体
  5. 初始化
  6. NVIC设置(使用中断需设置)
  7. 打开中断使能(使用中断需设置)
  8. 打开串口
  9. 编写中断服务函数(使用中断需设置)

1.初始化GPIO

TXD和RXD都使用 复用 推挽 浮空 即可
GPIO的初始化之前介绍过了,传送门

复用模式其实就是使用来自片内外设的数据的一种模式
这里给个例子

2.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);							//初始化
}

3.开启时钟

硬件串口挂载在APB1或APB2总线下
使用这两个函数启用时钟

void RCC_AHB1PeriphClockCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState)
void RCC_AHB2PeriphClockCmd(uint32_t RCC_AHB2Periph, FunctionalState NewState)

第一个参数是时钟名称
第二个是使能或失能
具体名称请在标准库源码中查看

4.初始化结构体

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;

USART_BaudRate

波特率,具体解释之前文章说过了,传送门
需要注意的是需要通信双方的波特率相同,可以查询串口助手来选择需要的波特率

USART_WordLength

字节宽度
一般选择8bit字宽
也就是这个宏定义

USART_WordLength_8b

USART_StopBits

停止位宽度
可以是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)

USART_Parity

校验位,可以是奇校验,偶校验,无校验
按宏定义选择

#define USART_Parity_No                      ((uint16_t)0x0000)//无校验
#define USART_Parity_Even                    ((uint16_t)0x0400)//偶校验
#define USART_Parity_Odd                     ((uint16_t)0x0600)//奇校验

USART_Mode

输入输出模式,这个可以使用或命令输入多个(|)
按这个宏定义选择即可

USART_Mode_Rx		//接收
USART_Mode_Tx		//发送

USART_HardwareFlowControl

在本文章中无需使用
按这个宏定义配置即可

USART_HardwareFlowControl_None

5.初始化

使用这个函数初始化串口

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);						//初始化

6.NVIC设置(使用中断需设置)

关于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);
}

7. 打开中断使能(使用中断需设置)

使用这个函数打开中断

void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState)

第一个输入串口号,第二个输入中断类型,第三个输入使能或使能
中断类型常用如下

USART_IT_RXNE   //接收数据中断

8. 打开串口

void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState)

输入串口号和使能或失能即可

9. 编写中断服务函数(使用中断需设置)

中断函数名如下

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);
    }
}

常用函数

USART_GetITStatus

原型

ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT)
名称描述
输入1串口号
输入2中断类型
输出设置或未设置

功能描述:检测指定的中断类型的置位状态

USART_ClearITPendingBit

原型

void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT)
名称描述
输入1串口号
输入2中断类型
输出

功能描述:清除指定串口的指定中断标志

USART_GetFlagStatus

原型

FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG)
名称描述
输入1串口号
输入2标志类型
输出设置或未设置

常用标志

USART_FLAG_TXE		//发送完成标志
USART_FLAG_RXNE		//接收完成标志

功能描述:读取指定串口的指定标志

USART_SendData

原型

void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
名称描述
输入1串口号
输入2发送的数据
输入

功能描述:将数据通过指定串口发送

USART_ReceiveData

原型

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从串口获取数据

避坑指南

串口通信

  1. 务必按照上文提到的步骤进行,顺序可以改变,单千万别少项
  2. 使用硬件串口的时候,管脚是固定的不能修改,请按照管脚连线
  3. 请确定串口助手的设置和单片机的设置相同或兼容
  4. 连接时务必注意TTL的电平是否吻合,避免损伤硬件

标准输入输出输出中文乱码

这是因为格式问题,需要将使用了printf的文件的编码方式更改为ASCLL
这里是如何更改的传送门
以及使用VScode时乱码问题

进入vscode的设置
在这里插入图片描述
搜索编码,将files.autoGuessEncoding选项打上勾即可

在这里插入图片描述

打开微库

keiil进入设置
在这里插入图片描述
按图设置
在这里插入图片描述

换行

在windows系统中的串口助手如果要用printf实现换行,需要使用\r\n而非\n
如图
在这里插入图片描述
在这里插入图片描述

成品

CSDN
链接:百度网盘
提取码:742k

驱动和串口助手

这里放我常用的驱动和串口助手
链接:百度网盘
提取码:040l

  • 3
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值