👹
Carlos's Tech Blog
  • 🧔ECUs
    • ZYNQ_Documents
      • [ZYNQ] 构建ZYNQ的BSP工程
      • [ZYNQ] 启动流程
      • [ZYNQ] Secure Boot Flow
      • [ZYNQ] Provisioning Guideline
      • [ZYNQ] Decrypting Partition by the Decrypt Agent Using PUF key
      • [ZYNQ] enabling the cryptsetup on ramdisk
      • [ZYNQ] Encrypt external files based on file system using PUF key
      • [ZYNQ] Loading an Encrypted Linux kernel at U-Boot with a KUP Key
      • [ZYNQ] cross-compile the cryptsetup on Xilinx ZYNQ aarch64 platform
      • [ZYNQ] Linux Linaro系统镜像制作SD卡启动
    • S32G_Documents
      • [S32G] Going through the s32g hard/soft platform
      • [S32G] S32g247's Secure Boot using HSE firmware
        • S32g2 HSE key config
        • How S32g verify secure boot image
        • S32g secure boot signature generation
        • How to download and build S32g Secure boot image
        • [S32G] OTA with Secure Boot
    • RT117x_Documents
      • [RT-117x]IMX RT1170 Provisioning Guideline
      • [RT-117x] Going through the MX-RT1170 hard/soft platform
      • [RT-117x] i.MX-RT1170's Secure Boot
        • [RT-117x]Signing image with the HSM (SignServer)
    • LS104x_Documents
      • [LS104x] bsp project
      • [LS104x] boot flow
      • [LS104x] secure boot
      • [LS104x] Application Note, Using the PKCS#11 in TCU platform
      • [LS104x] 使用ostree更新rootfs
      • [LS104x] ostree的移植
      • [LS104x] Starting with Yocto
      • [LS104x] 使用FIT的kernel格式和initramfs
    • IMX6/8_Documents
      • [IMX6] Defining A U-Boot Command
      • NXP IMX6 嵌入式板子一些笔记
      • NXP-imx6 initialization
    • Vehicle_Apps
      • [SecOC] Tree
        • [SecOC] SecOC Freshness and MAC Truncation
  • 😾TECH
    • Rust Arm OS
      • ARMv7m_Using_The_RUST_Cross_Compiler
    • ARM
      • ARM-v7-M
        • 01_ARMv7-M_处理器架构技术综述
        • 02_ARMv7-M_编程模型与模式
        • 03_ARMv7-M_存储系统结构
        • 04_ARMv7-M_异常处理及中断处理
      • ARM-v8-A
        • 02_ARMv8_基本概念
        • 03_ARMv8_指令集介绍_加载指令集和存储指令集
        • 04_ARMv8_指令集_运算指令集
        • 05_ARMv8_指令集_跳转_比较与返回指令
        • 06_ARMv8_指令集_一些重要的指令
        • 0X_ARMv8_指令集_基于汇编的UART驱动
        • 07_ARMv8_汇编器Using as
        • 08_ARMv8_链接器和链接脚本
        • 09_ARMv8_内嵌汇编(内联汇编)Inline assembly
        • 10_ARMv8_异常处理(一) - 入口与返回、栈选择、异常向量表
        • 11_ARMv8_异常处理(二)- Legacy 中断处理
        • 12_ARMv8_异常处理(三)- GICv1/v2中断处理
        • 13_ARMv8_内存管理(一)-内存管理要素
        • 14_ARMv8_内存管理(二)-ARM的MMU设计
        • 15_ARMv8_内存管理(三)-MMU恒等映射及Linux实现
        • 16_ARMv8_高速缓存(一)cache要素
        • 17_ARMv8_高速缓存(二)ARM cache设计
        • 18_ARMv8_高速缓存(三)多核与一致性要素
        • 19_ARMv8_TLB管理(Translation Lookaside buffer)
        • 20_ARMv8_barrier(一)流水线和一致性模型
        • 21_ARMv8_barrier(二)内存屏障案例
      • ARM Boot Flow
        • 01_Embedded_ARMv7/v8 non-secure Boot Flow
        • 02_Embedded_ARMv8 ATF Secure Boot Flow (BL1/BL2/BL31)
        • 03_Embedded_ARMv8 BL33 Uboot Booting Flow
      • ARM Compiler
        • Compiler optimization and the volatile keyword
      • ARM Development
        • 在MACBOOK上搭建ARMv8架构的ARM开发环境
        • Starting with JLink debugger or QEMU
    • Linux
      • Kernel
        • 0x01_LinuxKernel_内核的启动(一)之启动前准备
        • 0x02_LinuxKernel_内核的启动(二)SMP多核处理器启动过程分析
        • 0x21_LinuxKernel_内核活动(一)之系统调用
        • 0x22_LinuxKernel_内核活动(二)中断体系结构(中断上文)
        • 0x23_LinuxKernel_内核活动(三)中断体系结构(中断下文)
        • 0x24_LinuxKernel_进程(一)进程的管理(生命周期、进程表示)
        • 0x25_LinuxKernel_进程(二)进程的调度器的实现
        • 0x26_LinuxKernel_设备驱动(一)综述与文件系统关联
        • 0x27_LinuxKernel_设备驱动(二)字符设备操作
        • 0x28_LinuxKernel_设备驱动(三)块设备操作
        • 0x29_LinuxKernel_设备驱动(四)资源与总线系统
        • 0x30_LinuxKernel_设备驱动(五)模块
        • 0x31_LinuxKernel_内存管理(一)物理页面、伙伴系统和slab分配器
        • 0x32_LinuxKernel_内存管理(二)虚拟内存管理、缺页与调试工具
        • 0x33_LinuxKernel_同步管理_原子操作_内存屏障_锁机制等
        • 01_LinuxDebug_调试理论和基础综述
      • Userspace
        • Linux-用户空间-多线程与同步
        • Linux进程之间的通信-管道(上)
        • Linux进程之间的通信-管道(下)
        • Linux进程之间的通信-信号量(System V)
        • Linux进程之间的通信-内存共享(System V)
        • Linux进程之间的通信-消息队列(System V)
        • Linux应用调试(一)方法、技巧和工具 - 综述
        • Linux应用调试(二)工具之coredump
        • Linux应用调试(三)工具之Valgrind
        • Linux机制之内存池
        • Linux机制之对象管理和引用计数(kobject/ktype/kset)
        • Linux机制copy_{to, from}_user
        • Linux设备树 - DTS语法、节点、设备树解析等
        • Linux System : Managing Linux Services - inittab & init.d
        • Linux System : Managing Linux Services - initramfs
      • Kernel Examples
        • Linux Driver - GPIO键盘驱动开发记录_OMAPL138
        • 基于OMAPL138的Linux字符驱动_GPIO驱动AD9833(一)之miscdevice和ioctl
        • 基于OMAPL138的Linux字符驱动_GPIO驱动AD9833(二)之cdev与read、write
        • 基于OMAPL138的字符驱动_GPIO驱动AD9833(三)之中断申请IRQ
        • Linux内核调用SPI驱动_实现OLED显示功能
        • Linux内核调用I2C驱动_驱动嵌套驱动方法MPU6050
    • OPTEE
      • 01_OPTEE-OS_基础之(一)功能综述、简要介绍
      • 02_OPTEE-OS_基础之(二)TrustZone和ATF功能综述、简要介绍
      • 03_OPTEE-OS_系统集成之(一)编译、实例、在QEMU上执行
      • 05_OPTEE-OS_系统集成之(三)ATF启动过程
      • 06_OPTEE-OS_系统集成之(四)OPTEE镜像启动过程
      • 07_OPTEE-OS_系统集成之(五)REE侧上层软件
      • 08_OPTEE-OS_系统集成之(六)TEE的驱动
      • 09_OPTEE-OS_内核之(一)ARM核安全态和非安全态的切换
      • 10_OPTEE-OS_内核之(二)对安全监控模式的调用的处理
      • 11_OPTEE-OS_内核之(三)中断与异常的处理
      • 12_OPTEE-OS_内核之(四)对TA请求的处理
      • 13_OPTEE-OS_内核之(五)内存和cache管理
      • 14_OPTEE-OS_内核之(六)线程管理与并发
      • 15_OPTEE-OS_内核之(七)系统调用及IPC机制
      • 16_OPTEE-OS_应用之(一)TA镜像的签名和加载
      • 17_OPTEE-OS_应用之(二)密码学算法和安全存储
      • 18_OPTEE-OS_应用之(三)可信应用的开发
      • 19_OPTEE-OS_应用之(四)安全驱动开发
      • 20_OPTEE-OS_应用之(五)终端密钥在线下发系统
    • Binary
      • 01_ELF文件_目标文件格式
      • 02_ELF文件结构_浅析内部文件结构
      • 03_ELF文件_静态链接
      • 04_ELF文件_加载进程虚拟地址空间
      • 05_ELF文件_动态链接
      • 06_Linux的动态共享库
      • 07_ELF文件_堆和栈调用惯例以ARMv8为例
      • 08_ELF文件_运行库(入口、库、多线程)
      • 09_ELF文件_基于ARMv7的Linux系统调用原理
      • 10_ELF文件_ARM的镜像文件(.bin/.hex/.s19)
    • Build
      • 01_Script_makefile_summary
    • Rust
      • 02_SYS_RUST_文件IO
    • Security
      • Crypto
        • 1.0_Security_计算机安全概述及安全需求
        • 2.0_Security_随机数(伪随机数)
        • 3.0_Security_对称密钥算法加解密
        • 3.1_Security_对称密钥算法之AES
        • 3.2_Security_对称密钥算法之MAC(CMAC/HMAC)
        • 3.3_Security_对称密钥算法之AEAD
        • 8.0_Security_pkcs7(CMS)_embedded
        • 9.0_Security_pkcs11(HSM)_embedded
      • Tools
        • Openssl EVP to implement RSA and SM2 en/dec sign/verify
        • 基于Mac Silicon M1 的OpenSSL 编译
        • How to compile mbedtls library on Linux/Mac/Windows
    • Embedded
      • eMMC启动介质
  • 😃Design
    • Secure Boot
      • JY Secure Boot Desgin
    • FOTA
      • [FOTA] Module of ECUs' FOTA unit design
        • [FOTA] Tech key point: OSTree Deployment
        • [FOTA] Tech key point: repositories role for onboard
        • [FOTA] Tech key point: metadata management
        • [FOTA] Tech key point: ECU verifying and Decrpting
        • [FOTA] Tech key point: time server
      • [FOTA] Local-OTA for Embedded Linux System
    • Provisioning
      • [X-Shield] Module of the Embedded Boards initialization
    • Report
