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

[复制链接]
4934|9
 楼主| yang377156216 发表于 2022-9-4 10:34 | 显示全部楼层 |阅读模式
#申请原创#  @21小跑堂
本次评测旨在使用 STM32H750-DK 开发板基于 TouchGFX 库设计一套城市新能源监控系统的 UI 界面,大体上分为欢迎页和三大不同的能源版本主页。一边学习,一边体验高端产品的酷炫。 下面详述实现过程。
1.0 开发板.jpg
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按钮,创建工程。
1.2 TouchGFX 创建工程.jpg
TouchGFX生成工程结构如下:
1.2 TouchGFX 工程结构.jpg
生成的文件夹里面有一个STM32CubeMX的工程文件,由于默认配置中生成的是STM32CubeIDE工程代码,我们需要的是keil工程代码,所以需要切换IDE(除了这点,不改任何配置)。
1.2 STM32CubeMX.jpg
此时,keil工程模板基本建立,但此时如果直接打开keil工程,编译会报错,是由于TouchGFX没有生成代码,导致确少界面相关源代码,只需要回到TouchGFX,点击产生代码。
1.2 TouchGFX 生成代码.jpg
再次编译keil工程,无报错,如果有警告,再次编译后可消失,生成.hex文件。
1.2 创建完成.jpg
至此,基于STM32H750B-DK的MDK工程模板创建完成。
2 界面设计
2.1 启动界面
2.1 启动界面.jpg
2.1圆形进度条.png 2.1圆形进度条背景.png
圆形进度条:制作两张加载图片,一张为背景,另一张为指示进度,在定时中断函数handleTickEvent()中计数,模拟上电启动加载的动态过程,实际上启动很快,此处故意加了一个延迟。
相关代码如下:
  1. class CarScreenView : public CarScreenViewBase
  2. {
  3. public:
  4.   CarScreenView();
  5.   virtual ~CarScreenView() {}
  6.   virtual void setupScreen();
  7.   virtual void tearDownScreen();
  8.   virtual void clickButtonPressed();
  9.   virtual void handleTickEvent();

  10. protected:

  11.   int tickCounter;
  12.   int progress;
  13.   int start;
  14. };
  1. #include <gui/startscreen_screen/StartScreenView.hpp>
  2. #include <gui/mainscreen_screen/MainScreenView.hpp>

  3. StartScreenView::StartScreenView()
  4. {

  5. }

  6. void StartScreenView::setupScreen()
  7. {
  8.   StartScreenViewBase::setupScreen();
  9.   tickCounter = 0;
  10.   circleProgress.getRange(circleProgressMin, circleProgressMax);   
  11. }

  12. void StartScreenView::tearDownScreen()
  13. {
  14.   StartScreenViewBase::tearDownScreen();
  15. }

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

  20. void StartScreenView::updateDirection(uint16_t tick)
  21. {
  22.   if (tick % (circleProgressMax + 1) == 0)
  23.   {
  24. ​    application().gotoMainScreenScreenBlockTransition();
  25.   }
  26. }

  27. void StartScreenView::handleTickEvent()
  28. {

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

  4.   line.invalidate();
  5.   line.getStart(startX, startY);
  6.   line.getEnd(endX, endY);
  7.   if (endX < length)
  8.   {
  9. ​    if((endX - startX) < 10)
  10. ​    {
  11. ​      line.setStart(startX, startY);
  12. ​      line.setEnd(endX + 1, endY);
  13. ​    }
  14. ​    else
  15. ​    {
  16. ​      line.setStart(startX + 1, startY);
  17. ​      line.setEnd(endX + 1, endY);
  18. ​    }   
  19.   }
  20.   else
  21.   {
  22. ​    if(startX < length)
  23. ​    {
  24. ​      line.setStart(startX + 1, startY);
  25. ​      line.setEnd(endX, endY);      
  26. ​    }
  27. ​    else
  28. ​    {
  29. ​      line.setStart(0, startY);
  30. ​      line.setEnd(0, endY);        
  31. ​    }
  32.   }
  33.   line.invalidate();
  34. }

  35. void WaterSupplyScreenView::drawVerticalLineDown(Line &line,uint16_t length)
  36. {
  37.   int startX, startY,endX, endY;
  38.   
  39.   line.invalidate();
  40.   line.getStart(startX, startY);
  41.   line.getEnd(endX, endY);
  42.   if (endY < length)
  43.   {
  44. ​    if((endY - startY) < 10)
  45. ​    {
  46. ​      line.setStart(startX, startY);
  47. ​      line.setEnd(endX, endY + 1);
  48. ​    }
  49. ​    else
  50. ​    {
  51. ​      line.setStart(startX, startY + 1);
  52. ​      line.setEnd(endX, endY + 1);
  53. ​    }   
  54.   }
  55.   else
  56.   {
  57. ​    if(startY < length)
  58. ​    {
  59. ​      line.setStart(startX, startY + 1);
  60. ​      line.setEnd(endX, endY);      
  61. ​    }
  62. ​    else
  63. ​    {
  64. ​      line.setStart(startX, 0);
  65. ​      line.setEnd(endX, 0);        
  66. ​    }
  67.   }
  68.   line.invalidate();
  69. }

  70. void WaterSupplyScreenView::drawVerticalLineUp(Line &line,int length)
  71. {
  72.   int startX, startY,endX, endY;
  73.   
  74.   line.invalidate();
  75.   line.getStart(startX, startY);
  76.   line.getEnd(endX, endY);
  77.   
  78.   if (endY > 0)
  79.   {
  80. ​    if((startY - endY) < 10)
  81. ​    {
  82. ​      line.setStart(startX, startY);
  83. ​      line.setEnd(endX, endY - 1);
  84. ​    }
  85. ​    else
  86. ​    {
  87. ​      line.setStart(startX, startY - 1);
  88. ​      line.setEnd(endX, endY - 1);
  89. ​    }   
  90.   }
  91.   else
  92.   {
  93. ​    if(startY == 0)
  94. ​    {
  95. ​      line.setStart(startX, length);
  96. ​      line.setEnd(endX, length);        
  97. ​    }
  98. ​    else if(startY <= 10)
  99. ​    {
  100. ​      line.setStart(startX, startY - 1);
  101. ​      line.setEnd(endX, endY);      
  102. ​    }   
  103.   }
  104.   line.invalidate();
  105. }

  106. void WaterSupplyScreenView::handleTickEvent()
  107. {
  108.   static double data = 1.0;

  109.   tickCounter ++;
  110.   if(tickCounter % 5 != 0)
  111. ​    return;

  112.   drawHorizontalLine(line1, 28);
  113.   drawHorizontalLine(line2, 25);  
  114.   drawHorizontalLine(line3, 90);  
  115.   drawHorizontalLine(line4, 15);  
  116.   drawHorizontalLine(line5, 15);  
  117.   drawHorizontalLine(line6, 40);  
  118.   drawHorizontalLine(line7, 15);  
  119.   drawHorizontalLine(line8, 15);  
  120.   drawHorizontalLine(line9, 15);  
  121.   drawHorizontalLine(line10, 15);  
  122.   drawHorizontalLine(line11, 20);  
  123.   drawHorizontalLine(line12, 15);  
  124.   drawHorizontalLine(line13, 15);  
  125.   drawHorizontalLine(line14, 15);  
  126.   drawHorizontalLine(line15, 15);  
  127.   drawHorizontalLine(line16, 15);  
  128.   drawHorizontalLine(line17, 15);  
  129.   drawVerticalLineDown(line18, 30);   
  130.   drawVerticalLineDown(line19, 30);   
  131.   drawVerticalLineDown(line20, 65);   
  132.   drawVerticalLineDown(line21, 30);   
  133.   drawVerticalLineUp(line22, 110);   
  134.   drawVerticalLineUp(line23, 30);   
  135.   drawVerticalLineUp(line24, 80);  
  136.   if(tickCounter > 50)
  137.   {
  138. ​    drawHorizontalLine(line25, 90);
  139. ​    drawVerticalLineUp(line28, 110);   
  140. ​    drawVerticalLineDown(line34, 65);
  141. ​    drawVerticalLineUp(line37, 30);  
  142. ​    drawHorizontalLine(line38, 40);  
  143. ​    drawVerticalLineUp(line40, 80);  
  144. ​    drawVerticalLineDown(line39, 30);  
  145.   }
  146.   if(tickCounter > 150)
  147.   {
  148. ​    drawHorizontalLine(line26, 90);
  149. ​    drawVerticalLineUp(line29, 110);
  150. ​    drawVerticalLineDown(line35, 65);
  151. ​    drawVerticalLineUp(line41, 80);      
  152.   }
  153.   if(tickCounter > 250)
  154.   {
  155. ​    drawHorizontalLine(line27, 90);  
  156. ​    drawVerticalLineUp(line30, 110);  
  157. ​    drawVerticalLineDown(line36, 65);
  158. ​    drawVerticalLineUp(line42, 80);   
  159.   }   
  160.   if(tickCounter > 350)
  161.   {
  162. ​    drawHorizontalLine(line31, 90);  
  163. ​    drawVerticalLineUp(line32, 110);
  164.   }  
  165.   if(tickCounter > 450)
  166.   {
  167. ​    drawVerticalLineUp(line33, 110);
  168.   }  
  169.   if(tickCounter % 50 == 0)
  170.   {
  171. ​    data += 0.1;
  172. ​    if(data > 3.0)
  173. ​    {
  174. ​      data = 1.0;
  175. ​    }
  176. ​    Unicode::snprintfFloat(textCounter1Buffer, TEXTCOUNTER1_SIZE, "%0.1f", data);
  177. ​    textCounter1.invalidate();  
  178. ​    Unicode::snprintfFloat(textCounter2Buffer, TEXTCOUNTER2_SIZE, "%0.1f", data);
  179. ​    textCounter2.invalidate();        
  180.   }
  181. }
