智能家居控制系统开发笔记


问题

段错误

段错误: C/C++
segmentation fault

内存的非法访问。 <= 段错误
不能读的内存,你去读
不能写的内存,你去写
不是你的内存,你去访问

指针(数组)有误

int a[10];
	int i = 11;
a[i] ;
int *p = NULL;
a = *p;
	*p = 250;
int *p1;  //p1野指针
		*p1;  => 内存的非法访问

一个进程运行过程中,出现段错误啦,操作系统是如何处理的呢?
把这个进程给kill掉。

找段错误,比较容易:
加打印。

printf("abc\n");   => abc
xxxx();
printf("123\n");

printf(“%s L_%d\n”, __FUNCTION__, __LINE__ );

__FUNCTION__ 字符串,函数名
__LINE__ int, 当前行号
__FILE__ 字符串,文件名

Linux交叉开发环境搭建

项目开发规范

代码规范

缩进问题

对齐问题

文件规范

模块化思想开发

文件夹规范

smart_home/ <<<<< 包含项目的所有资料
src/ < —-包含工程源代码
main.c/main.h
lcd.c/lcd.h
ts.c/ts.h
….

代码编译

GCC:GNU C Compiler

编译器:xxx-yyy-gcc均为跨平台的交叉编译器,如arm-Linux-gcc为目标平台为arm,编译平台为Linux,编译语言为c语言的编译器。

如何解决各平台,如Windows、Linux、arm平台之间的文件传输问题?
A:Windows与Linux:共享文件夹功能
Windows与arm:串口的xModern协议传输文件

下载程序
rx : receive xModern
用串口的xModern协议来接收文件
如:
rx smart_home

改变可执行文件的权限
	chmod +x  smart_home
eXcute

运行可执行程序

./可执行程序

什么是终端(terminal) ?
A 硬件 B 软件
终端是介于用户和OS内核之间的桥梁软件。它接收用户输入的命令,
并解析命令,提交给OS执行,并把OS执行命令的结果反馈给用户。

Linux常用命令

几条基本的常用的linux命令:
(1)linux文件系统
linux文件系统是“树”的形式结构的。
绝对路径:
指根目录“/”开始的路径。
如:
/home/gec/1.txt

系统认识文件是以 绝对路径 来识别的。
		相对路径:
			不以根目录"/"开始的路径。
working directory 工作目录/当前目录
			在linux下每个进程都会有一个“当前目录”
如:
				假设你的当前目录 为 /home
				此时,你可以用目录:
					gec/1.txt  --> /home/gec/1.txt
当前目录+相对路径 =〉 绝对路径
操作系统会自动为每个目录创建两个子目录
				./  代表的是当前路径
				../  代表的是上一级路径

(2)cd
change directory 改变目录

语法:
	 	cd  目录名
	 		目录名代表的是你要切换到的那个目录
	 		目录名指定可以用 绝对路径,也可以用相对路径

(3) ls
list 列举。列举一个目录或文件的信息

ls  目录
	  ls
	  	列举当前目录的文件信息

(4) pwd
print working directory
输出当前工作目录的完整的路径名

(5) 创建目录mkdir
make directory创建目录的

mkdir 要创建的目录名
目录名可以用绝对路径指定,也可以用相对路径

mkdir -p 要创建的目录名
-p parent 双亲
如果要创建的目录的父目录(父目录的父目录,…)没有,则一并创建。
(6) 删除文件或目录
rm remove

rm -rf 要删除的文件名或目录
(7) 创建新的空的普通文件
touch

语法:
touch 要创建的普通文件的文件名(可以多个,多个的话以空格隔开)

例子:

touch main.c main.h lcd.c lcd.h ts.c ts.h detect.c detect.h
(8) 工程编译命令
make
但是make的正确运行离不开一个配置文件 Makefile/makefile

用法:
make clean 清除中间文件和可执行文件
make 自动进行编译工作
这一步如果没有语法错误,则会最终生成可执行文件

如:
-〉 smart_home

LCD屏幕的使用

Linux下一切皆文件,屏幕的使用同样是操作文件的过程。

LCD屏幕原理