由 GitBook 提供支持
在本页
  • 基于OMAPL138的Linux字符驱动_GPIO驱动AD9833(一)之miscdevice和ioctl
  • 0. 导语
  • 1. 开发驱动综述
  • 2. Linux字符驱动模板
  • 3. AD9833芯片级时序驱动
  • 3. 与驱动通信的ioctl函数
  • 4. 将驱动程序编入Linux内核代码树
  • 源代码下载
  • 参考文献
  1. TECH
  2. Linux
  3. Kernel Examples

基于OMAPL138的Linux字符驱动_GPIO驱动AD9833(一)之miscdevice和ioctl

https://github.com/carloscn/blog/issues/35

基于OMAPL138的Linux字符驱动_GPIO驱动AD9833(一)之miscdevice和ioctl

0. 导语

在嵌入式的道路上寻寻觅觅很久,进入嵌入式这个行业也有几年的时间了,从2011年后半年开始,我清楚的记得当时拿着C51的板子闪烁了LED灯,从那时候开始,就进入到了嵌入式的大门里面。嵌入式的学习从来没有停止过,中间也有无数的插曲和机缘巧合学会C++和Java,做一些好玩的应用。无论是嵌入式DSP也好,还是如今的嵌入式ARM,7年之久从来没有停止过。技术最大的好处就是,**无论发展到什么境地,那种第一次点亮LED灯欣喜永远的可以伴随着你,只要你解决了一个卡了你很久的问题,这就是技术的魅力。**我也将开始大肆的从嵌入式DSP转入到嵌入式Linux,在研究生阶段,完成这个转型。

