昨天看了一天的 PMS7003 传输协议。然后网上搜索了一下,仅有的几篇文章讲代码怎么写的。
参看:DIY 空气质量检测表
参看:Dust Sensor - PMS 5003/6003/7003
参看:MartyMacGyver/PMS7003-on-Particle
参看:PMS7003 PM2.5测试仪,带SHT20温湿度 STM32源码
参看:(SKU:SEN0177)PM2.5激光粉尘传感器
但是你有没有发现,这几篇文章要么是在 QT 上实现的、要么用的是 Arduino 开发板、要么是单片机。
对于我现在要在 DM368 板子上实现,好像没什么卵用... 现在真的有点懵逼。
还算好的是,前辈有留下一点,之前的PM2.5采集器的源码。仅供参看... 没办法,只能一点一点来做。
默认波特率:9600bps 校验位:无 停止位:1位
协议总长度:32字节
字节 | 数据 |
---|---|
起始符1 | 0x42 |
起始符2 | 0x4D |
帧长度高八位 | 帧长度=2x13+2(数据+校验位) |
帧长度低八位 | |
数据1高八位 | 数据1表示PM1.0浓度(CF=1,标准颗粒物)单位μg/m3 |
数据1低八位 | |
数据2高八位 | 数据2表示PM2.5浓度(CF=1,标准颗粒物)单位μg/m3 |
数据2低八位 | |
数据3高八位 | 数据3表示PM10浓度(CF=1,标准颗粒物)单位μg/m3 |
数据3低八位 | |
数据4高八位 | 数据4表示PM1.0浓度(大气环境下)单位μg/m3 |
数据4低八位 | |
数据5高八位 | 数据5表示PM2.5浓度(大气环境下)单位μg/m3 |
数据5低八位 | |
数据6高八位 | 数据6表示PM10浓度 (大气环境下)单位μg/m3 |
数据6低八位 | |
数据7高八位 | 数据7表示0.1升空气中直径在0.3um以上颗粒物个数 |
数据7低八位 | |
数据8高八位 | 数据8表示0.1升空气中直径在0.5um以上颗粒物个数 |
数据8低八位 | |
数据9高八位 | 数据9表示0.1升空气中直径在1.0um以上颗粒物个数 |
数据9低八位 | |
数据10高八位 | 数据10表示0.1升空气中直径在2.5um以上颗粒物个数 |
数据10低八位 | |
数据11高八位 | 数据11表示0.1升空气中直径在5.0um以上颗粒物个数 |
数据11低八位 | |
数据12高八位 | 数据12表示0.1升空气中直径在10um以上颗粒物个数 |
数据12低八位 | |
数据13高八位 | 版本号 |
数据13低八位 | 错误代码 |
数据和校验高八位 | 校验码=起始符1+起始符2+……..+数据13低八位 |
数据和校验低八位 |
输出结果
主要输出为单位体积内各浓度颗粒物质量以及个数,其中颗粒物个数的单位体积为 0.1 升,质量浓度单位为:微克/立方米。
输出分为主动输出和被动输出两种状态。传感器上电后默认状态为主动输出,即传感器主动向主机发送串行数据,时间间隔为 200~800ms,空气中颗粒物浓度越高,时间间隔越短。主动输出又分为两种模式:平稳模式和快速模式。在空气中颗粒物浓度变化较小时,传感器输出为平稳模式,即每三次输出同样的一组数值,实际数据更新周期约为 2s。当空气中颗粒物浓度变化较大时,传感器输出自动切换为快速模式,每次输出都是新的数值,实际数据更新周期为 200~800ms。
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Demo: interfacing a Plantower PMS7003 air quality sensor to a Particle IoT microcontroller
/*
Copyright (c) 2016 Martin F. Falatic
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
int incomingByte = 0; // for incoming serial data
const int MAX_FRAME_LEN = 64;
char frameBuf[MAX_FRAME_LEN];
int detectOff = 0;
int frameLen = MAX_FRAME_LEN;
bool inFrame = false;
char printbuf[256];
const bool DEBUG = false;
uint16_t calcChecksum = 0;
struct PMS7003_framestruct {
uint8_t frameHeader[2];
uint16_t frameLen = MAX_FRAME_LEN;
uint16_t concPM1_0_CF1;
uint16_t concPM2_5_CF1;
uint16_t concPM10_0_CF1;
uint16_t concPM1_0_amb;
uint16_t concPM2_5_amb;
uint16_t concPM10_0_amb;
uint16_t rawGt0_3um;
uint16_t rawGt0_5um;
uint16_t rawGt1_0um;
uint16_t rawGt2_5um;
uint16_t rawGt5_0um;
uint16_t rawGt10_0um;
uint8_t version;
uint8_t errorCode;
uint16_t checksum;
} thisFrame;
void setup() {
Serial.begin(57600);
delay(1000);
Serial.println("-- Initializing...");
}
bool pms7003_read() {
// Particle.publish("PMS7003", printbuf, 60, PRIVATE);
// send data only when you receive data:
Serial.println("-- Reading PMS7003");
Serial1.begin(9600);
bool packetReceived = false;
while (!packetReceived) {
if (Serial1.available() > 32) {
int drain = Serial1.available();
if (DEBUG) {
Serial.print("-- Draining buffer: ");
Serial.println(Serial1.available(), DEC);
}
for (int i = drain; i > 0; i--) {
Serial1.read();
}
}
if (Serial1.available() > 0) {
if (DEBUG) {
Serial.print("-- Available: ");
Serial.println(Serial1.available(), DEC);
}
incomingByte = Serial1.read();
if (DEBUG) {
Serial.print("-- READ: ");
Serial.println(incomingByte, HEX);
}
if (!inFrame) {
if (incomingByte == 0x42 && detectOff == 0) {
frameBuf[detectOff] = incomingByte;
thisFrame.frameHeader[0] = incomingByte;
calcChecksum = incomingByte; // Checksum init!
detectOff++;
}
else if (incomingByte == 0x4D && detectOff == 1) {
frameBuf[detectOff] = incomingByte;
thisFrame.frameHeader[1] = incomingByte;
calcChecksum += incomingByte;
inFrame = true;
detectOff++;
}
else {
Serial.print("-- Frame syncing... ");
Serial.print(incomingByte, HEX);
if (DEBUG) {
}
Serial.println();
}
}
else {
frameBuf[detectOff] = incomingByte;
calcChecksum += incomingByte;
detectOff++;
uint16_t val = frameBuf[detectOff-1]+(frameBuf[detectOff-2]<<8);
switch (detectOff) {
case 4:
thisFrame.frameLen = val;
frameLen = val + detectOff;
break;
case 6:
thisFrame.concPM1_0_CF1 = val;
break;
case 8:
thisFrame.concPM2_5_CF1 = val;
break;
case 10:
thisFrame.concPM10_0_CF1 = val;
break;
case 12:
thisFrame.concPM1_0_amb = val;
break;
case 14:
thisFrame.concPM2_5_amb = val;
break;
case 16:
thisFrame.concPM10_0_amb = val;
break;
case 18:
thisFrame.rawGt0_3um = val;
break;
case 20:
thisFrame.rawGt0_5um = val;
break;
case 22:
thisFrame.rawGt1_0um = val;
break;
case 24:
thisFrame.rawGt2_5um = val;
break;
case 26:
thisFrame.rawGt5_0um = val;
break;
case 28:
thisFrame.rawGt10_0um = val;
break;
case 29:
val = frameBuf[detectOff-1];
thisFrame.version = val;
break;
case 30:
val = frameBuf[detectOff-1];
thisFrame.errorCode = val;
break;
case 32:
thisFrame.checksum = val;
calcChecksum -= ((val>>8)+(val&0xFF));
break;
default:
break;
}
if (detectOff >= frameLen) {
sprintf(printbuf, "PMS7003 ");
sprintf(printbuf, "%s[%02x %02x] (%04x) ", printbuf,
thisFrame.frameHeader[0], thisFrame.frameHeader[1], thisFrame.frameLen);
sprintf(printbuf, "%sCF1=[%04x %04x %04x] ", printbuf,
thisFrame.concPM1_0_CF1, thisFrame.concPM2_5_CF1, thisFrame.concPM10_0_CF1);
sprintf(printbuf, "%samb=[%04x %04x %04x] ", printbuf,
thisFrame.concPM1_0_amb, thisFrame.concPM2_5_amb, thisFrame.concPM10_0_amb);
sprintf(printbuf, "%sraw=[%04x %04x %04x %04x %04x %04x] ", printbuf,
thisFrame.rawGt0_3um, thisFrame.rawGt0_5um, thisFrame.rawGt1_0um,
thisFrame.rawGt2_5um, thisFrame.rawGt5_0um, thisFrame.rawGt10_0um);
sprintf(printbuf, "%sver=%02x err=%02x ", printbuf,
thisFrame.version, thisFrame.errorCode);
sprintf(printbuf, "%scsum=%04x %s xsum=%04x", printbuf,
thisFrame.checksum, (calcChecksum == thisFrame.checksum ? "==" : "!="), calcChecksum);
Serial.println(printbuf);
Particle.publish("Data1", printbuf, 60, PRIVATE);
packetReceived = true;
detectOff = 0;
inFrame = false;
}
}
}
}
Serial1.end();
return (calcChecksum == thisFrame.checksum);
}
void loop () {
if (!pms7003_read()) {
delay(4000);
}
}
#include
#include
static int helloword_init (void) //入口函数 成功返回0 static限定作用域
{
printf (“hello,world!\n”);
return 0;
}
static void helloworld_exit (void) //出口函数
{
printf (“goodbye,world!\n”);
}
module_init (helloworld_init);
module_exit (helloworld_exit);
MODULE_LICENSE (“GPL”); //GPL 开源
Makefile 编写
obj-m += helloworld.o #将helloworld.c
#编译生成最终的二进制可执行文件helloworld.ko
all:
make -C /opt/kernel SUBDIRS=$(PWD) modules
#到内核/opt/kernel源码中进行make编译,然后告诉内核,在你的源码以外还有一个目录/opt/drivers/day01/1.0,在这个目录下有一个.c文件需要你进行编译,要把它编译生成.ko文件
clean:
make -C /opt/kernel SUBDIRS=$(PWD) clean
insmod 加载
rmmod 卸载
lsmod 查看
内核提供的GPIO操作库函数 gpio_request:申请资源
gpio_free:释放资源
gpio_direction_output:配置工作模式为输出,输出某个状态
gpio_direction_input:配置工作模式为输入
gpio_get_value:获取状态
gpio_set_value:设置状态
设备号的操作
申请
alloc_chrdev_region(设备号,0, 次设备号的个数,设备名称);
释放
unregister_chrdev_region(设备号,次设备号的个数);
实现一个字符设备驱动的编程步骤
定义初始化字符设备对象
cdev_init
注册字符设备对象到内核
cdev_add
卸载字符设备对象
cdev_del
linux内核描述字符设备使用的数据结构
struct cdev {
dev_t dev; //保存申请的设备号
unsigned int count;//设备的个数
const struct file_operations *ops;
//给字符设备驱动赋予操作硬件的方法,并且将这些方法最终提供给用户
};
linux内核字符设备硬件操作接口struct file_operations {
.owner = THIS_MODULE,
.open = led_open, //打开设备
.release = led_close, //关闭设备
.read = led_read, //读设备
.write = led_write, //写设备
.unlocked_ioctl = led_ioctl, //向设备发送命令并且能够进行读写操作
......
};
设备文件创建函数struct class *cls; //创建设备类指针
//定义初始化设备类(长树枝,树枝名叫tarena)
//结果是在/sys/class目录下生成一个tarena目录,存放创建设备文件所需的原材料
cls = class_create(THIS_MODULE, "tarena");
//创建设备文件(长苹果)
//结果是在/dev/生成设备文件
device_create(cls, NULL, 申请的设备号,NULL,s设备文件名);
//删除设备文件(采摘苹果)
device_destroy(cls, 申请的设备号);
//删除设备类(砍树枝)
class_destroy(cls);
混杂设备驱动的编程步骤
1.定义初始化混杂设备对象
struct file_operations led_fops = {
...
};
struct miscdevice led_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "myled",
.fops = &led_fops
};
2.向内核注册混杂设备对象,一旦注册完毕,内核就有一个真实的混杂设备驱动
misc_register(&led_misc);
3.从内核卸载混杂设备对象
misc_deregister(&led_misc);
然后之前写过一篇关于 485 串口编程的文章,可供参考。
参看:UNIX再学习 -- RS485 串口编程
这个很有必要哦,里面的串口编程,很实用。然后只需要将其改为只接收即可。
然后具体的更改代码,这里我就不再重复。
编译:
arm-none-linux-gnueabi-gcc com.c -o com -pthread
tftp 到文件系统中:
tftp -g -r com 192.168.2.xx
设置权限:
chmod 777 com
手动加载设备:
mknod /dev/pio c 203 0
执行
./com
最后是传输协议开发程序,只看 read 读取部分即可。其他的没啥可讲的。
#include //文件控制定义
#include //标准输入输出定义
#include //标准函数库定义
#include //Unix标准函数定义
#include //错误好定义
#include //POSIX终端控制定义
#include //ioctl函数定义
#include //字符操作
#include
#include
#include
#include
int fd_gpio;
struct termios newtio, oldtio;
typedef struct {
int pin_idx;
int pin_dir;
int pin_sta;
} davinci_gio_arg;
typedef enum {
AT91PIO_DIR_OUT = 0,
AT91PIO_DIR_INP
} davinci_gio_dir;
//驱动判断输入输出模式
davinci_gio_arg arg;
#define DEV_PIO_LED "/dev/pio"
// 需要手动添加设备号 mknod /dev/pio c 203 0
#define PIO_NUM 47
// 47pin 为控制输入输出方向引脚
#define DEV_UART "/dev/ttyS1"
// /dev/ttyS1 为串口设备
#define IOCTL_PIO_SETDIR 1 //set gpio direct
#define IOCTL_PIO_GETDIR 2 //get gpio direct
#define IOCTL_PIO_SETSTA 3 //set gpio status
#define IOCTL_PIO_GETSTA 4 //get gpio status
//保存信息
int log_init( const char *strFileName )
{
int fdLog = -1;
if( -1 == (fdLog = open( strFileName, O_CREAT|O_TRUNC ) ) )
{
}
close( fdLog );
}
int log_out( const char *strFileName, const char * szLog )
{
int fdLog = -1;
if( -1 == ( fdLog = open( strFileName, O_CREAT|O_WRONLY|O_APPEND ) ) )
{
printf( "LOG (%s) open error!\n", strFileName );
return -1;
}
write( fdLog, szLog, strlen( szLog ) );
close( fdLog );
return 0;
}
//配置串口
/* 参数说明:fd 设备文件描述符,nspeed 波特率,nbits 数据位数(7位或8位),
parity 奇偶校验位('n'或'N'为无校验位,'o'或'O'为偶校验,'e'或'E'奇校验),
nstop 停止位(1位或2位)
成功返回1,失败返回-1。
*/
int set_com_opt( int fd, int nspeed, int nbits, char parity, int nstop )
{
char szTmp[128];
//打印配置信息
sprintf( szTmp, "set_com_opt - speed:%d,bits:%d,parity:%c,stop:%d\n",
nspeed, nbits, parity, nstop );
log_out( "./485.log", szTmp );
//保存并测试现在有串口参数设置,在这里如果串口号等出错,会有相关的出错信息
if( tcgetattr( fd, &oldtio ) != 0 )
{
sprintf( szTmp, "SetupSerial 1" );
log_out( "./485.log", szTmp );
perror( "SetupSerial 1" );
return -1;
}
//修改输出模式,原始数据输出
bzero( &newtio, sizeof( newtio ));
newtio.c_cflag &=~(OPOST);
//屏蔽其他标志位
newtio.c_cflag |= (CLOCAL | CREAD );
newtio.c_cflag &= ~CSIZE;
//设置数据位
switch( nbits )
{
case 7:
newtio.c_cflag |= CS7;
break;
case 8:
newtio.c_cflag |= CS8;
break;
default:
perror("Unsupported date bit!\n");
return -1;
}
//设置校验位
switch( parity )
{
case 'n':
case 'N': //无奇偶校验位
newtio.c_cflag &= ~PARENB;
newtio.c_iflag &= ~INPCK;
break;
case 'o':
case 'O': //设置为奇校验
newtio.c_cflag |= ( PARODD | PARENB );
newtio.c_iflag |= ( INPCK | ISTRIP );
break;
case 'e':
case 'E': //设置为偶校验
newtio.c_iflag |= ( INPCK |ISTRIP );
newtio.c_cflag |= PARENB;
newtio.c_cflag &= ~PARODD;
break;
default:
perror("unsupported parity\n");
return -1;
}
//设置停止位
switch( nstop )
{
case 1:
newtio.c_cflag &= ~CSTOPB;
break;
case 2:
newtio.c_cflag |= CSTOPB;
break;
default :
perror("Unsupported stop bit\n");
return -1;
}
//设置波特率
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;
case 460800:
cfsetispeed( &newtio, B460800 );
cfsetospeed( &newtio, B460800 );
break;
default:
cfsetispeed( &newtio, B9600 );
cfsetospeed( &newtio, B9600 );
break;
}
//设置等待时间和最小接收字符
newtio.c_cc[VTIME] = 0;
newtio.c_cc[VMIN] = 0;
//VTIME=0,VMIN=0,不管能否读取到数据,read都会立即返回。
//输入模式
newtio.c_lflag &= ~(ICANON|ECHO|ECHOE|ISIG);
//设置数据流控制
newtio.c_iflag &= ~(IXON|IXOFF|IXANY); //使用软件流控制
//如果发生数据溢出,接收数据,但是不再读取 刷新收到的数据但是不读
tcflush( fd, TCIFLUSH );
//激活配置 (将修改后的termios数据设置到串口中)
if( tcsetattr( fd, TCSANOW, &newtio ) != 0 )
{
sprintf( szTmp, "serial set error!\n" );
log_out( "./485.log", szTmp );
perror( "serial set error!" );
return -1;
}
log_out( "./485.log", "serial set ok!\n" );
return 1;
}
//打开串口并返回串口设备文件描述
int open_com_dev( char *dev_name )
{
int fd;
char szTmp[128];
log_init( "./485.log" );
if(( fd = open( dev_name, O_RDWR|O_NOCTTY|O_NDELAY)) == -1 )
{
perror("open\n");
//printf("Can't open Serial %s Port!\n", dev_name );
sprintf( szTmp, "Can't open Serial %s Port!\n", dev_name );
log_out( "./485.log", szTmp );
return -1;
}
sprintf( szTmp, "open %s ok!\n", dev_name );
log_out( "./485.log", szTmp );
if(fcntl(fd,F_SETFL,0)<0)
{
printf("fcntl failed!\n");
}
//printf("Open %s ok\n",dev_name );
return fd;
}
//单片机数据收发
void* task(void* p)
{
char buf[64];
char frameBuf[64];
int detectOff = 0;
int res = 0, nread = 0;
int m_pm1_factory;
int m_pm25_factory;
int m_pm10_factory;
int m_pm1_outdoor;
int m_pm25_outdoor;
int m_pm10_outdoor;
int m_count03;
int m_count05;
int m_count1;
int m_count25;
int m_count5;
int m_count10;
unsigned short m_length;
unsigned short m_version;
unsigned short m_errorno;
while (1) {
arg.pin_sta = 0; //设为低电平 接收态
ioctl(fd_gpio, IOCTL_PIO_SETSTA, &arg);
int fd_r=open_com_dev( DEV_UART );
if( fd_r < 0 )
{
printf( "open UART device error! %s\n", DEV_UART );
}
else
set_com_opt(fd_r, 9600,8,'n',1);
//执行select
fd_set rd;
FD_ZERO(&rd);
FD_SET(fd_r, &rd);
if ((res = select (fd_r+1,&rd, NULL, NULL, NULL) )< 0)
{
perror ("read err");
exit (-1);
}
memset (buf, 0, sizeof (buf));
if (FD_ISSET (fd_r, &rd))
{
//接收数据 8 8 2
int res1 = 0;
int val = 0;
int calcChecksum = 0;
int checksum = 0;
int i = 0;
while ((nread = read(fd_r, buf, 1)) > 0)
{
//printf ("%02X ",*buf);
//frameBuf[detectOff] = buf[0];
memcpy (frameBuf+detectOff, buf, 1);
//calcChecksum += *buf;
detectOff++;
if (frameBuf[0] == 0x42 && frameBuf[1] == 0x4d)
{
// m_length = frameBuf[3]+(frameBuf[2]<<8);
m_pm1_factory = frameBuf[5]+(frameBuf[4]<<8);
m_pm25_factory = frameBuf[7]+(frameBuf[6]<<8);
m_pm10_factory = frameBuf[9]+(frameBuf[8]<<8);
m_pm1_outdoor = frameBuf[11]+(frameBuf[10]<<8);
m_pm25_outdoor = frameBuf[13]+(frameBuf[12]<<8);
m_pm10_outdoor = frameBuf[15]+(frameBuf[14]<<8);
// m_count03 = frameBuf[17]+(frameBuf[16]<<8);
// m_count05 = frameBuf[19]+(frameBuf[18]<<8);
// m_count1 = frameBuf[21]+(frameBuf[20]<<8);
// m_count25 = frameBuf[23]+(frameBuf[22]<<8);
// m_count5 = frameBuf[25]+(frameBuf[24]<<8);
// m_count10 = frameBuf[27]+(frameBuf[26]<<8);
// m_version = frameBuf[28];
// m_errorno = frameBuf[29];
// checksum = frameBuf[31]+(frameBuf[30]<<8);
// calcChecksum -= ((checksum>>8)+(checksum&0xFF));
}
//退出循环, 这里有点疑问
if (detectOff == 32)
{
printf ("\n");
printf ("pm1_factory = %d ug/m3\npm25_factory = %d ug/m3\npm10_factory = %d ug/m3\n", m_pm1_factory, m_pm25_factory, m_pm10_factory);
printf ("pm1_outdoor = %d ug/m3\npm25_outdoor = %d ug/m3\npm10_outdoor = %d ug/m3\n",m_pm1_outdoor,m_pm25_outdoor,m_pm10_outdoor);
// printf ("m_count03 = %02X\nm_count05 = %02X\nm_count1 = %02X\nm_count25 = %02X\nm_count5 = %02X\nm_count10 = %02X\n",
// m_count03, m_count05, m_count1, m_count25, m_count5, m_count10);
// printf ("m_length = %d\n", m_length);
// printf ("m_version = %s\n", m_version);
// printf ("m_errorno = %s\n", m_errorno);
// printf("checksum = %02X %s calcChecksum = %02X",checksum, (calcChecksum == checksum ? "==" : "!="), calcChecksum);
// for (i = 0;i<32;i++)
// {
// printf ("%02X ", frameBuf[i]);
// }
// printf ("\n");
memset (frameBuf, 0, sizeof (frameBuf));
detectOff = 0;
break;
}
}
}
close (fd_r);
usleep (200000);
}
}
int main (void)
{
int error = 0, error1 = 0;
arg.pin_idx = PIO_NUM;
arg.pin_dir = AT91PIO_DIR_OUT;
//打开/dev/pio设备
fd_gpio = open(DEV_PIO_LED, O_RDWR);
if(fd_gpio < 0)
{
perror("fd_gpio open err");
exit (-1);
}
pthread_t tid;
//创建线程
if ((error = pthread_create (&tid, NULL, task, NULL)) < 0)
{
perror ("pthread_cread error");
return -1;
}
//等待线程结束
pthread_join (tid, NULL);
//关闭设备
close (fd_gpio);
return 0;
}
测试结果: