安卓硬件驱动开发

安卓驱动开发学习--举例--调试 android 驱动开发 调试 adb 阅读详情
AI摘要

本文详细介绍了Android系统的硬件驱动开发过程,包括硬件抽象层(HAL)的作用,以及如何在内核空间和用户空间中实现驱动程序,通过设备文件系统和属性操作来访问硬件。还展示了如何编译内核驱动程序和创建设备文件示例。

安卓硬件驱动

Android系统的硬件抽象层(Hardware Abstract Layer, HAL) 运行在用户空间中, 它向下屏蔽硬件驱动模块的实现细节, 向上提供硬件访问服务。

Android系统的体系结构 :

Android系统的体系结构

依次涉及Android系统的硬件驱动模块、 硬件抽象层、 外部库 和 运行时库层、 应用程序框架层 和 应用程序层等

开发一个应用访问硬件的流程为 : 首先在Android系统的内核空间中为一个硬件开发驱动程序, 接着在用户空间中为该硬件添加一个硬件抽象层模块, 并且在应用程序框架层中添加一个硬件访问服务, 最后开发一个应用程序来访问该硬件服务

image-20200715144129859

开发Android硬件驱动程序

实现内核驱动程序模块

驱动程序freg的目录结构 :

~/Android/kernel/goldfish
    drivers
        freg
            freg.h    # 源代码文件
            freg.c    # 源代码文件
            Kconfig    # 编译选项配置文件
            Makefile    # 编译脚本文件

freg.h 源代码文件 :

// kernel\goldfish\drivers\freg\freg.h

#ifndef _FAKE_REG_H_
#define _FAKE_REG_H_

#include <linux/cdev.h>
#include <linux/semaphore.h>

//定义了四个字符串常量,分别用来描述虚拟硬件设备 freg 在设备文件系统中的名称
#define FREG_DEVICE_NODE_NAME  "freg"
#define FREG_DEVICE_FILE_NAME  "freg"
#define FREG_DEVICE_PROC_NAME  "freg"
#define FREG_DEVICE_CLASS_NAME "freg"

// 描述虚拟硬件设备freg
struct fake_reg_dev 
{
    // 描述一个虚拟寄存器
    int val;
    // 一个信号量, 用来同步访问虚拟寄存器 val
    struct semaphore sem;
    // 一个标准的Linux字符设备结构体变量, 用来标志该虚拟硬件设备 freg 的类型为字符设备
    struct cdev dev;
};

#endif

freg.c 实现文件 :

// kernel\goldfish\drivers\freg\freg.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>

#include "freg.h"

/* 主设备号 */
static int freg_major = 0;
/* 从设备号变量 */
static int freg_minor = 0;

/* 设备类别 */
static struct class* freg_class = NULL;
/* 设备变量 */
static struct fake_reg_dev* freg_dev = NULL;

/* 传统的设备文件操作方法 */
static int freg_open(struct inode* inode, struct file* filp);
static int freg_release(struct inode* inode, struct file* filp);
static ssize_t freg_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos);
static ssize_t freg_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos);

/* 传统的设备文件操作方法表 */
static struct file_operations freg_fops = {
        .owner = THIS_MODULE,
        .open = freg_open,
        .release = freg_release,
        .read = freg_read,
        .write = freg_write,
};

/* devfs 文件系统的设备属性操作方法 */
static ssize_t freg_val_show(struct device* dev, struct device_attribute* attr,  char* buf);
static ssize_t freg_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count);

/* devfs 文件系统的设备属性 */
static DEVICE_ATTR(val, S_IRUGO | S_IWUSR, freg_val_show, freg_val_store);

/* 打开设备方法 */
static int freg_open(struct inode* inode, struct file* filp) 
{
    struct fake_reg_dev* dev;

    //将自定义设备结构体保存在文件指针的私有数据域中, 以便访问设备时可以直接拿来用
    dev = container_of(inode->i_cdev, struct fake_reg_dev, dev);
    filp->private_data = dev;

    return 0;
}

/* 设备文件释放时调用, 空实现 */
static int freg_release(struct inode* inode, struct file* filp) 
{
    return 0;
}

