Linux设备驱动开发详解

宋宝华《Linux设备驱动开发详解》 宋宝华《Linux设备驱动开发详解》 虚拟机以及源码。 阅读详情

文件目录 :


Linux设备驱动开发详解( 宋宝华 )笔记

Linux设备驱动概述及开发环境构建

设备驱动的作用

设备驱动提供了硬件和应用软件之间的纽带

应用软件时只需 调用系统软件的应用编程接口(API) 就可让硬件去完成要求的工作

无操作系统时的设备驱动

不是任何一个计算机系统都一定要有操作系统

循环 对设备中断的检测 或 对设备的轮询

//单任务软件架构
int main(int argc, char* argv[])
{
    while(1)
    {
        /*有串口中断*/
        if(serialInt == 1)
        {
            ProcessSerialInt(); /* 处理串口中断 */
            serialInt = 0; 	/* 中断标志变量清0 */
        }
        /* 有按键中断 */
        if(keyInt == 1)
        {
            ProcessKeyInt(); /* 处理按键中断 */
            keyInt = 0; /* 中断标志变量清0 */
        }
        
        status = CheckXXX();
        switch (status)
        {
			//...
        }
        //...
    }
}
//无操作系统情况下串口的驱动
/**********************
 * serial.h
 **********************/
extern void SerialInit(void);
extern void SerialSend(const char buf*, int count);
extern void SerialRecv(char buf*, int count);

/**********************
 * serial.c文件
 **********************/

 /* 初始化串口 */
void SerialInit(void)
{
	//...
}

/* 串口发送 */
void SerialSend(const char *buf, int count)
{
	//...
}

 /* 串口接收 */
void SerialRecv(char *buf, int count)
{
	//...
}

/* 串口中断处理函数 */
void SerialIsr(void)
{
	//...
	serialInt = 1;
}

无操作系统时硬件、 设备驱动和应用软件的关系 :

无操作系统时硬件、 设备驱动和应用软件的关系

驱动与应用高耦合的不合理设计 :

image-20200627172554374

应用直接访问硬件的不合理设计:

image-20200627172626039

有操作系统时的设备驱动

操作系统 把单一的“ 驱使硬件设备行动 ”变成了操作系统内与硬件交互的模块, 设为操作系统的API, 给应用程序提供接口

硬件、驱动、操作系统和应用程序的关系 :

image-20200627172833099

操作系统给我们提供内存管理机制 :

MMU的32位处理器 , 可以让 每个进程都可以独立地访问 4GB 的内存空间

应用程序将可使用统一的系统调用接口来访问各种设备

Linux设备驱动

设备的分类及特点

计算机系统的硬件主要由 CPU、 存储器 和 外设组成

一般的处理器都集成了UART、I2C控制器、 SPI控制器、 USB控制器、 SDRAM控制器等, 有的处理器还集成了GPU(图形处理器) 、 视频编解码器

Linux将 存储器 和 外设 分为3个基础大类 :

字符设备 : 以串行顺序依次进行访问的设备, 如触摸屏、 磁带驱动器、 鼠标等。

块设备 : 按任意顺序进行访问, 以块为单位进行操作,如硬盘、 eMMC等

使用文件系统的操作接口 open() 、 close() 、read() 、 write() 等进行访问

网络设备 : 面向数据包的 接收 和 发送 , 使用套接字接口

Linux设备驱动与整个软硬件系统的关系

字符设备 与 块设备都 被映射到 Linux文件系统的文件和目录, 通过文件系统的系统调用接口 open() 、 write() 、read()、 close() 等 即可访问 字符设备 和 块设备

Linux的块设备有两种访问方法 :

类似 dd命令对应的原始块设备, 如“ /dev/sdb1 ”等

块设备上建立FAT、EXT4、 BTRFS等文件系统, 然后以文件路径 , 如“ /home/barry/hello.txt ”的形式进行访问

在Linux中, 对 NOR、 NAND 等提供了独立的内存技术设备(Memory Technology Device, MTD) 子系统, 可以运行YAFFS2、 JFFS2、UBIFS等具备 擦除 和 负载均衡 能力的文件系统。

Linux设备驱动与整个软硬件系统的关系 :

image-20200627175735717

C库函数是通过系统调用接口而实现, 如C库函数 fopen() 、 fwrite() 、 fread() 、 fclose() 分别会调用操作系统的API open() 、 write() 、 read() 、 close()

为了代码可移植性的目的 , 应用程序最好使用C库函数

Linux设备驱动的重点、 难点

懂得SRAM、Flash、 SDRAM、 磁盘的读写方式, UART、 I2C、 USB等设备的接口以及轮询、 中断、 DMA的原理, PCI总线工作方式以及CPU的内存管理单元(MMU)

运用C语言的结构体、 指针、 函数指针及内存动态申请和释放等

有一定的Linux内核基础,至少要明白驱动与内核的接口。 尤其是对于块设备、 网络设备、 Flash设备、 串口设备等复杂设备

有 多任务并发控制 和 同步 的基础, 因为在驱动中有大量的自旋锁、 互斥、 信号量、 等待队列等 并发与同步机制

Linux设备驱动的开发环境构建

PC上的Linux环境
QEMU实验平台
源代码阅读和编辑

http://lxr.free-electrons.com、 http://lxr.oss.org.cn/ , 这样的网站提供了Linux内核源代码的交叉索引

Linux主机上阅读和编辑Linux源码的常用方式是 vim + cscope或者 vim + ctags, vim 是一个文本编辑器, 而 cscope 和 ctags 则可建立代码索引

​ vscode使用

设备驱动Hello World: LED驱动

无操作系统时的LED驱动

GPIO一般由 两组寄存器控制, 一组控制寄存器 和 一组数据寄存器

控制寄存器可设置 GPIO 口的工作方式为 输入 或 输出 。

当引脚被设置为输出时, 向 数据寄存器 的对应位写入 1 和 0 会分别在引脚上产生 高电平和 低电平

当引脚设置为输入时, 读取 数据寄存器 的对应位可获得引脚上的电平为 高 或 低

如:

在 GPIO_REG_CTRL 物理地址中控制寄存器处的第 n 位写入 1 可设置 GPIO 口为输出,

