6.串口通信和传感器的使用—-粤嵌gec6818开发板上实现智能家居(光照气度,压强,海拔,湿度,温度达到临界值蜂鸣器自动警报等)

一、串口通信及传感器数据采集

1.串口及初始化

串口是一种简单的通信接口,也是单片机中最常用,最简单的通信方式

通常传感器都是采用串口作为与上位机的通信接口

ARM板上提供了三个可以外接传感器的串口接口,位于开发板的右上角,

从上到下分别是:

​ 串口号 串口驱动设备文件名

​ CON2 “/dev/ttySAC1”

​ CON3 “/dev/ttySAC2”

​ CON4 “/dev/ttySAC3”

我们将传感器链接到对应的串口端口以后,在Linux中通过串口通信去获取该传感器的数据极其简单

只需要先打开串口设备文件,并将串口进行初始化配置,就可以调用write函数发送数据给传感器,调用read函数从传感器中获取数据

串口的初始化函数如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h> //串口配置需要的头文件
/*
 *	init_serial:自定义的用来打开串口文件并对串口进行初始化的函数
 *	arg:
 *		@fild:串口设备文件名,指定您要使用的串口
 *		@baudrate:整数,指定您要配置的串口的通信波特率
 *			9600/115200/19200
 *	配置的串口协议:数据位8,停止位1,不要校验,不要硬件控制流
 *	return:
 *		成功,返回打开 并初始化好的 串口的文件描述符,后期根据该返回值来与链接在该串口上的传感器进行通信
 *		失败返回-1
 **/
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;
}

常见的串口传感器,通信方式一般有两种:

1 配置式通信:上位机只需要发送一次配置命令给传感器,传感器就会周期性采集并返回数据回复给上位机

比如:GY-39

2.询问式通信:上位机发送一次命令给传感器,传感器就会采集并返回一次数据给上位机,直到下一次上位机再发送命令,传感器彩虹再一次采集回复数据==》上位机每发送一次指令,才能获取一次数据 ==》烟雾传感器

int main(void)
{
	int gy39_fd = 0;
	gy39_fd = init_serial("/dev/ttySAC1",9600);

	read(gy39_fd,...,...);//从gy39中读取数据存入到...
	write(gy39_fd,...,...);往gy_39里面写入...

}

在使用串口与传感器通信,来获取传感器采集到的数据时,我们必须能够熟练地阅读传感器的使用手册

2.GY-39的使用

接线引脚

​ VCC:电源正极,接上位机的5v(vcc)

​ CT(UART_TX):串口数据发送端,接上位机的RX引脚

​ DR(UART_RX):串口数据接收端,接上位机的TX引脚

​ GND:电源接地,接上位机的GND

通信协议

命令

命令是由上位机发送给GY-39传感器的,用来告诉GY-39本次需要采集那些数据

GY-39的命令由三个字节组成:

指令格式:帧头+指令+校验和(8bit)

帧头:固定0xa5,表示一帧命令的开始

指令:表示命令码,8bit,不同的bit有不同的含义

校验和:前面两个字节的和,只取低8位

eg:

unsigned char cmd[3]={0xa5,0x81,0x26};
write(gy39_fd,cmd,3);//发送获取光强的命令给GY-39,之后GY-3会周期性给上位机回复光强数据

回复数据:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BOcFnHMQ-1686970317682)(pic/image-20230612104707196.png)]

读取GY-39的回复数据,有两种方式

1.简单粗暴

发送光强命令0x81,读取9字节

发送气象命令0x82,读取15字节

发送光强+气象数据0x83,读取15+9个字节

这种读取方式,前提是上位机与GY-39的通信必须一直稳定,不能出现误传

2.一个字节一个字节的读取,根据通信协议中数据帧的格式来边解析边读取数据

int gy39_getlux(void)
{
	int gy39_fd = 0;
	int i = 0;
	ssize_t rsize = 0;
	unsigned char cmd[3]={0xa5,0x81,0x26};
	unsigned char buf[25]={0};
	
	gy39_fd = init_serial("/dev/ttySAC1",9600);
	write(gy39_fd,cmd,3);//发送获取光强的命令给GY-39,之后GY-3会周期性给上位机回复光强数据

	while(1)
	{
		rsize = read(gy39_fd,buf,9);
		if (rsize == 9)
		{
			printf("GY-39 lux buffer:\n");
			for ( i = 0; i < 9; ++i)
			{
				printf("0x%2x\t",buf[i]);
			}
			printf("\n");
		}
	}
	close(gy39_fd);

}

int main(int argc, char const *argv[])
{
	gy39_getlux();
	return 0;
}

int gy39_lux(void)
{
	int i = 0;
	int lux = 0;
	int gy39_fd = 0;
	ssize_t rsize = 0;
	unsigned char cmd[3]={0xa5,0x81,0x26};//命令数组帧头、指令、校验和(8bit)
	unsigned char buf[25]={0};//缓存数组保存从传感器返回的数据

	gy39_fd =init_serial("/dev/ttySAC1",9600);

	write(gy39_fd,cmd,3);//发送读取光强的命令,GY-39开始周期性的回复光强数据
	
		rsize=read(gy39_fd,buf,9);
		if (rsize = 9&&buf[2]=0x15)//数据帧9bit且byte2=0x15
		{
			lux=(buf[4]&0xff)<<24|(buf[5]&0xff)<<16|(buf[6]&0xff)<<8|buf[7];
            //Lux=(前高8位<<24) | (前低8位<<16) | (后高8位<<8) | 后低8位
			printf("GY-39 LUX BUFFER:%d\n",lux);
		}

	close(gy39_fd);
	return lux;
}

任务:请编写GY-39的数据采集(光照和气象),并且封装成GY-39.h和GY-39.c

