嵌入式Linux应用开发基础
韦东山的视频
一、hello world背后的故事
agrc 与argv
argc :代表了输入参数的个数
**argcv(*argv[])是指针的指针/指针的数组,argv[0] 表示输入第一个参数的地址空间
要是我们输入./hello xzj 这样就是输入两个参数,第一个参数./hello 第二个参数xzj
argc = 2
argv[0] = ./hello argv[0]里面存放一个指针指向./hello
argv[1] = xzj argv[1]里面存放一个指针指向 xzj
头文件
一般是将一些自定义的函数或者系统的库导入到主程序中
< > 库函数
“ ” 自定义函数
头文件的代码在编译链文件中(<>),或者在主程序的目录下("")
当然我们也可以自己指定
挂载
我们只能访问开发板自身携带的目录,要是想访问外部目录,就把外部目录挂载在自带的某个目录,然后通过自身目录访问外部目录
mount -t nfs -o nolock,vers=3 192.168.1.137:/home/book/nfs_rootfs /mnt
//挂载 192.168.1.137的/home/book/nfs_rootfs到 本地的/mnt目录上
二、gcc编译流程
显示编译过程
gcc - o test test.c -v //只需要在原代码后面加上-v就可以
头文件查找是在预处理阶段实现的
链接就是将很多的可执行文件.o(.so库文件、.a静态库文件)合在一起,比如一个库文件生成一个文件,一个自定义程序生成一个文件,这就会有很多的文件
链接就是将很多的二进制的.文件组合在一起
头文件查找
<>:工具链所指定的系统目录中查找(一般都是在工具链文件夹中)
交叉编译工具链的路径是
头文件查找路径:
指定找某个位置查找头文件
<std.h> 头文件查找路径默认是工具链中(库)
“std.h” 头文件查找路径默认是当前目录(自定义函数)
gcc -c -o main.o main.c -v -I ./
使用-I在./路径下查找头文件(也会在默认目录中查找)
静态库与动态库
我们在编译的时候,除了主程序以外还有很多子程序,需要我们把他们编译完后互相可以调用,才可以执行,我们就把这些的可执行.0文件全部封装成一个库,这种封装也就是链接了
静态库(比较大)
将多个.o压缩为一个.libsub.a库文件
ar crs libsub.a sub.o sub2.o sub3.o //没有指定目录一般都是当前目录中
主程序与库文件链接为一个程序
gcc -o test main.o libsub.a //要是.a不在当前路径需要,指定它的绝对或者相对路径
动态库
gcc -shared -o libsub.so sub.o sub2.o sub3.o //将多个.o文件生成一个动态库.so
1、主程序与动态库链接
gcc -o test main.o libsub.so //库在当前目录下
//指定路径查找库文件
gcc -o test2 main.o -L ./ -lsub //lsub就是libsub.so的简写
2、在运行的时候也要指出动态库
修改环境变量
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./
修改环境变量为当前目录
总结
在命令后面加上-v,就可以看到在哪里查找文件
查看工具链的默认头文件与库文件查找路径
echo 'main(){}'| gcc -E -v -
三、Makefile的使用
作用细化步骤,减少编译次数,其他依赖文件都在Makefile同一目录下
makefile的最基本规则
依赖更新,目标就要更新
当依赖比目标新就会指向命令,更新目标文件
执行make,就会在当前目录下找到名字叫做Makefile的文件
make 命令,本意是生成test,其他都是附带的(第一次是三条命令都执行)
test:a.o b.o
gcc - o tets a.o b.o
test依赖于a.o b.o 要是发现依赖比test新就会执行gcc - o tets a.o b.o 生成新test
a.o :a.c
gcc -c -0 a.0 a.c
a.o 依赖于a.c,要是发现依赖a.c比a.o新,就会运行gcc -c -0 a.0 a.c 生成新的a.o
b.o :b.c
gcc -c -0 a.0 a.c
a.o 依赖于a.c,要是发现依赖a.c比a.o新,就会运行gcc -c -0 a.0 a.c 生成新的a.o
Makefile的语法
运行流程
当我们敲下了make指令的时候,会从第一条规则开始,也就是重链接出edit文件。但是在链接之前,make必须首先处理edit文件所依赖的输入文件,也就是.o文件。所以make会先编译所有需要重新编译的源文件,直到.o文件齐全。这有点像是一个金字塔结构,底层的target完成以后才能做顶层的target
目标文件 $@ 表示目标文件 表示最终目的,要是没有就默认第一个是目标
怎么使用makefile
make[目标] 要是没有目标默认第一个目标
通配符
test: a.o b.o
gcc -o test a.o b.o
a.o:a.c
gcc -c -o a.o a.c
b.o:b.c
gcc -c -o b.o b.c
test: a.o b.o
gcc -o test $^ //$^表示全部依赖是指上面的a.o,b.o两个
%.o : %.c
gcc -c -o $@ $< //$@ 表示目标的目标文件是上面的依赖.o, $< 这个依赖是.c
假想目标
这样输入make clean 就可清除所有文件,但是要是当前目录下面有clean文件就无法执行这个命令
.PHONY clean 定义为假想目标,这样就不会判断名为clean的文件是否存在
@each B = $(B) 这样就不会打印出each B = B,只有B
makefile函数
$for(foreach var,list,text)
取出list中的每一个元素,然后赋值給var,最后把var改成text描述的形式
A = a b c
B = $(foreach f,$(A),$(f).o) # 将A 取出给f,然后变成.o的形式
all:
@each B = $(B) //all 是表示B这个是最终目标文件
@each B = $(B) 这样就会打印出这条命令,只会打印出B =
输出
$(filter pattern...,text) #在text中取出符合pattern格式的值
$(filter-out pattern...,text) #在text中取出不符合pattern格式的值
C = a b c d/
D =$(filter %/, $(c))
E =$(filter-out %/, $(c))
all:
@each D = $(D)
@each E = $(E)
$(wildcard pattern)
pattern所描述的文件是否存在,都列出来
头文件的问题
我们在某个.c文件中引入了一个头文件,一般而言只有第一次编译正确,后续要是修改了头文件,我们的最终目标并不会重新编译
修改为
但是太麻烦了
gcc -M c.c # 显示当前c.c文件的依赖
gcc -M -MF c.d c.c # 将c.c文件的依赖生成一个c.d文件
gcc -c -o c.o c.c -MD -MF c.d # 将c.c文件的依赖生成一个c.d文件同时也会编译生成c.o
objs = a.o b.o c.o
dep_files := $(patsubst %, .%.d , $(objs)) #依赖文件
dep_files = $(wildcard $(dep_files)) #判断依赖文件是否存在,存在就包含进来
test: $(objs)
gcc -o test $^
ifneq ($(dep_files),)
include $(dep_files)
endif
%.o : %.c
gcc -c -o $@ $< -MD -MF .$@.d #.$@.d 是隐藏文件,这样我们就看不到依赖文件
clean :
rm *.o test
distclean :
rm $(dep_files)
.PHONY :clean
CFLAGS :编译选项将警告显示为错误
通用makefile的使用
四、文件IO读写文件
联系三者
应用程序要是访问文件系统就会使用内核中fs代码,要是硬件就会使用内核中的驱动代码
库函数是应用程序才可以调用的
man 2 函数 来看函数用法
F 前翻 D翻 Q退出
open函数怎么调用内核的呢?
用户层与内核层是不同两个代码,我们用户层使用直接使用open,会调用从glib.c之类的库中调用open函数
内核接受到信息提取出参数然后就会调用内核层中的sys_open函数,去操作硬件
APP与内核之间有个渠道,一旦触发操作系统就会执行内核中某个位置的代码,传入参数不同,执行不同函数
APP访问文件的接口函数是open /read /write
文件的来处
man查找函数
可以用来查找函数所需要的系统头文件
sd卡挂载
查看硬件设备
ls /dev
挂载
mount /dev/sda1 /mnt
#挂载sda1到mnt目录
sys 虚拟文件
操作硬件
我们使用硬件对应的驱动程序来实现操作硬件
显示所有的驱动硬件节点
ls /dev/*
第五章 Framebuffer 应用编程
内存每次保存一帧图像,将每个数据传给LCD控制器,LCD控制器通过驱动程序,设置好lcd屏幕的图像
第六章 文字显示及图像显示
1、ASCII
一个字节八位第一位不使用,就剩下2^7 = 128,可以表示128个字符
每个字符都可以使用一个值来表示
局限;中文就不够了
2、ANSI
ASCII中的英文字符加符号使用一个128内的值(字节),其他的使用两个值(两个字节)来表示
非ASCII是要选择对应的字符集,字符集选择的不同显示的字符不同
3、UNICDE
全球通用,地球上任意一个字符,都只对应一个值
可以由一个字符表示一个字,也可以多个字符表示一个字。
编码格式将字符隔开
用三个字节表示一个字符态浪费,我们使用两个字节表示
小字节序 --UTF-16 LE 数值中权重低的存放在前面
大字节序 --UTF-16 BE 数值中权重低的存放在后面
缺点:要是传输过程中丢失一个字符,全部都乱了
UTF-8
6.2 点阵字符的显示
在内核存放一个文件里面有各个字符的点阵
这个A的显示,每一个字符都表示一行显示
6.3 中文字符的点阵显示
我们在编写程序的时候要设置寻址方式,我们汉字库是GB2312的在程序里面就要设置为GB2312格式
6.4 交叉编译程序
1、编译程序时去哪里找头文件
在交叉编译工具链的某个include目录里面
2、链接时去哪里找库文件
系统目录:交叉编译工具链的某个lib目录
库文件:
库是已经写好的、成熟的、可复用的代码,编译好的
头文件:一些声明的代码,比如stdio.h
3、运行时库文件在板子上,头文件不需要了
注意我们编译可能会缺少头文件、库文件,毕竟我们下载的工具链只有常见的库和头文件,这个时候我们就需要将需要使用的库和头文件编译好然后存放到编译链库文件、头文件的位置
我们库文件还需要放到运行板子的库文件中,程序运行的时候需要库文件不需要头文件
程序在板子上运行时,需要用到板子上/lib 或/usr/lib 下的库文件;程序 运行时不需要头文件。
第七章 输入系统应用编程
所有的输入设备都有统一的驱动程序,可以使用统一的接口,使用一样的代码获得不同的数据
输入系统核心层将各种数据转换成统一的格式上报给事件层
事件层将各种数据发送给设备相应的设置节点,只要app打开节点就可以获取相应数据
1、我们按下硬件,硬件发生中断
2、输入系统驱动层就会处理中断,将信息上传给核心层
3、输入系统核心层将各种数据转换成统一的格式上报给事件层
4、事件层将各种数据发送给设备相应的设置节点
5、只要app打开节点就可以获取相应数据
事件层(结构体)
内核中表示一个输入设备和信息
查询方式
APP在调用open函数的时候,传入O_NONBLOCK 表示非阻塞
APP调用read函数读取数据的时候,如果驱动程序中有数据,那么APP的read函数会返回数据,否则也会立刻返回错误
休眠-唤醒方式
APP在调用open函数的时候,不要传入O_NONBLOCK,要是驱动程序中有数据,read函数就会返回数据,不然APP就会在内核态休眠,等待驱动程序中有数据出现,再度激活
POLL/SELECT 方式
APP 先调用 open 函数时,先调用 poll 或 select 函数这 2 个函 数中可以传入“超时时间”。
它们的作用是:如果驱动程序中有数据,则立刻返回; 否则就休眠。在休眠期间,如果有人操作了硬件,驱动程序获得数据后就会把 APP 唤醒,导致 poll 或 select 立刻返回;如果在“超时时间”内无人操作硬件,则 时间到后 poll 或 select 函数也会返回。APP 可以根据函数的返回值判断返回 原因:有数据?无数据超时返回?
tslib (触摸屏的库)
第 八章 网络编程
传输三要素
源、目的、长度
打开文件
fd = open("文件名)
read(fd,buf,len); // 从fd中读取文件到buf,读取长度为len
write(fd,buf,len); //将buf中的文件写到fd中去,读取长度为len
在访问一个电脑的时候,为了访问不同app,需要加上端口号,来区分不同应用
IP + 端口 = 源
服务器,TCP传输
0、得到一个文件句柄
1、服务器绑定自己的端口来检测,是否需要响应请求
2、开始监测数据
3、接受客客户端建立连接
发数据:send
收数据:relv
客户端
1、
2、connect与服务端accept建立连接
初等函数介绍
socket 套接字,也就是通信的一个接口,将通信比作插电,socket就是插座盒子
1、socket() 创建socket描述符
int socket(int domain,int type,int protocol)
//成功返回非负描述符,失败返回-1
socket的地址类型,信息传送方式,传输协议
2、bind()绑定实际地址
int bind(int sockfd, const struct sockaddr *addr, socklen_t
addrlen);
//返回值:成功则为0,失败为-1
套接字文件描述符,协议结构体,协议结构体大小
3、listen()主机监听
int listen(int sockfd, int backlog);
//返回值:成功则为0,失败为-1
要监听的socket描述字,可以监听的最大个数
4、connect() 从机链接
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//返回值:成功则为0,失败为-1
客户端的socket描述字、服务器的socket地址、socket地址的长度、
5、accept 建立链接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//返回值:成功则为0,失败为-1
服务器的socket描述字、返回客户端的协议地址、协议地址的长度。
6、常用数据类型
int sockfd 存放socket的文件描述符
1、通用的socket地址
socket地址的是结构体sockaddr
# include <bits/socket.h>
struct sockaddr
{
sa_family_t sa_family;//地址族类型变量
char sa_data[14];//存放socket地址值
};
2、专门的socket地址sockaddr_in(一般来表示客户端或者服务端地址)
struct sockaddr_in
{
sa_family_t sin_family;//地址族:AF_INET
u_int16_t sin_port;//端口号,要用网络字节序表示(下面详细讲解)
struct in_addr sin_addr;//IPv4地址结构体,见下面
};
struct in_addr
{
u_int32_t s_addr;//IPv4地址,要用网络字节序表示
};
所有专用socket地址类型的变量在实际使用时都需要转化为通用socket地址类型sockaddr(强制转换即可),因为所有协议族底层的socket编程接口使用的都是 struct_sockaddr 结构体参数。
3、sockfd:为socket文件描述符
,socket系统调用函数的返回值
4、addrlen参数指出该socket地址的长
度
5、my_addr:地址
6、buf:缓冲区位置
TCP
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
/* socket
* bind
* listen
* accept
* send/recv
*/
#define SERVER_PORT 8888 //定义一个服务端的端口
#define BACKLOG 10 //定义最大只有10个客户端可以同时连接服务端
int main(int argc, char **argv)
{
int iSocketServer; //接受服务端套接字的文件描述符
int iSocketClient; //接受客户端套接字的文件描述符
struct sockaddr_in tSocketServerAddr;//服务端的socket专门地址
struct sockaddr_in tSocketClientAddr;//客户端的socket专门地址
int iRet; //接受bing函数的返回值、接受listen函数返回值
int iAddrLen;//存放地址大小
int iRecvLen; //一个缓冲区,存放读取的信息
unsigned char ucRecvBuf[1000];
int iClientNum = -1;
signal(SIGCHLD,SIG_IGN);
iSocketServer = socket(AF_INET, SOCK_STREAM, 0);//创建一个服务端的socket套接字
if (-1 == iSocketServer) //返回值为-1,创建socket失败
{
printf("socket error!\n");
return -1;
}
//给服务端地址结构体赋值
tSocketServerAddr.sin_family = AF_INET; //地址族,也就是某些地址的集合
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short */ //监测的服务端端口号,htons主机字节序转换成网络字节序
tSocketServerAddr.sin_addr.s_addr = INADDR_ANY; //监测的ip地址,表示本机所有ip
memset(tSocketServerAddr.sin_zero, 0, 8); //将某段地址初始化为0
//绑定
iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));//绑定服务端socket与服务端地址
if (-1 == iRet)
{
printf("bind error!\n");
return -1;
}
//监听
iRet = listen(iSocketServer, BACKLOG);//谁监听,最多监听多少路连接
if (-1 == iRet)
{
printf("listen error!\n");
return -1;
}
//服务器等待响应
while (1)
{
iAddrLen = sizeof(struct sockaddr);
//客户端连接上
iSocketClient = accept(iSocketServer, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);//服务器的socket描述字、返回客户端的协议地址、协议地址的长度
if (-1 != iSocketClient)
{
iClientNum++; //客户端一
printf("Get connect from client %d : %s\n", iClientNum, inet_ntoa(tSocketClientAddr.sin_addr));
if (!fork())
{
//fork<0,子进程
/* �ӽ��̵�Դ�� */
while (1)
{
/* ���տͻ��˷��������ݲ���ʾ���� */
iRecvLen = recv(iSocketClient, ucRecvBuf, 999, 0);//接受客户端的信息,存放在 ucRecvBuf,最多999个。标志位是0
if (iRecvLen <= 0)//没接受到数据
{
close(iSocketClient);
return -1;
}
else //打印出来
{
ucRecvBuf[iRecvLen] = '\0';
printf("Get Msg From Client %d: %s\n", iClientNum, ucRecvBuf);
}
}
}
}
}
close(iSocketServer);
return 0;
}
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
/* socket
* connect
* send/recv
*/
#define SERVER_PORT 8888 //定义一个服务端的端口号
int main(int argc, char **argv)
{
int iSocketClient; //接受客户端socket函数返回值
struct sockaddr_in tSocketServerAddr; //特殊的地址结构体,服务端地址结构体
int iRet; //存放connect函数返回值
unsigned char ucSendBuf[1000];
int iSendLen;
//客户端输入./client ip
if (argc != 2)
{
printf("Usage:\n");
printf("%s <server_ip>\n", argv[0]);
return -1;
}
iSocketClient = socket(AF_INET, SOCK_STREAM, 0);//创建一个客户端的套接字
//给服务端的地址结构体赋值
tSocketServerAddr.sin_family = AF_INET; //地址族
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short 服务端端口号 */
//tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))//设置要连接的ip
{
printf("invalid server_ip\n");
return -1;
}
memset(tSocketServerAddr.sin_zero, 0, 8);
iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));//客户端socket,服务端地址,地址协议大小
if (-1 == iRet)
{
printf("connect error!\n");
return -1;
}
//不断发数据
while (1)
{
if (fgets(ucSendBuf, 999, stdin))//从标准输入stdin中获得999个数据,存放在ucSendBuf
{
iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);//通过客户端socket发送数据从ucSendBuf中
if (iSendLen <= 0) //长度小于0
{
close(iSocketClient);
return -1;
}
}
}
return 0;
}
fork
创建出一个进程,两个进程走不同的路
主进程是走fork > 0
子进程走fork <= 0
每连接一个客户端就fork出一个
UDP编程
第九章 多线程编程
线程实最小的操作系统度量单位,多个线程或者一个组成进程。进程就是完成一件事情。
线程的有点
1、共享这个进程中的全局变量
进程号:PID
线程号:tid
主线程先执行,然后是子线程,主线程sleep就会退出主线程,然后指向子线程。
查看线程id ps -T/ ps
杀死线程 kill -9 线程号
一、线程的是使用
线程的创建
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routi
ne) (void *), void *arg);
第一个参数是一个pthread_t类的指针,保存了线程的线程号
第二个参数是pthread_attr_t类指针,一般是NULL默认属性
第三个参数是一个函数指针,就是线程的执行函数。类型是void*
第四个参数是向线程执行函数中传入参数,要是不传就是NULL
向线程中传入参数
线程的退出与回收
1、线程主动退出
2、线程被动退出
3、线程资源回收(阻塞方式)
4、线程资源回收(非阻塞方式)
二、线程的控制
第10章 串口应用编程(UART接口)
10-2 硬件知识
作用
1、打印调试信息
2、外接各种模块,gps
串口怎么通信,通信协议是什么?
怎么发生一个1 Byte的数据给对方
"A",0X41, obo1ooooo1这个八位数据发送
1、双方约定好每一位占据的时间,也就是波特率
开发板的每个电位都会持续一段时间,我们pc机会在中间时间段数据位读取数据
较验位
奇/偶 :数据位加上较验位中为1的个数是奇数
传输一个字节需要10位,8位数据一位开始一位停止
10-3 TTY体系中设置节点的差别
tty虚拟终端
10-4 串口应用编程
设置行规层,比如波特率、数据位、停止位、检验位、RAM模式、一有数据就返回
设置行规层的一些函数
二、应用编程
要是想获得串口的最原始数据,将行规则设置为原始模式
回环流程图
1、左边串口将a送到行规层,然后因为是字符不会继续向上发送
2、左边串口将ent发送带行规层,然后行规层继续向上发送,app获得一个字符a
3、app将字符下发给右边串口
4、然后回环到接受引脚
1、打开串口
2、设置串口
3、读取数据
4、关闭串口
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <stdlib.h>
/* set_opt(fd,115200,8,'N',1) */
int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)// 打开串口的文件句柄、串口速度、数据位、效验类型、停止位
{
struct termios newtio,oldtio;//创建两个结构体newtio、oldtio
/*保存测试现有串口参数设置,在这里如果串口号等出错,会有相关的出错信息*/
if ( tcgetattr( fd,&oldtio) != 0) {
perror("SetupSerial 1");
return -1;
}
bzero( &newtio, sizeof( newtio ) );
/*步骤一,设置字符大小*/
newtio.c_cflag |= CLOCAL | CREAD;
newtio.c_cflag &= ~CSIZE;
newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); /*Input*/
newtio.c_oflag &= ~OPOST; /*Output*/
/*设置停止位*/
switch( nBits )
{
case 7:
newtio.c_cflag |= CS7;
break;
case 8:
newtio.c_cflag |= CS8;
break;
}
/*设置奇偶校验位*/
switch( nEvent )
{
case 'O'://奇数
newtio.c_cflag |= PARENB;
newtio.c_cflag |= PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
break;
case 'E': //偶数
newtio.c_iflag |= (INPCK | ISTRIP);
newtio.c_cflag |= PARENB;
newtio.c_cflag &= ~PARODD;
break;
case 'N': //无奇偶校验位
newtio.c_cflag &= ~PARENB;
break;
}
//设置波特率
switch( nSpeed )
{
case 2400:
cfsetispeed(&newtio, B2400);
cfsetospeed(&newtio, B2400);
break;
case 4800:
cfsetispeed(&newtio, B4800);
cfsetospeed(&newtio, B4800);
break;
case 9600:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
case 115200:
cfsetispeed(&newtio, B115200);
cfsetospeed(&newtio, B115200);
break;
default:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
}
//设置停止位
if( nStop == 1 )
newtio.c_cflag &= ~CSTOPB;
else if ( nStop == 2 )
newtio.c_cflag |= CSTOPB;
/*设置等待时间和最小接收字符*/
newtio.c_cc[VMIN] = 1; /* 读数据时的最小字节数: 没读到这些数据我就不返回! */
newtio.c_cc[VTIME] = 0; /* 等待第1个数据的时间:
* 比如VMIN设为10表示至少读到10个数据才返回,
* 但是没有数据总不能一直等吧? 可以设置VTIME(单位是10秒)
* 假设VTIME=1,表示:
* 10秒内一个数据都没有的话就返回
* 如果10秒内至少读到了1个字节,那就继续等待,完全读到VMIN个数据再返回
*/
tcflush(fd,TCIFLUSH);///*处理未接收字符*/
/*激活新配置*/
if((tcsetattr(fd,TCSANOW,&newtio))!=0)
{
perror("com set error");
return -1;
}
//printf("set done!\n");
return 0;
}
int open_port(char *com)
{
int fd;
//fd = open(com, O_RDWR|O_NOCTTY|O_NDELAY);
fd = open(com, O_RDWR|O_NOCTTY);//打开串口com、O_NOCTTY标志告诉 UNIX 这个程序不需要成为该端口的“控制终端”、O_NDELAY标志告诉 UNIX 这个程序不在乎 DCD信号线处于什么状态
// 打开失败
if (-1 == fd){
return(-1);
}
if(fcntl(fd, F_SETFL, 0)<0) /* 设置串口为阻塞状态,读数据不等待,没有数据就返回0*/
{
printf("fcntl failed!\n");
return -1;
}
return fd;
}
/*
* ./serial_send_recv <dev>
*/
int main(int argc, char **argv)
{
int fd; //存放open函数的返回值
int iRet; //存放set函数的返回值
char c;
/* 1. open */
/* 2. setup
* 115200,8N1
* RAW mode
* return data immediately
*/
/* 3. write and read */
if (argc != 2)//输入参数不是两个就会报错
{
printf("Usage: \n");
printf("%s </dev/ttySAC1 or other>\n", argv[0]);
return -1;
}
//1、打开串口
fd = open_port(argv[1]);
if (fd < 0)
{
printf("open %s err!\n", argv[1]);
return -1;
}
// 2、设置串口
iRet = set_opt(fd, 115200, 8, 'N', 1);
if (iRet)
{
printf("set port err!\n");
return -1;
}
printf("Enter a char: ");
//循环读写数据
while (1)
{
scanf("%c", &c);
// 3、读写串口
iRet = write(fd, &c, 1);
iRet = read(fd, &c, 1);
if (iRet == 1)
printf("get: %02x %c\n", c, c);
else
printf("can not get data\n");
}
return 0;
}
输入两个字符一个是 b ent
get : a
get: end
gps
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <stdlib.h>
/* set_opt(fd,115200,8,'N',1) */
int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
{
struct termios newtio,oldtio;
if ( tcgetattr( fd,&oldtio) != 0) {
perror("SetupSerial 1");
return -1;
}
bzero( &newtio, sizeof( newtio ) );
newtio.c_cflag |= CLOCAL | CREAD;
newtio.c_cflag &= ~CSIZE;
newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); /*Input*/
newtio.c_oflag &= ~OPOST; /*Output*/
switch( nBits )
{
case 7:
newtio.c_cflag |= CS7;
break;
case 8:
newtio.c_cflag |= CS8;
break;
}
switch( nEvent )
{
case 'O':
newtio.c_cflag |= PARENB;
newtio.c_cflag |= PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
break;
case 'E':
newtio.c_iflag |= (INPCK | ISTRIP);
newtio.c_cflag |= PARENB;
newtio.c_cflag &= ~PARODD;
break;
case 'N':
newtio.c_cflag &= ~PARENB;
break;
}
switch( nSpeed )
{
case 2400:
cfsetispeed(&newtio, B2400);
cfsetospeed(&newtio, B2400);
break;
case 4800:
cfsetispeed(&newtio, B4800);
cfsetospeed(&newtio, B4800);
break;
case 9600:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
case 115200:
cfsetispeed(&newtio, B115200);
cfsetospeed(&newtio, B115200);
break;
default:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
}
if( nStop == 1 )
newtio.c_cflag &= ~CSTOPB;
else if ( nStop == 2 )
newtio.c_cflag |= CSTOPB;
newtio.c_cc[VMIN] = 1; /* 读数据时的最小字节数: 没读到这些数据我就不返回! */
newtio.c_cc[VTIME] = 0; /* 等待第1个数据的时间:
* 比如VMIN设为10表示至少读到10个数据才返回,
* 但是没有数据总不能一直等吧? 可以设置VTIME(单位是10秒)
* 假设VTIME=1,表示:
* 10秒内一个数据都没有的话就返回
* 如果10秒内至少读到了1个字节,那就继续等待,完全读到VMIN个数据再返回
*/
tcflush(fd,TCIFLUSH);
if((tcsetattr(fd,TCSANOW,&newtio))!=0)
{
perror("com set error");
return -1;
}
//printf("set done!\n");
return 0;
}
int open_port(char *com)
{
int fd;
//fd = open(com, O_RDWR|O_NOCTTY|O_NDELAY);
fd = open(com, O_RDWR|O_NOCTTY);
if (-1 == fd){
return(-1);
}
if(fcntl(fd, F_SETFL, 0)<0) /* 设置串口为阻塞状态*/
{
printf("fcntl failed!\n");
return -1;
}
return fd;
}
int read_gps_raw_data(int fd, char *buf)
{
int i = 0;
int iRet;
char c;
int start = 0;
while (1)
{
iRet = read(fd, &c, 1);//从fd中读取文件到&c中,长度为1
if (iRet == 1)
{
if (c == '$')//表示开始
start = 1;
//将c中从数据存放到buf缓冲区中去
if (start)
{
buf[i++] = c;
}
//读取数据结束
if (c == '\n' || c == '\r')
return 0;
}
else
{
return -1;
}
}
}
//解析数据
/* eg. $GPGGA,082559.00,4005.22599,N,11632.58234,E,1,04,3.08,14.6,M,-5.6,M,,*76"<CR><LF> */
int parse_gps_raw_data(char *buf, char *time, char *lat, char *ns, char *lng, char *ew)
{
char tmp[10];
if (buf[0] != '$')//第一个字符
return -1;
else if (strncmp(buf+3, "GGA", 3) != 0)//从第三个符开始连续三个字符,与GGA相比是否相等
return -1;
else if (strstr(buf, ",,,,,"))//判断后面的",,,,,,"是否为buf的子串
{
printf("Place the GPS to open area\n");
return -1;
}
else {
//printf("raw data: %s\n", buf);
sscanf(buf, "%[^,],%[^,],%[^,],%[^,],%[^,],%[^,]", tmp, time, lat, ns, lng, ew);从buf中读取全部数据跳过","。然后存放在tmp、time等中
return 0;
}
}
/*
* ./serial_send_recv <dev>
*/
int main(int argc, char **argv)
{
int fd;
int iRet;
char c;
//定义一些缓冲区存放数据
char buf[1000];
char time[100];
char Lat[100];
char ns[100];
char Lng[100];
char ew[100];
float fLat, fLng;
/* 1. open */
/* 2. setup
* 115200,8N1
* RAW mode
* return data immediately
*/
/* 3. write and read */
if (argc != 2)
{
printf("Usage: \n");
printf("%s </dev/ttySAC1 or other>\n", argv[0]);
return -1;
}
fd = open_port(argv[1]);
if (fd < 0)
{
printf("open %s err!\n", argv[1]);
return -1;
}
iRet = set_opt(fd, 9600, 8, 'N', 1);
if (iRet)
{
printf("set port err!\n");
return -1;
}
while (1)
{
/* eg. $GPGGA,082559.00,4005.22599,N,11632.58234,E,1,04,3.08,14.6,M,-5.6,M,,*76"<CR><LF>*/
/* read line */
iRet = read_gps_raw_data(fd, buf); //读一行数据保存在buf中
/* parse line ,解析数据*/
if (iRet == 0)
{
iRet = parse_gps_raw_data(buf, time, Lat, ns, Lng, ew);
}
/* printf */
if (iRet == 0)
{
printf("Time : %s\n", time);
printf("ns : %s\n", ns);
printf("ew : %s\n", ew);
printf("Lat : %s\n", Lat);
printf("Lng : %s\n", Lng);
/* 纬度格式: ddmm.mmmm */
sscanf(Lat+2, "%f", &fLat);//从第三个字符开始读取lat,转换成%f,存放在flat中
fLat = fLat / 60;
fLat += (Lat[0] - '0')*10 + (Lat[1] - '0');
/* 经度格式: dddmm.mmmm */
sscanf(Lng+3, "%f", &fLng);
fLng = fLng / 60;
fLng += (Lng[0] - '0')*100 + (Lng[1] - '0')*10 + (Lng[2] - '0');
printf("Lng,Lat: %.06f,%.06f\n", fLng, fLat);//浮点型数小数点后六位数据
}
}
return 0;
}
第 11 章 I2C接口
开始信号:SCL维持高信号,SDA从高变低
停止信号:SCL维持高信号,SDA从底变高
对方设备就是在每个SCL脉冲的高电平时刻读取SDA得到一位数据
SDL变低就表示开始下一位,然后SDL变高,再读取第二位数据
从设备怎么回应,就是将SDA拉低电平
11-2、i2c协议
数据传输流程
写 操作
主设备发信号,从设备回应
读操作
从设备发信号,主设备回应
11-3 、SMBus协议
11-4 i2c-Tool
app 一般是通过驱动程序i2c-dev.c来操作硬件,内核提供了这个驱动的话就不需要再写了
uu 表示内核中存放该设置的驱动
11、6 编写app直接访问ERPROM
1、使用那个I2C控制器?
2、设备的地址是多少?
3、
更多推荐
所有评论(0)