在地址 GPIO_REG_DATA 物理地址中数据寄存器的第 n 位写入1 或 0 可在引脚上产生 高 或 低电平.

//无操作系统时的LED驱动
//启动硬件MMU后,将寄存器的 物理地址 转化为 虚拟地址
#define reg_gpio_ctrl *(volatile int *)(ToVirtual(GPIO_REG_CTRL))
#define reg_gpio_data *(volatile int *)(ToVirtual(GPIO_REG_DATA))

/* 初始化LED */
void LightInit(void)
{
	reg_gpio_ctrl |= (1 << n); /* 设置GPIO为输出 */
}

/* 点亮LED */
void LightOn(void)
{
	reg_gpio_data |= (1 << n); /* 在GPIO上输出高电平 */
}

/* 熄灭LED */
void LightOff(void)
{
	reg_gpio_data &= ~(1 << n); /* 在GPIO上输出低电平 */
}
Linux下的LED驱动

内核中实际实现了一个提供sysfs节点的GPIO LED驱动, 位于 drivers/leds/leds-gpio.c 中

#include //.../* 包含内核中的多个头文件 */

/* 设备结构体 */
struct light_dev 
{
    struct cdev cdev;	 /* 字符设备cdev结构体 */
    unsigned char vaule; /* LED亮时为1, 熄灭时为0, 用户可读写此值 */
};

struct light_dev *light_devp;
int light_major = LIGHT_MAJOR;
/* 打开和关闭函数 */
int light_open(struct inode *inode, struct file *filp)
{
    struct light_dev *dev;
    
    /* 获得设备结构体指针 */
    dev = container_of(inode->i_cdev, struct light_dev, cdev);
    /* 让设备结构体作为设备的私有信息 */
    filp->private_data = dev;
    
    return 0;
}

int light_release(struct inode *inode, struct file *filp)
{
    return 0;
}

/* 读写设备:可以不需要 */
ssize_t light_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    struct light_dev *dev = filp->private_data; /* 获得设备结构体 */
    
    if(copy_to_user(buf, &(dev->value), 1))
    {
        return -EFAULT;
    }
    
    return 1;
}

ssize_t light_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
    struct light_dev *dev = filp->private_data;
    
    if(copy_from_user(&(dev->value), buf, 1))
    {
        return -EFAULT;
    }
    
    /* 根据写入的值点亮和熄灭LED */
    if(dev->value == 1)
    {
        light_on();
    }
    else
    {
        light_off()
    }
    
    return 1;
}

/* ioctl函数 */
int light_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    struct light_dev *dev = filp->private_data;
    
    switch(cmd) 
    {
		case LIGHT_ON:
            dev->value = 1;
            light_on();
            break;
            
		case LIGHT_OFF:
            dev->value = 0;
            light_off();
            break;

		default:
            /* 不能支持的命令 */
            return -ENOTTY;
    }
   
    return 0;
}

struct file_operations light_fops = {
    .owner = THIS_MODULE,
    .read = light_read,
    .write = light_write,
    .unlocked_ioct1 = light_ioctl,
    .open = light_open,
    .release = light_release,
};

/* 设置字符设备cdev结构体 */
static void light_setup_cdev(struct light_dev *dev, int index)
{
    int err;
    int devno = MKDEV(light_major, index);
    
    cdev_init(&dev->cdev, &light_fops);
    dev->cdev.owner = THIS_MODULE;
    dev->cdev.ops = &light_fops;
    
    err = cdev_add(&dev->cdev, devno, 1);
    if(err)
    {
        printk(KERN_NOTICE "Error %d adding LED%d", err, index);
    }
}

