外设驱动库开发笔记35:迪文触摸屏驱动

  有些时候嵌入式系统也需要显示更为复杂的图形,需要更丰富的数据展示。为此,我们需要更大,色彩更丰富,带触屏的显示屏,当然性价比更高就最好了。在我们的项目中遇到此类需求,我们有时会选择DWIN触摸屏。在本篇中,我们就来设计并实现DWIN触摸屏的驱动。

1、功能概述

  我们这里所说的是迪文的串口屏,该屏有多种接口类型,有使用RS485接口的屏,也有可通过跳线实现TTL接口或RS232接口的屏。但不论什么接口均采用相同的通讯协议。迪文串口屏采用的通讯协议的完整指令结构如下图所示:

  其中,CRC校验不包括帧头和数据长度,仅针对指令和数据进行校验。CRC 校验采用 ANSI CRC-16 (X16+X15+X2+1)格式。当启用 CRC 帧校验并开启自动应答功能后(R2.4=1,RC.3=1),DGUS 屏会在 CRC 校验完成后自动应答校验情况,返回指令结构如下:

帧头**+02+****(DGUS 屏接收的)指令+数据(0xFF 表示 CRC **校验正确,0x00 表示 CRC **校验错误)+CRC。**

  迪文的DGUS 把用户图形界面的每一个页面分解成多个控件变量,即 DGUS 屏采用变量驱动模式工作,屏的工作模式和GUI的状态完全由数据变量来控制。因此,串口指令也只需要对变量进行读、写即可,指令集非常简单,一共只有5条指令。读写指令集如下图所示:

  配置寄存器空间是用于存放指令状态的,比如RTC(实时时间)、背光亮度等实时的状态。了解寄存器的地址以及各寄存器的功能,就可以通过串口指令来实现上位机与DGUS屏信息传输及控制。寄存器地址0x00~0xFF,具体功能查看迪文寄存器功能说明。

2、驱动设计与实现

  已经了解了迪文屏的通讯协议,我们就可以据此编写迪文屏的驱动程序。我们知道迪文屏的通讯协议有5个指令,我们的驱动就是通过这5个指令操作迪文屏。

2.1、对象定义

  我们们依然采用基于对象的操作方式来实现,所以首先我们依然是定义迪文屏的对象类型。具体定义如下:

/* 定义迪文串口屏对象类型 */
typedef struct DwinObject {DwinCheckCodeType checkMode;       //校验方式void (*SendData)(uint8_t *txData,uint16_t length);   //发送数据void (*GetRegister)(struct DwinObject *dwin,uint8_t regAddress,uint8_t readByteLength);void (*SetRegister)(struct DwinObject *dwin,uint8_t regAddress,uint8_t *txData,uint16_t length);
}DwinObjectType;

  迪文屏对象类型我们并没有抽象出太多属性,因为屏作为从设备并没有返回太多信息,也没有什么选择特性。考虑到通讯信息的校验可以选择是否启用,所以我们将其抽象为属性以区别于不同的情况。

  在对象使用之前同样需要对其初始化,所以我们需要对象初始化函数。初始化函数如下:

/* 初始化迪文串口屏对象 */
void DwinInitialization(DwinObjectType *dwin,DwinCheckCodeType checkMode,SendDataForDwinType SendData)
{if((dwin==NULL)||(SendData==NULL)){return;}dwin->checkMode=checkMode;dwin->SendData=SendData;dwin->GetRegister=GetRegisterDataFromDwinLCD;dwin->SetRegister=SetRegisterDataToDwinLCD;
}

2.2、对象操作

  定义并初始化过的对象就可以对其进行操作。我们已经说过,迪文屏通讯协议有5个操作码。分别是:0x80、写寄存器;0x81,读寄存器;0x82,写数据存储器;0x83,读数据存储器;0x84,写曲线缓冲区。我们对屏的操作就是实现对这5个操作码的使用。