/* 读取设备的寄存器val的值 */
static ssize_t freg_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos) 
{
    ssize_t err = 0;
    struct fake_reg_dev* dev = filp->private_data;

    /* 同步访问 */
    if(down_interruptible(&(dev->sem))) 
    {    
        return -ERESTARTSYS;
    }

    //检查类型是否一致
    if(count < sizeof(dev->val)) 
    {
        goto out;
    }

    /* 将寄存器val的值复制到用户提供的缓冲区中 */
    if(copy_to_user(buf, &(dev->val), sizeof(dev->val))) 
    {
        err = -EFAULT;
        goto out;
    }

    err = sizeof(dev->val);

out:
    // 撤销同步
    up(&(dev->sem));

    return err;
}

/* 写设备的寄存器val的值 */
static ssize_t freg_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos) 
{
    struct fake_reg_dev* dev = filp->private_data;
    ssize_t err = 0;

    /* 同步访问 */
    if(down_interruptible(&(dev->sem))) 
    {
        return -ERESTARTSYS;
    }

    //检查类型是否一致
    if(count != sizeof(dev->val)) 
    {
        goto out;
    }

    /* 将用户提供的缓冲区的值写到设备寄存器中 */
    if(copy_from_user(&(dev->val), buf, count)) 
    {
        err = -EFAULT;
        goto out;
    }

    err = sizeof(dev->val);

out:
    up(&(dev->sem));
    return err;
}

/* 将寄存器val的值读取到缓冲区buf中, 内部使用 */
static ssize_t __freg_get_val(struct fake_reg_dev* dev, char* buf) 
{
    int val = 0;

    /* 同步访问 */
    if(down_interruptible(&(dev->sem))) 
    {
        return -ERESTARTSYS;
    }

    val = dev->val;

    up(&(dev->sem));

    return snprintf(buf, PAGE_SIZE, "%d\n", val);
}

/* 把缓冲区buf的值写到设备寄存器val中, 内部使用 */
static ssize_t __freg_set_val(struct fake_reg_dev* dev, const char* buf, size_t count) 
{
    int val = 0;

    /* 将字符串转换为数字 */
    val = simple_strtol(buf, NULL, 10);

    /* 同步访问 */
    if(down_interruptible(&(dev->sem))) 
    {
        return -ERESTARTSYS;
    }

    dev->val = val;
    up(&(dev->sem));

    return count;
}

/* 读设备属性val的值 */
static ssize_t freg_val_show(struct device* dev, struct device_attribute* attr, char* buf) 
{
    struct fake_reg_dev* hdev = (struct fake_reg_dev*)dev_get_drvdata(dev);

    return __freg_get_val(hdev, buf);
}

/* 写设备属性val的值 */
static ssize_t freg_val_store(struct device* dev, 
                              struct device_attribute* attr, 
                              const char* buf, 
                              size_t count) 
{
    struct fake_reg_dev* hdev = (struct fake_reg_dev*)dev_get_drvdata(dev);

    return __freg_set_val(hdev, buf, count);
}

/* 读取设备寄存器 val 的值 , 保存到page 缓冲区中*/
static ssize_t freg_proc_read(char* page, char** start, 
                              off_t off, 
                              int count, 
                              int* eof, 
                              void* data) 
{
    if(off > 0) 
    {
        *eof = 1;
        return 0;
    }

    return __freg_get_val(freg_dev, page);    
}

/* 把缓冲区的值 buff 保存到设备寄存器 val 中 */
static ssize_t freg_proc_write(struct file* filp, 
                               const char __user *buff, 
                               unsigned long len, 
                               void* data) 
{    
    int err = 0;
    char* page = NULL;

    if(len > PAGE_SIZE) 
    {
        printk(KERN_ALERT"The buff is too large: %lu.\n", len);
        return -EFAULT;
    }

    page = (char*)__get_free_page(GFP_KERNEL);
    if(!page) 
    {
        printk(KERN_ALERT"Failed to alloc page.\n");
        return -ENOMEM;
    }

    /* 先把用户提供的缓冲区的值复制到内核缓冲区中 */
    if(copy_from_user(page, buff, len)) 
    {
        printk(KERN_ALERT "Failed to copy buff from user.\n");
        err = -EFAULT;

        goto out;
    }

    err = __freg_set_val(freg_dev, page, len);

out:
    free_page((unsigned long)page);
    return err;    
}

/* 创建 /proc/freg 文件 */
static void freg_create_proc(void) 
{
    struct proc_dir_entry* entry;

    entry = create_proc_entry(FREG_DEVICE_PROC_NAME, 0, NULL);
    if(entry) 
    {
        entry->owner = THIS_MODULE;
        entry->read_proc = freg_proc_read;
        entry->write_proc = freg_proc_write;
    }
}

