Linux驱动开发 --- 架构方面的一些感悟

本文介绍了如何利用面向对象的设计思想实现软硬件的解耦,特别是在MCU和Linux内核中的应用。通过接口的定义,实现了驱动程序与硬件操作的分离,使得驱动更具可移植性。文章还探讨了如何通过接口的继承和重写来实现设备驱动的通用化,以应对不同硬件的需求,并以DMA控制器驱动为例展示了这一框架的实际应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2022/9/8 Ryan AT TsingMicro

软硬件分离

Linux内核虽然没有使用面向对象语言,但还是用C去实现了面向对象,以更好的管理这个巨大的工程。不仅如此,面向对象的规则也给软件解耦带来了巨大的作用。
过去开发MCU时,软件和硬件紧紧地贴在一起,回想一下我们是如何做的?
开发单片机时最简单的方法就是每种设备的驱动都直接操作寄存器,这样做确实十分简洁:可以封装出重复使用的函数,如从传感器读,写的函数。这种函数内部都是直接操作寄存器。简单的背后带来的是难以移植,如果换了一套硬件,这个驱动就没有用了,需要重新写一份。
那么面向对象的设计又是如何带来好处的?

  1. 面向对象中的接口是一个可以做到分层作用的设计,接口是一种"契约",约定使用者不可以多要,提供方不能少给。这句话会在下面的解释中解密。
  2. 面向对象时可以将硬件描述为一个对象,这个对象的属性就是就是对硬件属性的描述,同时它身上的API也就是对这个硬件的操作。当硬件操作十分复杂时这种设计就能让系统变得更清晰。

架构变迁之路

MCU时代的架构:

软硬件一一对应,所有驱动都直接操作寄存器
请添加图片描述

采用接口来解耦后

为了让设备驱动程序不直接依附操作寄存器部分的代码,我们可以在设备驱动程序中只调用接口中提供的操作,这些操作的实际实现则放在另一部分(这实际上也就是驱动开发者需要填充的)。这样一来,如果接口中的函数没有实现,驱动层就无法工作;相对的,如果是接口中没有约定的,驱动开发者没有实现,调用层也是无法调用的。
请添加图片描述
从这个图中可知,不同的设备驱动现在只依赖中间接口层提供的操作,与硬件没有任何关系。实际的寄存器控制实现放在底层。

以设计一个AT24C02驱动为例,假设现在项目中要使用的设备型号没有确定,硬件可能会帮你设计好I2C控制器,也可能不会。应该如何去设计这个驱动呢?
从上面的要求就可以体会到你的老板对一份可移植驱动迫切的心情,我们要考虑到分离硬件操作,因为SOC设计还没出bit文件,硬件部门也没有出原理图,我们现在完全是面对着虚空写代码。此时我们就应该设计出一个接口作为假想,以这个假想去设计驱动,假想的实现也就是寄存器操作放到设计和硬件部门给出资料后再实现。

AT24C02. 是一款CMOS EEPROM,该器件通过IIC总线接口进行操作。对于这个设备对象来说,最常用的几个操作是:init,write,read。那么这个接口就应该约定好这些操作,接口代码如下:

typedef struct 
{
    void (*init)(void);
    int32_t (*read)(uint32_t reg);
    int8_t (*write)(uint32_t reg,uint32_t value);
}i2c_control_t;

i2c_control_t结构体就是一个接口,包含三个函数指针:init,read,write。你可以把他看作一份契约,现在让我们履行这个契约,首先是对接口的实现:

//software simulation implementaion of the i2c_control_t
void init_simu(void)
{

}
int32_t stm32_read_reg_simu(uint32_t reg)
{
    ...
}
int8_t stm32_write_reg_simu(uint32_t reg,uint32_t value)
{
    ...
}

//hardware implementaion of the i2c_control_t
void init_hard(void)
{

}
int32_t stm32_read_reg_hard(uint32_t reg)
{
    ...
}
int8_t stm32_write_reg_hard(uint32_t reg,uint32_t value)
{
    ...
}

以上两组函数分别是对i2c_control_t接口的软件模拟实现和硬件控制器实现,这些函数里省略的内容就是驱动开发需要根据寄存器手册实现的部分。
如果调用层需要使用软件模拟版本的实现:

