我无法掩饰自己的机动之情,太感谢原创作者。这几天我一直很愁自己什么都不会。真心想拜拜大神。赶紧的转过来。出处http://blog.csdn.net/u013704336/article/details/50850926
硬件电路主要包括以下几个:
下面就分别讲解着三个电路的设计
主控电路采用的是STM32F103C8T6单片机,关于为什么选择这个型号的,主要有两个原因,一是稍微便宜一些,淘宝上便宜一点的6块多大洋就可以了;二是因为综合考虑了下我能使用到的资源,选择这个型号的已经够用了。如下图所示:
主控电路实际上就是一个单片机的最小系统的设计,而最小系统的设计主要包括:电源部分、晶振部分、程序下载部分、芯片电路部分。其中下载电路可以根据自己的需要进行设计,可以选择USB下载或者JTAG下载,两种方式根据自己的实际情况进行选择。
以下是我的主控部分的电路原理图:
STM32最小系统原理图
从图中可以看出,我的硬件中同时有USB下载和JTAG下载,方便后期根据自己情况进行选择,在程序调试过程中,不得否认JTAG的作用还是挺大的,调试非常的方便。
本次使用到的传感器主要有:DS18B20(温度传感器),MQ_2(烟雾传感器).DHT11(温湿度传感器),TELESKY(光敏传感器),HC-SR501(人体红外感应模块)、BEEP(蜂鸣器)。这几个传感器很普通,也就是常见的模块,以下是主要的电路图:
DS18B20 是由 DALLAS 半导体公司推出的一种的“一线总线”接口的温度传感器。具有体积小,硬件开销低,抗干扰能力强、精度高的特点,测量温度范围为-55~+125℃,精度为±0.5℃。现场温度直接以“一总线“的数字方式传输,打打提高了系统的抗干扰性。为了方便,设定分辨率及报警温度存储在EEPROM中,掉电后任然保存。
DS18B20测温原理下图示。图中低温度系数晶振的振荡频率受温度影响很小,用于产生固定频率的脉冲信号送给计数器1。高温度系数晶振随温度变化其振荡率明显改变,所产生的信号作为计数器2的脉冲输入。计数器1和温度寄存器被预置在-55℃所对应的一个基数值。计数器1对低温度系数晶振产生的脉冲信号进行减法计数,当计数器1的预置值减到0时,温度寄存器的值将加1,计数器1的预置将重新被装入,计数器1重新开始对低温度系数晶振产生的脉冲信号进行计数,如此循环直到计数器2计数到0时,停止温度寄存器值的累加,此时温度寄存器中的数值即为所测温度。斜率累加器用于补偿和修正测温过程中的非线性,其输出用于修正计数器1的预置值。
DS18B20通讯方式为单总线结构!
所有的单总线器件要求采用严格的信号时序,以保证数据的完整性。 DS18B20 共有 6 种信号类型:复位脉冲、应答脉冲、写 0、写 1、读 0 和读 1。所有这些信号,除了应答脉冲以外,都由主机发出同步信号。并且发送所有的命令和数据都是字节的低位在前。
其他详细介绍可以查看芯片手册。或者从我后期资料包中寻找。
DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数字模块采集技术和温湿度传感技术,确保产品具有极高的可靠性与卓越的长期稳定性。传感器包括一个电阻式感湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接。因此该产品具有品质卓越、超快响应、抗干扰能力强、性价比极高等优点。每个DHT11传感器都在极为精确的湿度校验室中进行校准。校准系数以程序的形式储存在OTP内存中,传感器内部在检测信号的处理过程中要调用这些校准系数。单线制串行接口,使系统集成变得简易快捷。超小的体积、极低的功耗,信号传输距离可达20米以上,使其成为各类应用甚至最为苛刻的应用场合的最佳选则。产品为4针单排引脚封装。连接方便,特殊封装形式可根据用户需求而提供。
DATA用于微处理器与DHT11之间的通讯和同步,采用单总线数据格式,一次通讯时间4ms左右,数据分小数部分和整数部分,具体格式在下面说明,当前小数部分用于以后扩展,现读出为零.操作流程如下:一次完整的数据传输为40bit,高位先出。数据格式:8bit湿度整数数据+8bit湿度小数数据+8bi温度整数数据+8bit温度小数数据+8bit校验和数据传送正确时校验和数据等于“8bit湿度整数数据+8bit湿度小数数据+8bi温度整数数据+8bit温度小数数据”所得结果的末8位。
主要参数:
DHT11通信方式也是属于单总线,和DS18B20类似。
该模块有两种方式进行数据采集,模拟和数字。这里我选择的是模拟量的,因为我的上位机需要历史数据,数字式的只有0和1。具体介绍可以从代码中看出。
当有人进入其感应范围则输入高电平,人离开感应范围则自动延时关闭高电平。输出低电平。因此这个模块很容易编写代码进行驱动。
光敏传感器和烟雾传感器驱动方式很类似,都可以利用STM32内部的ADC进行驱动转化,进而可以得到更加准确的数据。
这篇博客就来说说界面的问题吧
打开下位机系统第一版,发现它是这个样子的,才想起来自己当时只是想在PC机上跑跑而已,而如今已经跑在了ARM板上。
最终版本的界面如下图所示:
最初设计的时候界面我全部是使用控件拖出来的,这样开发很快,但是也有弊端。
怎么样,界面很朴素吧,剩下的工作全部交给QSS就可以了。
在上图中,4个按钮都是ToolButton,然后设置对应的Icon就行了,因为QToolButton能够设置文本和图标,所以选择了ToolButton,这样就可以设置文本显示在图标的下边了。
tbn->setAutoRaise(true);
//设置文本在图标下边
tbn->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
界面中的其他图标设置样式都是类似的,只不过是大小策略的不同而已,关于界面实现的细节,通过修改样式表完全可以解决,样式表我就不做过多的介绍,因为官方的文档介绍的太详细了。
http://doc.qt.io/qt-4.8/stylesheet-examples.html
从题目中可以看出使用的是zigbee进行通信的,但是,仔细一想,通信的接口到头来还是串口,因此上位机通信编程其实就是串口编程了,说到串口,Qt5已经给我们封装好了类,只需在工程配置文件中添加QT += serialport就可以直接进行调用相关类了。
到了Qt4里面是没有这个类的。由于Qt4中没有串口类,只要5里边有,因此很多时候是直接使用的第三串口方库,直接将它添加到我们自己的工程当中来使用的,出于方便,也可以直接使用Qt4自带的qmake将库编译到Qt4库当中,以后想用的话直接使用就可以了。使用的时候需要在.pro文件中包含进来:
可以直接下载Qt 5的串口部分源码,注意是串口部分的源码,当然你也可以全部下载,那样就比较耗时,Qt很友好的给我们提供了模块下载,这真的是非常好的。
下载地址:
串口源码下载(http://download.qt.io/archive/qt/5.1/5.1.1/submodules/
打开后会发现设计到串口相关的有4个压缩包,在window下的话就直接下载第一个(红色圈起来的部分),当然也可以下载第二个也行了。这份源码可以在不同平台进行编译一次,这样你的代码移植性就比较好。这里只介绍在windows下的编译,其他平台的编译方法类似。
下载源码后,打开cmd命令窗口(前提是你的qt环境变量已经配置,若没有配置则直接使用Qt command),进入到串口源码目录,执行qmake qtserialport.pro,此时会生成Makefile文件,然后make && make install。会在编译完成后安装到Qt的目录下面。
编译安装完成后到Qt4安装目录查看,确实有了:
include路径
lib路径:
这里说明一下为什么要使用Qt5的源码进行编译,而不使用第三方串口类。因为Qt5串口类中有个QSerialPortInfo这个类,有了这个可以进行查看当前串口设备信息,这是很方便的,以前使用第三方串口类的时候并没有发现那样的放,要想实现列举当前串口设备,在window下是查看注册表来是现代,不过这样不通用,在linux下没有试过,因此直接编译了Qt5的串口类。在使用上差不多是一样的。
设计到通信部分,协议肯定是少不了的,工业控制方面目前比较有名的协议有Modbus、BACnet等,其中Modbus是比较有名的。我这里只是简单的对设备进行了定义,并没有使用上述复杂的协议。
这里列举一部分出来
模块类型宏定义和枚举
数据包:
对一帧数据必须进行区分是发送的命令还是数据,这里做了简单的区分,实际中可以在添加奇偶校验位,对数据校验。要发送出去还必须对数据进行打包处理,完了从我源码中可以查看,这里就不足过多的介绍了。
关于串口通信编程部分,我就不写了,网上写的比我详细多了。其实使用串口编程也很简单,设置几个参数再进行判断即可。在串口打开后数据传输都放在了线程里边进行接收与发送,效率还是可以的,可能我的数据量太小的缘故吧。
数据接收时需要注意的一点就是对开始接收到数据大小进行一个判断,否则可能造成数据丢失。必须使用bytesAvailable()函数对接收到的数据进行判断,小于某个数就返回再读取,否则收到的数据不完整。
以下会分别介绍下位机中关键部分的实现技术,包括μC/OS-II多任务实现、串口数据的读取、对数据的解析等。首先看一张下位机整体结构图,这样心里就有了比较清晰的框架了:
μC/OS-II是一个可裁剪的、可固话的、可移植的、抢占式实时多任务系统内核,适用于多种微处理器和微控制器,能够移植到超过100多种微处理器应用开发中。本次下位机中就移植了这样一个小型的嵌入式操作系统,使得整个系统显示十分的流畅,而且也很好管理。
μC/OS-II的移植这里就不说了,网上有好多的介绍,其中正点原子和野火的讲的都比较详细,可以去看看,首先说明,我的这个工程也是参考他们的。一下是我的整个工程的结构,整体上还算比较详细的:
通过代码可以很清晰的看出来各个传感器的 驱动、数据的读取等细节,这里主要说说系统多的任务的实现。
主函数开始后进行了一些列的初始化操作,随后初始化了μC/OS-II系统,并且创建了一个开始任务:
各个模块初始化操作:
delay_init(); //延时初始化
uart_init(115200); //串口1初始化
uart3_init(115200); //串口3初始化(连接ZigBee接口)
JTAG_Set(JTAG_SWD_DISABLE); //关闭JTAG接口
NVIC_Configuration(); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
BEEP_Init(); //初始化蜂鸣器接口
LED_Init(); //初始化与LED连接的硬件接口
Scan_Key_Configuration(); //初始化按键接口
STEP_MOTOR_Start(); //初始化步进电机接口
Lsens_Init(); //初始化光敏传感器接口
MQ_2_Configuration(); //初始化烟雾传感器接口
HC_SR501_GPIO_Configuration(); //初始化红外热释点接口
STEP_MOTOR_Configuration(); //初始化步进电机接口
while(DHT11_Init()) //DHT11初始化,检测不到会卡死在这里
{
#if FLAG_SHOW_VALUE
printf("\r\nDHT11 Init Error");
#endif
delay_ms(600);
}
#if FLAG_SHOW_VALUE
printf("DHT11 OK\r\n");
#endif
while(DS18B20_Init()) //初始化DS18B20,检测不到会卡死在这里不断检测
{
printf("\r\nDS18B20 Init Error");
delay_ms(600);
}
printf("DS18B20 Init OK\r\n");
创建开始任务:
OSInit();
OSTaskCreate(start_task,(void *)0,(OS_STK *)&START_TASK_STK[START_STK_SIZE-1],START_TASK_PRIO );//创建起始任务
OSStart();
开始任务创建成功后便会在开始任务里边进行其他任务的初始化操作:
//开始任务
void start_task(void *pdata)
{
OS_CPU_SR cpu_sr = 0;
pdata = pdata;
OSStatInit(); //初始化统计任务.这里会延时1秒钟左右
OS_ENTER_CRITICAL(); //进入临界区(无法被中断打断)
//红外热释任务
OSTaskCreate(hc_sr501_task,(void *)0,
(OS_STK*)&HC_SR501_TASK_STK[HC_SR501_STK_SIZE-1],HC_SR501_TASK_PRIO);
//创建温湿度任务
OSTaskCreate(dht11_task,(void *)0,
(OS_STK*)&DHT11_TASK_STK[DHT11_STK_SIZE - 1],DHT11_TASK_PRIO);
//创建温度任务
OSTaskCreate(ds18b20_task,(void *)0,
(OS_STK*)&DS18B20_TASK_STK[DS18B20_STK_SIZE-1],DS18B20_TASK_PRIO);
//创建光敏检测任务
OSTaskCreate(telesky_task,(void *)0,
(OS_STK*)&TELESKY_TASK_STK[TELESKY_STK_SIZE-1],TELESKY_TASK_PRIO);
//创建烟雾检测任务
OSTaskCreate(mq_2_task,(void *)0,
(OS_STK*)&MQ_2_TASK_STK[MQ_2_STK_SIZE-1],MQ_2_TASK_PRIO);
//创建串口3任务,用来进行和上位机通讯
OSTaskCreate(uart3_task,(void *)0,
(OS_STK*)&UART3_TASK_STK[UART3_STK_SIZE-1],UART3_TASK_PRIO);
//创建蜂鸣器任务
OSTaskCreate(beep_task,(void *)0,
(OS_STK*)&BEEP_TASK_STK[BEEP_STK_SIZE-1],BEEP_TASK_PRIO);
//按键扫描任务
OSTaskCreate(key_task,(void *)0,
(OS_STK*)&KEY_TASK_STK[KEY_STK_SIZE-1],KEY_TASK_PRIO);
//创建步进电机任务
OSTaskCreate(step_motor_task,(void *)0,
(OS_STK*)&STEP_MOTOR_TASK_STK[STEP_MOTOR_STK_SIZE-1],STEP_MOTOR_TASK_PRIO);
OSTaskSuspend(START_TASK_PRIO); //挂起起始任务.
OS_EXIT_CRITICAL(); //退出临界区(可以被中断打断)
}
可以很清楚的看到,开始任务里边创建了多个任务,接着,各个任务创建成功后边会挂起任务,退出临界区,开始各个任务的轮训操作。
上面各个任务创建时,都指定了任务优先级,堆栈大小等信息。在app.h文件中可以看到:
//开始任务
#define START_TASK_PRIO 10 //开始任务的优先级设置为最低
#define START_STK_SIZE 64 //设置任务堆栈大小
OS_STK START_TASK_STK[START_STK_SIZE]; //创建任务堆栈空间
void start_task(void *pdata); //任务函数接口
//创建步进电机任务
#define STEP_MOTOR_TASK_PRIO 11
#define STEP_MOTOR_STK_SIZE 64
OS_STK STEP_MOTOR_TASK_STK[STEP_MOTOR_STK_SIZE];
void step_motor_task(void *pdata);
// //按键扫描任务
// #define KEY_TASK_PRIO 10 设置任务优先级
#define KEY_STK_SIZE 90 //设置任务堆栈大小
OS_STK KEY_TASK_STK[KEY_STK_SIZE]; //创建任务堆栈空间
void key_task(void *pdata); //任务函数接口
//蜂鸣器任务--根据各个传感器数据,进行报警
#define BEEP_TASK_PRIO 9
#define BEEP_STK_SIZE 64
OS_STK BEEP_TASK_STK[BEEP_STK_SIZE];
void beep_task(void *pdata);
// //人体感应模块 任务
#define HC_SR501_TASK_PRIO 8
#define HC_SR501_STK_SIZE 64
OS_STK HC_SR501_TASK_STK[HC_SR501_STK_SIZE];
void hc_sr501_task(void *pdata);
//光敏传感器采集任务
#define TELESKY_TASK_PRIO 7 //设置任务优先级
#define TELESKY_STK_SIZE 64 //设置任务堆栈大小
OS_STK TELESKY_TASK_STK[TELESKY_STK_SIZE]; //创建任务堆栈空间
void telesky_task(void *pdata); //任务函数接口
//DHT11任务(温湿度传感器)
#define DHT11_TASK_PRIO 6 //设置任务优先级
#define DHT11_STK_SIZE 64 //设置任务堆栈大小
OS_STK DHT11_TASK_STK[DHT11_STK_SIZE]; //创建任务堆栈空间
void dht11_task(void *pdata); //任务函数接口
//MQ-2任务(烟雾传感器)
#define MQ_2_TASK_PRIO 5 //设置任务优先级
#define MQ_2_STK_SIZE 90 //设置任务堆栈大小
OS_STK MQ_2_TASK_STK[MQ_2_STK_SIZE]; //创建任务堆栈空间
void mq_2_task(void *pdata); //任务函数接口
//DS18B20任务
#define DS18B20_TASK_PRIO 4
#define DS18B20_STK_SIZE 64
OS_STK DS18B20_TASK_STK[DS18B20_STK_SIZE];
void ds18b20_task(void *pdata);
//串口发送数据任务(用来想上位机 实时 传输输出)
#define UART3_TASK_PRIO 3
#define UART3_STK_SIZE 90
OS_STK UART3_TASK_STK[UART3_STK_SIZE];
void uart3_task(void *pdata);
任务的优先级决定了任务在被打断时执行的顺序,优先级越高,越有优先权。
在单片机开发过程中串口的操作可以算是最普通也是最基础的操作了,好多东西都是需要通过串口进行输出,这样可以对程序执行过程中的一些中间数据进行输出。这里主要说说串口数据的读取。
数据的读取使用中断的方式进行交互,这样效率很高。这里执行看核心代码即可:
void recv_zigbee_msg(u8 Res)
{
if(Res == END_RESD_MSG)
{
FLAG_ZIGBEE_RECV_BEGIN = 0;
USART3_RX_BUF[USART3_RX_STA++&0X3FFF] = Res;
cpynbyte2buffer(USART3_RX_BUF, USART3_RX_STA);
USART3_RX_STA = 0;
memset(USART3_RX_BUF, 0, sizeof(USART3_RX_BUF));
}else if(Res == BGN_RESD_MSG || FLAG_ZIGBEE_RECV_BEGIN)
{
FLAG_ZIGBEE_RECV_BEGIN = 1;
USART3_RX_BUF[USART3_RX_STA++&0X3FFF] = Res;
}else
{
FLAG_ZIGBEE_RECV_BEGIN = 0;
USART3_RX_STA = 0;
memset(USART3_RX_BUF, 0, sizeof(USART3_RX_BUF));
}
}
这里接收数据时对帧头进行了一个简单的判断,正确后接着接收,碰到帧尾后将数据拷贝到了一个缓冲区中,然后清空接收缓冲。整个过程就这么简单。
前一部分介绍了数据的接收,这里再说说对数据的解析。从上面的代码中可以看出来有个函数:cpynbyte2buffer(),该函数是吸纳对数据的解析操作,如下所示:
void cpynbyte2buffer(u8 *data, u8 len)
{
int i = 0;
u8 data_postion = 0;
protocol recvMsg;
if (BGN_RESD_MSG == data[data_postion++])
{
printf("begin\r\n");
recvMsg.potocol_len = data[data_postion++];
recvMsg.device = data[data_postion++];
recvMsg.device_cmd = data[data_postion++];
recvMsg.data_len = data[data_postion++];
if (recvMsg.data_len > 0)
{
recvMsg.data = (u8 *)malloc(sizeof(u8)*recvMsg.data_len);
for (i = 0;i < recvMsg.data_len ;i++)
{
recvMsg.data[i] = data[data_postion++];
}
}
if (data[data_postion] == END_RESD_MSG)
{
printf("end\r\n");
recv_data(&recvMsg);
}
}
}
在上面对数据结构体进行了赋值后便开始了整整的数据或者命令的执行,如 recv_data(protocol *protocol)所示:
void recv_data(protocol *protocol)
{
switch(protocol->device)
{
case MODULE_BEEP: exec_module_beep(protocol); break;
case MODULE_BED_ROOM_LED_LEFT: exec_module_led(protocol); break;
case MODULE_BED_ROOM_LED_RIGHT: exec_module_led(protocol); break;
case MODULE_PARLOUR_LED_MAIN: exec_module_led(protocol); break;
case MODULE_PARLOUR_LED_TOP: exec_module_led(protocol); break;
case MODULE_PARLOUR_LED_HELP: exec_module_led(protocol); break;
case MODULE_KITCHIN_LED: exec_module_led(protocol); break;
//case MODULE_CURTAIN: exec_module_curtain(protocol); break;
case MODULE_ALL_LED: exec_module_all_led(protocol); break;
//case MODULE_LEAVE_HOME: exec_module_leave_home(protocol); break;
case MODULE_GO_HOME: exec_module_go_home(protocol); break;
case MODULE_SMOKE: exec_module_change_smoke_value(protocol); break;
case MODULE_DS18B20: exec_module_change_parlour_temp(protocol);break;
case MODULE_DHT11_HUM: exec_module_change_parlour_hum(protocol); break;
default: break;
}
}
数据发送任务函数:
pack_send_data(MODULE_DS18B20, PROTOCOL_FULL_DATA, DATA_SIZE,parlour_temp_data); //客厅温度
delay_ms(400);
pack_send_data(MODULE_DHT11_TEMP, PROTOCOL_FULL_DATA, DATA_SIZE, bed_tempture_data);//卧室温度
delay_ms(400);
pack_send_data(MODULE_DHT11_HUM,PROTOCOL_FULL_DATA,DATA_SIZE,humidity_data); //客厅湿度
delay_ms(400);
pack_send_data(MODULE_SMOKE,PROTOCOL_FULL_DATA,DATA_SIZE,kitchen_smoke_data); //厨房浓度值
delay_ms(400);
上面是数据发送任务里调用的发送函数,该函数中对各个传感器采集到的数据进行了打包并发送操作。可以看打包发送函数:
void pack_send_data(u8 drive, u8 drive_cmd, u8 data_len, u8 *data)
/*
* @function pack_send_data
* @input
* @output
* @brief 通讯协议功能的实现.
* 本文件实现了数据包的发送和接受,以及根据数据类型执行对应的函数
*/
void pack_send_data(u8 drive, u8 drive_cmd, u8 data_len, u8 *data)
{
protocol Msg;
Msg.send_begin = BGN_RESD_MSG; //帧头 #
//若数据长度 > 0,即有数据
if(data_len > 0)
{
Msg.potocol_len = PROTOCOL_BASIC_SIZE + data_len - 1;
}else
{
Msg.potocol_len = PROTOCOL_BASIC_SIZE;
}
//填充数据结构体
Msg.device = drive;
Msg.device_cmd = drive_cmd;
Msg.data_len = data_len;
Msg.data = data;
Msg.send_end = END_RESD_MSG; //帧尾*
//发送数据
send_data(&Msg);
}
这里列出来核心代码,具体实现代码请看工程。打包发送函数中主要做了2件事情:一:对数据成员初始化,二:判断一帧数据的大小!数据大小值很重要的,这在数据解析过程可需要用到。
下面简单介绍下系统使用到的一些资料和系统实现的功能。
开发系统:Windows10 64位
Ubuntu 14.04 32 位
软件版本:Qt 4.8.6
IAR .0
Altium Designer 13.0
工具链:arm-linux-gcc 4.4.3
系统实现的主要功能如下所示:
1. STM32下位机
(1) 应用了小型的嵌入式操作系统μC/OS-II;
(2) 各个传感器的数据以多任务的形式获取,实时性很好;
(3) 数据调试使用STM32串口1;
(4) 数据的发送使用STM32的串口3,并以任务的形式进行打包发送,具体数据帧格式请看代码;
(5) STM32接收数据采用终端方式,当有数据到来时会触发终端,进入数据接收解析过程,在此过程中会对数据的帧头等信息进行判断;
2. Zigbee无线通信
Zigbee部分其实没有太多功能,一句话概括就是一个串口透传功能!本来当初计划直接使用zigbee驱动各个传感器,然后组网后发送给协调器,考虑到既然学习了STM32,就利用上吧!其实这里完全可以直接使用zigbee,不使用STM32的。
两套方案的编码工作其实早就已经完成了,思考了好久才决定采用STM32+zigbee方式,这样下来可以学到很多,尤其是μC/OS-II系统的移植,还是很深奥的,很值的以后继续深入的去学习。
3. ARM上位机
ARM上位机采用友善支臂的SMART210开发板,自己后续移植了Qt4.8。上位机主要实现了一下几个功能:
(1) 模拟室内三个场景:卧室、客厅、厨房
(2) 分别对这个三个场景下的温度,湿度以及烟雾浓度进行一个测量并显示;
(3) 使用LED灯模拟室内照明灯;
(4) 使用人体红外传感器进行检测是否有人(这个传感器有点不靠谱,最后取消了)
(5) 当初计划添加视频监控,到了后期发现工作量太大,没有时间做了,所以就没有完成,这点以后会添加上去的。
(6) 代码中使用了“liudianwu”分享的一些api,由于时间紧,所以直接拿来用了,在此感谢!
(7) 代码重复率有点高,好几处的button样式是相同的,完全可以提取出来,在此请大家海涵。
资源中主要包含了一下部分内容,各位可以先看下,是否是自己需要的。
这是实际大小,请下载解压后核对。
上位机界面源码地址:
http://download.csdn.net/detail/u013704336/9490337
全部资料地址:
http://download.csdn.net/detail/u013704336/9560355
如果你觉得还可以,请给个赞给与支持,谢谢。