/* 模块加载函数 */
int light_init(void)
{
    int result;
    dev_t dev = MKDEV(light_major, 0);
    /* 申请字符设备号 */
    if(light_major)
    {
        //字符设备注册
        result = register_chrdev_region(dev, 1, "LED");
    }
    else
    {
        // 分配
        result = alloc_chrdev_region(&dev, 0, 1, "LED");
        light_major = MAJOR(dev);
    }
    
    if((result < 0)
    {
		return result;
    }
       
	/* 分配设备结构体的内存 */
	light_devp = kmalloc(sizeof(struct light_dev), GFP_KERNEL);
    if(!light_devp)
	{
		result = -ENOMEM;
        goto fail_malloc;
	}
       
       
fail_malloc:
	unregister_chrdev_region(dev, light_devp);
	return result;
}
       
/* 模块卸载函数 */
void light_cleanup(void)
{
    /* 删除字符设备结构体 */
	cdev_del(&light_devp->cdev);
    /* 释放在light_init中分配的内存 */
    kfree(light_devp);
	/* 删除字符设备 */
    unregister_chrdev_region(MKDEV(light_major, 0), 1); 
}

MODULE_AUTHOR("cpucode");
MODULE_LICENSE("GPL");

驱动设计的硬件基础

处理器

通用处理器

很多的通用处理器(GPP) 采用SoC(片上系统) 的芯片设计方法, 集成了各种功能模块, 每一种功能都是由硬件描述语言设计程序,然后在SoC内由电路实现的

ARM SoC范例: Snapdragon 810 :

image-20200627232016367

ARM移动处理芯片供应商包括 高通(Qualcomm) 、 三星(Samsung) 、 英伟达(Nvidia) 、 美满(Marvell) 、 联发科(MTK) 、 海思(HiSilicon) 、 展讯(Spreadtrum) 等

中央处理器的体系结构可以分为两类 :

冯·诺依曼结构( 普林斯顿结构 ),将程序指令存储器和数据存储器合并在一起的存储器结构 , 程序指令和数据的宽度相同。 如 : Intel公司的中央处理器

哈佛结构 , 将 程序指令 和 数据 分开存储, 指令和数据是不同的数据宽度 如 : Cortex A系列

哈佛结构还采用了独立的程序总线和数据总线, 分别为CPU 与 每个存储器之间的专用通信路径, 提高了执行效率

冯· 诺依曼结构 与 哈佛结构 :

image-20200627232522881

改进的哈佛架构, 有独立的地址总线和数据总线 , 两条总线由 程序存储器 和 数据存储器 分时共用

改进的哈佛结构对 程序 和 数据, 使用公用数据总线来完成 程序存储模块 或 数据存储模块 与 CPU 之间的数据传输, 公用的地址总线来寻址 程序 和 数据

改进的哈佛结构 :

image-20200627232605530

指令集的角度来讲, 中央处理器也可以分为两类 : RISC(精简指令集计算机) 和CISC(复杂指令集计算机)

CSIC 强调增强指令的能力、 减少目标代码的数量, 但 指令复杂, 指令周期长;

而RISC强调尽量减少指令集、 指令单周期执行, 但 目标代码会更大

数字信号处理器

数字信号处理器(DSP) 针对通信、 图像、 语音和视频处理等领域的算法而设计

DSP分为两类, 定点DSP, 浮点DSP。

浮点DSP的浮点运算用硬件来实现, 可以在单周期内完成, 因此浮点运算处理速度高于定点DSP。

而定点DSP只能用定点运算模拟浮点运算

网络处理器器件内部通常由 n个微码处理器 和 n个硬件协处理器 组成, 多个微码处理器在网络处理器内部并行处理, 通过预先编制的微码来控制处理流程

处理器分类 :

处理器
按应用领域分类
融合,数字信号控制器_DSC_
通用处理器_GPP_
微控制器_MCU_简称单片机
微处理器__MPU
数字信号处理器_DSP_
定点DSP
浮点DSP
专门处理器_ASO_及ASIC
网络处理器
音视频编解码器
按体系结构分类
冯诺依曼结构
哈佛结构
按指令集分类
RISC
CSIC

MCU 处理图形用户界面和用户的按键输入并运行多任务操作系统

DSP进行音视频编解码

射频方面则采用ASIC

存储器

存储器主要可分类为只读储存器(ROM) 、 闪存(Flash) 、 随机存取存储器(RAM) 、 光/磁介质储存器。

ROM可细分为 不可编程ROM、 可编程ROM(PROM) 、 可擦除可编程ROM(EPROM) 和电可擦除可编程ROM(E2PROM) , E2PROM完全可以用软件来擦写

Flash闪存技术两种主要的是 NOR(或非) 和 NAND(与非)

NOR Flash的特点是可芯片内执行(eXecute InPlace, XIP) , 程序可以直接在NOR内运行

NAND Flash以 块方式进行访问, 不支持芯片内执行

典型的类SRAM接口 :

image-20200628100348132

公共闪存接口(Common Flash Interface, CFI) 是一个从NOR Flash器件中读取数据的公开、 标准接口

如 芯片不支持CFI, 可使用JEDEC(Joint Electron Device EngineeringCouncil, 电子电器设备联合会), 需要读出 制造商ID 和 设ID, 来获取 Flash的大小

NAND Flash的主要接口

  • I/O总线: 地址、 指令 和 数据通过这组总线传输, 一般为8位或16位

  • 芯片启动(Chip Enable, CE#) : 如果没有检测到CE信号, NAND器件就保持待机模式, 不对任何控制信号做出响应

  • 写使能(Write Enable, WE#) : WE#负责将数据、 地址或指令写入NAND之中

  • 读使能(Read Enable, RE#) : RE#允许数据输出

  • 指令锁存使能(Command Latch Enable, CLE) : 当CLE为高电平时,在WE#信号的上升沿, 指令将被锁存到NAND指令寄存器中

  • 地址锁存使能(Address Latch Enable, ALE) : 当ALE为高电平时, 在WE#信号的上升沿, 地址将被锁存到NAND地址寄存器中。

  • 就绪/忙(Ready/Busy, R/B#) : 如果NAND器件忙, R/B#信号将变为低电平。 该信号是漏极开路, 需要采用上拉电阻

使用NAND Flash的同时, 应采用 错误探测 / 错误更正(EDC / ECC) 算法

Flash的编程原理都是 只能将1写为0, 而 不能将0写为1

RAM可分为静态RAM(SRAM) 和 动态RAM(DRAM)

DRAM以电荷形式进行存储, 数据存储在电容器中 , 需要定期刷新

SRAM是静态的, 只要供电它就会保持一个值, SRAM没有刷新周期

DPRAM: 双端口RAM

DPRAM的优点是通信速度快、 实时性强、 接口简单, 而且两边处理器都可主动进行数据传输

双端口RAM :

image-20200628100700440

CAM: 内容寻址RAM

CAM是以内容进行寻址的存储器 , 输入要查询的数据, 输出就是 数据地址 和 匹配标志。 若匹配(即搜寻到数据) , 则输出数据地址。

CAM的输入与输出 :

image-20200628100651583

FIFO: 先进先出队列

FIFO存储器的特点是先进先出, 进出有序, FIFO多用于数据缓冲 .

存储器分类 :

存储器
非易失性存储器_NVM_
ROM
_ROM_
PROM
EPROM
E2PROM
Flash
NORFlash
NANDFlash
光/磁介质存储器
掉电丢失数据的RAM
SRAM
DRAM_SDRM_DDR_SDRAM_
特定类型的RAM一般采用SRAM
NVRAM
DPRAM
CAM
FIFO

接口与总线

串口

RS-232、 RS-422 与 RS-485 都是串行数据接口标准

RS-232 : 命名为 EIA-232-E

RS-422 : 定义了一种平衡通信接口, 它是一种单机发送、 多机接收的单向、 平衡传输规范, 被命名为 TIA/EIA-422-A 标准

RS-485 : 增加了多点、 双向通信能力, 允许多个发送器连接到同一条总线上,同时增加了 发送器的驱动能力 和 冲突保护特性, 并扩展了 总线共模范围, 被命名为TIA/EIA-485-A标准。

RS-232C : 为连接 DTE(数据终端设备) 与 DCE(数据通信设备) 而制定 .

RS-232C标准接口有25条线(4条数据线、 11条控制线、 3条定时线、 7条备用和未定义线), 常用的只有9根, 它们是RTS/CTS(请求发送/清除发送流控制) 、RxD/TxD(数据收发) 、 DSR/DTR(数据设置就绪/数据终端就绪流控制) 、DCD(数据载波检测, 也称RLSD, 即接收线信号检出) 、 Ringing-RI(振铃指示) 、 SG(信号地) 信号。

  • RTS: 用来表示 DTE请求DCE发送数据, 当终端要发送数据时, 使该信号有效

  • CTS: 用来表示DCE准备好接收DTE发来的数据, 是对RTS的响应信号。

  • RxD: DTE通过RxD接收从DCE发来的串行数据。

  • TxD: DTE通过TxD将串行数据发送到DCE。

  • DSR: 有效(ON状态) 表明DCE可以使用。

  • DTR: 有效(ON状态) 表明DTE可以使用。

  • DCD: 当本地DCE设备收到对方DCE设备送来的载波信号时, 使DCD有效, 通知DTE准备接收, 并且由DCE将接收到的载波信号解调为数字信号, 经RxD线送给DTE。

  • Ringing-RI: 当调制解调器收到交换台送来的振铃呼叫信号时, 使该信号有效(ON状态) , 通知终端, 已被呼叫。

RS-232C串口电路原理 :

image-20200628145348941
I2C

I2C( 内置集成电路) 总线是由Philips公司开发的两线式串行总线

I2C总线支持多主控( Multi-Mastering) 模式, 任何能够进行发送和接收的设备都可以成为主设备。

两个信号为数据线SDA和时钟SCL

总线的输出端必须是开漏输出( CMOS )或 集电极开路( TTL )输出的结构。

总线空闲时, 上拉电阻使 SDA 和 SCL线都保持高电平。

根据 开漏输出 或 集电极开路输出信号的“ 线与 ”逻辑, I2C总线上任意器件输出 低电平 都会使相应总线上的信号线变低

当 SCL稳定在高电平时, SDA 由高到低 的变化将产生一个开始位, 而 由低到高 的变化则产生一个停止位

I2C总线的开始位和停止位 :

image-20200628145547766

开始位 和 停止位 都由I2C主设备产生。 在选择从设备时, 如果从设备采用7位地址, 则主设备在发起传输过程前, 需先发送1字节的地址信息, 前7位为设备地址, 最后1位为读写标志。 数据传输, 每次传输的数据也是1字节, 从MSB开始传输。 每个字节传完后, 在SCL的第9个上升沿到来之前, 接收方应该发出1个ACK位。

I2C总线的时序 :

image-20200628145603514
SPI

SPI( Serial Peripheral Interface, 串行外设接口) 总线系统是一种同步串行外设接口 , 使CPU与各种外围设备以串行方式进行通信以交换信息。一般主控SoC作为SPI的“ 主 ”, 而外设作为SPI的“ 从 ”

SPI接口一般使用4条线: 串行时钟线( SCLK) 、 主机输入/从机输出数据线 MISO、 主机输出/从机输入数据线 MOSI 和 低电平有效的从机选择线SS

SPI主、 从硬件连接图 :

image-20200628145616617

在SPI总线的传输中, SS信号是低电平有效的, 当我们要与某外设通信的时候, 需要将该外设上的SS线置低。 SPI从设备支持的SPI总线最高时钟频率( 决定了SCK的频率) 决定了数据与时钟之间的偏移 , 外设的CPHA决定了采样的时刻、CPOL模式决定了触发的边沿是上升沿还是下降沿

SPI模块为了和外设进行数据交换, 根据外设工作要求, 其输出串行同步时钟极性(CPOL) 和相位(CPHA) 可以进行配置。

如 CPOL = 0, 串行同步时钟的空闲状态 = 低电平;

如 CPOL = 1, 串行同步时钟的空闲状态 = 高电平。

如 CPHA = 0, 在串行同步时钟的第一个跳变沿(上升或下降) 数据被采样;

如 CPHA = 1, 在串行同步时钟的第二个跳变沿(上升或下降) 数据被采样

SPI总线的时序 :

image-20200628145641748
USB

USB( 通用串行总线) 具有数据传输率高、 易扩展、 支持即插即用 和 热插拔的优点

USB 1.1包含全速和低速两种模式, 低速模式的速率为1.5Mbit/s, 支持小数据吞吐量 和 高实时性的设备, 如鼠标等。 全速模式为12Mbit/s, 可以外接速率更高的外设。

在USB 2.0中, 增加了一种高速方式,数据传输率达到480Mbit/s, 半双工, 满足更高速外设的需要。 采用4芯的屏蔽线, 一对差分线( D+、 D-) 传送信号, 另一对( VBUS、 电源地) 传送+5V的直流电 . 增添的 Bulk模式支持1个数据流

而USB3.0( 也被认为是Super Speed USB) 的最大传输带宽高达5.0Gbit/s( 即640MB/s) , 全双工。 设计了8条内部线路, 除VBUS、 电源地之外, 其余3对均为 数据传输线路。 其中保留了 D+ 与 D- 这两条兼容USB 2.0的线路, 新增了SSRX与SSTX专为USB 3.0所设的线路 . 增加了 Bulk Streams传输模式 支持多个数据流, 每个数据流被分配一个Stream ID(SID) , 每个SID与一个主机缓冲区对应 .

开发板需要挂接USB设备, 则需提供USB主机( Host) 控制器和连接器;

作为USB设备, 则需提供USB设备适配器和连接器

USB的物理拓扑结构 :

image-20200628145659602

每一个USB设备会有一个或者多个逻辑连接点在里面, 每个连接点叫端点

以下是端点传输方式 :

控制(Control) 传输方式

控制传输是双向传输, 数据量通常较小, 主要用来进行查询、 配置和给USB设备发送通用命令。 所有USB设备必须支持标准请求(StandardRequest) , 控制传输方式 和 端点0。

同步(Isochronous) 传输方式 ( Streaming Real-time )

同步传输提供了确定的带宽和间隔时间, 它用于时间要求严格并具有较强容错性的流数据传输, 或 用于要求恒定数据传送率的即时应用 . 如: 语音业务传输

中断(Interrupt) 传输方式

中断方式传送是单向的, 对于USB主机而言, 只有输入。 中断传输方式主要用于定时查询设备是否有中断数据要传送, 该传输方式应用在少量分散的、 不可预测的数据传输场合, 如: 键盘、 游戏杆和 鼠标

批量(Bulk) 传输方式

批量传输主要应用在没有带宽、 间隔时间要求的批量数据的传送和接收中, 它要求保证传输。 如 : 打印机和扫描仪

集线器负责检测设备的连接和断开, 利用其中断IN端点(Interrupt IN Endpoint) 来向主机报告。 一旦获悉有新设备连接上来, 主机就会发送一系列请求给设备所挂载的集线器, 再由集线器建立起一条连接主机和设备之间的通信通道。 然后主机以控制传输的方式, 通过端点0对设备发送各种请求, 设备收到主机发来的请求后回复相应的信息, 进行枚举(Enumerate) 操作。 因此USB总线具备热插拔的能力

以太网接口

以太网接口由MAC(以太网媒体接入控制器) 和 PHY(物理接口收发器) 组成

以太网MAC由 IEEE 802.3 以太网标准定义, 实现了数据链路层

MAC 和 PHY之间采用 MII(媒体独立接口) 连接, 它是 IEEE-802.3定义的以太网行业标准, 包括 1个数据接口 与 MAC 和 PHY 之间的1个管理接口

数据接口包括分别用于 发送 和 接收 的两条独立信道, 每条信道都有自己的 数据、时钟 和 控制信号, MII数据接口总共需要16个信号。

MII管理接口有两个信号, 时钟信号, 数据信号。 通过管理接口, 上层能 监视 和 控制PHY

以太网接口的硬件电路原理 :

image-20200628145720172
PCI和PCI-E

PCI(外围部件互连) 是由 Intel 推出的一种局部总线, 作为一种通用的总线接口标准

PCI总线具有如下特点 :

  • 数据总线为32位, 可扩充到64位
  • 可进行突发(Burst) 模式传输。 突发方式传输是指取得总线控制权后连续进行多个数据的传输。 突发传输时, 只需要给出目的地的首地址, 访问第1个数据后, 第2~n个数据会在首地址的基础上按一定规则自动寻址和传输。 与突发方式对应的是单周期方式, 它在1个总线周期只传送1个数据。
  • 总线操作与处理器——存储器子系统操作并行
  • 采用中央集中式总线仲裁
  • 支持全自动配置、 资源分配, PCI卡内有设备信息寄存器组为系统提供卡的信息, 可实现即插即用
  • PCI总线规范独立于微处理器, 通用性好
  • PCI设备可以完全作为主控设备控制总线

基于PCI总线的计算机系统逻辑示意图 :

image-20200628145740570

系统的各个部分通过PCI总线和PCI-PCI桥连接在一起

CPU和RAM通过PCI桥连接到PCI总线0(即主PCI总线) , 而具有PCI接口的显卡则可以直接连接到主PCI总线上。

PCI-PCI桥是一个特殊的PCI设备, 它负责将 PCI总线0 和 PCI总线1(即从PCI主线) 连接在一起, 通常PCI总线1称为 PCI-PCI桥的下游(Downstream) , 而PCI总线0则称为 PCI-PCI桥的上游(Upstream) 。

为了兼容旧的ISA总线标准, PCI总线还可以通过 PCI-ISA桥 来连接ISA总线, 从而支持以前的ISA设备。

PCI配置空间 :

image-20200628145759043

PCI配置空间共为256字节 = (3CH + 04H)* 4 :

  • 制造商标识(Vendor ID) : 由PCI组织分配给厂家
  • 设备标识(Device ID) : 按产品分类给本卡的编号
  • 分类码(Class Code) : 本卡功能的分类码, 如图卡、 显示卡、 解压卡等
  • 申请存储器空间: PCI卡内有存储器 或 以存储器编址的寄存器和I/O空间, 为使驱动程序和应用程序能访问它们, 需申请CPU的一段存储区域以进行定位。 配置空间的基地址寄存器用于此目的
  • 申请I/O空间: 配置空间中的基地址寄存器用来进行系统I/O空间的申请
  • 中断资源申请: 配置空间中的中断引脚和中断线用来向系统申请中断资源。 偏移 3D H 处为中断引脚寄存器, 其值表明PCI设备使用了哪一个中断引脚, 对应关系为1—INTA#、 2—INTB#、 3—INTC#、 4—INTD#

PCI-E( PCI Express) 是Intel公司提出的新一代的总线接口, PCI Express采用了目前业内流行的点对点串行连接, 比起PCI以及更早的计算机总线的共享并行架构, 每个设备都有自己的专用连接, 采用串行方式传输数据, 并把数据传输率提高到一个很高的频率, 达到PCI所不能提供的高带宽。

SD和SDIO

SD(Secure Digital) 是一种关于Flash存储卡的标准, 也就是一般常见的SD记忆卡, 在设计上与MMC(Multi-Media Card) 保持了兼容

SDHC(SDHigh Capacity) 是大容量SD卡, 支持的最大容量为32GB。

SDXC(SD eXtended Capacity) 则支持最大2TB大小的容量

SDIO(Secure Digital Input and Output Card, 安全数字输入输出卡) 在SD标准的基础上, 定义了除存储卡以外的外设接口。

SDIO主要有两类应用——可移动和不可移动。

不可移动设备遵循相同的电气标准, 但不要求符合物理标准。

有很多的手机或者手持装置都支持SDIO的功能, 以连接WiFi、 蓝牙、 GPS等模块。

SD/SDIO的传输模式有:

  • SPI模式
  • 1位模式
  • 4位模式

SDIO接口引脚定义 :

image-20200628145832178

CLK为时钟引脚, 每个时钟周期传输一个命令 或 数据位;

CMD是命令引脚, 命令在CMD线上串行传输, 是双向半双工的(命令从主机到从卡, 而命令的响应是从卡发送到主机)

DAT[0] ~ DAT[3]为数据线引脚;

在SPI模式中, 第8脚位被当成中断信号

SDIO单模块读、 写的典型时序 :

image-20200628145852582

eMMC( Embedded Multi Media Card) 是当前移动设备本地存储的主流解决方案, 目的在于简化手机存储器的设计。

eMMC就是NAND Flash、 闪存控制芯片和标准接口封装的集合, 它把NAND和控制芯片封装成为一个多芯片封装( Multi-Chip Package, MCP) 芯片。

eMMC支持 DAT[0] ~ DAT[7] 8位的数据线。

上电或者复位后, 默认处于1位模式, 只使用DAT[0], 后续可以配置为4位或者8位模式。

CPLD和FPGA

CPLD(复杂可编程逻辑器件) 由 完全可编程的与或门阵列 以及 宏单元构成。

CPLD中的基本逻辑单元是宏单元,

宏单元由一些“ 与或 ”阵列加上 触发器 构成, 其中“ 与或 ”阵列完成 组合逻辑 功能, 触发器完成 时序逻辑 功能。

宏单元中与阵列的输出称为乘积项, 其数量标示着CPLD的容量。

乘积项阵列实际上就是一个“与或”阵列, 每一个交叉点都是一个可编程熔丝, 如 导通就是实现“与”逻辑。

在“ 与 ”阵列后一般还有一个“ 或 ”阵列, 用以完成最小逻辑表达式中的“ 或 ”关系。

典型的CPLD的单个宏单元结构

image-20200628145916715

CPLD由 LAB(逻辑阵列模块, 由多个宏单元组成) 通过PIA(可编程互连阵列) 互连组成, 而CPLD与外部的接口则由I/O控制模块提供。

宏单元的输出会经I/O控制块送至I/O引脚, I/O控制块控制每一个I/O引脚的工作模式, 决定其为输入、 输出还是双向引脚, 并决定其三态输出的使能端控制。

典型的CPLD整体架构

image-20200628145943802

FPGA由IOC(输入/输出控制模块) 、 EAB(嵌入式阵列块) 、 LAB和快速通道互连构成

IOC是内部信号到I/O引脚的接口, 它位于快速通道的行和列的末端, 每个IOC包含一个双向I/O缓冲器和一个既可作为输入寄存器也可作为输出寄存器的触发器

EAB(嵌入式存储块) 是一种输入输出端带有寄存器的非常灵活的RAM。 EAB不仅可以用作存储器, 还可以事先写入查表值以用来构成如乘法器、 纠错逻辑等电路。 当用于RAM时, EAB可配制成8位、 4位、 2位和1位长度的数据格式

LAB主要用于逻辑电路设计, 一个LAB包括多个LE(逻辑单元) , 每个LE包括组合逻辑及一个可编程触发器。 一系列LAB构成的逻辑阵列可实现普通逻辑功能, 如计数器、 加法器、 状态机等

典型的FPGA内部结构 :

image-20200628145955367

器件内部信号的互连和器件引出端之间的信号互连由快速通道连线提供, 快速通道遍布于整个FPGA器件中, 是一系列水平和垂直走向的连续式布线通道。

实际逻辑电路与查找表的实现 :

image-20200628150019064

原理图分析

硬件时序分析

时序分析的概念
典型的硬件时序

芯片数据手册阅读方法

仪器仪表使用

万用表
示波器
逻辑分析仪

Linux内核及内核编程

Linux内核的发展与演变

Linux操作系统是UNIX操作系统的一种克隆系统

UNIX操作系统

鼻祖

Minix操作系统

Minix操作系统也是UNIX的一种克隆系统

GNU计划

GNU项目已经开发出许多高质量的免费软件, 其中包括 emacs编辑系统、 bash shell程序、 gcc系列编译程序、 GDB调试程序等 . 为Linux操作系统的开发创造了一个合适的环境

POSIX标准

POSIX(Portable Operating System Interface, 可移植的操作系统接口) 是由IEEE和ISO/IEC开发的一组标准。 基于UNIX, 描述了操作系统的调用服务接口, 用于保证编写的应用程序的移植

Linux操作系统版本的历史及特点 :

image-20200629200000931

android采用Linux内核, 并在内核里加入了一系列补丁

Linux内核开发人员和补丁情况 :

image-20200629200130885

Linux 2.6后的内核特点

新的调度器

CFS(Completely FairScheduler, 完全公平调度) 算法

Linux 3.14 中: 增加 EDF(Earliest Deadline First, 最早截止期限优先) 调度算法

内核抢占

一个内核任务可以被抢占, 从而提高系统的实时性

Linux 2.4的内核中, 在IRQ1的中断服务程序唤醒RT(实时) 任务后, 必须要等待前面一个Normal(普通) 任务的系统调用完成, 返回用户空间的时候, RT任务才能切入

Linux 2.6内核中, Normal任务的临界区(如自旋锁) 结束的时候, RT任务就从内核切入

Linux 2.6以后的内核仍然存在中断、 软中断、 自旋锁等原子上下文进程无法抢占执行

Linux 2.4和2.6以后的内核在抢占上的区别 :

image-20200629200338341

改进的线程模型

虚拟内存的变化

文件系统

音频

总线、 设备和驱动模型

电源管理

联网和IPSec

用户界面层

Linux 3.0后ARM架构的变更

Linux内核的组成

Linux内核源代码的目录结构

Linux内核的组成部分

image-20200629200514816

Linux内核空间与用户空间

Linux内核的编译及加载

Linux下的C编程特点

工具链

实验室建设

串口工具

Linux内核模块

Linux内核模块简介

Linux内核模块程序结构

模块加载函数

模块卸载函数

Linux文件系统与设备文件

Linux文件操作

字符设备驱动

Linux设备驱动中的并发控制

Linux设备驱动中的阻塞与非阻塞I/O

Linux设备驱动中的异步通知与异步I/O

中断与时钟

内存与I/O访问

Linux设备驱动的软件架构思想

Linux块设备驱动

Linux网络设备驱动

Linux I2C核心、 总线与设备驱动

USB主机、 设备与Gadget驱动

I2C、 SPI、 USB驱动架构类比

ARM Linux设备树

Linux电源管理的系统架构和驱动

Linux芯片级移植及底层驱动

Linux设备驱动的调试

linux设备驱动开发详解 书名:《Linux设备驱动开发详解》第二版 主机环境:Linux version 2.6.25-14.fc9.i686@Fedora arm-linux-gcc版本:version 4.4.1 开发板:mini2440-256M 版权信息:版权归作者M.Bing和博客园共同所有,可以随意转载,但请保留此条信息! 备注:由于作者水平有限,文章难免出错,欢迎大家指正,共同学习。 一、字符设备简介   在Linux2.6内核中,使用cdev结构描述一个字符设备,cdev结构如下所示 复制代码 struct cde 阅读详情

相关推荐

Linux设备驱动开发实战详解

简介:《Linux设备驱动开发详解》是一本全面讲解Linux设备驱动开发的专业书籍。书中不仅提供理论框架,还包含大量实践案例,覆盖硬件基础、文件系统、内存与I/O访问、字符设备、块设备、特定设备驱动(如音频、LCD、USB)以及设备驱动调试等关键领域。旨在帮助读者理解Linux内核与设备的交互机制,掌握从硬件到高级驱动开发的各项技术要点。

weixin_42591908的博客 1000

最全Linux驱动开发全流程详细解析(持续更新)

Linux驱动开发详细解析

拉依达不拉跨的博客 11万+

Linux设备驱动开发详解 - 经典教材学习指南

I/O子系统是操作系统中负责管理输入/输出请求的关键组成部分。它作为操作系统和硬件设备之间的桥梁,使得上层的应用程序能够高效、安全地与硬件设备进行通信。I/O子系统的设计对于系统的性能和稳定性有着深远的影响。I/O子系统的主要功能包括:I/O请求的调度与管理:负责接收来自用户空间的I/O请求,将它们排队,并根据设备的当前状态和系统的调度策略进行处理。设备驱动程序的管理:I/O子系统提供了对设备驱动程序的管理机制,驱动程序是操作系统与硬件设备之间进行通信的软件接口。缓冲管理。

weixin_35973118的博客 1167

Linux设备驱动开发详解笔记

设备驱动提供了硬件和应用软件之间的纽带应用软件时只需 调用系统软件的应用编程接口(API) 就可让硬件去完成要求的工作。

实力派,无需多言! 1558

Linux 设备驱动开发(PDF格式含源码)

Linux 设备驱动开发英文版(PDF格式含源码)

宋宝华 linux设备驱动开发

这个把整本书分成章节,这样更方便读者学习,并且很清晰

linux设备驱动开发详解_Linux设备驱动概述

本篇文章将介绍Linux设备驱动开发的基本知识,带你快速拨开Linux驱动这团迷雾。文章分为如下3个小节进行讲解:1、设备驱动的概念和作用;2、在无操作系统和有操作系统的情况下设备驱动的设计开发;3、Linux设备驱动的重点、难点和学习方法。文章最后会给出一个简单的LED驱动在无操作系统和Linux操作系统下的实现示例。好,Let's go1、设备驱动的概念和作用大家都知道在Linux开发中是一个...

weixin_39785524的博客 1942

《linux驱动开发》.pdf

《linux驱动开发》.pdf

探索Linux设备驱动开发的宝藏:《Linux设备驱动开发详解》资源仓库推荐

探索Linux设备驱动开发的宝藏:《Linux设备驱动开发详解》资源仓库推荐 【下载地址】宋宝华Linux设备驱动开发详解资源下载 宋宝华《Linux设备驱动开发详解》资源下载本仓库提供宋宝华所著《Linux设备驱动开发详解》一书的资源文件下载 ...

gitblog_06503的博客 459

Python+AI能源管理实战:从电力负荷预测到智能调度的全流程开发

本系统展示了Python在能源管理领域的强大能力,通过AI技术实现了从预测到优化的智能决策闭环。随着碳中和目标的推进,智能能源系统将成为新型电力系统的核心大脑。📌 专业建议:系统部署需考虑网络安全防护,关键控制指令应设置多重验证机制!关注我的优快云账号获取能源系统安全最佳实践~下期预告:我们将探索Python在智慧医疗中的应用,开发基于多模态数据的智能诊断辅助系统。想提前获取代码?评论区留言"智慧医疗"获取GitHub源码链接!

资深全栈架构师,乐于在 优快云 分享技术见解,与大家携手共进,共攀技术巅峰! 624

【知识点总结】电路原理 第一讲

大家好,从今天起,我将开始更新自上大学以来所学到的各种知识,之所以选择更新它们是因为最近我一直在收拾整理自己自上大学以来所收获到的各种知识,看着那厚厚一沓的书本心里不由得犯愁。如果我想找到特定的知识,那岂不是得翻到猴年马月。俗话说得好,下雨天打鞋子,闲着也是闲着,为此我设置一个专栏,将知识点一一分类总结,目的在于更好的整理所学的知识。本人学艺不精,有一些知识点地方可能存在瑕疵,希望各位大佬可以多多指教。以下是本篇文章正文内容虚断:一般通用性运算放大器的正反向输入端之间的电阻非常非常的大。

weixin_51130221的博客 4万+

【Linux】设备驱动开发:如何在Linux上开发和调试设备驱动

设备驱动程序是连接硬件与操作系统的软件桥梁。开发和调试设备驱动程序是Linux内核开发中的重要环节。本文将详细介绍如何在Linux上开发和调试设备驱动程序,帮助读者理解和掌握这一过程。

科技改变人类,技术成就未来 1504

从零开始:MTK ISP调试中的ImagiqSimulator实战指南

本文详细介绍了MTK ISP调试中ImagiqSimulator的实战应用,从环境准备、参数加载到核心模块调整,提供全面的调试流程指南。重点讲解了DM模块参数优化技巧和常见问题解决方案,帮助工程师高效完成ISP调试工作,提升图像处理质量。

weixin_29168813的博客 480

探索Linux设备驱动开发的宝典:《Linux设备驱动开发详解》第二版

探索Linux设备驱动开发的宝典:《Linux设备驱动开发详解》第二版 【下载地址】Linux设备驱动开发详解_宋宝华著高清带目录第二版分享 Linux设备驱动开发详解_宋宝华著(高清带目录第二版) 项目地址: https://g...

gitblog_06643的博客 940

Win10家庭版没有Hyper-V?教你一招轻松解决(含避坑指南)

本文详细介绍了在Win10家庭版中启用Hyper-V的完整实战指南。通过DISM工具链绕过系统版本限制,无需重装系统即可完整启用所有Hyper-V组件,适用于开发者测试多系统环境和安全隔离应用场景。文章包含详细操作步骤、常见问题解决方案及性能优化配置,帮助用户轻松解决Win10家庭版没有Hyper-V的问题。

weixin_29239661的博客 495

从零到一:YOLOv8在边缘设备上的轻量化部署实战

本文详细介绍了YOLOv8在边缘设备上的轻量化部署实战,涵盖模型优化、TensorRT加速、剪枝与知识蒸馏等关键技术。通过工业质检和智能门禁项目案例,展示了如何将YOLOv8高效部署到树莓派、Jetson Nano等资源受限设备,显著提升推理速度和降低内存占用。文章还提供了动态分辨率调整、多模型级联部署等实用技巧,助力开发者实现边缘AI应用的高效落地。

weixin_29040367的博客 379

LINUX设备驱动开发详解.pdf清晰完整版——掌握Linux驱动开发核心技术的宝典

LINUX设备驱动开发详解.pdf清晰完整版——掌握Linux驱动开发核心技术的宝典 【下载地址】LINUX设备驱动开发详解.pdf清晰完整版简介 探索Linux设备驱动开发的奥秘,这本《LINUX设备驱动开发详解》是您不可错过的学习宝典。书中全面覆盖了从基础概念到高级技巧的各个层面,包括开发环境配置、内核编程、驱动模块...

gitblog_06775的博客 962

fast-lio系列改进版本

2.回环检测的优化结果,更新到FAST-LIO2的当前帧位姿中,幷进行ikdtree的重构,进而更新submap。其它的效果还不清楚,实测lio部分稳定性很好。用fast-lio_slam就很稳定。融合了fast-lio2和sc-pgo,两部分分开运行,SC-PGO从FAST-LIO2节点获取里程计和激光雷达点云主题。的作者kim在FAST-LIO2的基础上,添加SC-PGO模块,通过加入ScanContext全局描述子,进行回环修正,SC-PGO模块与FAST-LIO2解耦,非常方便,很优秀的工作。

z1872385的博客 3189

《linux设备驱动详解》资源文件——Linux设备驱动开发的最佳伴侣

《linux设备驱动详解》资源文件——Linux设备驱动开发的最佳伴侣 去发现同类优质开源项目:https://gitcode.com/ 项目核心功能/场景 Linux设备驱动开发理论与实践详解,适用于Linux设备驱动开发人员。 项目介绍 在开源世界里,Linux系统的稳定性与灵活性使其成为众多开发者和技术爱好者的首选操作系统。而在Linux系统中,设备驱动的开发是一个至关重要且技术要求较高的环...

gitblog_06711的博客 971

LeRobot 入门教程(二)强化学习训练及 SmolVLA 模型微调

本文介绍了LeRobot框架中基于人类在环的样本高效强化学习(HIL-SERL)工作流,包含真实机器人训练和模拟环境训练两大模块。在真实机器人部分,详细说明了从配置设置、工作空间边界确定、演示数据收集、奖励分类器训练到演员-学习者架构部署的全流程,并强调了人类干预的关键作用。同时介绍了适用于模拟训练的gym_hil环境及其配置使用方法。最后,文章提供了微调SmolVLA基础模型的指南,包括数据集收集、微调步骤和模型评估方法。该工作流实现了人机协作的高效强化学习,适用于各类机器人操作任务。

2506_90492529的博客 3896

【Power Compiler手册】4.saif文件

功耗计算的准确性取决于切换活动数据的准确性。这些数据是通过使用RTL仿真或门级仿真生成的,并存储在SAIF文件中。在执行功耗优化和分析之前,应该使用SAIF文件来反标设计对象上的切换活动信息。SAIF是由Synopsys支持的ASCII格式,用于促进不同Synopsys工具之间的信息交换(参见IEEE 1801标准,附录J)。使用`read_saif`命令读取SAIF文件,使用`write_saif`命令写出SAIF文件。有关更多信息,请参见`read_saif`和`write_saif`命令的手册页。

QRBQ 2186

Linux设备驱动开发环境的搭建

<br />经过两周的摸索,终于对Linux设备驱动开发有了个初步的认识,下面对Linux设备驱动开发环境的搭建做个小结,以方便自己以后查询,同时也能给同道的初学者一点帮助。<br />刚接触Linux设备驱动时,初学者往往连如何编译驱动程序都不懂,更别说编译进内核或加载测试了。一般都是在网上找个最简单的 helloworld驱动程序,然后严格按照网上所说的步骤编译,结果却得到一大堆见都没见过的错误,更不要说根据错误信息来解决问题了,很多人到这里就 不知道如何往下进行了。十几天前我也卡在这里很长时间,现在知

转角遇到嵌入式 1546

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

linux设备驱动开发详解孔夫子,Linux设备驱动开发详解 。、

【内容简介】本书是一本介绍Linux设备驱动开发理论、框架与实例的书,本书以Linux2.6版本内核为蓝本,详细介绍自旋锁、信号量、完成量、中断顶/底半部、定时器、内存和I/O映射以及异步通知、阻塞I/O、非阻塞I/O等Linux设备驱动理论;字符设备、块设备、TTY设备、I2C设备、LCD设备、音频设备、USB设备、网络设备、PCI设备等Linux设备驱动的架构和框架中各个复杂数据架构和函数的关...

weixin_30636449的博客 365

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

《Linux设备驱动开发详解(第3版)》(即《Linux设备驱动开发详解:基于最新的Linux 4.0内核》)进展同步更新

本博实时更新《Linux设备驱动开发详解(第3版)》(即《Linux设备驱动开发详解:基于最新的Linux 4.0内核》)的最新进展。 目前已经完成稿件。 2015年8月9日,china-pub开始上线预售: http://product.china-pub.com/4733972 2015年8月20日,各路朋友报喜说已经拿到了书。 本书已经rebase到开发中的Linux 4.0内核,案例多数基于多核CORTEX-A9平台。 本书微信公众号"linux阅码场", 扫描下方二维码关注

宋宝华 6万+

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

linux设备驱动开发详解孔夫子,Linux设备驱动开发详解

【内容简介】《Linux设备驱动开发详解(第2版)》是一本介绍linux设备驱动开发理论、框架与实例的书,《Linux设备驱动开发详解(第2版)》基于ldd6410开发板,以linux2.6版本内核为蓝本,详细介绍自旋锁、信号量、完成量、中断顶/底半部、定时器、内存和i/o映射以及异步通知、阻塞i/o、非阻塞i/o等linux设备驱动理论;字符设备、块设备、tty设备、i2c设备、lcd设备、音频...

weixin_42548708的博客 148
上一篇: Linux 块设备驱动
下一篇: AT&T ASM Syntax
onnx
博客等级 码龄5年 1万+粉丝 416原创
评论
成就一亿技术人!
拼手气红包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、付费专栏及课程。

余额充值