2.2.1、写寄存器

  向指定的寄存器地址写入数据,指令码为0x80。该命令支持多个寄存器的连续写操作,但最多只能写入16个字节的数据。我们按照前面说的消息帧的格式编写操作函数如下:

/*写寄存器数据,一次最多允许写16个字节,即length<=16*/
static void SetRegisterDataToDwinLCD(DwinObjectType *dwin,uint8_t regAddress,uint8_t *txData,uint16_t length)
{/*命令的长度由帧头(2个字节)+数据长度(1个字节)+指令(1个字节)+寄存器地址(1个字节)+写的数据(最多16字节)+CRC校验(2个字节)*/uint16_t cmd_Length=length+5;uint8_t cmd_Reg_Write[23];cmd_Reg_Write[0]=0x5A;cmd_Reg_Write[1]=0xA5;cmd_Reg_Write[2]=(uint8_t)(length+2);cmd_Reg_Write[3]= FC_REG_Write;cmd_Reg_Write[4]=regAddress;for(int dataIndex=0;dataIndex<length;dataIndex++){cmd_Reg_Write[dataIndex+5]=txData[dataIndex];}if(dwin->checkMode>DwinNone){uint16_t checkCode=CalcDwinCRC16(&cmd_Reg_Write[3],length+2);cmd_Reg_Write[length+5]=(uint8_t)checkCode;cmd_Reg_Write[length+6]=(uint8_t)(checkCode>>8);cmd_Length=cmd_Length+2;}dwin->SendData(cmd_Reg_Write,cmd_Length);
}

2.2.2、读寄存器

  从指定的寄存器地址开始读取指定字节长度的数据,指令码为0x81。一次读取一到多个寄存器。由于寄存器地址是0x00到0xFF,所以理论上可以一次读取全部寄存器。我们可以根据消息格式编写操作函数如下:

/*读寄存器数据*/
static void GetRegisterDataFromDwinLCD(DwinObjectType *dwin,uint8_t regAddress,uint8_t readByteLength)
{/*命令的长度由帧头(2个字节)+数据长度(1个字节)+指令(1个字节)+寄存器地址(1个字节)+读取寄存器的字节长度(1个字节)+CRC校验(2个字节)*/uint16_t cmd_Length=6;uint8_t cmd_Reg_Read[]={0x5A,0xA5,0x03,FC_REG_Read,0x00,0x00,0x00,0x00};//读数据命令cmd_Reg_Read[4]=regAddress;cmd_Reg_Read[5]=readByteLength;if(dwin->checkMode>DwinNone){uint16_t checkCode=CalcDwinCRC16(&cmd_Reg_Read[3],3);cmd_Reg_Read[6]=(uint8_t)checkCode;cmd_Reg_Read[7]=(uint8_t)(checkCode>>8);cmd_Length=cmd_Length+2;}dwin->SendData(cmd_Reg_Read,cmd_Length);
}

2.2.3、写存储器

  从指定的变量存储器地址开始写入数据串(字数据)到变量存储区,指令码为0x82。存储区与寄存器不一样,地址和数据都是16位的。理论上说一次可写差不多100个字的数据,事实上通常不建议这种极限方式。所以我们将长度限制在100个字节以内。我们可以根据消息格式编写操作函数如下:

/*写数据变量存储器,一次最多允许写47个字,即length<=94*/
void WriteFlashDataToDwinLCD(DwinObjectType *dwin,uint16_t startAddress,uint8_t *txData,uint16_t length)
{/*命令的长度由帧头(2个字节)+数据长度(1个字节)+指令(1个字节)+起始地址(2个字节)+数据(长度为length)+CRC校验(2个字节)*/uint16_t cmd_Length=length+6;uint8_t cmd_VAR_Write[102];cmd_VAR_Write[0]=0x5A;cmd_VAR_Write[1]=0xA5;cmd_VAR_Write[2]=(uint8_t)(length+3);cmd_VAR_Write[3]= FC_VAR_Write;cmd_VAR_Write[4]=(uint8_t)(startAddress>>8);//起始地址cmd_VAR_Write[5]=(uint8_t)startAddress;//起始地址for(int dataIndex=0;dataIndex<length;dataIndex++){cmd_VAR_Write[dataIndex+6]=txData[dataIndex];}if(dwin->checkMode>DwinNone){uint16_t checkCode=CalcDwinCRC16(&cmd_VAR_Write[3],length+2);cmd_VAR_Write[length+6]=(uint8_t)checkCode;cmd_VAR_Write[length+7]=(uint8_t)(checkCode>>8);cmd_Length=cmd_Length+2;}dwin->SendData(cmd_VAR_Write,cmd_Length);
}