二、驱动模块的加载与使用

1.驱动模块

Linux中有一些常用的设备驱动程序已经直接编译在内核中了,比如:串口驱动,LCD屏幕驱动等

但是我们使用的ARM开发板是一个教学平台,主打的是一个多功能,因此内核开发者无法预料到我们在使用过程中需要增加那些外设,因此一些特定外设的驱动需要用户自行加载

驱动模块是内核工程师/驱动工程师 根据外设和硬件平台所编写的一种可以动态加载和动态卸载的内核程序文件

==》内核模块 Kernel Module 后缀名.ko

在Linux中驱动模块等同看做windows中的驱动程序:==》只有加载了驱动模块,才能在Linux中访问相应的外设

Linux中关于驱动模块的操作命令:

insmod xxx.ko ==>将xxx.ko 内核模块加载到Linux中

rmmod xxx.ko ==>将Linux中的xxx模块卸载

lsmod ==>查看当前Linux中已经加载的模块

提示:如果我们的项目需要加载内核驱动模块,请先lsmod查看一下当前板子上已经加载的模块,将不需要的模块卸载掉

例如:默认的GEC6818开发板中,为了支持iot程序运行,加载了一些驱动模块,都可以卸载掉

iot程序是GEC6818开发板在开机时自动启动的一个演示程序,现象:在lcd屏幕上显示一个登录界面,可以使用killall iot将程序关闭,避免影响到我们自己项目的LCD屏幕操作

2.加载内核驱动模块

如果我们的项目需要使用外接的传感器(串口接口的除外除外),则需要加载相关的内核驱动模块,xxx.ko文件,使用例程

我们如果需要使用某个驱动,则只需要完成一下几个步骤即可:

1.将xxx.ko文件传输到ARM板上==》rx xxx.ko

2.加载内核驱动模块到Linux中==》insmod xxx.ko

3.==>通过使用例程读懂该驱动如何去访问

4.在自己的项目代码中添加对驱动的访问

比如在开发板上的pwm控制的蜂鸣器

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(void)
{
	int fd,ret;
	char beep_ctrl[1]; //0 --> 不响  1 --->响
	
	//[1]打开蜂鸣器的驱动设备文件
	fd = open("/dev/pwm",O_RDWR);
	if(fd < 0)
	{
		printf("open /dev/pwm failed\n");
		return -1;
	}
	//[2]---write 写入控制命令
	while(1)
	{
		beep_ctrl[0] = 0;  //不响
		ret = write(fd,beep_ctrl,sizeof(beep_ctrl));
		if( ret == -1)
		{
			perror("write");
		}
		sleep(1);
		
		beep_ctrl[0] = 1; 	//响
		ret = write(fd,beep_ctrl,sizeof(beep_ctrl));
		if( ret == -1)
		{
			perror("write");
		}
		sleep(1);
	}

	 //[3] 
	 close(fd);
	return 0;
}

案例:实现智能家居(光照气度,压强,海拔,湿度,温度达到临界值蜂鸣器自动警报等等)

** 关键代码:(需要源码私我)**

//智能家居文字显示
void word_view()
{
	//标题(智能家居)
	for(int t=0;t<5;t++)
	{
		show_word(biaoti[t],sizeof(biaoti[t]),32,0x00FFA500,300+60*t,70);
	}
	//光强
	for(int t=0;t<2;t++)
	{
		show_word(guangqiang[t],sizeof(guangqiang[t]),16,0,200+80*t,190);
	}
	//温度
	for(int t=0;t<2;t++)
	{
		show_word(wendu[t],sizeof(wendu[t]),16,0,200+80*t,240);
	}
	//气压
	for(int t=0;t<2;t++)
	{
		show_word(qiya[t],sizeof(qiya[t]),16,0,200+80*t,290);
	}
	//湿度
	for(int t=0;t<2;t++)
	{
		show_word(shidu[t],sizeof(shidu[t]),16,0,200+80*t,340);
	}
	//海拔
	for(int t=0;t<2;t++)
	{
		show_word(haiba[t],sizeof(haiba[t]),16,0,200+80*t,390);
	}
	//光强单位
	for(int t=0;t<3;t++)
	{
		show_word(guangqiangdanwei[t],sizeof(guangqiangdanwei[t]),8,0,355 + 7*t,190);
	}
	//温度单位
	for(int t=0;t<1;t++)
	{
		show_word(wendudanwei[t],sizeof(wendudanwei[t]),8,0,355 + 7*t,240);
	}
	//气压单位
	for(int t=0;t<2;t++)
	{
		show_word(qiyadanwei[t],sizeof(qiyadanwei[t]),8,0,360 + 7*t,290);
	}
	//湿度单位
	for(int t=0;t<1;t++)
	{
		show_word(shidudanwei[t],sizeof(shidudanwei[t]),8,0,355 + 7*t,340);
	}
	//海拔单位
	for(int t=0;t<1;t++)
	{
		show_word(haibadanwei[t],sizeof(haibadanwei[t]),8,0,355 + 7*t,390);
	}

	//显示冒号
	show_word(biaodian[0],sizeof(biaodian[0]),16,0,295,190);
	show_word(biaodian[0],sizeof(biaodian[0]),16,0,295,240);
	show_word(biaodian[0],sizeof(biaodian[0]),16,0,295,290);
	show_word(biaodian[0],sizeof(biaodian[0]),16,0,295,340);
	show_word(biaodian[0],sizeof(biaodian[0]),16,0,295,390);
}

版权声明:本文为博主作者:黎明的前夜原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/xqmids99/article/details/131257923

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
扎眼的阳光的头像扎眼的阳光普通用户
上一篇 2024年1月11日
下一篇 2024年1月11日

相关推荐