/* 删除 /proc/freg 文件 */
static void freg_remove_proc(void) 
{
    remove_proc_entry(FREG_DEVICE_PROC_NAME, NULL);
}

/* 初始化设备 */
static int  __freg_setup_dev(struct fake_reg_dev* dev) 
{
    int err;
    dev_t devno = MKDEV(freg_major, freg_minor);

    memset(dev, 0, sizeof(struct fake_reg_dev));

    /* 初始化字符设备 */
    cdev_init(&(dev->dev), &freg_fops);
    dev->dev.owner = THIS_MODULE;
    dev->dev.ops = &freg_fops;

    /* 注册字符设备 */
    err = cdev_add(&(dev->dev), devno, 1);
    if(err) 
    {
        return err;
    }    

    /* 初始化信号量 */
    init_MUTEX(&(dev->sem));
    /* 初始化寄存器val的值 */
    dev->val = 0;

    return 0;
}

/* 模块加载方法 */
static int __init freg_init(void) 
{ 
    int err = -1;
    dev_t dev = 0;
    struct device* temp = NULL;

    printk(KERN_ALERT"Initializing freg device.\n");

    /* 动态分配主设备号 和 从设备号 */
    err = alloc_chrdev_region(&dev, 0, 1, FREG_DEVICE_NODE_NAME);
    if(err < 0) 
    {
        printk(KERN_ALERT"Failed to alloc char dev region.\n");
        goto fail;
    }

    freg_major = MAJOR(dev);
    freg_minor = MINOR(dev);

    /* 分配freg设备结构体 */
    freg_dev = kmalloc(sizeof(struct fake_reg_dev), GFP_KERNEL);
    if(!freg_dev) 
    {
        err = -ENOMEM;
        printk(KERN_ALERT"Failed to alloc freg device.\n");
        goto unregister;
    }

    /* 初始化设备 */
    err = __freg_setup_dev(freg_dev);
    if(err) 
    {
        printk(KERN_ALERT"Failed to setup freg device: %d.\n", err);
        goto cleanup;
    }

    /* 在/sys/class/ 目录下创建设备类别目录freg */
    freg_class = class_create(THIS_MODULE, FREG_DEVICE_CLASS_NAME);
    if(IS_ERR(freg_class)) 
    {
        err = PTR_ERR(freg_class);
        printk(KERN_ALERT"Failed to create freg device class.\n");
        goto destroy_cdev;
    }

    /* 在 /dev/ 目录 和 /sys/class/freg目录下分别创建设备文件frag */
    temp = device_create(freg_class, NULL, dev, "%s", FREG_DEVICE_FILE_NAME);
    if(IS_ERR(temp)) 
    {
        err = PTR_ERR(temp);
        printk(KERN_ALERT"Failed to create freg device.\n");
        goto destroy_class;
    }

    /* 在 /sys/class/freg/freg 目录下创建属性文件val */
    err = device_create_file(temp, &dev_attr_val);
    if(err < 0) 
    {
        printk(KERN_ALERT"Failed to create attribute val of freg device.\n");
        goto destroy_device;
    }

    dev_set_drvdata(temp, freg_dev);

    /* 创建 /proc/freg文件 */
    freg_create_proc();

    printk(KERN_ALERT"Succedded to initialize freg device.\n");

    return 0;

destroy_device:
    device_destroy(freg_class, dev);
destroy_class:
    class_destroy(freg_class);
destroy_cdev:
    cdev_del(&(freg_dev->dev));    
cleanup:
    kfree(freg_dev);
unregister:
    unregister_chrdev_region(MKDEV(freg_major, freg_minor), 1);    
fail:
    return err;
}

/* 模块卸载方法 */
static void __exit freg_exit(void) 
{
    dev_t devno = MKDEV(freg_major, freg_minor);

    printk(KERN_ALERT"Destroy freg device.\n");

    /* 删除 /proc/freg 文件 */
    freg_remove_proc();

    /* 注销设备类别 和 设备 */
    if(freg_class) 
    {
        device_destroy(freg_class, MKDEV(freg_major, freg_minor));
        class_destroy(freg_class);
    }

    /* 删除字符设备 和 释放设备内存 */
    if(freg_dev) 
    {
        cdev_del(&(freg_dev->dev));
        kfree(freg_dev);
    }

    /* 释放设备号资源 */
    unregister_chrdev_region(devno, 1);
}

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Fake Register Driver");

