文章目录
- 一、前言
- 二、项目功能简介
- 三、PCB设计
- 四、原理图设计
- 五、程序设计
- 六、包含内容
- 七、资料分享
一、前言
实物项目成品
仿真项目成品:
哔哩哔哩开源视频链接(复制浏览器打开): https://www.bilibili.com/video/BV1i5hG6gEhM/ (资料分享见文末)
二、项目功能简介
功能介绍: (1)通过OLED屏幕显示电动车行驶的里程、速度、时间、温度 (2)可以通过独立按照设置温度阈值、速度阈值 (3)按键可以控制电动车的启停、手动按键清除行驶里程和时长 (4)通过TB6612驱动TT电机带动测速盘进行转动,可以通过按键调节电动车转速(PWM) (5)霍尔测速传感器监测TT电机的转速换算成里程以及速度,当速度超出设定阈值时,蜂鸣器进行声光报警 (6)使用DS18B20温度传感器采集环境温度,显示到OLED屏幕上 (7)使用DS1302时钟模块,在OLED屏幕上显示实时时间,电动车行驶运行时长 (8)使用蓝牙 APP显示OLED显示的内容、远程调节转速、时间校准以及阈值修改等
三、PCB设计

四、原理图设计

五、程序设计
部分代码如下:
#include "sys.h" // 系统核心头文件
#include <stdio.h> // 标准输入输出库
#include <stdlib.h> // 标准库
#include <string.h> // 字符串处理库
#include "delay.h" // 延时函数库
#include "gpio.h" // GPIO通用端口库
#include "key.h" // 按键驱动库
#include "oled.h" // OLED显示屏驱动库
#include "usart.h" // 串口通信库
#include "ds18b20.h" // 温度传感器DS18B20驱动
#include "DS1302.h" // 实时时钟DS1302驱动
#include "timer.h" // 定时器驱动库
/************************* 宏定义区 *************************/
// 车轮直径(mm)
#define WHEEL_DIAMETER_MM 26.0f
// 车轮周长(mm) = π*直径
#define WHEEL_CIRCUMFERENCE_MM ((float)(3.1415926f * WHEEL_DIAMETER_MM))
// 车辆等效缩放比例
#define VEHICLE_EQUIV_SCALE 10.0f
// 车辆等效周长(mm)
#define VEHICLE_CIRCUMFERENCE_MM (WHEEL_CIRCUMFERENCE_MM * VEHICLE_EQUIV_SCALE)
// 霍尔传感器每转脉冲数
#define HALL_PULSE_PER_REV 1U
// 电机PWM最大值(占空比100%)
#define MOTOR_PWM_MAX 100U
// PWM调节步长
#define MOTOR_PWM_STEP 5U
// 速度阈值调节步长(×10)
#define SPEED_TH_STEP_X10 5U
// 温度阈值调节步长(×10)
#define TEMP_TH_STEP_X10 5U
// 霍尔信号消抖时间(ms)
#define HALL_DEBOUNCE_MS 8U
// 速度检测超时时间(ms),超时判定静止
#define SPEED_TIMEOUT_MS 1500U
// 周期滤波缓冲区大小
#define PERIOD_FILTER_SIZE 6U
/************************* 枚举定义 *************************/
// 页面枚举:定义系统所有显示界面
typedef enum
{
PAGE_MAIN = 0, // 主页面
PAGE_SET_SPEED, // 设置速度阈值页面
PAGE_SET_TEMP, // 设置温度阈值页面
PAGE_SET_PWM, // 设置电机PWM页面
PAGE_SET_YEAR, // 设置年份页面
PAGE_SET_MONTH, // 设置月份页面
PAGE_SET_DAY, // 设置日期页面
PAGE_SET_HOUR, // 设置小时页面
PAGE_SET_MIN, // 设置分钟页面
PAGE_SET_SEC, // 设置秒页面
PAGE_COUNT // 页面总数
} PageTypeDef;
/************************* 全局变量定义 *************************/
// 霍尔传感器相关变量
static volatile uint32_t g_hall_total_pulse = 0; // 霍尔总脉冲计数
static volatile uint32_t g_hall_last_tick_ms = 0; // 上一次脉冲时间(ms)
static volatile uint32_t g_hall_period_ms = 0; // 滤波后脉冲周期(ms)
static volatile uint32_t g_hall_period_buf[PERIOD_FILTER_SIZE] = {0}; // 周期滤波缓冲区
static volatile uint8_t g_hall_period_index = 0; // 缓冲区索引
static volatile uint8_t g_hall_period_count = 0; // 有效周期数据个数
// 系统状态变量
static PageTypeDef g_page = PAGE_MAIN; // 当前显示页面
static uint8_t g_motor_pwm = 40; // 电机PWM占空比(默认40%)
static uint8_t g_motor_run = 1; // 电机运行状态(1运行0停止)
static uint16_t g_speed_threshold_x10 = 150; // 速度报警阈值(×10,15.0km/h)
static uint16_t g_temp_threshold_x10 = 350; // 温度报警阈值(×10,35.0℃)
static uint16_t g_speed_x10 = 0; // 实时速度(×10)
static uint16_t g_speed_filtered_x10 = 0; // 滤波后速度(×10)
static uint32_t g_distance_mm = 0; // 行驶总距离(mm)
static uint32_t g_drive_seconds = 0; // 行驶总时长(s)
static short g_temperature_x10 = 0; // 温度值(×10)
static uint8_t g_alarm_on = 0; // 报警状态(1报警0关闭)
static uint8_t g_refresh_display = 1; // 显示屏刷新标志(1刷新)
static uint8_t g_ds18b20_ok = 0; // DS18B20初始化状态(1成功0失败)
static uint8_t g_is_moving = 0; // 移动状态(1运动0静止)
static uint8_t g_time_edit_loaded = 0; // 时间编辑加载标志
static uint8_t g_set_year = 26; // 设置的年份
static uint8_t g_set_month = 1; // 设置的月份
static uint8_t g_set_day = 1; // 设置的日期
static uint8_t g_set_hour = 0; // 设置的小时
static uint8_t g_set_min = 0; // 设置的分钟
static uint8_t g_set_sec = 0; // 设置的秒
// 外部变量声明
extern uint8_t usart1_buf[100]; // 串口1接收缓冲区
extern volatile uint8_t g_flag_100ms; // 100ms定时标志
extern volatile uint8_t g_flag_1s; // 1s定时标志
extern volatile uint32_t g_tick_ms; // 系统毫秒计数器
/************************* 函数声明 *************************/
static void HallSensor_Init(void); // 霍尔传感器初始化
static void Motor_Init(void); // 电机初始化
static void Motor_SetDuty(uint8_t duty); // 设置电机PWM占空比
static void Motor_SetRun(uint8_t run); // 设置电机启停
static void Alarm_Set(uint8_t on); // 设置报警开关
static void App_Init(void); // 应用层初始化
static void Key_Task(void); // 按键处理任务
static void Sensor_Task_1s(void); // 传感器1秒任务
static void Alarm_Task(void); // 报警检测任务
static void Display_Task(void); // 显示任务
static void Bluetooth_Task(void); // 蓝牙处理任务
static void Bluetooth_SendStatus(void); // 蓝牙发送状态信息
static void ParseBluetoothCommand(char *cmd); // 解析蓝牙指令
static uint16_t ParseIntValue(const char *src, const char *key, uint16_t current, uint16_t min_val, uint16_t max_val); // 解析整数参数
static uint16_t ParseFixed1Value(const char *src, const char *key, uint16_t current, uint16_t min_val, uint16_t max_val); // 解析小数参数(×10)
static void ShowLine(uint8_t row, const char *text); // OLED显示一行文字
static void ClampAndApply(void); // 限制参数范围并应用
static void TimeEdit_LoadCurrent(void); // 加载当前时间到编辑变量
static void TimeEdit_Save(void); // 保存设置的时间到DS1302
/************************* 主函数 *************************/
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 设置中断分组2
Delay_Init(); // 延时初始化
Gpio_Init(); // GPIO初始化
Key_Init(); // 按键初始化
Oled_Init(); // OLED初始化
Oled_Clear_All(); // 清屏
Usart1_Init(9600); // 串口1初始化(波特率9600)
TIM3_Init(999, 71); // 定时器3初始化(用于定时中断)
App_Init(); // 应用初始化
Display_Task(); // 首次刷新显示
// 主循环
while (1)
{
Key_Task(); // 按键扫描处理
Bluetooth_Task(); // 蓝牙数据处理
// 100ms执行一次显示刷新
if (g_flag_100ms)
{
g_flag_100ms = 0;
Display_Task();
}
// 1s执行一次传感器、报警、蓝牙发送
if (g_flag_1s)
{
g_flag_1s = 0;
Sensor_Task_1s();
Alarm_Task();
g_refresh_display = 1;
Bluetooth_SendStatus();
}
}
}
/************************* 应用初始化函数 *************************/
static void App_Init(void)
{
g_ds18b20_ok = (DS18B20_Init() == 0); // 初始化DS18B20,保存状态
DS1302_init(26, 5, 8, 12, 0, 0); // 初始化DS1302时间(2026-05-08 12:00:00)
DS1302_Readtime(); // 读取DS1302时间
HallSensor_Init(); // 霍尔传感器初始化
Motor_Init(); // 电机初始化
Motor_SetRun(g_motor_run); // 设置电机初始状态
Motor_SetDuty(g_motor_pwm); // 设置电机初始PWM
Alarm_Set(0); // 关闭报警
Sensor_Task_1s(); // 执行一次传感器采集
}
/************************* 霍尔传感器初始化 *************************/
static void HallSensor_Init(void)
{
GPIO_InitTypeDef gpio_init;
EXTI_InitTypeDef exti_init;
NVIC_InitTypeDef nvic_init;
// 使能GPIOA和复用功能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
// 配置PA0为上拉输入
gpio_init.GPIO_Pin = GPIO_Pin_0;
gpio_init.GPIO_Mode = GPIO_Mode_IPU;
gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio_init);
// 配置PA0为外部中断线0
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
// 配置EXTI0:下降沿触发中断
exti_init.EXTI_Line = EXTI_Line0;
exti_init.EXTI_Mode = EXTI_Mode_Interrupt;
exti_init.EXTI_Trigger = EXTI_Trigger_Falling;
exti_init.EXTI_LineCmd = ENABLE;
EXTI_Init(&exti_init);
// 配置中断优先级
nvic_init.NVIC_IRQChannel = EXTI0_IRQn;
nvic_init.NVIC_IRQChannelPreemptionPriority = 1;
nvic_init.NVIC_IRQChannelSubPriority = 0;
nvic_init.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvic_init);
}
/************************* 电机驱动初始化 *************************/
static void Motor_Init(void)
{
GPIO_InitTypeDef gpio_init;
TIM_TimeBaseInitTypeDef tim_base;
TIM_OCInitTypeDef tim_oc;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 使能TIM2时钟
// 配置PA4/PA5为推挽输出(电机方向控制)
gpio_init.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
gpio_init.GPIO_Mode = GPIO_Mode_Out_PP;
gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio_init);
// 配置PA2为复用推挽输出(TIM2_CH3 PWM输出)
gpio_init.GPIO_Pin = GPIO_Pin_2;
gpio_init.GPIO_Mode = GPIO_Mode_AF_PP;
gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio_init);
// TIM2基本配置:自动重装载值100,预分频71
tim_base.TIM_Prescaler = 71;
tim_base.TIM_Period = MOTOR_PWM_MAX;
tim_base.TIM_ClockDivision = TIM_CKD_DIV1;
tim_base.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &tim_base);
// TIM2_CH3 PWM配置
tim_oc.TIM_OCMode = TIM_OCMode_PWM1;
tim_oc.TIM_OutputState = TIM_OutputState_Enable;
tim_oc.TIM_OCPolarity = TIM_OCPolarity_High;
tim_oc.TIM_Pulse = 0;
TIM_OC3Init(TIM2, &tim_oc);
TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Enable); // 使能预装载
TIM_ARRPreloadConfig(TIM2, ENABLE); // 使能自动重装载
TIM_Cmd(TIM2, ENABLE); // 启动TIM2
}
/************************* 设置电机PWM占空比 *************************/
static void Motor_SetDuty(uint8_t duty)
{
if (duty > MOTOR_PWM_MAX) // 限制最大值
{
duty = MOTOR_PWM_MAX;
}
g_motor_pwm = duty;
// 电机运行时输出PWM,停止时输出0
TIM_SetCompare3(TIM2, g_motor_run ? g_motor_pwm : 0);
}
/************************* 设置电机启停 *************************/
static void Motor_SetRun(uint8_t run)
{
g_motor_run = run ? 1 : 0;
PAout(4) = g_motor_run; // 控制电机使能引脚
PAout(5) = 0; // 方向固定
// 设置PWM输出
TIM_SetCompare3(TIM2, g_motor_run ? g_motor_pwm : 0);
g_refresh_display = 1; // 刷新显示
}
/************************* 控制报警蜂鸣器 *************************/
static void Alarm_Set(uint8_t on)
{
g_alarm_on = on ? 1 : 0;
ALARM = g_alarm_on; // 控制报警IO口
}
/************************* 按键处理任务(核心) *************************/
static void Key_Task(void)
{
uint8_t key = Chiclet_Keyboard_Scan(0); // 扫描按键
if (key == 0) // 无按键按下
{
return;
}
// 根据按键值和当前页面处理
switch (key)
{
case 1: // 按键1:切换页面
// 时间设置页面:切换下一项
if ((g_page >= PAGE_SET_YEAR) && (g_page <= PAGE_SET_SEC))
{
if (g_page == PAGE_SET_SEC)
{
g_page = PAGE_MAIN;
g_time_edit_loaded = 0;
}
else
{
g_page = (PageTypeDef)(g_page + 1);
}
}
else // 普通设置页面:循环切换
{
if (g_page == PAGE_MAIN)
g_page = PAGE_SET_SPEED;
else if (g_page == PAGE_SET_SPEED)
g_page = PAGE_SET_TEMP;
else if (g_page == PAGE_SET_TEMP)
g_page = PAGE_SET_PWM;
else if (g_page == PAGE_SET_PWM)
{
TimeEdit_LoadCurrent();
g_page = PAGE_SET_YEAR;
}
else
g_page = PAGE_MAIN;
}
// 时间设置页面自动加载当前时间
if ((g_page >= PAGE_SET_YEAR) && (g_page <= PAGE_SET_SEC) && (g_time_edit_loaded == 0))
{
TimeEdit_LoadCurrent();
}
Oled_Clear_All(); // 清屏
break;
case 2: // 按键2:数值减
if (g_page == PAGE_MAIN)
{
g_hall_total_pulse = 0; // 清零里程
g_distance_mm = 0;
}
else if (g_page == PAGE_SET_PWM)
{
// PWM值递减
if (g_motor_pwm >= MOTOR_PWM_STEP)
g_motor_pwm -= MOTOR_PWM_STEP;
else
g_motor_pwm = 0;
Motor_SetDuty(g_motor_pwm);
}
else if (g_page == PAGE_SET_SPEED)
{
// 速度阈值递减
if (g_speed_threshold_x10 > SPEED_TH_STEP_X10)
g_speed_threshold_x10 -= SPEED_TH_STEP_X10;
else
g_speed_threshold_x10 = 0;
}
else if (g_page == PAGE_SET_TEMP)
{
// 温度阈值递减
if (g_temp_threshold_x10 > TEMP_TH_STEP_X10)
g_temp_threshold_x10 -= TEMP_TH_STEP_X10;
else
g_temp_threshold_x10 = 0;
}
// 时间设置项递减
else if (g_page == PAGE_SET_YEAR) {if (g_set_year > 0) g_set_year—;}
else if (g_page == PAGE_SET_MONTH) {if (g_set_month > 1) g_set_month—;}
else if (g_page == PAGE_SET_DAY) {if (g_set_day > 1) g_set_day—;}
else if (g_page == PAGE_SET_HOUR) {if (g_set_hour > 0) g_set_hour—;}
else if (g_page == PAGE_SET_MIN) {if (g_set_min > 0) g_set_min—;}
else if (g_page == PAGE_SET_SEC) {if (g_set_sec > 0) g_set_sec—;}
break;
case 3: // 按键3:数值加
if (g_page == PAGE_MAIN)
{
g_drive_seconds = 0; // 清零行驶时间
}
else if (g_page == PAGE_SET_PWM)
{
// PWM值递增
if (g_motor_pwm + MOTOR_PWM_STEP <= MOTOR_PWM_MAX)
g_motor_pwm += MOTOR_PWM_STEP;
else
g_motor_pwm = MOTOR_PWM_MAX;
Motor_SetDuty(g_motor_pwm);
}
else if (g_page == PAGE_SET_SPEED)
{
// 速度阈值递增
if (g_speed_threshold_x10 < 9995)
g_speed_threshold_x10 += SPEED_TH_STEP_X10;
}
else if (g_page == PAGE_SET_TEMP)
{
// 温度阈值递增
if (g_temp_threshold_x10 < 995)
g_temp_threshold_x10 += TEMP_TH_STEP_X10;
}
// 时间设置项递增
else if (g_page == PAGE_SET_YEAR) {if (g_set_year < 99) g_set_year++;}
else if (g_page == PAGE_SET_MONTH) {if (g_set_month < 12) g_set_month++;}
else if (g_page == PAGE_SET_DAY) {if (g_set_day < 31) g_set_day++;}
else if (g_page == PAGE_SET_HOUR) {if (g_set_hour < 23) g_set_hour++;}
else if (g_page == PAGE_SET_MIN) {if (g_set_min < 59) g_set_min++;}
else if (g_page == PAGE_SET_SEC) {if (g_set_sec < 59) g_set_sec++;}
break;
case 4: // 按键4:返回/启停/保存
if (g_page == PAGE_MAIN)
{
Motor_SetRun(!g_motor_run); // 电机启停切换
}
else if ((g_page >= PAGE_SET_YEAR) && (g_page <= PAGE_SET_SEC))
{
TimeEdit_Save(); // 保存时间
g_time_edit_loaded = 0;
g_page = PAGE_MAIN; // 返回主页面
Oled_Clear_All();
}
else
{
g_page = PAGE_MAIN; // 返回主页面
g_time_edit_loaded = 0;
Oled_Clear_All();
}
break;
default:
break;
}
ClampAndApply(); // 限制参数范围
Alarm_Task(); // 检测报警
g_refresh_display = 1; // 刷新显示
}
/************************* 1秒执行一次:传感器采集、速度计算、时间更新 *************************/
static void Sensor_Task_1s(void)
{
uint32_t now_ms;
uint32_t period_ms;
float speed_mm_s;
now_ms = g_tick_ms;
// 计算总行驶距离:脉冲数×等效周长/每转脉冲数
g_distance_mm = (uint32_t)((g_hall_total_pulse * VEHICLE_CIRCUMFERENCE_MM) / HALL_PULSE_PER_REV);
// 速度超时判定:静止
if ((g_hall_last_tick_ms == 0) || ((now_ms – g_hall_last_tick_ms) > SPEED_TIMEOUT_MS))
{
g_speed_x10 = 0;
g_speed_filtered_x10 = 0;
g_is_moving = 0;
}
else
{
period_ms = g_hall_period_ms;
if (period_ms > 0)
{
// 计算速度(mm/s) → 转换为km/h(×10)
speed_mm_s = (VEHICLE_CIRCUMFERENCE_MM * 1000.0f) / (period_ms * HALL_PULSE_PER_REV);
g_speed_x10 = (uint16_t)(speed_mm_s * 0.036f + 0.5f);
// 一阶滤波:滤波速度 = 0.7×旧速度 + 0.3×新速度
g_speed_filtered_x10 = (uint16_t)((g_speed_filtered_x10 * 7U + g_speed_x10 * 3U + 5U) / 10U);
g_is_moving = 1;
}
}
if (g_is_moving) // 运动时累加行驶时间
{
g_drive_seconds++;
}
if (g_ds18b20_ok) // 读取温度
{
g_temperature_x10 = DS18B20_Get_Temp();
}
DS1302_Readtime(); // 读取时钟
}
/************************* 报警检测:超速/超温则报警 *************************/
static void Alarm_Task(void)
{
uint8_t over_speed = (g_speed_filtered_x10 >= g_speed_threshold_x10) ? 1 : 0;
uint8_t over_temp = (g_temperature_x10 >= (short)g_temp_threshold_x10) ? 1 : 0;
Alarm_Set(over_speed || over_temp); // 任一条件满足则报警
}
/************************* OLED显示任务 *************************/
static void Display_Task(void)
{
char line[24];
uint32_t meters_int;
uint32_t meters_dec;
uint32_t drive_min;
uint32_t drive_sec;
if (!g_refresh_display) // 无需刷新则退出
{
return;
}
g_refresh_display = 0;
// 主页面显示
if (g_page == PAGE_MAIN)
{
// 显示速度
snprintf(line, sizeof(line), "V:%3u.%u km/h", g_speed_filtered_x10 / 10, g_speed_filtered_x10 % 10);
ShowLine(1, line);
// 显示里程
meters_int = g_distance_mm / 1000U;
meters_dec = (g_distance_mm % 1000U) / 10U;
snprintf(line, sizeof(line), "L:%4lu.%02lum", meters_int, meters_dec);
ShowLine(2, line);
// 显示温度+速度阈值
snprintf(line, sizeof(line), "T:%2d.%dC S:%2u.%uk", g_temperature_x10 / 10, abs(g_temperature_x10 % 10), g_speed_threshold_x10 / 10, g_speed_threshold_x10 % 10);
ShowLine(3, line);
// 显示时间+行驶时长
drive_min = (g_drive_seconds / 60U) % 100U;
drive_sec = g_drive_seconds % 60U;
snprintf(line, sizeof(line), "%02u:%02u:%02u %02lu:%02lu", hour, min, sec, drive_min, drive_sec);
ShowLine(4, line);
}
// 速度阈值设置页面
else if (g_page == PAGE_SET_SPEED)
{
ShowLine(1, "Set Speed max");
snprintf(line, sizeof(line), "Val:%3u.%u km/h", g_speed_threshold_x10 / 10, g_speed_threshold_x10 % 10);
ShowLine(2, line);
ShowLine(3, "K2- K3+");
ShowLine(4, "K1:Next K4:Back");
}
// 温度阈值设置页面
else if (g_page == PAGE_SET_TEMP)
{
ShowLine(1, "Set Temp max");
snprintf(line, sizeof(line), "Val:%2u.%u C", g_temp_threshold_x10 / 10, g_temp_threshold_x10 % 10);
ShowLine(2, line);
ShowLine(3, "K2- K3+");
ShowLine(4, "K1:Next K4:Back");
}
// PWM设置页面
else if (g_page == PAGE_SET_PWM)
{
ShowLine(1, "Set Motor PWM");
snprintf(line, sizeof(line), "Duty:%3u%% Run:%u", g_motor_pwm, g_motor_run);
ShowLine(2, line);
ShowLine(3, "K2- K3+");
ShowLine(4, "K1:Next K4:Back");
}
// 时间设置系列页面
else if (g_page == PAGE_SET_YEAR)
{
ShowLine(1, "Set Year");
snprintf(line, sizeof(line), "20%02u", g_set_year);
ShowLine(2, line);
ShowLine(3, "K2- K3+ K1Next");
ShowLine(4, "K4: SaveExit");
}
else if (g_page == PAGE_SET_MONTH)
{
ShowLine(1, "Set Month");
snprintf(line, sizeof(line), "%02u", g_set_month);
ShowLine(2, line);
ShowLine(3, "K2- K3+ K1Next");
ShowLine(4, "K4: SaveExit");
}
else if (g_page == PAGE_SET_DAY)
{
ShowLine(1, "Set Day");
snprintf(line, sizeof(line), "%02u", g_set_day);
ShowLine(2, line);
ShowLine(3, "K2- K3+ K1Next");
ShowLine(4, "K4: SaveExit");
}
else if (g_page == PAGE_SET_HOUR)
{
ShowLine(1, "Set Hour");
snprintf(line, sizeof(line), "%02u", g_set_hour);
ShowLine(2, line);
ShowLine(3, "K2- K3+ K1Next");
ShowLine(4, "K4: SaveExit");
}
else if (g_page == PAGE_SET_MIN)
{
ShowLine(1, "Set Minute");
snprintf(line, sizeof(line), "%02u", g_set_min);
ShowLine(2, line);
ShowLine(3, "K2- K3+ K1:Next");
ShowLine(4, "K4: SaveExit");
}
// 秒设置页面
else
{
ShowLine(1, "Set Second");
snprintf(line, sizeof(line), "%02u", g_set_sec);
ShowLine(2, line);
ShowLine(3, "K2- K3+ K1:Next");
ShowLine(4, "K4: SaveExit");
}
}
/************************* 蓝牙数据处理任务 *************************/
static void Bluetooth_Task(void)
{
if (USART1_WaitRecive() == 0) // 接收到完整数据
{
ParseBluetoothCommand((char *)usart1_buf); // 解析指令
USART1_Clear(); // 清空接收缓冲区
ALARM=1; // 接收提示:蜂鸣器响
Delay_ms(80);
ALARM=0;
g_refresh_display = 1; // 刷新显示
}
}
/************************* 蓝牙发送系统状态信息 *************************/
static void Bluetooth_SendStatus(void)
{
UsartPrintf(USART1,
"DIS=%lu.%02lu m,SPD=%u.%u km/h,TMP=%d.%d C,TIME=%02u:%02u:%02u,DRV=%02lu:%02lu,PWM=%u,RUN=%u,VTH=%u.%u,TTH=%u.%u,ALM=%u\\r\\n",
g_distance_mm / 1000U, // 里程整数部分
(g_distance_mm % 1000U) / 10U, // 里程小数部分
g_speed_filtered_x10 / 10, // 速度整数
g_speed_filtered_x10 % 10, // 速度小数
g_temperature_x10 / 10, // 温度整数
abs(g_temperature_x10 % 10), // 温度小数
hour, min, sec, // 时间
(g_drive_seconds / 60U) % 100U, // 行驶分钟
g_drive_seconds % 60U, // 行驶秒
g_motor_pwm, // PWM占空比
g_motor_run, // 运行状态
g_speed_threshold_x10 / 10, // 速度阈值整数
g_speed_threshold_x10 % 10, // 速度阈值小数
g_temp_threshold_x10 / 10, // 温度阈值整数
g_temp_threshold_x10 % 10, // 温度阈值小数
g_alarm_on); // 报警状态
}
/************************* 解析蓝牙控制指令 *************************/
static void ParseBluetoothCommand(char *cmd)
{
char *time_pos = strstr(cmd, "TIME="); // 查找时间指令
if (strstr(cmd, "RUN=1") != NULL) // 启动电机
Motor_SetRun(1);
if (strstr(cmd, "RUN=0") != NULL) // 停止电机
Motor_SetRun(0);
if (strstr(cmd, "CLRDIS=1") != NULL) // 清零里程
{
g_hall_total_pulse = 0;
g_distance_mm = 0;
}
if (strstr(cmd, "CLRDRV=1") != NULL) // 清零行驶时间
{
g_drive_seconds = 0;
}
// 解析各项参数
g_motor_pwm = (uint8_t)ParseIntValue(cmd, "PWM=", g_motor_pwm, 0, 100);
g_speed_threshold_x10 = ParseFixed1Value(cmd, "VTH=", g_speed_threshold_x10, 0, 9995);
g_temp_threshold_x10 = ParseFixed1Value(cmd, "TTH=", g_temp_threshold_x10, 0, 995);
// 解析时间设置指令
if (time_pos != NULL)
{
int yy, mm, dd, hh, mi, ss;
if (sscanf(time_pos + 5, "%d,%d,%d,%d,%d,%d", &yy, &mm, &dd, &hh, &mi, &ss) == 6)
{
DS1302_SetTime((uchar)yy, (uchar)mm, (uchar)dd, (uchar)hh, (uchar)mi, (uchar)ss);
DS1302_Readtime();
}
}
ClampAndApply(); // 参数限制
Alarm_Task(); // 报警检测
}
/************************* 解析整数型参数 *************************/
static uint16_t ParseIntValue(const char *src, const char *key, uint16_t current, uint16_t min_val, uint16_t max_val)
{
const char *pos = strstr(src, key);
uint16_t value;
if (pos == NULL) // 未找到关键字,返回当前值
{
return current;
}
value = (uint16_t)atoi(pos + strlen(key)); // 转换数值
if (value < min_val) value = min_val; // 下限限制
if (value > max_val) value = max_val; // 上限限制
return value;
}
/************************* 解析带一位小数的参数(存储为×10的整数) *************************/
static uint16_t ParseFixed1Value(const char *src, const char *key, uint16_t current, uint16_t min_val, uint16_t max_val)
{
const char *pos = strstr(src, key);
int a = 0;
int b = 0;
int used = 0;
uint16_t value;
if (pos == NULL) // 未找到关键字,返回当前值
{
return current;
}
used = sscanf(pos + strlen(key), "%d.%d", &a, &b); // 解析小数
if (used >= 2)
value = (uint16_t)(a * 10 + (b % 10)); // 转换为×10整数
else
value = (uint16_t)(atoi(pos + strlen(key)) * 10);
if (value < min_val) value = min_val; // 下限限制
if (value > max_val) value = max_val; // 上限限制
return value;
}
/************************* OLED显示一行(固定16字符,不足补空格) *************************/
static void ShowLine(uint8_t row, const char *text)
{
char buf[17];
size_t len = strlen(text);
size_t i;
for (i = 0; i < 16; i++)
{
buf[i] = (i < len) ? text[i] : ' ';
}
buf[16] = '\\0';
Oled_ShowString(row, 0, buf);
}
/************************* 限制PWM范围并应用 *************************/
static void ClampAndApply(void)
{
if (g_motor_pwm > MOTOR_PWM_MAX)
g_motor_pwm = MOTOR_PWM_MAX;
Motor_SetDuty(g_motor_pwm);
}
/************************* 加载当前时钟到时间编辑变量 *************************/
static void TimeEdit_LoadCurrent(void)
{
DS1302_Readtime();
g_set_year = year;
g_set_month = mon;
g_set_day = day;
g_set_hour = hour;
g_set_min = min;
g_set_sec = sec;
g_time_edit_loaded = 1;
}
/************************* 保存编辑的时间到DS1302 *************************/
static void TimeEdit_Save(void)
{
DS1302_SetTime(g_set_year, g_set_month, g_set_day, g_set_hour, g_set_min, g_set_sec);
DS1302_Readtime();
}
/************************* 外部中断0服务函数(霍尔传感器脉冲) *************************/
void EXTI0_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line0) != RESET)
{
uint32_t now_ms = g_tick_ms;
uint32_t delta_ms = now_ms – g_hall_last_tick_ms;
// 软件消抖:时间过短忽略
if ((g_hall_last_tick_ms != 0) && (delta_ms < HALL_DEBOUNCE_MS))
{
EXTI_ClearITPendingBit(EXTI_Line0);
return;
}
g_hall_total_pulse++; // 脉冲计数+1
// 计算平均周期(滤波)
if (g_hall_last_tick_ms != 0)
{
uint32_t period_sum = 0;
uint8_t i;
g_hall_period_buf[g_hall_period_index] = delta_ms;
g_hall_period_index++;
if (g_hall_period_index >= PERIOD_FILTER_SIZE)
{
g_hall_period_index = 0;
}
if (g_hall_period_count < PERIOD_FILTER_SIZE)
{
g_hall_period_count++;
}
// 求和取平均
for (i = 0; i < g_hall_period_count; i++)
{
period_sum += g_hall_period_buf[i];
}
g_hall_period_ms = period_sum / g_hall_period_count;
}
g_hall_last_tick_ms = now_ms; // 更新最后脉冲时间
EXTI_ClearITPendingBit(EXTI_Line0); // 清除中断标志
}
}
六、包含内容

七、资料分享
资料获取:请查看主页介绍搜索或观看开源视频获取




