打印
[GUI]

【STM32H750B-DK评测】新能源监控系统 UI

[复制链接]
3622|9
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
#申请原创#  @21小跑堂
本次评测旨在使用 STM32H750-DK 开发板基于 TouchGFX 库设计一套城市新能源监控系统的 UI 界面,大体上分为欢迎页和三大不同的能源版本主页。一边学习,一边体验高端产品的酷炫。 下面详述实现过程。

1 搭建MDK5工程1.1开发环境
开发板:STM32H750B-DK
TouchGFX软件版本:V4.20.0
MDK5软件版本:V5.33.0
STM32CubeMX软件版本:V6.6.1
操作系统:Windows10
1.2下载官方demo
为了快速GUI开发,使用TouchGFX基于STM32H750B-DK的工程模板
打开TouchGFX,在搜索框中输入STM32H750B-DK,点击Create按钮,创建工程。
TouchGFX生成工程结构如下:
生成的文件夹里面有一个STM32CubeMX的工程文件,由于默认配置中生成的是STM32CubeIDE工程代码,我们需要的是keil工程代码,所以需要切换IDE(除了这点,不改任何配置)。
此时,keil工程模板基本建立,但此时如果直接打开keil工程,编译会报错,是由于TouchGFX没有生成代码,导致确少界面相关源代码,只需要回到TouchGFX,点击产生代码。
再次编译keil工程,无报错,如果有警告,再次编译后可消失,生成.hex文件。
至此,基于STM32H750B-DK的MDK工程模板创建完成。
2 界面设计
2.1 启动界面


圆形进度条:制作两张加载图片,一张为背景,另一张为指示进度,在定时中断函数handleTickEvent()中计数,模拟上电启动加载的动态过程,实际上启动很快,此处故意加了一个延迟。
相关代码如下:
class CarScreenView : public CarScreenViewBase
{
public:
  CarScreenView();
  virtual ~CarScreenView() {}
  virtual void setupScreen();
  virtual void tearDownScreen();
  virtual void clickButtonPressed();
  virtual void handleTickEvent();

protected:

  int tickCounter;
  int progress;
  int start;
};
#include <gui/startscreen_screen/StartScreenView.hpp>
#include <gui/mainscreen_screen/MainScreenView.hpp>

StartScreenView::StartScreenView()
{

}

void StartScreenView::setupScreen()
{
  StartScreenViewBase::setupScreen();
  tickCounter = 0;
  circleProgress.getRange(circleProgressMin, circleProgressMax);   
}

void StartScreenView::tearDownScreen()
{
  StartScreenViewBase::tearDownScreen();
}

void StartScreenView::updateProgress(uint16_t tick)
{
  circleProgress.setValue(tick % (circleProgressMax + 1));
}

void StartScreenView::updateDirection(uint16_t tick)
{
  if (tick % (circleProgressMax + 1) == 0)
  {
​    application().gotoMainScreenScreenBlockTransition();
  }
}

void StartScreenView::handleTickEvent()
{

  tickCounter++;
  updateProgress(tickCounter);
  updateDirection(tickCounter);
}
2.2 主界面
按钮:是一种感应触控事件的控件,能够在按钮被释放时发送回调。 每种状态(按下和释放)都关联了图像。添加按下和放开图片。
交互:用来设置触发条件满足时要执行的动作。触发条件为按钮点击,动作为更换屏幕,并选择屏幕的过渡效果。
2.3无负压供水
线条:一个基于画布控件的控件,能够绘制从一个点到另一个点的直线。本例通过函数  line.setStart(startX, startY)、 line.setEnd(endX, endY)改变线条的起始坐标,进而动态的改变线条的长短和位置,模拟水流的动画效果。
主要实现代码如下:
void WaterSupplyScreenView::drawHorizontalLine(Line &line,uint16_t length)
{
  int startX, startY,endX, endY;

  line.invalidate();
  line.getStart(startX, startY);
  line.getEnd(endX, endY);
  if (endX < length)
  {
​    if((endX - startX) < 10)
​    {
​      line.setStart(startX, startY);
​      line.setEnd(endX + 1, endY);
​    }
​    else
​    {
​      line.setStart(startX + 1, startY);
​      line.setEnd(endX + 1, endY);
​    }   
  }
  else
  {
​    if(startX < length)
​    {
​      line.setStart(startX + 1, startY);
​      line.setEnd(endX, endY);      
​    }
​    else
​    {
​      line.setStart(0, startY);
​      line.setEnd(0, endY);        
​    }
  }
  line.invalidate();
}

