STM32实战项目:从零打造GPS蓝牙自行车码表,掌握传感器、蓝牙、Flash存储等核心技术

一、 引言

骑行,作为一项绿色健康的运动方式,越来越受到人们的喜爱。而记录骑行数据,分析速度、里程等信息,则成为了许多骑行爱好者的追求。本篇文章将带你使用STM32单片机,DIY一款功能完备的自行车码表,记录你的每一次骑行轨迹!

二、 功能概述

本项目将实现以下功能:

  • 实时速度显示: 利用霍尔传感器采集车轮转速,计算并显示实时速度。
  • 里程统计: 记录单次和总里程,方便用户了解骑行距离。
  • 骑行时间记录: 记录单次骑行时间,方便用户掌握运动量。
  • 轨迹记录: 利用GPS模块获取经纬度信息,记录骑行轨迹,并在手机APP上进行查看。
  • 数据存储: 将骑行数据存储到外部Flash芯片,方便用户后续查看和分析。
  • LCD显示: 使用LCD屏幕实时显示速度、里程、时间等信息。
  • 蓝牙传输: 通过蓝牙模块将骑行数据传输至手机APP,实现更丰富的功能和数据分析。

三、 系统设计

3.1 硬件设计

本项目硬件部分主要由以下模块组成:

fddfc539cffa46c5b1233aae529bd8c1.png

  • 主控芯片: STM32F103C8T6,性价比高,性能满足项目需求。
  • 霍尔传感器: 用于检测车轮转速,可选择磁铁+霍尔传感器组合或一体式霍尔传感器。
  • GPS模块: 用于获取经纬度信息,记录骑行轨迹,可以选择支持GPS+北斗双模的模块,提高定位精度。
  • LCD显示屏: 用于显示速度、里程、时间等骑行数据,可以选择1602、OLED等类型。
  • 蓝牙模块: 用于与手机APP进行数据传输,可以选择HC-05、HM-10等蓝牙2.0模块,或CC2540、nRF51822等蓝牙4.0/BLE模块。
  • 外部Flash: 用于存储骑行数据,可以选择SPI接口的Flash芯片,容量根据需求选择。
  • 电源模块: 为整个系统提供电源,可以使用锂电池供电,并设计充电电路。

3.2 软件设计

软件部分主要包括以下几个模块:

6f7f80fcf8434779808181a15a8b4491.png

  • 传感器数据采集: 通过定时器中断读取霍尔传感器数据,计算车轮转速。
  • 数据处理: 对传感器数据进行滤波处理,计算实时速度、里程等信息。
  • 显示控制: 将处理后的数据格式化输出到LCD屏幕上。
  • 蓝牙通信: 通过蓝牙模块与手机APP进行数据交互,发送实时数据和接收控制指令。
  • 数据存储: 将骑行数据存储到外部Flash芯片,方便用户后续查看和分析。

四、 软件实现

本项目的软件部分基于STM32标准库进行开发,核心代码使用C语言编写,并结合STM32CubeMX进行初始化配置,简化开发流程。

4.1 霍尔传感器数据采集

霍尔传感器用于检测车轮转动,每次磁铁经过传感器时,会产生一个脉冲信号。我们利用STM32的定时器中断来捕捉这个脉冲信号,并计算车轮转速。

// 定时器中断服务函数
void TIM2_IRQHandler(void)
{
  if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) // 判断定时器更新中断
  {
    // 读取霍尔传感器状态
    sensor_state = GPIO_ReadInputDataBit(SENSOR_GPIO_PORT, SENSOR_GPIO_PIN);

    // 判断上升沿,计算脉冲个数
    if (sensor_state == SET && last_sensor_state == RESET) 
    {
      pulse_count++; 

      // 计算时间差
      current_time = TIM_GetCounter(TIM2);
      time_diff = current_time - last_time;
      last_time = current_time;

      // 计算速度,避免除零错误
      if (time_diff > 0) 
      {
        speed = (WHEEL_CIRCUMFERENCE * 3600.0) / (time_diff * TIM2_PRESCALER); // 单位:km/h
      }
    }

    last_sensor_state = sensor_state;
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 清除中断标志位
  }
}

