参考 - 【1】
在 STM32 的参考手册中,串口被描述成通用同步异步收发器(USART),它提供了一种灵活的方法与使用工业标准 NRZ 异步串口数据格式的外部设备之间进行全双工数据交换。
简单区分同步和异步就是看通信时需不需要对外提供时钟输出。
串行通信一般是以帧格式传输数据,即是一帧一帧的传输,每帧包含有起始信号、数据信息、校验信息(自己设置)、停止信号。
参考 - 【3】
- 操作串口一般有两种方式:查询与中断;STM32 还支持第三种 DMA 方式。
- 查询:串口程序不断地循环查询标志,看看当前有没有数据要进行传输(发送或接收)。如果有,就进行相应的写操作与读操作进行发送或接收数据。
- 中断:平时串口只要打开中断即可。如果发现有一个中断来,则意味着有数据进行传输(发送中断或接收中断)。
- DMA 方式:设置好 DMA 工作方式,由 DMA 来自动接收或发送数据。
参考 - 【1】
通信协议分为 物理层 和 协议层
规定通信系统中具有机械、电子功能部分特性,确保原始数据在物理媒体的传输,通俗一点就是硬件部分。
TTL:晶体管-晶体管逻辑电平。很多单片机内部如 STM32 等,以及一些传感器一般都是TTL电平。
RS-232:是一种串行数据传输形式,称其为串行连接。最经典的标志就是9针孔的 DB9 电缆(如下图所示)。RS-232 电压表示逻辑1,0的范围大,极大的增强了容错率,主要用于工业设备通信。
TTL 与 RS-232 的区别:
由上表可知,TTL 和 RS-232 标准逻辑大不相同,若单片机与其他 TTL 设备采用 RS-232 通信(DB9),需要进行电平转化 TTL->RS-232,RS-232->TTL。如下图所示
两个通信设备的 DB9 接口 之间通过串口信号线建立起连接,串口信号线中使用 RS-232 标准 传输数据信号。但是呢,由于 RS-232 标准 的电平信号不能直接被控制器识别,所以这些信号会经过一个 电平转换芯片(一般有 MAX3232,SP3232)转换成控制器能够识别的 TTL标准 的电平信号,才能实现通信。
USB 转串口:主要用于设备(STM32)与电脑通信。其中电平转换芯片一般有 CH340(野火的用这个)、PL2303、CP2102(本人使用的板子采用的就是此芯片)、FT232。使用的时候,电脑会按照电平转换芯片的驱动虚拟出一个串口。
原生的串口通信主要是控制器跟采用串口通信的设备或者与传感器通信(如 GPS 模块、GSM 模块、串口转 WIFI 块、HC04 蓝牙模块)。这些设备之间的通信都是采用 TTL标准,因此不需要额外的电平转换芯片来转换电平。
主要规定通讯逻辑,统一收发双方的数据打包、解包标准(软件)。规定了数据帧是由起始位、主体数据、校验位以及停止位组成的。并且通信双方的数据帧格式要约定一致(一样的起始位、数据主体、校验位和停止位)才能正常收发数据。
奇偶校验是用来检查数据传输的正确性的方法。奇偶校验能检测出传输数据的部分错误(然而仅仅是1位误码是能检测出,2 位及 2 位以上检测不出来),而且不能纠错。在发现错误后,只能要求重发。由于简单所以被广泛应用。
数据传输之前通常会确定是奇校验还是偶校验,以保证发送端和接收端采用相同的校验方式进行数据校验。若是校验位不符合,则误认为传输错误。
校验原理:假如采用奇校验,发送端发送的一个字符编码(含校验位)中,“1” 的个数一定为奇数个。在接收端对接收字符二进制位中的 “1” 的个数进行统计,若是统计出 “1” 的个数位偶数个,则意味着传输过程中有1位发生错误。
事实上,在传输中偶尔—位出错的机会最多,故奇偶校验法常常采用。
参考 - 【1】
下面三个常用:
TX:发送数据输出引脚;
RX:接收数据输入引脚;
SCLK:发送器时钟输出引脚。(仅仅适用于同步通信)。
下面三个不常用:
SW_RX:数据接收引脚,只用单线和智能卡模式,属于内部引脚,没有具体外部引脚。
nRTS:请求以发送(Request To Send),n 表示低电平有效。如果硬件流控制为 RTS 流控制,当 USART 接收器准备好接收新数据时就会将 nRTS 变成低电平;当接收寄存器已满时,nRTS 将被设置位高电平。该引脚只适用于硬件流控制。
nCTS:清楚以发送(Clear To Send),n 表示低电平有效。如果硬件流控制为 CTS 流控制,USART 发送器在发送下一帧数据之前会检测 nCTS 引脚,如果为低电平,表示可以发送数据;如果为高电平,则在发送完当前数据帧之后停止发送。该引脚只适用于硬件流控制。
发送器根据 M 位的状态发送 8 位或 9 位的数据字。当发送使能位(TE)被设置时,发送移位寄存器中的数据在 TX 脚上输出,相应的时钟脉冲在 CK 脚上输出。
一个字符帧发送需要三个部分:起始位 + 数据帧(可能带有校验位)+ 停止位。每一个数据帧之前都有一个低电平的起始位,数据帧之后跟着的是停止位(停止位位数可配置),停止位是一定时间周期的高电平。数据帧就是我们需要发送的 8 位或 9 位数据,数据是从最低位开始传输的。
配置步骤:
发送和接收都是用的波特率发生器驱动,当发送器和接收器的使能位分别置位时,分别为其产生时钟。
把要发送的数据写进 USART_DR 寄存器(此动作清楚 SR 中的 TXE位)。在只有一个缓冲器的情况下,对每个待发送的数据重复步骤 7。
**在 USART_DR 寄存器中写入最后一个数据字后,要等待 SR 中的 TC=1,它表示最后一个数据帧的传输结束(移位寄存器中的数据全部发送完毕)。**当需要关闭 USART 或需要进入停机模式之前,需要确认传输结束,避免破坏最后一次传输。
深入理解 SR 中的 TXE 位与 TC 位:
清零 TXE 位总是通过对数据寄存器的写操作( CPU 或 DMA)来完成的,当 TXE 位已经被硬件置 1 它表明:
1. 数据已经从 TDR 移送到移位寄存器,数据发送已经开始(发送移位寄存器 正在一位一位向外传输数据);
2. TDR 寄存器被清空;
3. 下一个数据可以被写进 USART_DR 寄存器而不会覆盖先前的数据。如果 TXEIE 位被设置,此标志将产生一个中断。
如果此时 USART 正在发送数据(发送移位寄存器正在一位一位向外传输数据)。对 USART_DR 寄存器的写操作把数据存进 TDR 寄存器,并在 当前传输结束时把数据复制进移位寄存器,也就是说移位寄存器里面的数据并不会被覆盖。所以只要发送一帧数据等待 TXE 置 “1” 即可,就算时发送多帧数据时最后也不用等待 TC=1。
如果此时 USART 没有在发送数据,处于空闲状态。对 USART_DR 寄存器的写操作直接把数据放进移位寄存器,数据传输开始,TXE 位立即被置 “1”。
当一帧发送完成时(停止位发送后)并且设置了 TXE 位,TC 位被置 “1”。如果 USART_CR1寄存器中的 TCIE 位被置 “1” 时,则会产生中断。
使用下列软件过程清楚 TC 位:
1. 读一次 USART_SR寄存器;
2. 写一次 USART_DR寄存器。
TC 位也可以通过软件对它写 ‘0’来清除。此清零方式只推荐在多缓冲器通信模式下使用。
如果将 USART_CR1寄存器的 RE 位置 “1”,使能 USART 接收,使得接收器在 RX 线开始搜索起始位。确定起始位后,就根据 RX 线电平状态把数据存放在接收移位寄存器内。接收完成后就把接收移位寄存器数据移传输到 RDR 内,并把 USART_SR 寄存器的 RXNE 位置 “1” ,同时如果 USART_CR2寄存器的 RXNEIE 置 “1” 就可以产生中断。
当一个字符被接收到时
溢出错误:
如果 RXNE 还没有被复位(还没有读出 DR 寄存器的数据),又接收到一个字符,则会发生溢出错误。**数据只有当 RXNE 位被清零后才能从移位寄存器转移到 RDR 寄存器。**RXNE 是在接收到每个字节后被置 “1” 的。如果写一个数据已经被收到或是 DMA 请求还没被服务时,RXNE 标志仍是 “1”,溢出错误产生。
当移除错误产生时
1. ORE 位被置位。
2. RDR 内容将不会丢失。读 USART_DR 寄存器仍能得到先前的数据。
3. 移位寄存器中以前的内容将被覆盖,随后接收到的数据都将丢失。(!)
4. 如果 RXNEIE 位被设置或 EIE 和 DMAR 位都被设置,中断产生。
5. 顺序执行对 USART_SR 和 USART_DR 寄存器的读操作,可复位 ORE 位。
USART 的中断请求:其时间标志常用于防止误触发中断。在第 6 节的中断服务函数中有使用
USART 结构体代码实现,同样是库函数里面已经定义了。引用了 32 的头文件就可以直接调用
typedef struct
{
uint32_t USART_BaudRate; // 波特率
uint16_t USART_WordLength; // 字长
uint16_t USART_StopBits; // 停止位
uint16_t USART_Parity; // 校验位
uint16_t USART_Mode; // USART 模式
uint16_t USART_HardwareFlowControl; // 硬件流控制
}USART_InitTypeDef;
**RCC 配置 **
RCC_APB2PeriphClockCmd();
进行初始化。UARTx 需要分情况讨论,如果是 UART1,则挂在 APB2 桥上;其余的 UART2~5 均挂在 APB1 上。GPIO 配置 (GPIO 了解可参考这篇文章【4】)
GPIO_InitTypeDef
中。
GPIO_Mode
字段设置为 GPIO_Mode_AF_PP
(复用推挽输出),GPIO_Speed
切换速率 设置为 GPIO_Speed_50MHZ
。GPIO_Mode
字段设置为 GPIO_IN_FLOATING
(浮空输入),不需要设置切换速率。最后通过 GPIO_Init()
使能 IO 口。注:AHB 总线、APB2 总线、APB1 总线 介绍参考【5】
USART 配置
NVIC_PriorityGroupConfig()
实现。参考 - 【1】、【6】、【8】、【9】
使用 STM32 标准库,使用的功能如串口 USART 功能,DMA 功能、NVIC 功能、I2C 功能、SPI 功能等都有一个类似的配置流程。
编程要点:
实际使用中,前四步都是一样的(因此前四部理解即可)。后面的第 5、6步就需要根据自己的需求来实现。
下面 在 usart.h 中进行函数声明与宏的定义;在 usaert.c 中进行函数实现与相应初始化操作;在 main.c 调用。
usart.h
—相关宏定义与函数声明/* 注:
1.XX初始化结构体属于标准库函数的部分已经定义好(用户不用编写),具体的配置包含在初始化配置函数中(用户自己编写)。
如: USART 初始化结构体 USART_InitTypeDef; USART 时钟初始化结构体 USART_Clock_InitTypeDef 或 USART_ClockInitTypeDef。
2.下面串口 1~5(USART1~5)需要用到哪几个就上对应的宏即可,无需全部写上。
*/
#ifndef _USART_H
#define _USART_H
#include "stm32f4xx.h" // 我用的是 f4 的芯片,如果为其他的更换为其他 32 的头文件
#include
#define DEBUG_USART1 1
#define DEBUG_USART2 2
/* 串口宏定义(起个别名,方便移植),不同的串口挂载的总线和 I/O 不一样 */
#if DEBUG_USART1
/* 串口 1-USART1 */
#define DEBUG_USARTx USART1 // 串口 1
#define DEBUG_USART_CLK RCC_APB2Periph_USART1 // 串口 1 的时钟
#define DEBUG_USART_APBxClkCmd RCC_APB2PeriphClockCmd // 由于 UART1 的 TX、RX 和 AFIO 都挂在 APB2 桥上,因此采用固件库函数 RCC_APB2PeriphClockCmd() 进行初始化。后面会用到该代码 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); 把 USART1 外设的 TX、RX 映射到其它 I/O 口上。即被映射的 I/O 口与内置外设原来所对应的 I/O 口的作用相同( I/O 管脚的复用功能)。
#define DEBUG_USART_BAUDRATE 115200 // 设置串口 1 的波特率为 115200
/* USART GPIO 引脚宏定义 */
#define DEBUG_USART_GPIO_CLK (RCC_APB2Periph_GPIOA) // GPIO 的时钟
#define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd // GPIO 外设是挂在 APB2 总线上的。APB2 的时钟 = GPIO 外设的时钟
#define DEBUG_USART_TX_GPIO_PORT GPIOA // TX 复用在 GPIOA 上
#define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_9 // GPIOA 的 9 引脚 即 A9
#define DEBUG_USART_RX_GPIO_PORT GPIOA // RX 复用在 GPIOA 上
#define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_10 // GPIOA 的 10 引脚 即 A10
#define DEBUG_USART_IRQ USART1_IRQn // 设置 串口 USART1 为中断源
#define DEBUG_USART_IRQHandler USART1_IRQHandler
/* 串口 2-USART2 */
#elif DEBUG_USART2
#define DEBUG_USARTx USART2
#define DEBUG_USART_CLK RCC_APB1Periph_USART2
#define DEBUG_USART_APBxClkCmd RCC_APB1PeriphClockCmd
#define DEBUG_USART_BAUDRATE 115200
// USART GPIO 引脚宏定义
#define DEBUG_USART_GPIO_CLK (RCC_APB2Periph_GPIOA)
#define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_USART_TX_GPIO_PORT GPIOA
#define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_2
#define DEBUG_USART_RX_GPIO_PORT GPIOA
#define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_3
#define DEBUG_USART_IRQ USART2_IRQn
#define DEBUG_USART_IRQHandler USART2_IRQHandler
/* 串口 3-USART3 */
#elif DEBUG_USART3
#define DEBUG_USARTx USART3
#define DEBUG_USART_CLK RCC_APB1Periph_USART3
#define DEBUG_USART_APBxClkCmd RCC_APB1PeriphClockCmd
#define DEBUG_USART_BAUDRATE 115200
// USART GPIO 引脚宏定义
#define DEBUG_USART_GPIO_CLK (RCC_APB2Periph_GPIOB)
#define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_USART_TX_GPIO_PORT GPIOB
#define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_10
#define DEBUG_USART_RX_GPIO_PORT GPIOB
#define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_11
#define DEBUG_USART_IRQ USART3_IRQn
#define DEBUG_USART_IRQHandler USART3_IRQHandler
/* 串口 4-USART4 */
#elif DEBUG_USART4
#define DEBUG_USARTx UART4
#define DEBUG_USART_CLK RCC_APB1Periph_UART4
#define DEBUG_USART_APBxClkCmd RCC_APB1PeriphClockCmd
#define DEBUG_USART_BAUDRATE 115200
// USART GPIO 引脚宏定义
#define DEBUG_USART_GPIO_CLK (RCC_APB2Periph_GPIOC)
#define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_USART_TX_GPIO_PORT GPIOC
#define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_10
#define DEBUG_USART_RX_GPIO_PORT GPIOC
#define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_11
#define DEBUG_USART_IRQ UART4_IRQn
#define DEBUG_USART_IRQHandler UART4_IRQHandler
/* 串口5-UART5 */
#elif DEBUG_USART5
#define DEBUG_USARTx UART5
#define DEBUG_USART_CLK RCC_APB1Periph_UART5
#define DEBUG_USART_APBxClkCmd RCC_APB1PeriphClockCmd
#define DEBUG_USART_BAUDRATE 115200
// USART GPIO 引脚宏定义
#define DEBUG_USART_GPIO_CLK (RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOD)
#define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_USART_TX_GPIO_PORT GPIOC
#define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_12
#define DEBUG_USART_RX_GPIO_PORT GPIOD
#define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_2
#define DEBUG_USART_IRQ UART5_IRQn
#define DEBUG_USART_IRQHandler UART5_IRQHandler
#endif
void USART_Config(void); // 配置初始化 函数
void Usart_SendByte(USART_TypeDef* pUSARTx, uint8_t data); // 发送一个字节
void Usart_SendHalfWord(USART_TypeDef* pUSARTx, uint16_t data); // 发送一个 16 位的数据
void Usart_SendArray(USART_TypeDef* pUSARTx, uint8_t *array,uint8_t num); // 发送一个 8 位的数组
void Usart_SendString(USART_TypeDef* pUSARTx, char *str); // 发送字符串
#endif /* __BSP_USART_H */
usart.c
- 函数实现与相关初始化操作#include "usart.h"
/* 配置 嵌套向量中断控制器 NVIC */
static void NVIC_Configuration(void)
{
// 创建一个 NVIC 对象,用于后面初始化。NVIC_InitTypeDef 为标准库函数,已经定义好了,直接用即可。
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 嵌套向量中断控制器组选择
NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ; // 配置 USART 为中断源
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01; // 主优先级(抢断优先级)配置
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01; // 子优先级配置
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中断
NVIC_Init(&NVIC_InitStructure); // 初始化配置 NVIC
}
/* 配置 USART、GPIO */
void USART_Config(void)
{
// 分别定义 GPIO 与 USART 对象,用于后面初始化。GPIO_InitTypeDef 与 USART_InitTypeDef 同样为标准库函数,直接用即可
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
// 打开串口GPIO的时钟
DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
// 打开串口外设的时钟
DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);
/* 配置 GPIO 的工作参数 */
// 将 USART Tx 的 GPIO 配置为推挽复用模式
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN; // 设置 GPIO 的输出引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 设置 GPIO 的工作模式为 推挽输出模式(常用输出设置)
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置 GPIO 的输出速度
GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure); // 初始化配置 GPIO 的输出
// 将 USART Rx 的 GPIO 配置为浮空输入模式
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN; // 设置 GPIO 的输入引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 设置 GPIO 的工作模式为 浮空输入模式
GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure); // 初始化配置 GPIO 的输入
/* 配置串口的工作参数 */
USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE; // 配置波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 配置 帧数据字长
USART_InitStructure.USART_StopBits = USART_StopBits_1; // 配置停止位
USART_InitStructure.USART_Parity = USART_Parity_No ; // 配置校验位
USART_InitStructure.USART_HardwareFlowControl =
USART_HardwareFlowControl_None; // 配置硬件流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 配置工作模式,收发一起
USART_Init(DEBUG_USARTx, &USART_InitStructure); // 完成串口的初始化配置
NVIC_Configuration(); //中断配置
USART_ITConfig(DEBUG_USARTx,USART_IT_RXNE, ENABLE); //开启串口接收中断
USART_Cmd(DEBUG_USARTx, ENABLE); //使能串口
}
/* 发送一个字节的数据 */
void Usart_SendByte(USART_TypeDef* pUSARTx, uint8_t date)
{
USART_SendData(pUSARTx,date); // 调用 STM32 的库函数 USART_SendData(串口,数据),来发送数据。
/* 等待发送寄存器发送完成 */
while( USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
// 关于 TXE 的知识点在上面 4. USART 功能框图 -> 4.4 控制单元(划重点!)-> 4.4.2.发送器 -> 第 7 点关于控制寄存器 SR 中 TXE(发送寄存器空)的介绍。
// 概括来讲 就是 TDR 中的数据还没被转移到 移位寄存器中。TXE 位就一直为 “0”。反过来,就为 “1”.
// USART_GetFlagStatus()(该函数的返回值 只有两种:RESET = 0, SET = !RESET)函数在此处就是判断 串口 pUSARTx 的 USART_FLAG_TXE(发送数据寄存器空标志位)。如果 TXE 为 “0”,则与 返回 RESET,反之,返回 SET。
// 因此,还没发送完,就通过 while(...) 函数进行等待,等待数据发送完成。
}
// 发送一个16位的数据
void Usart_SendHalfWord(USART_TypeDef * pUSARTx, uint16_t date)
{
uint16_t tmp_h;
uint16_t tmp_l;
tmp_h = date>>0x08; // 向右移动 8 位,获取高 8 位。
tmp_l = date & 0xff; // 通过与 1111 1111 进行 “与” 操作,获得数据的低八位。
Usart_SendByte(pUSARTx,tmp_h); // 先发送高 8 位
Usart_SendByte(pUSARTx,tmp_l); // 后发送低 8 位
// 为什么不能直接发送 16 位数据,而分别发送高 8 位 与 低 8 位?
// 这是因为串口最大支持数据长度为 9bit。
}
// 发送一个8位的数组
void Usart_SendArray(USART_TypeDef * pUSARTx, uint8_t *arr, uint16_t num)
{
while(num--)
{
Usart_SendByte( pUSARTx ,*arr++);
}
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TC) == RESET); // 等待发送成功,TXE 被置 “1”.
}
// 发送字符串
void Usart_SendString( USART_TypeDef * pUSARTx, char *str)
{
while( *str!='\0' )
{
Usart_SendByte( pUSARTx, *str++);
}
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TC)== RESET); // 等待数据发送成功(与发送数据类型无关).
}
/* c语言中 printf 函数默认输出设备是显示器,如果实现在串口或 LCD 等上显示,必须重定义标准库函数李米娜调用的输出设备定义的相关函数。即使用 printf 输出到串口,需要将 fputc 里面的输出指向串口,这一过程称为 重定向。 */
// 重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f) // 函数默认的,在使用 printf 函数时自动调用
{
/* 发送一个字节数据到串口 */
USART_SendData(DEBUG_USARTx, (uint8_t) ch);
/* 等待发送完毕 */
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);
return (ch);
}
// 重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数
// scanf函数(默认键盘输入,我们要重定向到串口接收)
int fgetc(FILE *f)
{
/* 等待串口输入数据 */
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(DEBUG_USARTx);
}
//中断服务函数
void DEBUG_USART_IRQHandler(void)
{
uint16_t tmp;
/* 实现 串口在接收到数据后,又把接收到数据通过串口发送出去。 */
// 该 if 语句表明作用:1、判断是哪个串口产生中断;2、防止误触发中断(即通过 RXNE 进行判断)。
if(USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) != RESET)
{
tmp = USART_ReceiveData(DEBUG_USARTx); // 将串口接收到的数据保存在 tmp 中
USART_SendData(DEBUG_USARTx,tmp); // 接收到数据后,把接收到的数据通过串口发送出去
while( USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE)== RESET); // 等待发送成功
// 关于 RXNE 的知识点在上面 4.USART 功能框图 -> 4.4 控制单元(划重点!)-> 4.4.2.接收器。
}
}
main.c
主程序#include "stm32f10x.h"
#include "usart.h"
int main(void)
{
uint8_t a[8] = {0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7};
/*串口初始化*/
USART_Config();
//printf("\n 单个字符输出测试:");
Usart_SendByte( DEBUG_USARTx, 'F');
//printf("\n 8位数字输出测试:");
Usart_SendByte( DEBUG_USARTx, 0xFF);
//printf("\n 16位数输出测试:");
Usart_SendHalfWord( DEBUG_USARTx, 0xFFFF);
//printf("\n 数组输出测试:");
Usart_SendArray( DEBUG_USARTx, a, 8);
//printf("\n 字符串输出测试:");
Usart_SendString( DEBUG_USARTx, "字符串 hello world\r\n");
while(1);
}
目前还在学习阶段,所以还是仅仅在依靠别人博客进行笔记真理的阶段。不过 厚积薄发嘛,在整理过程也 get 了很多。串口通信这方面,差不多基本掌握了,但是也只局限于串口通信。比如里面还有 GPIO、DMA、NVIC 等还没有往下面挖掘。以及上面提到的已经被 32 包含的相应库函数,虽然已经在源码中看明白了,还是需要通过笔记的形式进行输出。嘿嘿,后面再继续加油!道阻且长,我依然选择一步一步前行。
【1】STM32串口通信详解_rivencode的博客-CSDN博客_stm32串口通信
【2】(5条消息) 数据通信中,奇偶校验原理_又小雪666的博客-CSDN博客
【3】STM32–USART详解_阿槐123456的博客-CSDN博客_usart
【4】GPIO简介_旭日初扬的博客-CSDN博客_gpio
【5】STM32中AHB总线_APB2总线_APB1总线这些是什么_m0_59949484的博客-CSDN博客_apb1总线
【6】STM32——串口通信实验_骤雨南山下的博客-CSDN博客_stm32串口通信实验
【7】嵌套向量中断控制器 (NVIC)_不知名的好人的博客-CSDN博客_nvic
【8】STM32的USART_GetFlagStatus和USART_GetITStatus解析_嵌入式快跑的博客-CSDN博客
【9】printf重定向_旭日初扬的博客-CSDN博客_printf重定向