module_init(freg_init);
module_exit(freg_exit);

修改驱动编译文件

Kconfig 编译选项配置文件 :

# kernel\goldfish\drivers\freg\Kconfig
# 执行 make menuconfig 命令来设置这些编译选项

config FREG
    tristate "Fake Register Driver"
    default n
    help
    This is the freg driver for android system.

驱动程序 一般有三种方式来编译。

第一种方式是直接内建在内核中; 第二种方式是编译成内核模块; 第三种方式是不编译到内核中。

默认的编译方式为 n, 就是 不编译到内核中, 因此, 在编译驱动程序 之前, 我们需要执行 make menuconfig 命令来修改它的编译选项, 以便可以将 驱动程序 内建到内核中 或 以模块的方式来编译。

Makefile 编译脚本文件 :

# kernel\goldfish\drivers\freg\Makefile

#  $(CONFIG_FREG) 是一个变量, 它的值与驱动程序freg的编译选项有关

#如果选择将驱动程序freg内建到内核中, 那么变量$(CONFIG_FREG) 的值为y; 
# 如果选择以模块的方式来编译驱动程序freg, 那么变量$(CONFIG_FREG) 的值为m; 
# 如果变量$(CONFIG_FREG) 的值既不为y, 也不为m,那么驱动程序freg就不会被编译

obj-$(CONFIG_FREG) += freg.o

修改内核编译文件

修改内核Kconfig文件 :

在默认情况下,在执行make menuconfig命令配置内核编译选项时, 编译系统是无法找到 对应的驱动Kconfig文件的。 所以需要修改内核的根Kconfig文件, 使得编译系统能够找到驱动程序freg的Kconfig文件

当执行make menuconfig命令时, 编译系统会读取arch/$(ARCH) 目录下的Kconfig文件, 其中, $(ARCH) 指向编译的目标CPU体系架构

一般我们都会将$(ARCH) 的值设置为arm, 因此, 就需要修改arch/arm目录下的Kconfig文件, 使得编译系统可以找到驱动程序freg的Kconfig文件。

Arm架构下:

# arch/arm/Kconfig

menu "Device Drivers"

# 将驱动程序freg的Kconfig文件包含进来
source "drivers/freg/Kconfig"
source "drivers/base/Kconfig"
source "drivers/connector/Kconfig"

# ...

endmenu

x86体系架构下的Kconfig文件 :

# drivers/Kconfig

# 把drivers目录下的Kconfig文件包含进去
source "drivers/Kconfig"

endmenu

修改内核Makefile文件 :

在默认情况下, 在执行make命令编译内核时, 编译系统是无法找到这个Makefile文件的。 这时候, 就需要修改drivers目录下的Makefile文件, 使得编译系统能够找到驱动程序freg的Makefile文件

# drivers/Makefile

# 当 make 编译内核时,编译系统就会对驱动程序 freg 进行编译
obj-$(CONFIG_FREG)        += freg/
obj-y                += gpio/
obj-$(CONFIG_PCI)        += pci/
# ...

编译内核驱动程序模块

在编译驱动程序freg之前, 我们需要执行make menuconfig命令来配置它的编译方式

make menuconfig

第一个配置界面中用上下箭头键选择“Device Drivers”项, 按Enter键

第二个配置界面中继续用上下箭头键选择“Fake Register Driver”项, 按Y键或 M键, 就可以看到选项前面方括号中的字符变成“ * ”或者“M”符号, 它们分别表示将驱动程序freg编译到内核中 或 以模块的方式来编译

如果我们要以模块的方式来编译驱动程序freg, 那就必须在第一个配置界面中选择“Enable loadable module support”选项, 并且按 Y 键将它的值设置为true, 就是让内核可以支持动态加载模块 .

如果要使得内核支持动态卸载模块, 那么就要在第一个配置界面中选择“Enable loadable module support”选项中的子选项“Module unloading ”, 并且按Y键将它的值设置为true。

第二个配置界面中按M 键来配置“Fake RegisterDriver”选项配置完成后, 保存编译配置选项, 退出make menuconfig命令

执行make命令来编译驱动程序freg

make

驱动程序freg编译成功 :

image-20200713165801391

编译得到的内核镜像文件zImage保存在arch/arm/boot目录下