在定时器中断服务函数中,我们首先读取霍尔传感器的状态。如果检测到上升沿,则认为车轮转动一圈,脉冲计数加一。同时,我们记录当前时间和上次时间,计算时间差,并根据预设的车轮周长和定时器预分频系数计算出实时速度。

4.2 数据处理

为了提高数据准确性,我们需要对原始的传感器数据进行滤波处理,去除噪声干扰。同时,我们还需要根据速度计算里程,并进行其他数据统计。

// 数据处理函数
void data_process(void)
{
  // 使用滑动平均滤波器对速度进行滤波
  filtered_speed = (int)(0.9 * filtered_speed + 0.1 * speed); 

  // 计算里程,单位:km
  distance += (float)filtered_speed * TIME_INTERVAL / 3600.0; 

  // ...其他数据处理,如时间统计、最大速度记录等
}

这里我们使用简单的滑动平均滤波器对速度进行平滑处理。当然,你也可以根据实际情况选择其他滤波算法,例如卡尔曼滤波等。

4.3 显示控制

为了直观地显示骑行数据,我们使用LCD屏幕进行实时显示。

// LCD显示函数
void display_data(void)
{
  // 清屏
  LCD_Clear(Black);

  // 显示速度
  LCD_SetCursor(0, 0);
  LCD_Printf("Speed: %2d.%d km/h", (int)filtered_speed, (int)(filtered_speed * 10) % 10);

  // 显示里程
  LCD_SetCursor(0, 1);
  LCD_Printf("Dist: %d.%d km", (int)distance, (int)(distance * 10) % 10);

  // ...显示其他数据,如时间、平均速度等
}

在LCD显示函数中,我们首先清屏,然后使用LCD库函数将速度、里程等信息格式化输出到LCD屏幕上。

 

4.4 蓝牙通信

为了实现更丰富的功能,例如数据记录、轨迹显示等,我们可以使用蓝牙模块将骑行数据传输到手机APP。

// 蓝牙数据发送函数
void bluetooth_send_data(void)
{
  // 数据打包
  uint8_t data_packet[PACKET_SIZE];
  data_packet[0] = 0xAA; // 数据头
  data_packet[1] = (uint8_t)(filtered_speed >> 8);
  data_packet[2] = (uint8_t)filtered_speed;
  // ...打包其他数据,如里程、时间等

  // 通过蓝牙发送数据
  HAL_UART_Transmit_DMA(&huart1, data_packet, sizeof(data_packet)); 
}

在蓝牙数据发送函数中,我们首先定义一个数据包,将需要发送的数据按照一定的协议格式进行打包。这里我们使用了简单的协议,数据包开头为固定的数据头,后面跟着各个数据项。数据打包完成后,我们使用HAL库函数 HAL_UART_Transmit_DMA 将数据通过蓝牙模块发送出去。

4.5 数据存储

为了保存骑行数据,方便用户后续查看和分析,我们可以使用外部Flash芯片进行数据存储。

// 数据存储函数
void data_store(void)
{
  // 将骑行数据写入Flash
  uint32_t address = FLASH_START_ADDR + data_counter * sizeof(RideData);

  RideData data;
  data.speed = filtered_speed;
  data.distance = distance;
  // ...存储其他数据

  // 使用HAL库函数将数据写入Flash
  if (HAL_FLASH_Unlock() == HAL_OK)
  {
    if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address, (uint32_t *)&data, sizeof(RideData)/4) == HAL_OK)
    {
      data_counter++; // 数据计数加一
      HAL_FLASH_Lock();
    }
    else
    {
      // 处理写入错误
    }
  }
  else
  {
    // 处理解锁错误
  }
}

在数据存储函数中,我们首先定义一个结构体 RideData 用于存储一次骑行的数据,例如速度、里程、时间等。然后,我们使用HAL库函数 HAL_FLASH_Unlock 解锁Flash,使用 HAL_FLASH_Program 将数据写入指定地址,最后使用 HAL_FLASH_Lock 锁定Flash。

