实验过程
实验目的: 在龙芯开发板上面使用单总线驱动DS18B20温度传感器
① 根据原理图连接DS18B20模块
② 将i2c0引脚的功能复用为GPIO
③ 注册字符设备,按照DS18B20的读写时序编写读写驱动接口
④ 编写测试用例解析传感器的数值
原理图
将板子上面的GPIO48连接传感器的DAT引脚,其余引脚连接如下
然后记得在设备树中把i2c0部分代码注释掉,将PIN16复用为GPIO48
驱动代码
定义相关传感器设备结构体
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/errno.h>
#define DS18B20_DEV_NUM 1
#define DS18B20_DEV "ds18b20"
#define DS18B20_GPIO 48
#define DS18B20_DQ_OUT(x) gpio_direction_output(ds18b20.gpio, x)
#define DS18B20_DQ_IN ds18b20_get_io()
struct ds18b20_dev {
dev_t dev_id;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
int minor;
int gpio;
unsigned char data[2];
struct timer_list timer;
struct work_struct work;
};
struct ds18b20_dev ds18b20;
传感器数据读写交互
static int ds18b20_get_io(void)
{
gpio_direction_input(ds18b20.gpio);
return gpio_get_value(ds18b20.gpio);
}
static void ds18b20_reset(void)
{
DS18B20_DQ_OUT(0); /* 拉低DQ,复位 */
udelay(750); /* 拉低750us */
DS18B20_DQ_OUT(1); /* DQ=1, 释放复位 */
udelay(15); /* 延迟15US */
}
uint8_t ds18b20_check(void)
{
uint8_t retry = 0;
uint8_t rval = 0;
while (DS18B20_DQ_IN && retry < 200) /* 等待DQ变低, 等待200us */
{
retry++;
udelay(1);
}
if (retry >= 200)
{
rval = 1;
}
else
{
retry = 0;
while (!DS18B20_DQ_IN && retry < 240) /* 等待DQ变高, 等待240us */
{
retry++;
udelay(1);
}
if (retry >= 240) rval = 1;
}
return rval;
}
static uint8_t ds18b20_read_bit(void)
{
uint8_t data = 0;
DS18B20_DQ_OUT(0);
udelay(2);
DS18B20_DQ_OUT(1);
udelay(12);
if (DS18B20_DQ_IN)
{
data = 1;
}
udelay(50);
return data;
}
static uint8_t ds18b20_read_byte(void)
{
uint8_t i, b, data = 0;
for (i = 0; i < 8; i++)
{
b = ds18b20_read_bit(); /* DS18B20先输出低位数据 ,高位数据后输出 */
data |= b << i; /* 填充data的每一位 */
}
return data;
}
static void ds18b20_write_byte(uint8_t data)
{
uint8_t j;
for (j = 1; j <= 8; j++)
{
if (data & 0x01)
{
DS18B20_DQ_OUT(0); /* Write 1 */
udelay(2);
DS18B20_DQ_OUT(1);
udelay(60);
}
else
{
DS18B20_DQ_OUT(0); /* Write 0 */
udelay(60);
DS18B20_DQ_OUT(1);
udelay(2);
}
data >>= 1; /* 右移,获取高一位数据 */
}
}
static void ds18b20_start(void)
{
ds18b20_reset();
ds18b20_check();
ds18b20_write_byte(0xcc); /* skip rom */
ds18b20_write_byte(0x44); /* convert */
}
static int ds18b20_init(void)
{
gpio_direction_output(ds18b20.gpio, 0);
ds18b20_reset();
return ds18b20_check();
}
注册字符设备,绑定相关回调函数
static int ds18b20_open(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t ds18b20_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret;
ret = copy_to_user(buf, &ds18b20.data[0], 2);
if(ret)
return -ENOMEM;
return ret;
}
static struct file_operations ds18b20_fops = {
.owner = THIS_MODULE,
.open = ds18b20_open,
.read = ds18b20_read,
};
static void ds18b20_work_callback(struct work_struct *work)
{
ds18b20_start(); /* ds1820 start convert */
ds18b20_reset();
ds18b20_check();
ds18b20_write_byte(0xcc); /* skip rom */
ds18b20_write_byte(0xbe); /* convert */
ds18b20.data[0] = ds18b20_read_byte(); /* LSB */
ds18b20.data[1] = ds18b20_read_byte(); /* MSB */
}
static void ds18b20_timer_callback(struct timer_list *arg)
{
schedule_work(&ds18b20.work);
mod_timer(&ds18b20.timer, jiffies + (1000 * HZ/1000));
}
static int ds18b20_module_init(void)
{
int ret = 0;
ds18b20.gpio = DS18B20_GPIO;
if (!gpio_is_valid(ds18b20.gpio)) {
return -EINVAL;
}
ret = gpio_request(ds18b20.gpio, "DS18B20-GPIO");
if (ret) {
printk(KERN_ERR "ds18b20 : Failed to request gpio\n");
return ret;
}
ds18b20_init();
if (ds18b20.major) {
ds18b20.dev_id = MKDEV(ds18b20.major, 0);
ret = register_chrdev_region(ds18b20.dev_id, DS18B20_DEV_NUM, DS18B20_DEV);
if(ret < 0) {
pr_err("cannot register %s char driver [ret=%d]\n", DS18B20_DEV, DS18B20_DEV_NUM);
goto free_gpio;
}
}
else {
ret = alloc_chrdev_region(&ds18b20.dev_id, 0, DS18B20_DEV_NUM, DS18B20_DEV);
if(ret < 0) {
pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", DS18B20_DEV, ret);
goto free_gpio;
}
ds18b20.major = MAJOR(ds18b20.dev_id);
ds18b20.minor = MINOR(ds18b20.dev_id);
}
ds18b20.cdev.owner = THIS_MODULE;
cdev_init(&ds18b20.cdev, &ds18b20_fops);
cdev_add(&ds18b20.cdev, ds18b20.dev_id, DS18B20_DEV_NUM);
if(ret < 0)
goto del_unregister;
ds18b20.class = class_create(THIS_MODULE, DS18B20_DEV);
if (IS_ERR(ds18b20.class)) {
goto del_cdev;
}
ds18b20.device = device_create(ds18b20.class, NULL, ds18b20.dev_id, NULL, DS18B20_DEV);
if (IS_ERR(ds18b20.device)) {
goto destroy_class;
}
timer_setup(&ds18b20.timer, ds18b20_timer_callback, 0);
ds18b20.timer.expires=jiffies + msecs_to_jiffies(1000);
add_timer(&ds18b20.timer);
INIT_WORK(&ds18b20.work, ds18b20_work_callback);
return 0;
destroy_class:
class_destroy(ds18b20.class);
del_cdev:
cdev_del(&ds18b20.cdev);
del_unregister:
unregister_chrdev_region(ds18b20.dev_id, DS18B20_DEV_NUM);
free_gpio:
gpio_free(ds18b20.gpio);
return -EIO;
}
整合代码
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/errno.h>
#define DS18B20_DEV_NUM 1
#define DS18B20_DEV "ds18b20"
#define DS18B20_GPIO 48
#define DS18B20_DQ_OUT(x) gpio_direction_output(ds18b20.gpio, x)
#define DS18B20_DQ_IN ds18b20_get_io()
struct ds18b20_dev {
dev_t dev_id;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
int minor;
int gpio;
unsigned char data[2];
struct timer_list timer;
struct work_struct work;
};
struct ds18b20_dev ds18b20;
static int ds18b20_get_io(void)
{
gpio_direction_input(ds18b20.gpio);
return gpio_get_value(ds18b20.gpio);
}
static void ds18b20_reset(void)
{
DS18B20_DQ_OUT(0); /* 拉低DQ,复位 */
udelay(750); /* 拉低750us */
DS18B20_DQ_OUT(1); /* DQ=1, 释放复位 */
udelay(15); /* 延迟15US */
}
uint8_t ds18b20_check(void)
{
uint8_t retry = 0;
uint8_t rval = 0;
while (DS18B20_DQ_IN && retry < 200) /* 等待DQ变低, 等待200us */
{
retry++;
udelay(1);
}
if (retry >= 200)
{
rval = 1;
}
else
{
retry = 0;
while (!DS18B20_DQ_IN && retry < 240) /* 等待DQ变高, 等待240us */
{
retry++;
udelay(1);
}
if (retry >= 240) rval = 1;
}
return rval;
}
static uint8_t ds18b20_read_bit(void)
{
uint8_t data = 0;
DS18B20_DQ_OUT(0);
udelay(2);
DS18B20_DQ_OUT(1);
udelay(12);
if (DS18B20_DQ_IN)
{
data = 1;
}
udelay(50);
return data;
}
static uint8_t ds18b20_read_byte(void)
{
uint8_t i, b, data = 0;
for (i = 0; i < 8; i++)
{
b = ds18b20_read_bit(); /* DS18B20先输出低位数据 ,高位数据后输出 */
data |= b << i; /* 填充data的每一位 */
}
return data;
}
static void ds18b20_write_byte(uint8_t data)
{
uint8_t j;
for (j = 1; j <= 8; j++)
{
if (data & 0x01)
{
DS18B20_DQ_OUT(0); /* Write 1 */
udelay(2);
DS18B20_DQ_OUT(1);
udelay(60);
}
else
{
DS18B20_DQ_OUT(0); /* Write 0 */
udelay(60);
DS18B20_DQ_OUT(1);
udelay(2);
}
data >>= 1; /* 右移,获取高一位数据 */
}
}
static void ds18b20_start(void)
{
ds18b20_reset();
ds18b20_check();
ds18b20_write_byte(0xcc); /* skip rom */
ds18b20_write_byte(0x44); /* convert */
}
static int ds18b20_init(void)
{
gpio_direction_output(ds18b20.gpio, 0);
ds18b20_reset();
return ds18b20_check();
}
static int ds18b20_open(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t ds18b20_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret;
ret = copy_to_user(buf, &ds18b20.data[0], 2);
if(ret)
return -ENOMEM;
return ret;
}
static struct file_operations ds18b20_fops = {
.owner = THIS_MODULE,
.open = ds18b20_open,
.read = ds18b20_read,
};
static void ds18b20_work_callback(struct work_struct *work)
{
ds18b20_start(); /* ds1820 start convert */
ds18b20_reset();
ds18b20_check();
ds18b20_write_byte(0xcc); /* skip rom */
ds18b20_write_byte(0xbe); /* convert */
ds18b20.data[0] = ds18b20_read_byte(); /* LSB */
ds18b20.data[1] = ds18b20_read_byte(); /* MSB */
}
static void ds18b20_timer_callback(struct timer_list *arg)
{
schedule_work(&ds18b20.work);
mod_timer(&ds18b20.timer, jiffies + (1000 * HZ/1000));
}
static int ds18b20_module_init(void)
{
int ret = 0;
ds18b20.gpio = DS18B20_GPIO;
if (!gpio_is_valid(ds18b20.gpio)) {
return -EINVAL;
}
ret = gpio_request(ds18b20.gpio, "DS18B20-GPIO");
if (ret) {
printk(KERN_ERR "ds18b20 : Failed to request gpio\n");
return ret;
}
ds18b20_init();
if (ds18b20.major) {
ds18b20.dev_id = MKDEV(ds18b20.major, 0);
ret = register_chrdev_region(ds18b20.dev_id, DS18B20_DEV_NUM, DS18B20_DEV);
if(ret < 0) {
pr_err("cannot register %s char driver [ret=%d]\n", DS18B20_DEV, DS18B20_DEV_NUM);
goto free_gpio;
}
}
else {
ret = alloc_chrdev_region(&ds18b20.dev_id, 0, DS18B20_DEV_NUM, DS18B20_DEV);
if(ret < 0) {
pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", DS18B20_DEV, ret);
goto free_gpio;
}
ds18b20.major = MAJOR(ds18b20.dev_id);
ds18b20.minor = MINOR(ds18b20.dev_id);
}
ds18b20.cdev.owner = THIS_MODULE;
cdev_init(&ds18b20.cdev, &ds18b20_fops);
cdev_add(&ds18b20.cdev, ds18b20.dev_id, DS18B20_DEV_NUM);
if(ret < 0)
goto del_unregister;
ds18b20.class = class_create(THIS_MODULE, DS18B20_DEV);
if (IS_ERR(ds18b20.class)) {
goto del_cdev;
}
ds18b20.device = device_create(ds18b20.class, NULL, ds18b20.dev_id, NULL, DS18B20_DEV);
if (IS_ERR(ds18b20.device)) {
goto destroy_class;
}
timer_setup(&ds18b20.timer, ds18b20_timer_callback, 0);
ds18b20.timer.expires=jiffies + msecs_to_jiffies(1000);
add_timer(&ds18b20.timer);
INIT_WORK(&ds18b20.work, ds18b20_work_callback);
return 0;
destroy_class:
class_destroy(ds18b20.class);
del_cdev:
cdev_del(&ds18b20.cdev);
del_unregister:
unregister_chrdev_region(ds18b20.dev_id, DS18B20_DEV_NUM);
free_gpio:
gpio_free(ds18b20.gpio);
return -EIO;
}
static void ds18b20_module_exit(void)
{
cdev_del(&ds18b20.cdev);
unregister_chrdev_region(ds18b20.dev_id, DS18B20_DEV_NUM);
device_destroy(ds18b20.class, ds18b20.dev_id);
class_destroy(ds18b20.class);
del_timer(&ds18b20.timer);
cancel_work_sync(&ds18b20.work);
gpio_free(ds18b20.gpio);
}
module_init(ds18b20_module_init);
module_exit(ds18b20_module_exit);
MODULE_LICENSE("GPL");
Makefile文件
obj-m += ds18b20.o
KDIR:=/home/asensing/loongson/linux-4.19
ARCH=loongarch
CROSS_COMPILE=loongarch64-linux-gnu-
PWD?=$(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
构建脚本
export PATH=$PATH:/home/asensing/loongson/loongson-gnu-toolchain-8.3-x86_64-loongarch64-linux-gnu-rc1.3-1/bin
make -j8
loongarch64-linux-gnu-gcc test.c -o test
FILE=$PWD/$(basename $PWD).ko
scp $FILE test root@192.168.137.216:/home/root
测试用例
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#define DEV_NAME "/dev/ds18b20"
int main()
{
int fd, ret;
unsigned char result[2];
unsigned char TH, TL;
short tmp = 0;
float temperature;
int flag = 0;
fd = open(DEV_NAME, 0);
if(fd < 0)
{
printf("open %s device failed\n", DEV_NAME);
exit(1);
}
else
printf("Open %s success!\n", DEV_NAME);
while(1)
{
ret = read(fd, &result, sizeof(result));
if(ret == 0) { /* 读取到数据 */
TL = result[0];
TH = result[1];
if(TH > 7) { /* 负数处理 */
TH = ~TH;
TL = ~TL;
flag = 1; /* 标记为负数 */
}
tmp = TH;
tmp <<= 8;
tmp += TL;
if(flag == 1) {
temperature = (float)(tmp+1) * 0.0625; /* 计算负数的温度 */
temperature = -temperature;
}else {
temperature = (float)tmp *0.0625; /* 计算正数的温度 */
}
if(temperature < 125 && temperature > -55) { /* 温度范围 */
printf("Environment Temperature Now : %0.2f℃\n", temperature);
}
}
flag = 0;
sleep(1);
}
close(fd); /* 关闭文件 */
}
设备树版驱动
当然也可以使用设备树描述节点(对应的设备树IO为 gpa3 0 1
),文件路径:arch/loongarch/boot/dts/loongson/loongson_2k0300.dtsi
ds18b20 {
compatible = "loongson,ds18b20";
ds18b20-gpio = <&gpa3 0 1>;
status = "okay";
};
驱动代码部分需要改为platform_driver
的方式进行匹配,其它逻辑跟上面的保持一致,这里不详细展开了
static const struct of_device_id of_math_table[] = {
{ .compatible = "loongson,ds18b20" },
};
static struct platform_driver ds18b20_driver = {
.driver = {
.name = "ds18b20",
.of_match_table = of_math_table,
},
.probe = ds18b20_probe,
.remove = ds18b20_remove,
};
通过of函数获取GPIO节点
static int ds18b20_init_gpio(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int ret;
ds18b20.gpio = of_get_named_gpio(dev->of_node, "ds18b20-gpio", 0);
if (!gpio_is_valid(ds18b20.gpio)) {
return -EINVAL;
}
ret = devm_gpio_request(dev, ds18b20.gpio, "DS18B20 Gpio");
if (ret) {
return ret;
}
return 0;
}
实验效果
插入驱动后,使用测试用例实时读取环境温度值
广东太热,我要回非洲!
参考
以上驱动参考自RT-Thread的工程:rtt-psoc62/one-wire/ds18b20.c at main · hywing/rtt-psoc62 (github.com)