你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

F429I-DISC1体验报告(4) 温度可视化动态图表的实现丨国庆开发板测评活动

[复制链接]
cm32 发布时间:2025-12-5 00:02

综述

本文将完成以下任务

开发一个温度传感器数据可视化应用,实时显示温度数据的图表。

STM32F4 内部集成温度传感器,并通过 ADC 获取温度数据。 在应用层中,ADC 采样值交由 TouchGFX 的 Model 处理,再由 Model 通过 MVP 架构传递给 Presenter,最后由 Presenter 更新 View,实现温度在界面上的实时显示。

开发工具

TouchGFX Designer 4.26.0,STM32CubeIDE 2.0.0,STM32CubeMX

MVP架构简介

在 TouchGFX 的 MVP 架构中,Model、Presenter 和 View 构成界面数据流的三层结构,各自承担不同职责。Model 负责维护应用层的数据状态,并从底层逻辑(如传感器采集、通信模块等)接收更新。当 Model 内部数据发生变化时,它不会直接操作界面,而是通过一个抽象接口 ModelListener 将更新通知上层。

ModelListener 是 Presenter 实现的回调接口,起到连接 Model 与 Presenter 的桥梁作用。通过该接口,Model 不需要关心当前显示的是哪个界面,也无需了解界面逻辑结构,从而实现彻底解耦**。

Presenter 作为中间层,一方面实现 ModelListener 以接收 Model 的数据更新;另一方面持有对 View 的引用,将处理后的数据传递给界面。Presenter 同时也是 View 的逻辑控制者,负责响应用户界面事件并协调 Model 的行为。

View 则专注于界面呈现,其职责仅包括根据 Presenter 提供的数据进行显示更新,不包含业务逻辑,也不直接访问 Model。

通过 Model → Presenter → View 的单向数据流,以及 ModelListener 的回调机制,TouchGFX 实现了结构清晰、层次分离、低耦合的界面框架,使界面渲染、界面逻辑与业务逻辑互不干扰。本项目应用的架构如下图所示。实现还是较为简单的。

image.png

温度图表界面的实现

UI绘制


Dynamic Graph 简介

Dynamic Graph通过固定长度的环形缓冲区存储点,每次加入新数据,当占据整个横坐标时自动滚动图线或重绘(可以在TouchGFX Designer中选择具体刷新方案),并在TouchGFX Designer中支持自定义数据范围、图线颜色、线宽、刷新方式和多曲线绘制,适合实时波形、传感器趋势等场景。

Dynamic Graph提供两个接口:

<控件名>.addDataPoint(0);//写入环形缓冲区

<控件名>.invalidate();//局部刷新,屏幕显示最新波形

具体可以参考官方文档,写的十分详细:动态图表 | TouchGFX Documentation

绘制步骤

  • 首先新建一个Screen,重命名为TempScreen,设置为Startup Screen;同时打开Canvas Buffer,大小为默认的3600即可。

  • 添加一个box并拉到最大,作为底图。 image.png

  • 添加一个Dynamic Graph,重命名为tempGraph,位置和大小设置为W240 H260Graph Area MarginGraph Area Padding 分别是边距和填充区,具体可以参考下面的示意图;为了给坐标轴留足空间,Right和Bottom设置的稍微大一些,分别是:

    Graph Area Margin

    Top Bottom Left Right
    10 15 20 10

    Graph Area Padding

    Top Bottom Left Right
    10 15 20 10

    image.png

    image.png

  • 接下来修改的是数据点的表达:

    • Dynamic Behavior 动态行为(更新形式):
    • 第一个是Wrap and Clear,是指当数据点达到横坐标轴最大值时,清空屏幕并重新从原点开始画(横坐标不会清零);
    • 第二个是Scroll,顾名思义就是滚动显示;
    • 第三个是Wrap and Overwrite,中间向右动态移动的竖线,将右边旧的数据更换成新的数据;

    这里选择的是中间的Scroll,缺点是数据点设置得太多的话后面动态更新的时候会比较卡;后面如果测试的效果比较卡顿,可以将Data Points 修改为60甚至更低。

    为了较为精确地显示芯片内部温度,精度等级设置为0.1。

    image.png

  • 接下来是图表的图线元素、网格线和坐标轴的设置:

    • 首先是Elements,可以按照喜好加入圆点、方形、菱形、直线、面积等图元,这里只用Area和Line,其中Area的透明度(Alpha)调节到100左右;
    • 然后是Grid Lines 网格,根据需要选择纵向的或者横向的;
    • 最后是Labels 坐标轴,这边只修改了字体大小为10px;

    image.png

  • 放置一个Text Area,添加Wildcard通配符,修改名称为tempText,修改初始值为00.0,保证显示的内容的长度,并勾选上Buffer;

    修改Translation中的文本为Temp:<value>°C

    image.png

    来到Text->Typographies->你的TextArea设置的字体 下,在Wildcard Characters中加入1234567890.°,确保数字、小数点、℃能够正常显示。 image.png

  • 完成以上内容后,点击右下角最左边的按钮,生成代码。

    image.png

CubeMX配置

打开项目文件夹中的.ioc文件;

image.png

在Analog->ADC1->Mode里面找到Temperature Sensor Channel,勾选;

image.png

勾选后直接点击GENERATE CODE生成代码,可能会有以下弹窗,点击Yes即可。

image.png

生成完成后会有弹窗问你是否打开项目,点击Close或者直接关掉这个弹窗,手动打开CubeIDE工程。

image.png

CubeIDE代码编写

在<项目名>/CubeIDE中有.project和.cproject两个文件,双击打开任意一个。

image.png

