STM32 的USART1 和 USART2 串口接收发送数据应用(自定义通讯格式)
目录
一、通讯格式定义
二、硬件接线
三、STLINK 硬件接线
四、代码实现
mian.c 代码
version.h 代码
version.c 代码
五、代码编译—正常
六、功能实现
一、通讯格式定义
/********************************************************
通讯协议定义: 接收到 功能 返回发送
A100000D 打开门1 返回数据 A100000D 并返回门1的状态
A200000D 打开门2 返回数据 A200000D 并返回门2的状态
A300000D 打开拍照LED 返回数据 A300000D 并返回开灯成功 A30000001D
A400000D 关闭拍照LED 返回数据 A400000D 并返回开灯成功 A40000001D
A500000D 获取版本号 返回数据 1.0.0
A600101D 修改版本号 101为新修改的版本号
*******************************************************/
二、硬件接线
/************************************************
硬件接线:
STM32F103C8T6控制板 ———-USB转TTL板
串口1 使用PA9 PA10 连接
PA9(Serial1 TX) ————– RX
PA10(Serial1 RX) ————– TX
GND ————– GND
串口2 使用PA2 PA3 连接
PA2(Serial2 TX) ————– USART2_RX
PA3(Serial2 RX) ————– USART2_TX
GND ————– GND
*************************************************/
三、STLINK 硬件接线
/************************************************
使用STLINK 烧录程序
SWD 硬件接线: STM32F103C8T6控制板 STLINK烧录器
SWCLK ————– SWCLK
SWDIO ————– SWDIO
GND ————– GND
3.3V ————– 3.3V
*************************************************/
四、代码实现
mian.c 代码
/************************************************
代码实现了通过 USART1 和 USART2 串口接收特定格式的指令(以 'A' 开头,以 'D' 结尾,长度为 8)
同时解析打印接收到的指令内容
对于错误的指令格式,也做了对于的数据提示处理
此外,还初始化了一个 LED 引脚,但在当前代码中 LED 仅完成初始化*************************************************/
/************************************************
使用STLINK 烧录程序
SWD 硬件接线: STM32F103C8T6控制板 STLINK烧录器
SWCLK -------------- SWCLK
SWDIO -------------- SWDIO
GND -------------- GND
3.3V -------------- 3.3V
*************************************************/
/************************************************
硬件接线: STM32F103C8T6控制板 USB转TTL板
串口1 使用PA9 PA10 连接
PA9(Serial1 TX) -------------- RX
PA10(Serial1 RX) -------------- TX
GND -------------- GND
串口2 使用PA2 PA3 连接
PA2(Serial2 TX) -------------- USART2_RX
PA3(Serial2 RX) -------------- USART2_TX
GND -------------- GND
*************************************************/
// 本程序控制的电磁锁是 状态反馈:上锁状态蓝白线断开(断),开锁状态蓝白线短路(通)
/********************************************************
PA4 初始是高电位,在高电位时 表示 锁是关闭状态,
低电位状态表示锁是打开状态PA5 初始是高电位,在高电位时 表示 锁是关闭状态,
低电位时 表示 锁是打开状态*******************************************************/
/********************************************************
通讯协议定义: 接收到 功能 返回发送
A100000D 打开门1 返回数据 A100000D 并返回门1的状态
A200000D 打开门2 返回数据 A200000D 并返回门2的状态
A300000D 打开拍照LED 返回数据 A300000D 并返回开灯成功 A30000001D
A400000D 关闭拍照LED 返回数据 A400000D 并返回开灯成功 A40000001D
A500000D 获取版本号
A600101D 修改版本号 101为新修改的版本号
*******************************************************/
/*============= 头文件包含 =============*/
#include "stm32f10x.h" // 包含 STM32F10x 系列微控制器的标准库头文件,提供了对 STM32 外设的操作函数和寄存器定义
#include <stdio.h> // 包含标准输入输出库头文件,用于使用 printf 等函数进行数据的输出
#include "version.h"
/*============= 定义 LED 引脚和端口 =============*/
#define LED_PIN GPIO_Pin_13 // 定义 LED 所连接的引脚为 GPIO_Pin_13
#define LED_PORT GPIOC // 定义 LED 所连接的端口为 GPIOC
/*============= 定义接收缓冲区相关参数 =============*/
#define RX_BUFFER_SIZE 128 // 定义接收缓冲区的大小为 8 字节,用于存储接收到的指令数据
uint8_t rx_buffer1[RX_BUFFER_SIZE]; // 定义一个大小为 RX_BUFFER_SIZE 的无符号 8 位整型数组,用于存储 USART1 接收到的数据
uint8_t rx_index1 = 0; // 定义一个无符号 8 位整型变量,用于记录 USART1 接收缓冲区的当前索引,初始值为 0
volatile uint32_t timeout_counter1 = 0; // 定义一个易变的无符号 32 位整型变量,用于 USART1 超时计数,初始值为 0
#define TIMEOUT_VALUE 50 // 定义超时时间值为 50,用于判断接收数据是否超时
uint8_t rx_buffer2[RX_BUFFER_SIZE]; // 定义一个大小为 RX_BUFFER_SIZE 的无符号 8 位整型数组,用于存储 USART2 接收到的数据
uint8_t rx_index2 = 0; // 定义一个无符号 8 位整型变量,用于记录 USART2 接收缓冲区的当前索引,初始值为 0
volatile uint32_t timeout_counter2 = 0; // 定义一个易变的无符号 32 位整型变量,用于 USART2 超时计数,初始值为 0
/*============= 定义门状态相关变量 =============*/
volatile uint8_t door1_state = 0; // 定义一个易变的无符号 8 位整型变量,用于记录门 1 的状态,初始值为 0 表示关闭
volatile uint8_t door2_state = 0; // 定义一个易变的无符号 8 位整型变量,用于记录门 2 的状态,初始值为 0 表示关闭
volatile uint32_t door1_open_start_time = 0; // 定义一个易变的无符号 32 位整型变量,用于记录门 1 打开的起始时间,初始值为 0
volatile uint32_t door2_open_start_time = 0; // 定义一个易变的无符号 32 位整型变量,用于记录门 2 打开的起始时间,初始值为 0
volatile uint32_t timer_count = 0; // 定义一个易变的无符号 32 位整型变量,用于记录定时器的计数,初始值为 0
volatile uint8_t need_check_door1 = 0; // 标记是否需要在延时后检查门 1 状态
volatile uint8_t need_check_door2 = 0; // 标记是否需要在延时后检查门 2 状态
/*============= 系统时钟配置函数 =============*/
// 该函数用于配置系统时钟,使系统工作在合适的时钟频率下
void SystemClock_Config(void) {
RCC_DeInit(); // 复位 RCC 寄存器到默认状态,确保时钟配置从初始状态开始
RCC_HSEConfig(RCC_HSE_ON); // 使能外部高速时钟(HSE),为系统提供稳定的时钟源
while (RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET); // 等待外部高速时钟准备好,确保时钟源稳定后再进行后续配置
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); // 使能 FLASH 预取缓冲区,提高代码执行效率
FLASH_SetLatency(FLASH_Latency_2); // 设置 FLASH 等待周期为 2 个时钟周期,确保在高速时钟下 FLASH 能正常工作
RCC_HCLKConfig(RCC_SYSCLK_Div1); // 设置 AHB 时钟(HCLK)为系统时钟(SYSCLK)的 1 分频,即 AHB 时钟频率等于系统时钟频率
RCC_PCLK2Config(RCC_HCLK_Div1); // 设置 APB2 高速外设时钟(PCLK2)为 AHB 时钟(HCLK)的 1 分频,即 PCLK2 时钟频率等于 AHB 时钟频率
RCC_PCLK1Config(RCC_HCLK_Div2); // 设置 APB1 低速外设时钟(PCLK1)为 AHB 时钟(HCLK)的 2 分频,降低低速外设的时钟频率
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); // 配置 PLL 时钟源为 HSE 不分频,PLL 倍频系数为 9,将系统时钟频率提升
RCC_PLLCmd(ENABLE); // 使能 PLL,开启锁相环以生成更高频率的时钟
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); // 等待 PLL 时钟准备好,确保 PLL 输出稳定的时钟信号
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); // 设置系统时钟源为 PLL 时钟,将系统时钟切换到 PLL 输出的时钟信号
while (RCC_GetSYSCLKSource() != 0x08); // 等待系统时钟源切换完成,确保系统时钟切换成功
}
/*============= 串口 1 初始化函数 =============*/
// 该函数用于初始化 USART1 串口,使其能够进行数据的收发
void USART1_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure; // 定义 GPIO 初始化结构体变量,用于配置 GPIO 引脚的参数
USART_InitTypeDef USART_InitStructure; // 定义 USART 初始化结构体变量,用于配置 USART 的参数
NVIC_InitTypeDef NVIC_InitStructure; // 定义 NVIC 初始化结构体变量,用于配置中断优先级和使能中断
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE); // 使能 GPIOA 和 USART1 的时钟,为后续的 GPIO 和 USART 配置提供时钟源
// 配置 USART1 Tx (PA9) 为复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; // 设置要配置的引脚为 PA9
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 设置引脚模式为复用推挽输出,用于发送数据
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置引脚速度为 50MHz,提高数据传输速度
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化 GPIOA 的 PA9 引脚
// 配置 USART1 Rx (PA10) 为浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // 设置要配置的引脚为 PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 设置引脚模式为浮空输入,用于接收数据
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化 GPIOA 的 PA10 引脚
USART_InitStructure.USART_BaudRate = 115200; // 设置 USART1 的波特率为 115200,确定数据传输的速率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 设置数据位长度为 8 位,符合常见的数据传输格式
USART_InitStructure.USART_StopBits = USART_StopBits_1; // 设置停止位为 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 模式为接收和发送模式,使 USART 能够同时进行数据的接收和发送
USART_Init(USART1, &USART_InitStructure); // 初始化 USART1,将上述配置应用到 USART1 上
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 使能 USART1 的接收缓冲区非空中断,当接收缓冲区有数据时触发中断
USART_Cmd(USART1, ENABLE); // 使能 USART1,开启 USART1 的工作
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; // 设置中断通道为 USART1 中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 设置抢占优先级为 0,具有最高的抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 设置子优先级为 0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中断通道,允许 USART1 中断触发
NVIC_Init(&NVIC_InitStructure); // 初始化 NVIC,将上述中断配置应用到 NVIC 上
}
/*============= 串口 2 初始化函数 =============*/
// 该函数用于初始化 USART2 串口,使其能够进行数据的收发
void USART2_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure; // 定义 GPIO 初始化结构体变量,用于配置 GPIO 引脚的参数
USART_InitTypeDef USART_InitStructure; // 定义 USART 初始化结构体变量,用于配置 USART 的参数
NVIC_InitTypeDef NVIC_InitStructure; // 定义 NVIC 初始化结构体变量,用于配置中断优先级和使能中断
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); // 使能 USART2 的时钟,为 USART2 的配置提供时钟源
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能 GPIOA 的时钟,为 GPIO 引脚的配置提供时钟源
// 配置 USART2 Tx (PA2) 为复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; // 设置要配置的引脚为 PA2
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 设置引脚模式为复用推挽输出,用于发送数据
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置引脚速度为 50MHz,提高数据传输速度
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化 GPIOA 的 PA2 引脚
// 配置 USART2 Rx (PA3) 为浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; // 设置要配置的引脚为 PA3
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 设置引脚模式为浮空输入,用于接收数据
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化 GPIOA 的 PA3 引脚
USART_InitStructure.USART_BaudRate = 115200; // 设置 USART2 的波特率为 115200,确定数据传输的速率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 设置数据位长度为 8 位,符合常见的数据传输格式
USART_InitStructure.USART_StopBits = USART_StopBits_1; // 设置停止位为 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 模式为接收和发送模式,使 USART 能够同时进行数据的接收和发送
USART_Init(USART2, &USART_InitStructure); // 初始化 USART2,将上述配置应用到 USART2 上
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); // 使能 USART2 的接收缓冲区非空中断,当接收缓冲区有数据时触发中断
USART_Cmd(USART2, ENABLE); // 使能 USART2,开启 USART2 的工作
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; // 设置中断通道为 USART2 中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 设置抢占优先级为 0,具有最高的抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 设置子优先级为 0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中断通道,允许 USART2 中断触发
NVIC_Init(&NVIC_InitStructure); // 初始化 NVIC,将上述中断配置应用到 NVIC 上
}
/*============= LED 初始化函数 =============*/
// 该函数用于初始化 LED 引脚,使其能够控制 LED 的亮灭
void LED_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure; // 定义 GPIO 初始化结构体变量,用于配置 GPIO 引脚的参数
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); // 使能 GPIOC 的时钟,为 LED 引脚的配置提供时钟源
GPIO_InitStructure.GPIO_Pin = LED_PIN; // 设置要配置的引脚为 LED_PIN
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 设置引脚模式为推挽输出,用于控制 LED 的亮灭
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置引脚速度为 50MHz,提高引脚的响应速度
GPIO_Init(LED_PORT, &GPIO_InitStructure); // 初始化 LED_PORT 的 LED_PIN 引脚
// GPIO_SetBits(LED_PORT, LED_PIN); // 置高 LED 引脚,点亮 LED
GPIO_ResetBits(LED_PORT, LED_PIN); // 置低 LED 引脚,点亮 LED
}
/*============= GPIO 配置函数 =============*/
// 该函数用于配置 GPIO 引脚,包括门状态检测引脚和门控制引脚
void GPIO_Configuration(void) {
GPIO_InitTypeDef GPIO_InitStructure; // 定义 GPIO 初始化结构体变量,用于配置 GPIO 引脚的参数
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能 GPIOA 的时钟,为 GPIO 引脚的配置提供时钟源
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 使能 GPIOB 的时钟,为 GPIO 引脚的配置提供时钟源
// 配置 PA4 和 PA5 为上拉输入,用于检测门状态
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5; // 设置要配置的引脚为 PA4 和 PA5
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 设置引脚模式为上拉输入,保证引脚在无信号输入时为高电平
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置引脚速度为 50MHz,提高引脚响应速度
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化 GPIOA 的 PA4 和 PA5 引脚
// 配置 PA6 和 PA7 为推挽输出,用于控制门
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; // 设置要配置的引脚为 PA6 和 PA7
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 设置引脚模式为推挽输出,可输出高、低电平来控制门
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置引脚速度为 50MHz,提高引脚响应速度
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化 GPIOA 的 PA6 和 PA7 引脚
// 配置 PB0 为推挽输出,用于拍照LED灯
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 ; // 设置要配置的引脚为 PB0
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 设置引脚模式为推挽输出,可输出高、低电平来控制门
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置引脚速度为 50MHz,提高引脚响应速度
GPIO_Init(GPIOB, &GPIO_InitStructure); // 初始化 GPIOB 的 PB0
}
// 简单的毫秒级延时函数
void Delay_ms(uint32_t ms) {
// 外层循环控制延时的毫秒数
for (uint32_t i = 0; i < ms ; i++) {
// 内层循环进行空操作,起到延时作用
for(uint32_t j = 110; j > 0; j--);
}
}
/*============= 定时器 2 初始化函数 =============*/
// 该函数用于初始化定时器 2,用于计时门打开的时间
void TIM2_Configuration(void) {
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; // 定义定时器时基初始化结构体变量,用于配置定时器的基本参数
NVIC_InitTypeDef NVIC_InitStructure; // 定义 NVIC 初始化结构体变量,用于配置定时器中断的优先级和使能
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 使能定时器 2 的时钟,为定时器的配置和工作提供时钟源
TIM_TimeBaseStructure.TIM_Period = 7199; // 设置定时器周期为 7199,结合预分频器,可确定定时器的定时时间
TIM_TimeBaseStructure.TIM_Prescaler = 999; // 设置定时器预分频器为 999,对时钟进行分频
TIM_TimeBaseStructure.TIM_ClockDivision = 0; // 设置时钟分割为 0,不进行时钟分割
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 设置计数器模式为向上计数,计数器从 0 开始递增
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); // 初始化定时器 2 的时基,将上述配置应用到定时器 2
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); // 使能定时器 2 的更新中断,当定时器计数器溢出时触发中断
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; // 设置中断通道为定时器 2 中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 设置抢占优先级为 1,具有一定的抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 设置子优先级为 1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中断通道,允许定时器 2 中断触发
NVIC_Init(&NVIC_InitStructure); // 初始化 NVIC,将上述中断配置应用到 NVIC 上
TIM_Cmd(TIM2, DISABLE); // 禁用定时器 2,在需要时再开启
}
/*============= 重定向标准输出函数printf =============*/
// 重定向 fputc 函数,使 printf 函数输出的数据通过 USART1 发送
int fputc(int ch, FILE *f) {
// 等待 USART1 发送缓冲区为空,确保可以发送新的数据
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET){
// 空循环体,等待发送缓冲区为空
}
USART_SendData(USART1, (uint8_t)ch);
// 等待 USART2 发送缓冲区为空,循环体为空是为了等待标志位变化
while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET){
// 空循环体,等待发送缓冲区为空
}
USART_SendData(USART2, (uint8_t)ch); // 发送字符到 USART1
return ch; // 返回发送的字符
}
/*============= 解析接收到的指令 =============*/
// 该函数用于解析接收到的指令,根据指令内容执行相应操作
void parse_command(uint8_t *rx_buffer, uint8_t *rx_index) {
int a_count = 0; // 记录接收到的指令中 'A' 的数量
int d_count = 0; // 记录接收到的指令中 'D' 的数量
// 遍历接收缓冲区中的数据
for (int i = 0; i < *rx_index; i++) {
// 如果当前字符为 'A',则 'A' 的数量加 1
if (rx_buffer[i] == 'A') {
a_count++;
}
// 如果当前字符为 'D',则 'D' 的数量加 1
if (rx_buffer[i] == 'D') {
d_count++;
}
}
// 检查 'A' 和 'D' 的数量是否都为 1
if (a_count != 1 || d_count != 1) {
printf("Error: There should be exactly one 'A' and one 'D' in the command.
"); // 若不满足条件,打印错误信息
*rx_index = 0; // 清空接收缓冲区索引,准备接收新数据
return;
}
// 检查指令的第一个字符是否为 'A',最后一个字符是否为 'D'
if (rx_buffer[0] != 'A' || rx_buffer[7] != 'D') {
printf("Error: The first character should be 'A' and the last character should be 'D'.
"); // 若不满足条件,打印错误信息
*rx_index = 0; // 清空接收缓冲区索引,准备接收新数据
return;
}
// 打印接收到的每个字符及其位置
for (int i = 0; i < *rx_index; i++) {
// printf("Received char at position %d: %c
", i, rx_buffer[i]);
printf("%c",rx_buffer[i]);
}
printf("
"); // 在循环外添加换行符,实现全部字符输出后换行
// 检查是否为打开门 1 的指令
if (rx_buffer[1] == '1' && rx_buffer[2] == '0' && rx_buffer[3] == '0' && rx_buffer[4] == '0' && rx_buffer[5] == '0' && rx_buffer[6] == '0') {
// 若门 1 处于关闭状态
if (!door1_state) {
GPIO_SetBits(GPIOA, GPIO_Pin_6); // 置高 PA6 引脚,打开门 1
door1_state = 1; // 更新门 1 的状态为打开
door1_open_start_time = timer_count; // 记录门 1 打开的起始时间
TIM_Cmd(TIM2, ENABLE); // 使能定时器 2,开始计时
need_check_door1 = 1; // 标记需要在延时后检查门 1 状态
}
}
// 检查是否为打开门 2 的指令
else if (rx_buffer[1] == '2' && rx_buffer[2] == '0' && rx_buffer[3] == '0' && rx_buffer[4] == '0' && rx_buffer[5] == '0' && rx_buffer[6] == '0') {
// 若门 2 处于关闭状态
if (!door2_state) {
GPIO_SetBits(GPIOA, GPIO_Pin_7); // 置高 PA7 引脚,打开门 2
door2_state = 1; // 更新门 2 的状态为打开
door2_open_start_time = timer_count; // 记录门 2 打开的起始时间
TIM_Cmd(TIM2, ENABLE); // 使能定时器 2,开始计时
need_check_door2 = 1; // 标记需要在延时后检查门 2 状态
}
}
// 打开LED灯 的指令
else if (rx_buffer[1] == '3' && rx_buffer[2] == '0' && rx_buffer[3] == '0' && rx_buffer[4] == '0' && rx_buffer[5] == '0' && rx_buffer[6] == '0') {
GPIO_SetBits(GPIOB, GPIO_Pin_0); // 置高 PB0 引脚,打开LED灯
printf("A300001D
"); //返回开灯数据
}
// 关闭LED灯 的指令
else if (rx_buffer[1] == '4' && rx_buffer[2] == '0' && rx_buffer[3] == '0' && rx_buffer[4] == '0' && rx_buffer[5] == '0' && rx_buffer[6] == '0') {
GPIO_ResetBits(GPIOB, GPIO_Pin_0); // 置高 PB0 引脚,打开LED灯
printf("A400001D
"); //返回关灯成功数据
}
// 获取硬件版本号的指令
else if (rx_buffer[1] == '5' && rx_buffer[2] == '0' && rx_buffer[3] == '0' && rx_buffer[4] == '0' && rx_buffer[5] == '0' && rx_buffer[6] == '0') {
read_version_from_flash(); // 获取版本号输出
}
// 修改硬件版本号的指令
else if (rx_buffer[1] == '6' && rx_buffer[2] == '0' && rx_buffer[3] == '0' ) {
uint8_t ver_1, ver_2, ver_3;
ver_1=rx_buffer[4]-48; //字符 转 数字(int)计算
ver_2=rx_buffer[5]-48; //字符 转 数字(int)计算
ver_3=rx_buffer[6]-48; //字符 转 数字(int)计算
set_firmware_version(ver_1, ver_2, ver_3) ; // 将计算出来的新版本号 进行设置
read_version_from_flash(); //输出新版本号
}
// 检查是否为查询门 1 状态的指令
else if (rx_buffer[1] == '1' && rx_buffer[2] == '1' && rx_buffer[3] == '0' && rx_buffer[4] == '0' && rx_buffer[5] == '0' && rx_buffer[6] == '0') {
// 读取门 1 的状态
if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_4) == 0) {
// 若门 1 打开,发送开门状态指令
printf("A100001D
");
printf("door1 Open
");
} else {
// 若门 1 关闭,发送关门状态指令
printf("A100002D
");
printf("door1 Close
");
}
}
// 检查是否为查询门 2 状态的指令
else if (rx_buffer[1] == '2' && rx_buffer[2] == '1' && rx_buffer[3] == '0' && rx_buffer[4] == '0' && rx_buffer[5] == '0' && rx_buffer[6] == '0') {
// 读取门 2 的状态
if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_5) == 0) {
// 若门 2 打开,发送开门状态指令
printf("A200001D
");
printf("door2 Open
");
} else {
// 若门 2 关闭,发送关门状态指令
printf("A200002D
");
printf("door2 Close
");
}
}
// 清空接收缓冲区索引,准备接收新数据
*rx_index = 0;
timeout_counter1 = 0;
timeout_counter2 = 0;
}
/*============= 发送门状态指令 =============*/
// 该函数用于根据门的编号和状态发送相应的状态指令
void send_door_status(uint8_t door_num, uint8_t status) {
// 若为门 1
if (door_num == 1) {
// 若门 1 打开
if (status == 0) {
// 发送门 1 打开的状态指令
printf("A100001D
");
} else {
// 发送门 1 关闭的状态指令
printf("A100002D
");
}
// 若为门 2
} else if (door_num == 2) {
// 若门 2 打开
if (status == 0) {
// 发送门 2 打开的状态指令
printf("A200001D
");
} else {
// 发送门 2 关闭的状态指令
printf("A200002D
");
}
}
}
/*============= 检查门状态并发送状态指令 =============*/
// 该函数用于实时监测门的状态变化,并在状态改变时发送状态指令
void check_door_status() {
static uint8_t last_door1_state = 0; // 静态变量,用于记录门 1 上一次的状态,初始值为 0
static uint8_t last_door2_state = 0; // 静态变量,用于记录门 2 上一次的状态,初始值为 0
uint8_t current_door1_state = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_4); // 读取门 1 当前的状态
uint8_t current_door2_state = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_5); // 读取门 2 当前的状态
// 若门 1 当前状态与上一次记录的状态不同
if (current_door1_state != last_door1_state) {
send_door_status(1, current_door1_state); // 发送门 1 的当前状态指令
last_door1_state = current_door1_state; // 更新门 1 上一次的状态为当前状态
}
// 若门 2 当前状态与上一次记录的状态不同
if (current_door2_state != last_door2_state) {
// 发送门 2 的当前状态指令
send_door_status(2, current_door2_state);
// 更新门 2 上一次的状态为当前状态
last_door2_state = current_door2_state;
}
}
/*============= SysTick 中断处理函数 =============*/
// SysTick 定时器中断处理函数,用于处理接收数据的超时情况
void SysTick_Handler(void) {
// 若 USART1 接收缓冲区已经接收到了数据
if (rx_index1 > 0) {
timeout_counter1++; // 超时计数器加 1
// 若超时计数器的值超过了预设的超时值
if (timeout_counter1 >= TIMEOUT_VALUE) {
printf("Error: USART1 Timeout, received data length is less than 8 bytes. Clearing buffer and restarting.
");// 打印错误信息,提示接收数据超时且长度不足 8 字节
rx_index1 = 0; // 清空接收缓冲区的索引,准备重新接收数据
timeout_counter1 = 0; // 重置超时计数器
}
}
// 若 USART2 接收缓冲区已经接收到了数据
if (rx_index2 > 0) {
timeout_counter2++; // 超时计数器加 1
// 若超时计数器的值超过了预设的超时值
if (timeout_counter2 >= TIMEOUT_VALUE) {
printf("Error: USART2 Timeout, received data length is less than 8 bytes. Clearing buffer and restarting.
");// 打印错误信息,提示接收数据超时且长度不足 8 字节
rx_index2 = 0; // 清空接收缓冲区的索引,准备重新接收数据
timeout_counter2 = 0; // 重置超时计数器
}
}
}
/*============= 定时器 2 中断服务函数 =============*/
// 定时器 2 中断服务函数,用于处理门打开时间的计时和关闭操作
void TIM2_IRQHandler(void) {
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { // 检查定时器 2 的更新中断标志位是否被置位
timer_count++; // 定时器计数变量加 1,用于记录时间的流逝
// 若门 1 处于打开状态
if (door1_state) {
if ((timer_count - door1_open_start_time) >= 10) { // 计算门 1 打开的时长,判断是否超过了 10 个计时单位 1S
GPIO_ResetBits(GPIOA, GPIO_Pin_6); // 拉低 GPIOA 的引脚 6,关闭门 1
door1_state = 0; // 标记门 1 为关闭状态
}
}
// 若门 2 处于打开状态
if (door2_state) {
if ((timer_count - door2_open_start_time) >= 10) { // 计算门 2 打开的时长,判断是否超过了 10 个计时单位 1S
GPIO_ResetBits(GPIOA, GPIO_Pin_7); // 拉低 GPIOA 的引脚 7,关闭门 2
door2_state = 0; // 标记门 2 为关闭状态
}
}
// 检查是否需要在延时后检查门 1 状态
if (need_check_door1 && (timer_count - door1_open_start_time) >= 10) {
// 读取门 1 的状态
if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_4) == 0) {
// 若门 1 打开,发送开门状态指令
printf("A100001D
");
printf("door1 Open
");
}
else {
// 若门 1 关闭,发送关门状态指令
printf("A100002D
");
printf("door1 Close
");
}
need_check_door1 = 0; // 重置标记,表明已完成检查
TIM_Cmd(TIM2, DISABLE); // 禁用定时器 2,停止计时
}
// 检查是否需要在延时后检查门 2 状态
if (need_check_door2 && (timer_count - door2_open_start_time) >= 10) {
// 读取门 2 的状态
if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_5) == 0) {
// 若门 2 打开,发送开门状态指令
printf("A200001D
");
printf("door2 Open
");
} else {
// 若门 2 关闭,发送关门状态指令
printf("A200002D
");
printf("door2 Close
");
}
need_check_door2 = 0; // 重置标记,表明已完成检查
TIM_Cmd(TIM2, DISABLE); // 禁用定时器 2,停止计时
}
// 如果门 1 和门 2 都处于关闭状态
if (!door1_state && !door2_state) {
TIM_Cmd(TIM2, DISABLE); // 禁用定时器 2,停止计时
}
TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 清除定时器 2 的更新中断标志位,避免重复触发中断
}
}
/*============= USART1 中断处理函数 =============*/
// USART1 中断处理函数,用于处理 USART1 接收到的数据
void USART1_IRQHandler(void) {
if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { // 检查 USART1 的接收缓冲区非空中断标志位是否被置位
// 如果该标志位被置位,说明接收缓冲区中有新的数据可供读取
uint8_t data = USART_ReceiveData(USART1); // 从 USART1 接收一个字节的数据,并将其存储在变量 data 中
timeout_counter1 = 0; // 重置超时计数器,因为接收到了新的数据
// 超时计数器用于检测数据接收是否超时
if (rx_index1 < RX_BUFFER_SIZE) { // 如果接收缓冲区还没有满(索引小于 RX_BUFFER_SIZE)
rx_buffer1[rx_index1++] = data; // 将接收到的数据存入接收缓冲区,并将索引加 1
if (rx_index1 == 8) { // 如果接收缓冲区已满(索引达到 8)
if (rx_buffer1[7] == 'D') { // 检查接收到的最后一个字符是否为 'D'
parse_command(rx_buffer1, &rx_index1); // 调用解析命令的函数,对接收到的指令进行解析
// 传入接收缓冲区和索引的指针
rx_index1 = 0; // 重置索引,准备接收下一个命令
while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET) { // 跳过多余的数据,直到遇到下一个 'A'
// 避免后续数据被误当作新命令的起始
data = USART_ReceiveData(USART1); // 从 USART1 接收一个字节的数据
// 如果接收到的数据是 'A'
if (data == 'A') {
rx_buffer1[rx_index1++] = data; // 将 'A' 存入接收缓冲区,并将索引加 1
break; // 跳出循环,开始接收新的命令
}
}
} else { // 如果最后一个字符不是 'D',说明接收到的数据不是有效的命令
rx_index1 = 0; // 重置索引,准备接收新的命令
// 跳过多余的数据,直到遇到下一个 'A'
while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET) {
data = USART_ReceiveData(USART1); // 从 USART1 接收一个字节的数据
if (data == 'A') {
rx_buffer1[rx_index1++] = data; // 将 'A' 存入接收缓冲区,并将索引加 1
break; // 跳出循环,开始接收新的命令
}
}
}
}
}
else {
// 打印错误信息,提示接收到的数据长度超过 8 字节
// 丢弃整个数据,等待新的数据
printf("Error: USART1 The received data length exceeds 8 bytes. Discarding the entire data and waiting for new data.
");
rx_index1 = 0; // 清空接收缓冲区的索引,准备重新接收数据
timeout_counter1 = 0; // 重置超时计数器
while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET) { // 跳过多余的数据,直到遇到下一个 'A'
// 避免后续数据被误当作新命令的起始
data = USART_ReceiveData(USART1); // 从 USART1 接收一个字节的数据
// 如果接收到的数据是 'A'
if (data == 'A') {
rx_buffer1[rx_index1++] = data; // 将 'A' 存入接收缓冲区,并将索引加 1
break; // 跳出循环,开始接收新的命令
}
}
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE); // 清除 USART1 的接收缓冲区非空中断标志位
// 避免重复触发中断
}
}
// 定义 USART2 的中断处理函数,当 USART2 发生中断时会自动调用该函数
/*============= USART2 中断处理函数 =============*/
// USART2 中断处理函数,用于处理 USART2 接收到的数据
void USART2_IRQHandler(void) {
if (USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) { // 检查 USART2 的接收缓冲区非空中断标志位是否被置位
// 如果该标志位被置位,说明接收缓冲区中有新的数据可供读取
uint8_t data = USART_ReceiveData(USART2); // 从 USART2 接收一个字节的数据,并将其存储在变量 data 中
timeout_counter2 = 0; // 重置超时计数器,因为接收到了新的数据
// 超时计数器用于检测数据接收是否超时
if (rx_index2 < RX_BUFFER_SIZE) { // 如果接收缓冲区还没有满(索引小于 RX_BUFFER_SIZE)
rx_buffer2[rx_index2++] = data; // 将接收到的数据存入接收缓冲区,并将索引加 1
if (rx_index2 == 8) { // 如果接收缓冲区已满(索引达到 8)
if (rx_buffer2[7] == 'D') { // 检查接收到的最后一个字符是否为 'D'
parse_command(rx_buffer2, &rx_index2); // 调用解析命令的函数,对接收到的指令进行解析
// 传入接收缓冲区和索引的指针
rx_index2 = 0; // 重置索引,准备接收下一个命令
while (USART_GetFlagStatus(USART2, USART_FLAG_RXNE) == SET) { // 跳过多余的数据,直到遇到下一个 'A'
// 避免后续数据被误当作新命令的起始
data = USART_ReceiveData(USART2); // 从 USART2 接收一个字节的数据
// 如果接收到的数据是 'A'
if (data == 'A') {
rx_buffer2[rx_index2++] = data; // 将 'A' 存入接收缓冲区,并将索引加 1
break; // 跳出循环,开始接收新的命令
}
}
} else {
// 如果最后一个字符不是 'D',说明接收到的数据不是有效的命令
// 重置索引,准备接收新的命令
rx_index2 = 0;
while (USART_GetFlagStatus(USART2, USART_FLAG_RXNE) == SET) { // 跳过多余的数据,直到遇到下一个 'A'
data = USART_ReceiveData(USART2);
if (data == 'A') {
rx_buffer2[rx_index2++] = data; // 将 'A' 存入接收缓冲区,并将索引加 1
break;
}
}
}
}
}
else {
// 打印错误信息,提示接收到的数据长度超过 8 字节
// 丢弃整个数据,等待新的数据
printf("Error: USART2 The received data length exceeds 8 bytes. Discarding the entire data and waiting for new data.
");
rx_index2 = 0; // 清空接收缓冲区的索引,准备重新接收数据
timeout_counter2 = 0; // 重置超时计数器
while (USART_GetFlagStatus(USART2, USART_FLAG_RXNE) == SET) { // 跳过多余的数据,直到遇到下一个 'A'
// 避免后续数据被误当作新命令的起始
data = USART_ReceiveData(USART2); // 从 USART2 接收一个字节的数据
// 如果接收到的数据是 'A'
if (data == 'A') {
rx_buffer2[rx_index2++] = data; // 将 'A' 存入接收缓冲区,并将索引加 1
break; // 跳出循环,开始接收新的命令
}
}
}
// 清除 USART2 的接收缓冲区非空中断标志位
// 避免重复触发中断
USART_ClearITPendingBit(USART2, USART_IT_RXNE);
}
}
/*============= 主函数,程序的入口点 =============*/
int main(void) {
SystemClock_Config(); // 配置系统时钟,使系统工作在合适的时钟频率下
SysTick_Config(SystemCoreClock / 1000); // 配置 SysTick 定时器,设置其定时周期为 1ms
USART1_Init(); // 初始化 USART1 串口,用于数据的收发
USART2_Init(); // 初始化 USART2 串口,用于数据的收发
LED_Init(); // 初始化 LED 引脚,用于控制 LED 的亮灭
GPIO_Configuration(); // 配置 GPIO 引脚,包括门状态检测引脚和门控制引脚
TIM2_Configuration(); // 初始化定时器 2,用于计时门的打开时间
//print_firmware_version(); // 初始化版本号输出
read_version_from_flash();
// 进入无限循环,程序将在此循环中不断运行
while (1) {
// 调用检查门状态的函数,实时监测门的状态变化并发送状态信息
// check_door_status();
}
}
version.h 代码
#ifndef _VERSION_H_
#define _VERSION_H_
#include <stdint.h> // 必须包含,声明 uint8_t 类型
/*============= 定义 版本号 =============*/
// 定义存储版本号的Flash地址
/*============= 定义 版本号 地址 =============*/
/****************************************
****************************************/
#define VERSION_STORAGE_ADDRESS 0x08007800
// 定义全局变量来存储版本号
extern uint8_t FIRMWARE_VERSION_MAJOR; // 定义固件版本号的主版本号,主版本号通常在有重大功能更新、架构改变时递增
extern uint8_t FIRMWARE_VERSION_MINOR; // 定义固件版本号的次版本号,次版本号一般在有新功能添加但不影响整体架构时递增
extern uint8_t FIRMWARE_VERSION_PATCH; // 定义固件版本号的修订版本号,修订版本号通常用于修复一些小的bug、进行小的优化时递增
// 函数声明
char* get_firmware_version(void);//版本号存储
void print_firmware_version(void); //获取版本号
// 函数声明,set_firmware_version 函数用于修改固件的版本号
// 参数分别为新的主版本号、次版本号和修订版本号
void set_firmware_version(uint8_t major, uint8_t minor, uint8_t patch);
void read_version_from_flash(void); // read_version_from_flash 函数用于从Flash中读取之前存储的版本号
void write_version_to_flash(void); // write_version_to_flash 函数用于将当前的版本号写入到Flash中进行保存
#endif
version.c 代码
#include <stdio.h>
#include <string.h>
// 包含STM32F10x系列的标准外设库头文件,提供了对STM32F103C8T6芯片外设的操作函数和定义
#include "stm32f10x.h"
#include "stm32f10x_flash.h" // 添加此头文件,引入 Flash 操作函数声明
#include "version.h"
// 初始定义全局变量来存储版本号
uint8_t FIRMWARE_VERSION_MAJOR = 1;
uint8_t FIRMWARE_VERSION_MINOR = 0;
uint8_t FIRMWARE_VERSION_PATCH = 0;
// 返回固件版本号字符串的函数
char* get_firmware_version(void) {
static char version_str[20]; // 定义一个静态字符数组来存储格式化后的版本号字符串
// 将版本号格式化到字符串数组 version_str 中
// 格式为 "Firmware Version: X.X.X",其中 X 分别代表主版本号、次版本号和修订版本号
sprintf(version_str, "Firmware Version: %d.%d.%d", FIRMWARE_VERSION_MAJOR, FIRMWARE_VERSION_MINOR, FIRMWARE_VERSION_PATCH);
return version_str;
}
// 获取并打印版本号的函数
void print_firmware_version() {
char* version = get_firmware_version(); // 调用 get_firmware_version 函数获取版本号字符串
printf("%s
", version); // 使用 printf 函数将版本号字符串打印到控制台
}
// 将版本号写入Flash的函数
void write_version_to_flash(void) {
FLASH_Unlock(); // 解锁Flash,因为在对Flash进行写入或擦除操作前需要先解锁
FLASH_ErasePage(VERSION_STORAGE_ADDRESS); // 擦除扇区,由于Flash写入操作需要先擦除对应扇区,这里使用 FLASH_ErasePage 函数擦除存储版本号的扇区
uint32_t data = (FIRMWARE_VERSION_MAJOR << 16) | (FIRMWARE_VERSION_MINOR << 8) | FIRMWARE_VERSION_PATCH; // 将当前的版本号组合成一个32位的数据
// 主版本号左移16位,次版本号左移8位,修订版本号保持不变,然后通过按位或运算组合在一起
FLASH_ProgramWord(VERSION_STORAGE_ADDRESS, data); // 使用 FLASH_ProgramWord 函数将组合好的32位数据写入到指定的Flash地址
FLASH_Lock(); // 锁定Flash,写入操作完成后需要锁定Flash,防止意外的写入或擦除操作
}
// 从Flash读取版本号的函数
void read_version_from_flash(void) {
uint32_t *addr = (uint32_t *)VERSION_STORAGE_ADDRESS; // 将存储版本号的Flash地址强制转换为指向uint32_t类型的指针
FIRMWARE_VERSION_MAJOR = (*addr & 0xFF0000) >> 16; // 从Flash地址中读取32位数据,并通过位运算提取出主版本号
FIRMWARE_VERSION_MINOR = (*addr & 0xFF00) >> 8; // 从Flash地址中读取32位数据,并通过位运算提取出次版本号
FIRMWARE_VERSION_PATCH = *addr & 0xFF; // 从Flash地址中读取32位数据,并通过位运算提取出修订版本号
print_firmware_version(); // 调用 print_firmware_version 函数打印读取到的版本号
// 简单校验,如果读取的值不合理(超过了uint8_t类型的范围),使用默认值
if (FIRMWARE_VERSION_MAJOR > 255 || FIRMWARE_VERSION_MINOR > 255 || FIRMWARE_VERSION_PATCH > 255) {
FIRMWARE_VERSION_MAJOR = 1;
FIRMWARE_VERSION_MINOR = 0;
FIRMWARE_VERSION_PATCH = 0;
print_firmware_version(); // 再次调用 print_firmware_version 函数打印默认版本号
}
}
// 实现 set_firmware_version 函数
// 该函数用于修改固件的版本号
void set_firmware_version(uint8_t major, uint8_t minor, uint8_t patch) {
FIRMWARE_VERSION_MAJOR = major; // 将传入的主版本号赋值给全局变量 FIRMWARE_VERSION_MAJOR
FIRMWARE_VERSION_MINOR = minor; // 将传入的次版本号赋值给全局变量 FIRMWARE_VERSION_MINOR
FIRMWARE_VERSION_PATCH = patch; // 将传入的修订版本号赋值给全局变量 FIRMWARE_VERSION_PATCH
// 调用 write_version_to_flash 函数,将修改后的版本号写入Flash进行保存
write_version_to_flash(); //修改后的新版本号写入STM32的flash 中
}
五、代码编译—正常

六、功能实现
这里采用的实际开发的一个简易的开发板,实现SMT32 控制,有兴趣的可以私信获取
