第七章 LED将为我闪烁:控制发光二极管
2024-09-30 来源:cnblogs
该Linux驱动用来控制开发板上的4个LED灯,即通过向Linux驱动发送数据可以控制LED灯的开关。LED驱动提供两种交互方式:命令和读写设备文件。
测试LED驱动之前需用USB线连接开发板,然后打开开发板。成功启动后,执行build.sh脚本文件编译和安装LED驱动。build.sh脚本文件会自动将s3c6410_leds.ko文件上传到开发板并安装。LED驱动只能在开发板上安装,build.sh执行了build_s3c6410.sh脚本文件进行编译和安装。LED驱动会建立一个/dev/s3c6410_leds设备文件,该Linux驱动可控制4个LED,通过向设备文件发送长度为1到4的字符串可以控制这4个LED的开关。1表示开,0表示关。字符串长度不足4个,相当于后面补0。执行命令
“# adb shell 'echo '1'> /dev/s3c6410_leds' #打开第一个LED,其他的都关闭
# adb shell 'echo '1010'> /dev/s3c6410_leds' #第一个和第三个LED打开,第二个和第四个关闭
# adb shell 'echo '1111'> /dev/s3c6410_leds' #打开所有的LED”可控制开发板上的LED。可使用命令“# sh ~/drivers/s3c6410_leds/test_leds.sh”执行test_leds.sh脚本文件测试LED。执行脚本文件后,开发板上的4个LED会根据0到15的二进制形式控制LED,第一个为最低位。脚本文件使用的是标准的Bash Shell,如果在Ubuntu下无法成功执行,是因为其将dash作为默认的脚本解析器。可使用命令“# dpkg-reconfigure dash”将默认脚本解析器改成Bash,出现设置界面时,选择“否”,再回车即可。
创建LED驱动的设备文件,步骤如下;1.描述设备文件需要使用一个cdev结构体,该结构体在 “int cdev_add(struct cdev *p,dev_t dev,unsigned count){ p->dev=dev; p->count=count; return kobj_map(cdev_map,dev,count,NULL,exact_match,exact_lock,p); }”,调用该函数需指定设备文件指针p、设备号dev和设备文件数量count。在该函数还调用了一个重要的函数kobj_map,此函数负责将设备文件的相关信息添加到保存已建立的设备文件的probes数组中。kobj_map()和probes数组都在 “struct class *leds_class=NULL; leds_class=class_create(THIS_MODULE,'dev_name');”,dev_name是设备文件名称。class_create宏实际上使用了_class_create()创建struct class。该函数在 卸载LED驱动的设备文件:卸载操作会稍简单一些,需依次调用device_destroy、class_destroy和unregister_chrdev_region()。leds_destroy_device()用于卸载LED驱动的设备文件,leds_exit()是LED驱动的卸载函数,它通过调用leds_destroy_device()来完成卸载LED驱动设备文件的工作。 设置寄存器与初始化LED驱动:ARM处理器有多个寄存器,通过设置不同寄存器的值。可以设置LED引脚的状态、打开或禁止上拉电路以及控制LED的亮和灭。我们必须知道的有:①LED有两个引脚:GPB0和GPB1,其中一个引脚连接到了ARM处理器的GPI0端口,另一个引脚经过一个限流电阻连接到电源VCC3上。当GPI0端口为低电平时,LED两端产生电压差,LED有电流通过发光;反之当GPI0端口为高电平时,LED中没有电流通过,灯熄灭。高低电平之间切换非常快,LED亮灭之间有一定的延迟②控制LED需要通过3个寄存器完成,GPMCON端口配置寄存器、GPMDAT端口数据寄存器和GPMPUD端口上拉电路寄存器③每一个寄存器可以使用4个字节,即一个int类型数据占用的空间④使用GPMCON寄存器的低16位将LED的两个端口GPB0、GPB1的属性设为Output。每4位设置一个LED,共4个LED。output的值是0001,若使用十六进制表示,寄存器的低16位的值是0x1111⑤使用GPMDAT寄存器的低4位控制4个LED的亮、灭。每一位控制一个LED,最低位控制离电池最近的LED。0表示亮、1表示灭⑥使用GPMPUD寄存器的低8位分别打开4个LED的上拉电路。每两位控制一个LED的上拉电路。10为打开上拉电路。使用十六进制的话,GPMPUD寄存器的低8位是0xAA,才能同时打开4个LED的上拉电路。以上3个寄存器在内存中都有一个虚拟地址。向这些地址写入数据后,ARM处理器会使用一套算法将虚拟地址映射成物理地址,并根据物理地址将数据写入相应的硬件端口。ARM处理器中的GPMCOM、GPMDAT和GPMPUD的虚拟地址在Linux内核中都使用了宏定义。为了跟踪这些宏,需再加两个include路径:/root/kernel/linux_kernel_2.6.36/arch/arm/mach-s3c64xx/include和/root/kernel/linux_kernel_2.6.36/arch/arm/plat-samsung/include。这三个寄存器的虚拟地址对应的宏分别为S3C64XX_GPMCON、S3C64XX_GPMPUD、S3C64XX_GPMDAT。这三个宏涉及了4个头文件共9个宏。可推出S3C64XX_GPM_BASE的值是0xF04500820,GPMCON、GPMDAT和GPMPUD寄存器的虚拟地址分别为0xF04500820、0xF04500824和0xF04500828,这三个虚拟地址是固定的,可向这三个地址写数据。更好的是使用S3C64XX_GPMCON、S3C64XX_GPMPUD、S3C64XX_GPMDAT来操作这3个地址。一般需在LED驱动装载时初始化上述3个寄存器。只要在leds_init()中调用leds_init_gpm()就可完成寄存器的初始化。 控制LED:LED驱动可使用两种方式控制LED:通过字符串控制LED和通过I/O命令控制LED。要使用以上两种方式控制LED,驱动必须接收相应的数据。若通过字符串控制LED,需使用file_operations.write(),可接收向设备文件写入的数据。若通过I/O命令控制,需使用file_operations.ioctl(),可接收向字符设备发送的命令和参数。s3c6410_leds_write()用于接收向LED驱动的设备文件写入控制LED的数据,在实现其功能编写代码时需了解:①4个LED的亮灭用一个长度为4的mem数组。1表示点亮LED,0表示熄灭LED。与GPMDAT寄存器的低4位表示的含义正好相反②若写入的字符串长度小于等于4,直接写入这些字符串。若长度大于4,则只写入前4个字符串。s3c6410_leds_write()要按传入该函数的字符串长度返回,否则系统会调用多次该函数写入字符串③事先mem数组已被清零,若要写入的字符串长度小于4,则相当于后面的字符都是④向GPMDAT寄存器写入数据之前最好先读取GPMDAT寄存器的当前值,并通过位与、或等操作保留与本次操作无关的值⑤ioread32、iowrite32用于读写虚拟地址中的32位数据。使用命令 “# adb shell 'echo 1101 > /dev/s3c6410_leds' # adb shell 'echo 1 > /dev/s3c6410_leds'”可通过字符串控制LED的亮、灭。I/O命令无法使用命令行方式进行测试。 LED驱动的模块参数:若想在装载LED驱动时指定默认状态值,就要使用模块参数。为Linux驱动指定一个模块参数需使用module_param(name,type,perm)宏。name表示参数名,type表示参数类型,perm表示读/写权限。module_param支持的参数类型包括byte、short、ushort、int、uint、long、charp、bool和invbool。使用module_param宏指定模块参数时,会在/sys/module目录下生成和驱动设备文件同名的目录。若在装载Linux驱动时未指定某个参数,则参数文件的内容是该参数在Linux驱动源代码中指定的默认值。通过module_param宏可指定参数文件的访问权限。S_IRUGO表示所有的用户都可访问该参数文件中的内容,但不能修改。S_IRUGO|S_IWUSR表示允许所有用户读,以及创建文件的用户写。Linux内核还提供了更多的定义访问权限的宏。S_IRWXUGO表示所有用户可对文件读、写和执行。IWUGO表示所有用户对文件只有写权限。需要修改LED驱动的代码,为LED驱动添加一个模块参数,该参数存储了4个LED的初始状态,参数类型为int。参数值的范围是0到15.参数值控制LED的规则与GPMDAT寄存器低4位控制LED的规则相同。为LED驱动添加模块参数首先要定义一个保存模块参数值的变量,然后使用module_param宏指定模块参数的相关信息。最后修改leds_init()代码,将leds_init_gpm()的参数值改成~leds_state。使用命令“# adb shell insmod /data/local/s3c6410_leds.ko leds_state=3”可测试LED驱动的模块参数。执行完命令后,会在/sys/module/s3c6410_leds/parameters目录下生成一个leds_state文件,使用命令 “# adb shell cat /sys/module/s3c6410_leds/parameters/leds_state”可看到文件内容为3。使用命令“# adb shell 'echo 5 > /sys/module/s3c6410_leds/parameters/leds_state'”可将文件内容改为5。修改leds_state文件内容后,在LED驱动代码中的leds_state变量值会变成5。Linux驱动在装载时会将指定的参数值写入参数文件,若未指定参数值,Linux驱动会将参数的默认值写入参数文件。在Linux驱动工作的过程中,参数值会与参数文件中的内容同步。使用module_param_array(name,type,nump,perm)宏可为Linux驱动指定数组形式的模块参数。nump表示存储数组长度的变量的指针,perm表示参数文件的访问权限。通过命令“# adb shell insmod /data/local/s3c6410_leds.ko 'leds_state=11 param=str1,str2,str3'”可指定params参数值。如果params参数指定的值的个数少于数组长度,后面的数组元素使用默认值。如果大于数组长度,LED驱动装载失败,并在日志中输出信息。使用模块参数要注意:①通过module_param_array宏的第3个参数指定数组长度时要使用指针类型的数据②如果Linux驱动含有多个模块参数,需将这些参数用单引号或双引号括起来③指定数组类型的参数值时,逗号前不能有空格。
上一篇:Android深度探索(卷1)HAL与驱动开发第七章总结
下一篇:uboot移植