在<项目名>/Core/Inc中新建一个文本文档,改名为temp_sensor.h,在<项目名>/Core/Src中新建一个文本文档,改名为temp_sensor.c

image.png

将 temp_sensor.c 拖到/Application/User文件夹中,会弹出一个窗口,选择link to files;

temp_sensor.h不用动,因为/Core/Inc本就在工程的头文件路径里;

image.png


temp_sensor.h

主要声明1个函数:TS_GetTemperature(void),这一部分直接写在 main.hmain.c 也是可以的,单独新建文件只是为了项目结构更清晰。

#ifndef TEMP_SENSOR_H
#define TEMP_SENSOR_H

#include "main.h"

#ifdef __cplusplus
extern "C" {
#endif

float TS_GetTemperature(void);

#ifdef __cplusplus
}
#endif

#endif

temp_sensor.c

F429内部温度传感器的计算公式为:

$$ T(℃)={(Vsense - V25) /Avg_Slope}+25

$$

其中 Vsense是传感器读取到的电压值,V25 Vsense 在25℃时的典型值,Avg_Slope 是温度与 Vsense 曲线的平均斜率,典型值为2.5mV/℃。

#include "temp_sensor.h"

extern ADC_HandleTypeDef hadc1;

float TS_GetTemperature(void)
{
    ADC_ChannelConfTypeDef sConfig = {0};

    sConfig.Channel = ADC_CHANNEL_TEMPSENSOR;
    sConfig.Rank = 1;
    sConfig.SamplingTime = ADC_SAMPLETIME_480CYCLES;
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);

    HAL_ADC_Start(&hadc1);
    HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);

    uint16_t adcValue = HAL_ADC_GetValue(&hadc1);

    float VSENSE = adcValue * 3.3f / 4095.0f;  // 若 Vref=3.0 改成 3.0f,以此类推
    float temp = ((VSENSE - 0.76f) / 0.0025f) + 25.0f;
    return temp;    // 单位 ℃
}

Model.hpp

主要在Model类的声明中加入了以下成员函数和变量的声明:

protected字段:

int tickCount = 0; //用于计数进入Model::tick的次数,如果采样频率太快可以启用
bool enableTemperatureSampling = false; //用于确定是否需要采样,当不在当前界面时停止采样,节省设备资源

public字段:

void setTemperatureSampling(bool enable) //提供给其他类的接口
    {
        enableTemperatureSampling = enable;
    }

完整的类的声明如下:

class Model
{
public:
    Model();

    void bind(ModelListener* listener)
    {
        modelListener = listener;
    }

    void tick();

    void setTemperatureSampling(bool enable)
    {
        enableTemperatureSampling = enable;
    }
protected:
    ModelListener* modelListener;
    int tickCount = 0;
    bool enableTemperatureSampling = false;
};

Model.cpp

需要包含"temp_sensor.h"头文件:

#include "temp_sensor.h"

主要修改Model::tick()函数,当温度界面激活时,在每个tick获取温度数据,并通过onNewTemperature()上传数据到View;

为了减少数据在层级之间传递的开销,将温度 t 乘以10后转化为int16_t类型的变量 t10 ,这样温度在Model->ModelListener->Presenter->View这一传输链路上都是以int16_t传递,减少性能上的浪费。

void Model::tick()
{
    if (!enableTemperatureSampling){
        return;     // ← 关键:不在温度界面时不采样
    }
        tickCount++;
    //if (tickCount >= 6)
    //{
        tickCount = 0;

        float t = TS_GetTemperature();      // 调用 C 函数
        int16_t t10 = (int16_t)(t * 10);    // 放大 10 倍传给 GUI(避免用 float)

        if (modelListener != 0)
        {
            modelListener->onNewTemperature(t10);
        }
    //}
}

ModelListener.hpp

在文件头加入#include <cstdint>,否则int16_t类型会报错;

在public中加入以下声明:

virtual void onNewTemperature(int16_t temp10) {};

因为此处声明的onNewTemperature()是虚函数,可以在后续的Presenter中提供具体实现。


TempScreenPresenter.hpp

在public中加入以下声明:

virtual void onNewTemperature(int16_t temp10);

TempScreenPresenter.hpp

Presenter主要做两件事:一是当窗口激活/失效时,分别使能/失能Model获取温度数据;二是当Model获取到数据时,通过view的updateTemperature()接口显示到图表和文本上,这个函数后面会实现;

void TempScreenPresenter::activate()
{
    model->setTemperatureSampling(true);
}

void TempScreenPresenter::deactivate()
{
    model->setTemperatureSampling(false);
}
void TempScreenPresenter::onNewTemperature(int16_t temp10)
{
    view.updateTemperature(temp10);
}

TempScreenView.hpp

在public中声明:

void updateTemperature(int16_t temp10);

TempScreenView.cpp

包含头文件:#include <cstdint>

注意从Model传递来的是10倍温度的整型数据,为了正常显示,应该把它转化为浮点型:

void TempScreenView::updateTemperature(int16_t temp10)
{
    // 1. 更新数字显示(tempText)
    float t = temp10 / (10.0f);
    Unicode::snprintfFloat(tempTextBuffer, TEMPTEXT_SIZE, "%.1f", t);
    tempText.invalidate();

    // 2. 更新折线图
    tempGraph.addDataPoint(t);
    tempGraph.invalidate();
}

下载验证

编写完成后通过CubeIDE编译并下载到开发板上:

image.png

代码已经放在Github上:STM32F429-TouchGFX/MyApplication_TemperatureMonitor at main · Chiando-1100/STM32F429-TouchGFX

收藏 评论0 发布时间:2025-12-5 00:02

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版