分辨率:800*480 480P
1920*1080 1080P
1080*720 720P
2K
4K
由480行 每行有800个像素点来组成

像素点(piexl)是什么?
像素点就是能显示某种颜色的店
在屏幕上显示图像 是不是就是给每个像素点一个颜色

颜色如何描述的
红色
RED ….
如何统一
各种颜色其实由三基色组成
red green blue

red的程度不一样 怎么来描述呢?
量化:数量化

read
*
**

read green blue
255 0 0 耀世红
0 255 0 原谅绿
0 0 255 天空蓝

RGB:占3个字节 0xff0000

“透明度”
一个像素点由4个字节来描述的
ARGB:
A 透明度
0~255
ARGB:0x00 00 00 00 ==> int

Linux层次

中间层OS
对上应用层提供操作硬件的接口函数,并且屏蔽实现的细节
于是我们就有人提出在内存上开辟一段缓冲区,用来保存屏幕上的每一个像素点的颜色值,然后应用程序直接把要显示的图像的颜色值直接写入到这块内存上即可。
buffer至少要多大:480*800*4
这种缓冲区,我们在Linux下面称之为:帧缓冲 fram buffer === fb
帧缓冲设备是对图像设备的一种抽象,它让上层应用不必关心具体的实现的细节,上层应用只需要在帧缓冲中填上合适的颜色值即可。
然后帧缓冲的驱动按一定的刷新频率,把颜色值在屏幕上正确的显示即可。以上就是帧缓冲的原理。

6818开发板上:屏幕 /dev/fb0

文件IO

系统IO
		标准IO
		命令man
			查看帮助文档,linux提供了API函数的帮助文档(说明文档)
			eg:
				man printf
			可选项-f 显示所有的相关的页
			查看具体那一页:直接man + 页数
			eg:
				man 3 printf
		操作文件
			打开、关闭、读、写
		a.open close
		open--打开文件
		NAME
        open, openat, creat - open and possibly create a file
SYNOPSIS
	       #include <sys/types.h>
	       #include <sys/stat.h>
	       #include <fcntl.h>
int open(const char *pathname, int flags);
	  函数功能:打开文件
	  函数参数:
	  	pathname:文件路径名,可以是绝对路径也可以是相对路径
	  	flags:标志位,文件以什么权限打开
	  		O_RDONLY:只读 open_read_only
	  		O_WRONLY:只写
	  		O_RDWR:可读可写
	  	三个标志只能选其一
	  返回值:
	  	成功,返回一个文件描述符(>0)
	  	失败,返回-1,并且errno被设备。
	  	同学    槟榔
	     0     买到了
	     -1    没买到
	     -2    被抓到了
	     -3    槟榔被吃光了
	     ....
	     errno
	     可以由perror来解释
	     NAME
perror - print a system error message
SYNOPSIS
	       #include <stdio.h>
void perror(const char *s);
	       eg:
	       	perror("binglang:");
	       在屏幕打印:
	       	槟榔:错误信息\n
	代码举例:
		int fd = open("1.txt",O_RDWR);//以可读可写打开文件
		if(fd<0)
		{
			perror("open fail");
			return ;
		}
b.close
	NAME
close - close a file descriptor
SYNOPSIS
	       #include <unistd.h>
int close(int fd);
	       fd:文件描述符是open的返回值
c.read
	NAME
    read - read from a file descriptor
SYNOPSIS
	       #include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
函数功能:
		从一个已经打开的文件中读 count个字节,放到buf所指向的空间中去。
	函数参数:
		fd:
		buf:
		count:
	  PS:void* 空指针
	  PS:const C语言关键字,所修饰的变量,告诉系统不能轻易的去改变。为了提升程序健壮性。
	 函数返回值:
	 	成功,返回成功读取到字节数,文件偏移量(光标)会随之增加
	 	失败,返回-1,并且errno被设置
d.write
		NAME
write - write to a file descriptor
SYNOPSIS
#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);
函数功能:
从buf所指向的空间里面取count个字节写到fd中去
函数参数:
fd:
buf:
count:
函数返回值:
成功,返回成功写入的字节数,文件偏移量(光标)会随之增加
失败,返回-1,并且errno被设置

