文章目录

  • 一、相关知识点(涉及接口、结构体、调用关系等)
    • 1.1 裸机开发步骤与驱动开发过程对比
      • 1.1.1 裸机开发步骤
      • 1.1.2 Linux系统下LED驱动开发步骤
    • 1.2 预备知识:写驱动时涉及的接口、结构体、宏等
  • 二、基本驱动框架
    • 2.1 基本驱动框架实现流程(思路)
    • 2.2 基本驱动框架结构
  • 三、代码实现分析
    • 3.1 Led操作驱动框架代码
    • 3.2 看电路图,找到led分别对应哪个GPIO口
    • 3.3 查看芯片手册,查看需要配置那些寄存器,以及各个寄存器地址
      • 3.3.1 GPIO_OE
      • 3.3.2 GPIO_CLRDATAOUT
      • 3.3.3 GPIO_SETDATAOUT
      • 3.3.4 GPIO1基地址
      • 3.3.5 宏定义寄存器地址
    • 3.4 完成寄存器地址映射与删除寄存器地址映射
    • 3.5 完成led操作功能代码
    • 3.6 将上述步骤中各个功能代码填充到驱动框架中
  • 四、led驱动代码(完整代码,亲测有效)
    • 4.1 led_drv.c
    • 4.2 Makefile
    • 4.3 编译生成.ko文件(make)
  • 五、加载/卸载模块等常用操作(开发板命令行常用操作)
    • 5.1 加载驱动到内核(insmod xxx.ko)
    • 5.2 cat /proc/devices查看设备
    • 5.3 命令rmmod xxx
  • 六、led测试程序【led_app.c】
    • 6.1 led_app.c
    • 6.2 编译,生成可执行文件
  • 七、测试效果
    • 7.1 先将之前加载进内核的led_drv模块卸载掉,执行./led_app
    • 7.2 加载模块,再执行应用程序./led_app
  • 八、github工程连接

前言:最近在学习韦东山老师的arm驱动部分教学,本文记录一下自己对最基本的驱动框架结构的理解,以及最基本的led驱动实现的方法。本博客以米尔科技的rico board开发板为例,完成rico board的led驱动代码的实现过程。

备注:完整代码直接跳转到《led驱动代码(完整代码,亲测有效)》章节或跳转至《github工程链接》章节,中间各个小结的代码为实现过程(不完整),无法正确编译。


一、相关知识点(涉及接口、结构体、调用关系等)

一个软件系统可以分为以下四层:应用程序、库、内核、驱动,借用韦老师一副图,如下图:
即:应用层开发的功能,各个接口会通过库、通过内核调用到底层驱动程序的对应接口,从而执行对应的功能。

1.1 裸机开发步骤与驱动开发过程对比

1.1.1 裸机开发步骤

裸机开发时,我们需要按照如下过程进行开发。

步骤:
1、看电路图,查清楚led在哪个管脚,对应哪个GPIO口
2、看芯片手册,查看需要配置的相关寄存器地址——(主要查看需要配置哪些寄存器可以将GPIO配置成输出模式,地址可以在map地址映射章节找到)
3、看芯片手册,查清楚怎么样能将GPIO配置成输出高电平或低电平。
4、编译代码,烧写到板卡中,重新上电

1.1.2 Linux系统下LED驱动开发步骤

备注:裸机开发与带操作系统的驱动开发对寄存器的操作不同,裸机开发直接操作寄存器地址即可,而带linux操作系统时,必须要进行地址映射(有相关接口可以直接使用),映射完成后,再进行操作即可。