void WaterSupplyScreenView::drawVerticalLineDown(Line &line,uint16_t length)
{
  int startX, startY,endX, endY;
  
  line.invalidate();
  line.getStart(startX, startY);
  line.getEnd(endX, endY);
  if (endY < length)
  {
​    if((endY - startY) < 10)
​    {
​      line.setStart(startX, startY);
​      line.setEnd(endX, endY + 1);
​    }
​    else
​    {
​      line.setStart(startX, startY + 1);
​      line.setEnd(endX, endY + 1);
​    }   
  }
  else
  {
​    if(startY < length)
​    {
​      line.setStart(startX, startY + 1);
​      line.setEnd(endX, endY);      
​    }
​    else
​    {
​      line.setStart(startX, 0);
​      line.setEnd(endX, 0);        
​    }
  }
  line.invalidate();
}

void WaterSupplyScreenView::drawVerticalLineUp(Line &line,int length)
{
  int startX, startY,endX, endY;
  
  line.invalidate();
  line.getStart(startX, startY);
  line.getEnd(endX, endY);
  
  if (endY > 0)
  {
​    if((startY - endY) < 10)
​    {
​      line.setStart(startX, startY);
​      line.setEnd(endX, endY - 1);
​    }
​    else
​    {
​      line.setStart(startX, startY - 1);
​      line.setEnd(endX, endY - 1);
​    }   
  }
  else
  {
​    if(startY == 0)
​    {
​      line.setStart(startX, length);
​      line.setEnd(endX, length);        
​    }
​    else if(startY <= 10)
​    {
​      line.setStart(startX, startY - 1);
​      line.setEnd(endX, endY);      
​    }   
  }
  line.invalidate();
}

void WaterSupplyScreenView::handleTickEvent()
{
  static double data = 1.0;

  tickCounter ++;
  if(tickCounter % 5 != 0)
​    return;

  drawHorizontalLine(line1, 28);
  drawHorizontalLine(line2, 25);  
  drawHorizontalLine(line3, 90);  
  drawHorizontalLine(line4, 15);  
  drawHorizontalLine(line5, 15);  
  drawHorizontalLine(line6, 40);  
  drawHorizontalLine(line7, 15);  
  drawHorizontalLine(line8, 15);  
  drawHorizontalLine(line9, 15);  
  drawHorizontalLine(line10, 15);  
  drawHorizontalLine(line11, 20);  
  drawHorizontalLine(line12, 15);  
  drawHorizontalLine(line13, 15);  
  drawHorizontalLine(line14, 15);  
  drawHorizontalLine(line15, 15);  
  drawHorizontalLine(line16, 15);  
  drawHorizontalLine(line17, 15);  
  drawVerticalLineDown(line18, 30);   
  drawVerticalLineDown(line19, 30);   
  drawVerticalLineDown(line20, 65);   
  drawVerticalLineDown(line21, 30);   
  drawVerticalLineUp(line22, 110);   
  drawVerticalLineUp(line23, 30);   
  drawVerticalLineUp(line24, 80);  
  if(tickCounter > 50)
  {
​    drawHorizontalLine(line25, 90);
​    drawVerticalLineUp(line28, 110);   
​    drawVerticalLineDown(line34, 65);
​    drawVerticalLineUp(line37, 30);  
​    drawHorizontalLine(line38, 40);  
​    drawVerticalLineUp(line40, 80);  
​    drawVerticalLineDown(line39, 30);  
  }
  if(tickCounter > 150)
  {
​    drawHorizontalLine(line26, 90);
​    drawVerticalLineUp(line29, 110);
​    drawVerticalLineDown(line35, 65);
​    drawVerticalLineUp(line41, 80);      
  }
  if(tickCounter > 250)
  {
​    drawHorizontalLine(line27, 90);  
​    drawVerticalLineUp(line30, 110);  
​    drawVerticalLineDown(line36, 65);
​    drawVerticalLineUp(line42, 80);   
  }   
  if(tickCounter > 350)
  {
​    drawHorizontalLine(line31, 90);  
​    drawVerticalLineUp(line32, 110);
  }  
  if(tickCounter > 450)
  {
​    drawVerticalLineUp(line33, 110);
  }  
  if(tickCounter % 50 == 0)
  {
​    data += 0.1;
​    if(data > 3.0)
​    {
​      data = 1.0;
​    }
​    Unicode::snprintfFloat(textCounter1Buffer, TEXTCOUNTER1_SIZE, "%0.1f", data);
​    textCounter1.invalidate();  
​    Unicode::snprintfFloat(textCounter2Buffer, TEXTCOUNTER2_SIZE, "%0.1f", data);
​    textCounter2.invalidate();        
  }
}
2.4风电监控系统
动画图像:添加一组同一标识符风车的图像,每个图像间隔的时长为100ms,从头至尾运行,模拟风车动画效果。