这个Demo意义重大,使用Linux也有四五年的时间了,Linux良好的基础和嵌入式基础让我在嵌入式inux道路上算的上是顺风顺水。**这个Demo将过去STM32,F28xx的DSP或者那些单片机桥接起来,将过去裸机上的程序全部编到内核里面,通过嵌入式的应用进行互联。 **

本DEMO依然使用AD9833作为例子,将用linux内核级的gpio对AD9833写时序,完成对于AD9833的驱动程序,在嵌入式Linux上生成/dev/目录节点,使用Linux命令行对AD9833产生波形进行控制。(只要有了/dev节点,使用Qt,C++,Python都可以控制了,这就是物联网最注重的。)

效果视频观看地址: https://v.youku.com/v_show/id_XMzY3NDUwNTMwOA==.html?spm=a2h3j.8428770.3416059.1

1. 开发驱动综述

本开发驱动基于Linux3.3内核版本,且内核必须编译正确,否则不能运行。 这个Demo可以归结为三个部分,一个部分为Linux字符驱动模板,第二部分为AD9833驱动程序,第三部分为通信协议。还附加一个配置文件。

  • Linux字符驱动模板主要包含init exit 还有ioctl,函数;

  • AD9833驱动程序为AD9833的GPIO时序(AD9833为SPI协议,这里先用GPIO模拟时序,后续升级为SPI外设);

  • 通信协议格式方式,用户对于AD9833的控制字,比如发送波形命令,频率命令等;

  • 将自己编写的驱动写入内核的代码树,编译成模块或者编译进内核随内核启动;