2.4风电监控系统 2.4风电监控系统.jpg
动画图像:添加一组同一标识符风车的图像,每个图像间隔的时长为100ms,从头至尾运行,模拟风车动画效果。
2.4 wind_01.png 2.4wind_02.png 2.4wind_03.png 2.4wind_04.png 2.4wind_05.png 2.4wind_06.png 2.4wind_07.png 2.4wind_08.png 2.4wind_09.png 2.4wind_10.png 2.4wind_12.png 2.4wind_13.png
动态图表:应用程序在Y轴显示发电功率,X轴显示时间,实时显示风力发电的功率,动态图表支持三种类型的动态行为:换行并清除、滚动、换行并覆盖。本例选用滚动。
2.4动态图表.jpg
主要实现代码如下:
  1. static uint16_t randomish(int32_t seed)
  2. {
  3.   static uint16_t last = 0;
  4.   const uint16_t num = (seed * (1337 + last)) % 0xFFFF;
  5.   last = num;
  6.   return num;
  7. }

  8. void WindPowerScreenView::handleTickEvent()
  9. {
  10.   tickCounter++;
  11.   if (tickCounter % 5 == 0)
  12.   {
  13. ​    float yMax = WindDynamicGraph.getGraphRangeYMaxAsFloat();
  14. ​    int data = (int)((sinf(tickCounter * .02f) + 1) * (yMax / 2.2f)) + randomish(tickCounter) % (int)(yMax / 10.f);
  15. ​    WindDynamicGraph.addDataPoint(data);
  16. ​    Unicode::snprintf(powerBuffer, POWER_SIZE, "%d", data);
  17. ​    power.invalidate();  
  18. ​    Unicode::snprintf(WindSpeedBuffer, WINDSPEED_SIZE, "%d", data/5);
  19. ​    WindSpeed.invalidate();        
  20.   }
  21. }