五、 手机APP设计

为了更方便地查看和分析骑行数据,我们可以开发配套的手机APP。APP可以通过蓝牙接收码表发送的数据,并实现以下功能:

  • 实时数据显示: 实时显示速度、里程、时间、轨迹等信息。
  • 历史数据查看: 查看历史骑行记录,并进行数据分析。
  • 轨迹地图显示: 在地图上显示骑行轨迹,并可以分享到社交平台。
  • 设置参数: 设置车轮周长、时间格式等参数。

手机APP的开发可以使用Java、Kotlin、Swift等语言,并选择合适的蓝牙库进行开发。

六、 总结

本文介绍了基于STM32的自行车码表的设计与实现,涵盖了硬件设计、软件实现、手机APP设计等方面。通过本项目的学习,可以掌握STM32单片机开发、传感器应用、蓝牙通信、数据存储等知识。当然,你也可以根据自己的需求,扩展更多功能,例如心率监测、踏频测量、导航等,打造一款功能更强大的智能自行车码表!

相关知识点链接

  • STM32开发:
    • STM32官方网站
    • STM32CubeMX
    • STM32 HAL库
  • 蓝牙:
    • 蓝牙协议栈
    • 你所选蓝牙模块的AT指令集文档

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/781461.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【开放集目标检测】Grounding DINO

一、引言 论文: Grounding DINO: Grounding DINO: Marrying DINO with Grounded Pre-Training for Open-Set Object Detection 作者: IDEA 代码: Grounding DINO 注意: 该算法是在Swin Transformer、Deformable DETR、DINO基础上…

【LeetCode】有效的数独

目录 一、题目二、解法 一、题目 请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。 数字 1-9 在每一行只能出现一次。 数字 1-9 在每一列只能出现一次。 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。&…

代码随想录算法训练营第二十七天 |56. 合并区间 738.单调递增的数字 968.监控二叉树 (可跳过)

56. 合并区间 以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。 示例 1: 输入:in…

赤壁之战的烽火台 - 观察者模式

“当烽火连三月,家书抵万金;设计模式得其法,千军如一心。” 在波澜壮阔的三国历史长河中,赤壁之战无疑是一场改变乾坤的重要战役。而在这场战役中,一个看似简单却至关重要的系统发挥了巨大作用——烽火台。这个古老的…

探索InitializingBean:Spring框架中的隐藏宝藏

​🌈 个人主页:danci_ 🔥 系列专栏:《设计模式》《MYSQL》 💪🏻 制定明确可量化的目标,坚持默默的做事。 ✨欢迎加入探索MYSQL索引数据结构之旅✨ 👋 Spring框架的浩瀚海洋中&#x…

Javascript常见数据结构和设计模式

在JavaScript中,常见的数据结构包括两大类:原始数据类型(Primitive Types)和对象类型(Object Types)。对象类型又可以进一步细分为多种内置对象、数组、函数等。下面是一些JavaScript中常见的数据结构&…

《算法笔记》总结No.4——散列

