STM32实战总结:HAL之HMI

什么是HMI?

HMI人机界面,HMI是Human Machine Interface 的缩写,“人机接口”,也叫人机界面。

人机界面(又称用户界面或使用者界面)是系统和用户之间进行交互和信息交换的媒介, 它实现信息的内部形式与人类可以接受形式之间的转换。凡参与人机信息交流的领域都存在着人机界面。

从上图中,我们可以看到产品里面有TFT-LCD模块。

我们要理解HMI和LCD的关系及区别。通常我们使用一块TFT屏幕,然后需要自己去移植图形化界面。而HMI已经将这一整套系统(比如MCU、存储系统等等)给集成好了,我们只用使用图形软件来做图然后导入程序就行。所以,HMI会缩短开发周期,但是成本高。TFT相对成本更低,但是开发周期长,实际中需要根据具体情况来做选择。

举个例子来说,在一座工厂里头,我们要搜集工厂各个区域的温度、湿度以及工厂中机器的状态等等的信息透过一台主控器监视并记录这些参数,并在一些意外状况发生的时候能够加以处理。这便是一个很典型的SCADA/HMI的运用,一般而言,HMI系统必须有几项基本的能力:

实时的资料趋势显示——把撷取的资料立即显示在屏幕上。

自动记录资料——自动将资料储存至数据库中,以便日后查看。

历史资料趋势显示——把数据库中的资料作可视化的呈现。

报表的产生与打印——能把资料转换成报表的格式,并能够打印出来。

图形接口控制——操作者能够透过图形接口直接控制机台等装置。

警报的产生与记录——使用者可以定义一些警报产生的条件,比方说温度过度或压力超过临界值,在这样的条件下系统会产生警报,通知作业员处理。

补充:SCADA一般指SCADA系统。 SCADA(Supervisory Control And Data Acquisition)系统,即数据采集与监视控制系统。SCADA系统是以计算机为基础的DCS与电力自动化监控系统。

人机界面(HMI)产品的组成及工作原理:

人机界面产品由硬件和软件两部分组成,硬件部分包括处理器、显示单元、输入单元、通讯接口、数据存贮单元等,其中处理器的性能决定了HMI 产品的性能高低,是HMI的核心单元。根据HMI的产品等级不同,处理器可分别选用8位、16位、32位的处理器。HMI软件一般分为两部分,即运行于 HMI硬件中的系统软件和运行于PC机Windows操作系统下的画面组态软件(如JB-HMI画面组态软件)。使用者都必须先使用HMI的画面组态软件制作“工程文件”,再通过PC机和HMI 产品的串行通讯口,把编制好的“工程文件”下载到HMI的处理器中运行。

人机界面产品的基本功能:

设备工作状态显示,如指示灯、按钮、文字、图形、曲线等

数据、文字输入操作,打印输出

生产配方存储,设备生产数据记录

简单的逻辑和数值运算

可连接多种工业控制设备组网

选型指标:

显示屏尺寸及色彩,分辨率

HMI的处理器速度性能

输入方式:触摸屏或薄膜键盘

画面存贮容量,注意厂商标注的容量单位是字节(byte)、还是位(bit)

通讯口种类及数量,是否支持打印功能

人机界面产品分类

薄膜键输入的HMI,显示尺寸小于5.7ˊ,画面组态软件免费,属初级产品。如POP-HMI 小型人机界面;触摸屏输入的HMI,显示屏尺寸为5.7ˊ~12.1ˊ,画面组态软件免费,属中级产品。基于平板PC计算机的、多种通讯口的、高性能HMI,显示尺寸大于10.4ˊ,画面组态软件收费,属高端产品。

人机界面的使用方法

明确监控任务要求,选择适合的HMI产品

在PC机上用画面组态软件编辑“工程文件”

测试并保存已编辑好的“工程文件”

PC机连接HMI硬件,下载“工程文件”到HMI中

连接HMI和工业控制器(如PLC、仪表等),实现人机交互

所有高效能 HMI具备多样的选择及具有竞争力的价格,可靠、高效、聪明的硬件及坚固的外壳,高质量产品,最佳的特点,动画编辑多样、高分辨率的图形,采用C语言程序设计,面板 7"以上可选购不锈钢前框,IP 66K防护等级,可应用于严酷的环境。

 人机界面与人们常说的“触摸屏”有什么区别?

从严格意义上来说,两者是有本质上的区别的。因为“触摸屏”仅是人机界面产品中可能用到的硬件部分,是一种替代鼠标及键盘部分功能,安装在显示屏前端的输入设备;而人机界面产品则是一种包含硬件和软件的人机交互设备。在工业中,人们常把具有触摸输入功能的人机界面产品称为“触摸屏”,但这是不科学的。

