前言
这次使用C语言来实现树莓派读取DS18B20测量的温度,加深一下对文件I/O的理解
DS18B20是一个比较常用的温度传感器,采用单总线控制,以前用单片机编程控制时严格按照单总线的时序控制,今天来看看在linux系统下如何通过DS18B20来获取温度信息。
一、实验准备
1、硬件准备
硬件 | 数量 |
---|---|
Raspberry 4B | 1 |
DS18B20 温度传感器 | 1 |
杜邦线 | 若干 |
2、软件准备
在开始之前,要使能树莓派内核的单总线协议驱动模块(1-Wire)
具体操作如下
sudo raspi-config
单总线的接口默认是GPIO 4(BCM),使用默认接口的话可以忽略下面更换引脚的操作
若想更换自己选择的端口,则需要在 /boot/config.txt 文件的最后那行中加上 “,gpiopin=你想要的端口”。
vim /boot/config.txt
在最后一行添加以下内容,这里的 gpiopin = 4 是采用BCM GPIO的标准。
如果是模块化的DS18B20,则添加以下内容【因为模块中已包含上拉电阻】
dtoverlay=w1-gpio, gpiopin=4
如果是单独的一个DS18B20芯片【也就是普通直插三极管的样子】
dtoverlay=w1-gpio-pullup, gpiopin=4
最后重启系统
sudo reboot
确认系统有自动加载单总线协议的驱动模块:
lsmod | grep w1
以下为正常加载
二、实现过程
配置好单总线的驱动协议模块后,将DS18B20接上树莓派
Raspberry Pi | DS18B20 |
---|---|
3.3V | VCC |
GND | GND |
4 (BCM) | DQ |
接上树莓派后,可以在 /sys/bus/w1/devices/ 目录下找到一个28-xxxxxx的文件夹(xxxxxxx为该DS18B20芯片的序列号)
该文件夹存放 DS18B20 的数据信息
DS18B20测量的温度数据存放在该文件夹的 w1_slave 文件中。
从图中看出,这时DS18B20测量的温度值为 31.562 ℃
1、找该DS18B20芯片的设备文件夹
- ①、打开 “/sys/bus/w1/devices/” 这个文件夹,并让dirp指向该文件夹
- ②、从该文件夹中,逐个找 “ 文件名中包含有 28- 的文件 ”
- ③、找到则将文件夹名复制给字符数组 dir_name
- ③、找不到则报错,退出函数
char path[50] = "/sys/bus/w1/devices/";
//打开文件夹"/sys/bus/w1/devices/"
if ((dirp = opendir(path)) == NULL)
{
printf("ERROR: Open Directory '%s' Failure: %s\n", path, strerror(errno));
return -1;
}
printf("Open Directory '%s' Successfully\n", path);
//在文件夹中找DS18B20的文件夹28-xx
while ((direntp = readdir(dirp)) != NULL)
{
if (strstr(direntp->d_name, "28-") != NULL)
{
strncpy(dir_name, direntp->d_name, strlen(direntp->d_name));
printf("Find file: %s\n", dir_name);
found = 1;
break;
}
}
closedir(dirp);
//找不到该文件夹
if (found == -1)
{
printf("ERROR: Can not find DS18B20 in %s\n", path);
return -2;
}
2、拼接文件夹和文件名到path中
- ①、拼接文件夹和文件名到path中,得到一个完整的文件路径【绝对路径】
- “ /sys/bus/w1/devices/28-3c01e0761972/w1_slave ”
//将文件夹名和文件名分别拼接到path上
strncat(path, dir_name, strlen(dir_name));
strncat(path, "/w1_slave", sizeof(path)-strlen(path));
3、打开w1_slave文件
- ①、上一步骤得到 w1_slave 文件的绝对路径后,便可使用系统调用函数 open() 直接打开文件w1_slave
- ②、因为只需要读该文件中的内容,所以我们以只读的形式(O_RDONLY)打开即可
- ③、打开成功后,便会返回一个指向该文件的文件描述符fd
- 在运行最终代码的时候,会看到 fd=3 ,是因为
- 操作系统默认会打开 0、1、2 这三个文件描述符
- 0:标准输入【默认是键盘】
- 1:标准输出【默认是屏幕】
- 2:标准出错【默认是屏幕】
- 而新建的文件描述符 fd,是当前可用的文件描述符中最小的非负整数,所以这里的 fd 为 3
//打开w1_slave文件
if ((fd = open(path, O_RDONLY)) < 0)
{
printf("ERROR: Open file '%s' Failure: %s\n", path, strerror(errno));
return -3;
}
printf("Open file '%s' fd[%d] Successfully\n", path, fd);
4、读w1_slave文件
- ①、使用系统调用函数 read() 将 fd 所指向的文件的内容读到 buf 中,一次读 sizeof(buf) 字节大小
//从w1_slave中读取内容
if ((rv = read(fd, buf, sizeof(buf))) < 0)
{
printf("ERROR: Read data from file '%s' Failure: %s\n", path, strerror(errno));
rv = -4;
goto cleanup;
}
printf("Read %dB data from file '%s'\n", rv, path);
5、匹配数据并计算、赋值
- ①、从 buf 中匹配 “t=”这个字符串,这时 strstr() 返回的是 “t” 所存放的地址
- ②、要想最终获得的地址是数据的地址,则需要将该地址向右移 2 位,这时的地址就是数据的首地址
- ③、这时候的数据还是字符串形式,我们使用 atof() 函数将数据转为double型,并/1000,即可得到正确的温度值了
//从buf里匹配"t=",并将ptr移到数据的首地址上
if ((ptr= strstr(buf, "t=") + 2) == NULL)
{
printf("ERROR: Find data Failure: %s", strerror(errno));
rv = -5;
goto cleanup;
}
//将数据转为double型,除1000得到正常的十进制温度
*temp = atof(ptr)/1000;
三、具体代码
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
int Get_DS18B20_Temp(double *temp);
/*
* The main function
*/
int main(int argc, char **argv)
{
double temp;
if (Get_DS18B20_Temp(&temp) < 0)
{
printf("ERROR: DS18B20 Get Temperature Failure\n");
return -1;
}
printf("Temperature is: %.3f C\n", temp);
return 0;
} /*-----End of main function-----*/
/*
* 获取DS18B20测量的温度信息
*/
int Get_DS18B20_Temp(double *temp)
{
int fd = -1;
int rv = -1;
char path[50] = "/sys/bus/w1/devices/";
char dir_name[20];
int found = -1;
DIR *dirp;
struct dirent *direntp;
char buf[128];
char *ptr;
//清空dir_name和buf内存空间的值,避免随机值产生乱码
memset(dir_name, 0, sizeof(dir_name));
memset(buf, 0, sizeof(buf));
//打开文件夹"/sys/bus/w1/devices/"
if ((dirp = opendir(path)) == NULL)
{
printf("ERROR: Open Directory '%s' Failure: %s\n", path, strerror(errno));
return -1;
}
printf("Open Directory '%s' Successfully\n", path);
//在文件夹中找DS18B20的文件夹28-xx
while ((direntp = readdir(dirp)) != NULL)
{
if (strstr(direntp->d_name, "28-") == NULL)
continue;
strncpy(dir_name, direntp->d_name, strlen(direntp->d_name));
printf("Find file: %s\n", dir_name);
found = 1;
break;
}
closedir(dirp);
//找不到该文件夹
if (found == -1)
{
printf("ERROR: Can not find DS18B20 in %s\n", path);
return -2;
}
//将文件夹名和文件名分别拼接到path上
strncat(path, dir_name, strlen(dir_name));
strncat(path, "/w1_slave", sizeof(path)-strlen(path));
//打开w1_slave文件
if ((fd = open(path, O_RDONLY)) < 0)
{
printf("ERROR: Open file '%s' Failure: %s\n", path, strerror(errno));
return -3;
}
printf("Open file '%s' fd[%d] Successfully\n", path, fd);
//从w1_slave中读取内容
if ((rv = read(fd, buf, sizeof(buf))) < 0)
{
printf("ERROR: Read data from file '%s' Failure: %s\n", path, strerror(errno));
rv = -4;
goto cleanup;
}
printf("Read %dB data from file '%s'\n", rv, path);
//从buf里匹配"t=",并将ptr移到数据的首地址上
if ((ptr = strstr(buf, "t=") + 2) == NULL)
{
printf("ERROR: Find data Failure: %s", strerror(errno));
rv = -5;
goto cleanup;
}
//将数据转为double型,除1000得到正常的十进制温度
*temp = atof(ptr)/1000;
cleanup:
if (fd > 0)
close(fd);
return rv;
}
看到这里可能很多小伙伴会有疑惑,为什么返回值又是-1又是-2的,因为在 Linux 中,一般函数正常运行的情况下,返回的值为 0,函数出错的话,一般是返回 -1 这种负整数,遵循这个规律写代码,也方便我们的调试。
四、测试结果
总结
以上是对树莓派读取DS18B20温度信息的一些理解,如有写的不好的地方,还请各位大佬不吝赐教