基于6818的智能家居系统
开发环境:Ubuntu、SourceInsight、SecureCRT
文章中的不是完整代码,仅是部分摘录
源码放最后了,需要自取
交叉编译:
在一个环境下编译生成 适用于 另一个环境下运行的可执行文件 的过程
Linux -----> ARM
为交叉编译工具创建软连接:
sudo ln -s /usr/lib/x86_64-linux-gnu/libmpfr.so.6/usr/lib/x86_64-linux-gnu/libmpfr.so.4
使用 arm-linux-gcc 来进行编译
arm-linux-gcc xxx.c ---> 默认生成的可执行文件 名为 a.out
arm-linux-gcc yyy.c -o yy ---> 生成指定名为 yy 的可执行文件
此时 a.out和yy 只能在ARM开发板上运行
串口终端软件 SecureCRT
连接开发板步骤
1)打开设备管理器 查看端口号(COM X)
2)打开SecureCRT 点击快速连接
协议:Serial
端口: 端口号选择自己查看到的端口号
波特率:115200
数据位:8
奇偶校验位:none
停止位:1
把流控选项的所有√去掉
点击连接 出现绿色的√则表示连接成功
若CRT没有数据 则重启开发板
重启后 终端上会出现启动信息 并且会出现命令行[root@GEC6818 /]#
若没有出现而是出现err = 0 则回车按ctrl+C 出现[root@GEC6818 /]#即可
3)下载可执行文件到开发板上
rx 文件名 --》 回车 --》 点击 传输 --》 选择 发送Xmodem --》
–》 选择要下载的文件 --》 发送
给可执行文件加权限
r 读
w 写
x 执行
加执行的权限:
chmod +x 文件名 或者 chmod 0777 文件名
LCD显示屏
原理:
LCD屏幕的分辨率: 800480
分辨率:单位面积内的像素点的个数
800480 --》 一行有800个像素点, 总共有480行
像素点 pixel
像素点能够显示某种颜色的点
在屏幕上显示一个颜色,就是给对应的像素点 一个颜色值即可
LCD屏幕的每一个像素点 占4个字节 --》 a r g b a:透明度
int color;
color = 0xFF0000; //红色
帧缓冲
是底层专门用来操作屏幕的,开发板上屏幕对应的设备文件路径名为 /dev/fb0
只需要把颜色值 写入到 /dev/fb0 文件中,底层驱动就会把屏幕上对应的像素点显示对应的颜色
文件IO
IO: input / output
文件IO:对于文件的读/写操作
Linux的核心:
Everything is a file, in Linux. 在Linux下,一切皆文件
所有的东西的操作 都是对文件的操作,或者说 操作任何的东西 都是通过文件的接口来实现的
文件操作的步骤:
打开文件 open()
操作文件 read() / write() / ....
关闭文件 close()
1)打开文件 open
int open(const char *pathname, int flags);
功能: 打开或者创建一个文件
参数:
pathname:指定要打开的文件的路径名(不带路径 则默认当前路径)
flags:打开文件的标志
O_RDONLY 只读
O_WRONLY 只写
O_RDWR 读写
返回值:
int类型 文件描述符fd
如果一个文件被成功打开,那么就可以用一个int类型的整数来表示整个文件 ,后续对于该文件的所有操作 都是去操作这个整数
失败 返回-1
2)操作文件
2.1)写 write
ssize_t write(int fd, const void *buf, size_t count);
功能: 往一个文件描述符中去写入数据
参数:
fd:指定要写入的那个文件的文件描述符
buf:void * 通用指针 ,指定的空间 保存要写入的数据
count: 指定你要写入多少个字节数
返回值:
成功,返回实际写入的字节数
失败,返回-1
3)关闭文件 close
int close(int fd);
- 功能:关闭一个文件描述符
- 参数:
- fd:指定要关闭的文件的文件描述符
- 返回值:
- 成功,返回0
- 失败,返回-1
- IO效率不高:
- 拷贝内存耗时间
- write是系统调用的函数,函数调用本身会有开销
- 且 系统状态(用户态和内核态)的切换也会有开销
内存映射
Frame Buffer 本身就是一块内存,在C语言中只要知道一个内存地址, 就可以通过指针去操作这个内存
Linux操作系统提供了一个接口:
内存映射 Memory Map
映射 mmap
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
功能:映射一个文件或者设备到内存
参数:
addr:地址,指定要映射到内存的哪个地址上去 ,一般 填 NUL,表示让系统自行分配
length:指定要映射多大的内存空间,单位:字节 (8004804 )
prot: 指定映射区的权限
PROT_EXEC 可执行
PROT_READ 可读
PROT_WRITE 可写
PROT_NONE 没有权限
读写:PROT_READ | PROT_WRITE
flags: 映射标志
MAP_SHARED 共享映射,对映射区的操作会立即反馈到文件中
MAP_PRIVATE 私有映射,对映射区的操作仅对代码可见
fd:指定要映射的文件的描述符
offset:偏移量,要从文件的哪个位置开始映射,一般为 0 , 表示从文件开头的位置映射
返回值:
成功,返回映射区的首地址
失败,返回 MAP_FAILED
解除映射 munmap
int munmap(void *addr, size_t length);
功能:解除映射
参数:
addr: 指定要解除映射区的首地址,即mmap的返回值
length:指定要解除映射的空间的大小 8004804
返回值:
成功,返回0
失败,返回-1
bmp图片
bmp --> bitmap位图文件,是由Microsoft发明的一种无压缩的图片文件格式,每一个像素点的原始数据都保存图片文件中
bmp图片文件的格式
1)BITMAP文件头
保存文件的魔数、大小等数据
固定占14个字节
2)DIB头
保存图片的宽、高、色深等数据
固定占40个字节
宽度 width
偏移量: 0x12
占4个字节
int width = 0;
lseek( fd, 0x12, SEEK_SET );
read( fd, &width, 4 );
>0 每一行的像素点数据 从左至右存放的
<0 每一行的像素点数据 从右至左存放的
高度 height
偏移量: 0x16
占4个字节
inr height = 0;
>0 从下至上 保存每一行的像素点数据
<0 从上至下 保存每一行的像素点数据
色深 depth
每个像素点所占的bit位数
偏移量: 0x1C
占2个字节
short depth = 0;
depth == 24 24位bmp图片
depth == 32 32位bmp图片
字模软件
在屏幕上 显示字符(数字、汉字、字母等),笔画经过的地方 显示颜色, 没有经过的地方就不显示
点阵液晶取模软件 :这个软件 把字符 转换成 一些十六进制的取模数据
一个数据 占1个字节(8bits),对应了8个像素点
例如:宽x高 --》24x31
–》生成的取模数据的总字节数: (24/8) * 31 == 93
取模方式:
横向取模:
从上至下,从左至右
高位优先,先扫描的点 保存在数据的高位
显示字符
显示: 就是相反的操作 --》 把取模数据 还原成 字符形状
1)首先要把这些取模数据 保存起来
二维数组
unsigned char word[h][w/8]
2)显示
解析数据的每一个bit位,为1就显示颜色,为0就不显示
触摸屏
“输入设备” : 键盘、鼠标、触摸屏、 ...
内核要监听这些“输入设备”上的事件(Event),当我们去触摸屏幕、点击鼠标、按下按键… 操作输入设备时 ,就会产生相应的输入事件
输入事件: Linux下 触摸事件、鼠标事件、按键事件等
这些输入设备 在Linux下 对应的设备文件名为
/dev/input/eventX (X=0,1,2,…)
开发板上,触摸屏对应的设备文件名为
/dev/input/event0
Linux输入设备的基本原理
经过内核驱动以及输入子系统的处理之后,会把输入设备上产生的输入事件的信息,保存在对应的设备文件中(如:/dev/input/event0 )
不同的输入设备 对应输入事件是不同的,但是 Linux操作系统用了一个标准的事件结构体来描述输入事件
struct input_event {
struct timeval time; //事件发送的时间
__u16 type; //事件的类型
#define EV_SYN 0x00 同步事件
#define EV_KEY 0x01 按键事件
#define EV_REL 0x02 相对事件(鼠标事件)
#define EV_ABS 0x03 绝对事件(触摸事件)
__u16 code; //事件的编码,根据type的不同 而有不同的含义
当 type == EV_ABS 时, code表示坐标轴
code == ABS_X //x轴 #define ABS_X 0x00
code == ABS_Y //y轴 #define ABS_Y 0x01
code == ABS_PRESSURE //触摸屏压力事件
#define ABS_PRESSURE 0x18
当 type == EV_KEY 时, code表示键值
KEY_A
KEY_B
...
BTN_TOUCH --> 把整个屏幕当作是一个按键来使用
__s32 value; //事件的值, 根据type的不同 而有不同的含义
当 type == EV_ABS 时, value坐标轴的值
code == ABS_X //x轴的坐标
code == ABS_Y //y轴的坐标
code == ABS_PRESSURE //压力值
== 0 触摸屏弹起
>0 触摸屏按下
当 type == EV_KEY 时,value表示按键的状态
1 按键按下
0 按键松开
};
GY39传感器
传感器和开发板的通信方式: 串口
//6818开发板对应的串口
#define COM2 "/dev/ttySAC1"
#define COM3 "/dev/ttySAC2"
#define COM4 "/dev/ttySAC3"
串口的初始化
int init_serial(const char *file, int baudrate)
{
int fd;
fd = open(file, O_RDWR);
if (fd == -1)
{
perror("open device error:");
return -1;
}
struct termios myserial;
//清空结构体
memset(&myserial, 0, sizeof (myserial));
//O_RDWR
myserial.c_cflag |= (CLOCAL | CREAD);
//设置控制模式状态,本地连接,接受使能
//设置 数据位
myserial.c_cflag &= ~CSIZE; //清空数据位
myserial.c_cflag &= ~CRTSCTS; //无硬件流控制
myserial.c_cflag |= CS8; //数据位:8
myserial.c_cflag &= ~CSTOPB;// //1位停止位
myserial.c_cflag &= ~PARENB; //不要校验
//myserial.c_iflag |= IGNPAR; //不要校验
//myserial.c_oflag = 0; //输入模式
//myserial.c_lflag = 0; //不激活终端模式
switch (baudrate)
{
case 9600:
cfsetospeed(&myserial, B9600); //设置波特率
cfsetispeed(&myserial, B9600);
break;
case 115200:
cfsetospeed(&myserial, B115200); //设置波特率
cfsetispeed(&myserial, B115200);
break;
case 19200:
cfsetospeed(&myserial, B19200); //设置波特率
cfsetispeed(&myserial, B19200);
break;
}
/* 刷新输出队列,清除正接受的数据 */
tcflush(fd, TCIFLUSH);
/* 改变配置 */
tcsetattr(fd, TCSANOW, &myserial);
return fd;
}
线程
进程: 正在运行的程序,是程序的一次活动
线程:线程比进程更加小的活动单位,它进程的执行分支,线程同进程内的其他线程用整个进程的资源,进程有一个主线程,就是main()
创建一个线程
pthread_create()
在pthread中 用类型pthread_t 来表示一个线程的id ,用pthread_attr_t 来描述线程的属性
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void ), void arg);
功能:创建一个新线程
参数:
thread:地址,指向的空间用来保存新线程的id
attr:指定新线程的属性 ,一般为 NULL , 表示默认属性
start_routine:函数指针,它指向 返回值为void 且带有一个void 参数的函数,即 线程函数
线程函数的类型应该为:
void * xxx( void * arg )
{
}
arg: 线程函数的参数
返回值:
成功,返回0
失败,返回一个错误的值
注意:
Compile and link with -pthread.
编译时 需要链接库 -pthread
线程一旦创建成功,线程函数就会立即被指向
打开显示屏屏幕
int fd =-1;
int * plcd = NULL; //帧缓冲的首地址
int size = 800*480*4;
void lcd_init()
{
fd = open("/dev/fb0" , O_RDWR);
if( fd == -1)
{
perror("open /dev/fb0 error"); // 打开文件出错
//关闭文件
close(fd);
return ;
}
//内存映射
plcd = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
}
关闭屏幕
void lcd_close()
{
//解除映射
munmap(plcd,size );
//关闭屏幕
close(fd);
}
在显示屏上画点 x 表示列 y表示行(共有800列 480行)
void display_point( int x, int y, int color )
{
if( x>=0 && x<800 && y>=0 && y<480 )
{
*( plcd + y*800 + x ) = color;
}
}
在显示屏上画矩形
void draw_Rectangle(int a1,int a2,int b1,int b2,int color )
{
for(int i=a1;i<=a2;i++)
{
for(int j=b1;j<=b2;j++)
{
display_point(i, j, color);
}
}
}
在显示屏上显示一张纯色的图片(清屏)
void show_a_color( int color )
{
for(int j=0;j<480;j++)
{
for(int i=0;i<800;i++)
{
display_point(i, j, color);
}
}
}
显示BMP图片
int show_bmp(int x0,int y0,char *filename)
{
//打开图片
int fd = open(filename , O_RDWR);
if( fd == -1)
{
perror("open pic error"); // 打开图片
printf("open pic error");
return -1;
}
int width = 0;
//宽度大于零,像素点从左到右存放,小于零反之
int height = 0;
//高度大于零,像素点从下到上存放,小于零反之
int depth = 0;
int line_size = 0;
int laizi = 0;
int color;
unsigned b,g,r,a =0;
int i,j;
int num = 0;
lseek(fd,0x12,SEEK_SET);
read(fd,&width,4);
lseek(fd,0x16,SEEK_SET);
read(fd,&height,4);
lseek(fd,0x1C,SEEK_SET);
read(fd,&depth,2);
laizi = 4 - (abs(width)*(depth/8))%4;
if (laizi == 4)
{
laizi = 0;
}
line_size = abs(width)*(depth/8) + laizi;
//把所有像素点都读取出来
unsigned char buf[abs(height) * line_size];
lseek(fd,0x36,SEEK_SET);
read(fd,buf,abs(height) * line_size);//读取
for(j = y0;j< abs(height) + y0;j++)
{
for (i =x0; i < abs(width) +x0; i++)
{
b = buf[num++];
g = buf[num++];
r = buf[num++];
if (depth == 32)
{
a = buf[num++];
}
color = ((a<<24) | (r<<16) | (g<<8)| b);
display_point(width>0 ? i :abs(width)-1-i,
height>0 ? abs(height)-1-j : j,
color);
}
num = num+laizi;//跳过无效数据
//关闭图片
close(fd);
}
字符取模后显示
void show_word(int x0,int y0,int w,int h,unsigned char word[][w/8],int color)
{
int i,j ,k;
for(i=0;i<h;i++)
{
for(j=0;j<w/8;j++)
{
// 解析word[i][j] 的 8个bit位,为1就显示,为0就不显示
for(k=7;k>=0;k--)
{
if(word[i][j]&(1<<k))
{
// 画点
display_point(j*8+(7-k)+x0,y0+i,color);
}
}
}
}
}
数字取模后显示
void show_number(int x0,int y0,int num)
{
int numx[10];
int i=0;
while(num/10!=0)
{
numx[i++] = num%10;
num = num/10;
}
numx[i++] = num%10; // nu倒序存放
int ci = i; // 共有i个数
i--;
for(int k=1;k<=ci;k++)
{
show_word(x0+(k-1)*20, y0, 24, 35, nu[numx[i--]], 0xff0000);
}
}
获取触摸屏的数据并判断点击了什么开关
void get_touch(void )
{
fd = open("/dev/input/event0" , O_RDWR);
if( fd == -1)
{
perror("open /dev/input error"); // 打开文件出错
//关闭文件
close(fd1);
}
struct input_event ev;
while(1)
{
read(fd,&ev,sizeof(ev));
chu = 0;
if( ev.type == EV_ABS ) //触摸事件
{
chu = 1;
printf("touch activate1...\n");
if(ev.code == ABS_X) // x轴
{
xx = ev.value;
}
else if( ev.code == ABS_Y ) //y轴
{
yy= ev.value;
}
else if( ev.code == ABS_PRESSURE ) //压力事件压力为0 就结束循环
{
//压力为0退出
if(ev.value == 0)
{
break;
}
}
// 触摸事件中判断点了哪些开关
judge();
}
else if( ev.type ==EV_KEY ) //按键事件 ,按键松开,就结束循环
{
//value = 0 退出
if(ev.value == 0)
{
break;
}
xx=0;
yy = 0;
}
}
close(fd1);
}
void judge()
{
tempx = xx,tempy =yy;
printf("x= %d ,y = %d\n",xx,yy);
printf("enter judge...\n");
// 碰到空调开关
if(yy>=279&&yy<=321 &&xx>=939 &&xx<= 1005)
{
air_flag_hand =1; // 表示
if(air_flag ==0)
{
air_change(1); // 1表示换成开着的图标
air_flag = 1;
led_beep_ctrl(BEEP, 1);
sleep(1);
led_beep_ctrl(BEEP, 0);
show_bmp(380, -275,"ktydk.bmp");
printf("on air\n");
}
else
{
air_change(0);
air_flag = 0;
show_bmp(380, -275,"bai.bmp");
printf("off air\n");
}
}
……
//碰到 灯、窗帘、电视开关代码同上
// 听歌开关
if(yy>=200&&yy<=355 &&xx>=450 &&xx<= 750)
{
if(aniya_flag == 0)
{
system("madplay -Q xiju.mp3 &");
show_bmp(380, -80,"maniya.bmp");
aniya_flag = 1;
}
else
{
system("killall madplay");
show_bmp(380, -80,"aniya.bmp");
aniya_flag = 0;
}
}
}
初始化GY939传感器
int get_gy39()
{
int fd = init_serial( COM2, 9600 );
int m = 2;
char w_buf[][3] ={{0xa5, 0x81, 0x26}, {0xa5, 0x82, 0x27}, {0xa5, 0x83, 0x28}};
int Lux = 0, T = 0, P = 0, Hum = 0, H = 0;
int Lux0 = 0, T0 = 0, P0 = 0, Hum0 = 0, H0 = 0;
unsigned char r_buf[24];
int r;
if( fd == -1)
{
perror("open uart error"); // 打开文件出错
//关闭文件
close(fd);
}
unsigned char cmd[3] = { 0xA5,0x81,0x26 };
int re = write(fd, w_buf[m], 3);
if(re == -1)
{
perror("write 1 error:");
}
usleep(1000);
while( 1 )
{
r = read(fd, r_buf, 24);
if (r == 9 && r_buf[2] == 0x15)
{
Lux = (r_buf[4] << 24 | r_buf[5] << 16 | r_buf[6] << 8 | r_buf[7]) / 100;
}
else if (r == 15 && r_buf[2] == 0x45)
{
T = (r_buf[4] << 8 | r_buf[5]) / 100;
P = (r_buf[6] << 24 | r_buf[7] << 16 | r_buf[8] << 8 | r_buf[9]) / 100;
Hum = (r_buf[10] << 8 | r_buf[11]) / 100;
H = (r_buf[12] << 8 | r_buf[13]) / 100;
}
else if (r == 24)
{
Lux = (r_buf[4] << 24 | r_buf[5] << 16 | r_buf[6] << 8 | r_buf[7]) / 100;
T = (r_buf[13] << 8 | r_buf[14]) / 100;
P = (r_buf[15] << 24 | r_buf[16] << 16 | r_buf[8] << 8 | r_buf[9]) / 100;
Hum = (r_buf[10] << 8 | r_buf[11]) / 100;
H = (r_buf[12] << 8 | r_buf[13]) / 100;
}
printf("r = %d Lux = %d, T = %d, P = %d, Hum = %d, H = %d\n",r, Lux, T, P, Hum, H);
if(Lux!=Lux0)
{
printf("Lux! = Lux0\n");
show_number_init(3,Lux);
}
if(T!=T0)
{
show_number_init(1,T);
}
if(P!=P0)
{
show_number_init(2,P/1000);
}
if(Hum!=Hum0)
{
show_number_init(4,Hum);
}
if(H!=H0)
{
show_number_init(5,H);
}
Lux0 = Lux;T0 = T; P0= P; Hum0 =Hum; H0 = H;
sleep(1);
}
close(fd);
}
获取GY939传感器的数据并显示
void get_datas(void)
{
r = read(fd6, r_buf, 24);
if (r == 9 && r_buf[2] == 0x15)
{
Lux = (r_buf[4] << 24 | r_buf[5] << 16 | r_buf[6] << 8 | r_buf[7]) / 100;
}
else if (r == 15 && r_buf[2] == 0x45)
{
T = (r_buf[4] << 8 | r_buf[5]) / 100;
P = (r_buf[6] << 24 | r_buf[7] << 16 | r_buf[8] << 8 | r_buf[9]) / 100;
Hum = (r_buf[10] << 8 | r_buf[11]) / 100;
H = (r_buf[12] << 8 | r_buf[13]) / 100;
}
else if (r == 24)
{
Lux = (r_buf[4] << 24 | r_buf[5] << 16 | r_buf[6] << 8 | r_buf[7]) / 100;
T = (r_buf[13] << 8 | r_buf[14]) / 100;
P = (r_buf[15] << 24 | r_buf[16] << 16 | r_buf[8] << 8 | r_buf[9]) / 100;
Hum = (r_buf[10] << 8 | r_buf[11]) / 100;
H = (r_buf[12] << 8 | r_buf[13]) / 100;
}
}
void show_data()
{
show_number_init2(3,Lux0);
show_number_init(3,Lux);
show_number_init2(1,T0);
show_number_init(1,T);
show_number_init2(2,P0/1000);
show_number_init(2,P/1000);
show_number_init2(4,Hum0);
show_number_init(4,Hum);
show_number_init2(5,H0);
show_number_init(5,H);
Lux0 = Lux;T0 = T; P0= P; Hum0 =Hum; H0 = H;
}
主函数中创建线程
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdlib.h>
#include "lcd.h"
#include "bmp.h"
#include "chart.h"
#include "touch.h"
#include "chuan.h"
#include <pthread.h>
extern int tempx,tempy,xx,yy,chu;
// 创建线程
pthread_t task_gy39;
void start();
void login();
void start()
{
show_a_color(0xffffff);
show_bmp(0, 0,"start.bmp");
get_touch();
if(tempx != 0 || tempy !=0)
login();
}
void login()
{
show_a_color(0xffffff);
printf("xx = %d yy = %d\n",tempx,tempy);
printf("enter login...\n");
show_bmp(720,-200, "off.bmp");
show_bmp(720,-100, "off.bmp");
show_bmp(720,-250, "off.bmp");
show_bmp(720,-150, "off.bmp");
show_bmp(30,-100, "name.bmp");
total();
if(pthread_create(&task_gy39, NULL, (void *)get_onegy39, NULL) == -1)
{
perror("fail to create pthread task_rfid\n");
return ;
}
while(1)
{
printf("enter while1..\n");
get_touch();
}
}
int main()
{
led_beep_ctrl(LED, 0);
// 1.打开屏幕
lcd_init();
// 2.清屏
show_a_color(0xffffff);
system("insmod kobject_led.ko");
start();
login();
// 关闭屏幕
lcd_close();
return 0;
}
错误分析
在显示屏上出现汉字时,显示乱码,经排查后发现问题在于函数 show_word() 传参过程中的宽和高颠倒了,导致输出汉字字符和数字时都显示为了乱码。
结果展示
源代码
- 永久有效
链接: https://pan.baidu.com/s/1E3zXpWt3ge2Bp9JfDX917A 提取码: qnbp