人机界面产品中是否有操作系统?

任何人机界面产品都有系统软件部分,系统软件运行在HMI的处理器中,支持多任务处理功能,处理器中需有小型的操作系统管理软件的运行。基于平板计算机的高性能人机界面产品中,一般使用WINCE,LINUX等通用的嵌入式操作系统。

人机界面只能连接PLC吗?

不是这样的。人机界面产品是为了解决PLC的人机交互问题而产生的,但随着计算机技术和数字电路技术的发展,很多工业控制设备都具备了串口通讯能力,所以只要有串口通讯能力的工业控制设备,如变频器、温控仪表、数采模块等都可以连接人机界面产品,来实现人机交互功能。

人机界面只能通过标准的串行通讯口与其它设备相连接吗?

大多数情况下是这样的。但随着计算机和数字电路技术的发展,人机界面产品的接口能力越来越强。除了传统的串行(RS232、RS485)通讯接口外,有些人机界面产品已具有网口、并口、USB口等数据接口,它们就可与具有网口、并口、USB口等接口的工业控制设备相连接,来实现设备的人机的交互。

是否有通讯功能的设备一定能和人机界面产品连接?

应该是这样的。因为通用的人机界面产品都提供了大量的、可供选择的常用设备通讯驱动;一般情况下,只要在人机界面的画面组态软件中选择与连接设备相对应的通讯驱动程序,就可以完成HMI和设备的通讯连接。如果所选HMI产品的组态软件中没有要连接设备的通讯驱动程序,用户则可以把要连接设备的通讯口类型和协议内容告知HMI产品的生产商,请HMI厂商代为编制该设备的通讯驱动程序。

智能串口屏和自己写界面有较大的区别。 

使用智能串口屏时,一般都是通过商家提供的上位机软件,通过绘制界面的方式来生成软件,并下载到串口屏中的芯片。然后,就涉及到我们自己的单片机和串口屏芯片之间的通信,我们自己的单片机也要下载相应的软件,然后通过收发指令来实现插件的控制,好处是方便,不用用户自己写界面程序,缺点是不够灵活,只能使用串口屏提供的功能,而无法灵活地自定义。

相反,自己写界面软件,这些软件都会下载到自己的单片机中,然后再控制屏幕,屏幕中也没有控制系统,只是一个单纯地显示模块。另外,自己写界面,通用性也比较强,不局限于特定的商家,只要满足接口协议即可,不像HMI,各商家的设计和指令集都不一样。

这样对比下来,就算是用HMI,也得先去学一学商家的软件操作,学一学商家的指令集,而且,各商家还不通用,每次都得重新学。用TFT屏幕的话,编写界面的思路是通用的,可以自己编写简单的界面,也可以用LVGL中间件来实现,然后就是TFT屏幕可能用的接口协议不一样,遇到不一样的需要去学新的接口协议。

具体自己根据需要去学吧。建议HMI会用,TFT精通,然后学一学LVGL。

原理图

既然是智能串口屏,就是用串口来传输数据。

就涉及到两个问题:

第一个就是上位机要通过USB转串口连接到串口屏的串口接口上,才能下载界面;

之后就是单片机的串口也要连接到HMI的串口,才能实现数据通信;

所以,原理图上提供了两个互相连接的接口,一个可用来连接单片机,一个可用来连接USB

其实也可以只有一个接口,就是先后进行。同时连接,方便联调而已。

另外要注意,HMI使用的是5V电压,所以必须接上电源口或者USB来提供。

电路连接:

上位机

当使用一款HMI时,首先需要去指定的官网查看使用说明书。

这里的串口屏用的是深圳市淘晶驰电子有限公司的一款TJC3224T128_011R

进入官网串口屏_串口屏方案_串口屏知名厂家排名_深圳淘晶驰电子的资料中心

start [USART HMI 资料中心]

接着下载上位机软件USART HMI

之后可通过该软件绘制所需要的界面并下载。

上位机使用可详见官网资料:上位机基本功能介绍 — 淘晶驰资料中心 1.1.0-2023-04-14 11:05:26 文档

有几个问题要注意:

1、

新建界面工程时,需要选择相应的HMI型号,并设置显示的一些属性比如方向(横屏还是竖屏)等。

每个工程界面都有一个名称,每个名称对应一个编号,通常是从0开始,用来识别该界面。

2、

界面绘制完成后,可以在软件中进行编译(就是编译程序)和调试(预览),然后通过USB转串口的方式将对应的程序下载到HMI中。