动态图表:应用程序在Y轴显示发电功率,X轴显示时间,实时显示风力发电的功率,动态图表支持三种类型的动态行为:换行并清除、滚动、换行并覆盖。本例选用滚动。

主要实现代码如下:
static uint16_t randomish(int32_t seed)
{
  static uint16_t last = 0;
  const uint16_t num = (seed * (1337 + last)) % 0xFFFF;
  last = num;
  return num;
}

void WindPowerScreenView::handleTickEvent()
{
  tickCounter++;
  if (tickCounter % 5 == 0)
  {
​    float yMax = WindDynamicGraph.getGraphRangeYMaxAsFloat();
​    int data = (int)((sinf(tickCounter * .02f) + 1) * (yMax / 2.2f)) + randomish(tickCounter) % (int)(yMax / 10.f);
​    WindDynamicGraph.addDataPoint(data);
​    Unicode::snprintf(powerBuffer, POWER_SIZE, "%d", data);
​    power.invalidate();  
​    Unicode::snprintf(WindSpeedBuffer, WINDSPEED_SIZE, "%d", data/5);
​    WindSpeed.invalidate();        
  }
}
2.5新能源汽车充电
动画图像:按钮按下后,从头至尾运行汽车动画,模拟充电,按下再次按下,动画停止显示。

文本进度条:显示充电进度,并且通过函数batteryTextProgress.setXY(x,y)改变文本的显示坐标。
线性进度条:显示当前充电进度。
主要实现代码如下:
void CarScreenView::handleTickEvent()
{
    if (CarAnimatedImage.isAnimatedImageRunning())   
    {
        tickCounter++;
        if (tickCounter > 1000)
        {
            CarAnimatedImage.stopAnimation();
            Unicode::snprintfFloat(moneyBuffer, MONEY_SIZE, "%0.1f", tickCounter/100);
            money.invalidate();   
            Unicode::snprintfFloat(KwhBuffer, KWH_SIZE, "%0.1f", tickCounter/100);
            Kwh.invalidate();   
            modalWindow.show();                    
        }
        else
        {
            batteryLineProgress.setValue(tickCounter/10);
            batteryTextProgress.setValue(tickCounter/10);
            batteryTextProgress.setXY((43 + (382-43)*tickCounter/10/100), 204);
        }      
    }     
}
模式窗口:是容器类型的控件,用于显示窗口,并禁止对下层视图和控件的触摸事件。充电完成或按键按下后,弹出窗口,显示充电费用和充电电量。
外部事件作为触发器:物理按钮作为触发器,应用程序响应外部按钮的输入,在TouchGFX/target 下创建KeyController.cpp,KeyController.hpp文件,编写按键初始化函数,以及按键采样函数,按键值设为‘1’,在电脑上模拟时按下数字键盘上的1,可模拟开发板上的物理按钮。
主要实现代码如下:
#ifndef KEYCONTROLLER_HPP
#define KEYCONTROLLER_HPP
#include <platform/driver/button/ButtonController.hpp>
namespace touchgfx
{
        class KeyController : public ButtonController
        {
                public:
                virtual void init();
                virtual bool sample(uint8_t& key);
        };
}
#endif
#include <KeyController.hpp>
#include "stm32h7xx_hal.h"
using namespace touchgfx;

