🥁作者: 华丞臧.
📕专栏:【项目经验】
各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞+收藏+关注
)。如果有错误的地方,欢迎在评论区指出。
推荐一款刷题网站 👉 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 0X0000FF
int 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取默认值0
if (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--)
{
/*
判断该位是不是1
s[i]>>j & 1 是否 >0
1000 0000 >> 7 =>0000 0001
0000 0001
& 0000 0001
0000 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:0
VCC接电源 3.3v~5v
GND接地
CT 发送数据线
DR 接收数据线
串口通信:全双工,串行的通信协议
ARM(6818) GY39
VCC-------------VCC
GND-------------GND
TX<------------->DR
RX<------------->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);
// 可设置接收字符和等待时间,无特殊要求可以将其设置为0
termios_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 = > 0x82
unsigned char cmd2[3] = { 0xa5, 0x82, 0x27 }; // 命令字节,获取温湿度大气压强海拔
//1000 0011 = 》0x83
unsigned 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) //
{
//mp3
if (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:返回线程ID
attr:设置线程的属性,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:智能家居系统