瑞芯微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主板
第165章DS18B20驱动使用ioctl读取分辨率
在上个章节中成功在驱动中添加了ioctl相关代码,通过ioctl对DS18B20采集到的温度分辨率进行设置。而在本章节将继续完善驱动程序,对DS18B20采集到的温度分辨率进行读取,然后编写相应的应用程序进行分辨率读取测试。
165.1 ioctl读取分辨率驱动代码编写
本实验对应的网盘路径为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\100_ds18b20_07\01_module。
相较于上一章节的驱动代码,添加了ioctl查看分辨率相关的函数,除此之外完成了查看温度分辨率函数read_resolution(),编写完成的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>
#include <linux/uaccess.h>
#define SET_RESOLUTION _IOW('A', 0, int)
#define READ_RESOLUTION _IOR('A', 1, int)
#define SET_RESOLUTION_9 9
#define SET_RESOLUTION_10 10
#define SET_RESOLUTION_11 11
#define SET_RESOLUTION_12 12
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;
}
/**
* 从 DS18B20 读取温度值
* @return 读取到的温度值
*/
int ds18b20_readtemp(void) {
int temp_l, temp_h, temp;
ds18b20_reset();// 复位 DS18B20
ds18b20_writebyte(0xCC);// 发送写入字节命令 0xCC(跳过 ROM)
ds18b20_writebyte(0x44);// 发送写入字节命令 0x44(启动温度转换)
mdelay(750);// 延时 750 微秒,等待温度转换完成
ds18b20_reset();// 复位 DS18B20
ds18b20_writebyte(0xCC);// 发送写入字节命令 0xCC(跳过 ROM)
ds18b20_writebyte(0xBE);// 发送写入字节命令 0xBE(读取温度值)
temp_l = ds18b20_readbyte();// 读取温度低位字节
temp_h = ds18b20_readbyte();// 读取温度高位字节
temp_h = temp_h << 8;// 将温度高位字节左移 8 位
temp = temp_h | temp_l;// 组合温度值
return temp;
}
/**
* 设置 DS18B20 温度传感器的分辨率
* @param args 分辨率参数
*/
void set_resolution(int args) {
ds18b20_reset(); // 复位 DS18B20 温度传感器
ds18b20_writebyte(0xCC); // 发送跳过 ROM 命令
ds18b20_writebyte(0x4E); // 发送写配置寄存器命令
ds18b20_writebyte(60); // 发送配置字节 1,设置温度上限阈值
ds18b20_writebyte(10); // 发送配置字节 2,设置温度下限阈值
switch (args) {
case SET_RESOLUTION_9: // 设置分辨率为 9 位
ds18b20_writebyte(0x1F); // 发送配置字节 3,设置分辨率为 9 位
break;
case SET_RESOLUTION_10: // 设置分辨率为 10 位
ds18b20_writebyte(0x3F); // 发送配置字节 3,设置分辨率为 10 位
break;
case SET_RESOLUTION_11: // 设置分辨率为 11 位
ds18b20_writebyte(0x5F); // 发送配置字节 3,设置分辨率为 11 位
break;
case SET_RESOLUTION_12: // 设置分辨率为 12 位
ds18b20_writebyte(0x7F); // 发送配置字节 3,设置分辨率为 12 位
break;
default:
break;
}
}
/**
* 检查参数的有效性
* @param args 参数
* @return 返回执行结果,成功返回 0,失败返回 -1
*/
int check_args(int args) {
int ret = -1; // 返回值,默认为失败
ds18b20_reset(); // 复位传感器
ds18b20_writebyte(0xCC); // 发送指令字节 0xCC
ds18b20_writebyte(0xBE); // 发送指令字节 0xBE
ds18b20_readbyte(); // 读取一个字节
ds18b20_readbyte(); // 读取一个字节
ds18b20_readbyte(); // 读取一个字节
ds18b20_readbyte(); // 读取一个字节
switch (args) {
case SET_RESOLUTION_9:
if (ds18b20_readbyte() == 0x1F) { // 读取一个字节并与 0x1F 进行比较
ret = 0; // 设置返回值为成功
}
break;
case SET_RESOLUTION_10:
if (ds18b20_readbyte() == 0x3F) { // 读取一个字节并与 0x3F 进行比较
ret = 0; // 设置返回值为成功
}
break;
case SET_RESOLUTION_11:
if (ds18b20_readbyte() == 0x5F) { // 读取一个字节并与 0x5F 进行比较
ret = 0; // 设置返回值为成功
}
break;
case SET_RESOLUTION_12:
if (ds18b20_readbyte() == 0x7F) { // 读取一个字节并与 0x7F 进行比较
ret = 0; // 设置返回值为成功
}
break;
default:
break;
}
return ret; // 返回结果
}
/**
* 读取分辨率。
*
* @return 分辨率值
*/
int read_resolution(void) {
int ret;
// 复位传感器
ds18b20_reset();
// 发送指令字节0xCC,跳过ROM操作,直接与单个设备通信
ds18b20_writebyte(0xCC);
// 发送指令字节0xBE,读取当前设备的配置寄存器
ds18b20_writebyte(0xBE);
// 读取4个字节的数据,但实际上只有最后一个字节是分辨率信息
ds18b20_readbyte();
ds18b20_readbyte();
ds18b20_readbyte();
ds18b20_readbyte();
// 读取最后一个字节,即分辨率信息
ret = ds18b20_readbyte();
// 返回分辨率值
return ret;
}
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) {
int ds18b20_temp;
ds18b20_temp = ds18b20_readtemp();// 从 DS18B20 读取温度值
// 将温度值复制到用户空间缓冲区
if (copy_to_user(buf, &ds18b20_temp, sizeof(ds18b20_temp))) {
return -1; // 复制失败,返回错误代码
}
return 0; // 成功读取并复制温度值
}
int ds18b20_release(struct inode *inode, struct file *file)
{
return 0;
}
/**
* DS18B20 温度传感器的 ioctl 函数
* @param file 文件指针
* @param cmd 命令
* @param args 参数
* @return 返回执行结果,成功返回 0,失败返回 -1
*/
long ds18b20_ioctl(struct file *file, unsigned int cmd, unsigned long args) {
int resolution;
if (cmd == SET_RESOLUTION) { // 判断命令是否为设置分辨率
if (args >= SET_RESOLUTION_9 && args <= SET_RESOLUTION_12) { // 判断参数是否在有效的分辨率范围内
set_resolution(args); // 调用设置分辨率的函数
return 0; // 返回成功
}
}
else if (cmd == READ_RESOLUTION) {
// 读取分辨率
resolution = read_resolution();
// 将分辨率的值复制给用户空间的args
if (copy_to_user((int *)args, &resolution, sizeof(resolution))) {
// 复制失败,返回-1表示失败
return -1;
}
}
// 如果不匹配 SET_RESOLUTION 或者 args 不在有效范围内,不执行任何操作
return -1; // 返回失败
}
struct file_operations ds18b20_fops = {
.open = ds18b20_open,
.read = ds18b20_read,
.release = ds18b20_release,
.unlocked_ioctl = ds18b20_ioctl,
.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");
165.2 应用程序编写
在上一小节中编写了ioctl通过ioctl设置DS18B20获取采集温度分辨率的程序,而ioctl需要跟用户空间的应用程序相配合才能进行使用,所以在本小节将编写测试用的应用程序,编写好的测试程序存放位置为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\100_ds18b20_07\02_app
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#define SET_RESOLUTION _IOW('A', '0', int)
#define READ_RESOLUTION _IOR('A', '1', int)
/**
* 根据传感器读取的原始数据计算温度值
* @param value 传感器读取的原始数据
*/
void ds18b20_get_temp(int value) {
char sig; // 温度正负号
float temp; // 温度值
// 判断温度正负号
if ((value >> 11) & 0x01) {
sig = '-';
value = ~value + 1;
value &= ~(0xf8 << 8);
} else {
sig = '+';
}
// 计算温度值
temp = value * 0.0625;
// 打印温度信息
printf("温度为 %c%.4f\n", sig, temp);
}
/**
* 根据传感器分辨率值打印分辨率信息
* @param value 传感器分辨率值
*/
void ds18b20_get_resolution(int value) {
switch (value) {
case 0x1F:
printf("分辨率为 9 位\n");
break;
case 0x3F:
printf("分辨率为 10 位\n");
break;
case 0x5F:
printf("分辨率为 11 位\n");
break;
case 0x7F:
printf("分辨率为 12 位\n");
break;
default:
break;
}
}
int main(int argc, char *argv[]) {
int fd; // 文件描述符
int data; // 读取的数据
int args; // 参数值
int resolution; // 返回的分辨率的值
// 打开设备文件
fd = open("/dev/ds18b20", O_RDWR);
if (fd < 0) {
printf("打开设备文件出错\n");
return -1;
}
// 获取命令行参数
args = atoi(argv[1]);
printf("参数值为 %d\n", args);
// 检查参数范围
if (args < 9 || args > 12) {
printf("错误!参数范围应为 9 - 12\n");
return -1;
}
// 设置分辨率
ioctl(fd, SET_RESOLUTION, args);
// 读取分辨率
ioctl(fd, READ_RESOLUTION, &resolution);
ds18b20_get_resolution(resolution);
while (1) {
// 读取数据
read(fd, &data, sizeof(data));
// 处理并打印温度信息
ds18b20_get_temp(data);
}
return 0;
}
165.3 运行测试
165.3.1 编译驱动程序
在上一小节中的de18b20.c代码同一目录下创建 Makefile 文件,Makefile 文件内容如下所示:
export ARCH=arm64#设置平台架构
export CROSS_COMPILE=aarch64-linux-gnu-#交叉编译器前缀
obj-m +=ds18b20.o #此处要和你的驱动源文件同名
KDIR :=/home/topeet/Linux/linux_sdk/kernel #这里是你的内核目录
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules #make操作
clean:
make -C $(KDIR) M=$(PWD) clean #make clean操作
对于Makefile的内容注释已在上图添加,保存退出之后,来到存放ds18b20.c和Makefile文件目录下,如下图所示:
然后使用命令“make”进行驱动的编译,编译完成如下图所示:
编译完生成ds18b20.ko目标文件,如下图所示:
至此驱动模块就编译成功了。
165.3.2 编译应用程序
首先进行应用程序的编译,因为测试APP是要在开发板上运行的,所以需要aarch64-linux-gnu-gcc来编译,输入以下命令,编译完成以后会生成一个app的可执行程序,如下图所示:
aarch64-linux-gnu-gcc app.c -o app
然后将编译完成的可执行程序拷贝到开发板上。
165.3.3 运行测试
首先启动开发板,进入系统之后如下所示:
然后将上两个小节编译完成的ds18b20.ko驱动和可执行程序app文件拷贝到开发板上,拷贝完成如下所示:
然后使用以下命令进行驱动的加载,如下图所示:
insmod ds18b20.ko
然后使用以下命令运行可执行程序app,设置分辨率为9位,然后打印采集到的温度,如下图所示:
./app 9
第一个打印表示我们的传入参数9,第二个打印为通过ioctl获取得到的ds18b20的温度分辨率,可以看到设置和和通过ioctl获取的分辨率相同,且后面得到的温度确实为9位分辨率,证明试验成功,至此,关于通过ioctl获取ds18b20温度采集分辨率的实验就完成了。