e.lseek
NAME
lseek - reposition read/write file offset

SYNOPSIS
	       #include <sys/types.h>
	       #include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
	      函数功能:重定位光标
	      函数参数:
	      	fd:
	      	offset:要设置的偏移量
	      	whence:定位光标
	      SEEK_SET:以文件的开头定位  offset>0
          	The file offset is set to offset bytes.
SEEK_CUR:以当前光标位置进行定位 offset可正可负
	              The file offset is set to its current location plus offset bytes.
SEEK_END:以文件末尾进行定位 offset可正可负
	              The file offset is set to the size of the file plus offset bytes.
	    返回值:
	    	成功返回新的光标位置相对于文件开头的偏移量(单位字节)
	    	失败,返回-1,并且errno被设置
	   作业一:
	   		使用lseek来求文件大小。
作业二:
	   		在开发板屏幕上显示德国国旗。
作业三:
	   		在开发板屏幕上显示太极图案

BMP图片显示

Linux帧缓存驱动

物理屏幕分辨率:
800*480

屏幕由480行,且每行有800个像素点组成。
	每个像素点可以颜色一个标准的颜色:
		ARGB8888
但是每个不同的屏幕,显示时序、接口等等都不一样,
那如果这样,我应用程序如果要显示一个颜色,还要根据不同
的屏幕,去组织不同的代码,这个是不是就很麻烦。
从应用角度来说:
		“我(应用工程师)认为,所以的显示屏都应该是一样的,
		因为我用它就是用来显示某个颜色”

为了实现这个:
Linux内核特地引入 “帧缓冲”

在内核开辟了一个 Frame Buffer,用来保存显示屏上每一个像素
	点的颜色(从上至下,从左至右)
应用程序只需要把准备好的颜色值数据,拷贝到frame buffer,
	然后 帧缓冲驱动,会按照一定的刷新频率、按照特定的屏幕显示
	的要求、接口、时序......把颜色值正确地显示到屏幕上去。
对上(应用)屏蔽了具体的硬件实现细节。

frame buffer本质就是一块内存
显示任何图像都是直接去操作这块内存

可以通过指针去操作这个frame buffer,进而就可以通过指针去显示图像!!!

int *plcd; //假设 让plcd指向linux内核帧缓冲的首地址

*plcd = 0xff0000; //屏幕上第0行的第0点显示红色
*(plcd + 1) = 0xff0000;//屏幕上第0行的第1个点显示红色
	*(plcd + 2) = 0xff0000;//屏幕上第0行的第2个点显示红色
	...
如果是任意的一个点(x,y)如何通过 plcd来显示颜色呢?
(0,0)------------------------------->x
		|
		|
		|
		|		(x,y)
		|
		|
y

*(plcd + 800*y + x) = 0xff0000;

void lcd_draw_point(int x, int y, int color)
	{
		if (x >= 0 && x < 800 && y >= 0 && y < 480)
		{

*(plcd + 800*y + x) = color;
}

}
....
	关键点:
		如何获取linux内核帧缓冲的首地址呢?

内存映射

内存函数:

mmap: memory map

NAME
mmap, munmap - map or unmap files or devices into memory

SYNOPSIS
#include <sys/mman.h>

mmap用来把内核和文件的内存,映射到应用程序空间。
目的是让应用程序通过指针直接去访问内核、驱动、文件的内容。
void* mmap(void *addr, size_t length, int prot,
int flags, int fd, off_t offset);

addr: 地址,表示您要映射到哪个位置上去。
有人说,我怎么知道映射到哪个位置上去。
一般的人不知道,所以此处可以填 NULL,表示让
操作系统自动分配。
length:
要映射的内存区的长度,如: 800*480*4
prot: 映射区的权限,如下:
PROT_READ
PROT_WRITE: 可读可写
PROT_EXEC : excute,可执行
PROT_NONE
flags: 映射标志。
MAP_SHARED <<<<<<<<
MAP_PRIVATE

fd :文件描述符,表示您要映射哪个文件。

offset:偏移量,表示您要从文件的哪个位置开始映射
返回值:
成功,返回映射区的首地址。
失败,返回MAP_FAILED