3、

显示文字时要有相应的字库才可以。 

字库范围选择指定字符即可。

4、

当制作好各个界面之后,如何实现界面之间的跳转呢?比如在0号界面上按一个按钮就跳到1号界面,然后在1号界面中按返回又可以返回0号界面。这就要用到事件和指令。选中任意一个按钮之后,都会有对应的按下和弹起事件。

之后可以选择在弹起事件中跳转到1号界面,使用相应的指令(详见指令集)。

这里的page是HMI的界面跳转指令,Display是待跳转界面的名称(也可以用编号)。

注意,在指令上方有个“发送键值”按钮,勾选上,就可以将相应的数据发送给单片机。

当我们按下按钮时,就可以实现界面的跳转,同时会返回一个特定的数据。

我们在预览界面看看。

比如上面的数据,65 00 02 00 FF FF FF,65是起始符,00表示页面0,02表示2号控件,00表示是弹起事件,后面的FF FF FF应该是结束符。总之,会返回这样的一串数据。

这个数据意义重大,单片机串口读取到数据后,就知道是进行了什么操作,那么单片机在程序里执行相应的动作就行了。

5、

有时,需要在界面中做一些更复杂的动作,比如按下开关后,显示“开”或者“关”,同时显示不同的数字等,就需要用到一些前端语句。

这里的bt0、n0、n1等都是对应控件的名称。

具体的语法去看说明书。

6、

可以通过简单的语句来使得HMI在上电前执行一些初始化指令,比如改变下载的波特率

7、

单片机可以读取界面的键值来进行不同的动作,那么,如果单片机中有一些数据需要显示到界面中,要怎么操作呢?答案是:通过指令。

比如以下模拟单片机发送指令:

注意图片左下角:

单片机可以发送指令给HMI,让其进行不同的操作。比如跳转到界面1,让控件n1显示1或者2,等等。(解释性语言的特点)

从以上内容可以得出一个结论:

单片机控制HMI,本质上就是通过串口来让单片机接收HMI的键值数据,以及单片机向HMI发送相应的操作指令。

再直白点,就是串口通信。

MX初始化

单纯使用串口,速度太慢。需要结合使用DMA以及空闲中断

先配置串口:

接着配置DMA:

DMA主要用在接收数据时,发送的数据量较小。

配置空闲中断:

开启全局中断,里面包含了空闲中断

其中断的优先级相对配高些:

什么是空闲中断?

空闲中断是:检测到接收数据后,在数据总线上的一个字节时间内,没有接收到数据则触发空闲中断。空闲的定义是总线上在一个字节的时间内没有再接收到数据。空闲中断是检测到有数据被接收后,总线上在一个字节的时间内没有再接收到数据的时候发生的。而总线在什么情况时,会有一个字节时间内没有接收到数据呢?一般就只有一个数据帧发送完成的情况,所以串口的空闲中断也叫帧中断。

大家都知道串口在发数据的时候有起始位,数据位,结束位等等,传统的串口处理数据的方式是一个个字节接收,这样做猛地一看好像没有啥问题,但却留下很多隐患,比如:

1.固定的报头,一个个字节接收少接收一字节,导致报文接收异常;

2.同时开两个115200波特率的串口,因为两个串口本身是独立的,一个个字节接收数据导致频繁进入中断,存在中断嵌套的风险;

3.驱动底层代码会非常繁琐,把一个STM32硬生生玩成一个FPGA,判断报头报尾,CRC校验还需要C语言做成类似Verilog状态机的逻辑等等,笔者在这里不在赘述了,因为不是这篇博客的重点。

串口空闲中断可以简单地理解成,当串口在没有数据传输时会产生的中断,然后我们可以巧妙地把这些数据通过DMA搬运到指定变量地址上,这样当主机不发数据的时候,即可以得到整条报文的数据,和报文的长度,之后再对整条报文进行处理,包括CRC校验,逻辑层赋值,应用层控制等其他相关操作,这样就极大地减少了频繁进入中断的次数,也优化了底层逻辑,因为收到是一整条报文而不是一个个字节。

在普通的接收中断的时候仅仅保存数据,在帧中断的时候需要执行相应处理函数。
如果没有帧中断,必须在接收中断中判断每一个接收数据与帧头帧尾是否相符,效率极低。

空闲中断+串口DMA。不开接收中断,这样收到空闲中断了直接去处理DMA保存过来的数据。这样能减少CPU的负担。

空闲中断是接收数据后出现一个byte的高电平(空闲)状态,就会触发空闲中断.并不是空闲就会一直中断,准确的说应该是上升沿(停止位)后一个byte。