2.2.4、读存储器

  从变量存储区指定地址开始读取指定字长度的字数据,指令码为0x83。读取操作理论也可以读取256个字节,其实显示屏主要用于数据展示,主要是接收主机发来的数据。需要发送给主站的数据很有限。我们可以根据消息格式编写操作函数如下:

/*读变量存储器数据*/
void ReadFlashDataFromDwinLCD(DwinObjectType *dwin,uint16_t startAddress,uint8_t readWordLength)
{/*命令的长度由帧头(2个字节)+数据长度(1个字节)+指令(1个字节)+起始地址(2个字节)+读取的字长度(1个字节)+CRC校验(2个字节)*/uint16_t cmd_Length=7;uint8_t cmd_VAR_Read[]={0x5A,0xA5,0x04,FC_VAR_Read,0x00,0x00,0x00,0x00,0x00};//读数据命令cmd_VAR_Read[4]=(uint8_t)(startAddress>>8);//起始地址cmd_VAR_Read[5]=(uint8_t)startAddress;//起始地址cmd_VAR_Read[6]=readWordLength;//读取长度if(dwin->checkMode>DwinNone){uint16_t checkCode=CalcDwinCRC16(&cmd_VAR_Read[3],4);cmd_VAR_Read[7]=(uint8_t)checkCode;cmd_VAR_Read[8]=(uint8_t)(checkCode>>8);cmd_Length=cmd_Length+2;}dwin->SendData(cmd_VAR_Read,cmd_Length);
}

2.2.5、写曲线缓冲区

  DGUS屏有一个16KB、可存储8条曲线趋势图的曲线缓冲区,用于用户简单、快速显示曲线。曲线缓冲区的数据都是16位无符号数。写曲线缓冲区的指令码为0x84。我们可以根据消息格式编写操作函数如下:

/*写曲线缓冲区,一次最多允许写8个字,即length<=16*/
void WriteCurveToDwinLCD(DwinObjectType *dwin,uint8_t *txData,uint16_t length,uint8_t channelMode)
{/*命令的长度由帧头(2个字节)+数据长度(1个字节)+指令(1个字节)+通道模式(1个字节)+数据(length,最多8个字)+CRC校验(2个字节)*/uint16_t cmd_Length=length+5;uint8_t cmd_Curve_Write[23];//写曲线缓冲区命令cmd_Curve_Write[0]=0x5A;cmd_Curve_Write[1]=0xA5;cmd_Curve_Write[2]=(uint8_t)(length+2);cmd_Curve_Write[3]= FC_Curve_Write;cmd_Curve_Write[4]=channelMode;for(int dataIndex=0;dataIndex<length;dataIndex++){cmd_Curve_Write[dataIndex+5]=txData[dataIndex];}if(dwin->checkMode>DwinNone){uint16_t checkCode=CalcDwinCRC16(&cmd_Curve_Write[3],length+2);cmd_Curve_Write[length+5]=(uint8_t)checkCode;cmd_Curve_Write[length+6]=(uint8_t)(checkCode>>8);cmd_Length=cmd_Length+2;}dwin->SendData(cmd_Curve_Write,cmd_Length);
}

3、驱动的使用

  我们已经实现了迪文触摸屏的驱动,接下来我们就使用驱动开发应用。驱动的使用并不复杂,依然是定义对象,然后根据需要操作对象。