验证内核驱动程序模块

通过proc文件系统和devfs文件系统来验证它的功能是否正确

# 使用 得到的内核镜像文件zImage来启动Android模拟器
emulator -kernel kernel/goldfish/arch/arm/boot/zImage &

# # 用adb工具连接上
adb shell

# 进入 /dev目录下
cd dev

# 查看 一个设备文件freg 
# 存在,说明成功的注册到设备文件系统中
ls freg
# 进入到/proc
cd proc

# 读取文件freg的内容
cat freg

# 往文件freg中写入一个新的内容
echo '5' > freg

# 将文件freg的内容读取出来
cat freg

# 值一样 , 说明 proc文件系统接口成功
# 进入到/sys/class/freg/freg
cd sys/class/freg/freg

# 读取val文件的内容
cat val

# 往文件val中写入一个新的内容
echo '0' > freg

# 将文件val中的内容读取出
cat freg

# 值一样 , 说明 devfs文件系统接口成功

开发C可执行程序验证Android硬件驱动程序

通过编写一个C可执行程序来验证驱动程序freg所提供的dev文件系统接口的正确性, 这是通过调用read和write函数读写设备文件/dev/freg的内容来实现

在Android源代码工程环境中, 不仅可以用C/C++语言来开发可执行程序,还可以开发动态链接库, 也就是 so文件。

使用adb工具命令连接上Android模拟器之后, 进入到/system/bin或者/system/lib目录中, 就可以看到很多可执行程序或者动态链接库文件。

在Android源代码工程环境中开发的C可执行程序源文件一般保存在external目录中,因此, 需要进入到external目录中, 并且创建一个freg目录, 用来保存我们将要开发的C可执行程序源文件。

目录结构 :

~/Android
    exiternal
        freg
            freg.c    # 源文件
            Android.mk # 编译脚本文件

源文件 freg.c :

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

#define FREG_DEVICE_NAME "/dev/freg"

int main(int argc, char** argv)
{
    int fd = -1;
    int val = 0;

    // 以读写方式打开设备文件/dev/freg
    fd = open(FREG_DEVICE_NAME, O_RDWR);
    if(fd == -1)
    {
        printf("Failed to open device %s.\n", FREG_DEVICE_NAME);
        return -1;
    }

    printf("Read original value:\n");

    // 读取它的内容, 即读取虚拟硬件设备 freg 的寄存器 val 的内容
    read(fd, &val, sizeof(val));
    // 打印出来
    printf("%d.\n\n", val);

    val = 5;
    printf("Write value %d to %s.\n\n", val, FREG_DEVICE_NAME);
    // 将一个整数 5 写入到虚拟硬件设备 freg 的寄存器 val 中
    write(fd, &val, sizeof(val));

    printf("Read the value again:\n");

    // 读取它的内容, 即读取虚拟硬件设备 freg 的寄存器 val 的内容
    read(fd, &val, sizeof(val));
    // 打印
    printf("%d.\n\n", val);

    close(fd);
    return 0;
}

编译脚本文件Android.mk :

# 将编译结果保存在 out/target/product/gerneric/system/bin 目录中
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
# 源文件
LOCAL_MODULE := freg
LOCAL_SRC_FILES := $(call all-subdir-c-files)

# 当前要编译的是一个可执行应用程序模块
include $(BUILD_EXECUTABLE)
# 编译
mmm ./external/freg/
# 打包这个C可执行程序
make snod

编译成功后, 就可以在out/target/product/gerneric/system/bin目录下看到一个freg文件;

打包成功后, 编译好的文件就会包含在out/target/product/gerneric目录下的Android系统镜像文件system.img里

# 将得到的 system.img 文件启动 Android模拟器
emulator -kernal kernel/goldfish/arch/arm/boot/zImage &

# adb工具连接上它
adb shell

# 进入到/system/bin目录中
cd system/bin

# 执行里面的freg文件
./freg

# 验证驱动程序freg的dev文件系统访问接口的正确性
基于“xxx” Androidx平台的驱动及系统开发 之 触摸板篇 触摸IC 驱动适配及遇到的问题并解决 详情 阅读详情

相关推荐

android平台驱动开发(一)

最简单的hello world 驱动

墨染天玑的博客 2855

android驱动开发从零到一

驱动c文件放在编译好的安卓源码里,放在其本身有的驱动一起的位置,所有驱动一起编译并构建镜像

