瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。
【公众号】迅为电子
【粉丝群】824412014(加群获取驱动文档+例程)
【视频观看】嵌入式学习之Linux驱动(第十四篇 单总线_全新升级)_基于RK3568
【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板
第162章DS18B20驱动读时序编写
在上个章节中完善了DS18B20驱动的写时序,在本小节中将完善DS18B20的读时序。
162.1 读时序分析
DS18B20数据手册中关于读取的时序图如下所示:
关于DS18B20有读0和读1两种时序,他们的时序是不同的,接下来首先对前半部分读0进行分析。
步骤1:主机拉低总线,从高电平变成低电平,且有时间限制,要确保拉低的时间最少为1微秒
步骤2:主机释放总线,从机拉低总线,15us以内主机完成采样工作,如果这时候从机仍旧处于拉低总线的状态,则采集到的就是0.
步骤3:拉高总线,恢复总线的高电平状态,且要求读操作必须大于60毫秒。
总结出的读取0操作代码如下所示:
unsigned char ds18b20_readbit(void) {
unsigned char bit;
gpiod_direction_output(ds18b20->ds18b20_gpio, 1);// 将 GPIO 方向设置为输出
gpiod_set_value(ds18b20->ds18b20_gpio, 0);// 将 GPIO 输出设置为低电平
udelay(2);// 延时 2 微秒
gpiod_direction_input(ds18b20->ds18b20_gpio);// 将 GPIO 方向设置为输入
udelay(10);// 延时 10 微秒
bit = gpiod_get_value(ds18b20->ds18b20_gpio);// 读取 GPIO 的值作为位(bit)
udelay(60);// 延时 60 微秒
return bit;
}
然后来对读1的步骤进行分析:
步骤1:主机拉低总线,从高电平变成低电平,且有时间限制,要确保拉低的时间最少为1微秒
步骤2:通过电阻进行上拉,15us以内主机完成采样工作,如果这时候处于电阻上拉的状态,则采集到的就是1
总结出的读1操作代码如下所示:
unsigned char ds18b20_readbit(void) {
unsigned char bit;
gpiod_direction_output(ds18b20->ds18b20_gpio, 1);// 将 GPIO 方向设置为输出
gpiod_set_value(ds18b20->ds18b20_gpio, 0);// 将 GPIO 输出设置为低电平
udelay(2);// 延时 2 微秒
gpiod_direction_input(ds18b20->ds18b20_gpio);// 将 GPIO 方向设置为输入
bit = gpiod_get_value(ds18b20->ds18b20_gpio);// 读取 GPIO 的值作为位(bit)
return bit;
}
综合上面读1和读0操作的代码以及时序图,可以将两个代码进行整合在一起,整合之后的代码如下所示,大家可以神奇的发现该函数和读0中的函数是相同的。
unsigned char ds18b20_readbit(void) {
unsigned char bit;
gpiod_direction_output(ds18b20->ds18b20_gpio, 1);// 将 GPIO 方向设置为输出
gpiod_set_value(ds18b20->ds18b20_gpio, 0);// 将 GPIO 输出设置为低电平
udelay(2);// 延时 2 微秒
gpiod_direction_input(ds18b20->ds18b20_gpio);// 将 GPIO 方向设置为输入
udelay(10);// 延时 10 微秒
bit = gpiod_get_value(ds18b20->ds18b20_gpio);// 读取 GPIO 的值作为位(bit)
udelay(60);// 延时 60 微秒
return bit;
}
但这样修改之后的代码仅仅只能读取一个字符,如果要读取8位字符就需要连续使用8次该函数,而为了更方便,可以重新添加一个函数,从而直接读取一个字节的数据,具体内容如下所示:
/**
* 从 DS18B20 读取单个位(bit)
* @return 读取到的位(bit),0 或 1
*/
unsigned char ds18b20_readbit(void) {
unsigned char bit;
gpiod_direction_output(ds18b20->ds18b20_gpio, 1);// 将 GPIO 方向设置为输出
gpiod_set_value(ds18b20->ds18b20_gpio, 0);// 将 GPIO 输出设置为低电平
udelay(2);// 延时 2 微秒
gpiod_direction_input(ds18b20->ds18b20_gpio);// 将 GPIO 方向设置为输入
udelay(10);// 延时 10 微秒
bit = gpiod_get_value(ds18b20->ds18b20_gpio);// 读取 GPIO 的值作为位(bit)
udelay(60);// 延时 60 微秒
return bit;
}
/**
* 从 DS18B20 读取一个字节(byte)数据
* @return 读取到的字节数据
*/
int ds18b20_readbyte(void) {
int data = 0;
int i;
for (i = 0; i < 8; i++) {
// 读取单个位(bit)并根据位的位置进行左移操作
data |= ds18b20_readbit() << i;
}
return data;
}
至此,关于DS18B20的读操作相关函数就编写完成了,会在下个小节编写填加写时序相关函数的驱动。
162.2 DS18b20驱动读时序编写
本实验对应的网盘路径为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\97_ds18b20_04\
编写完成的ds18b20.c代码如下所示:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h> // 添加此头文件
#include <linux/delay.h>
struct ds18b20_data
{
dev_t dev_num;
struct cdev ds18b20_cdev;
struct class *ds18b20_class;
struct device *ds18b20_device;
struct gpio_desc *ds18b20_gpio;
};
struct ds18b20_data *ds18b20;
void ds18b20_reset(void)
{
// 设置 GPIO 方向为输出,输出低电平
gpiod_direction_output(ds18b20->ds18b20_gpio, 1);
gpiod_set_value(ds18b20->ds18b20_gpio, 0);
udelay(700); // 延迟 700 微秒
// 设置 GPIO 输出高电平,并将 GPIO 方向设置为输入
gpiod_set_value(ds18b20->ds18b20_gpio, 1);
gpiod_direction_input(ds18b20->ds18b20_gpio);
// 等待直到 GPIO 输入为低电平
while (gpiod_get_value(ds18b20->ds18b20_gpio))
;
// 等待直到 GPIO 输入为高电平
while (!gpiod_get_value(ds18b20->ds18b20_gpio))
;
udelay(480); // 延迟 480 微秒
}
/**
* 向 DS18B20 写入单个位(bit)
* @param bit 要写入的位(bit),0 或 1
*/
void ds18b20_writebit(unsigned char bit) {
// 将 GPIO 方向设置为输出
gpiod_direction_output(ds18b20->ds18b20_gpio, 1);
// 将 GPIO 输出设置为指定的位(bit)
gpiod_set_value(ds18b20->ds18b20_gpio, 0);
// 若 bit 为 1,则延时 10 微秒
if (bit)
{
udelay(10);
// 将 GPIO 方向设置为输出
gpiod_direction_output(ds18b20->ds18b20_gpio, 1);
}
// 延时 65 微秒
udelay(65);
// 将 GPIO 方向设置为输出
gpiod_direction_output(ds18b20->ds18b20_gpio, 1);
// 延时 2 微秒
udelay(2);
}
/**
* 向 DS18B20 写入一个字节(byte)数据
* @param data 要写入的字节数据
*/
void ds18b20_writebyte(int data) {
int i;
for (i = 0; i < 8; i++) {
// 逐位写入数据
ds18b20_writebit(data & 0x01);
data = data >> 1;
}
}
/**
* 从 DS18B20 读取单个位(bit)
* @return 读取到的位(bit),0 或 1
*/
unsigned char ds18b20_readbit(void) {
unsigned char bit;
gpiod_direction_output(ds18b20->ds18b20_gpio, 1);// 将 GPIO 方向设置为输出
gpiod_set_value(ds18b20->ds18b20_gpio, 0);// 将 GPIO 输出设置为低电平
udelay(2);// 延时 2 微秒
gpiod_direction_input(ds18b20->ds18b20_gpio);// 将 GPIO 方向设置为输入
udelay(10);// 延时 10 微秒
bit = gpiod_get_value(ds18b20->ds18b20_gpio);// 读取 GPIO 的值作为位(bit)
udelay(60);// 延时 60 微秒
return bit;
}
/**
* 从 DS18B20 读取一个字节(byte)数据
* @return 读取到的字节数据
*/
int ds18b20_readbyte(void) {
int data = 0;
int i;
for (i = 0; i < 8; i++) {
// 读取单个位(bit)并根据位的位置进行左移操作
data |= ds18b20_readbit() << i;
}
return data;
}
int ds18b20_open(struct inode *inode, struct file *file)
{
return 0;
}
ssize_t ds18b20_read(struct file *file, char __user *buf, size_t size, loff_t *offs)
{
return 0;
}
int ds18b20_release(struct inode *inode, struct file *file)
{
return 0;
}
struct file_operations ds18b20_fops = {
.open = ds18b20_open,
.read = ds18b20_read,
.release = ds18b20_release,
.owner = THIS_MODULE,
};
int ds18b20_probe(struct platform_device *dev)
{
int ret;
printk("This is probe \n");
// 分配内存给ds18b20_data结构体
ds18b20 = kzalloc(sizeof(*ds18b20), GFP_KERNEL);
if (ds18b20 == NULL)
{
printk("kzalloc error\n");
ret = -ENOMEM;
goto error_0;
}
// 分配字符设备号
ret = alloc_chrdev_region(&ds18b20->dev_num, 0, 1, "myds18b20");
if (ret < 0)
{
printk("alloc_chrdev_region error\n");
ret = -EAGAIN;
goto error_1;
}
// 初始化字符设备
cdev_init(&ds18b20->ds18b20_cdev, &ds18b20_fops);
ds18b20->ds18b20_cdev.owner = THIS_MODULE;
cdev_add(&ds18b20->ds18b20_cdev, ds18b20->dev_num, 1);
// 创建设备类
ds18b20->ds18b20_class = class_create(THIS_MODULE, "sensors");
if (IS_ERR(ds18b20->ds18b20_class))
{
printk("class_create error\n");
ret = PTR_ERR(ds18b20->ds18b20_class);
goto error_2;
}
// 创建设备
ds18b20->ds18b20_device = device_create(ds18b20->ds18b20_class, NULL, ds18b20->dev_num, NULL, "ds18b20");
if (IS_ERR(ds18b20->ds18b20_device))
{
printk("device_create error\n");
ret = PTR_ERR(ds18b20->ds18b20_device);
goto error_3;
}
// 获取GPIO描述符
ds18b20->ds18b20_gpio = gpiod_get_optional(&dev->dev, "ds18b20", 0);
if (ds18b20->ds18b20_gpio == NULL)
{
ret = -EBUSY;
goto error_4;
}
// 设置GPIO方向为输出
gpiod_direction_output(ds18b20->ds18b20_gpio, 1);
return 0;
error_4:
device_destroy(ds18b20->ds18b20_class, ds18b20->dev_num);
error_3:
class_destroy(ds18b20->ds18b20_class);
error_2:
cdev_del(&ds18b20->ds18b20_cdev);
unregister_chrdev_region(ds18b20->dev_num, 1);
error_1:
kfree(ds18b20);
error_0:
return ret;
}
const struct of_device_id ds18b20_match_table[] = {
{.compatible = "ds18b20"},
{},
};
struct platform_driver ds18b20_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "ds18b20",
.of_match_table = ds18b20_match_table,
},
.probe = ds18b20_probe,
};
static int __init ds18b20_init(void)
{
int ret;
// 注册平台驱动
ret = platform_driver_register(&ds18b20_driver);
if (ret < 0)
{
printk("platform_driver_register error\n");
return -1;
}
return 0;
}
static void __exit ds18b20_exit(void)
{
// 释放资源
gpiod_put(ds18b20->ds18b20_gpio);
device_destroy(ds18b20->ds18b20_class, ds18b20->dev_num);
class_destroy(ds18b20->ds18b20_class);
cdev_del(&ds18b20->ds18b20_cdev);
unregister_chrdev_region(ds18b20->dev_num, 1);
kfree(ds18b20);
platform_driver_unregister(&ds18b20_driver);
}
module_init(ds18b20_init);
module_exit(ds18b20_exit);
MODULE_LICENSE("GPL");
由于读时序的实验需要后续的知识作为支撑,所以会在下个小节的驱动中完善温度读取相关的函数之后,再进行测试。