munmap解映射。
int munmap(void *addr, size_t length);
addr:要解映射的地址
length:长度
返回值:
成功返回0
失败返回其他值

BMP图片解析及显示

bitmap ,位图文件, microsoft。
是一种无压缩的图片格式 。

每一张BMP图片文件 ,可以分为如下四个部分:

BITMAP文件头
14bytes

DIB文件头
40bytes
0x12处的4个字节

width:
int width;
lseek(fd, 0X12, SEEK_SET);
read(fd, &width, 4);

width > 0
			每一行的像素点的数据是从左至右保存的
width < 0
			每一行的像素点的数据是从右至左保存的

height:
0x16处的4个字节
int height;
lseek(fd, 0x16,SEEK_SET);
read(fd, &height, 4);

height > 0
			先保存了是最底下一行的数据, 从下至上保存每一行
		height < 0
			先保存了最上面一行的数据,从上至下保存每一行

depth:
色深。指的是每个像素点数据所占的bits位数。
1, 2, 4, 8, ……。

depth = 24
			RGB888
depth = 32
			ARGB8888

调色板
标准的图片(depth=24 or 32),这一部分没有

像素数组
保存了每一个像素点的颜色值。

如:
			width > 0
			height > 0
			depth = 24
B G R (图片最左下角的那个点的颜色值)

思考:
(1) 图片缩放功能

(2) 图片显示的动画功能

字模

横向取模

纵向取模

纵向取模_多个字

字模图片

实现图片的动画显示效果

线程和音乐播放

程序的运行方式

串行方式:
先把程序1执行完毕,然后再执行程序2,……。

缺点:
		CPU利用率非常低。
程序
			S1 输入数据
			S2 运算
			S3 写回结果
为了提高CPU利用率

并发方式
允许多个程序同时运行。

为了提高CPU利用率,程序的运行采用并发的方式。
	现代操作系统为了实现程序的并发方式,特地引入进程。
引入进程的目的是为了实现并发,让多个程序同时运行,
	请问操作系统是如何达到这个目的呢?

进程状态

程序:静态的概念
进程:动态的概念。
进程就一个程序的执行过程。

Ready
	Running
	Blocking
进程是粗粒度的并发,
	线程是细粒度的并发。线程是进程内部一条指令执行路径。
			一个进程可以有多个线程,一个进程内部的多个线程
			共享整个进程地址空间的。
			一个进程内的线程通信 效率 要比 多个进程通信效率高。

Linux线程创建

pthread: posix thread

NAME
pthread_create - create a new thread

SYNOPSIS
#include <pthread.h>

pthread_t 类型是用来描述一个线程信息,如:线程id,...
       			 pthread_t的实例是用来 唯一标识一个线程的。
int pthread_create(pthread_t *thread,
       						const pthread_attr_t *attr,
                          void *(*start_routine) (void *),
                          void *arg);
thread: 指向的空间,用来保存线程id,
                         attr: 用来指定线程的属性,一般此处填NULL,
                         		采用默认的线程属性。
start_routine: 函数指针,指向“线程函数”
                         arg: 将作为“线程函数”参数
                  返回值:
                  	成功返回 0
                  	失败返回 -1,
Compile and link with -pthread.
例子:

	#include <pthread.h>
	     	void* task1(void *data)
	{

		//while (1)
		//{
		     	//	}
	//	system("madplay  xxx.mp3");
		char cmd[32];

		sprintf(cmd, "madplay %s", mp3_file_name);

		system(cmd);
	}
	     	pthread_t tid;
	main()
	{

		ret = pthread_create(&tid, NULL, task1, NULL);
		if (ret != 0)
		{

		}

		//...
	}

获取传感器数据

上位机和下位机

在项目中,经常需要与其他的外部模块进行通信。
通信双方,其中一方为上位机,另外一方为下位机。
上位机:
把性能比较强,大部分数据处理在上位机完成。

下位机:
			功能比较单一,一般只负责数据采集的那一端。

GY-39通信接口及协议

UART

//串口所对应的文件名
	#define COM2 "/dev/ttySAC1"
	#define COM3 "/dev/ttySAC2"
	#define COM4 "/dev/ttySAC3"