icedrunker的博客 1万+

Android 驱动开发:深入底层,驾驭硬件,构建系统基石

本文系统介绍了Android驱动开发的关键技术,包括Linux内核机制、设备驱动模型、设备树配置等核心内容。重点剖析了显示、摄像头、音频等复杂模块的驱动框架,并提供了调试技巧、性能优化策略和稳定性保障方法。文章还精选了典型面试问题,考察驱动开发的核心概念和实践经验。随着Android在智能设备中的广泛应用,驱动开发面临异构计算、安全增强等新挑战,需要工程师持续深化技术积累,掌握前沿工具和方法。本文为Android驱动开发者提供了全面的技术参考和实践指南。

郑伟强dev的专栏 281

基于RK3288/RK3566 android 平台驱动开发

 安卓驱动开发,涉及dts相关kernel驱动开发,以及android,apk如何调用驱动,涉及到硬件访问服务,以及JNI访问驱动相关内容,学习安卓首先RK3288/RK3566。能更快的对安卓驱动框架有个大致的了解。

RK3568平台Android显示驱动硬件抽象层开发指南

通过实现HWC接口,并根据RK3568平台的硬件特性进行相应的修改,你可以实现高效的图形合成和进行相应的修改,你可以实现高效的图形合成和显示,提供流畅的用户体验。你需要实现该接口,并根据RK3568平台的硬件特性进行相应的修改。其次,了解RK3568平台的硬件架构和显示子系统的相关信息,包括显示控制器、显示模式和分辨率等。HWC是Android系统中的一个关键组件,它位于图形栈的顶层,负责处理图形缓冲区的合成和显示。HWC与硬件层交互,将应用程序的图形输出合成为最终的显示图像,并将其传递给显示器进行显示。

CodeGu的博客 456

深入剖析 Android 驱动开发工程师:从硬件接口到系统服务的桥梁

本文系统阐述了Android驱动开发工程师的职责、技术要求和开发流程。核心职责包括Linux驱动/HAL开发测试、硬件协同设计和问题调试。关键技术涵盖ARM架构、C/C++、Linux内核原理、设备树和硬件基础。文章详细解析了从设备树配置到驱动开发、HAL实现的完整流程,并提供了调试技巧和面试问题示例。随着5G、AI和汽车电子发展,该岗位需持续跟进新技术,强调理论基础与实践能力的结合。本文为Android驱动开发者提供了全面的技术参考和发展指引。

郑伟强dev的专栏 97

android平台led开发之内核硬件驱动层