2.5新能源汽车充电 2.5充电.jpg
动画图像:按钮按下后,从头至尾运行汽车动画,模拟充电,按下再次按下,动画停止显示。
2.5car_01.png 2.5car_02.png 2.5car_03.png 2.5car_04.png 2.5car_05.png 2.5car_06.png
文本进度条:显示充电进度,并且通过函数batteryTextProgress.setXY(x,y)改变文本的显示坐标。
线性进度条:显示当前充电进度。
主要实现代码如下:
  1. void CarScreenView::handleTickEvent()
  2. {
  3.     if (CarAnimatedImage.isAnimatedImageRunning())   
  4.     {
  5.         tickCounter++;
  6.         if (tickCounter > 1000)
  7.         {
  8.             CarAnimatedImage.stopAnimation();
  9.             Unicode::snprintfFloat(moneyBuffer, MONEY_SIZE, "%0.1f", tickCounter/100);
  10.             money.invalidate();   
  11.             Unicode::snprintfFloat(KwhBuffer, KWH_SIZE, "%0.1f", tickCounter/100);
  12.             Kwh.invalidate();   
  13.             modalWindow.show();                    
  14.         }
  15.         else
  16.         {
  17.             batteryLineProgress.setValue(tickCounter/10);
  18.             batteryTextProgress.setValue(tickCounter/10);
  19.             batteryTextProgress.setXY((43 + (382-43)*tickCounter/10/100), 204);
  20.         }      
  21.     }     
  22. }