本Demo就围绕这三点进行。

2. Linux字符驱动模板

* 函数ioctl

主要负责进行数据交互的。当设备生成字符设备驱动节点(/dev目录下),使用shell级命令cat或者编译一段C应用程序用open打开节点的时候,后面将参数就是通过ioctl函数进行传递。(在嵌入式Linux端定义一个ioctl的函数,在C语言的程序也有一个ioctl用来和其进行对应,这样就完成了数据参数传递。)

*结构体file_operations

static int
ad9833_ioctl(struct file  *file, unsigned int cmd, unsigned long arg )
{

	printk(DRV_NAME "\tRecv cmd: %u\n", cmd);
	printk(DRV_NAME "\tRecv arg: %lu\n", arg);
	switch( cmd ) {
	case CMD_TYPE_SIN:
		ad9833->set_wave_freq(ad9833, 1500);
		ad9833->set_wave_type(ad9833, SIN);
		printk( DRV_NAME " set wave is sine wave! arg = %lu\n" , arg );

		break;

	case CMD_TYPE_TRI:
		ad9833->set_wave_freq(ad9833, 1500);
		ad9833->set_wave_type(ad9833, TRI);
		printk( DRV_NAME " set wave is tri wave! arg = %lu\n" , arg );
		break;

	case CMD_TYPE_SQE:
		ad9833->set_wave_freq(ad9833, 1500);
		ad9833->set_wave_type(ad9833, SQU);
		printk( DRV_NAME " set wave is sw wave! arg = %lu\n" , arg );
		break;

	}
	return	0;
}

ioctl函数不能独立的存在需要file_operations指针进行操作,ioctl为一个执行命令的清单,file_operations就是这个清单的执行者。下面就是file_operations的指针,里面的成员需要接收到ad9833_ioctl的函数地址,在内部运行的时候会调用该地址。

static struct file_operations ad9833_fops = {

		.owner				=	THIS_MODULE,
		.unlocked_ioctl  	=  	ad9833_ioctl,
};

###* 结构体miscdevice *miscdevice结构体为字符驱动的一级,字符驱动如同文献[3]所说的一样,非常的凌乱,到底里面使用了miscdevice还是cdev还是platform-device or platform-driver,这里暂时不进行理,这里使用miscdevice级的字符驱动设备向Linux内核进行设备的注册,后续有文章进行区分,类似的文献还有我的《Linux GPIO键盘驱动开发记录_OMAPL138》,这里使用的室platform-device进行。

static struct miscdevice ad9833_miscdev  = {
		// DRV_NAME 在前面进行define
		// #define	DRV_NAME 	"AD9833-ADI"
		.name				=	DRV_NAME,
		.fops				=	&ad9833_fops,
};

可以看见,在miscdev里面指定了file指针的地址,miscdev主要的作用就是向内核注册该驱动。

*函数init

内核级的嵌入式Linux驱动给出的硬性要求进行init函数,并标识init函数为__init,而且还要在module_init中填写init函数的地址。