步骤:
1、撰写基本字符驱动框架代码(字符驱动代码框架实现步骤详见第2节:基本驱动框架实现流程
2、看电路图,查清楚led在哪个管脚,对应哪个GPIO口
3、看芯片手册,查看需要配置的相关寄存器地址——(主要查看需要配置哪些寄存器可以将GPIO配置成输出模式,地址可以在map地址映射章节找到)
4、在驱动入口函数与出口函数中完成寄存器地址映射与取消映射
5、将led操作功能代码填充到驱动框架中

1.2 预备知识:写驱动时涉及的接口、结构体、宏等

name 含义
struct file_operations 结构体:该结构体中告诉每一个接口成员指向某个函数指针,指向的函数指针则为要实现的功能
struct class
module_init(lxxx_init); 注册驱动入口接口
module_exit(xxx_exit); 注册驱动出口接口
MODULE_LICENSE(“Dual BSD/GPL”); 声明
xxx_init() 驱动入口函数
xxx_exit() 驱动出口函数
register_chrdev() 注册模块,告诉内核让内核知道有这个模块,并且返回值为自动分配的主设备号
unregister_chrdev() 卸载驱动程序,告诉内核
class_create() 创建一个类
device_create(major, …); 创建一个设备,使用该接口创建设备以后,在板卡中insmod xxx之后,会自动生成/dev/设备,并且会将major主设备号分配给该设备
device_destroy() 删除设备,删除设备号
class_destroy 删除类
ioremap(); 映射寄存器地址
iounmap(); 删除映射关系

二、基本驱动框架

本小结,作为预备知识,先对字符驱动基本框架做一个简要描述,具体从两个方面进行描述,分别是:

  • 2.1 基本驱动框架实现流程(思路)
  • 2.2 基本驱动框架结构

2.1 基本驱动框架实现流程(思路)

撰写驱动框架时,按照以下驱动思路来写即可,后续代码实现,即可对比参照此流程来分析代码。

步骤如下:
1、实现结构体:【xxx_fops】。该结构体内部制定了相关的功能代码接口,从而应用能够知道哪个对应功能在哪个接口中实现。
2、实现步骤1中的相关函数(读写函数)框架,此时不需要实现函数具体细节,函数内部为空即可。
3、驱动入口函数:【xxx_init()】

xxx_init() {(a)在函数前面定义全局变量【int major】,下一步用该变量接收系统自动分配的主设备号。(b)驱动入口函数中实现注册函数,返回值为系统自动分配的主设备号:major = register_chrdev(0,设备名称,结构体);
}

4、驱动入口修饰函数:【modules_init();】
5、驱动出口函数:【xxx_exit();】

xxx_exit() {(a)驱动出口函数中实现卸载函数:【unregister_chrdev(主设备号,设备名称)】
}

6、驱动出口修饰函数:【modules_exit();】
7、给sysfs文件系统提供更多驱动信息,这样上层应用因为【udev机制】便可自动创建设备节点,跟底层驱动代码中自动生成的主设备号保持一致,从而关联起来。(备注:udev机制个mdev机制基本可以认为是同一个东西,mdev是udev的简化版本)

(a)定义结构体class:【static struct class *xxx_class;】
(b)驱动入口函数xxx_init()中增加两个接口:【创建类class_create()】和【创建设备device_create()】
(c)驱动出口函数xxx_exit()中增加两个接口:【删除设备device_destroy()】和【删除类class_destroy()】
(d)注意:步骤b和步骤c中两个接口顺序是相反的!!

8、添加Licence:【MODULE_LICENSE(“Dual BSD/GPL”);】。

2.2 基本驱动框架结构

/* 头文件 */
#include xxxxxxx/* 功能接口1 */
x1()
{...
}/* 功能接口2 */
x2()
{...
}/* 功能接口3 */
x3()
{...
}/* 重要结构体:指定对应功能接口,该结构体中告诉每一个接口成员指向某个函数,指向的函数则为要实现的功能 */
static struct file_operations xxx_fops {.owner    = THIS_MODULE, /* 固定格式 */.其他接口1 = x1,          /* 函数指针:指向x1,功能实现接口1 */.其他接口2 = x2,          /* 函数指针:指向x2,功能实现接口2 */.其他接口3 = x3,          /* 函数指针:指向x3,功能实现接口3 */...
};/* 驱动入口函数 */
xxx_init()
{major = register_chrdev(...);  /* 注册模块,告诉内核让内核知道有这个模块,并且返回值为自动分配的主设备号 */class_create(...);             /* 创建一个类 */device_create(major, ...);     /* 创建一个设备,使用该接口创建设备以后,在板卡中insmod xxx之后,会自动生成/dev/设备,并且会将major主设备号分配给该设备 */ioremap();                     /* 映射寄存器地址 */
}/* 驱动出口函数 */
xxx_exit()
{unregister_chrdev(major, ...);              /* 卸载驱动程序,告诉内核 */device_destroy(xxx_class, MKDEV(major, 0)); /* 删除设备,删除设备号 */class_destroy(xxx_class);                   /* 删除类 */iounmap(); /* 删除映射关系 */
}module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("Dual BSD/GPL");

三、代码实现分析

不管是有系统的驱动代码,还是无系统裸机操作,说到底,最底层始终是对寄存器进行操作,裸机无非是直接操作寄存器,配置好相关寄存器,开始运行程序即可。有linux系统时,区别仅在于,不能直接操作寄存器,需要对寄存器地址进行映射,之后在操作映射后的地址(接口为:remap()和unmap())。

按照第一小节实现思路步骤,与上一章节的基本驱动框架,逐步往下实现

3.1 Led操作驱动框架代码

按照第二小节框架,写出led驱动框架代码如下:
我先计划功能实现如下:使用open、release、write接口实现对GPIO的操作

  • 1、在open函数中实现GPIO寄存器配置为输出功能
  • 2、在release函数中实现GPIO寄存器配置为输出低电平
  • 3、在write函数中实现GPIO寄存器配置为输出高电平
/* 头文件 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>static int major = (-1); /* 初始化为无效值 */
static struct class *led_drv_class;/* 功能接口1 */
static int led_drv_open(struct inode *inode, struct file *file)
{/* 功能:配置GPIO为输出功能,待填充 */return 0;
}/* 功能接口2 */
static int led_drv_release(struct inode *pinode , struct file *pfile)
{/* 功能:配置GPIO为低电平,待填充 */return 0;
}/* 功能接口3 */
static ssize_t led_drv_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{/* 功能:配置GPIO为高电平,待填充 */return 0;
}/* 结构体,对应成员分别指向各个功能接口 */
static struct file_operations led_drv_fops = {.owner   = THIS_MODULE,.open    = led_drv_open,     /* 函数指针:指向x1,功能实现接口1 */.release = led_drv_release,  /* 函数指针:指向x2,功能实现接口2 */.write   = led_drv_write,    /* 函数指针:指向x3,功能实现接口3 */
};/* 驱动入口函数 */
static int led_drv_init(void)
{major = register_chrdev(0, "led_drv", &led_drv_fops); // 注册, 告诉内核,返回值major为自动分配的主设备号led_drv_class = class_create(THIS_MODULE, "leddrv");device_create(led_drv_class, NULL, MKDEV(major, 0), NULL, DEV_NAME); /* insmod xxx后,会自动生成/dev/xyz设备,并且自动给该设备分配主设备号major *//* 映射寄存器地址,待填充 */return 0;
}/* 驱动出口函数 */
static void led_drv_exit(void)
{unregister_chrdev(major, "led_drv");               /* 卸载驱动程序,告诉内核 */device_destroy(led_drv_class, MKDEV(major, 0));class_destroy(led_drv_class);/* 删除映射关系,待填充 */
}module_init(led_drv_init);
module_exit(led_drv_exit);MODULE_LICENSE("Dual BSD/GPL");

3.2 看电路图,找到led分别对应哪个GPIO口

打开电路图,我们看到rico board开发板led分别为D23/D24/D25/D26分别对应GPIO1_24/GPIO1_25/GPIO1_26/GPIO1_27


对应关系如下表格:

led名字 对应GPIO
D23 GPIO1_24
D24 GPIO1_25
D25 GPIO1_26
D26 GPIO1_27

3.3 查看芯片手册,查看需要配置那些寄存器,以及各个寄存器地址

看芯片手册第28.3.4.3章节,如下图:

大致意思是:当GPIO作为输出功能,我们需要配置以下三个寄存器从而让对应GPIO口输出高低电平:

寄存器名称 对应功能
GPIO_OE 配置GPIO口功能
GPIO_CLRDATAOUT GPIO输出低电平寄存器
GPIO_SETDATAOUT GPIO输出高电平寄存器

继续往下看手册,跳转到三个对应寄存器的描述:

3.3.1 GPIO_OE


可以看到两个信息:
1、该寄存器偏移地址为:0x134
2、含义:OE寄存器为使能GPIO输出功能,当复位以后,GPIO输入输出功能不可用,再看表格中的Description可知:当该寄存器写入0时,表示GPIO为输出功能,故我们需要将GPIO_OE的24/25/26/27bit配置为0。

3.3.2 GPIO_CLRDATAOUT

1、该寄存器偏移地址为:0x190
2、寄存器对应位写入0时不影响原功能,写入1时清除GPIO口输出寄存器(GPIO_DATAOUT),即GPIO口会输出低电平

3.3.3 GPIO_SETDATAOUT

1、该寄存器偏移地址为:0x194
2、寄存器对应位写入0时不影响原功能,写入1时置位GPIO口输出寄存器(GPIO_DATAOUT),即GPIO口会输出高电平

3.3.4 GPIO1基地址

在文档中搜索GPIO1,Memory Map章节中搜到GPIO1基地址为0x4804C000

3.3.5 宏定义寄存器地址

根据以上资料,为了代码中操作方便,使用宏定义寄存器地址。

/* 寄存器地址宏定义 */
#define GPIO1_BASE_ADDR           0x4804c000
#define GPIO1_OE_ADDR             ((GPIO1_BASE_ADDR) + (0x134))
#define GPIO1_DATA_OUT_ADDR       ((GPIO1_BASE_ADDR) + (0x13C))
#define GPIO1_CLEAR_DATA_OUT_ADDR ((GPIO1_BASE_ADDR) + (0x190))
#define GPIO1_SET_DATA_OUT_ADDR   ((GPIO1_BASE_ADDR) + (0x194))

3.4 完成寄存器地址映射与删除寄存器地址映射

volatile unsigned long *gpio1_oe = NULL;
volatile unsigned long *gpio1_dataout = NULL;
volatile unsigned long *gpio1_clrdata = NULL;
volatile unsigned long *gpio1_setdata = NULL;/* 映射寄存器地址 */
gpio1_oe      = (volatile unsigned long *)ioremap(GPIO1_OE_ADDR, 32);
gpio1_dataout = (volatile unsigned long *)ioremap(GPIO1_DATA_OUT_ADDR, 32);
gpio1_clrdata = (volatile unsigned long *)ioremap(GPIO1_CLEAR_DATA_OUT_ADDR, 32);
gpio1_setdata = (volatile unsigned long *)ioremap(GPIO1_SET_DATA_OUT_ADDR, 32);/* 删除映射关系 */
iounmap(gpio1_setdata);
iounmap(gpio1_clrdata);
iounmap(gpio1_dataout);
iounmap(gpio1_oe);

3.5 完成led操作功能代码

由于功能简单,此处不再赘述,直接看最后一章节完整代码即可,各个功能都有注释。

3.6 将上述步骤中各个功能代码填充到驱动框架中

可以将上述所有功能代码全部封装成接口,因为功能比较简单,这里我没有做封装,直接将代码填充到驱动框架中,完整代码如下一章节。

四、led驱动代码(完整代码,亲测有效)

备注:附github工程连接:https://github.com/Warrior-Asa/led_drv

4.1 led_drv.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>#define DEV_NAME "led"/* led */
#define GPIO1_BASE_ADDR           0x4804c000
#define GPIO1_OE_ADDR             ((GPIO1_BASE_ADDR) + (0x134))
#define GPIO1_DATA_OUT_ADDR       ((GPIO1_BASE_ADDR) + (0x13C))
#define GPIO1_CLEAR_DATA_OUT_ADDR ((GPIO1_BASE_ADDR) + (0x190))
#define GPIO1_SET_DATA_OUT_ADDR   ((GPIO1_BASE_ADDR) + (0x194))volatile unsigned long *gpio1_oe = NULL;
volatile unsigned long *gpio1_dataout = NULL;
volatile unsigned long *gpio1_clrdata = NULL;
volatile unsigned long *gpio1_setdata = NULL;
/* led end */static int major = (-1); /* 初始化为无效值 */
static struct class *led_drv_class;static int led_drv_open(struct inode *inode, struct file *file)
{/* 将oe寄存器bit25、26、27置0:表示output功能 */*gpio1_oe &= ((~(1<<25)) | (~(1<<26)) | (~(1<<27)));return 0;
}static int led_drv_release(struct inode *pinode , struct file *pfile)
{/* 将setdata寄存器bit25、26、27置1,表示输出高电平 */*gpio1_setdata |= ((1<<25) | (1<<26) | (1<<27));return 0;
}static ssize_t led_drv_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{/* 将cleardata寄存器bit25、26、27置1,表示清除输出,则输出低电平 */*gpio1_clrdata |= ((1<<25) | (1<<26) | (1<<27));return 0;
}static struct file_operations led_drv_fops = {.owner   = THIS_MODULE,.open    = led_drv_open,.release = led_drv_release,.write   = led_drv_write,
};/* 驱动入口函数 */
static int led_drv_init(void)
{major = register_chrdev(0, "led_drv", &led_drv_fops); // 注册, 告诉内核,返回值major为自动分配的主设备号led_drv_class = class_create(THIS_MODULE, "leddrv");device_create(led_drv_class, NULL, MKDEV(major, 0), NULL, DEV_NAME); /* insmod xxx后,会自动生成/dev/xyz设备,并且自动给该设备分配主设备号major *//* 映射寄存器地址 */gpio1_oe      = (volatile unsigned long *)ioremap(GPIO1_OE_ADDR, 32);gpio1_dataout = (volatile unsigned long *)ioremap(GPIO1_DATA_OUT_ADDR, 32);gpio1_clrdata = (volatile unsigned long *)ioremap(GPIO1_CLEAR_DATA_OUT_ADDR, 32);gpio1_setdata = (volatile unsigned long *)ioremap(GPIO1_SET_DATA_OUT_ADDR, 32);return 0;
}/* 驱动出口函数 */
static void led_drv_exit(void)
{unregister_chrdev(major, "led_drv"); /* 卸载驱动程序,告诉内核 */device_destroy(led_drv_class, MKDEV(major, 0));class_destroy(led_drv_class);/* 删除映射关系 */iounmap(gpio1_setdata);iounmap(gpio1_clrdata);iounmap(gpio1_dataout);iounmap(gpio1_oe);
}module_init(led_drv_init);
module_exit(led_drv_exit);MODULE_LICENSE("Dual BSD/GPL");

4.2 Makefile

KERN_DIR = ~/mier/Kernel/linux-3.12.10-ti2013.12.01all:make -C $(KERN_DIR) M=`pwd` modulesclean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderobj-m    += led_drv.o

4.3 编译生成.ko文件(make)

至此代码写完,执行make命令,会生成.ko文件

五、加载/卸载模块等常用操作(开发板命令行常用操作)

将上步骤生成【led_drv.ko】文件拷贝到开发板中。

5.1 加载驱动到内核(insmod xxx.ko)

在开发板中执行命令insmod led_drv.ko,使用lsmod命令查看当前已加载的模块,可以看到led_drv模块已经加载到内核中

5.2 cat /proc/devices查看设备

使用命令cat /proc/devices可以查看该模块分配的设备号为243,左边一列数字为主设备号,右边字符串为设备名称

5.3 命令rmmod xxx

使用命令rm led_drv可以卸载该模块

六、led测试程序【led_app.c】

6.1 led_app.c

现在回到linux开发环境中,写一套led驱动测试app代码,代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>#define DEV_NAME "/dev/led"int main(int argc, char const *argv[])
{int fd = 0;int ret = 0;int led_status = 0;fd = open(DEV_NAME,O_RDWR); /* 根据设备名称打开设备 */if (fd<0) {printf("You are wrong!!!\n");exit(0);}ret = write(fd, &led_status, 1); /* 向设备中写入0,灯亮 */printf("Set led GPIO to %d\n",ret);sleep(5);close(fd);return 0;
}

6.2 编译,生成可执行文件

使用交叉编译工具链进行编译,生成可执行文件led_app

将该可执行文件拷贝到开发板中。

七、测试效果

7.1 先将之前加载进内核的led_drv模块卸载掉,执行./led_app

如下图,因为内核中没有自己写的led_drv驱动模块,所以应用程序打开/dev/led时打开失败,提示you are wrong!

7.2 加载模块,再执行应用程序./led_app

先将自己写的驱动模块加载到内核中,在执行测试程序./led_app,可以看到应用正常执行,设备正常打开,观察开发板,led亮5秒之后熄灭,测试成功


八、github工程连接

github工程连接:https://github.com/Warrior-Asa/led_drv

linux驱动之一、LED驱动(驱动代码小结附:github代码链接)相关推荐

  1. 2022-10-20 Linux自带LED灯驱动gpio-leds 心跳呼吸灯heartbeat、定时器timer的使用

    一.测试环境:Android 9 系统 二.驱动文件在\kernel\drivers\leds\leds-gpio.c 三.dts的官方配置文档 kernel\Documentation\device ...

  2. Python 爬虫的工具列表( 附Github代码下载链接)

    Python 爬虫的工具列表( 附Github代码下载链接) 这个列表包含与网页抓取和数据处理的Python库 网络 通用 urllib -网络库(stdlib). requests -网络库. gr ...

  3. yolov3网络结构图_目标检测——YOLO V3简介及代码注释(附github代码——已跑通)...

    GitHub: liuyuemaicha/PyTorch-YOLOv3​github.com 注:该代码fork自eriklindernoren/PyTorch-YOLOv3,该代码相比master分 ...

  4. 顶会快讯|5篇AAAI2020相关论文抢先看(附GitHub代码地址)

    Top AI Papers with Code 整理:Marlin 内容:5篇AAAI论文简介+GitHub地址 本文继续整理了AAAI 2020在Github的热门项目,并按照整理时star的数量进 ...

  5. python cnn模型_CNN系列模型发展简述(附github代码——已全部跑通)

    目录: 1 LeNet 2 AlexNet 3 VGG 4 GoogLeNet 5 ResNet 6 DenseNet 7 Non-Local Networks 8 Deformable Convol ...

  6. 用LSTM做文本情感分类(以英文为例)附github代码

    一 获得规整的数据集 1 原始数据预处理: 去除标点符号 .去停用词.大小写转换等 获得处理后的m条评价记录,reviews=["is good", "very happ ...

  7. word2vec的原理及实现(附github代码)

    目录 一.word2vec原理 二.word2vec代码实现 (1)获取文本语料 (2)载入数据,训练并保存模型 ①  # 输出日志信息 ②  # 将语料保存在sentence中 ③  # 生成词向量 ...

  8. 双麦克风语音去混响算法C代码实现(附github项目链接)

    1. 算法依据 算法的依据是论文<Multi-Channel Linear Prediction Speech Dereverberation Algorithm Based on QR-RLS ...

  9. 阿里妈妈广告点击转化率(CTR)预估项目(附github代码)

    赛题与数据:https://tianchi.aliyun.com/competition/entrance/231647/introduction?spm=5176.12281957.1004.3.3 ...

最新文章

  1. 0040-如何重置Cloudera Manager的admin密码
  2. C++入门课程系列:基础知识篇(1)
  3. React开发(125):ant design学习指南之form中的hasFeedback
  4. Pycharm新建文件时自动添加基础信息
  5. java中string的方法_java中String的常用方法
  6. python自动化测试常见面试题二_思考|自动化测试面试题第二波
  7. java 写传奇游戏吗,文字版传奇游戏
  8. 文件在IDEA中已进行Git的Commit操作,使其从暂存区提交到本地仓库,但是未Push到远程仓库,此时进行Pull操作出现代码冲突
  9. expdp 字符集从ZHS16GBK到AL32UTF8
  10. EasyCHM(CHM电子书制作工具) v3.84.545 绿色版
  11. win10计算机更新后网络卡,win10更新后很卡怎么办
  12. beyond compare this license key has been revoked
  13. 适用场景:All kinds of GCs
  14. Power Apps配置安全角色和对象权限
  15. MIKE 21 教程 2.11 结果输出查看,使用,分析与三维河道的制作
  16. 【硬件】标准阻值的由来
  17. 2015移动互联网App行业发展趋势绿皮书
  18. 电磁波和无线电是什么
  19. 如何清空linux的DNS缓存,如何清空DNS缓存方法/命令
  20. 申论中关于经济问题预设答案示例

热门文章

  1. ios服务器需要开启ipv6的支持,关于ios苹果APP审核 支持IPv6的问题解答
  2. 正向代理和反向代理区别
  3. 频繁更新背后,微信究竟在思考什么?
  4. basler恢复出厂设置_实现图像实时采集(使用BaslerSDK)-C
  5. Minimit Anima – 硬件加速的 CSS3 动画插件
  6. 如何完成一次快速的查询
  7. 力扣 532. 数组中的 k-diff 数对
  8. 全球最大电音音乐节Tomorrowland | FTX Europe将与电子音乐节Tomorrowland合作
  9. 《C++ Primer》学习笔记
  10. excel表格汇总怎么操作