打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
嵌入式知识Linux驱动开发基础



驱动基本概念

设备

计算机最基本的三个组成部分:CPU、内存及其输入输出(I/O)设备。我们说的设备驱动中的设备就是输入输出设备。

常见的设备有:键盘、鼠标、串口、声卡、显卡、网卡、SD、flash、IDE、USB、PCI….

CPU与这些设备的接口就是输入/输出。CPU从这些设备上获取数据叫做输入,CPU将数据写入到设备上就是输出。例如对硬盘的读写。

以上的设备中键盘、鼠标是我们常见的数据设备。用来接受用户的输入。串口是一个数据传输设备,工作原理相对简单而且工作稳定。

声卡、显卡是常见的多媒体设备,主要是输出信息给用户。

网卡是我们常见的网络设备,也是计算机里非常重要的设备之一。它的作用主要用来在计算机之间的通讯。

SD卡、flash、IDE都是存储设备,具有容量较大、断电数据不丢失等特点。

USB、PCI都是总线协议驱动,他们的驱动不针对详细设备。注意:这里USB总线不包含USB设备(例如:USB鼠标驱动就是一个设备,而不是一个总线驱动)。

设备驱动的一般结构:

SoC (主芯片 -> 设备控制器 -> 外设引脚---------------------------------------设备相关芯片 (DM9000/WM8960/MAX3232)---------------------------------------设备接插件 (RJ45/Speaker/DB9)

设备驱动

我们可以写个简单的驱动来访问设备。可以不在任何具有操作系统的裸机上,也可以在bootloader里。例如:串口,网卡等。 但常见的驱动运行在Linux,Windows操作系统上。我们这节课要研究的就是Linux操作系统上的驱动。

Linux操作系统的驱动与裸机(或者bootloader) 上的驱动有很多的不同。

* 分层
要考虑与应用层的接口,例如:应用程序获取键盘输入;

* 并发
考虑多用户,例如:几个程序都在访问串口;

* 协议
考虑其他的协议,例如:网络协议;

等等还有很多细节区别。关于这个区别的细节后面章节将会有描述。

设备、驱动和操作系统三者之间的关系是:

* 驱动是提供操作系统访问硬件的接口;
* 设备可以通过产生中断通知操作系统有数据到来或者发送;
* 驱动是操作系统内核和硬件之间的一个中间接口和媒介;
* 内核通过驱动来最终控制硬件;
* 操作系统中的驱动和设备的关系是一一对应的;
* 应用和驱动是一对多关系;

从图中我们可以看出应用并不是直接和设备驱动进行交互,而是通过抽象层统一的系统调用接口和驱动交互。

操作系统中的驱动的任务

* 具有一般驱动的操作功能:初始化设备,读写设备;
* 将设备的数据分配给应用;例如:网卡驱动,控制台驱动;
* 将用户数据分配给设备;例如:读写硬盘上的文件;

用户程序和操作系统,驱动程序之间是如何实现关联的? 这就需要了解设备文件这个概念。

设备文件

Unix(Linux是类Unix系统)操作系统从一开始就将设备看作文件,通过操作文件的接口统一操作设备。 Linux上大部分设备都有对应的设备文件; 应用程序可以通过设备文件访问设备。

Linux通过设备驱动程序为应用程序提供了统一抽象的接口,从而隐藏了大量不同设备之间的区别和细节。 在Linux中对硬件设备的操作和通常的文件一样,利用标准的文件操作可以对设备上进行打开、关闭、读取或者写入操作。 系统中的每个设备由“设备特殊文件”来代表。 通过/dev访问驱动程序,/dev目录下的文件可用来访问驱动程序

ls /dev/

常用设备文件名

/dev/hda 系统中的第一个IDE硬盘/dev/hdc 光驱/dev/sdb 系统中的u盘/dev/mmcblk 系统中的sd卡/dev/ttyS0 PC 串口设备/dev/ttySAC0 板子上的串口设备/dev/tty0-6 虚拟控制台/dev/zero 软件设备/dev/null 空设备/dev/urandom 随机数字/dev/fb0 LCD显示屏/dev/dsp 音频声卡

常用设备文件操作方法

重定向符 > 写设备

向串口写入数据:echo data > /dev/ttyS0  向LCD写入数据:cat sunflower.bmp > /dev/fb0  向声卡写入数据:cat ringing.wav > /dev/dsp  

cat 命令 读设备

从串口读取数据: cat /dev/ttyS0 (PC Linux)从串口读取数据: cat /dev/ttySAC0 (板子 Linux)

dd 命令 读写设备

将文件写入串口中  dd if=1.txt of=/dev/ttyS0  写入0x002.txt中,每次读写的数据量是512个字节,写入2次  dd if=/dev/zero	of=2.txt bs=512 count=2  随机显示  dd if=/dev/uramdom of=/dev/fb0  播放 1.wav 文件dd if=1.wav of=/dev/dsp查找 wav 文件及其大小的命令  find . -name *.wav -exec ls -l {} \;   

硬件基础知识

处理器

内核

* 主流处理器体系结构
ARM
MIPS
PowerPC
68K/ColdFire
X86

* 冯·诺伊曼结构和哈佛结构
冯·诺伊曼结构
程序指令存储器和数据存储器合并在一起的存储器结构。
程序指令存储地址和数据存储地址指向同一个存储器的不同物理位置,
因此程序指令和数据的宽度相同。

哈佛结构 将程序指令和数据分开存储,指令和数据可以有不同的数据宽度。 独立的程序总线和数据总线,分别作为 CPU 与每个存储器之间的专用通信路径,具有较高的执行效率。
  • RISC vs CISC RISC
    精简指令集计算机,减少指令条数、指令单周期执行。
  • CISC
    复杂指令集计算机,指令复杂,指令周期长。
  • 控制器和特殊功能寄存器
  • * 常用控制器
    内存控制器 MemC
    中断控制器 IntC
    定时器 Timer
    DMA控制器 DMAC
    串口控制器 UARTC
  • * 特殊功能寄存器
    控制类 CON, CFG, DIV, MSK
    数据类 DAT, TXH/RXH, CNTO
    状态类 STAT, PND
  • 存储MMU 虚拟地址和物理地址
  • MMU
    Memory Management Unit 存储管理单元
  • CP15
    ARM 协处理器
  • TLB Translation Lookaside Buffer,即转换旁路缓存,是转换表的Cache,因此也经常被称为“快表” 。
  • ref: http://blog.sina.com.cn/s/blog_5d6836440100bfgg.html
    http://www.cnblogs.com/timkyle/archive/2012/03/09/2388384.html
  • MMU/Core/SoC/SDRAM 之间的关系
    ProcessCore = ALU + Regsister(PC, R0-R15, CPSR)
    CORE = ProcessCore + CP15 + MMU + Cache
    SoC = CORE + SFR + iROM + iRAM
    SoC <–bus–> SDRAM
  • * 重要结论
    无论在用户应用程序,还是在内核模块中,打印出的变量(全局和局部),函数名,代表的都是虚拟地址。
    裸板编程中用到的地址,从数据手册中得到的地址,都是没有启用MMU的,代表的都是物理地址。
    LDR/STR 命令中,涉及到的内存地址,从本质上说,都是虚拟地址;
    当MMU没有启用的情况下,上面的虚拟地址就等于物理地址;如果启动MMU,这些虚拟地址就会被映射为不同的物理地址。

内核空间和用户空间

  1. X86 Linux 内存设计
    0 - 3G 用户空间 (0x0 - 0xC0000000)
    3G - 4G 系统空间 (0xC0000000 - 0xFFFFFFFF)
  2. ARM Linux 内存设计
    0 - (3G-16M) 用户空间 (0x0 - 0xBF000000)
    (3G-16M) - 3G Kernel Modules (0xBF000000 - 0xC0000000)
    3G - 4G 系统空间 (0xC0000000 - 0xFF000000)

ref: http://blog.csdn.net/hzpeterchen/article/details/5363518

常见接口和总线

* 串口

  • I2C总线
  • USB总线
  • ISA总线
  • PCI总线 * 以太网接口

安装交叉编译器

http://github.com/limingth/ARM-Tools/tree/gh-pages/dev

tar zxvf arm-linux-gcc-4.5.1-v6-vfp-20120301.tgz -C /usr/binexport PATH=$PATH:your-install-dirvi ~/.bashrc	-> 修改 PATH 环境变量which arm-linux-gcc

编写应用程序

/* led.c */#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/ioctl.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>int main(int argc, char **argv){ int on; int led_no; int fd; if (argc != 3 || sscanf(argv[1], '%d', &led_no) != 1 || sscanf(argv[2],'%d', &on) != 1 || on < 0 || on > 1 || led_no < 0 || led_no > 3) { fprintf(stderr, 'Usage: leds led_no 0|1\n'); exit(1); } fd = open('/dev/leds0', 0); if (fd < 0) { fd = open('/dev/leds', 0); } if (fd < 0) { perror('open device leds'); exit(1); } ioctl(fd, on, led_no); close(fd); return 0;}

编译生成可执行程序

arm-linux-gcc led.c -o ledfile led

搭建测试环境

方法1 串口上传

$ rx xxx (通过xmodem协议) xmodem -> 2 spaces -> 1 space + enter

方法2 ftp 上传

配置网络连通

1. 有ip吗?没有则用ifconfig;	sudo /etc/init.d/networking restart2. 能ping通自己ip吗?3. 能ping通网关ip吗?	不能则换网线试试;4. 能ping通 8.8.8.8 吗?	不能则sudo route add default gw 192.168.x.x 设置默认网关;5. 能ping通www.google.com 吗? 	不能则修改 /etc/resolv.conf 配置dns ,添加nameserver 8.8.8.8

设置开发板 ip 地址

vi /etc/eth0-settingIP = 192.168.0.201Gateway = 192.168.0.1DNS = 192.168.0.1

设置主机 ip 地址

sudo ifconfig eth0 192.168.0.200

ftp 上传

$ ftp 192.168.0.201Name: rootPassword: rootftp> binaryftp> put ledftp> quit

ftp 脚本
vi ftp.sh

#!/bin/shDIR=$1FILE=$2ftp -i -in <<!open 192.168.0.201 21user username passwordcd /homelcd $DIR         binary put $FILEbye!ls -l $DIR/$FILE

test ftp.sh

chmod 777 ftp.sh./ftp.sh leds led (leds is dir name, led is file name)./ftp.sh . led (if ftp.sh is in same diretory as led file)

开发调试流程

编译应用程序

$ make led$ make buttons_test$ make pwm_test

编译设备驱动

/* mini210_leds.c */#include <linux/kernel.h>#include <linux/module.h>#include <linux/miscdevice.h>#include <linux/fs.h>#include <linux/types.h>#include <linux/moduleparam.h>#include <linux/slab.h>#include <linux/ioctl.h>#include <linux/cdev.h>#include <linux/delay.h>#include <mach/gpio.h>#include <mach/regs-gpio.h>#include <plat/gpio-cfg.h>#define DEVICE_NAME 'leds'static int led_gpios[] = { S5PV210_GPJ2(0), S5PV210_GPJ2(1), S5PV210_GPJ2(2), S5PV210_GPJ2(3),};#define LED_NUM ARRAY_SIZE(led_gpios)static long mini210_leds_ioctl(struct file *filp, unsigned int cmd, unsigned long arg){ switch(cmd) { case 0: case 1: if (arg > LED_NUM) { return -EINVAL; } gpio_set_value(led_gpios[arg], !cmd); //printk(DEVICE_NAME': %d %d\n', arg, cmd); break; default: return -EINVAL; } return 0;}static struct file_operations mini210_led_dev_fops = { .owner = THIS_MODULE, .unlocked_ioctl = mini210_leds_ioctl,};static struct miscdevice mini210_led_dev = { .minor = MISC_DYNAMIC_MINOR, .name = DEVICE_NAME, .fops = &mini210_led_dev_fops,};static int __init mini210_led_dev_init(void) { int ret; int i; for (i = 0; i < LED_NUM; i++) { ret = gpio_request(led_gpios[i], 'LED'); if (ret) { printk('%s: request GPIO %d for LED failed, ret = %d\n', DEVICE_NAME, led_gpios[i], ret); return ret; } s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT); gpio_set_value(led_gpios[i], 1); } ret = misc_register(&mini210_led_dev); printk(DEVICE_NAME'\tinitialized\n'); return ret;}static void __exit mini210_led_dev_exit(void) { int i; for (i = 0; i < LED_NUM; i++) { gpio_free(led_gpios[i]); } misc_deregister(&mini210_led_dev);}module_init(mini210_led_dev_init);module_exit(mini210_led_dev_exit);MODULE_LICENSE('GPL');MODULE_AUTHOR('FriendlyARM Inc.');

下载至开发板上

ftp.sh	网络传输rx 串口传送

加载驱动

insmod led.koprintk 输出调试信息

测试应用

调用 ioctl 点亮 led 灯./led编译 buttons_test.c ,运行按键测试程序./buttons_test编译 pwm_test.c ,运行蜂鸣器测试程序./pwm_test

编译驱动内核模块

$ tar zxvf linux-2.6.35.7-20120829.tar.gz$ cd linux-2.6.35.7/$ ls Process: kernel/arch Memery: mm/ File: fs/ Device: drivers/ Net: net/$ du -sh $ find . -name *.c | wc -l$ make menuconfig$ sudo aptitude search ncurses$ sudo apt-get install ncurses-dev$ make

课堂小练习

完成一个 跑马灯 的应用程序。读取用户按键编号 n,作为启动 跑马灯 之后的循环次数。要求每次按键按下时,蜂鸣器播放一个对应频率的按键音,循环结束后,蜂鸣器播放一个结束音。
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
如何查看linux下串口是否可用?串口名称等?
Linux终端tty
第十四章 Linux模块与设备管理
linux驱动基础开发1——linux 设备驱动基本概念
tty驱动程序框架
从串口驱动到Linux驱动模型
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服