i2c_control_t my_i2c_simu =
{
    .init = init_simu,
    .read = stm32_read_reg_simu,
    .write = stm32_write_reg_simu,
};

如果后续需要使用硬件控制器版本的实现:

i2c_control_t my_i2c_hard = 
{
    .init = init_hard,
    .read = stm32_read_reg_hard,
    .write = stm32_write_reg_hard,
};

至此提供方的工作接近完成,调用方的调用应该如下:

void init(void)
{
    init_simu();
}

int32_t at24c02_read(i2c_control_t my_i2c)
{
    my_i2c.read(my_i2c,0x3a);
}
int32_t at24c02_write(i2c_control_t my_i2c)
{
    my_i2c.write(my_i2c,0x3a,123);
}

可以看出驱动不再依赖硬件资源就可以对某个寄存器进行读写,但其实这里隐藏着一个严重的问题:如果是使用软件模拟I2C,所使用的SCL/SDA引脚又会是另一对,stm32_read_reg_simu实现时可不知道有这么多要求,随便挑两个引脚作为SCL和SDA,如果需要更换可以在后期再修改板级支持包。而且,如果我们去使用两个控制器呢?那我们也同样不知道怎样初始化底层。因此,一个这个接口还需要更加完善。
面对更多的信息需要绑定,这个接口变得不再”纯洁“了,我是说,它需要包含更多类型的变量了,不仅仅是函数指针:

typedef struct i2c_control_descp //i2c 控制器描述结构体
{
   int8_t control_index; //指定使用哪个 i2c 控制器,如果为-1,则表示使用 io 模拟
   int8_t data_width; //数据位宽,一般为 8
   uint32_t scl_pin; //如果使用 io 模拟,指定 scl 引脚,引脚 = 32*GPIO 组+偏移
   uint32_t sda_pin; //如果使用 io 模拟,指定 sda 引脚
   uint32_t speed_hz; //指定 I2C 时钟速率
}i2c_control_descp_t;

typedef struct i2c_control
{
   i2c_control_descp_t descp;
   void (*init)(void);
   int32_t (*read_reg)(uint32_t reg);
   int8_t (*write_reg)(uint32_t reg, uint32_t value);
}i2c_control_t;

在之前只有函数指针的基础上,添加了一个描述结构体,用来描述I2C控制器。其中包含了这个控制器是使用IO模拟,还是某个硬件控制器;I2C通讯时每个数据包的数据位宽;使用IO模拟的话使用的是哪两个引脚;I2C时钟速率。
实例化这个I2C控制器对象时,附带的descp成员的实例化就为告诉了控制器日后的工作基调。
现在,整个架构已经大变样了,看下图:
请添加图片描述
这个框架将设备信息(如i2c_control_t中的descp)从驱动程序中分离开来。对于AT24C02设备而言, 不必知道使用哪个I2C控制器,也不必知道SCL和SDA接到了哪个引脚上,这些信息都记录在一个descp结构体中,也就是设备信息。这就实现了驱动和设备分离(设备的不同本质上是设备信息的不同)这种架构设计的目的就是一个驱动可以兼容多个设备,不需要每个设备都写一套驱动。依据这个思路你会发现,设备信息从驱动中分离开来是一种万金油的做法。驱动像是一台机器,而信息则像喂给机器的power。以此类推
BSP层面的驱动也是需要信息的,是的,寄存器信息。以这个思路来说,最终的架构应该再变动一点点:
请添加图片描述
这样设备信息和硬件信息就都分离了出来。

以下内容是引用自宋宝华《Linux设备驱动开发详解》

继承与重写的应用

在面向对象程序设计中,可以为每一类相似的事物抽象出一个基类,而具体的事物可以继承这个基类中的函数。所有继承自这个基类的对象,如果对于基类中的函数有特殊的要求,则可以自定义这个函数,也就是重写它(overriding)。子类可以写出与父类具有相同的方法名,返回类型和参数列表的方法(但是内部逻辑是自定义的),新方法覆盖基类中的方法,这就是“多态”,可以极大限度的提高代码的可重用能力。

在Linux汪洋肆意的驱动代码中往往会需要很多同类设备的驱动,这些同类设备一般都会做一个框架,框架中的核心层就是实现基类功能的层面,在这一层通常会实现该类设备的一些通用功能,当这些通用功能无法满足设备时可以对他们进行重写:

return_type core_funca(xxx_device * bottom_dev, param_type param)
{
	/* 检查底层设备是否重写funca */
	if(bottom_dev->funca)
		return bottom_dev->funca(param);
	/* 没有重写则直接运行通用层的funca */
	...
	....
	.....
}

如上代码首先检测是否底层设备实现了funca()函数,如果实现了则返回底层设备实现的那份funca函数的指针。
如果没有实现,则直接运行通用层已经实现的funca

上述代码假定为了实现funca(), 对于同类设备而言,操作流程一致,都要经过
“通用代码A 底层ops1 通用代码B 底层ops2 通用代码C 底层ops3”
这几步, 分层设计带来的明显好处是, 对于通用代码A,B,C,具体的底层驱动不需要再实现,而仅仅只要关心其底层的操作ops1,ops2,ops3即可。
这种写法可以归纳成如下图的一种架构图:
在这里插入图片描述

实战:DMA控制器驱动框架

由于我在实际开发过程中遇到了一些需求,我开始使用接口去做解耦,核心要义就是一句话“让消费层面向接口而不是面向实现”
需求:某个芯片上有多种DMA控制器需要驱动,对于每个控制器来说,框架如何管理他们的驱动,消费层如何使用他们的驱动,都是值得考虑的话题,这里我是用类似Linux的方式,采用ops接口对DMA的行为进行规范,驱动层实现这个ops。
驱动层编写控制器的初始化API,这个API类似probe函数,但是由于我的bare metal没有实现设备树和probe机制,所以这个API需要在初始化阶段手动调用。

DMA控制器的抽象和管理由中间层负责,定义了一个dma_device_t类型的结构体,驱动层需要为每一个控制器实现一个对象,并加到一个全局dma设备链表中。
中间层提供了通过name(后面也会实现通过idx)查找dma设备的API,消费层调用时只需要找到对应的dma控制器对象就可以调用应用层提供的其他对DMA操作的API(start、config等),这些API回调用对应的控制器的ops中实现的函数。

中间层代码:

ts_dmac.h

#ifndef DMA_FRAMEWORK_TS_DMAC_H
#define DMA_FRAMEWORK_TS_DMAC_H

#include "list.h"
#include "ts_dmac.h"
#include "stdio.h"
#include "string.h"

typedef unsigned int u32;
#define NULL    0

typedef enum {
    DMA_OPS_ERR = -1,
    DMA_OPS_OK  =  0,
}dma_ops_stat_e;

typedef enum {
    DMA_XFER_CMPL = 0,
    DMA_XFER_FAIL,
}dma_xfer_stat_e;

typedef void* dma_handler_t;

typedef struct dma_cfg_info {
    u32 src_addr;
    u32 dst_addr;
    u32 src_master;
    u32 dst_master;
    u32 src_bus_width;
    u32 dst_bus_width;
    u32 src_burst_size;
    u32 dst_burst_size;
    u32 src_addr_change_mode;
    u32 dst_addr_change_mode;
    u32 src_hs_mode;
    u32 dst_hs_mode;
    u32 src_hs_num;
    u32 dst_hs_num;
    u32 trans_mode;
    u32 block_size;
    u32 fifo_mode;
} dma_cfg_info_t;

typedef struct dma_ops{
    dma_ops_stat_e (*config_dmac)(void *dmac_handler, dma_cfg_info_t dmaCfgInfo);
    dma_ops_stat_e (*enable_dmac)(void *dmac_handler);
    dma_ops_stat_e (*disable_dmac)(void *dmac_handler);
    dma_xfer_stat_e (*start_xfer)(void *dmac_handler);
    dma_xfer_stat_e (*stop_xfer)(void *dmac_handler);
}dma_ops_t;

typedef struct dma_chan {
    int chan_idx;
    int chan_isbusy;
    void *src_buff;
    int  xfer_cnt;
} dma_chan_t;

typedef struct dma_device {
    char *dma_name;
    int dma_idx;
    dma_handler_t dmac_handler;
    dma_chan_t *dmac_chan;
    dma_ops_t *dmac_ops;
    struct list_head dma_list;
} dma_device_t;

