DS18B20是比较常用到的温度传感器,采用单总线控制。是美国DALLAS半导体公司继DS1820之后最新推出的一种改进型智能温度传感器。
这里对于它就不进行过多的讲解,主要讲解如何在树莓派上用它来获取温度。详细资料参考:DS18B20介绍。
DS18B20的接线连接参照下图,VCC接3.3v的引脚,GND接地,DQ线连在GPIO04上。
接好线之后,我们要配置内核启动后自动加载一线协议驱动,在命令行键入sudo raspi-config,依次选择如下选项,就可以使能内核以一线协议(1-wire)驱动模块。
然后命令行键入vim /boot/config.txt,并在最后一行添加 dtoverlay=w1-gpio-pullup,gpiopin=4。(这里的4就是pin7,应该是采用BCM GPIO的标准)。
随后sudo reboot重启系统,键入 lsmod | grep w1 检查系统是否已经自动加载一线协议的驱动模块。
我们知道Linux下“一切皆文件”,DS18B20的文件就在“/sys/bus/w1/devices/”下
这个叫“28-041731f7c0ff”的文件夹是我用的这个DS18B20的产品序列号,然后在这个文件夹里面的“w1_slave”里就存放着实时的温度,我们可以看一看
前面那些数据我们不管他,可以看到“t=11250”,这个就是我这个设备上的实时温度(11.25℃)。了解到这些之后,我们来捋一捋代码里面要用什么样的方式来读取这个温度,并显示出来。
如果我们人为的想要在树莓派上读取温度,我们需要转到相应路径下去,查看相应的文件即可,但是我们编写程序的时候,为了得到更广泛的适用性,并不能直接简单粗暴地用read()去读取相应路径下的文件,因为每个DS18B20的产品序列号不一样,那么温度保存文件的路径也就会不一样。
因为序列号不同的缘故,我们的代码需要逐步实现如下功能:
step1. 用opendir()和readdir()的组合,打开每个设备都一样的默认路径(/sys/bus/w1/devices/),并读取路径下的内容。
step2. 找出默认路径下“28-”开头的的文件夹,并将其名称保存到一个缓存区中,然后用这个缓存区的内容来更新目标路径。
step3. 用open()和read()读取目标路径的内容,找出其中“t=”的部分,并记录下来。随后便可以简单粗暴的一个printf()将实时温度打印到标准输出上。
依照上面的思路,我们可以先做出前两部分寻找目标路径的代码,这里我用了strstr()来找以“28-”开头的文件夹。还用了strncat()来更新目标文件路径。
strstr()的作用是判断前者字符串中是否包含有后者字符串的内容,若包含,返回其第一次出现的首地址,若无则返回NULL
strncat()的作用是在一个字符串的后面加上另一个字符串,并且能保证前者的剩余空间足够放下后者。
if((dir = opendir(path)) == NULL ) //打开文件夹并判断成败
{
printf("Open directory failed: %s", strerror(errno)); //失败打印提示
return -1;
}
while((direntp = readdir(dir)) != NULL) //读取文件夹内容,用direntp保存其地址
{
/***************************步骤1和步骤2的假想分割线********************************/
if(strstr(direntp->d_name, "28")); //用if循环找“28-”开头的文件夹
{
strcpy(dstfile, direntp->d_name); //将目标路径文件夹保存到缓冲区
flag = 1; //成功找到“28-”开头的文件夹的标志,
}
}
closedir(dir); //关闭文件夹
if(flag != 1) //利用标志判断有没有成功找到“28-”开头的文件夹
{
printf("Can not found the temperature file.\n");
return -2;
}
strncat(path, dstfile, sizeof(path)); //给默认路径加上找到的“28-”开头的文件夹
strncat(path, "/w1_slave", sizeof(path)); //继续加上需要打开的文件“w1_slave”,这样就完成了目标路径的更新
简单介绍一下opendir()、readdir() 和 closedir() 这几个函数的使用,他们的原型和介绍如下:
#include //opendir()的头文件
#include //opendir()的头文件
DIR *opendir(const char *name); //打开参数name指定的目录,并返回DIR*类型的目录流,类似于文件描述符
#include //readdir()的头文件
#include //readdir()的头文件
struct dirent *readdir(DIR *dir); //读取某个文件夹里的内容,保存到结构体dirent里,返回该结构体的地址
struct dirent //readdir返回的结构体内容
{
ino_t d_ino; // 此目录进入点的inode
ff_t d_off; // 目录文件开头至此目录进入点的位移
signed short int d_reclen; // 文件的的长度, 不包含NULL 字符
unsigned char d_type; // 文件类型
har d_name[256]; // 文件名(这里我们只关心这个!)
};
#include //closedir()的头文件
#include //closedir()的头文件
closedir(DIR *dir); //类似cloes,简单粗暴,用完就关
有了目标路径,那接下来就好办了,用一套open()、read() 和 close() 的组合就能很简单的读出来,同样用strstr()来找到文件中“t=”开头的字符串首地址,但是我们在打印温度的时候需要将这个地址向后偏移两个字节,去掉“t=”
if((fd = open(path, O_RDONLY)) < 0 ) //根据目标路径以只读的形式打开文件,同样加上错误判断
{
printf("Open temperature file failed: %s\n", strerror(errno)); //出错提醒
return -3;
}
if((rv = read(fd, buf, sizeof(buf))) < 0) //读取文件内容,并保存在buf里
{
printf("Read temperature data failed: %s\n", strerror(errno));
return -4;
}
close(fd);
if((ptr = strstr(buf, "t=")) != NULL) //strstr()找到温度存放的地址,并保存起来
{
tem = atof(ptr+2)/1000; //这里加2是将地址偏移两个字节,跳过文件中的“t=”,只保留有用的温度数据
printf("The temperature in LingYun Studio is %f now.\n", tem); //简单粗暴,快哉!
}
else
{
printf("Can not read the temperature data: %s.\n", strerror(errno)); //buf里面没有读取到温度数据,就会报错
return -5;
}
给上面的代码加上头文件,并声明相关参数,就可以得到完整的代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PATH "/sys/bus/w1/devices/" //宏定义默认路径
#define BUF_SIZE 128
#define PATH_SIZE 100
int main(int argc, char **argv[])
{
char path[PATH_SIZE] = PATH; //局部变量保存默认路径
DIR *dir; //opendir的返回类型,用来指定打开的文件夹
struct dirent *direntp; //readdir返回的结构体指针,用来指向打开文件夹的内容
char dstfile[48]; //目标路径缓存区
int flag = -1; //初始化文件夹读取成败标志
int fd = -1; //初始化文件描述符
int rv = -1; //初始化文件读取返回值
char buf[BUF_SIZE]; //实时温度缓冲区
double tem = 0.00; //初始化实时温度数据
char *ptr = NULL; //初始化实时温度缓冲区指针
if((dir = opendir(path)) == NULL ) //打开文件夹并判断成败
{
printf("Open directory failed: %s", strerror(errno)); //失败打印提示
return -1;
}
while((direntp = readdir(dir)) != NULL) //读取文件夹内容,用direntp保存其地址
{
if(strstr(direntp->d_name, "28")); //找到“28-”开头的文件夹
{
strcpy(dstfile, direntp->d_name); //将目标路径文件夹保存到缓冲区
flag = 1; //成功找到“28-”开头的文件夹的标志,
}
}
closedir(dir); //关闭文件夹
if(flag != 1) //利用标志判断有没有成功找到“28-”开头的文件夹
{
printf("Can not found the temperature file.\n");
return -2;
}
strncat(path, dstfile, sizeof(path)); //给默认路径加上找到的“28-”开头的文件夹
strncat(path, "/w1_slave", sizeof(path)); //继续加上需要打开的文件“w1_slave”,这样就完成了目标路径的更新
if((fd = open(path, O_RDONLY)) < 0 ) //根据目标路径以只读的形式打开文件,同样加上错误判断
{
printf("Open temperature file failed: %s\n", strerror(errno)); //出错提醒
return -3;
}
if((rv = read(fd, buf, sizeof(buf))) < 0) //读取文件内容,并保存在buf里
{
printf("Read temperature data failed: %s\n", strerror(errno));
return -4;
}
close(fd);
if((ptr = strstr(buf, "t=")) != NULL) //strstr()找到温度存放的地址,并保存起来
{
tem = atof(ptr+2)/1000; //这里加2是讲地址便宜两个字节,跳过文件中的“t=”,只保留有用的温度数据
printf("The temperature in LingYun Studio is %f now.\n", tem); //简单粗暴,快哉!
}
else
{
printf("Can not read the temperature data: %s.\n", strerror(errno)); //buf里面没有读取到温度数据,就会报错
return -5;
}
return 0; // ending
}