static int __init ad9833_dev_init( void )
{
	int  i,ret;

	/*
	 * AD9833 new device
	 * */
	printk( DRV_NAME "\tApply memory for AD9833.\n" );
	ad9833 = ad9833_dev_new();

	/*
	 * AD9833 init gpios.
	 * */
	printk( DRV_NAME "\tInititial GPIO\n" );

	for ( i = 0; i < 3; i ++ ) {
		ret	=	gpio_request( ad9833_gpios[i], "AD9833 GPIO" );
		if( ret ) {
			printk("\t%s: request gpio %d for AD9833 failed, ret = %d\n", DRV_NAME,ad9833_gpios[i],ret);
			return ret;
		}else {
			printk("\t%s: request gpio %d for AD9833 set succussful, ret = %d\n", DRV_NAME,ad9833_gpios[i],ret);
		}
		gpio_direction_output( ad9833_gpios[i],1 );
		gpio_set_value( ad9833_gpios[i],0 );
	}

	ret = misc_register( &ad9833_miscdev );
	printk( DRV_NAME "\tinitialized\n" );
	return ret;
}

module_init( ad9833_dev_init );

当我们运行insmod xxxx.ko的时候,此时运行的就是这个init函数,在这个函数中主要完成对于设备内存的请求和一些初始状态的注册。在本DEMO中对对于AD9833的结构体进行了注册,并对gpio进行申请。ret = misc_register( &ad9833_miscdev ); 重点室这句话。

*函数exit

除此之外内核也要求了exit函数,主要进行对init中内存申请的释放。

static void __exit ad9833_dev_exit( void )
{
	int i;
	for( i = 0; i < 3; i++) {
		gpio_free( ad9833_gpios[i] );
	}
	misc_deregister( &ad9833_miscdev );

}
module_exit( ad9833_dev_exit );

这是一个非常简单的字符驱动的模板,然后就需要我们添加AD9833的驱动了。

3. AD9833芯片级时序驱动

到此,基本上就是裸机嵌入式的知识了,对于芯片功能的描述,对于芯片时序的把握。作为本博客不在赘述,给出函数的列表,如果喜欢,本文将DEMO的源码放在后面,自行下载观看。

static void ad9833_set_wave_type( AD9833 *dev, enum ad9833_wavetype_t wave_type );
static void ad9833_set_phase( AD9833 *dev, unsigned int phase_value );
static void ad9833_set_freq( AD9833 *dev, float freq );
static void ad9833_set_para( AD9833 *dev, unsigned long freqs_value, unsigned int phase_value, enum ad9833_wavetype_t wave_type );
static void ad9833_init_device( AD9833 *dev ) ;
static void ad9833_write_reg( AD9833 *dev, unsigned int reg_value );
static int 	ad9833_ioctl(struct file  *file, unsigned int cmd, unsigned long arg );
AD9833 *ad9833;

AD9833 *ad9833_dev_new()
{
	AD9833 *dev = (AD9833*)kcalloc(1, sizeof(AD9833), GFP_ATOMIC);

	dev->hw.fsy			  =	  AD9833_FSY_IO;
	dev->hw.sdi			  =   AD9833_DAT_IO;
	dev->hw.clk			  =	  AD9833_CLK_IO;

	dev->set_wave_para    =   &ad9833_set_para;
	dev->init_device      =   &ad9833_init_device;
	dev->write_reg        =   &ad9833_write_reg;
	dev->set_wave_freq    =   &ad9833_set_freq;
	dev->set_wave_phase	  =   &ad9833_set_phase;
	dev->set_wave_type    =   &ad9833_set_wave_type;
	dev->init_device( dev );


	return dev;
}

该设备使用链表进行描述。

3. 与驱动通信的ioctl函数

在参考文献[1]中,给出了Linux字符设备驱动开发重要的ioctl函数解析,写的很接地气,很朴实,也写的很明白,包括利用ioctl函数应用程序和驱动程序进行交互,ioctl函数使用MAGIC_number幻数对命令进行转换。 在ioctrl函数里面通常使用switch 和case进行执行,见上衣章的内容。 这里给出使用ioctl的应用程序,它和内核驱动进行通信:


#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

#define				AD9833_MAGIC				'k'
#define				CMD_TYPE_SIN				_IO( AD9833_MAGIC, 0)
#define				CMD_TYPE_TRI				_IO( AD9833_MAGIC, 1)
#define				CMD_TYPE_SQE				_IO( AD9833_MAGIC, 2)