void KeyController::init()
{
    GPIO_InitTypeDef GPIO_Initure;

    __HAL_RCC_GPIOC_CLK_ENABLE();           //开启GPIOA时钟
    GPIO_Initure.Pin = GPIO_PIN_13;            //PC13
    GPIO_Initure.Mode = GPIO_MODE_INPUT;       //输入
    GPIO_Initure.Pull = GPIO_NOPULL;            //浮空
    GPIO_Initure.Speed = GPIO_SPEED_FREQ_LOW;   //低速
    HAL_GPIO_Init(GPIOC, &GPIO_Initure);
}

bool  KeyController::sample(uint8_t& key)
{
    static int flag=0;
   
    if (HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_13) == GPIO_PIN_SET)
    {
        if(flag == 0)
        {
            key = '1';
            flag = 1;
            return true;
        }
    }
    else
    {
        flag = 0;
    }
    return false;
}
在TouchGFXHAL.cpp文件添加代码,调用初始化函数,注册按键。

void CarScreenView::clickButtonPressed()
{
  if(start == 0)
  {
​    start = 1;
​    tickCounter = 0;
​    CarAnimatedImage.startAnimation(false, false, true);   
  }
  else
  {
​    start = 0;
​    CarAnimatedImage.stopAnimation();
​    Unicode::snprintfFloat(moneyBuffer, MONEY_SIZE, "%0.1f", tickCounter/100);
​    money.invalidate();  
​    Unicode::snprintfFloat(KwhBuffer, KWH_SIZE, "%0.1f", tickCounter/100);
​    Kwh.invalidate();
​    modalWindow.show();      
  }
}


void CarScreenView::handleTickEvent()
{
  if (CarAnimatedImage.isAnimatedImageRunning())   
  {
​    tickCounter++;
​    if (tickCounter > 1000)
​    {
​      CarAnimatedImage.stopAnimation();
​      Unicode::snprintfFloat(moneyBuffer, MONEY_SIZE, "%0.1f", tickCounter/100);
​      money.invalidate();  
​      Unicode::snprintfFloat(KwhBuffer, KWH_SIZE, "%0.1f", tickCounter/100);
​      Kwh.invalidate();  
​      modalWindow.show();           
​    }
​    else
​    {
​      batteryLineProgress.setValue(tickCounter/10);
​      batteryTextProgress.setValue(tickCounter/10);
​      batteryTextProgress.setXY((43 + (382-43)*tickCounter/10/100), 204);
​    }   
}   
}
3.总结
TouchGFX 是一款可用于STM32处理器的GUI框架。是ST推荐的一款高性能显示框架,可以在资源有限的微处理器上,提供类似于手机应用界面般的显示效果。下面是整体效果视频链接: https://www.bilibili.com/video/BV1aP411V7uB/
由于尺寸过大,本测评涉及到的资源以及源工程放于百度网盘,有需要的同学请自行下载。
链接:https://pan.baidu.com/s/1uISJtTwambTGfZGJ9vWjxw?pwd=H750 提取码:H750





使用特权

评论回复
沙发
carpsnow| | 2022-9-10 13:21 | 只看该作者
这一大篇,费功夫了

使用特权

评论回复
板凳
tpgf| | 2022-10-8 14:47 | 只看该作者
如果做图像的话 是不是只用cube配置就不是太够用了呢

使用特权

评论回复
地板
nawu| | 2022-10-8 15:01 | 只看该作者
TouchGFX 和cube以及编译工具有没有版本相匹配的问题呢

使用特权

评论回复
5
aoyi| | 2022-10-8 15:32 | 只看该作者
做图像处理的时候需要自己调整尺寸位置什么的吗

使用特权

评论回复
6
zljiu| | 2022-10-8 15:42 | 只看该作者
可以动态的调整显示图像的尺寸吗

使用特权

评论回复
7
gwsan| | 2022-10-8 15:53 | 只看该作者
请问TouchGFXHAL.cpp文件是使用c++编程的吗

使用特权

评论回复
8
tfqi| | 2022-10-8 16:04 | 只看该作者
如果想要做出图中这个图片的内容的话 是不是有现成的模板可以使用呢

使用特权

评论回复
9
huangchui| | 2022-10-8 22:54 | 只看该作者
同问,TouchGFX 和cube以及编译工具有没有版本相匹配的问题呢

使用特权

评论回复
10
yjwpm| | 2022-10-15 14:24 | 只看该作者
连接失效

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

33

主题

179

帖子

10

粉丝