stm32F103C8T6标准库串口接收之控制LED灯3

目录

  • 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亮灭

实现步骤

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

相关推荐
新思维软件1 小时前
基于单片机的激光防盗报警器语音报警系统:STM32 激光围栏 + ESP32-CAM 图像留痕全链路实现
stm32·单片机·物联网·物联网开发
新思维软件1 小时前
基于无线传输的地衡系统:三节点 LoRa 组网与 MQTT 上云的分布式称重方案
分布式·stm32·单片机·嵌入式硬件·物联网·物联网开发
芯祥联1 小时前
STM32 上跑 MQTT 怎么选?嵌入式 MQTT 客户端 C 语言实现对比
c语言·stm32·单片机·嵌入式硬件·网络协议·开源·broker
Brilliantwxx10 小时前
【STM32】 NVIC 与 EXTI 中断 · 超详细源码级解析
stm32·单片机·嵌入式硬件
加载力科技11 小时前
硬盘咔咔响是坏盘信号!赶在数据丢失前克隆换盘:不重装系统、不丢文件
windows·嵌入式硬件·系统安全·策略模式·命令模式·硬盘
niuTaylor12 小时前
飞腾 FT-2000/4 Ubuntu 内存卡 / NVMe SSD 系统制作完整指南
linux·服务器·人工智能·驱动开发·单片机
思茂信息13 小时前
CST软件BCI仿真模型及仿真案例
开发语言·单片机·嵌入式硬件·算法·emc
李永奉14 小时前
中科蓝讯SDK开发-BT897X、BT891X、AB573X、AB572X 系列 触摸按键提示音功能实现
网络·单片机·嵌入式硬件·mcu·物联网
Zw-awa14 小时前
定时器到底在数什么:STM32 的时间基准从哪里来
stm32