const char dev_path[]="/dev/AD9833-ADI";

int main(int argc , char *argv[])
{

    int fd = -1, i = 0;
    printf("ad9833 test program run....\n");


    fd = open(dev_path, O_RDWR|O_NDELAY);  // 打开设备
    if (fd < 0) {
        printf("Can't open /dev/AD9833-ADI\n");
        return -1;
    }

    printf("open device.\n");

    if( strcmp(argv[1],"1") == 0 ) {
	ioctl(fd, CMD_TYPE_SIN, 5);
		printf("argc = %d,sine wave = %s \n", CMD_TYPE_SIN, argv[1]);
    }else if(  strcmp(argv[1],"2") == 0 ) {
		ioctl(fd, CMD_TYPE_TRI, 1);
		printf("argc = %d,tri wave = %s \n", CMD_TYPE_TRI,argv[1]);
    }else{
 		ioctl(fd, CMD_TYPE_SQE, 1);
		printf("argc = %d,sqe wave = %s \n", CMD_TYPE_SQE, argv[1]);
    }
    
    printf("argc = %d\n", argc);
    close(fd);
    return 0;
}

在ioctl函数和嵌入式Linux驱动里面的ioctl函数就会对应,命令就传递过去了。

另外补充一个知识:

void main( int argc char *argv[] )

  • argc 为传递参数的个数

  • argv[1] 为一个字符串,第一个传递进来的字符串,比如 ./main.o nihao hello 1234 argv[1] 就是nihao, argv[2] 就是hello, argv[3] 就是1234

4. 将驱动程序编入Linux内核代码树

驱动开发完毕,就必须要将驱动编入Linux内核代码树,假如Linux内核代码在./linux-3.3目录,我们的驱动名字叫做ad9833.c,那么我们就要将ad9833.c文件放入./linux-3.3/drivers/char目录下,操作两件事情。

修改Kconfig文件

修改Kconfig文件,在menuconfig文件中会出现我们的内核配置选项。

config  AD9833_ADI
        tristate "AD9833 DDS support."
        depends on ARM
        help
          GPIO on OMAPL138 configuration is:
          AD9833_FSY_IO -> GPIO[0,1]
          AD9833_CLK_IO -> GPIO[0,5]
          AD9833_DAT_IO -> GPIO[0,0]
  • tristate: 内核在linux menuconfig菜单下显示的名字

  • depends on ARM: 只有在ARM架构下才会显示出来该驱动于menuconfig中

  • help :帮助文档,做一些提示,我这里给出了GPIO的接法。

修改该目录下的Makefile文件

在文末追加 obj-$(CONFIG_AD9833_ADI) += ad9833.o 这里CONFIG_后面接的必须和上面的Kconfig中 config字段一样 ad9833.o 的.o文件必须和放入该内核代码的ad9833.c名字字段一样。

编译内核

  • 配置menuconfig make CROSS_COMPILE=arm-none-linux-gnueabi- ARCH=arm menuconfig 然后,进入到drivers -> char.. device -> 找到你的驱动 以模块编译或者编译进内核。

  • 编译内核 make CROSS_COMPILE=arm-none-linux-gnueabi- ARCH=arm -j8

  • 生成uImage文件 (这个是omapl平台要求的) make CROSS_COMPILE=arm-none-linux-gnueabi- ARCH=arm uImage

  • 将内核和文件都放到目标板子 可以重启运行了

源代码下载

链接: https://pan.baidu.com/s/1rfZymtf-mRnZNlhb41RpGA 密码: 4pxx

参考文献

[1] zqixiao_09, Linux 字符设备驱动开发基础(四)—— ioctl() 函数解析 , 2016-03-11 [2] 草根老师, 解决undefined reference to __aeabi_uidivmod和undefined reference to __aeabi_uidiv'错误, 2012-07-21 21:59:03 [3] 小C爱学习, 一步一步写miscdevice的驱动模块, 2013-07-24 [4] 宋宝华,Linux设备驱动开发详解:基于最新的Linux 4.0内核

上一页Linux Driver - GPIO键盘驱动开发记录_OMAPL138下一页基于OMAPL138的Linux字符驱动_GPIO驱动AD9833(二)之cdev与read、write

最后更新于1年前

加载内核 insmod ad9833.ko

运行测试程序 可以看到效果了:

😾