散列的英文名是hash,即我们常说的哈希~该知识点在王道408考研的教材里面属于查找的范围。即便各位并无深入了解过,也听说过散列是一种更高效的查找方法。 一.引例 先来考虑如下一个假设:设有数组M和N分别如下: M[10][1,2,3,4,5,6…

【Spring AOP 源码解析前篇】什么是 AOP | 通知类型 | 切点表达式| AOP 如何使用

前言(关于源码航行) 在准备面试和学习的过程中,我阅读了还算多的源码,比如 JUC、Spring、MyBatis,收获了很多代码的设计思想,也对平时调用的 API 有了更深入的理解;但过多散乱的笔记给我的整理…

自动化设备上位机设计 四

目录 一 设计原型 二 后台代码 一 设计原型 二 后台代码 using SimpleTCP; using SqlSugar; using System.Text;namespace 自动化上位机设计 {public partial class Form1 : Form{SqlHelper sqlHelper new SqlHelper();SqlSugarClient dbContent null;bool IsRun false;i…

【机器学习实战】Datawhale夏令营:Baseline精读笔记2

# AI夏令营 # Datawhale # 夏令营 在原有的Baseline上除了交叉验证,还有一种关键的优化方式,即特征工程。 如何优化特征,关系着我们提高模型预测的精准度。特征工程往往是对问题的领域有深入了解的人员能够做好的部分,因为我们要…

链式二叉树oj题

1.输入k ,找第k层节点个数 int TreeKlevel(BTNode*root,int k) {if (root NULL) {return 0;}if (k 1) {return 1;}return TreeKlevel(root->left, k - 1)TreeKlevel(root->right, k - 1); } 在这里我们要确定递归子问题,第一个就是NULL时返回&…

强化学习中的Q-Learning和Sarsa算法详解及实战

强化学习(Reinforcement Learning, RL)是一种通过与环境交互来学习最优策略的机器学习方法。在强化学习中,Q-Learning和Sarsa是两种重要的基于值的算法。本文将详细讲解这两种算法,并通过实际代码示例展示其应用。 1. 强化学习基…

algorithm算法库学习之——不修改序列的操作

algorithm此头文件是算法库的一部分。本篇介绍不修改序列的操作函数。 不修改序列的操作 all_ofany_ofnone_of (C11)(C11)(C11) 检查谓词是否对范围中所有、任一或无元素为 true (函数模板) for_each 应用函数到范围中的元素 (函数模板) for_each_n (C17) 应用一个函数对象到序…

Vue88-Vuex中的mapActions、mapMutations

一、mapMutations的调用 此时结果不对,因为:若是点击事件不传值,默认传的是event!,所以,修改如下: 解决方式1: 解决方式2: 不推荐,写法麻烦! 1-…

排序算法简述(第八jiang)

目录 排序 选择排序 O(n2) 不稳定:48429 归并排序 O(n log n) 稳定 插入排序 O(n2) 堆排序 O(n log n) 希尔排序 O(n log2 n) 图书馆排序 O(n log n) 冒泡排序 O(n2) 优化: 基数排序 O(n k) 快速排序 O(n log n)【分治】 不稳定 桶排序 O(n…

【图解大数据技术】Flume、Kafka、Sqoop

【图解大数据技术】Flume、Kafka、Sqoop FlumeFlume简介Flume的应用场景 KafkaKafka简介Kafka架构Flume与Kafka集成 SqoopSqoop简介Sqoop原理sqoop搭配任务调度器实现定时数据同步 Flume Flume简介 Flume是一个数据采集工具,多用于大数据技术架构下的日志采集。 …

设计模式之模版方法

模版方法介绍 模版方法(Template Method)模式是一种行为型设计模式,它定义了一个操作(模板方法)的基本组合与控制流程,将一些步骤(抽象方法)推迟到子类中,使得子类可以在…

C语言下的文件详解

主要内容 文件概述文件指针文件的打开与关闭文件的读写 文件 把输入和输出的数据以文件的形式保存在计算机的外存储器上,可以确保数据能随时使用,避免反复输入和读取数据 文件概述 文件是指一组相关数据的有序集合 文件是存储数据的基本单位&#…

# mysql 中文乱码问题分析

mysql 中文乱码问题分析 一、问题分析: MySQL 中文乱码通常是因为字符集设置不正确导致的。MySQL 有多种字符集,如 latin1、utf8、utf8mb4 等,如果在创建数据库、数据表或者字段时没有指定正确的字符集,或者在插入数据时使用了与…

关于Java异常机制及finally关键字的详解

异常机制(Exception) 软件程序在运行过程中,非常可能遇到异常问题。常见的异常: 1、用户输入错误 2、设备错误 3、硬件问题,例如打印机关掉、服务器问题 4、物理限制:磁盘满了 Java是采用面向对象的方式来处理异常的。 处理过程…