所学来自百问网
目录
1.DS18B20 简介
DS18B20 温度传感器具有线路简单、体积小的特点,用来测量温度非常简单, 在一根通信线上可以挂载多个 DS18B20 温度传感器。用户可以通过编程实现 9~12 位的温度读数,每个DS18B20有唯一的64位序列号,保存在rom中,因 此一条总线上可以挂载多个DS18B20。
2.硬件设计
DS18B20 也使用的是“1-Wire单总线”,只通过一条数据线传输数据,既要控制器发送数据给芯片,又要通过芯片发送数据给控制器,是双向传输数据。
原理图如下:
3.软件设计
3.1 存储器介绍
DS18B20内部有个64位只读存储器(ROM)和 64位配置存储器(SCRATCHP)。 64 位只读存储器(ROM)包含序列号等,具体格式如下图:
低八位用于CRC校验,中间48位是DS18B20唯一序列号,高八位是该系列产品系列号(固定为28h)。因此,根据每个DS18B20 唯一的序列号,可以实现一条总线上可以挂载多个DS18B20时,获取指定DS18B20的温度信息。
64 位配置存储器(SCRATCHP)由9个Byte组成,包含温度数据、配置信息等,具体格式如下图:
Byte[0:1]:温度值。也就是当我们发出一个测量温度的命令之后,还需要发送一个读内存的命令才能把温度值读取出来。
Byte[2:3]:TL是低温阈值设置,TH是高温阈值设置。当温度低于/超过阈值,就会报警。 TL、TH存储在EEPROM中,数据在掉电时不会丢失;
Byte4:配置寄存器。用于配置温度精度为9、10、11或12位。配置寄存器也存储在EEPROM中,数据在掉电时不会丢失;
Byte[5:7]:厂商预留;
Byte[8]:CRC校验码。
3.2 通信时序
3.2.1 初始化时序
主机要跟DS18B20通信,首先需要发出一个开始信号。 深黑色线表示由主机驱动信号,浅灰色线表示由DS18B20驱动信号。 最开始时引脚是高电平,想要开始传输信号必须要拉低至少480us,这是复位信号; 然后拉高释放总线,等待15~60us之后,如果GPIO上连有DS18B20芯片,它会拉低60~240us。如果主机在最后检查到60~240us的低脉冲,则表示DS18B20初始化成功。
3.2.2 写时序
写0:拉低至少60us(写周期为60-120us)即可;
写1:先拉低至少1us,然后拉高,整个写周期至少为60us即可。
3.2.3 读时序
主机先拉低至少1us,随后读取电平,如果为0,即读到的数据是0,如果为1,即可读到的数据是1。
注意:整个过程必须在15us内完成,15us后引脚都会被拉高。
3.3 常用命令
DS18B20中有两类命令:ROM命令、功能命令
列表如下:
4. 示例代码
4.1 驱动代码
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
#include <asm/current.h>
#include <linux/delay.h>
#include <linux/version.h>
static int major;
static struct class *ds18b20_class;
static struct gpio_desc *ds18b20_gpio_pin;
// 延时函数
void ds18b20_delay_us(int us)
{
// 定义变量记录时间
u64 pre, last;
// 根据版本不同调用不同的函数
#if LINUX_VERSION_CODE < KERNEL_VERSION(5,0,0)
pre = ktime_get_boot_ns();
while(1)
{
last = ktime_get_boot_ns();
if(last - pre >= us * 1000)
break;
}
#else
pre = ktime_get_boottime_ns();
while(1)
{
last = ktime_get_boottime_ns();
if(last - pre >= us * 1000)
break;
}
#endif
}
// 响应信号
// 确认ds18b20设备的存在 先拉高后拉低 表示可以接收信号
int ds18b20_wait_for_ack(void)
{
int timeout_count = 500;
while(gpiod_get_value(ds18b20_gpio_pin) && timeout_count)
{
udelay(1);
timeout_count--;
}
if(!timeout_count)
{
return -1;
}
timeout_count = 500;
while(!gpiod_get_value(ds18b20_gpio_pin) && timeout_count)
{
udelay(1);
timeout_count--;
}
if(!timeout_count)
{
return -1;
}
return 0;
}
// 复位信号
static int ds18b20_reset(void)
{
int ret;
// 拉低480us,这是复位信号
gpiod_direction_output(ds18b20_gpio_pin, 0);
ds18b20_delay_us(480);
// 设置引脚为输入模式
ret = gpiod_direction_input(ds18b20_gpio_pin);
if(ds18b20_wait_for_ack())
return -1;
else
return 0;
}
// 写字节
static void ds18b20_write_byte(unsigned char data)
{
int i;
for(i = 0; i < 8; i++)
{
if(data & (1 << i))
{
// 输出1
gpiod_direction_output(ds18b20_gpio_pin, 0);
ds18b20_delay_us(2);
gpiod_direction_input(ds18b20_gpio_pin);
ds18b20_delay_us(60);
}else{
// 输出0
gpiod_direction_output(ds18b20_gpio_pin, 0);
ds18b20_delay_us(60);
gpiod_direction_input(ds18b20_gpio_pin);
ds18b20_delay_us(2);
}
}
}
// 读字节
unsigned char ds18b20_read_byte(void)
{
unsigned char data = 0;
int i;
for(i = 0; i < 8; i++)
{
gpiod_direction_output(ds18b20_gpio_pin, 0);
ds18b20_delay_us(2);
/* 设置为输入 */
gpiod_direction_input(ds18b20_gpio_pin);
/* 7us之后读引脚 */
ds18b20_delay_us(7);
if (gpiod_get_value(ds18b20_gpio_pin))
data |= (1<<i);
/* 读到数据后, 等待足够60us */
ds18b20_delay_us(60);
}
return data;
}
static ssize_t ds18b20_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
unsigned long flags;
unsigned char tempL=0,tempH=0;
unsigned int integer;
unsigned char decimal1,decimal2,decimal;
int err;
if (size != 5)
return -EINVAL;
local_irq_save(flags); // 关中断
if (ds18b20_reset())
{
gpiod_direction_output(ds18b20_gpio_pin, 1);
local_irq_restore(flags); // 恢复中断
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return -ENODEV;
}
ds18b20_write_byte(0xcc); //忽略rom指令,直接使用功能指令
ds18b20_write_byte(0x44); //温度转换指令
/* 不能省略! */
gpiod_direction_output(ds18b20_gpio_pin, 1);
local_irq_restore(flags); // 恢复中断
//转换需要时间,延时1s
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(HZ);
local_irq_save(flags); // 关中断
if (ds18b20_reset())
{
gpiod_direction_output(ds18b20_gpio_pin, 1);
local_irq_restore(flags); // 恢复中断
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return -ENODEV;
}
ds18b20_write_byte(0xcc); //忽略rom指令,直接使用功能指令
ds18b20_write_byte(0xbe); //读暂存器指令
tempL = ds18b20_read_byte(); //读温度低8位
tempH = ds18b20_read_byte(); //读温度高8位
if (tempH > 0x7f) //最高位为1时温度是负
{
tempL = ~tempL; //补码转换,取反加一
tempH = ~tempH+1;
integer = tempL/16+tempH*16; //整数部分
decimal1 = (tempL&0x0f)*10/16; //小数第一位
decimal2 = (tempL&0x0f)*100/16%10; //小数第二位
decimal = decimal1*10+decimal2; //小数两位
}
else
{
integer = tempL/16+tempH*16; //整数部分
decimal1 = (tempL&0x0f)*10/16; //小数第一位
decimal2 = (tempL&0x0f)*100/16%10; //小数第二位
decimal = decimal1*10+decimal2; //小数两位
}
local_irq_restore(flags); // 恢复中断
gpiod_direction_output(ds18b20_gpio_pin, 1);
err = copy_to_user(buf, &integer, 4);
err = copy_to_user(buf+4, &decimal, 1);
return 5;
}
static unsigned int ds18b20_poll (struct file *file, struct poll_table_struct *wait)
{
return 0;
}
static struct file_operations ds18b20_opes = {
.owner = THIS_MODULE,
.read = ds18b20_read,
.poll = ds18b20_poll,
};
int ds18b20_probe(struct platform_device *pdev)
{
ds18b20_gpio_pin = gpiod_get(&pdev->dev, NULL, GPIOD_OUT_HIGH);
device_create(ds18b20_class, NULL, MKDEV(major, 0), NULL, "myds18b20");
return 0;
}
int ds18b20_remove(struct platform_device *pdev)
{
device_destroy(ds18b20_class, MKDEV(major, 0));
gpiod_put(ds18b20_gpio_pin);
return 0;
}
static const struct of_device_id ask100_ds18b20[] = {
{ .compatible = "100ask,ds18b20" },
{},
};
static struct platform_driver ds18b20_dri = {
.probe = ds18b20_probe,
.remove = ds18b20_remove,
.driver = {
.name = "100ask_ds18b20",
.of_match_table = ask100_ds18b20,
},
};
static int __init ds18b20_init(void)
{
int err;
major = register_chrdev(0, "ds18b20", &ds18b20_opes);
ds18b20_class = class_create(THIS_MODULE, "ds18b20_class");
err = platform_driver_register(&ds18b20_dri);
return err;
}
static void __exit ds18b20_exit(void)
{
unregister_chrdev(major,"ds18b20");
class_destroy(ds18b20_class);
platform_driver_unregister(&ds18b20_dri);
}
module_init(ds18b20_init);
module_exit(ds18b20_exit);
MODULE_LICENSE("GPL");
4.2 应用代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <poll.h>
#include <signal.h>
#include <unistd.h>
/*
* ./ds18b20_test /dev/myds18b20
*
*/
int main(int argc, char **argv)
{
int fd;
unsigned char data[5];
unsigned int integer;
unsigned char decimal;
int i;
/* 1. 判断参数 */
if (argc != 2)
{
printf("Usage: %s <dev>\n", argv[0]);
return -1;
}
/* 2. 打开文件 */
// fd = open(argv[1], O_RDWR | O_NONBLOCK);
fd = open(argv[1], O_RDWR);
if (fd == -1)
{
printf("can not open file %s\n", argv[1]);
return -1;
}
while (1)
{
if (read(fd, data, 5) == 5)
{
integer = data[0] | (data[1]<<8) | (data[2]<<16) | (data[3]<<24);
decimal = data[4];
printf("get temprature: %d.%d\n", integer, decimal);
}
else
{
printf("get temprature: -1\n");
}
sleep(1);
}
close(fd);
return 0;
}
4.3 Makefile
# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH, 比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH, 比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
# 请参考各开发板的高级用户使用手册
#KERN_DIR = /home/book/100ask_stm32mp157_pro-sdk/Linux-5.4
KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88
all:
make -C $(KERN_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc -o ds18b20_test ds18b20_test.c
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order ds18b20_test
# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o
obj-m += ds18b20_drv.o