dma_ops_stat_e dma_register_dev(dma_device_t *dmac);
dma_ops_stat_e dma_get_dev_byname(dma_device_t **out_dmac, const char *dma_name);
dma_ops_stat_e dma_start_xfer(dma_device_t *dmac);

ts_dmac.c

#include "ts_dmac.h"

LIST_HEAD(ts_dmac);

dma_ops_stat_e dma_register_dev(dma_device_t *dmac)
{
    //add a new device struct to linklist
    if(dmac == NULL)
    {
        printf("dmac register operations err! \r\n");
        return DMA_OPS_ERR;
    }

    list_add(&dmac->dma_list, &ts_dmac);
    return DMA_OPS_OK;
}

dma_ops_stat_e dma_get_dev_byname(dma_device_t **out_dmac, const char *dma_name)
{
    //look up dma device handler by name
    struct  list_head *pos;
    dma_device_t *dmax;
    list_for_each(pos, &ts_dmac)
    {
        dmax = list_entry(pos, dma_device_t, dma_list);
        if(!strcmp(dmax->dma_name, dma_name))
        {
            printf("dma name=%s \r\n", dmax->dma_name);
            *out_dmac = dmax;
            break;
        }
    }
    return DMA_OPS_OK;
}

dma_ops_stat_e dma_start_xfer(dma_device_t *dmac)
{
    if(dmac == NULL || dmac->dmac_ops == NULL)
    {
        printf("dmac register operations err! \r\n");
        return DMA_OPS_ERR;
    }
    printf("excute dma start \r\n");
    dmac->dmac_ops->start_xfer(dmac->dmac_handler);
}

驱动层负责实现:

#include "ts_dmac.h"

LIST_HEAD(ts_dmac);

dma_ops_stat_e dma_register_dev(dma_device_t *dmac)
{
    //add a new device struct to linklist
    if(dmac == NULL)
    {
        printf("dmac register operations err! \r\n");
        return DMA_OPS_ERR;
    }

    list_add(&dmac->dma_list, &ts_dmac);
    return DMA_OPS_OK;
}

dma_ops_stat_e dma_get_dev_byname(dma_device_t **out_dmac, const char *dma_name)
{
    //look up dma device handler by name
    struct  list_head *pos;
    dma_device_t *dmax;
    list_for_each(pos, &ts_dmac)
    {
        dmax = list_entry(pos, dma_device_t, dma_list);
        if(!strcmp(dmax->dma_name, dma_name))
        {
            printf("dma name=%s \r\n", dmax->dma_name);
            *out_dmac = dmax;
            break;
        }
    }
    return DMA_OPS_OK;
}

dma_ops_stat_e dma_start_xfer(dma_device_t *dmac)
{
    if(dmac == NULL || dmac->dmac_ops == NULL)
    {
        printf("dmac register operations err! \r\n");
        return DMA_OPS_ERR;
    }
    printf("excute dma start \r\n");
    dmac->dmac_ops->start_xfer(dmac->dmac_handler);
}

一个APP Demo:

#include <stdio.h>
#include "list.h"
#include <stdbool.h>
#include <stdlib.h>
#include "ahb_dmac.h"
#include "ts_dmac.h"

int main() {
    printf("Hello, World!\n");

    // reisger ahb dma into system
    ahb_dma_device_register();
    dma_device_t *dmac;
    dma_get_dev_byname(&dmac, "dw-ahb-dmac");
    dma_start_xfer(dmac);

    return 0;
}