模式窗口:是容器类型的控件,用于显示窗口,并禁止对下层视图和控件的触摸事件。充电完成或按键按下后,弹出窗口,显示充电费用和充电电量。
2.5 弹窗.jpg
外部事件作为触发器:物理按钮作为触发器,应用程序响应外部按钮的输入,在TouchGFX/target 下创建KeyController.cpp,KeyController.hpp文件,编写按键初始化函数,以及按键采样函数,按键值设为‘1’,在电脑上模拟时按下数字键盘上的1,可模拟开发板上的物理按钮。
主要实现代码如下:
  1. #ifndef KEYCONTROLLER_HPP
  2. #define KEYCONTROLLER_HPP
  3. #include <platform/driver/button/ButtonController.hpp>
  4. namespace touchgfx
  5. {
  6.         class KeyController : public ButtonController
  7.         {
  8.                 public:
  9.                 virtual void init();
  10.                 virtual bool sample(uint8_t& key);
  11.         };
  12. }
  13. #endif
  1. #include <KeyController.hpp>
  2. #include "stm32h7xx_hal.h"
  3. using namespace touchgfx;

  4. void KeyController::init()
  5. {
  6.     GPIO_InitTypeDef GPIO_Initure;

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

  14. bool  KeyController::sample(uint8_t& key)
  15. {
  16.     static int flag=0;
  17.    
  18.     if (HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_13) == GPIO_PIN_SET)
  19.     {
  20.         if(flag == 0)
  21.         {
  22.             key = '1';
  23.             flag = 1;
  24.             return true;
  25.         }
  26.     }
  27.     else
  28.     {
  29.         flag = 0;
  30.     }
  31.     return false;
  32. }
在TouchGFXHAL.cpp文件添加代码,调用初始化函数,注册按键。
2.5初始化.jpg
  1. void CarScreenView::clickButtonPressed()
  2. {
  3.   if(start == 0)
  4.   {
  5. ​    start = 1;
  6. ​    tickCounter = 0;
  7. ​    CarAnimatedImage.startAnimation(false, false, true);   
  8.   }
  9.   else
  10.   {
  11. ​    start = 0;
  12. ​    CarAnimatedImage.stopAnimation();
  13. ​    Unicode::snprintfFloat(moneyBuffer, MONEY_SIZE, "%0.1f", tickCounter/100);
  14. ​    money.invalidate();  
  15. ​    Unicode::snprintfFloat(KwhBuffer, KWH_SIZE, "%0.1f", tickCounter/100);
  16. ​    Kwh.invalidate();
  17. ​    modalWindow.show();      
  18.   }
  19. }


  20. void CarScreenView::handleTickEvent()
  21. {
  22.   if (CarAnimatedImage.isAnimatedImageRunning())   
  23.   {
  24. ​    tickCounter++;
  25. ​    if (tickCounter > 1000)
  26. ​    {
  27. ​      CarAnimatedImage.stopAnimation();
  28. ​      Unicode::snprintfFloat(moneyBuffer, MONEY_SIZE, "%0.1f", tickCounter/100);
  29. ​      money.invalidate();  
  30. ​      Unicode::snprintfFloat(KwhBuffer, KWH_SIZE, "%0.1f", tickCounter/100);
  31. ​      Kwh.invalidate();  
  32. ​      modalWindow.show();           
  33. ​    }
  34. ​    else
  35. ​    {
  36. ​      batteryLineProgress.setValue(tickCounter/10);
  37. ​      batteryTextProgress.setValue(tickCounter/10);
  38. ​      batteryTextProgress.setXY((43 + (382-43)*tickCounter/10/100), 204);
  39. ​    }   
  40. }   
  41. }
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以及编译工具有没有版本相匹配的问题呢
aoyi 发表于 2022-10-8 15:32 | 显示全部楼层
做图像处理的时候需要自己调整尺寸位置什么的吗
zljiu 发表于 2022-10-8 15:42 | 显示全部楼层
可以动态的调整显示图像的尺寸吗
gwsan 发表于 2022-10-8 15:53 | 显示全部楼层
请问TouchGFXHAL.cpp文件是使用c++编程的吗
tfqi 发表于 2022-10-8 16:04 | 显示全部楼层
如果想要做出图中这个图片的内容的话 是不是有现成的模板可以使用呢
huangchui 发表于 2022-10-8 22:54 | 显示全部楼层
同问,TouchGFX 和cube以及编译工具有没有版本相匹配的问题呢
yjwpm 发表于 2022-10-15 14:24 | 显示全部楼层
连接失效
您需要登录后才可以回帖 登录 | 注册

本版积分规则

40

主题

239

帖子

13

粉丝
快速回复 在线客服 返回列表 返回顶部