单片机
返回首页

第七章 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结构体,该结构体在/include/linux/cdev.h文件中定义。其中的大多数成员变量只需调用cdev_init()就可被初始化,该函数在/fs/char_dev.c文件中。若要在一个Linux驱动中建立多个设备文件,cdev.count变量的值就是要建立的设备文件数。这些设备文件对应的cdev结构体就通过cdev.list.prev和cdev.list.next指针变量连接,从而形成一个双向链表。cdev.owner变量未在cdev.init函数中初始化,使用语句“leds_cdev.owner=THIS_MODULE;”来初始化2.Linux设备文件的设备号分为主设备号和次设备号。用一个int类型表示,其中前12位表示主设备号,后20位表示次设备号。设备号有两种指定方法:直接在代码中指定和动态分配。第一种方法虽比较直观,但如果主设备号和次设备号已存在,建立设备文件就会失败。以防万一,可使用alloc_chrdev_region()自动分配一个未使用的主设备号。习惯上将次设备号设为0。函数原型为int alloc_chrdev_region(dev_t*dev,unsigned baseminor,unsigned count,const char *name),其中dev表示设备号指针,函数会随机分配一个未使用的主设备号,根据baseminor参数值分配次设备号。count表示分配的次设备号范围。name表示设备文件名称。多个Linux设备文件可拥有同一个主设备号,但两个设备的主设备号和次设备号不能都一样。在使用函数自动分配设备号时,baseminor和count参数不要设太大,否则次设备号会溢出,且主设备号会进位,从而变成下一个主设备号。若要直接指定设备号,需使用register_chrdev_region()注册字符设备区域,该函数在/fs/char_dev.c文件中实现,原型为:int register_chrdev_region(dev_t from,unsigned count,const char *name),from表示设备号,count表示次设备号范围,name表示设备文件名称。一般采用分别指定主设备号和次设备号的方式指定设备号,需要MKDEV宏将主设备号和次设备号组合成设备号-“int dev_number=MKDEV(major,minor);”。也可分别使用MAJOR和MINOR宏从设备号中获取主设备号和次设备号,代码为:“int major=MAJOR(dev_number); int minor=MAJOR(dev_number);”3.cdev_add()用于将字符设备添加到probes数组中。函数在/fs/char_dev.c文件中实现,原型为

“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数组都在/drivers/base/map.c文件中4.struct class包含一些与设备文件有关的变量及一些回调函数指针变量,使用class_create宏创建struct class,代码为

“struct class *leds_class=NULL;

leds_class=class_create(THIS_MODULE,'dev_name');”,dev_name是设备文件名称。class_create宏实际上使用了_class_create()创建struct class。该函数在/drivers/base/class.c文件中实现5.device_create()用于创建设备文件,该函数在/include/linux/device.h文件中定义,在/drivers/base/core.c文件中实现。可使用代码“device_create(leds_class,NULL,dev_number,NULL,DEVICE_NAME);”调用device_create()创建设备文件,其中leds_class表示struct class,dev_number表示设备号,DEVICE_NAME表示设备文件的名称。编写leds_create_device()时应了解:①DEVICE_COUNT表示建立设备文件的个数②alloc_chrdev_region()的第二个参数表示分配的起始次设备号。如果第三个参数的值大于1,函数会依次分配次设备号③采用自动分配设备号的方式创建设备文件,建议使用MAJOR和MINOR宏获取主设备号和次设备号,并分别保存在major和minor变量中,以备之后使用到④LED驱动的设备号保存在dev_number变量中,要将leds_cdev.dev变量的值赋给dev_number变量。leds_init()是LED驱动的初始化函数,在函数中直接调用leds_create_device()即可。若将S3C6410_LEDS_MAJOR设为0,系统会自动分配一个未使用的主设备号,次设备号仍是10.在每次装载LED驱动时主设备号可能会不一样,但次设备号总是10。

卸载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驱动含有多个模块参数,需将这些参数用单引号或双引号括起来③指定数组类型的参数值时,逗号前不能有空格。


进入单片机查看更多内容>>
相关视频
  • RISC-V嵌入式系统开发

  • SOC系统级芯片设计实验

  • 云龙51单片机实训视频教程(王云,字幕版)

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

精选电路图
  • CCD图像传感器在微光电视系统中的应用

  • 基于IC555的可变PWM振荡器电路

  • 优化电路板布局的简单方法

  • 如何使用LED驱动器LM3915制作振动计

  • 电谐波图形均衡器示意图

  • 一种构建12V和230V双直流电源的简单方法

    相关电子头条文章