(1) open
(2) 配置串口参数

//初始化串口
//file: 串口所对应的文件名
//baudrate:波特率
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;
}

(3)read/write

(4)close


上位机                             GY39
			---------------------->
			cmd(0xA5, 0x83, 0x28)
GY39数据输出格式:
帧头 帧头 类型	 长度						 校验和
		0xA5 0XA5 type	 len  data1 data2 ... dataN  checksu

触摸屏输入事件获取

Linux输入子系统的层次

模块化是一个系统的属性,把一个系统分为 高内聚、低耦合的模块。
高内聚:把逻辑上相关的对象(概念)划分在一起
低耦合:减少模块间的依赖

模块化不是结构化分析方法特有的,面向对象的分析方法也有。
结构化分析方法 模块化是以子程序(子系统)为主
面向对象分析方法 模块化是 类和对象 划分为主。

linux下所有系统识别的“输入设备”,都会生成一个标准的文件接口:

/dev/input/eventX(X=0,1,2,...)

我的输入设备对应的到底是哪个文件 ?
/proc/bus/input/devices
上面这个文件里,把每个输入设备的信息保存起来.

读取事件的流程:

(1) open
	(2) read  事件结构体 struct input_event
	(3) 解析
	(4) close

事件结构体

struct input_event

struct input_event {
			struct timeval time; //事件发生的时间
		     __u16 type;
		     		type代表事件的类型,
		     			EV_KEY   按键类型

		     			EV_REL  relative 相对事件,: 鼠标
		     					相对于上一个点的位移(x轴和y轴)

		     			EV_ABS  absolute 绝对事件,: 触摸屏事件
		     					当前点的绝对坐标


			__u16 code;
					根据type的不同,code含义也不同。
					if type == EV_KEY, 
						code就是相应按键的键值
							 KEY_A
							 KEY_B
							 ...
							 BTN_TOUCH 

					if type == EV_ABS, 
						code就是相应的绝对坐标
							ABS_X  x轴事件
							ABS_Y  y轴事件
							ABS_PRESSURE  压力事件


			__s32 value;
				 根据type的不同,value含义也不同
				 	if type == EV_KEY
				 		value  1  按下
				 		value  0  弹起

				 	if  type == EV_ABS
				 		if code == ABS_X 
				 			value X轴的坐标值

				 		if code == ABS_Y
				 			value  Y轴的坐标值

				 		if code == ABS_PRESSSURE
				 			value  压力值
				 				>0  触摸屏按下
				 				=0  弹起
		};

Linux灯光驱动调用

驱动是什么?

A 硬件 B 软件

应用程序是也是软件。

驱动是操作硬件的代码。驱动层次位于应用与硬件之间。
驱动对上提供操作硬件的接口,并且需要向应用屏蔽硬件
实现的细节。 驱动对下(硬件)检测/监测硬件上的改变或
状态的变化,并向上报告其状态。

驱动与应用程序有什么区别?

裸机驱动
BSP驱动
linux驱动
windows驱动
……
在不同的平台下写驱动,框架及方法有不一样的。

裸机驱动:
		不跑操作系统。
		你想怎么操作硬件,想提供什么接口给应用,都由你来写。
linux/widows驱动:
		你必须按照linux/windows定义的框架,定义好的接口,去实现。

驱动控制灯光

open
read/write/ioctl/….
close

加载驱动:
insmod kobject_led.ko

卸载驱动:
rmmod kobject_led.ko

一旦加载成功,在开发板目录
/sys/kernel/gec_ctrl/
生成
led_d7
led_d8
led_d9
led_d10
led_all
beep

控制 led_d7
		open("/sys/kernel/gec_ctrl/led_d7", O_RDWR)
int on = 1; //写1到文件表示 写
		write(fd, &on,  4);
on = 0; //写0到文件表示 关
		write(fd, &on, 4);
[root@GEC6818 /mnt]# mount -t nfs 192.168.3.16:/mnt/hgfs /mnt -o nolock //要注意虚拟机文件的权限问题

img

挂载成功,现在主机,虚拟机,开发板共享了同一个目录。


文章作者: auntyang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 auntyang !
  目录