智能家居控制系统
🥁作者: 华丞臧.
📕专栏:【项目经验】
各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞+收藏+关注
)。如果有错误的地方,欢迎在评论区指出。
推荐一款刷题网站 👉 LeetCode刷题网站
文章目录
- 一、智能家居
- 1.1 项目介绍
- 1.2 开发平台
- 1.2.1 硬件
- 1.2.2 软件
- 1.3 项目功能
- 二、Linux的知识点
- 2.1 Linux
- 2.1.1 LINUX 下有两种编译器
- 2.1.2 LINUX文件操作
- LINUX下的文件系统
- 交叉开发
- 2.1.3 文件IO
- 【打开文件】open
- 【关闭文件】close
- 【读文件】read
- 【写文件】write
- 【改变文件指针】lseek
- 三、项目模块
- 3.1 项目接口
- 3.1.1 全局数据
- 3.2 图片显示和字符显示
- 3.2.1 像素点
- 3.2.2 图片显示
- 解析像素组
- 初始化屏幕和映射
- 关闭屏幕和解映射
- 3.2.4 字符显示
- 3.3 触摸屏
- 3.3.1 获取触摸坐标
- 3.4 音乐播放器
- 3.6 传感器模块
- 3.6.1 传感器初始化
- 3.6.2 读取传感器数据
- 3.6.3 LED灯
- 3.7 界面切换
- 3.8 模块合并
- 3.8.1 线程
- 创建一个新的线程
- 获取当前进程id--pthread_self
- 线程分离--pthread_detach
- 3.8.2 模块合并
- 四、功能测试
- 五、项目源码
一、智能家居
1.1 项目介绍
智能家居又称智能住宅,是以住宅为平台,利用先进的计算机、嵌入式系统和网络通讯技术,将家中各种设备,包括照明、环境控制系统、网络家电等通过家庭网络连接到一起,构建高效的住宅设施与家庭日程事务的管理系统。与普通的家居相比,智能家居既具有传统的居住功能,又提升了家居安全性、便利性、舒适性、艺术性,保证人们在任何一个有网络的地方就可以掌控家里的一切,因此成为当前人们家装所关注的热点。
1.2 开发平台
1.2.1 硬件
GEC6818 开发平台,核心板采用 10 层板工艺设计,确保稳定可靠,可以批量用于平板电脑,车机,学习机,POS 机,游戏机,行业监控等多种领域。该平台搭载三星 Cortex-A53 系列高性能八核处理器 S5P6818,最高主频高达 1.4GHz,可应用于嵌入式 Linux 和 Android 等操作系统的驱动、应用开发。开发板留有丰富的外设,支持千兆以太网、板载 LVDS 接口、MIPI 接口、USB 接口等。
1.2.2 软件
- VS2019
本次项目我使用的是VS2019,用来编写代码。
- VMware(ubantu)
Linux操作系统,用于编译代码并且生成可以在开发板上运行的可执行程序
- secureCRT
把Linux编译生成的目标文件下载开发板上
secureCRT连接开发板的步骤如下:
- 选择
Serial
协议;
- 配置串行端口数据;
- 点击完成。
- 点击连接 出现了绿色√那就说明连接成功了。
- 将可执行文件烧录到开发板上。
1.3 项目功能
- 把基本的传感器的数据正确的显示在开发板上。
- 通过触控控制灯的亮灭。
- 通过触控控制音乐播放器。
- 显示传感器的测量数据,并通过光照强度来控制灯的亮灭。
二、Linux的知识点
2.1 Linux
虚拟机和ubantu:
- 虚拟机可以让我们在一台电脑中运行不同的操作系统 “双系统”;
- ubantu是一个基于Linux内核的带图形化的一个Linux的操作系统,使用的是ubantu18.04;
2.1.1 LINUX 下有两种编译器
- 本地编译器: gcc
gcc xxx.c -> a.out 默认的可执行文件
gcc xxx.c -o xxx -> xxx是你命名的可执行文件
- 交叉编译器:arm-linux-gcc
在一个环境下编译生成适应于另外一个环境的可执行文件 linux -> arm
arm-linux-gcc xxx.c -o xxx -> xxx是你命名的可执行文件
注意:arm-linux-gcc
生成的可执行文件只能在arm板上运行
如果我们的代码是在windows上面,但是编译是在Linux上,我们又如何将我们代码放到Linux下面去编译运行;我们虚拟机提供的一个功能:共享文件夹
虚拟文件夹的设置:
- 虚拟机设置 → 选项 → 共享文件夹→ 总是启用 → 添加到共享文件夹中
2.1.2 LINUX文件操作
LINUX下的文件系统
文件系统是一套用来管理文件的系统
文件系统的结构
linux系统下只有目录,通过目录来管理文件,所有的文件都是以根目录“/”开头,通过文件的路径可以找到对应的文件。
- 绝对路径:以根目录开头的路径
- 相对路径:不以根目录开头的路径
- 当前路径名+相对路径名 = 绝对路径名
特殊目录:
.
当前目录
..
上一层目录
...
家目录
交叉开发
交叉开发的目的是让我们编写的代码能在目标板上跑起来。
2.1.3 文件IO
即对文件的输入和输出的操作。
Linux下应用程序设计的哲学:
- 在Linux下,所有一切皆文件;
- 所有一切包括设备.
【打开文件】open
打开文件所使用的的函数,其函数原型如下:
int open(const char *pathname, int flags)
;
pathname
:你要打开的这个文件的设备路径flags
:打开文件的方式,三种方式:
O_RDONLY
:read only 以只读方式打开O_WRONLY
:write only 以只写方式打开O_RDWR
: 以读写方式打开
返回值:
- 成功:>0的整数,这个整数就是文件描述符;所有打开的文件都是通过文件描述符来引用的,文件描述符在Linux下,唯一表示一个打开的文件,后面对文件所有的操作都是文件描述符。
- 失败:-1 错误码,文件不存在。
【关闭文件】close
关闭文件,其函数原型:
int close(int fd)
;
【读文件】read
从一个文件描述符里面去读,其函数原型如下:
size_t read(int fd, void *buf, size_t count)
;
fd
:文件描述符buf
:用来保存从文件中读取到的内容count
:你想要从文件当中读取多少个字节
函数返回值:
- 成功:返回我们实际读到的字节数;
- 失败:返回0,表示这个文件已经读到结尾了,没有内容可读。
【写文件】write
将我们的内容写到文件中去,其函数原型如下:
size_t write(int fd, const void *buf, size_t count)
;
fd
:文件描述符buf
:保存我们要写入文件里面去的内容count
:你想要写入的字节数
函数返回值:
- 写入成功:返回实际写入文件的字节数
- 写入失败:返回
-1
【改变文件指针】lseek
用来移动你的光标,也就是你的文件的偏移量,其函数原型如下:
off_t lseek(int fd, off_t offset, int whence)
;
offset
:文件的偏移量whence
:从文件的哪里开始偏移
SEEK_SET
: 定位到文件的开头
- 新光标的位置 = 文件开头+offset
- 让光标处于开头
- lseek(fd, 0, SEEK_SET);
SEEK_CUR
: 定位当前的位置
- 新光标的位置 = 当前的位置+offset
SEEK_END
: 定位到文件末尾
- 新光标的位置 = 末尾+offset
返回值:
- 成功:返回的是文件开头到新光标的位置;
- 失败:返回
-1
。
三、项目模块
3.1 项目接口
#ifndef _LCD_H_
#define _LCD_H_#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <linux/input.h>
#include <pthread.h>
#include <termios.h>
#include <netdb.h>
#include <string.h>
#include <errno.h>//图片显示
int display_bmp(int x0, int y0);//初始化屏幕和映射
int init_lcd();//关闭屏幕和解映射
int uninit_lcd();//映射
void lcd_draw_point(int i, int j, int color);//触摸屏
int get_xy();//读取图片数据
int read_data();//字模
void draw_word(int x0, int y0, int w, int h, int color, char s1[]);//图片转换
void bmp_switch();//led控制函数
void led_ctrl(char* led_id, int on_or_off);//音乐
int music_play();//数字显示
void lcd_number(int x0, int y0, double lf);初始化gy39串口
void init_tty(int fd);//测量
void gy39();
#endif
3.1.1 全局数据
#include "IHSys.h"//
#define N 3
#define LED_D7 "/sys/kernel/gec_ctrl/led_d7"
#define LED_D8 "/sys/kernel/gec_ctrl/led_d8"
#define LED_D9 "/sys/kernel/gec_ctrl/led_d9"
#define LED_D10 "/sys/kernel/gec_ctrl/led_d10"
#define LED_ALL "/sys/kernel/gec_ctrl/led_all"
#define BEEP "/sys/kernel/gec_ctrl/beep"#define COM2 "/dev/ttySAC1"
#define COM3 "/dev/ttySAC2"
#define COM4 "/dev/ttySAC3"int music_start = 0; //标记madplay是否占用int lcd_fd; //灯文件
int* plcd; //像数文件
int bmp_fd; //图片文件
int led_fd; //显示屏文件int width; //图片宽度
int height;//图片高度
int depth;//图片深度char* p; //像素数组int lack; //像数组每行缺少字节数
int total_bytes;//像素字节总数int read_x = -1, read_y = -1; //x横y纵
int ret_x, ret_y;//x横y纵int flag_cont_music = 1; //music状态
int music_count = 0; //MP3下标unsigned char rbuf1[9]; //获取光照数据
unsigned char rbuf2[15]; //获取温湿度大气压海拔数据
double LUX, T, P, HUM, H; //光照、温度、压强、湿度、海拔int count = 2; //歌曲计数
int start_all = 1; //进入主界面标记
int flag_light = 0; //进入灯界面标记
int flag_mp3 = 0; //进入音乐界面标记
int flag_dc = 0; //进入测量面标记
int flag_on_off = 1; //灯的状态
int flag_light_sys = 1; //灯驱动的状态char* mp3[7] = { "/music/music_list/01.mp3","/music/music_list/02.mp3","/music/music_list/03.mp3" ,"/music/music_list/04.mp3" ,"/music/music_list/05.mp3" ,"/music/music_list/06.mp3" ,"/music/music_list/07.mp3" }; //音乐所在路径(开发板上)char number[][24 * 48 / 8] = {{/*-- 文字: 0 --*/
/*-- 宋体36; 此字体下对应的点阵为:宽x高=24x48 --*/
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x01,0xFF,0x80,0x03,0xC3,
0xC0,0x07,0x81,0xE0,0x0F,0x81,0xF0,0x0F,0x00,0xF0,0x1F,0x00,0xF8,0x1F,0x00,0xF8,
0x1E,0x00,0x78,0x3E,0x00,0x7C,0x3E,0x00,0x7C,0x3E,0x00,0x7C,0x3E,0x00,0x7C,0x3E,
0x00,0x7C,0x3E,0x00,0x7C,0x3E,0x00,0x7C,0x3E,0x00,0x7C,0x3E,0x00,0x7C,0x3E,0x00,
0x7C,0x3E,0x00,0x7C,0x3E,0x00,0x7C,0x3E,0x00,0x7C,0x3E,0x00,0x7C,0x3E,0x00,0x7C,
0x3E,0x00,0x78,0x1F,0x00,0xF8,0x1F,0x00,0xF8,0x0F,0x00,0xF0,0x0F,0x81,0xF0,0x07,
0x81,0xE0,0x03,0xC3,0xC0,0x01,0xFF,0x80,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},{ /*-- 文字: 1 --*/
/*-- 宋体36; 此字体下对应的点阵为:宽x高=24x48 --*/
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0x00,0x00,0x1C,0x00,0x00,0x7C,
0x00,0x07,0xFC,0x00,0x07,0xFC,0x00,0x00,0x3C,0x00,0x00,0x3C,0x00,0x00,0x3C,0x00,
0x00,0x3C,0x00,0x00,0x3C,0x00,0x00,0x3C,0x00,0x00,0x3C,0x00,0x00,0x3C,0x00,0x00,
0x3C,0x00,0x00,0x3C,0x00,0x00,0x3C,0x00,0x00,0x3C,0x00,0x00,0x3C,0x00,0x00,0x3C,
0x00,0x00,0x3C,0x00,0x00,0x3C,0x00,0x00,0x3C,0x00,0x00,0x3C,0x00,0x00,0x3C,0x00,
0x00,0x3C,0x00,0x00,0x3C,0x00,0x00,0x3C,0x00,0x00,0x3C,0x00,0x00,0x3C,0x00,0x00,
0x3E,0x00,0x00,0x3E,0x00,0x07,0xFF,0xE0,0x07,0xFF,0xF0,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 },{ /*-- 文字: 2 --*/
/*-- 宋体36; 此字体下对应的点阵为:宽x高=24x48 --*/
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xFF,0x80,0x03,0xFF,0xC0,0x0F,0x83,
0xE0,0x0E,0x01,0xF0,0x1E,0x00,0xF8,0x1E,0x00,0xF8,0x3E,0x00,0x78,0x3E,0x00,0x78,
0x3F,0x00,0x78,0x3F,0x00,0x78,0x1F,0x00,0xF8,0x00,0x00,0xF8,0x00,0x00,0xF0,0x00,
0x01,0xF0,0x00,0x01,0xE0,0x00,0x03,0xC0,0x00,0x07,0xC0,0x00,0x0F,0x80,0x00,0x0F,
0x00,0x00,0x1E,0x00,0x00,0x3C,0x00,0x00,0x78,0x00,0x00,0xF0,0x00,0x01,0xE0,0x00,
0x03,0xC0,0x00,0x07,0x80,0x1C,0x0F,0x00,0x1C,0x0E,0x00,0x38,0x1C,0x00,0x38,0x3C,
0x00,0x78,0x3F,0xFF,0xF8,0x3F,0xFF,0xF8,0x3F,0xFF,0xF8,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 },{/*-- 文字: 3 --*/
/*-- 宋体36; 此字体下对应的点阵为:宽x高=24x48 --*/
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xFF,0x00,0x07,0xFF,0x80,0x0F,0x07,
0xC0,0x1E,0x03,0xE0,0x1E,0x01,0xF0,0x1E,0x01,0xF0,0x1F,0x00,0xF0,0x1F,0x00,0xF0,
0x1E,0x00,0xF0,0x00,0x00,0xF0,0x00,0x01,0xF0,0x00,0x01,0xF0,0x00,0x01,0xE0,0x00,
0x07,0xC0,0x00,0x1F,0x80,0x00,0xFE,0x00,0x00,0x7F,0x80,0x00,0x03,0xE0,0x00,0x01,
0xF0,0x00,0x00,0xF0,0x00,0x00,0xF8,0x00,0x00,0x78,0x00,0x00,0x7C,0x00,0x00,0x7C,
0x1E,0x00,0x7C,0x3F,0x00,0x7C,0x3F,0x00,0x78,0x3F,0x00,0xF8,0x3E,0x00,0xF8,0x1E,
0x01,0xF0,0x0F,0x03,0xE0,0x07,0xFF,0xC0,0x01,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 },{/*-- 文字: 4 --*/
/*-- 宋体36; 此字体下对应的点阵为:宽x高=24x48 --*/
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xC0,0x00,0x03,0xC0,0x00,0x07,
0xC0,0x00,0x0F,0xC0,0x00,0x0F,0xC0,0x00,0x1F,0xC0,0x00,0x1F,0xC0,0x00,0x3F,0xC0,
0x00,0x77,0xC0,0x00,0x77,0xC0,0x00,0xE7,0xC0,0x01,0xE7,0xC0,0x01,0xC7,0xC0,0x03,
0x87,0xC0,0x03,0x87,0xC0,0x07,0x07,0xC0,0x0E,0x07,0xC0,0x0E,0x07,0xC0,0x1C,0x07,
0xC0,0x3C,0x07,0xC0,0x38,0x07,0xC0,0x7F,0xFF,0xFC,0x7F,0xFF,0xFE,0x3F,0xFF,0xFC,
0x00,0x07,0xC0,0x00,0x07,0xC0,0x00,0x07,0xC0,0x00,0x07,0xC0,0x00,0x07,0xC0,0x00,
0x07,0xC0,0x00,0x07,0xC0,0x00,0x7F,0xFC,0x00,0x7F,0xFE,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 },{/*-- 文字: 5 --*/
/*-- 宋体36; 此字体下对应的点阵为:宽x高=24x48 --*/
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0xFF,0xF8,0x0F,0xFF,0xF8,0x0F,0xFF,
0xF8,0x0E,0x00,0x00,0x0E,0x00,0x00,0x0E,0x00,0x00,0x0E,0x00,0x00,0x0E,0x00,0x00,
0x0E,0x00,0x00,0x0E,0x00,0x00,0x0E,0x00,0x00,0x0E,0x3E,0x00,0x0F,0xFF,0xC0,0x0F,
0xFF,0xE0,0x0F,0x83,0xF0,0x1F,0x01,0xF0,0x1E,0x00,0xF8,0x0C,0x00,0xF8,0x00,0x00,
0x78,0x00,0x00,0x7C,0x00,0x00,0x7C,0x00,0x00,0x7C,0x00,0x00,0x7C,0x1E,0x00,0x7C,
0x3F,0x00,0x7C,0x3F,0x00,0x78,0x3F,0x00,0x78,0x3E,0x00,0xF8,0x1E,0x00,0xF0,0x1E,
0x01,0xF0,0x0F,0x03,0xE0,0x07,0xFF,0xC0,0x01,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 },{/*-- 文字: 6 --*/
/*-- 宋体36; 此字体下对应的点阵为:宽x高=24x48 --*/
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0xC0,0x00,0xFF,0xE0,0x03,0xE1,
0xF0,0x03,0xC1,0xF8,0x07,0x81,0xF8,0x0F,0x01,0xF0,0x0F,0x00,0xE0,0x1E,0x00,0x00,
0x1E,0x00,0x00,0x3E,0x00,0x00,0x3E,0x00,0x00,0x3E,0x00,0x00,0x3E,0x3F,0x80,0x3E,
0xFF,0xE0,0x3F,0xF3,0xF0,0x3F,0xC1,0xF8,0x3F,0x80,0xF8,0x3F,0x00,0x7C,0x3E,0x00,
0x7C,0x3E,0x00,0x7C,0x3E,0x00,0x3C,0x3E,0x00,0x3C,0x3E,0x00,0x3C,0x3E,0x00,0x3C,
0x3E,0x00,0x3C,0x3E,0x00,0x7C,0x1F,0x00,0x7C,0x1F,0x00,0x78,0x0F,0x80,0x78,0x0F,
0x80,0xF0,0x07,0xE1,0xE0,0x03,0xFF,0xC0,0x00,0xFF,0x80,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 },{ /*-- 文字: 7 --*/
/*-- 宋体36; 此字体下对应的点阵为:宽x高=24x48 --*/
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0xFF,0xFC,0x1F,0xFF,0xFC,0x1F,0xFF,
0xF8,0x1F,0x00,0x78,0x1C,0x00,0x70,0x1C,0x00,0xF0,0x38,0x00,0xE0,0x38,0x01,0xE0,
0x00,0x01,0xC0,0x00,0x03,0xC0,0x00,0x03,0x80,0x00,0x07,0x80,0x00,0x07,0x00,0x00,
0x0F,0x00,0x00,0x0E,0x00,0x00,0x1E,0x00,0x00,0x1E,0x00,0x00,0x1C,0x00,0x00,0x3C,
0x00,0x00,0x3C,0x00,0x00,0x7C,0x00,0x00,0x7C,0x00,0x00,0x78,0x00,0x00,0x78,0x00,
0x00,0xF8,0x00,0x00,0xF8,0x00,0x00,0xF8,0x00,0x00,0xFC,0x00,0x00,0xFC,0x00,0x00,
0xFC,0x00,0x00,0xF8,0x00,0x00,0xF8,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},{ /*-- 文字: 8 --*/
/*-- 宋体36; 此字体下对应的点阵为:宽x高=24x48 --*/
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xFF,0x80,0x07,0xFF,0xC0,0x0F,0x81,
0xE0,0x1F,0x00,0xF0,0x1E,0x00,0x78,0x3C,0x00,0x78,0x3C,0x00,0x78,0x3C,0x00,0x7C,
0x3C,0x00,0x7C,0x3E,0x00,0x78,0x1F,0x00,0x78,0x1F,0x80,0x70,0x0F,0xC0,0xF0,0x07,
0xF1,0xE0,0x03,0xFF,0xC0,0x01,0xFF,0x00,0x03,0xFF,0xC0,0x07,0x9F,0xE0,0x0F,0x07,
0xF0,0x1E,0x03,0xF0,0x3C,0x01,0xF8,0x3C,0x00,0xF8,0x3C,0x00,0x7C,0x7C,0x00,0x7C,
0x78,0x00,0x3C,0x78,0x00,0x3C,0x3C,0x00,0x3C,0x3C,0x00,0x78,0x3C,0x00,0x78,0x1E,
0x00,0xF0,0x0F,0x81,0xE0,0x07,0xFF,0xC0,0x01,0xFF,0x80,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 },{ /*-- 文字: 9 --*/
/*-- 宋体36; 此字体下对应的点阵为:宽x高=24x48 --*/
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xFF,0x00,0x07,0xFF,0x80,0x0F,0x83,
0xC0,0x1F,0x01,0xE0,0x1E,0x00,0xF0,0x3E,0x00,0xF0,0x3C,0x00,0x78,0x3C,0x00,0x78,
0x7C,0x00,0x78,0x7C,0x00,0x7C,0x7C,0x00,0x7C,0x7C,0x00,0x7C,0x7C,0x00,0x7C,0x3C,
0x00,0x7C,0x3E,0x00,0xFC,0x3E,0x00,0xFC,0x3E,0x01,0xFC,0x1F,0x07,0xFC,0x0F,0xFF,
0x7C,0x07,0xFE,0x7C,0x01,0xF8,0x7C,0x00,0x00,0xF8,0x00,0x00,0xF8,0x00,0x00,0xF8,
0x00,0x00,0xF8,0x00,0x00,0xF0,0x0E,0x01,0xF0,0x1F,0x01,0xE0,0x1F,0x03,0xE0,0x1F,
0x07,0xC0,0x1F,0x8F,0x80,0x0F,0xFF,0x00,0x03,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 },{ /*-- 文字: . --*/
/*-- 宋体36; 此字体下对应的点阵为:宽x高=24x48 --*/
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0x00,0x00,0x1F,0x80,0x00,0x3F,
0xC0,0x00,0x3F,0xC0,0x00,0x1F,0x80,0x00,0x0F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }
};
3.2 图片显示和字符显示
首先从智能家居系统图片显示开始,也就是说我们需要在屏幕上显示图片,并且能够进行图片切换的功能;在GEC6818开发板上配置了一块LCD屏幕,其分辨率为800*480,即表示屏幕上有480行每一行有800个像素点。
3.2.1 像素点
像素点是可以显示某种颜色的点,在开发板上显示一个颜色就是给对应的像素点赋值,在LInux操作系统中一切皆文件,LCD屏幕在OS内核中的一个结构体指向的一个文件,这个文件描述了LCD的相关属性,因此对LCD屏的操作就可以转化为对文件的操作。让LCD屏显示某种颜色就是将该颜色对应的值写入到LCD屏对应的文件中。那么颜色如何描述的呢?
一个像素点的颜色由三种基色组成,这三种基色分别是红绿蓝,三种基色通过量化可以表示出不同的颜色,量化即不同程度的红绿蓝数量化;我们把一个像素点的颜色看做四个字节的大小的整型ARGB,其中A表示透明度(了解),R表示红色,G表示绿色,B表示蓝色,在这4个字节中,从低到高位分别代表B、G、R、A,一般使用的都是低三个字节,本项目中也没有使用到透明度。
总结:
- 每一种基色占据1个字节:0~255。
- 每一种颜色由四字节表示。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>#define RED 0XFF0000
#define GREEN 0X00FF00
#define BULE 0X0000FFint main()
{//打开屏幕int lcd_fd = open("/dev/fb0", O_RDWR); //打开LCD屏幕文件if(-1 == lcd_fd){perror("open lcd_fd failed: ");return -1;}unsigned int color;int i,j;//写颜色for(i = 0; i < 480; i++){for(j = 0; j < 800; j++){if(i >= 0 && i < 160){color = RED;write(lcd_fd, &color, 4); //写入颜色}else if(i >= 160 && i < 320){color = GREEN;write(lcd_fd, &color, 4); //写入颜色}else{color = BULE;write(lcd_fd, &color, 4); //写入颜色}}}//关闭文件close(lcd_fd);return 0;
}
在ubantu上运行代码并将可执行文件下载到开发板上,运行结果如下:
3.2.2 图片显示
图片显示我选择使用bmp格式的图片,并且也是800*480的分辨率;如果图片不是bmp的可以使用电脑自带的画图将图片格式和分辨率改成合适的格式即可,步骤如下:
bmp图片的优势:bmp是位图文件,是一种无压缩的图片文件格式无压缩;在其中保存了每个像素点ARGB颜色的分量,可以直接读取图片中的像素点。
得到bmp格式的图片只是第一步,接下来需要将图片的像素点读取并且保存在内存中并且写入到LCD屏内核文件中;首先来了解bmp图片文件中的内容,如下图:
bmp格式的文件分为4部分,其中最值得我们关注的DIB头和像数组;在DIB头中我们可以读取图片的高度和宽度即分辨率,而在像数组中我们可以读取图片的每一个像素点并且将其保存下来;调色板是一种采用索引的压缩算法,目的是为了节省存储空间,只有图片的颜色小于256或等于色的时候才采用,对于像素深度高于16位的图像,不使用调色板,对于24位和32位色深的bmp图片是不需要调色板的。
//通过如下的代码可以读取bmp图片的宽度、高度、色深
//读取图片数据
//位图宽度:每一行所占的像素点的个数
int read_data(int i)
{bmp_fd = 0;char pch3[8] = { 0 };sprintf(pch3, "/bmp/p%d.bmp", i);printf("%s\n", pch3);bmp_fd = open(pch3, O_RDWR);if (-1 == bmp_fd){perror("open bmp failed:");return 0;}//宽度width = 0;lseek(bmp_fd, 0x12, SEEK_SET);int ret = read(bmp_fd, &width, 4);if (-1 == ret){printf("read width error.\n");return -1;}printf("width = %d\n", width);//但是我们读取到的width有可能是一个负值//width > 0:从左到右来保存每一个像素点//width < 0:从右往左来保存每一个像素点//位图高度:这张图片有多少行的像素点height = 0;lseek(bmp_fd, 0x16, SEEK_SET);ret = read(bmp_fd, &height, 4);if (-1 == ret){printf("read height error.\n");return -1;}printf("height = %d\n", height);//height > 0:从下到上保存像素点//height < 0 :从上到下保存像素点//位图的深度(色深):每个像素点所占的位数depth = 0;lseek(bmp_fd, 0x1c, SEEK_SET);ret = read(bmp_fd, &depth, 2);if (-1 == ret){printf("read depth error.\n");return -1;}printf("depth = %d\n", depth);//如果depth == 32,4个字节,则是ARGB//如果depth == 24,3个字节,则是RGB,A取默认值0if (abs(width) * (depth / 8) % 4){lack = 4 - abs(width) * (depth / 8) % 4;}int line_bytes = lack + abs(width) * (depth / 8);total_bytes = line_bytes * abs(height); //图片总字节数p = (char*)realloc(p, total_bytes);if (p == NULL){perror("realloc fail");return 0;}//读像素组lseek(bmp_fd, 0x36, SEEK_SET);int res = read(bmp_fd, p, total_bytes);if (-1 == res){perror("read fail");return 0;}close(bmp_fd);return 1;
}
内存分配单位是4字节,位图中每行像素数据是连续的,下一行和上一行不能共1个分配单元(4字节)。每行像素数据长度必须是4字节的倍数, 字节数 % 4 不等于0时, 后续字节用0补齐。因此对于每一行的像素我们都需要计算其是否是4的整数,而每一行像素都是一样的所以只需要计算一行的却少数即可。
- 一行像素点的个数:abs(width)
- 每一个像素点占多少个字节:depth/8
- 一行有多少个字节:abs(width)*(depth/8)
- 为了保证每一行的字节数就是4的倍数,我们就需要在末尾添上几个空白的字节。
- 每一行需要多少个字节:
int lack = 0;
if(abs(width)*(depth/8)%4)
{lack = 4 - abs(width)*(depth/8)%4;
}
- 一行总字节数:line_bytes = lack + abs(width)*(depth/8);
- 多少行:bs(height)
- 整个像素数组的大小:total_bytes = line_bytes * abs(height)
- 定义一个同样大小的数组去保存这个图片的所有像素点。
解析像素组
计算机操作系统字节序有大小端之分:
- 大端模式:存储器的低地址,存放的是数据的高字节。
- 小端模式:存储器的低地址,存放的是数据的低字节。
- 图片的本质是磁盘上的一个文件,文件中保存了图片的属性和数据,而bmp图片的像素点一般是24位大小或者32位大小,所以像素点有大小端之分。
bmp图片的像素点一般都是24位,也有32位的,如果是32位的像素点A就有相应的值,而如果是24位的就给A设置为0;以32位来说,我们可以用一个整型来保存一个像素点的大小,然后通过按位或来设置ARGB并将其映射到LCD屏上。
//图片显示
int display_bmp(int x0, int y0) //x0和y0表示映射的起始位置
{unsigned char a, r, g, b;int x, y;//x行,y列int i = 0;int color = 0;if (abs(width) * (depth / 8) % 4){lack = 4 - abs(width) * (depth / 8) % 4;}for (y = 0; y < abs(height); y++){for (x = 0; x < abs(width); x++){b = p[i++];g = p[i++];r = p[i++];if (depth == 32){a = p[i++]; //像素为4字节}else if (depth == 24){a = 0; //像素为3字节}color = ((a << 24) | (r << 16) | (g << 8) | b); //小端lcd_draw_point(width > 0 ? x + x0 : abs(width) - 1 - x + x0, height > 0 ? abs(height) - 1 - y + y0 : y + y0, color);}i += lack;}
}//映射
void lcd_draw_point(int i, int j, int color)
{if (i >= 0 && i < 800 && j >= 0 && j < 480){*(plcd + j * 800 + i) = color; //plcd从0开始映射,每个点都可以通过(plcd+偏移量)来找到}
}
初始化屏幕和映射
不同的显示屏硬件,数据线以及时序及硬件操作是不一样的;从应用开发的角度,所有的显示屏只有一个作用帮我们在正确的地方来显示颜色,显示图片。在Linux下,LCD屏幕就是一个文件,OS通过数据结构来管理该文件,那么当OS需要使用LCD资源时,该文件会被加载到内存中因此我们可以把LCD屏幕看做内存中的一片空间,所以只要我们可以将bmp图片的像数组映射到LCD在内核中的空间就可以在LCD屏上显示图片了。
//mmap一种内存映射文件的方法
//mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。
//mmap在用户空间映射调用系统中作用很大。
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
//addr:如果addr为NULL,则内核选择创建映射的地址;这是创建新地图最方便的方法平。
//如果addr不为NULL,那么内核会把它作为映射位置的提示;在Linux上,映射将在附近的页面边界。新映射的地址作为调用的结果返回。
//length:映射空间总大小;
//prot:描述了映射所需的内存保护
//flags:确定映射的更新对映射同一区域的其他进程是否可见,以及更新是否可见传递到底层文件。
//返回值:当映射成功后,会返回一个指向文件描述符所对应文件的指针
//初始化屏幕和映射
//lcd_fd和plcd是全局变量
int init_lcd()
{//打开屏幕lcd_fd = open("/dev/fb0", O_RDWR); //"/dev/fb0"是开发板上屏幕文件的路径if (-1 == lcd_fd){perror("open fail");return 0;}//映射plcd = mmap(NULL, 480 * 800 * 4, PROT_READ | PROT_WRITE, MAP_SHARED, lcd_fd, 0);if (NULL == plcd){perror("mmap fail");return 0;}return 1;
}
关闭屏幕和解映射
//解映射
int munmap(void *addr, size_t length);//关闭屏幕和解映射
int uninit_lcd()
{free(p);p = NULL;int n = munmap(plcd, 480 * 800 * 4);close(lcd_fd);return 1;
}
图片显示结果如下:
3.2.4 字符显示
图片显示和字符显示较为相似,在LCD屏幕上我们可以将字符看做一个一个的像素点组成的,因此我们只需要这个字符的像素点按照其位置一一映射即可实现字符显示,原理就是把屏幕上相对应的像素点点亮。我们可以通过取模软件把字符按照一定的规律变成了16进制的数。
软件链接👉取模软件 ,提取码为:r4ci。
取模软件操作如下:
- 配置取模方式,取消勾选直接倒序和保留;
- 选择字模的字体、字形以及字体大小;
- 取模。
在该项目中,除了传感器模块测量的数据需要动态更新外,其他的字符都是静态的,因此对于静态的字符都采用图片的方式显示即可,对于动态的字符使用字模的方式显示。为了很好的表示传感器测量的数据需要保留两位有效数据,因此还需要对.
进行取模,将0~9
和.
取模转换的数组存放在一个二维字符数组方便使用。
//字模
void draw_word(int x0, int y0, int w, int h, int color, char s[])
{// 遍历数组的每个元素(1个字节 代表每行的8个像素素点的显示信息)int i, j;int arrary_num = w / 8 * h; for (i = 0; i < arrary_num; i++){// 每一个数组元素的每一位代表了一个像素点 从高到低// 遍历它的每一位for (j = 7; j >= 0; j--){/*判断该位是不是1s[i]>>j & 1 是否 >01000 0000 >> 7 =>0000 00010000 0001& 0000 00010000 0001 >0*/if (*(s + i) >> j & 1){//在屏幕对应的位置显示即可lcd_draw_point(i % (w / 8) * 8 + 7 - j + y0, i / (w / 8) + x0, color);}}}
}//数字显示
void lcd_number(int x0, int y0, double lf) //(x0,y0)表示起始坐标
{char* number_string = (char*)calloc(sizeof(char), 50);if (NULL == number_string){perror("malloc fail");return;}sprintf(number_string, "%.2lf", lf);printf("number_string = %s\n", number_string);char* cur = number_string;int n = 0; //每个字模对应的下标int displayce = 10; //偏移量while (*cur){if (*cur != '.'){n = *cur - '0';}else{n = 10;}displayce += 20;draw_word(y0, x0 + displayce, 24, 48, 0x050505, number[n]);cur++;}free(number_string);number_string = NULL;
}
3.3 触摸屏
3.3.1 获取触摸坐标
触摸屏在Linux下同样是一个文件,其在开发板的路径为:/dev/input/event0,与打开屏幕是相似的。重要的是,Linux是如何描述触摸事件的,怎么保存对应的数据?
操作系统管理触摸事件一定是先描述再组织,因此首先是用一个结构体来描述输入事件,该结构体定义在<linux/input.h>头文件当中。
输入事件的结构体:input_event
struct input_event
{struct timeval time;//该输入事件发生的事件,可以精确到微秒 _u16 type;//事件的类型。如下:_u16 code;//要跟据type的不同,code表示的含义也不同_u16 value;//要根据type,code的痛而有不同的含义
}
说明:
- type
- type = EV_KEY 表示这是一个按键事件。
- type = EV_REL 表示这是一个鼠标事件。
- type = EV_ABS 表示这是一个触摸屏事件。
- type = EV_SYN 事件的分割标志。
- code:要跟据type的不同,code表示的含义也不同,如:type = EV_ABS
- code = BTN_TOUCH 表示触摸点击。
- code = ABS_X 表示的是x轴的坐标。
- code = ABS_Y 表示的是y轴的坐标。
- code = ABS_PRESSURE 表示的是给屏幕压力。
- value:要根据type,code的值而有不同的含义。
- type = EV_ABS,且 code = ABS_X,value 表示的是x轴的坐标。
- type = EV_ABS,且code = ABS_Y,value 表示的是y轴的坐标。
- type = EV_KEY,且code = BTN_TOUCH,value = 1 表示手指按下,value = 0 表示手指松开。
注意:6818开发板现在触摸屏的坐标分成两种 800480、1024600,如果板子是1024*600的话,那就要进行等比例缩小。
//触摸屏
int get_xy()
{read_x = -1; //初始化read_y = -1; //初始化struct input_event ts;int touch_fd = open("/dev/input/event0", O_RDONLY);while (1){//sleep(1);read(touch_fd, &ts, sizeof(struct input_event));if (ts.type == EV_KEY && ts.code == BTN_TOUCH){if (ts.value == 1){printf("press\n");}else{printf("release\n");}}if (ts.type == EV_ABS){if (ts.code == ABS_X){read_x = ts.value * 800 / 1024;ret_x = read_x;}if (ts.code == ABS_Y){read_y = ts.value * 480 / 600;ret_y = read_y;break;}}}close(touch_fd);
}
3.4 音乐播放器
首先来认识一个Linux上的软件–madplay,madplay是一个Linux下的音乐播放器;如果你想要在开发板上播放你的音乐,首先开发板上需要有对应MP3的音乐,所以需要拷贝音乐文件到开发板上路径自己选择。
播放方式:
- 播放某一首歌一次:madplay /lixiang/music/1.mp3
- 播放某一首歌(单曲循环):madplay /lixiang/music/1.mp3 -r
- 播放我现在这个目录下的全部MP3文件一次:madplay /lixiang/music/*.mp3
- 播放我现在这个目录下的全部MP3文件(列表循环):madplay /lixiang/music/*.mp3 -r
- 播放我现在这个目录下的全部MP3文件(随机播放):madplay /lixiang/music/*.mp3 -z
程序控制madplay播放,暂停播放,恢复播放,停止播放,关闭播放器:
- 后台播放目录下所有的.mp3文件:system(“madplay /lixiang/music/*.mp3 &”)
- 列表循环播放:system(“madplay /lixiang/music/*.mp3 -r &”)
- 暂停播放:system(“kill -STOP madplay &”)
- 恢复播放:system(“kill -CONT madplay &”)
- 停止播放:system("kill -9 madplay ")
- 注意:&为后台播放,如果不加&则为前台播放,将无法对其进行暂停,恢复操作
对于一个音乐播放器需要有暂停、下一首、上一首等功能,首先在音乐界面需要有这三个按钮,而通过触摸屏我们可以得到按钮在图片上的位置,所以根据在触摸屏按下的不同位置我们可以进行暂停、下一首、上一首等功能,如下图:
//system 执行一个shell命令
#include <stdlib.h>
int system(const char *command);
//音乐
int music_play()
{int input = 0;char cmd[250] = { 0 }; //存放指令//上一首 下一首 暂停播放 继续播放 停止播放if (flag_mp3 == 1) //进入音乐界面{if (read_x >= 220 && read_x <= 260 && read_y >= 380 && read_y <= 440) // 上一首{if (music_start == 1) {system("killall -9 madplay ");//停止当前正在播放的音乐}printf("music_start = %d\n", music_start);if (music_count == 0){music_count = 6;}else{music_count--;}printf("music_count = %d\n", music_count);read_data(7); //读取图片像素组display_bmp(0, 0); //显示图片sprintf(cmd, "madplay %s -r &", mp3[music_count]);music_start = 1; //标记音乐开始播放system(cmd); //播放音乐flag_cont_music = 1; //音乐正在播放}else if (read_x >= 540 && read_x <= 580 && read_y >= 380 && read_y <= 430) //下一首{if (music_start == 1) {system("killall -9 madplay ");//停止当前正在播放的音乐}printf("music_start = %d\n", music_start);if (music_count == 6){music_count = 0;}else{music_count++;}printf("music_count = %d\n", music_count);read_data(7);display_bmp(0, 0);sprintf(cmd, "madplay %s -r &", mp3[music_count]);music_start = 1;system(cmd);flag_cont_music = 1;}else if (read_x >= 350 && read_x <= 450 && read_y >= 370 && read_y <= 480) //暂停{if (music_start == 0){read_data(7);display_bmp(0, 0);sprintf(cmd, "madplay %s -r &", mp3[music_count]);system(cmd);music_start = 1;}else if (music_start == 1 && flag_cont_music == 1){read_data(3);display_bmp(0, 0);system("killall -STOP madplay &");flag_cont_music = -flag_cont_music;}else if (music_start == 1 && flag_cont_music == -1){read_data(7);display_bmp(0, 0);system("killall -CONT madplay &");flag_cont_music = -flag_cont_music;}}}
}
3.6 传感器模块
GY-39 是一款低成本,气压,温湿度,光强度传感器模块。工作电压 3-5v,功耗小,安装方便。其工作原理是, MCU 收集各种传感器数据,统一处理,直接输出计算后的结果,此模块,有两种方式读取数据,即串口 UART( TTL 电平)或者 IIC( 2 线)。串口的波特率有 9600bps 与 115200bps,可配置,有连续,询问输出两种方式,可掉电保存设置。可适应不同的工作环境,与单片机及电脑连接。模块另外可以设置单独传感器芯片工作模式,作为简单传感器模块, MCU 不参与数据处理工作。提供 arduino, 51, stm32 单片机通讯程序,不提供原理图及内部单片机源码。此 GY39 模块另外赠送安卓手机软件 app 查看数据,且支持 wifi 局域内网连接,手机及电脑同时显示数据。
gy39模块
1.接线方式:TTL电平 3.3v~5v:1 0v:0VCC接电源 3.3v~5vGND接地CT 发送数据线DR 接收数据线串口通信:全双工,串行的通信协议ARM(6818) GY39VCC-------------VCCGND-------------GNDTX<------------->DRRX<------------->CT
3.6.1 传感器初始化
//gy39传感器的初始化函数
void init_tty(int fd)
{//声明设置串口的结构体struct termios termios_new;//先清空该结构体bzero(&termios_new, sizeof(termios_new));// cfmakeraw()设置终端属性,就是设置termios结构中的各个参数。cfmakeraw(&termios_new);//设置波特率//termios_new.c_cflag=(B9600);cfsetispeed(&termios_new, B9600);cfsetospeed(&termios_new, B9600);//CLOCAL和CREAD分别用于本地连接和接受使能,因此,首先要通过位掩码的方式激活这两个选项。 termios_new.c_cflag |= CLOCAL | CREAD;//通过掩码设置数据位为8位termios_new.c_cflag &= ~CSIZE;termios_new.c_cflag |= CS8;//设置无奇偶校验termios_new.c_cflag &= ~PARENB;//一位停止位termios_new.c_cflag &= ~CSTOPB;tcflush(fd, TCIFLUSH);// 可设置接收字符和等待时间,无特殊要求可以将其设置为0termios_new.c_cc[VTIME] = 10;termios_new.c_cc[VMIN] = 1;// 用于清空输入/输出缓冲区tcflush(fd, TCIFLUSH);//完成配置后,可以使用以下函数激活串口设置if (tcsetattr(fd, TCSANOW, &termios_new))printf("Setting the serial1 failed!\n");}
3.6.2 读取传感器数据
GY39有两种工作模式:
- 只获取光照强度
- 只获取温湿度大气压强海拔
模块输出每帧包含8-13个字节
- 只输出光照强度9个字节
- 只输出温湿度压强海拔13个字节
更加详细的gy39使用方法参考下面的资料,其中详细介绍了gy39传感器的属性,以及如何使用命令字节切换传感器模式、如何读取GY39的测量数据数据。
链接👉:GY39传感器模块,提取码:1234。
数据的显示很简单,得到的数据应该是浮点型的数据,将这个浮点数的每一位都取出来转换成字模的形式打印出来即可,打印时需要注意每一位数字都需要加上一个偏移量,不然就会打印在同一个屏幕位置上。
void gy39()
{//打开串口int gy39_fd = open("/dev/ttySAC1", O_RDWR);//初始化串口init_tty(gy39_fd);int tmp = 0;unsigned char cmd1[3] = { 0xa5, 0x81, 0x26 }; // 命令字节,获取光照强度命令//write(fd, cmd, 3);//1000 0010 = > 0x82unsigned char cmd2[3] = { 0xa5, 0x82, 0x27 }; // 命令字节,获取温湿度大气压强海拔//1000 0011 = 》0x83unsigned char cmd3[3] = { 0xa5, 0x83, 0x28 }; // 命令字节,获取温湿度大气压强海拔光照强度int ret = write(gy39_fd, cmd1, 3);if (ret != 3){perror("write fail");//sleep(1);}ret = read(gy39_fd, rbuf1, 9);if (ret != 9){perror("rbuf1 read fail");return;}//获取光照强度if (rbuf1[0] == 0x5a && rbuf1[1] == 0x5a && rbuf1[2] == 0x15) //rbuf1[2] == 0x15 表示只获取光照强度{tmp = rbuf1[4] << 24 | rbuf1[5] << 16 | rbuf1[6] << 8 | rbuf1[7];LUX = tmp / 100.0;printf("LUX = %.2lf\n", LUX);}//光照强度大于100,熄灭灯if (LUX > 100){led_ctrl(LED_ALL, 0);if (flag_light == 1){read_data(4);display_bmp(0, 0);flag_on_off = 1;}else if (flag_light == 0){flag_on_off = 1;}}//切换模式 获取温湿度大气压强海拔ret = write(gy39_fd, cmd2, 3);if (ret != 3){perror("write fail");//sleep(1);}ret = read(gy39_fd, rbuf2, 15);if (ret != 15){perror("rbuf2 read fail");//return;}//获取温度if (rbuf2[0] == 0x5a && rbuf2[1] == 0x5a && rbuf2[2] == 0x45)//rbuf2[2] == 0x45 表示只获取温湿度大气压强海拔{//温度tmp = rbuf2[4] << 8 | rbuf2[5];T = tmp / 100.0;printf("T = %.2lf\n", T);//气压tmp = rbuf2[6] << 24 | rbuf2[7] << 16 | rbuf2[8] << 8 | rbuf2[9];P = tmp / 100.0;printf("P = %.2lf\n", P);//湿度tmp = rbuf2[10] << 8 | rbuf2[11];HUM = tmp / 100.0;printf("HUM = %.2lf\n", HUM);//海拔tmp = rbuf2[12] << 8 | rbuf2[13];H = tmp;printf("H = %.2lf\n", H);}printf("\n");if (flag_dc == 1){read_data(6);display_bmp(0, 0);lcd_number(150, 350, LUX); //光照强度lcd_number(560, 70, T); //温度lcd_number(140, 210, P); //压强lcd_number(150, 70, HUM); //湿度lcd_number(560, 215, H); //海拔sleep(1);}close(gy39_fd);return;
}
3.6.3 LED灯
控制LED灯同样是打开一个开发板上LED灯的文件,将1(高电平)或者0(低电平)写入即可操作灯的亮灭。
- 写1,亮。
- 写0 ,灭。
注意:加载led的内核驱动把kobject_led.ko下载到开发板上,然后输入命令:(kobject_led.ko文件👉LED驱动文件,提取码:1234)
//开发板上以命令行模式输入,每次开启开发板都需要设置
insmod kobject_led.ko//system 执行一个shell命令
system("insmod kobject_led.ko");
//led控制函数
#define LED_D7 "/sys/kernel/gec/ctrl/led_d7"
#define LED_D8 "/sys/kernel/gec/ctrl/led_d8"
#define LED_D9 "/sys/kernel/gec/ctrl/led_d9"
#define LED_D10 "/sys/kernel/gec/ctrl/led_d10"
#define LED_ALL "/sys/kernel/gec/ctrl/led_all"
#define BEEP "/sys/kernel/gec/ctrl/beep"void led_ctrl(char* led_id, int on_or_off)
{printf("%d\n", on_or_off);led_fd = open(led_id, O_RDWR);write(led_fd, &on_or_off, 4);close(led_fd);
}
3.7 界面切换
智能家居系统包含灯光控制、音乐播放、环境测量三个功能,系统启动后首先是一个启动界面,在屏幕上任意点击后进入系统主界面,主界面给用户提供三个按钮,分别是灯光控制、音乐播放、查看环境测量数据;用户点击对应的功能按钮进入对应的功能界面,每个界面中触摸屏的点击作用是不相同的,如:在灯光控制界面点击灯就是控制灯的亮灭,而在音乐界面点击的作用就是播放、上一首、下一首等;所以不同的界面必须有不同的标记,在该项目中我使用的是全局变量来标记触摸屏当前所在的界面。
//
int start_all = 1; //进入主界面标记
int flag_light = 0; //进入灯界面标记
int flag_mp3 = 0; //进入音乐界面标记
int flag_dc = 0; //进入测量面标记
int flag_on_off = 1; //灯的状态
int flag_light_sys = 1; //灯驱动的状态
int flag_cont_music = 1;//music状态
int music_count = 0;//MP3下标void bmp_switch()
{if (read_x != -1 && read_y != -1 && start_all == 1) //进入主界面{start_all = 0;read_data(2);display_bmp(0, 0);}else if (start_all == 0) //{//mp3if (read_x >= 300 && read_x <= 450 && read_y >= 170 && read_y <= 310&& start_all == 0 && flag_light != 1 && flag_mp3 == 0 && flag_dc != 1){flag_mp3 = 1;if (music_start == 1 && flag_cont_music == 1){read_data(7);display_bmp(0, 0);}else{read_data(3);display_bmp(0, 0);}}// mp3返回主界面,关闭后台播放if (read_x >= 760 && read_x <= 800 && read_y >= 0 && read_y <= 60&& start_all == 0 && flag_light != 1 && flag_mp3 == 1 && flag_dc != 1){system("killall -9 madplay ");flag_mp3 = 0;flag_cont_music = -1;music_start = 0;read_data(2);display_bmp(0, 0);}//灯if (read_x >= 60 && read_x <= 200 && read_y >= 170 && read_y <= 310&& start_all == 0 && flag_light == 0 && flag_mp3 != 1 && flag_dc != 1){if (flag_light_sys == 1){system("insmod kobject_led.ko");flag_light_sys = -flag_light_sys;}flag_light = 1;if (flag_on_off == -1){read_data(5);display_bmp(0, 0);}else{read_data(4);display_bmp(0, 0);}}if (read_x >= 300 && read_x <= 450 && read_y >= 170 && read_y <= 310&& start_all == 0 && flag_light == 1 && flag_mp3 != 1 && flag_dc != 1){//printf("%d\n",flag_on_off);if (flag_on_off == 1){read_data(5);display_bmp(0, 0);led_ctrl(LED_ALL, 1);}else if (flag_on_off == -1){read_data(4);display_bmp(0, 0);led_ctrl(LED_ALL, 0);}flag_on_off = -flag_on_off;}//温湿度if (read_x >= 550 && read_x <= 700 && read_y >= 170 && read_y <= 310&& start_all == 0 && flag_light != 1 && flag_mp3 != 1 && flag_dc == 0){flag_dc = 1;read_data(6);display_bmp(0, 0);}// 返回主界面,功能模块后台运行if (read_x >= 0 && read_x <= 40 && read_y >= 0 && read_y <= 60){if (start_all == 0 && flag_light != 1 && flag_mp3 == 1 && flag_dc != 1){flag_mp3 = 0;read_data(2);display_bmp(0, 0);}else if (start_all == 0 && flag_light == 1 && flag_mp3 != 1 && flag_dc != 1){flag_light = 0;read_data(2);display_bmp(0, 0);}else if (start_all == 0 && flag_light != 1 && flag_mp3 != 1 && flag_dc == 1){flag_dc = 0;read_data(2);display_bmp(0, 0);}}}
}
3.8 模块合并
3.8.1 线程
- Linux认为:没有进程,没有线程;在概念上区分,只有一个叫做执行流。
- 在一个程序里的一个执行路线就叫线程(thread),更准确的定义是:线程是一个进程内部的控制序列;一切进程至少都有一个执行线程。
- 线程在进程内部运行,本质是在进程地址空间内运行;在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化,透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流。
线程是真正的基本调度单位,线程是在进程中的执行流,在Linux当中线程是在进程的地址空间内运行的,地址空间是进程看待资源的统一的视角,多个执行流将进程的资源划分。
重新理解进程,在学习Linux中进程概念时,我们理解进程为内核数据结构加上进程对应的代码和数据;那么在学习了线程后,从内核视角来看:
进程是承担分配系统资源的基本实体(进程的基座属性),即向系统申请资源的基本单元。
- 单执行流进程: 内部只有一个执行流的进程;
- 多执行流进程:内部有多个执行流的进程;
线程总结:
- 在进程内部运行的执行流
- 线程比进程粒度更细,调度成本更低;
- 线程是CPU调度的基本单元;
- 进程内部的线程共享主线程的地址空间,每个进程可以占有进程的一部分资源,执行进程的一部分代码;
创建一个新的线程
功能:创建一个新的线程
原型int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
参数thread:返回线程IDattr:设置线程的属性,attr为NULL表示使用默认属性start_routine:是个函数地址,线程启动后要执行的函数arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码
获取当前进程id–pthread_self
- pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID
- 不是一回事。
- 前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
- pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
- 线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID:
pthread_t pthread_self(void);
线程分离–pthread_detach
- 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
- 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
- 新线程分离,但是主线程先退出,那么该进程就会退出对应的所有线程也就会全部退出;一般我们分离线程,对应的main thread一般不要退出,常驻内存。
int pthread_detach(pthread_t thread);
可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离;
pthread_detach(pthread_self());
joinable和分离是冲突的,一个线程不能既是joinable又是分离的。
3.8.2 模块合并
智能家居系统包括图片显示、触摸屏、LED灯、音乐播放、传感器测量等模块,其中的一些模块是相互关联的,点击屏幕不仅仅需要进行图片切换还需要操作某些硬件来完成相应的任务,如:进入LED灯的界面可以控制灯的开关,进入音乐界面可以播放、暂停、切换音乐;也就是说在触摸屏幕的同时需要进行其他的操作,而线程能够增加多个执行流,很好地实现模块的合并。
#include "IHSys.h"//获取坐标
void* get_user_touch()
{pthread_detach(pthread_self()); //线程分离while (1){get_xy();//printf("x = %d y = %d\n", ret_x, ret_y);}
}//测量
void* get_gy39()
{pthread_detach(pthread_self()); //线程分离while (1){gy39();}
}//音乐
void* get_music()
{pthread_detach(pthread_self()); //线程分离while (1){music_play();}
}//判断触摸点
void* get_bmp_switch()
{pthread_detach(pthread_self()); //线程分离while (1){bmp_switch();}
}int main()
{//初始化屏幕和映射init_lcd();//读取图片数据read_data(1); //系统启动界面display_bmp(0, 0);pthread_t id[4];pthread_create(&id[0], NULL, get_user_touch, (void*)NULL);pthread_create(&id[1], NULL, get_gy39, (void*)NULL);pthread_create(&id[2], NULL, get_music, (void*)NULL);pthread_create(&id[3], NULL, get_bmp_switch, (void*)NULL);while (1){;}//关闭屏幕和解映射uninit_lcd();return 0;
}
四、功能测试
视频观看 👉智能家居系统
五、项目源码
Gitee:智能家居系统
相关文章:
智能家居控制系统
🥁作者: 华丞臧. 📕专栏:【项目经验】 各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞收藏关注)。如果有错误的地方,欢迎在评论区指出。 推荐一款刷题网站 👉 LeetCode刷题网站…...
Linux 进程:fork()与vfork()的对比
目录一、fork函数二、vfork函数1.函数的原理2.函数的隐患3.解决函数隐患的方法在Linux的进程学习中,常使用fork函数来创建子进程,但其实还有一个vfork函数也可以创建子进程。但是这两个函数的实现机制不同,fork函数使用了写实拷贝技术&#x…...
环境搭建02-Ubuntu16.04 安装CUDA和CUDNN、CUDA多版本替换
1、CUDA安装 (1)下载需要的CUDA版本 https://developer.nvidia.com/cuda-toolkit-archive (2)安装 sudo sh cuda_8.0.61_375.26_linux.run(3)添加环境 gedit ~/.bashrc在文件末尾添加: ex…...
HOT100--(3)无重复字符的最长子串
点击查看题目详情 大思路: 创建哈希表,元素类型为<char, int>,分别是字符与其对应下标 用哈希表来存储未重复的子串,若有重复则记录下当前子串最大值maxhashsize 并且开始以相同方法记录下一子串 遍历完成以后,…...
vue keep-alive多层级路由支持
keep-alive使用 属性值 1.include - 字符串或正则表达式。只有名称匹配的组件会被缓存。 2.exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。 3.max - 数字。最多可以缓存多少组件实例。 注:匹配首先检查组件自身的 name 选项,如果 nam…...
从源码角度看React-Hydrate原理
React 渲染过程,即ReactDOM.render执行过程分为两个大的阶段:render 阶段以及 commit 阶段。React.hydrate渲染过程和ReactDOM.render差不多,两者之间最大的区别就是,ReactDOM.hydrate 在 render 阶段,会尝试复用(hydr…...
ARM基础 -- 2
文章目录一、可编程器件的编程原理1.1 电子器件的发展方向1.2 可编程器件的特点1.3 整个编程及运行过程二、指令集对CPU的意义2.1 汇编语言与C等高级语言的差异2.2 汇编语言的本质2.2.1 编程语言的发展过程2.2.2 汇编语言的特点和用途三、RISC和CISC的区别3.1 复杂指令集CPU --…...
Java 类型转换
Java 类型转换 int转Integer int int0 1; Integer integer1 int0; // 自动装箱 Integer integer2 new Integer(int0); Integer integer3 Integer.valueOf(int0);Integer转int Integer integer0 2; int int1 integer0; // 自动拆箱 int int2 integer0.intValue(); // …...
【Java开发】JUC基础 05:线程通信/协作
1 生产者消费者问题📌 线程通信应用的场景可以简单地描述为生产者和消费者问题假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费;如果仓库中没有产品,则生产者将产品放入仓库&a…...
哪些工具可以实现在线ps的需求
在线Photoshop有哪些工具可以选择?在 Adobe 的官网上就能够实现,很惊讶吧,其实 Adobe 官方推出了在线版本的 Photoshop 的,尽管目前还是 Beta版本,但其实也开放了蛮久了。编辑切换为居中添加图片注释,不超过…...
如何使用C2concealer生成随机化的C2 Malleable配置文件
关于C2concealer C2concealer是一款功能强大的命令行工具,在该工具的帮助下,广大研究人员可以轻松生成随机化的C2 Malleable配置文件,以便在Cobalt Strike中使用。 工具运行机制 开发人员对Cobalt Strike文档进行了详细的研究,…...
网络基础之IP地址和子网掩码
一、IP地址IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。习惯上,我们用分成四段的十进制数表示IP地址,从0.0.0.0 一直到255.255.255.255。互联网上的…...
G1D54-CRF
一、CRF的输入X是什么?是构造的特征吗? 如此,CRF的x只用于状态函数吗? CRF的例子解释调用代码 机器之心 知乎忆榛 此处线性链条件随机场的特征函数形式被统一了? BilstmCRF,强烈推荐!&#x…...
vue3 使用defineAsyncComponent与component标签实现动态渲染组件
内容有些啰嗦,内容记载了当时遇到了bug以及解决问题的思路。 业务场景简述: 前端做配置化组件,通过url内的唯一标识,请求后端sql 哪取页面配置信息,前端通过配置信息动态渲染查询表单,导出、表格ÿ…...
Linux下 C/C++ NTP网络时间协议详解
NTP(Network Time Protocol,网络时间协议)是由RFC 1305定义的时间同步协议。它是通过网络在计算机系统之间进行时钟同步的网络协议。NTP 在公共互联网上通常能够保持时间延迟在几十毫秒以内的精度,并在理想条件下,它能…...
Pytest自动化框架-权威教程02-Pytest 使用及调用方法
Pytest 使用及调用方法使用python -m pytest调用pytest2.0版本新增你可以在命令行中通过Python编译器来调用Pytest执行测试:Copypython -m pytest [...]通过python调用会将当前目录也添加到sys.path中,除此之外,这几乎等同于命令行直接调用pytest [...]。可能出现的执行退出cod…...
大数据技术——概述
根据IBM前首席执行官郭士纳的观点,IT领域每隔十五年就会迎来一次重大变革三次信息化浪潮1.存储设备容量不断增加2.CPU处理能力大幅提升3.网络带宽不断增加运营式系统阶段数据库的出现使得数据管理的复杂度大大降低,数据往往伴随着一定的运营活动而产生并记录在数据库…...
java-代理模式
背景 代理模式指的是提供一个代理对象用于访问目标对象,可以很方便的在不修改目标对象的情况下,提供额外的功能,扩展目标对象。 case1:静态代理 约束:代理对象和目标对象要实现相同的接口 优点:不修改目标对象的情况下扩展功能 缺点:必须实现相同的接口,如果接口发生变…...
路由网络的构建与配置
Part.1 ⑴ 需求分析 在构建的局域网中,通过路由器间配置静态路由,实现PC1和PC2主机直接连通,主机网段不能与路由器直接互联网段通信。 ⑵ 环境要求 配置虚拟网卡的计算机,安装华为eNSP模拟软件。 规划拓扑 Part.2 ⑴ 拓扑描述…...
软件测试-接口测试-数据库管理
文章目录 1.数据库介绍2.数据库基本操作2.1安装2.2 操作流程2.3数据准备2.4数据的基本操作2.4.1 连接数据库并查询数据库版本2.4.2 连接数据库执行数据库查询操作2.4.3 连接数据库执行数据库插入操作2.4.4 连接数据库执行数据库更新操作3.数据库事务操作3.1 案例:数据不一致性…...
【华为OD机试 】天然蓄水库(C++ Java JavaScript Python)
文章目录 题目描述输入描述输出描述备注用例题目解析C++JavaScriptJavaPython题目描述 公元2919年,人类终于发现了一颗宜居星球——X星。 现想在X星一片连绵起伏的山脉间建一个天热蓄水库,如何选取水库边界,使蓄水量最大? 要求: 山脉用正整数数组s表示,每个元素代表山脉…...
普元EOS中导出excl页面下载
起因 需要做一个筛选功能的导出表格 解决办法 这个垃圾eos我是真受不了,sb玩意的缺点三天三夜也说不完 后边就没法整response的这些个东西,可真是够愁人的 在网上搜了搜 在普元的帮助文档里也看了看 普元提供的像是老太太的裹脚布一般又臭又长 参照这个可以看一下...
内存的管理
取指令——译码——执行——返存 计组课我们学过cpu真正读指令并非是从内存中读入,而是从cache读和存,再由cache进行取指或返存,因为cpu指令周期比内存周期速度快很多,cpu若要取指或返存都需要等待内存完成他的动作才可以进行下一…...
OpenFeign 切换HttpClient遇到的问题
背景 OpenFeign支持三种Http请求方式,默认情况下通过jdk中的HttpURLConnection向下游服务发起http请求(详见下图,源码详见feign.Client.Default), 默认的Client 采用 HttpURLConnection, 这种是无法复用的…...
流计算框架storm概览
Attention: supervison 和 nimbus的状态都实时保存在zookeeper集群中和本地. Enchance, this means you can kill -9 Nimbus or the Supervisors and theyll start back up as nothing happened. Topologies 1. storm jar all-my-code.jar org.apache.storm.MyTopology a…...
如何使用Coercer强制Windows Server认证任意主机
关于Coercer Coercer是一款功能强大的Python脚本,该工具可以通过九种不同的方法来强制让一台Windows Server认证任意主机。 功能介绍 1、自动检测远程设备的开放SMP管道; 2、一一调用存在安全漏洞的RPC功能来强制一台Windows Server认证任意主机&#…...
【小程序】已有公众号认证,一步一步申请小程序(图文)
一、登陆公众号后台,找到左侧广告与服务,小程序管理,开通 二、选择快速注册认证小程序 三、快速创建 四、选择微信认证资质(复用),这样不用再付认证费了 五、需要一个新的邮箱,这点挺让人无语&a…...
Redis学习笔记:缓存运用常见问题
这是本人学习的总结,主要学习资料如下 马士兵教育 目录1、数据一致性的问题1.1、新增数据一致性的问题1.2、修改/删除一致性问题1.2.1、操作分析1.2.1、总结和再深入2、缓存穿透,缓存击穿和缓存雪崩2.1、缓存穿透(查不到)2.1.1、…...
使用python 脚本挑出coco 数据集中的某一类数据
文章大纲 简介代码样例制作一个走路玩手机数据集简介 MS COCO的全称是Microsoft Common Objects in Context,起源于微软于2014年出资标注的Microsoft COCO数据集,与ImageNet竞赛一样,被视为是计算机视觉领域最受关注和最权威的比赛之一。 COCO数据集是一个大型的、丰富的物…...
Python虚拟环境(pipenv、venv、conda一网打尽)[通俗易懂]
一、什么是虚拟环境 1. 什么是Python环境 要搞清楚什么是虚拟环境,首先要清楚Python的环境指的是什么。当我们在执行python test.py时,思考如下问题: python哪里来?这个主要归功于配置的系统环境变量PATH,当我们在命…...
linux网站建设论文/爱论坛
介绍: 这是一个用vuejs2.0和element搭建的后台管理界面。 相关技术: vuejs2.0:渐进式JavaScript框架,易用、灵活、高效,似乎任何规模的应用都适用。 element:基于vuejs2.0的ui组件库。 vue-routerÿ…...
网站建设包含以下哪些建设阶段/福建seo顾问
总线是计算机各种功能部件之间传送信息的公共通信干线,是由导线组成的传输线束。微机中有三种总线:数据总线、地址总线和控制总线;它们分别用来传输数据、数据地址和控制信号。本文操作环境:windows10系统、thinkpad t480电脑。相…...
做国外单的网站叫什么名字/seo有哪些优化工具
https://github.com/hzy46/Deep-Learning-21-Examples:21个项目资源地址 https://github.com/hzy46/Deep-Learning-21-Examples/issues:21个项目资源问题解决地址 https://pan.baidu.com/s/1i7pKvFf:对应的数据 密码:1kmf...
前端做的网站/站长工具seo综合查询问题
中国石油大学华东2013-2014-2C语言A卷A卷2013—2014学年第2学期《计算机程序设计 C(2-2)》期末考试试卷专业班级姓名学号开课系室计算机应用技术系考试日期2014年6月22日题号一二三总分得分阅卷人一、程序阅读题(每空2分,共20分)1.又是一年一…...
网站怎么做的支付/怎样推广自己的商城
场景说明 在执行任何的程序之前,必须确保程序和系统的版本位数是一致的,如果一种是x64,一种是32位的,就会出现上述的问题 具体例子 [rootjack 迅雷下载]# ./qt-opensource-linux-x64-5.7.0.run bash: ./qt-opensource-linux-x64-5.7.0.run: …...
wordpress仿链家/郑州seo技术顾问
我浏览了一个关于使用Tkinter的教程,看到了以下代码:>>> from Tkinter import *>>> winTk()这将生成一个标题为Tk的框,而没有其他内容。但是,当我尝试此代码时,不会出现这样的框。我没有发现任何错误…...