电子科技大学
目录
实验名称 :Linux驱动程序开发
一、实验目的
二、实验内容
三、实验步骤
五、实验总结
六、实验思考题
课程名称 微处理器与嵌入式系统
实验名称 :Linux驱动程序开发
一、实验目的
1.掌握Linux系统下设备驱动程序的作用与编写技巧
2.掌握Linux驱动程序模块加载和卸载的方法
3.了解串口驱动的原理和工作方式
4.了解串口驱动的原理和工作方式
二、实验内容
1基于ARM的模块方式驱动程序实验2基于ARM的串口驱动实验
三、实验步骤
Led驱动
1.在vivado里搭建好硬件工程,包括:led、sw和uart模块。(详见实验2)
2.搭建好后生成比特流文件,利用SDK软件将fsbl文件、比特流文件、u-boot文件生成boot.bin启动文件。(详见实验2)
3.在driver_code/led目录下找到ees331_led.c源文件,双击打开源文件,也可以通过终端命令gedit 打开:
编写led驱动源代码,修改三个部分:1)添加led的地址:/* Modify the address to your peripheral */#define ees331_led_PHY_ADDR /* Modify the address to your peripheral */led驱动的初始化函数为如下:int __init ees331_led_init(void){int ret;GPIO_Regs = ioremap(ees331_led_PHY_ADDR, 32);ret = misc_register(&ees331_led_dev); if (ret) { printk("ees331_led:[ERROR] Misc device register failed\n"); return ret; }, printk("ees331_led: success! Module init complete\n"); iowrite32(255, GPIO_Regs); return 0; /* Success */}2)添加控制函数的代码,如下:static int ees331_led_ioctl(struct file *filp, unsigned int reg_num, unsigned long arg){/* Add your code here */ //通过led地址给led寄存器赋值/* Add your code here */ return 0;}3)在驱动的文件操作结构体添加代码,如下static const struct file_operations ees331_led_fops ={ .owner = THIS_MODULE,/* Add your code here */ .open =, .release =, .read =, .unlocked_ioctl =, /* Add your code here */};4.打开Makefile 文件,修改其中部分,加入交叉编译器和内核源码树目录,如下。ifneq ($(KERNELRELEASE),)obj-m := ees331_led.oelseifeq ($(TARGET),)TARGET := $(shell uname -r)endifPWD := $(shell pwd)/* Add your code here */CC =KDIR ?=/* Add your code here */default:@echo $(TARGET) > module.target$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modulesclean:@rm -f *.ko *.o modules.order Module.symvers *.mod.? .ees331_led*.* *~@rm -rf .tmp_versions module.targetinstall: ees331_led.koinstall --mode 0644 ees331_led.ko /lib/modules/$(shell cat module.target)/kernel/drivers/char//sbin/depmod -a $(shell cat module.target)ees331_led.ko:$(MAKE)endif在需要添加部分加入交叉编译器和内核源码树目录$(CROSS_COMPILE)gcc、/home/zynq/Downloads/2016.3/linux-xlnx-xilinx-v2016.35.虚拟机中打开一个终端,在led目录下执行make命令,生成.ko驱动模块文件。make6.将测试文件ledtest.c进行交叉编译,(或者在开发板上执行gcc命令),生成可执行文件ledtest。arm-linux-gnueabihf-gcc ledtest.c -o ledtest7.在win7系统里打开putty软件,选择对应的com口,波特率选择为115200。8.putty的窗口里,进入到arm-linux系统里,将ees331_led.ko和 ledtest文件拷贝到SD卡根文件系统的root目录下。scp zynq@192.168.1.12:/home/zynq/driver_code/led/ledtest /rootscp zynq@192.168.1.12:/home/zynq/driver_code/led/ees331_led.ko /root9.putty的窗口里,在/root(~)目录下执行insmod 命令,将驱动模块动态加载到内核中。insmod ees331_led.ko驱动加载成功后,在/dev/目录中用ls命令可以查看到相应的设备。ls /dev/putty的窗口里,返回到/root目录,在/root(~)目录下运行测试程序ledtest,putty的ARM-linux 操作系统的命令行输入:./ledtestputty的窗口里,程序会提示:please enter the led status,输入与希望显示的led 状态对应的ledstatus 值(输入十进制值即可),观察led 的显示情况。putty的窗口里,卸载led驱动模块:rmmod ees331_ledUART驱动1.在vivado里搭建好硬件工程,包括:led、sw和uart模块。2.搭建好后生成比特流文件,利用SDK软件将fsbl文件、比特流文件、u-boot文件生成boot.bin启动文件。3.在driver_code/BOOT相应目录下找到devicetree.dts源文件,打开源文件(图中的目录是devicetree目录,实际是BOOT),修改设备树文件,如下:ees331_uart@42c00000 {clock-names = "ref_clk";clocks = "clkc 0";compatible = "xlnx,xps-uartlite-1.00.a";current-speed = <0x2580>;device_type = "serial";port-number = <0x1>;/* Add your code here */reg = <>; //寄存器的起始地址 和偏移量xlnx,baudrate = <>; //波特率xlnx,data-bits = <>; //数据位宽xlnx,odd-parity = <>; //奇偶校验位interrupt-parent=<>; //中断节点数interrupts=<>; //中断信息/* Add your code here */xlnx,s-axi-aclk-freq-hz-d = "100.0";xlnx,use-parity = <0x0>;};在注释部分增加设备树信息。4.在虚拟机终端中,将设备树文件dts转换成dtb格式,并通过读卡器将devicetree.dtb拷贝到SD里的BOOT分区里:dtc -I dts -O dtb -o devicetree.dtb devicetree.dts在driver_code/uartlite目录下修改测试程序uart.c。int main(void) { int fd,ret; /* Add your code here *///1.加入设备名称;2.填写自己的学号 char *uart = ""; char buffer_out[] = ""; /* Add your code here */ char buffer_in[512]; memset(buffer_in,0,512); fd = open(uart,O_RDWR | O_NOCTTY); if(fd == -1) { printf("%s open failed\n",uart);return -1; } printf("%s open success\n",uart); ret = set_opt(fd,9600,8,'N',1); if(ret == -1) { exit(-1); } ret = write(fd,buffer_out,strlen(buffer_out)); printf("your number sent %d\n",ret); while(1) { /* Add your code here *///1.主程序实现接收字符,并把接收到的字符在发送出去/* Add your code here */ } close(fd); } 6.对提供的测试程序uart.c 进行交叉编译,生成可执行文件uart,在终端下输入以下命令:cd /home/zynq/driver_code/uartlitesudo su (密码:1)source zynq-env.sh arm-linux-gnueabihf-gcc uart.c -o uart7.对驱动文件进行编译,生成驱动模块uartlite.ko,在同一个终端下继续输入命令:make8.在win7系统里打开putty软件,选择对应的com口,波特率选择为115200。9.putty的窗口里,将uartlite.ko和uart文件拷贝到SD卡的root目录下。scp zynq@192.168.1.12:~/driver_code/uartlite/uart /rootscp zynq@192.168.1.12:~/driver_code/uartlite/uartlite.ko /root10.putty的窗口里,进入到arm-linux系统里,在/root(~)目录下执行insmod 命令,将驱动模块动态加载到内核中。insmod uartlite.ko驱动加载成功后,在/dev/目录中用ls命令可以查看到相应的设备。ls /dev/11.用双头usb线将开发板上PL的串口(5上)与上位机连接,打开串口助手,选择对应的com口与波特率9600。12..putty的窗口里,在/root(~)目录下运行测试程序uart,putty的ARM-linux 操作系统的命令行输入:./uartlite
四、实验结果
基于ARM的模块方式驱动程序实验关键代码: |
|
基于ARM的模块方式驱动程序实验结果: |
|
实验现象:在putty运行相关程序后,在putty窗口输入相应的数字,板子上相应的灯会点亮。
基于ARM的串口驱动实验关键代码: |
|
基于ARM的串口驱动实验实验结果: |
|
实验现象解释:可以看到在实验当中,连接上之后,首先会发送自己的学号,然后在输入1234之后也会接收到消息,当输入有x字符后,程序会自动结束。
五、实验总结
1.在进行数码管的实验当中,利用linux系统进行驱动程序的编写,熟悉了Linux系统下的指令的编写,认识到了linux系统下的文件的操作方式,熟悉了虚拟机的使用。
2.在实验当中,深入了解了串口通信的原理,理解了波特率和端口的强大作用。
3.学习到了uart驱动,了解了设备和驱动程序之间的关系
4.理解了uart的测试程序,交叉编译后运行,进行串口通信,学习到了串口下相关接口函数使用。
六、实验思考题
基于ARM的模块方式驱动程序实验
1.驱动里led的地址是怎么查看的?
可以在硬件工程里面查看led的地址。
2.模块化的最大优点是什么?
每个模块可以实现不同的功能,方便团队之间分工协作。
3. printk()函数的作用是什么,怎么查看printk函数打印的消息?
printk ()函数的作用是将printk ()的内容输出到控制台。在shell中使用dmesg指令就可以查看printk函数打印的消息。1在终端下输入:
2 while true
3 do
4 sudo dmesg -c
5 sleep 1
6 done
4.文件操作结构体struct file_operations的作用是什么?
用来存储驱动内核模块提供的对设备进行各种操作的函数的指针。
结构体file_operations在头文件 linux/fs.h中定义,用来存储驱动内核模块提供的对设备进行各种操作的函数的指针。该结构体的每个域都对应着驱动内核模块用来处理某个被请求的事务的函数的地址。
每一个设备文件都代表着内核中的一个file结构体。该结构体在头文件linux/fs.h定义。file结构体是内核空间的结构体,这意味着它不会在用户程序的代码中出现。它绝对不是在glibc中定义的FILE。FILE自己也从不在内核空间的函数中出现。。
基于ARM的串口驱动实验
1. vivado里uart的波特率是怎么查看和修改?
在uart_register_driver中把 normal->init_termios.c_cflag改成:
normal->init_termios.c_cflag = B1152000 | CS8 | CREAD | HUPCL | CLOCAL;
而 B115200为0x00001002,即.c_cflag 0~3bit位十进制值为2,那么在tty_termios_baud_rate中会cbaud += 15后 cbaud为17,结baud_table[17] = 115200;在msm_hs_set_bps_locked中有case 115200: msm_hs_write(uport, UART_DM_CSR, 0xcc); 而uport->uartclk = 7372800;0xc的分频系数为 64,那么7372800/64=115200.
2. 设备树的作用是什么,与驱动程序有什么联系?设备树得作用是:描述板卡上的硬件资源信息
与驱动程序的联系是:在kernel 3.0以及之后的版本,都是采用设备树的方法实现驱动与设备之间的联系。将设备改为设备树实现,解决了总线方法中代码冗余多的问题。
设备树方法只需要在总线方法的基础上稍微修改一下。
3. 文件操作结构体struct file_operations的作用是什么?用来存储驱动内核模块提供的对设备进行各种操作的函数的指针。
结构体file_operations在头文件 linux/fs.h中定义,用来存储驱动内核模块提供的对设备进行各种操作的函数的指针。该结构体的每个域都对应着驱动内核模块用来处理某个被请求的事务的函数的地址。
每一个设备文件都代表着内核中的一个file结构体。该结构体在头文件linux/fs.h定义。file结构体是内核空间的结构体,这意味着它不会在用户程序的代码中出现。它绝对不是在glibc中定义的FILE。FILE自己也从不在内核空间的函数中出现。。