3.1、声明并初始化对象

  首先我们需要使用DwinObjectType类型声明一个迪文触摸屏的对象变量。这就是一个具体的屏对象,具体如下:

  DwinObjectType lcd;

  当然这个对象还不能使用,因为器并未赋值。所以我们还要使用DwinInitialization函数初始化这个对象。在初始化之前我们必须定义一个形如void (*SendDataForDwinType)(uint8_t *txData,uint16_t length)的函数,具体如下:

//数据发送
void SendData(uint8_t *txData,uint16_t length)
{uint16_t i;for(i=0;i<length;i++){//传送寄存器不为空,等待传送结束while(USART_GetFlagStatus(USART3, USART_FLAG_TXE) == RESET){}// 写一个字节到对应的串口传送数据寄存器USART_SendData(USART3, txData[i]);}
}

  并将函数指针传递作为参数传递给初始化函数。除了屏对象和发送数据函数指针外,初始化函数还有一个参数是校验方式。这里我们选择无校验码,所以初始化函数调用如下:

  DwinInitialization(&lcd,DwinNone,SendData);

  到这里对象的定义及初始化就完成了。

3.2、调用函数操作对象

  初始化之后的对象就可对其进行操作了。我们在前面已经针对5个操作码实现了对对象的驱动。那么我们要操作对象时,就是调用这5个函数来实现的。

  向屏的数据存储区写数据时,需要调用WriteFlashDataToDwinLCD函数,如我们要向0x0000地址开始写入8个字节的数据则:

  WriteFlashDataToDwinLCD(&lcd,0x0000,txData,8);

  从屏的数据存储区读取数据时,需要调用ReadFlashDataFromDwinLCD函数,如我们从0x000A地址开始读取4个字长度的数据则:

  ReadFlashDataFromDwinLCD(&lcd,0x000A,4);

  向曲线缓冲区写数据,总共有8个通道,一次最多允许写8个字。通道模式用于选择向哪些通道写数据,每一位代表一个通道。所以我们在使用WriteCurveToDwinLCD函数写曲线缓冲区时需要配置通道。比如我们要向8个通道写8个字的数据则:

  WriteCurveToDwinLCD(&lcd,txData,16,0xFF);

  对于寄存器的读写操作,我们封装了一些常用的,如读取LCD系统时间、校准LCD系统时间等。

  读取LCD系统时间:GetDateTimeFromDwinLCD(&lcd);

  校准LCD系统时间:CalibrationDateTimeForDwinLCD(&lcd,dateTime);

  音乐播放控制:HandleDwinLCDToPlayMusic(&l’c’d,playStart,playNum,volume);当playNum为0时表示停止播放。

  设置屏显示画面:SetDwinLCDDisplay(&lcd,picID);

  对于没有封装的寄存器操作,可以直接在对象中调用寄存器读写函数实现。如:

  lcd.GetRegister(&lcd, regAddress,readByteLength);

  lcd.SetRegister(&lcd,regAddress,txData,length);

4、应用总结

  我们通过实测,驱动迪文触摸屏的操作结果与预期一致。我们让MCU给显示屏发送一些数据,并在屏上显示出来:

  再来看看对传感器做一下扰动时(用配气仪和小型气泵向传感器管道送不同的气)数据的变化,传感器检测对象变化是屏幕显示也变化。

  再来改变一下气体成分和气泵的转速看看数据的变化:

  经过以上实验,迪文串口屏驱动已经达到预期。至于一些更复杂的操作方式也都可以以此为基础实现之。

欢迎关注:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/499311.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

快速实现一个室内空气质量检测仪

冬天我们大多会关闭门窗&#xff0c;而依靠暖通空调设备来维持室内温度。而在保证居室温度的同时&#xff0c;我们也希望保持居室内大气环境的健康度。鉴于此&#xff0c;我们设计了一个简单的室内空气质量检测器。 1、系统概述 我们依靠暖通空调设备来维持室内温度、湿度和通…

外设驱动库开发笔记36:NTC负温度系数热电阻测温驱动

在嵌入式产品中&#xff0c;温度检测非常常见。在成本比较敏感而精度要求较低时&#xff0c;NTC电阻是个不错的选择。在这一篇中&#xff0c;我们将讨论如何和设计并实现一个通用的NTC驱动&#xff0c;以便在后续的项目中更方便的复用。 1、功能概述 NTC是指随温度上升电阻呈指…

外设驱动库开发笔记37:S1336-5BQ光敏二极管作为光度计驱动

光敏二极管能够实现很多应用&#xff0c;用于光度检测即是其一。我们在一些产品中就曾使用S1336-5BQ光敏二极管进行光度值检测。所以在本篇中&#xff0c;我们将讨论如何设计并实现S1336-5BQ光敏二极管用于光度检测的驱动。 1、功能概述 根据相关的资料&#xff0c;光电二极管…

PID控制器改进笔记之六:改进PID控制器之参数设定

前面我们发布了一系列PID控制器相关的文章&#xff0c;包括经典PID控制器以及参数自适应的PID控制器。这一系列PID控制器虽说实现了主要功能&#xff0c;也在实际使用中取得了良好效果&#xff0c;但还有很多的细节部分可以改进以提高性能和灵活性。这篇中我们来讨论改进PID控制…

软件设计开发笔记1:基于状态机的程序设计

在编码实现的过程中&#xff0c;我们会经常使用到条件判断结构&#xff0c;而且使用起来很方便。但是在需要转移的状态比较多&#xff0c;或是条件比较复杂时&#xff0c;我们就可能需要很长的条件判断结构来处理。不过&#xff0c;过于复杂的条件判断结构会给代码的编写和维护…

外设驱动库开发笔记38:RTD热电阻测温驱动

我们已经讨论过多种温度检测方式&#xff0c;但我们尚未关注热电阻温度检测&#xff0c;但热电阻测温在工业环境中是非常常见的。尽管有很多集成的数字式的热电阻接口元器件&#xff0c;但这些器件不但成本较高&#xff0c;灵活性也大打折扣。所以我们有时会使用更简单灵活的电…

外设驱动库开发笔记39:按键操作驱动

按键在我们的项目中是经常使用到的组件。一般来说&#xff0c;我们都是在用到按键时直接针对编码&#xff0c;但这样每次都做很多重复性的工作。所以在这里我们考虑做一般性抽象得到一个可应用于按键操作的通用性驱动程序。 1、功能概述 按键操作在我们的产品种经常用到&#…

外设驱动库开发笔记40:AT25xxx外部存储器驱动

我们在前面开发过AT24CXX系列EEPROM存储器&#xff0c;它使用的是I2C接口。不过有时候我们也会使用SPI接口的EEPROM存储器。在这一篇我们将来讨论AT25XXX系列EEPROM存储器的驱动设计、实现及使用。 1、功能概述 AT25XXX系列EEPROM存储器采用SPI接口&#xff0c;因其操作简单且…

闪存中的键值对:无文件系统 minINI

许多嵌入式系统应用需要以持久的方式存储某种数据&#xff1a;校准值、设置或日志信息。对于较少的数据量&#xff0c;使用外部存储器或文件系统是一种过度大材小用。在许多系统中&#xff0c;我使用minINI以“ini-file”的方式存储键值解析&#xff0c;但它需要使用某种文件系…

外设驱动库开发笔记41:ADS1256 ADC驱动

我们经常会碰到多通道AD采集的需求&#xff0c;有时候甚至需要高精度的ADC器件。本篇我们将来设计并实现ADS1256模数转换器的驱动。并简单讨论该驱动使用方式。 1、功能概述 ADS1256是TI公司推出的一款低噪声高分辨率的24位Sigma-Delta(E-v)模数转换器(ADC)。E-vADC与传统的逐…

PID参数自整定库之一:继电反馈整定算法

在前述的篇章中&#xff0c;我们实现了PID控制器并在后续对其进行了改进。但作为经典PID控制器还存在PID参数整定的问题。通常我们可以采取人工整定的办法&#xff0c;但人工整定涉及到比较专业的知识&#xff0c;而且找到合适的参数本身也不是一件容易的事&#xff0c;所以人们…

外设驱动库开发笔记42:DAC8552 DAC驱动

模拟信号输出是经常会遇到的应用需求&#xff0c;解决的办法应多种&#xff0c;但我们使用最多的还是数模转换。对于不同的数模转换器我们需要为其编写适用的驱动程序&#xff0c;在这一篇中我们就来考虑如何实现DAC8552高精度模数转换器的驱动程序。 1、功能概述 该DAC8552是…

软件设计开发笔记2:基于QT设计串口调试工具

串口通信是我们经常会遇到的问题。很多时候当我们设计一个串口应用时&#xff0c;我们希望有一个简便的、可视的方式来验证它。这一篇中我们就来基于QT设计一个串口调试工具。 1、概述 在开始软件设计之前&#xff0c;我们来简略地分析一下这样一个小软件其要包含的主要内容有…

外设驱动库开发笔记43:GPIO模拟SPI驱动

SPI总线是我们常用的串行设备接口&#xff0c;一般情况下我们都会适应硬件SPI接口&#xff0c;但有些时候当硬件端口不足时&#xff0c;我们也希望可以使用软件来模拟SPI硬件接口&#xff0c;特别是要求不是很高的时候。在这一篇中我们将来讨论如何使用GPIO和软件来模拟SPI通讯…

外设驱动库开发笔记44:DDC114 ADC驱动

在产品设计过程中&#xff0c;很多时候都会用到ADC器件&#xff0c;而在一些特殊场合还需要一些特别的ADC器件。我们在这篇中将讨论常用于医疗器件方面的&#xff0c;DDC114这款电流输入ADC&#xff0c;并为其设计一个驱动程序。 1、功能概述 模数转换器DDC114是一款电流输入型…

PID控制器改进笔记之七:改进PID控制器之防超调设定

我们已经设计了PID控制器&#xff0c;并根据实际使用的情况对器进行了诸多的改进。在这一篇中我们将讨论如何改进PID控制器超调的问题。 1、问题提出 在前面的文章中&#xff0c;我们曾推导过增量式PID控制器的公式&#xff0c;并且对其进行了离散化以适用于程序实现&#xff…

软件设计开发笔记3:基于QT的Modbus RTU主站

Modbus是一种常见的工业系统通讯协议。在我们的设计开发工作中经常使用到它。在这一篇中我们将简单实现一个基于QT的Modbus RTU主站上位工具。 1、概述 Modbus RTU主站应用很常见&#xff0c;有一些是通用的&#xff0c;有一些是专用的。而这里我们希望实现一个主要针对我们的…

外设驱动库开发笔记45:MS4515DO压力传感器驱动

很多时候我们需要检测流量和压力这些参数&#xff0c;比如我们要检测大气压&#xff0c;或者通过测量差压来获得输送流体的流量等&#xff0c;都需要用到压力传感器。这一篇我们就来讨论MS4515DO压力传感器的数据获取。 1、功能概述 MS4515DO是TE公司推出的一款基于PCB安装的小…

外设驱动库开发笔记48:MCP4725单通道DAC驱动

在产品设计过程中&#xff0c;我们经常会遇到数模转换的应用需求。在本篇种我们就来讨论一下MCP4725单通道数模转换器的驱动设计与实现。 1、功能概述 MCP4725是一个低功耗&#xff0c;高精度&#xff0c;单通道&#xff0c;12位缓冲电压输出数字到模拟转换器(DAC)与非易失性存…

如何确保不使用动态内存

在许多嵌入式应用程序中&#xff0c;内存分配必须是静态的&#xff0c;而不是动态的。意味着在应用程序中不应使用对malloc()或free()等内容的调用&#xff0c;因为它们可能会在运行时失败&#xff08;内存不足、堆碎片&#xff09;。 但是&#xff0c;当与第三方库甚至 C/C 标…