目录
- 1.readme
- 2.串口收发问题情况
- [3. 存储串口发来的数据](#3. 存储串口发来的数据)
- 4.简单测试
-
- [4.1 bsp_debug.h](#4.1 bsp_debug.h)
- [4.2 bsp_debug.c](#4.2 bsp_debug.c)
- [4.3 app_debug.h](#4.3 app_debug.h)
- [4.4 app_debug.c](#4.4 app_debug.c)
- [4.5 usart.h](#4.5 usart.h)
- [4.6 usart.c](#4.6 usart.c)
- [4.7 main.h](#4.7 main.h)
- [4.8 main.c](#4.8 main.c)
- [5. 初始化部分](#5. 初始化部分)
-
- [5.1 复位](#5.1 复位)
- [5.2 初始化](#5.2 初始化)
- [5.3 任务](#5.3 任务)
- [6. 控制LED灯亮灭](#6. 控制LED灯亮灭)
- 7.实验现象
1.readme
/*********************************************************************************************/
【 * 】程序简介
-工程名称 :串口接收之控制LED灯
-实验平台 :野火 STM32F103C8T6-STM32开发板
【 !】功能简介:
1-程序正常运行指示灯:核心板红灯500ms翻转一次
2-按键分发数据: 短按KEY1发送数据给上位机,利用开发板的USART与电脑端进行初步通信
3-上位机发送指令给开发板,开发板接收上位机发送的指令(以\n结束),对照指令表完成点灯
指令 ------ 彩灯颜色
GREEN=ON ------ 绿灯亮
GREEN=OFF ------ 绿灯灭
BLUE=ON ------ 蓝灯亮
BLUE=OFF ------ 蓝灯灭
【 !】实验操作:
1.保证开发板相关硬件连接正确,用USB线连接开发板的USB转串口跟电脑,
2.在电脑端打开串口,调试助手并配置好相关参数:115200 8-N-1,
3.编译并下载程序到开发板即可观察现象。
4.复位开发板,上位机发送指令给开发板,开发板接收上位机发送的指令,对照指令表完成点灯
/*********************************************************************************************/
【*】 引脚分配
KEY1->PA0 按键按下为高电平 核心板板载默认下拉且硬件有消抖
NRST->NRST 按键按下为低电平 核心板板载默认上拉且硬件有消抖
LED_R->LED1->PA1 核心板板载LED灯低电平亮
LED_G->LED2->PA2 核心板板载LED灯低电平亮
LED_B->LED3->PA3 核心板板载LED灯低电平亮
DEBUG_TX->USART1_TXD->PA9 PA9作为USART1的TXD 核心板默认使用 PA9 做 DEBUG_TX
USART2_TXD->PA2 PA2作为USART2的TXD 备用DEBUG_TX
USART3_TXD->PB10 PB10作为USART3的TXD 备用DEBUG_TX
DEBUG_RX->USART1_RXD->PA10 PA10作为USART1的RXD 核心板默认使用 PA10 做 DEBUG_RX
USART2_RXD->PA3 PA3作为USART2的RXD 备用DEBUG_RX
USART3_RXD->PB11 PB11作为USART3的RXD 备用DEBUG_RX
1.在J5排针,将CH340 TX 连接 PA10(RX) ,CH340 RX 连接 PA9(TX),USB转串口口在程序上即用对应连接的脚组与串口组。
或
2.使用单独USB转串口线,绿线 TX 连接 PA10(RX) ,白线RX 连接 PA9(TX),黑线 GND 连接 核心板 GND,单独USB转串口线在程序上即用对应连接的脚组与串口组。
/*********************************************************************************************/
【*】程序描述:
< main.c >
1.初始化USART、LED和KEY对应的GPIO、端口等配置
2.使用一个红灯作为程序正常运行的心率灯
3.使用KEY1来发送数据给对应的上位机;
4.上位机发送指令给开发板,开发板接收上位机发送的指令,对照指令表完成点灯
/*********************************************************************************************/
2.串口收发问题情况
c
/**
* @brief DEBUG 串口中断 回调函数
* @param 无
* @retval 无
*/
void DEBUG_IRQHANDLER(void)
{
/* 数据回显 */
if (USART_GetITStatus(DEBUG_USARTX, USART_IT_RXNE) == SET)
{
uint8_t data_temp = USART_ReceiveData(DEBUG_USARTX); //读取数据寄存器,读取后对应的寄存器会被复位
USART_SenByte(DEBUG_USARTX, data_temp);
USART_ClearITPendingBit(DEBUG_USARTX, USART_IT_RXNE); //清除接收标志位
}
}
这个代码是通过串口中断方式,接收一个数据然后发送一个数据,例如发送123456数据,对方接收1,发送1回去,接收2发送回去,接收3发送回去,但是会不会出现一种情况。
发送数据123456
当我接收1的时候发送1,当我接收2的时候,此时3来了,没有接收到,4来了也没有接到,此时接收到的是一个5,然后发送5,那么会发现3和4可能没有接收到。就像我们讲话过程中,丢掉几个关键词,整个语意就变了,所以为了保证能够完整的接收,接收的过程中不要去插嘴。
就像双方在讲话,他讲一个字我插嘴一下,他讲一下我插嘴一下,这个音容易重叠,我就没有听清楚他在讲什么,对于我们的串口接收命令的时候也是一样的,不要接收一个去返回一个,接一个去返回一个是在我有足够的能力知道你要讲什么,你在讲一个字的时候我就知道你后面的意思是什么了,这种是在处理非常快的情况,但是这种也有可能丢包情况,所以我们要保证数据完全接收完成再发送回去。
所以我们可以通过一个数组去保存对方发送过来的所有数据。然后对这些数据进行回应处理。
c
#define DEBUG_BUFFER_SIZE 7
typedef struct
{
uint8_t buffer[DEBUG_BUFFER_SIZE];
uint32_t len;
uint32_t read_flag;
}DEBUG_DataTypeDef;
就需要定义这么一个结构体,用来存储发送过来的所有数据,第一个成员是存储数据的数组,最大长度是7,第二个成员是数组中存储的发送过来的数据个数,第三个成员是判断当前数据是是否可以接收,如果当前正在接收数据,就不可以接收下一个数据,如果当前数据接收完成了,就可以继续接收下一个数据。
read_flag = 1,我正在处理当前的数据,不可以接收新的数据
read_flag = 0,当前我处于空闲状态,可以处理新数据到来
也可以理解成当read_flag = 1的时候我处于忙碌的状态,就不可以处理新的数据,当read_flag = 0的时候我处于空闲的状态,就可以处理新的数据。
先初始化这个结构体成员,默认状态下,是空闲的,可以接收数据,当一个数据发送过来并且结束了,此时len就会置1,read_flag也会置1,表示我当前正在处理该数据,当前不接受新的数据到来,表示拒绝新数据的。
3. 存储串口发来的数据
c
*/
/**
* @brief DEBUG 串口中断 回调函数
* @param 无
* @retval 无
*/
void DEBUG_IRQHANDLER(void)
{
uint8_t data_temp = NULL;
/* 数据回显 */
if (USART_GetITStatus(DEBUG_USARTX, USART_IT_RXNE) == SET)
{
data_temp = USART_ReceiveData(DEBUG_USARTX); //读取数据寄存器,读取后对应的寄存器会被复位
/*如果此次接收到了指定'\n'*/
if (data_temp == '\n')
{
debug_receive.buffer[debug_receive.len] = '\0';
debug_receive.read_flag = 1;
}
/*当存储数据的数组没有满 并且 是空闲状态可以接收*/
if ((debug_receive.len < DEBUG_BUFFER_SIZE - 1) && (debug_receive.read_flag == 0))
{
debug_receive.buffer[debug_receive.len] = data_temp;
debug_receive.len++;
}
/*满数据包*/
if (debug_receive.len == DEBUG_BUFFER_SIZE - 1)
{
debug_receive.buffer[debug_receive.len] = '\0';
debug_receive.read_flag = 1;
}
// USART_SenByte(DEBUG_USARTX, data_temp);
USART_ClearITPendingBit(DEBUG_USARTX, USART_IT_RXNE); //清除接收标志位
}
}
当通过上位机发送过来的数据被data_temp变量接收,每次接收只能接收一个字符。当我发送的数据是abcdef这么一个连续的数据,data_temp只能存储一个a,一个b,一个c,每次只能存储一个字符,那么我需要用一个数组将每次接收过来的单个字符保存起来,这样就可以将一串连续发送过来的数据abcdef保存起来。
先看第二个if
c
#define DEBUG_BUFFER_SIZE 7
/*当存储数据的数组没有满 并且 是空闲状态可以接收*/
if ((debug_receive.len < DEBUG_BUFFER_SIZE - 1) && (debug_receive.read_flag == 0))
{
debug_receive.buffer[debug_receive.len] = data_temp;
debug_receive.len++;
}
例如定义的DEBUG_BUFFER_SIZE是7,表示数组最多只能存储7个字符,但是这个数组buffer前6个元素是可以正常存储接收过来的元素,第7个元素存储的是自己定义的一个结束符号'\0',所以这个if判断就是如果当前存储的字符个数小于最大存储元素个数-1,表示没有满可以存储,并且这个flag等于0才可以存储。
当flag = 0就意味着我发送过来的一组数据,一个是存储字符的数组没有满,或者发送过来的一组数据没有结束,数据是一组一组的发送,每组可以发送连续的6个过来。
满数据情况
例如我发送数据:12345678回车
我定义的数组空间最大是7,而能够存储数据的空间是6,第7个空间需要存储一个'\0',所以数组存储的数据是123456\0,这么7个元素,然后flag设置1,当flag设置1后,表示如果后续继续发送过来的数据我不能去接收,我需要先将存储的123456\0数据用掉后,然后flag设置0才可以接收新的一组数据。
不满数据情况
例如发送过来数据:12回车
最终数组存储的的数据是12\0,这组数据是这个,flag也是1,需要将目前的数据用掉,然后flag设置0,才能接收下一组数据。
可以理解成当我发送很多组数据,而数组每次只能接收一组例如abcde、111223、abcasd有三组数据
每次只能接收一组,当我吧接收的一组数据用掉后,我才能接收下一组,这个flag就是用来检测我是否可以接收下一组数据,当flag = 0表示我可以接收,当flag = 1表示我不可以接收新数据。
而第1个if和第3个if用来判断数组中的数据满的情况,和发送的某一组数据比较短提前结束的情况。
c
/*如果此次接收到了指定'\n'*/
if (data_temp == '\n')
{
debug_receive.buffer[debug_receive.len] = '\0';
debug_receive.read_flag = 1;
}
/*当存储数据的数组没有满 并且 是空闲状态可以接收*/
if ((debug_receive.len < DEBUG_BUFFER_SIZE - 1) && (debug_receive.read_flag == 0))
{
debug_receive.buffer[debug_receive.len] = data_temp;
debug_receive.len++;
}
/*满数据包*/
if (debug_receive.len == DEBUG_BUFFER_SIZE - 1)
{
debug_receive.buffer[debug_receive.len] = '\0';
debug_receive.read_flag = 1;
}
例如我发送过来的数据是12回车,这种是发送的数据比较短的情况,这个是第一个if做的事情,第三个数据是当我发送的数据是123456789回车,超过数组最大存储元素是7情况,会吧多余的789元素舍弃掉,存储数组的元素是123456\0
flag作用是让多组发送过来的数据,一组一组的排队接收,每一次接收的一组需要将该组处理完了之后才能接收下一组,flag = 0正常接收,flag = 1这一组数据接收完成,第二组需要等待第一组的数据处理完。
4.简单测试
4.1 bsp_debug.h
c
#ifndef __BSP_DEBUG_H
#define __BSP_DEBUG_H
#include "stm32f10x.h"
#define DEBUG_USART_NUM 1
#if(DEBUG_USART_NUM == 1)
#define DEBUG_TX_GPIO_PORT GPIOA /* GPIO 端口 */
#define DEBUG_TX_GPIO_CLK_PORT RCC_APB2Periph_GPIOA /* GPIO 端口时钟 */
#define DEBUG_TX_GPIO_PIN GPIO_Pin_9 /* 对应PIN脚 */
#define DEBUG_RX_GPIO_PORT GPIOA /* GPIO 端口 */
#define DEBUG_RX_GPIO_CLK_PORT RCC_APB2Periph_GPIOA /* GPIO 端口时钟 */
#define DEBUG_RX_GPIO_PIN GPIO_Pin_10 /*对应PIN脚 */
#define DEBUG_USARTX USART1 /* 串口号 */
#define DEBUG_USARTX_CLK_PORT RCC_APB2Periph_USART1 /* 串口外设时钟位 */
#define DEBUG_APBXCLKCMD RCC_APB2PeriphClockCmd /* 串口外设时钟 */
#define DEBUG_BAUDRATE 115200 /* 波特率 */
#define DEBUG_IRQ USART1_IRQn /*外部中断向量号*/
#define DEBUG_IRQHANDLER USART1_IRQHandler /*中断处理函数*/
#elif(DEBUG_USART_NUM == 2)
#define DEBUG_TX_GPIO_PORT GPIOA /* GPIO 端口 */
#define DEBUG_TX_GPIO_CLK_PORT RCC_APB2Periph_GPIOA /* GPIO 端口时钟 */
#define DEBUG_TX_GPIO_PIN GPIO_Pin_2 /* 对应PIN脚 */
#define DEBUG_RX_GPIO_PORT GPIOA /* GPIO 端口 */
#define DEBUG_RX_GPIO_CLK_PORT RCC_APB2Periph_GPIOA /* GPIO 端口时钟 */
#define DEBUG_RX_GPIO_PIN GPIO_Pin_3 /* 对应PIN脚 */
#define DEBUG_USARTX USART2 /* 串口号 */
#define DEBUG_USARTX_CLK_PORT RCC_APB1Periph_USART2 /* 串口外设时钟位 */
#define DEBUG_APBXCLKCMD RCC_APB1PeriphClockCmd /* 串口外设时钟 */
#define DEBUG_BAUDRATE 115200 /* 波特率 */
#define DEBUG_IRQ USART2_IRQn /*外部中断向量号*/
#define DEBUG_IRQHANDLER USART2_IRQHandler /*中断处理函数*/
#elif(DEBUG_USART_NUM == 3)
#define DEBUG_TX_GPIO_PORT GPIOB /* GPIO 端口 */
#define DEBUG_TX_GPIO_CLK_PORT RCC_APB2Periph_GPIOB /* GPIO 端口时钟 */
#define DEBUG_TX_GPIO_PIN GPIO_Pin_10 /* 对应PIN脚 */
#define DEBUG_RX_GPIO_PORT GPIOB /* GPIO 端口 */
#define DEBUG_RX_GPIO_CLK_PORT RCC_APB2Periph_GPIOB /* GPIO 端口时钟 */
#define DEBUG_RX_GPIO_PIN GPIO_Pin_11 /* 对应PIN脚 */
#define DEBUG_USARTX USART3 /* 串口号 */
#define DEBUG_USARTX_CLK_PORT RCC_APB1Periph_USART3 /* 串口外设时钟位 */
#define DEBUG_APBXCLKCMD RCC_APB1PeriphClockCmd /* 串口外设时钟 */
#define DEBUG_BAUDRATE 115200 /* 波特率 */
#define DEBUG_IRQ USART3_IRQn /*外部中断向量号*/
#define DEBUG_IRQHANDLER USART3_IRQHandler /*中断处理函数*/
#endif
#define DEBUG_BUFFER_SIZE 7
typedef struct
{
uint8_t buffer[DEBUG_BUFFER_SIZE];
uint32_t len;
uint32_t read_flag;
}DEBUG_DataTypeDef;
extern DEBUG_DataTypeDef debug_receive;
void DEBUG_USART_PinConfig(void);
void DEBUG_USART_MODEConfig(void);
void DEBUG_USART_Init(void);
void DEBUG_NVIC_Config(void);
#endif /*__BSP_DEBUG_H*/
4.2 bsp_debug.c
c
/**
******************************************************************************
* @file bsp_debug.c
* @author 作者
* @version v1.0
* @date 2026.6.6
* @brief 调试串口函数接口
******************************************************************************
* @attention
*
* 版权声明
*
******************************************************************************
*/
#include "debug/bsp_debug.h"
#include "usart/usart_com.h"
DEBUG_DataTypeDef debug_receive = { 0 };
/**
* @brief 初始化控制 DEBUG 串口 的IO
* @param 无
* @retval 无
*/
void DEBUG_USART_PinConfig(void)
{
GPIO_InitTypeDef GPIO_InitStruct = { 0 };
#if 1
/*对串口发送模式IO配置*/
/* 开启 DEBUG 相关的GPIO外设/端口时钟 */
RCC_APB2PeriphClockCmd(DEBUG_TX_GPIO_CLK_PORT, ENABLE); //开启GPIOA 端口时钟
// 配置引脚:选择GPIOA的Pin9
GPIO_InitStruct.GPIO_Pin = DEBUG_TX_GPIO_PIN;
// 配置输出速度:50MHz
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
// 配置工作模式:推挽复用
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(DEBUG_TX_GPIO_PORT, &GPIO_InitStruct);
#endif
#if 1
/*对串口接收模式IO配置*/
/* 开启 DEBUG 相关的GPIO外设/端口时钟 */
RCC_APB2PeriphClockCmd(DEBUG_RX_GPIO_CLK_PORT, ENABLE); //开启GPIOA 端口时钟
// 配置引脚:选择GPIOA的Pin10
GPIO_InitStruct.GPIO_Pin = DEBUG_RX_GPIO_PIN;
// 配置输出速度:50MHz
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
// 配置工作模式:上拉输入/浮空输入
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(DEBUG_RX_GPIO_PORT, &GPIO_InitStruct);
#endif
}
/**
* @brief 配置 DEBUG 串口模式
* @param 无
* @retval 无
*/
void DEBUG_USART_MODEConfig(void)
{
USART_InitTypeDef usart_InitStruct = { 0 };
/* 开启 DEBUG 相关的GPIO外设/端口时钟 */
DEBUG_APBXCLKCMD(DEBUG_USARTX_CLK_PORT, ENABLE); //开启 端口时钟
/* 配置串口的工作参数 */
//波特率配置:115200
usart_InitStruct.USART_BaudRate = DEBUG_BAUDRATE;
//硬件流控开关配置:无
usart_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
//配置工作模式:发送模式和接收模式
usart_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
//校验:无校验位
usart_InitStruct.USART_Parity = USART_Parity_No;
//停止位:1个停止位
usart_InitStruct.USART_StopBits = USART_StopBits_1;
//数据帧长度:8位
usart_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_Init(DEBUG_USARTX, &usart_InitStruct);
/* 开启串口数据接收中断 */
USART_ITConfig(DEBUG_USARTX, USART_IT_RXNE, ENABLE);
}
/**
* @brief 配置 DEBUG 中断配置
* @param 无
* @retval 无
*/
void DEBUG_NVIC_Config()
{
// 定义一个 NVIC 结构体
NVIC_InitTypeDef nvic_initstauct = { 0 };
/* 开启 AFIO 相关时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
/*配置中断源*/
nvic_initstauct.NVIC_IRQChannel = DEBUG_IRQ;
/*配置抢占优先级*/
nvic_initstauct.NVIC_IRQChannelPreemptionPriority = 1;
/*配置响应优先级*/
nvic_initstauct.NVIC_IRQChannelSubPriority = 0;
/*开启中断*/
nvic_initstauct.NVIC_IRQChannelCmd = ENABLE;
// 把配置写入硬件,生效
NVIC_Init(&nvic_initstauct);
}
/**
* @brief DEBUG 串口初始化
* @param 无
* @retval 无
*/
void DEBUG_USART_Init(void)
{
/* 配置 DEBUG 中断配置 */
DEBUG_NVIC_Config();
/*配置 DEBUG 串口模式*/
DEBUG_USART_MODEConfig();
/* 串口 GPIO 配置 */
DEBUG_USART_PinConfig();
/* 串口使能 */
USART_Cmd(DEBUG_USARTX, ENABLE);
}
/*
1.因为数组下标从0开始的,定义buffer[DEBUG_BUFFER_SIZE]时,可取的末尾是buffer[DEBUG_BUFFER_SIZE - 1];
2.因为数组下标从0开始接收,当接收了len个,buffer[len-1]为最近一次保存位置,buffer[len]指向的是下一个准备接收空位,所以用len与DEBUG_BUFFER_SIZE-1比较
*/
/**
* @brief DEBUG 串口中断 回调函数
* @param 无
* @retval 无
*/
void DEBUG_IRQHANDLER(void)
{
uint8_t data_temp = NULL;
/* 数据回显 */
if (USART_GetITStatus(DEBUG_USARTX, USART_IT_RXNE) == SET)
{
//读取数据寄存器,读取后对应的寄存器会被复位
data_temp = USART_ReceiveData(DEBUG_USARTX);
/*如果此次接收到了指定'\n'*/
if (data_temp == '\n')
{
debug_receive.buffer[debug_receive.len] = '\0';
debug_receive.read_flag = 1;
}
/*当存储数据的数组没有满 并且 是空闲状态可以接收*/
if ((debug_receive.len < DEBUG_BUFFER_SIZE - 1) && (debug_receive.read_flag == 0))
{
debug_receive.buffer[debug_receive.len] = data_temp;
debug_receive.len++;
}
/*满数据包*/
if (debug_receive.len == DEBUG_BUFFER_SIZE - 1)
{
debug_receive.buffer[debug_receive.len] = '\0';
debug_receive.read_flag = 1;
}
// USART_SenByte(DEBUG_USARTX, data_temp);
USART_ClearITPendingBit(DEBUG_USARTX, USART_IT_RXNE); //清除接收标志位
}
}
/***************************************END OF FILE *********************************************************/
4.3 app_debug.h
c
#ifndef __APP_DEBUG_H
#define __APP_DEBUG_H
#include "stm32f10x.h"
void DEBUG_ReadBufferReset(void);
void DEBUG_TaskInit(void);
void DEBUG_Task(void);
#endif /*__APP_DEBUG_H*/
4.4 app_debug.c
c
/**
******************************************************************************
* @file App_debug.c
* @author 作者
* @version v1.0
* @date 2026.9.22
* @brief DEBUG 串口 应用层功能接口
******************************************************************************
* @attention
*
* 版权声明
*
******************************************************************************
*/
#include "debug/bsp_debug.h"
#include "debug/app_debug.h"
#include "usart/usart_com.h"
#include <string.h>
/**
* @brief DEBUG串口 接收缓存区复位
* @param 无
* @retval 无
*/
void DEBUG_ReadBufferReset(void)
{
memset(debug_receive.buffer, NULL, debug_receive.len);
debug_receive.len = 0;
debug_receive.read_flag = 0;
}
/**
* @brief DEBUG串口 任务初始化
* @param 无
* @retval 无
*/
void DEBUG_TaskInit(void)
{
DEBUG_ReadBufferReset();
}
/**
* @brief DEBUG串口 任务
* @param 无
* @retval 无
*/
void DEBUG_Task(void)
{
if (debug_receive.read_flag)
{
printf("已接收命令:");
USART_SenArray(DEBUG_USARTX, debug_receive.buffer, debug_receive.len);
DEBUG_ReadBufferReset();
}
}
/***************************************END OF FILE *********************************************************/
4.5 usart.h
c
#ifndef __USART_COM_H
#define __USART_COM_H
#include "stm32f10x.h"
#include "stdio.h"
void USART_SenByte(USART_TypeDef* pUSARTx, uint8_t ch);
void USART_SenArray(USART_TypeDef* pUSARTx, uint8_t* array, uint32_t num);
void USART_SenString(USART_TypeDef* pUSARTx, char* str);
#endif /*__USART_COM_H*/
4.6 usart.c
c
/**
******************************************************************************
* @file usart_com.c
* @author 作者
* @version v1.0
* @date 2026.6.7
* @brief 重定向c库printf函数到usart端口
******************************************************************************
* @attention
*
* 版权声明
*
******************************************************************************
*/
#include "usart/usart_com.h"
#include "debug/bsp_debug.h"
/**
* @brief 发送一字节函数
* @param pUSARTx:USARTx(X=1,2,3)/UARTx(x=4,5)
* @param ch:要发送的数据
* @note
* @retval 无
*/
void USART_SenByte(USART_TypeDef* pUSARTx, uint8_t ch)
{
/* 等待发送完成 */
while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TC) == RESET);
/*发送一字节数据到pUSARTx*/
USART_SendData(pUSARTx, ch);
/* 等待发送数据寄存器为空 */
while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
/**
* @brief 发送8位的数组函数
* @param pUSARTx:USARTx(X=1,2,3)/UARTx(x=4,5)
* @param arrary:要发送的数组
* @param num: 数组大小
* @note
* @retval 无
*/
void USART_SenArray(USART_TypeDef* pUSARTx, uint8_t* array, uint32_t num)
{
/* 等待发送完成 */
while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TC) == RESET);
for(uint32_t i = 0; i < num; i++)
{
/*发送一字节数据到pUSARTx*/
USART_SendData(pUSARTx, array[i]);
/* 等待发送数据寄存器为空 */
while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
}
/**
* @brief 发送字符串函数
* @param pUSARTx:USARTx(X=1,2,3)/UARTx(x=4,5)
* @param str:要发送的字符串
* @note
* @retval 无
*/
void USART_SenString(USART_TypeDef* pUSARTx, char* str)
{
uint32_t k = 0;
/* 等待发送完成 */
while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TC) == RESET);
do
{
/*发送一字节数据到pUSARTx*/
USART_SendData(pUSARTx, *(str + k));
k++;
/* 等待发送数据寄存器为空 */
while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}while(*(str + k) != '\0');
}
/**
* @brief 将一个字符写入到文件中,重定向C库函数printf到串口,重定向后可使用printf函数
* @param ch:要写入的字符
* @param f:指向FILE结构的指针
* @note 无
* @retval 成功,返回该字符
*/
int fputc(int ch, FILE* f)
{
/* 等待发送完成 */
while(USART_GetFlagStatus(DEBUG_USARTX, USART_FLAG_TC) == RESET);
/*发送一字节数据到pUSARTx*/
USART_SendData(DEBUG_USARTX, ch);
/* 等待发送数据寄存器为空 */
while(USART_GetFlagStatus(DEBUG_USARTX, USART_FLAG_TXE) == RESET);
return ch;
}
/***************************************END OF FILE *********************************************************/
4.7 main.h
c
#ifndef _MAIN_H
#define _MAIN_H
#include "led/app_led.h"
#include "key/app_key.h"
#include "debug/app_debug.h"
#endif /*_MAIN_H*/
4.8 main.c
c
/**
******************************************************************************
* @file main.c
* @author 作者
* @version v1.0
* @date 2026.9.22
* @brief 串口接收之控制LED灯标准库写法
******************************************************************************
* @attention
*
* 版权声明
*
******************************************************************************
*/
#include "main.h"
#include "led/bsp_gpio_led.h"
#include "systick/bsp_systick.h"
#include "key/bsp_gpio_key.h"
#include "debug/bsp_debug.h"
#include "stm32f10x.h"
#include "usart/usart_com.h"
#include "delay/bsp_delay.h"
/**
* @brief 主函数
* @param 无
* @note 无
* @retval 无
*/
int main (void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
LED_GPIO_Config();
KEY_Init(&key1_info);
DEBUG_USART_Init();
DEBUG_TaskInit();
ProgramRunLed_TaskInit(500);
Key_TaskInit(20);
SysTick_Init();
while(1)
{
/* 程序正常指示灯 */
ProgramRunLed_Task();
/* 按键长短触发灯 */
Key_Task();
DEBUG_Task();
}
}
测试1,当我输入12,点击发送,然后输入34回车,点击发送运行结果是1234


核心代码
c
/**
* @brief DEBUG 串口中断 回调函数
* @param 无
* @retval 无
*/
void DEBUG_IRQHANDLER(void)
{
uint8_t data_temp = NULL;
/* 数据回显 */
if (USART_GetITStatus(DEBUG_USARTX, USART_IT_RXNE) == SET)
{
//读取数据寄存器,读取后对应的寄存器会被复位
data_temp = USART_ReceiveData(DEBUG_USARTX);
/*如果此次接收到了指定'\n'*/
if (data_temp == '\n')
{
debug_receive.buffer[debug_receive.len] = '\0';
debug_receive.read_flag = 1;
}
/*当存储数据的数组没有满 并且 是空闲状态可以接收*/
if ((debug_receive.len < DEBUG_BUFFER_SIZE - 1) && (debug_receive.read_flag == 0))
{
debug_receive.buffer[debug_receive.len] = data_temp;
debug_receive.len++;
}
/*满数据包*/
if (debug_receive.len == DEBUG_BUFFER_SIZE - 1)
{
debug_receive.buffer[debug_receive.len] = '\0';
debug_receive.read_flag = 1;
}
// USART_SenByte(DEBUG_USARTX, data_temp);
USART_ClearITPendingBit(DEBUG_USARTX, USART_IT_RXNE); //清除接收标志位
}
}
将数组元素发送给上位机代码
c
/**
* @brief DEBUG串口 任务
* @param 无
* @retval 无
*/
void DEBUG_Task(void)
{
if (debug_receive.read_flag)
{
printf("已接收命令:");
USART_SenArray(DEBUG_USARTX, debug_receive.buffer, debug_receive.len);
DEBUG_ReadBufferReset();
}
}
这是因为当输入1和2的时候,进入第二个if代码,给数组存入1和2,len此时变成2
当有输入3和4和回车,此时先进入两次第二个if,此时数组又存入的3和4,现在数组里面的元素是1234,此时的len变成4,最后进入第一个if,当前接收过来的是\n,此时数组元素存储的是1234\n,然后将flag设置1,接着在轮询代码中,将数组的元素发送出去,所以上位机收到的数据是1234
测试2,当我输入元素是12回车,运行结果就是12

当我输入1和2和回车,前面1和2会进入第二个if,此时数组元素是12,len的值是2,最后一个回车会进入第一个if,此时数组元素是12\n,同时flag设置1
测试3,当数据溢出情况,我定义的数组空间大小是7,也就是说最多只能存储6个元素,因为第7个元素我需要存储一个'\0'字符
当我输入12345678时候超过最大空间的时候,运行结果是123456会吧后面7跟8舍弃掉。

当输入12345678时候,前面123456会直接进入第二个if,之后此时数组元素是123456,len的值是6,因为先存入元素,len后自动加1,因为数组下标0、1、2、3、4、5分别存储了123456,6个元素,当len是5,也就是下标是5的时候,存储的是第6个元素,len然后+1,此时len变成6。
当len 是6的时候会在第三个if进行判断
c
/*满数据包*/
if (debug_receive.len == DEBUG_BUFFER_SIZE - 1)
{
debug_receive.buffer[debug_receive.len] = '\0';
debug_receive.read_flag = 1;
}
DEBUG_BUFFER_SIZE这个是7,所以6 == 6成立,此时给下标是6存储的是'\0'然后flag设置1
此时数组元素是123456'\0'这么7个元素,最终由于flag是1,所以可以将数组所有元素发送给上位机,显示结果就可以看到123456
5. 初始化部分
在进行4里面的实验见过之前先对数组有一个初始化部分。
5.1 复位
c
/**
* @brief DEBUG串口 接收缓存区复位
* @param 无
* @retval 无
*/
void DEBUG_ReadBufferReset(void)
{
memset(debug_receive.buffer, NULL, debug_receive.len);
debug_receive.len = 0;
debug_receive.read_flag = 0;
}
通过函数memset对数组初始化,数组第一个参数是数组的起始地址,第二个参数是初始化的值,第三个参数内存大小,就是将内存len个字节大小,从数组第一个位置开始到len个字节长度大小全部初始化为0
还要对len和flag初始化0
5.2 初始化
c
/**
* @brief DEBUG串口 任务初始化
* @param 无
* @retval 无
*/
void DEBUG_TaskInit(void)
{
DEBUG_ReadBufferReset();
}
通过调用复位函数,对数组,len,flag进行初始化
5.3 任务
c
/**
* @brief DEBUG串口 任务
* @param 无
* @retval 无
*/
void DEBUG_Task(void)
{
if (debug_receive.read_flag)
{
printf("已接收命令:");
USART_SenArray(DEBUG_USARTX, debug_receive.buffer, debug_receive.len);
DEBUG_ReadBufferReset();
}
}
当flag为1的时候就执行相应事情。将数组里面存储的大小发送给上位机。
6. 控制LED灯亮灭
c
/**
* @brief 打印指令输入提示信息
* @param 无
* @retval 无
*/
void Show_Message(void)
{
printf("\r\n这是一个通过串口通信指令控制G/B实验\n");
printf("使用USART参数为:%d 8-N-1\n",DEBUG_BAUDRATE);
printf("开发板接到指令后控制G/B灯,指令对应如下(以\\n结束):\n");
printf(" 指令 ------ 彩灯颜色\n");
printf("GREEN=ON ------ 绿灯亮\n");
printf("GREEN=OFF ------ 绿灯灭\n");
printf("BLUE=ON ------ 蓝灯亮\n");
printf("BLUE=OFF ------ 蓝灯灭\n");
}
/**
* @brief DEBUG串口 任务
* @param 无
* @retval 无
*/
void DEBUG_Task(void)
{
if (debug_receive.read_flag)
{
printf("已接收命令:");
//USART_SenArray(DEBUG_USARTX, debug_receive.buffer, debug_receive.len);
char* data_temp = NULL;
data_temp = strstr((char*)debug_receive.buffer, "GREEN=");
if (data_temp != NULL)
{
if(strncmp(data_temp + strlen("GREEN="), "ON", strlen("ON")) == 0)
{
LED_GPIO_ON(G_LED2_GPIO_PORT, G_LED2_GPIO_PIN, LED_LOW_TRIGGER);
}
else if(strncmp(data_temp + strlen("GREEN="), "OFF", strlen("OFF")) == 0)
{
LED_GPIO_OFF(G_LED2_GPIO_PORT, G_LED2_GPIO_PIN, LED_LOW_TRIGGER);
}
}
data_temp = strstr((char*)debug_receive.buffer, "BLUE=");
if (data_temp != NULL)
{
if(strncmp(data_temp + strlen("BLUE="), "ON", strlen("ON")) == 0)
{
LED_GPIO_ON(B_LED3_GPIO_PORT, B_LED3_GPIO_PIN, LED_LOW_TRIGGER);
}
else if(strncmp(data_temp + strlen("BLUE="), "OFF", strlen("OFF")) == 0)
{
LED_GPIO_OFF(B_LED3_GPIO_PORT, B_LED3_GPIO_PIN, LED_LOW_TRIGGER);
}
}
DEBUG_ReadBufferReset();
}
}
在这个代码中有两个函数用法需要注意
strstr("主串",""子串)
第一个参数是主串,第二个参数是子串,函数功能在主字符串中去寻找子字符串的位置,并且在主串中去返回子字符串起始地址
例如主字符串123abc,子字符串是abc
那么在主字符串123abc中,返回的是a的地址
c
char* data_temp = NULL;
data_temp = strstr((char*)debug_receive.buffer, "GREEN=");
例如我定义一个指针data_temp,在数组buffer中查找子字符串"GREEN="起始位置,data_temp指针指向字符G的位置
第二个函数
strncmp("字符串1","字符串2",长度)
这个函数关键在于第三个参数
例如串1是"abc123",串2是"abc456",第三个参数是3
就是这两个字符串中只比较前3个字符大小第一个字符串前三个字符是abc,第二个字符串前三个也是abc,就是吧原本长度大小字符串我只需要比较指定长度的字符串
这个函数是作用是比较指定长度的字符串大小

例如我在上位机发送数据
GREEN=ON BLUE=ON回车
首先我通过代码
c
char* data_temp = NULL;
data_temp = strstr((char*)debug_receive.buffer, "GREEN=");
先通过data_temp指针在buffer数组中去定位子字符串"GREEN="位置,然后该指针在buffer数组中指向的是G字符
在字符串"GREEN=ON BLUE=ON"中去查找子字符串"GREEN=",如果找到了就通过指针data_temp在主串中找到子串的起始位置
此时data_temp指向的是G位置
c
if(strncmp(data_temp + strlen("GREEN="), "ON", strlen("ON")) == 0)
整个字符串"GREEN=ON BLUE=ON"
在这个代码中,第一个参数data_temp指针移动字符串"GREEN="长度位置,"GREEN="字符串长度是6,所以data_temp向右移动6个位置,此时data_temp指向的是O位置
第二个参数是字符串"ON",第三参数是ON的长度是2
此时data_temp指向的位置开始是第1个位置,后面一个位置是第2个位置,这两个字符组成的字符串与字符串"ON"比较大小,这两个位置的字符串是"ON",与第二个参数字符串"ON"比较大小,就相同,因为第三个参数是2,只比较两个字符,此时就执行LED亮代码
7.实验现象
通过串口发送控制LED亮灭
实现步骤

上电后按下复位键,上位机接收数据,收到提示指令,按照指令语句发送数据,但是输入完指令后需要按下回车表示结束,然后点击发送即可