本节将进行实战,基础了解请查看第1,2,3节(Whappy)
开始背!! USART ---》全双工 异步/同步 点对点
C语言基础printf用法,这节将用到printf的重定向,来打印到串口助手上。
先给大家看一下整个过程,然后我再给大家做一个单一的串口发送的程序(不包括接收,将在下一节讲)
串口通信的完整过程概述
在嵌入式开发中,串口通信(UART)是最常用的通信方式之一,常用于调试和数据传输。今天我们先带大家梳理一下整个串口通信的过程和原理。
-
通信原理 串口通信是一种异步的串行数据传输方式,数据通过起始位、数据位、校验位和停止位逐位传输。STM32 的串口模块(USART/UART)将这些数据串行发送出去,而接收端则按相同协议解析接收到的数据。
-
硬件连接
- STM32 的 TX 引脚:负责发送数据。
- 电脑的 RX 引脚:负责接收数据。
- 共地:STM32 和电脑需要连接 GND 确保信号参考一致。
-
软件流程
- STM32 端: 配置 USART 外设,完成数据发送。
- 电脑端: 通过串口助手(如 SScom、SecureCRT 等)接收 STM32 发送的数据,并显示在屏幕上。
-
配置同步 STM32 和电脑串口助手需配置相同的通信参数(如波特率、数据位、停止位等),才能实现正确的通信。
在 STM32 开发中,串口通信(UART)是常用的通信方式之一。以下是详细的步骤和注意事项,包括如何实现数据发送、接收,以及配置相关的硬件和软件。
一、STM32 串口通信原理
-
发送端:STM32 STM32 的串口模块(USART)通过 UART 协议将数据位(包括起始位、数据位、校验位和停止位)串行输出到 TX 引脚。
-
接收端:电脑助手 电脑通过串口助手软件(如 SScom、SecureCRT 等)接收 STM32 发送的数据,解码并显示。
-
硬件连接
- TXD(STM32 发送端)连接到电脑串口模块的 RXD(接收端)。
- GND 需连接在一起以保证信号的公共参考。
二、串口通信基本配置
1. STM32 串口配置
以 STM32CubeMX 和 HAL 库为例:
1.1 硬件配置
- 时钟源: 启用 USART 的时钟(比如:APB1 或 APB2)。
- 引脚配置: 将 USART 的 TX/RX 引脚设置为
Alternate Function
模式。
1.2 CubeMX 配置
在 CubeMX 中:
- 打开 USARTx 外设,启用 Asynchronous 模式。
- 设置波特率(通常 9600、115200 等),并配置:
- 数据位:8 位
- 停止位:1 位
- 校验:无校验
- 硬件流控:无
2. 串口助手(电脑端)的配置
使用串口助手软件(如 SScom),按以下步骤操作:
- 选择正确的 COM 端口: 在设备管理器中查看 STM32 的虚拟串口号(如 COM3)。
- 配置波特率等参数: 波特率、数据位、停止位和校验位需与 STM32 的设置一致。
- 打开串口后即可接收 STM32 发送的数据。
三、调试与查看数据
-
通过串口助手查看数据 数据通过串口助手的软件窗口实时显示,确保编码格式正确(通常选择 ASCII 或 HEX)。
-
硬件问题排查
- 确认硬件连线正确。
- 使用示波器或逻辑分析仪查看串口波形,检查电平是否正常。
在 STM32 开发中,串口通信(UART)是常用的通信方式之一。以下是详细的步骤和注意事项,包括如何实现数据发送、接收,以及配置相关的硬件和软件。
一、STM32 串口通信原理
-
发送端:STM32 STM32 的串口模块(USART)通过 UART 协议将数据位(包括起始位、数据位、校验位和停止位)串行输出到 TX 引脚。
-
接收端:电脑助手 电脑通过串口助手软件(如 SScom、SecureCRT 等)接收 STM32 发送的数据,解码并显示。
-
硬件连接
- TXD(STM32 发送端)连接到电脑串口模块的 RXD(接收端)。
- GND 需连接在一起以保证信号的公共参考。
-
二、串口通信基本配置
1. STM32 串口配置
以 STM32CubeMX 和 HAL 库为例:
1.1 硬件配置
- 时钟源: 启用 USART 的时钟(比如:APB1 或 APB2)。
- 引脚配置: 将 USART 的 TX/RX 引脚设置为
Alternate Function
模式。 -
1.2 CubeMX 配置
在 CubeMX 中:
- 打开 USARTx 外设,启用 Asynchronous 模式。
- 设置波特率(通常 9600、115200 等),并配置:
- 数据位:8 位
- 停止位:1 位
- 校验:无校验
- 硬件流控:无
- 生成代码。
-
1.3 初始化代码
生成的代码中包含初始化函数,例如:
1.4 数据发送代码
使用
HAL_UART_Transmit
函数发送数据:1.5 数据接收代码(轮询模式)
使用
HAL_UART_Receive
函数接收数据:2. 串口助手(电脑端)的配置
使用串口助手软件(如 SScom),按以下步骤操作:
- 选择正确的 COM 端口: 在设备管理器中查看 STM32 的虚拟串口号(如 COM3)。
- 配置波特率等参数: 波特率、数据位、停止位和校验位需与 STM32 的设置一致。
- 打开串口后即可接收 STM32 发送的数据。
-
三、调试与查看数据
-
通过串口助手查看数据 数据通过串口助手的软件窗口实时显示,确保编码格式正确(通常选择 ASCII 或 HEX)。
-
硬件问题排查
- 确认硬件连线正确。
- 使用示波器或逻辑分析仪查看串口波形,检查电平是否正常。
-
四、完整的示例程序
发送“Hello, UART!”到串口助手
4.1 主程序
4.2 必备的 STM32 外设初始化代码
包含 GPIO、时钟等初始化函数(CubeMX 自动生成)。
五、注意事项
- 波特率的匹配: STM32 和电脑助手的波特率必须一致。
- 电平匹配: STM32 的串口为 TTL 电平,若使用 RS232 接口需添加电平转换芯片(如 MAX232)。
- 调试工具:
- 可用串口助手接收数据,调试发送内容。
- 使用 STM32 的 SWD 接口进行代码调试和断点跟踪。
-
-
总结
通过这个简单的串口发送程序,我们可以实现 STM32 向电脑串口助手发送数据的基本功能。下一节我们将详细讲解串口数据的接收方法,包括如何解析收到的数据并进行处理。
接下一来就是,我学习的重点了 用标准库写一个串口发送程序
连接方式
标准库介绍
TM32 USART 库函数详解
STM32 的 USART 库函数是通过标准外设库(StdPeriph Library)提供的接口,用于初始化、配置、控制 USART 外设,以及实现串口通信功能。以下对这些函数的作用、使用方法和关键点进行详细说明,并提供示例代码。
1. 函数详细说明
(1)USART 初始化和配置函数
函数 | 作用 |
---|---|
void USART_DeInit(USART_TypeDef* USARTx) | 将指定的 USART 外设寄存器恢复为默认值。用于复位配置。 |
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct) | 初始化 USART 外设,包括波特率、数据位、停止位等参数配置。 |
void USART_StructInit(USART_InitTypeDef* USART_InitStruct) | 将 USART_InitTypeDef 结构体初始化为默认值(一般用于快速配置前设置默认参数)。 |
(2)时钟相关函数
函数 | 作用 |
---|---|
void USART_ClockInit(USART_TypeDef* USARTx, USART_ClockInitTypeDef* USART_ClockInitStruct) | 配置 USART 的同步时钟模式(如 SPI 模式需要)。 |
void USART_ClockStructInit(USART_ClockInitTypeDef* USART_ClockInitStruct) | 初始化 USART_ClockInitTypeDef 结构体为默认值。 |
(3)使能和中断相关函数
函数 | 作用 |
---|---|
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState) | 使能或禁用指定的 USART 外设。 |
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState) | 使能或禁用 USART 的中断(如接收、发送完成中断)。 |
(4)DMA 功能
函数 | 作用 |
---|---|
void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState NewState) | 使能或禁用 USART 的 DMA 传输请求(支持发送或接收)。 |
(5)通信模式和高级配置
函数 | 作用 |
---|---|
void USART_SetAddress(USART_TypeDef* USARTx, uint8_t USART_Address) | 配置 USART 的地址(多处理器通信模式下使用)。 |
void USART_WakeUpConfig(USART_TypeDef* USARTx, uint16_t USART_WakeUp) | 配置 USART 唤醒模式。 |
void USART_ReceiverWakeUpCmd(USART_TypeDef* USARTx, FunctionalState NewState) | 使能或禁用接收器的唤醒功能。 |
(6)数据收发函数
函数 | 作用 |
---|---|
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data) | 向发送数据寄存器写入数据,发送数据。 |
uint16_t USART_ReceiveData(USART_TypeDef* USARTx) | 从接收数据寄存器读取数据。 |
(7)标志位与中断状态管理
函数 | 作用 |
---|---|
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG) | 获取指定的 USART 标志位状态(如发送完成、接收完成)。 |
void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG) | 清除指定的 USART 标志位。 |
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT) | 获取指定的 USART 中断状态。 |
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT) | 清除指定的 USART 中断挂起标志。 |
接下来我们就要根据上述框图来配置USART的的发送程序,根据程序上述蓝色框图,我们要让单片机发送程序,仅需将我们要发送的程序放到TX管脚即可!(接收部分下节再讲)
具体配置如下
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdarg.h>void Serial_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_InitStructrue;GPIO_InitStructrue.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructrue.GPIO_Pin = GPIO_Pin_9;GPIO_InitStructrue.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructrue);USART_InitTypeDef USART1_InitStructure;USART1_InitStructure.USART_BaudRate = 9600;USART1_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;USART1_InitStructure.USART_Mode = USART_Mode_Tx;USART1_InitStructure.USART_Parity = USART_Parity_No ;USART1_InitStructure.USART_StopBits = USART_StopBits_1;USART1_InitStructure.USART_WordLength = USART_WordLength_8b;USART_Init(USART1,&USART1_InitStructure);USART_Cmd(USART1,ENABLE);
}
包含头文件
c
复制代码
#include "stm32f10x.h" // STM32F10x 系列的设备头文件 #include <stdio.h> #include <stdarg.h>
-
stm32f10x.h
:- 包含 STM32F10x 系列微控制器的外设寄存器定义和标准外设库支持。
- 提供对 GPIO、USART 等外设的配置和操作功能。
-
stdio.h
和stdarg.h
:- 提供标准输入输出函数,如
printf
。 - 如果需要处理可变参数,这两个头文件是必要的。
- 提供标准输入输出函数,如
2. 函数 Serial_Init
该函数的作用是初始化串口1(USART1)以及相关的 GPIO 引脚。
2.1 使能时钟
c
复制代码
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd
:- 用于使能外设的时钟。
RCC_APB2Periph_USART1
:使能 USART1 的时钟。RCC_APB2Periph_GPIOA
:使能 GPIOA 的时钟(USART1 的 TX 引脚位于 GPIOA9)。
2.2 配置 GPIO
c
复制代码
GPIO_InitTypeDef GPIO_InitStructrue; GPIO_InitStructrue.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出模式(用于串口 TX) GPIO_InitStructrue.GPIO_Pin = GPIO_Pin_9; // 配置 GPIOA 的第 9 引脚(USART1 TX) GPIO_InitStructrue.GPIO_Speed = GPIO_Speed_50MHz; // 输出速率为 50MHz GPIO_Init(GPIOA, &GPIO_InitStructrue); // 初始化 GPIOA9
- 配置 GPIOA 引脚 9 为串口 TX(传输引脚)。
GPIO_Mode_AF_PP
:复用推挽输出模式,适用于串口功能。GPIO_Pin_9
:USART1 的 TX 引脚。GPIO_Speed_50MHz
:输出速度设为 50 MHz(用于快速信号传输)。
2.3 配置 USART1
c
复制代码
USART_InitTypeDef USART1_InitStructure; USART1_InitStructure.USART_BaudRate = 9600; // 波特率为 9600 USART1_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件流控 USART1_InitStructure.USART_Mode = USART_Mode_Tx; // 工作模式:仅发送(Tx) USART1_InitStructure.USART_Parity = USART_Parity_No; // 无校验位 USART1_InitStructure.USART_StopBits = USART_StopBits_1; // 停止位:1 USART1_InitStructure.USART_WordLength = USART_WordLength_8b; // 数据位:8 位 USART_Init(USART1, &USART1_InitStructure); // 初始化 USART1
- 配置串口的通信参数:
- 波特率:9600 波特。
- 硬件流控:禁用(常用于硬件握手信号)。
- 工作模式:仅发送模式(可以扩展为接收和发送)。
- 校验位:无校验位。
- 停止位:1 个停止位。
- 数据位:8 位。
2.4 使能 USART1
c
复制代码
USART_Cmd(USART1, ENABLE);
- 打开 USART1,使能其工作。
总结
这段代码完成了以下工作:
-
使能时钟:
- 为 GPIOA 和 USART1 启用外设时钟。
-
配置 GPIO:
- 将 GPIOA 的第 9 引脚配置为 USART1 的 TX 引脚(推挽输出模式)。
-
配置串口参数:
- 波特率:9600。
- 数据位:8 位。
- 停止位:1 位。
- 无校验位。
- 无硬件流控。
-
使能 USART1:
- 启用 USART1,使其能够发送数据。
注意事项
- 接收功能:目前只启用了发送模式。如果需要接收数据,还需配置 RX 引脚(GPIOA10)并将模式改为
USART_Mode_Tx | USART_Mode_Rx
。 - 波特率匹配:确保外部接收设备的波特率与 USART1 的设置一致。
- 中断或 DMA:如果需要更高效的串口通信,可以启用 USART 的中断或 DMA 功能。
接下来就是写几个库函数
这段代码的详细解释
函数 Serial_Printf
的作用是通过串口发送一个格式化的字符串。它模仿了标准库中的 printf
函数,用于将格式化后的数据通过串口输出。
函数功能
-
输入参数:
char* format
:格式化字符串,用于定义输出格式(如%d
表示整数,%s
表示字符串)。...
:可变参数,用于提供格式化字符串中占位符对应的实际值。
-
实现流程:
- 将格式化字符串和对应的可变参数组合成一个完整的字符串。
- 将生成的字符串通过串口发送。
代码逐步解析
1. 定义缓冲区
c
复制代码
char String[100];
- 定义一个字符数组
String
,大小为 100,用于存储格式化后的字符串。 - 注意:
String
的大小应该足够容纳格式化后的字符串。如果格式化结果超出 100 个字符,会导致缓冲区溢出。 - 可用
snprintf
替代vsprintf
来限制格式化结果的长度。
2. 定义可变参数列表
c
复制代码
va_list arg;
va_list
是一个类型,用于存储和访问可变参数。- 它由头文件
<stdarg.h>
定义,是处理可变参数的核心工具。
3. 初始化可变参数列表
c
复制代码
va_start(arg, format);
- 功能:
- 初始化
arg
,使其指向第一个可变参数。 - 参数
format
是固定参数,它告诉va_start
可变参数从哪里开始。
- 初始化
- 原理:
- 在函数调用时,固定参数和可变参数在内存中的位置是连续的。
va_start
通过format
确定固定参数的位置,从而找到第一个可变参数的位置。
4. 格式化字符串
c
复制代码
vsprintf(String, format, arg);
- 功能:
- 根据
format
和可变参数arg
,将格式化后的结果存入String
。 vsprintf
是标准库中的函数,作用类似于sprintf
,但支持可变参数。
- 根据
- 参数解释:
String
:用于存储格式化结果的缓冲区。format
:格式化字符串,定义输出格式。arg
:可变参数列表,包含需要插入到格式化字符串中的值。
- 示例:
c
复制代码
char buffer[100]; va_list args; va_start(args, "Value: %d\n"); vsprintf(buffer, "Value: %d\n", args); // 如果传入的可变参数是 42,则 buffer 的内容是 "Value: 42\n"。
5. 清理可变参数列表
c
复制代码
va_end(arg);
- 功能:
- 清理
va_list
,释放相关资源。 - 在
va_start
和va_end
之间,arg
的内容是有效的。调用va_end
后,它不能再被访问。
- 清理
6. 发送字符串
c
复制代码
Serial_SendString(String);
- 功能:
- 调用
Serial_SendString
函数,将格式化后的字符串通过串口发送出去。 Serial_SendString
会逐字符发送缓冲区String
的内容,直到遇到字符串的结束符\0
。
- 调用
示例运行过程
假设调用如下代码:
c
复制代码
Serial_Printf("Temperature: %d°C, Humidity: %.2f%%\n", 25, 65.23);
运行过程如下:
format
的内容是"Temperature: %d°C, Humidity: %.2f%%\n"
。- 可变参数列表
arg
包含两个值:25
和65.23
。 vsprintf
将format
和arg
合并,生成的字符串为:swift
复制代码
"Temperature: 25°C, Humidity: 65.23%\n"
Serial_SendString
将上述字符串逐字节发送到串口。
注意事项
-
缓冲区大小限制:
- 如果格式化后的字符串超过了
String[100]
的容量,可能会导致缓冲区溢出。 - 可以使用
vsnprintf
替代vsprintf
,限制格式化结果的长度。例如:c
复制代码
vsnprintf(String, sizeof(String), format, arg);
- 如果格式化后的字符串超过了
-
性能:
- 该实现直接调用
Serial_SendString
将整个字符串发送,性能较高。 - 如果字符串过长,发送时间可能会较久。
- 该实现直接调用
-
线程安全性:
- 该实现没有考虑多线程环境。如果多个线程同时调用
Serial_Printf
,可能会出现数据混乱。 - 可以通过互斥锁或其他同步机制解决。
- 该实现没有考虑多线程环境。如果多个线程同时调用
总结
- 作用:
Serial_Printf
是一个自定义的格式化输出函数,支持通过串口发送格式化字符串。 - 实现原理:
- 使用可变参数列表(
va_list
)。 - 利用标准库函数
vsprintf
格式化字符串。 - 调用
Serial_SendString
通过串口发送。
- 使用可变参数列表(
- 优点:
- 灵活性强,支持多种数据格式的输出。
- 易于扩展,类似于标准库中的
printf
。
- 改进建议:
- 使用
vsnprintf
提升安全性,防止缓冲区溢出。 - 添加线程同步以支持多线程环境。
- 使用
这种可变函数特别重要,在以后自己想要打印别的函数
可变参数函数(Variadic Functions)是一种函数设计模式,它允许函数接收数量可变的参数。C语言中,通过 <stdarg.h>
提供的工具(如 va_list
、va_start
、va_arg
和 va_end
),可以实现这种功能。
作用
可变参数函数的主要作用是为函数调用提供灵活性,适应输入参数数量和类型不固定的场景。以下是一些典型应用:
-
格式化输出:
- 函数如
printf
可以接收不同数量和类型的参数,灵活地处理格式化字符串和对应的数据。c
复制代码
printf("Hello, %s! You have %d messages.\n", "User", 5);
- 函数如
-
日志记录:
- 日志函数
log(const char* format, ...)
可以根据不同的输入生成对应的日志消息。c
复制代码
log("Error code: %d, Message: %s\n", 404, "Not Found");
- 日志函数
-
多参数计算:
- 例如实现一个简单的求和函数
sum(int count, ...)
:c
复制代码
int sum(int count, ...) { int total = 0; va_list args; va_start(args, count); for (int i = 0; i < count; i++) { total += va_arg(args, int); } va_end(args); return total; } sum(3, 1, 2, 3); // 返回 6
- 例如实现一个简单的求和函数
-
通用处理接口:
- 允许定义一个函数处理多种类型的数据,减少代码重复。例如在嵌入式系统中,可变参数函数用于调试信息的格式化输出。
好处
1. 提高灵活性
- 可变参数函数适用于输入参数数量或类型不确定的情况,可以根据需求动态调整输入参数,避免定义多个类似的函数。
- 例如,
printf
函数通过解析格式化字符串自动匹配参数,无需为不同的输出需求定义多个函数。
2. 简化代码
- 可以用一个通用函数代替多个特定函数,减少代码重复,提高代码可维护性。
- 示例:
c
复制代码
void print_hello(int times) { for (int i = 0; i < times; i++) { printf("Hello\n"); } }
3. 扩展性强
- 新的需求只需修改格式化字符串或参数传递方式,而不需要修改函数本身。
- 示例:
- 增加新参数的日志记录需求,只需调整调用方式,而无需重写函数逻辑。
4. 支持通用接口
- 在接口设计中,可变参数函数能够统一处理不同类型和数量的输入,提供一致的接口。
- 例如,设计一个数据打包函数
pack(int count, ...)
,可以根据数据类型和数量自动生成字节流。
局限性
尽管可变参数函数提供了许多优势,但它也有一些潜在的局限性和风险:
1. 缺乏类型检查
- 编译器无法对可变参数的类型和数量进行验证,容易引发运行时错误。
c
复制代码
printf("%d", "string"); // 类型不匹配,可能导致未定义行为
2. 难以理解和调试
- 可变参数函数的实现较为复杂,维护时需要仔细检查
va_list
的使用,特别是错误传参或遗漏va_end
时,可能导致不可预测的行为。
3. 性能开销
- 可变参数的处理需要额外的时间开销,特别是在函数被频繁调用的情况下,性能可能受到一定影响。
4. 不适用于所有场景
- 如果参数类型和数量可以提前确定,使用普通函数往往更安全和高效。
应用场景总结
可变参数函数适合以下场景:
- 参数数量和类型不固定。
- 需要统一接口处理不同类型的数据。
- 提供通用的输出格式(如日志记录、调试信息、格式化输出等)。
使用建议
- 安全使用:
- 使用
vsnprintf
替代vsprintf
以避免缓冲区溢出。 - 明确函数的用途,严格控制输入格式。
- 使用
- 合理场景:
- 对于参数确定的情况,优先选择普通函数。
- 在接口设计中,尽量提供一个明确的标志(如参数数量或格式字符串)以描述参数内容。
- 注重文档和注释:
- 为可变参数函数编写清晰的文档,描述参数的用法和格式。
总结
可变参数函数的主要好处是灵活、简化和通用,但它也需要小心处理,特别是在参数类型和数量动态变化的场景下。通过合理使用,可以极大提高代码的可扩展性和复用性,同时降低维护成本。
整个发送代码如下!
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdarg.h>void Serial_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_InitStructrue;GPIO_InitStructrue.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructrue.GPIO_Pin = GPIO_Pin_9;GPIO_InitStructrue.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructrue);USART_InitTypeDef USART1_InitStructure;USART1_InitStructure.USART_BaudRate = 9600;USART1_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;USART1_InitStructure.USART_Mode = USART_Mode_Tx;USART1_InitStructure.USART_Parity = USART_Parity_No ;USART1_InitStructure.USART_StopBits = USART_StopBits_1;USART1_InitStructure.USART_WordLength = USART_WordLength_8b;USART_Init(USART1,&USART1_InitStructure);USART_Cmd(USART1,ENABLE);
}void Serial_SendByte(uint8_t Byte)
{USART_SendData(USART1,Byte);while ((USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET));
}void Serial_SendArray(uint8_t* Array, uint8_t Lenght)
{uint16_t i;for(i=0; i<Lenght; i++){Serial_SendByte(Array[i]);}
}void Serial_SendString(char* String)
{uint8_t i;for(i=0; String[i] != '\0'; i++){Serial_SendByte(String[i]);}
}uint32_t Result(uint32_t X, uint32_t Y)
{uint8_t result = 1;while(Y--){result = result * X;}return result;
}void Serial_SendNum(uint32_t Num, uint16_t Lenght)
{uint16_t i;uint32_t ww;for(i=Lenght; i>0; i--){ww = Result(10,i-1);Serial_SendByte((Num/ww )% 10 + '0');}}int fputc(int ch, FILE* f)
{Serial_SendByte(ch);return ch;
}void Serial_Printf(char* format, ...)
{char String[100];va_list arg;va_start(arg,format);vsprintf(String, format, arg);va_end(arg);Serial_SendString(String);
}重定向C库函数printf()到串口,重定向后可使用printf();
//int fputc(int ch,FILE *f)
//{
// USART_SendData(USART1,(uint8_t)ch);
// while(!(USART_GetFlagStatus(USART1,USART_FLAG_TC)));
// return ch;
//}