#申请原创# @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
|