关键代码

/* Includes ------------------------------------------------------------------*/
#include "MyApplication.h"

/* Private define-------------------------------------------------------------*/
#define HMI_Rec_Buffer_LENGTH (uint8_t)20
#define	Protocol_Data_LEN     (uint8_t)7

/* Private variables----------------------------------------------------------*/

static uint8_t ucHMI_Rec_Buffer[HMI_Rec_Buffer_LENGTH]  = {0x00};
static uint8_t ucHMI_EndData[3]                         = {0xFF,0xFF,0xFF};

/* Private function prototypes------------------------------------------------*/  
static void HMI_Init(void);            //HMI初始化
static void HMI_SendString(uint8_t*);  //发送字符串给HMI
static void HMI_Protocol(void);        //接口协议

static void HMI_SendEndData(void);     //发送结束数据
/* Public variables-----------------------------------------------------------*/
HMI_t HMI = 
{
	Page_Main,
	ucHMI_Rec_Buffer,
	FALSE,
	
	HMI_Init,
	HMI_SendString,
	HMI_Protocol
};

/*
	* @name   HMI_SendEndData
	* @brief  发送结束数据
	* @param  None
	* @retval None      
*/
static void HMI_SendEndData(void) 
{
	//连续发送3个0xFF
	HAL_UART_Transmit(&huart_HMI,ucHMI_EndData,(uint8_t)3,0x0A);
}

/*
	* @name   HMI_Init
	* @brief  HMI初始化
	* @param  None
	* @retval None      
*/
static void HMI_Init(void) 
{
	HMI_SendEndData();
  //显示屏默认显示主界面
	HMI.SendString((uint8_t*)"page 0");
}

/*
	* @name   HMI_SendString
	* @brief  发送字符串给HMI
	* @param  pucStr -> 指向待发送字符串首地址的指针
	* @retval None      
*/
static void HMI_SendString(uint8_t* pucStr) 
{

	HAL_UART_Transmit(&huart_HMI,pucStr,strlen((const char*)pucStr),0x0A);
	HMI_SendEndData();
}