手上有一个android开发平台,于是想在底层加个自己的硬件模块然后通过Android的HAL方式向上封装出底层驱动供JAVA APP调用。既然是刚上手,那就用led灯来做为先导开发走走流程吧,至少得把从内核到应用层的流程走通才行。     从底层到上层一共分成四个阶段。 1: 内核硬件驱动层 2: HAL(硬件抽象层) 3: 框架层(framework,包含JNI和实现硬件服务的JA

651

Dify平台+Excel批量分析实战:5分钟搞定大模型数据处理(附完整代码)

本文详细介绍了如何利用Dify平台的数据集功能,结合Excel和Python脚本,快速构建大模型批量数据处理流水线。通过实战案例演示,读者可在5分钟内完成客户反馈情感分析、产品文案生成等任务,实现从原始数据到结构化洞察的高效转化,极大提升工作效率。

3a9bq4r8t2y的博客 318

Android系统的硬件驱动程序开发流程 通过 proc文件系统, devfs文件系统, C可执行程序验证驱动程序正确性

Android系统的硬件驱动程序开发流程 Android系统的硬件抽象层(Hardware Abstract Layer, HAL) 运行在用户空间中, 它向下屏蔽硬件驱动模块的实现细节, 向上提供硬件访问服务。 Android系统的体系结构 : 依次涉及Android系统的硬件驱动模块、 硬件抽象层、 外部库 和 运行时库层、 应用程序框架层 和 应用程序层等 开发一个应用访问硬件的流程为 : 首先在Android系统的内核空间中为一个硬件开发驱动程序, 接着在用户空间中为该硬件添加一个硬件抽象层

记录点点滴滴 1710

UE5实战技巧:TextRender高效集成中文字体与材质优化

本文针对UE5中TextRender组件显示中文的常见问题,提供了一套完整的实战解决方案。详细讲解了如何通过手动创建字体资产、精准控制字符集来优化性能,并重点介绍了通过调整材质中的Lerp节点参数(如设为-85, 92)来解决中文字体显示残缺和边缘模糊等核心难题,帮助开发者实现高效且美观的中文文本渲染。

weixin_28040125的博客 383

别再手动算频谱了!用这个在线FFT工具,5分钟搞定JSON/CSV数据分析

本文介绍了一款高效的在线FFT工具,帮助用户快速分析JSON/CSV格式的时域数据,5分钟内完成频谱分析。通过傅里叶变换(FFT)算法,大幅提升计算效率,解决传统手动计算的痛点,适用于工业振动分析、设备故障诊断等场景。

weixin_29172201的博客 264

嵌入式Linux安卓驱动开发:从硬件到CTS认证的真机实战路径

嵌入式Linux驱动开发是面向资源受限、实时性敏感、硬件强耦合系统的底层工程实践;其核心原理涵盖内核中断机制、内存映射管理、DMA一致性及跨层调试方法;技术价值在于打通硬件原理图→Device Tree→Kernel驱动→Android HAL→Framework调用的全链路交付能力;典型应用场景包括智能座舱显示驱动、车载传感器接入、AIoT边缘设备适配等;而安卓驱动开发因其强制叠加HAL抽象、Binder通信与CTS认证要求,已成为当前芯片原厂与终端厂商嵌入式岗位的主流技术栈。本文聚焦RK3399平台真实

weixin_29168675的博客 95

Altium Designer差分走线实战:从规则设置到阻抗匹配的完整流程

本文详细讲解了在Altium Designer中进行差分走线的完整实战流程,涵盖从原理图定义差分对、PCB布局管理、关键设计规则设置,到利用阻抗计算工具确定线宽与间距,以及布线技巧与等长处理等核心步骤。文章旨在帮助工程师掌握高速PCB设计中差分信号传输的关键技术,确保信号完整性并实现精确的阻抗匹配。

weixin_29163857的博客 259

深入理解安卓HAL层:硬件抽象层开发与驱动适配指南

安卓硬件抽象层(HAL)是连接系统框架与硬件驱动的关键纽带,它为安卓系统提供了标准化的硬件访问接口,同时保护硬件厂商的知识产权。本文将从架构解析、开发实践到驱动适配,全面讲解HAL层的核心技术与应用方法,帮助开发者快速掌握安卓硬件抽象层的开发精髓。 ## 一、安卓HAL层的核心架构与作用 安卓系统架构中,HAL层位于Linux内核与框架层之间,扮演着"翻译官"的角色。它将硬件厂商的私有驱动实现

gitblog_00525的博客 433

OpenClaw 股票自动化交易实践指南:从原理到实战的完整技术路径

量化交易策略的开发是一个系统工程,需要综合运用金融知识、编程技能和数据分析能力。OpenClaw通过引入提示词工程技术,大大降低了策略开发的技术门槛,使得更多投资者能够参与到量化交易中来。但技术只是工具,真正决定策略成败的是对市场的深刻理解和严格的风险管理。本章系统介绍了OpenClaw环境下的策略开发方法,从提示词驱动到传统代码实现,从参数优化到回测验证,涵盖了策略开发的完整流程。提示词驱动方式适合快速验证交易想法,传统代码方式适合实现复杂的定制逻辑,两种方式可以协同使用,发挥各自的优势。

lovely_yoshino的博客 5991

Davinci配置临界区Exclusive Area

在Exclusive Area专属区域内的代码段执行不会被中断,可以用于保护多个Runnable间的共享资源。Exclusive Area会对RTE的调度策略和配置进行约束。如果两个或多个Runnable指向同一个Exclusive Area,那么只有其中一个Runnable被允许在该Exclusive Area内执行。即这些Runnable在ExlcusiveArea内执行时不能并发运行(相互抢占)。:在完整执行期间,整个runnable都在临界区运行。:执行期间可以进入临界区。

qq_42851449的博客 511

android 写执行函数测试驱动,Android 字符驱动#0#开发硬件驱动程序和c测试程序

开发Android硬件驱动程序程序和C验证程序开发坏境本地在在6.x的环境上调试Android版本号:mtk_6737_6.xLinux内核 版本号:Linux version 3.18.19+ ,arm64 //cat /proc/version开发Androd硬件驱动实现内核驱动程序模块驱动文件包括三个文件:Kconfig,Makefile,hello.chello.c#include #in...

weixin_39568232的博客 292

国科大学习资料--图像处理(彭思龙、杨戈)--2020年期末考试题解析(学长整理分析)

在对20年卷子分析前,先看看19年卷子的解析(后面有20年的变化,由于从核心课调整到了普及课,20年的卷子相对简单,但是还是有很多细节) 解析:(字写的不好别嫌弃!!!!) 20年的卷子 第一题:和19年基本一致,但是加了高斯核计算费用,要算一个节省计算成本比率,所以去多看看PPT 第二题:和19年基本一致,加了证明两个高斯卷积仍然是高斯卷积,相当于19年题2和3结合 第三题:采样定理,和19年基本一致,但要注意加了证明 第四题:考了形态学提取图像边界 第五题:直方图,和19年一

无名何许人的博客 4364

实测AI辅助安卓硬件驱动开发:RK3399 I2C传感器驱动全记录

Linux内核驱动开发是嵌入式系统工程中的核心环节,尤其是I2C设备驱动的编写,涉及内核接口选型、设备树配置、权限管理等多个层次,对准确性和稳定性要求极高。随着AI编码工具的兴起,工程师开始探索其能否在如此严谨的领域中发挥作用。以RK3399安卓平台上的SHT30温湿度传感器为例,完整记录使用DeepSeek v4.1 flash辅助开发I2C驱动、打通SELinux权限、并最终实现用户态读取数据的全过程。实测显示,AI能高效生成驱动框架和定位常见编译错误,但也容易在新旧内核API差异、位运算细节、设备树c

weixin_28716769的博客 133

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

嵌入式Linux安卓驱动开发实战:从内核到应用层打通硬件链路

从底层系统与硬件交互的基础概念切入,阐述操作系统如何通过内核驱动管理外设,设备树描述硬件资源,HAL层解耦上层框架与内核实现。这种分层架构是安卓系统稳定运行的关键,掌握其原理有助于排查从引脚中断到应用层无响应的完整链路。在物联网、智能终端等场景中,涉及按键、传感器、屏幕背光等常见外设的开发,均需要开发者能独立完成驱动、HAL到JNI的工程落地。本文以嵌入式Linux安卓驱动实战项目为例,详细拆解字符设备框架、平台总线、中断处理、SELinux配置等技术要点,帮助读者构建系统级视图,提升底层开发与工程调试能力

weixin_33858485的博客 475

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

安卓驱动开发之-(二)硬件访问服务_框架

安卓驱动中怎么实现硬件访问服务? 一、先从安卓源码中分析一下振动器服务的的流程。 (1)、打开SystemServer.java (\frameworks\base\services\java\com\android\server) 该函数主要注册了一个JNI的本地方法。 (2)、打开Onload.cpp (\frameworks\base\services\core\jni)

昇威技术 678

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

安卓进阶二:硬件抽象层HAL之开发安卓硬件驱动程序及测试

本文是学习罗升阳大佬的《安卓系统源代码情景分析》做的笔记和总结 一、

我的安卓之路 567

s5p4418 Android 4.4.2 驱动层 HAL层 服务层 应用层 开发流程记录(一 硬件驱动层)

本文章是记录Android开发中驱动层、HAL层、应用层之间的关系,以及其开发方法,本文将会以实现LED的控制为例来进行记录。 一是可以给以后自己做开发做参考,二是希望可以帮助正在学习的朋友参考。 一般的app不需要我们去关注hal和驱动,但在设计一个硬件系统时,原生的Android并未提供合适的服务,所以我们才需要去了解这个流程。由于也是刚入门,很多还不太懂,朋友们有什么疑问可以留言。

sean 4927

Android应用开发 led 驱动层 hal硬件抽象层 应用层 详细教程记录(含源码)

本篇文章是为了能够更好的搜索到介绍驱动到应用的详细介绍文章。 关于驱动层 hal硬件抽象层 应用层请参考s5p4418 Android 4.4.2 驱动层 HAL层 服务层 应用层 开发流程记录系列,总四篇文章,以及Android 4.4.2 动态添加JNI库方法记录系列,总两篇文章。 s5p4418 Android 4.4.2 驱动层 HAL层 服务层 应用层 开发流程记录(一

sean 3312
上一篇: 安卓硬件访问服务
下一篇: C/C++日志写入接口
onnx
博客等级 码龄5年 1万+粉丝 416原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

onnx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值