├ │ ├01 - 从零开始认识开发板1.mp4 │ ├02 - 从零开始认识开发板2.mp4 │ ├03 - 从零开始认识开发板3.mp4 │ ├04 - 从零开始认识开发板4.mp4 │ └05 - 从零开始认识开发板5.mp4 ├ │ ├01 - 说在前面的话1.mp4 │ ├02 - 说在前面的话2.mp4 │ ├03 - 说在前面的话3.mp4 │ ├04 - 说在前面的话4.mp4 │ ├05 - 计算机组成原理概述1 .mp4 │ ├06 - 计算机组成原理概述2 .mp4 │ ├07 - 计算机组成原理概述3 .mp4 │ ├08 - Linux基础及操作系统框架1.mp4 │ ├09 - Linux基础及操作系统框架2.mp4 │ ├10 - Linux基础及操作系统框架3.mp4 │ ├11 - Linux基础及操作系统框架4.mp4 │ ├12 - Shell命令机制1.mp4 │ ├13 - Shell命令机制2.mp4 │ ├14 - Shell命令机制3.mp4 │ ├15 - Shell命令机制4.mp4 │ ├16 - Linux命令类库机制及常用命令_命令操练1.mp4 │ ├17 - 命令操练2.mp4 │ ├18 - 命令操练3.mp4 │ ├19 - 命令操练4.mp4 │ ├20 - 命令操练5.mp4 │ ├21 - 命令操练6.mp4 │ ├22 - 命令操练7.mp4 │ ├23 - 命令操练8.mp4 │ ├24 - 命令操练9.mp4 │ ├25 - 命令操练10.mp4 │ ├26 - 命令操练11.mp4 │ ├27 - Linux应用程序安装及卸载1.mp4 │ ├28 - Linux应用程序安装及卸载2.mp4 │ ├29 - Linux应用程序安装及卸载3.mp4 │ ├30 - Linux应用程序安装及卸载4.mp4 │ ├31 - Linux应用程序安装及卸载5.mp4 │ ├32 - Linux服务程序的安装及配置1.mp4 │ ├33 - Linux服务程序的安装及配置2.mp4 │ ├34 - Linux服务程序的安装及配置3.mp4 │ ├35 - Linux服务程序的安装及配置4.mp4 │ ├36 - Vi的设计思想及使用1.mp4 │ ├37 - Vi的设计思想及使用2.mp4 │ └38 - Vi的设计思想及使用3.mp4 ├ │ ├001 - GNU开发环境基础1.mp4 │ ├002 - GNU开发环境基础2.mp4 │ ├003 - GNU开发环境基础3.mp4 │ ├004 - GNU开发环境基础4.mp4 │ ├005 - GNU开发环境基础_gcc编译1.mp4 │ ├006 - GNU开发环境基础_gcc编译2.mp4 │ ├007 - GNU开发环境基础_gcc编译3.wmv │ ├008 - GNU开发环境基础_gdb.mp4 │ ├009 - GNU开发环境基础_Makefile1.mp4 │ ├10 - GNU开发环境基础_Makefile2.wmv │ ├11 - GNU开发环境基础_Makefile3.wmv │ ├12 - GNU开发环境基础_Autotools1.mp4 │ ├14 - GNU开发环境基础_Autotools3.mp4 │ ├15 - GNU开发环境基础_EclipseForLinux.mp4 │ ├16 - Linux高级程序_IO操作1.mp4 │ ├17 - Linux高级程序_IO操作2.wmv │ ├18 - Linux高级程序_IO操作3.mp4 │ ├19 - Linux高级程序_IO操作4.wmv │ ├20 - Linux高级程序_IO操作5.mp4 │ ├21 - Linux高级程序_IO操作6.wmv │ ├22 - Linux高级程序_IO操作7.mp4 │ ├23 - Linux高级程序_IO操作8.wmv │ ├24 - Linux高级程序_IO操作9.mp4 │ ├25 - Linux高级程序_IO操作_lock1.wmv │ ├26 - Linux高级程序_IO操作_lock2.wmv │ ├27 - Linux高级程序_IO操作_lock3.mp4 │ ├28 - Linux高级程序_IO操作_dir1.mp4 │ ├29 - Linux高级程序_IO操作_dir2.wmv │ ├30 - Linux高级程序_IO操作_dir3.wmv │ ├31 - Linux高级程序_进程管理1.mp4 │ ├32 - Linux高级程序_进程管理2.mp4 │ ├33 - Linux高级程序_进程管理3.wmv │ ├34 - Linux高级程序_进程管理4.wmv │ ├35 - Linux高级程序_进程管理5.mp4 │ ├36 - Linux高级程序_进程管理6.mp4 │ ├37 - Linux高级程序_进程管理_Daemon1.avi │ ├38 - Linux高级程序_进程管理_Daemon2.avi │ ├39 - Linux高级程序_进程管理_Daemon3.mp4 │ ├40 - Linux高级程序_进程间通信_Pipe1.avi │ ├41 - Linux高级程序_进程间通信_Pipe2.mp4 │ ├42 - Linux高级程序_进程间通信_Pipe3.avi │ ├43 - Linux高级程序_进程间通信_Fifo1.mp4 │ ├44 - Linux高级程序_进程间通信_Fifo2.mp4 │ ├45 - Linux高级程序_进程间通信_Signal1.avi │ ├46 - Linux高级程序_进程间通信_Signal2.avi │ ├47 - Linux高级程序_进程间通信_Signal3.mp4 │ ├48 - Linux高级程序_进程间通信_SHM1.avi │ ├49 - Linux高级程序_进程间通信_SHM2.avi │ ├50 - Linux高级程序_进程间通信_SHM3.mp4 │ ├51 - Linux高级程序_进程间通信_MsgQueue1.avi │ ├52 - Linux高级程序_进程间通信_MsgQueue2.avi │ ├53 - Linux高级程序_进程间通信_Semaphore1.mp4 │ ├54 - Linux高级程序_进程间通信_Semaphore2.avi │ ├55 - 线程_Thread1.avi │ ├56 - 线程_Thread2.mp4 │ ├57 - 线程_Thread3.mp4 │ ├58 - 网络编程_Concept1.mp4 │ ├59 - 网络编程_Concept2.avi │ ├60 - 网络编程_Concept3.mp4 │ ├61 - 网络编程_Concept4.mp4 │ ├62 - 网络编程_TCPIP编程基础1.mp4 │ ├63 - 网络编程_TCPIP编程基础2.avi │ ├64 - 网络编程_TCPIP编程基础3.avi │ ├65 - 网络编程_TCPIP编程基础4.mp4 │ ├66 - 网络编程_TCPIP编程基础5.avi │ ├67 - 网络编程_TCPIP编程基础6.mp4 │ ├68 - 网络编程_HTTP原理1.mp4 │ ├69 - 网络编程_HTTP原理2.avi │ ├70 - 网络编程_HTTP原理3.avi │ ├71 - 网络编程_HTTP原理4 .mp4 │ ├72 - 网络编程_HTTP原理5.mp4 │ ├73 - 网络编程_HTTP原理6.mp4 │ ├74 - 网络编程_Socket内核跟踪1.avi │ └75 - 网络编程_Socket内核跟踪2.mp4 ├ │ ├01 - ARM准备工作及熟悉开发板1.mp4 │ ├02 - ARM准备工作及熟悉开发板2.mp4 │ ├03 - ARM准备工作及熟悉开发板3.avi │ ├04 - ARM准备工作及熟悉开发板4.mp4 │ ├05 - 原理图及电路图1.mp4 │ ├06 - 原理图及电路图2.avi │ ├07 - 原理图及电路图3.mp4 │ ├08 - ARM世界 .mp4 │ ├09 - 开发环境搭建1.mp4 │ ├10 - 开发环境搭建2.avi │ ├100 - 中断流程代码1.mp4 │ ├101 - 中断流程代码2.avi │ ├102 - 中断流程代码3.mp4 │ ├103 - IIC协议介绍1.avi │ ├104 - IIC协议介绍2.mp4 │ ├105 - IIC协议介绍3.mp4 │ ├106 - IIC协议介绍4.avi │ ├107 - IIC协议介绍5.mp4 │ ├108 - IIC协议介绍6.mp4 │ ├109 - watchdog介绍1.mp4 │ ├11 - 补充Linux装Sourceinsight.mp4 │ ├110 - watchdog介绍2.mp4 │ ├111 - RTC介绍.avi │ ├112 - PWM介绍1.mp4 │ ├113 - PWM介绍2.avi │ ├114 - PWM介绍3.mp4 │ ├115 - ADC介绍1.avi │ ├116 - ADC介绍2.mp4 │ ├117 - ARM硬件接口项目介绍1.avi │ ├118 - ARM硬件接口项目介绍2.mp4 │ ├119 - ARM硬件接口项目介绍3.avi │ ├12 - 补充MiniTool烧写开发-11.30更新工具.avi │ ├120 - ARM硬件接口项目介绍4.mp4 │ ├121 - ARM硬件接口项目介绍5.avi │ ├122 - ARM硬件接口项目介绍6.mp4 │ ├123 - ARM硬件接口项目介绍7.mp4 │ ├13 - ARM体系结构-学习方法.mp4 │ ├14 - ARM体系结构-处理器和名词1.mp4 │ ├15 - ARM体系结构-处理器和名词2.avi │ ├16 - ARM体系结构-工具和交叉工具链.mp4 │ ├17 - 处理器模式和片内寄存器1.avi │ ├18 - 处理器模式和片内寄存器2.mp4 │ ├19 - 处理器模式和片内寄存器3.avi │ ├20 - 处理器模式和片内寄存器4.mp4 │ ├21 - ARM汇编寻址模式1.mp4 │ ├22 - ARM汇编寻址模式2.avi │ ├23 - ARM汇编寻址模式3.mp4 │ ├24 - ARM汇编算数操作1.mp4 │ ├25 - ARM汇编算数操作2.avi │ ├26 - ARM汇编算数操作3.mp4 │ ├27 - ARM汇编算数操作4.mp4 │ ├28 - ARM汇编内存操作1.avi │ ├29 - ARM汇编内存操作2.mp4 │ ├30 - 跳转指令及其它1.mp4 │ ├31 - 跳转指令及其它2.avi │ ├32 - ARM汇编伪指令1.mp4 │ ├33 - ARM汇编伪指令2.avi │ ├34 - ARM汇编伪指令3.avi │ ├35 - ARM汇编伪指令4.mp4 │ ├36 - 混合编程1.avi │ ├37 - 混合编程2.mp4 │ ├38 - ARM硬件基础概述1.avi │ ├39 - ARM硬件基础概述2 .mp4 │ ├40 - ARM硬件基础概述3 .mp4 │ ├41 - ARM硬件基础-SIMD&NEON;.mp4 │ ├42 - ARM硬件基础-Cache1.avi │ ├43 - ARM硬件基础-Cache2.mp4 │ ├44 - ARM硬件基础-MMU1.mp4 │ ├45 - ARM硬件基础-MMU2(1).mp4 │ ├45 - ARM硬件基础-MMU2.mp4 │ ├46 - ARM硬件基础-MMU3.mp4 │ ├47 - ARM异常及中断1 .avi │ ├48 - ARM异常及中断2.mp4 │ ├49 - 第一个裸板试验1.avi │ ├50 - 第一个裸板试验2.mp4 │ ├51 - S5PV210启动原理1.mp4 │ ├52 - S5PV210启动原理2.avi │ ├53 - ARM硬件接口GPIO1.mp4 │ ├54 - ARM硬件接口GPIO2.avi │ ├55 - ARM硬件接口GPIO3.mp4 │ ├56 - ARM硬件接口GPIO4.avi │ ├57 - ARM硬件接口GPIO5.mp4 │ ├58 - ARM硬件接口开发前言.mp4 │ ├59 - ARM硬件接口开发简介.avi │ ├60 - 确定开发板资源1.avi │ ├61 - 确定开发板资源2.mp4 │ ├62 - 确定开发板资源3.avi │ ├63 - 确定开发板资源4.mp4 │ ├64 - 驱动开发板资源5.avi │ ├65 - 驱动开发板资源6.mp4 │ ├66 - 驱动开发板资源7.mp4 │ ├67 - bootloader概述1.mp4 │ ├68 - bootloader概述2 .avi │ ├69 - bootloader概述3.avi │ ├70 - bootloader概述4.mp4 │ ├71 - bootloader概述5.mp4 │ ├72 - C5工程搭建Makefile1.mp4 │ ├73 - C5工程搭建Makefile2.avi │ ├74 - C5工程搭建Makefile3.mp4 │ ├75 - 工程搭建链接脚本录像1.mp4 │ ├76 - 工程搭建链接脚本录像2.avi │ ├77 - 工程搭建C代码点灯录像1.avi │ ├78 - 工程搭建C代码点灯录像2.avi │ ├79 - 工程搭建C代码点灯录像3.mp4 │ ├80 - 通信模型介绍录像1.mp4 │ ├81 - 通信模型介绍录像2.avi │ ├82 - UART协议介绍录像1.mp4 │ ├83 - UART协议介绍录像2.avi │ ├84 - UART协议介绍录像3.avi │ ├85 - UART控制器介绍录像1.mp4 │ ├86 - UART控制器介绍录像2.mp4 │ ├87 - 通过串口发送一个字符录像1.mp4 │ ├88 - 通过串口发送一个字符录像2.avi │ ├89 - 通过串口发送一个字符录像3.avi │ ├90 - 通过串口发送一个字符录像4.mp4 │ ├91 - 通过串口发送一个字符串1.avi │ ├92 - 通过串口发送一个字符串2.mp4 │ ├93 - 中断介绍1.avi │ ├94 - 中断介绍2.mp4 │ ├95 - 中断介绍3.avi │ ├96 - 中断介绍4.mp4 │ ├97 - 中断初始化代码1.mp4 │ ├98 - 中断初始化代码2.avi │ └99 - 中断初始化代码3.mp4 ├ │ ├01 - 开篇的话1.mp4 │ ├02 - 开篇的话2.mp4 │ ├03 - 开篇的话3.mp4 │ ├04 - 内核开发初探1.mp4 │ ├05 - 内核开发初探2.avi │ ├06 - 内核开发初探3.mp4 │ ├07 - Linux内核的基本概念1.mp4 │ ├08 - Linux内核的基本概念2.avi │ ├09 - Linux内核的基本概念3.avi │ ├10 - Linux内核的基本概念4.mp4 │ ├11 - Linux内核的基本概念5.mp4 │ ├12 - Linux驱动程序开发基础1.avi │ ├13 - Linux驱动程序开发基础2.mp4 │ ├14 - Linux驱动程序开发基础3.mp4 │ ├15 - Linux驱动程序开发基础4.avi │ ├16 - Linux驱动程序开发基础5.mp4 │ ├17 - LED设备驱动1.mp4 │ ├18 - LED设备驱动2.mp4 │ ├19 - LED设备驱动3.avi │ ├20 - LED设备驱动4.mp4 │ ├21 - LED设备驱动5.avi │ ├22 - RTC驱动1.mp4 │ ├23 - RTC驱动2.avi │ ├24 - RTC驱动3.avi │ ├25 - RTC驱动4.mp4 │ ├26 - 电容触摸屏驱动1.avi │ ├28 - 电容触摸屏驱动3.avi │ ├29 - 电容触摸屏驱动4.mp4 │ ├30 - 网络设备驱动1.mp4 │ ├31 - 网络设备驱动2.avi │ ├32 - 网络设备驱动3.avi │ ├33 - 网络设备驱动4.mp4 │ ├34 - 网络设备驱动5.avi │ ├35 - 网络设备驱动6.mp4 │ ├36 - 声卡驱动1.mp4 │ ├37 - 声卡驱动2.avi │ ├38 - 声卡驱动3.avi │ ├39 - 声卡驱动4.mp4 │ ├40 - 声卡驱动5.mp4 │ ├41 - 声卡驱动6.mp4 │ ├42 - LCD驱动1.mp4 │ ├43 - LCD驱动2.mp4 │ ├44 - LCD驱动3.avi │ ├45 - LCD驱动4.mp4 │ ├46 - LCD驱动5.avi │ ├47 - LCD驱动6.avi │ ├48 - LCD驱动7.mp4 │ ├49 - LCD驱动8.mp4 │ ├50 - LCD驱动9.avi │ ├51 - LCD驱动10.mp4 │ ├52 - nandflash驱动1.mp4 │ ├53 - nandflash驱动2.mp4 │ ├54 - nandflash驱动3.avi │ ├55 - nandflash驱动4.avi │ ├56 - nandflash驱动5.mp4 │ ├57 - nandflash驱动6.mp4 │ └58 - 驱动课程总结提高.mp4 ├ │ ├linux-3.0.8.tiny210.ok.tar │ ├readme.txt │ ├Sundy-AndroidLowLevel.mmap │ ├tiny210.uboot-2011.06.last.tar │ ├tiny210.uboot-2011.06.ok.tar │ ├底层课程资料包.zip │ ├ │ │ ├EABI-4.3.3_EmbedSky_20100610.tar.bz2 │ │ ├gdb-6.8a.tar.bz2 │ │ ├hypertrm超级终端支持Win7.rar │ │ ├putty.zip │ │ └SecureCRT.rar
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值