/*
	* @name   HMI_Protocol
	* @brief  接口协议 - 处理HMI的键值信息
	* @param  None
	* @retval None      
*/
static void HMI_Protocol(void) 
{
		uint8_t Temp_Array[7] = {0x00};
		uint8_t i = 0,Index = 0;
	
		//串口1停止DMA接收
		HAL_UART_DMAStop(&huart1);
	
		//读取HMI缓存数据,共7个字节,起始值为0x65
		for(i=0;i<HMI_Rec_Buffer_LENGTH;i++)
		{
			//检测键值起始数据0x65
			if(Index == 0)
			{
				if(*(HMI.pucRec_Buffer+i) != 0x65)
					continue;
			}
			
			Temp_Array[Index] = *(HMI.pucRec_Buffer+i);

			//已读取7个字节
			if(Index == Protocol_Data_LEN)
				break;
			
			Index++;
		}
		
		//串口1开启DMA接收
		HAL_UART_Receive_DMA(&huart1,HMI.pucRec_Buffer,(uint16_t)20);
		
		//处理数据
		if(Index == Protocol_Data_LEN)
		{
			//主页面的键值信息
			if(Temp_Array[1] == 0x00)
			{
				//控件2弹起事件
				if((Temp_Array[2] == 0x02) && (Temp_Array[3] == 0x00))
				{
					//切换至数码管页面
					HMI.Page = Page_Display;
					Display.Disp_Clr();
				}
				
				//控件3弹起事件
				if((Temp_Array[2] == 0x03) && (Temp_Array[3] == 0x00))
				{
					//切换至电机页面
					HMI.Page = Page_Step_Motor;
					
					Display.Disp_Clr();
					//显示电机圈数
					Display.Disp_HEX(Disp_NUM_6,Unipolar_Step_Motor.Circle,Disp_DP_OFF);
					//显示电机速度
					switch(Unipolar_Step_Motor.Speed)
					{
						case 100: Display.Disp_HEX(Disp_NUM_1,1,Disp_DP_OFF); break;
						case 90:  Display.Disp_HEX(Disp_NUM_1,2,Disp_DP_OFF); break;
						case 80:  Display.Disp_HEX(Disp_NUM_1,3,Disp_DP_OFF); break;
						case 70:  Display.Disp_HEX(Disp_NUM_1,4,Disp_DP_OFF); break;
						case 60:  Display.Disp_HEX(Disp_NUM_1,5,Disp_DP_OFF); break;
						case 50:  Display.Disp_HEX(Disp_NUM_1,6,Disp_DP_OFF); break;
						case 40:  Display.Disp_HEX(Disp_NUM_1,7,Disp_DP_OFF); break;
						case 30:  Display.Disp_HEX(Disp_NUM_1,8,Disp_DP_OFF); break;
						case 20:  Display.Disp_HEX(Disp_NUM_1,9,Disp_DP_OFF); break;
						default: break;
					}
				}
			}
			
			//数码管页面的键值信息
			if(Temp_Array[1] == 0x01)
			{
				//控件2弹起事件
				if((Temp_Array[2] == 0x01) && (Temp_Array[3] == 0x00))
				{
					//切换至数码管页面
					HMI.Page = Page_Main;				
					Display.Disp_Clr();
				}
			}
			
			//步进电机页面的键值信息
			if(Temp_Array[1] == 0x02)
			{
				//控件11弹起事件
				if((Temp_Array[2] == 0x0B) && (Temp_Array[3] == 0x00))
				{
					//切换至主页面
					HMI.Page = Page_Main;
					Display.Disp_Clr();
					//关闭步进电机
					Unipolar_Step_Motor.Status = Stop_State;
					CLR_Motor_A;
					CLR_Motor_B;
					CLR_Motor_C;
					CLR_Motor_D;
					LED.LED_OFF(LED3);
					
					Unipolar_Step_Motor.Circle = 0;
				}
				
				//控件5弹起事件 - 对于开关机按键
				if((Temp_Array[2] == 0x05) && (Temp_Array[3] == 0x00))
				{
					HMI.Page_Step_Motor_KEY_Flag = TRUE;
					HAL_GPIO_EXTI_Callback(KEY1_Pin);
					HMI.Page_Step_Motor_KEY_Flag = FALSE;
				}
				
				//控件8弹起事件 - 对应正反向按键
				if((Temp_Array[2] == 0x08) && (Temp_Array[3] == 0x00))
				{
					HMI.Page_Step_Motor_KEY_Flag = TRUE;
					HAL_GPIO_EXTI_Callback(KEY2_Pin);
					HMI.Page_Step_Motor_KEY_Flag = FALSE;
				}
				
				//控件6弹起事件 - 对于加速按键
				if((Temp_Array[2] == 0x06) && (Temp_Array[3] == 0x00))
				{
					HMI.Page_Step_Motor_KEY_Flag = TRUE;
					HAL_GPIO_EXTI_Callback(KEY3_Pin);
					HMI.Page_Step_Motor_KEY_Flag = FALSE;
				}
				
				//控件7弹起事件 - 对应减速按键
				if((Temp_Array[2] == 0x07) && (Temp_Array[3] == 0x00))
				{
					HMI.Page_Step_Motor_KEY_Flag = TRUE;
					HAL_GPIO_EXTI_Callback(KEY4_Pin);
					HMI.Page_Step_Motor_KEY_Flag = FALSE;
				}				
			}
		}
}
/********************************************************
  End Of File
********************************************************/
  • 4
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
在实践中,我们可以使用STM32F0系列微控制器以及HAL库来开发高速显示的PDF应用程序。 首先,我们需要配置STM32F0微控制器的运行环境。通过HAL库提供的功能,我们可以轻松地初始化外部时钟和系统时钟,以确保微控制器正常运行。 接下来,我们需要选择一个合适的PDF库,如mupdf,来解析和渲染PDF文件。通过HAL库提供的UART和SPI功能,我们可以将PDF文件从计算机发送到STM32F0微控制器,并使用PDF库进行解析和渲染。 在解析和渲染PDF文件的过程中,我们可以利用STM32F0微控制器的强大计算和图形处理能力。HAL库提供了丰富的功能,如GPIO、DMA和定时器,可以帮助我们高效地处理图形数据和页面渲染。 为了实现高速显示,我们可以使用DMA功能将解析和渲染的图像数据直接发送到屏幕上。这样可以减少CPU的负担,并提高整体的显示性能。同时,HAL库的中断机制可以使我们实现流畅的页面切换和用户交互。 最后,我们可以通过HAL库提供的GPIO和SPI功能将STM32F0微控制器连接到合适的显示设备上,如OLED或LCD屏幕。通过初始化和配置相应的引脚和接口,我们可以将渲染的PDF页面显示在屏幕上,实现高显生的PDF应用程序。 总结而言,通过使用STM32F0微控制器和HAL库进行实战开发,我们可以轻松地实现高速显示的PDF应用程序。HAL库提供了丰富的功能和易于使用的接口,使得开发过程更加简单和高效。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值