×
单片机 > 单片机程序设计 > 详情

Linux-2.6.32.2内核在mini2440上的移植(十五)---移植LED驱动

发布时间:2020-06-06 发布时间:
|

移植环境 

1,主机环境:VMare下CentOS 5.5 ,1G内存。

2,集成开发环境:Elipse IDE

3,编译编译环境:arm-linux-gcc v4.4.3,arm-none-linux-gnueabi-gcc v4.5.1。

4,开发板:mini2440,2M nor flash,128M nand flash。

5,u-boot版本:u-boot-2009.08

6,linux 版本:linux-2.6.32.2

7,参考文章:

嵌入式linux应用开发完全手册,韦东山,编著。

Mini2440 之Linux 移植开发实战指南

嵌入式Linux之我行——LED驱动在2440上的实例开发

【1】LED 驱动原理及编写

在此介绍的LED 驱动实际上是一个十分典型的字符设备驱动程序,它就像学习C 语言时的“Hello,World”程序一样,代表了你对嵌入式Linux 驱动程序的入门认识,因此我们将对此驱动进行详细的介绍和分析,以便深入掌握编写驱动程序的
方法和原理。
说明:在 mini2440 的用户手册中,我们其实也已经介绍了LED 驱动,下面的内容也是基于用户手册而来的。
要写实际的驱动,就必须了解相关的硬件资源,比如用到的寄存器,物理地址,中断等,在mini2440 的原理图中,我们可以看到LED 的硬件连接是这样的:

 查看 S3C2440 数据手册,可以知道,他们对应的硬件资源如下表
                                                                            

 LED 序号 对应的GPIO

对应的CPU 引脚

 LED1 GPB5 K2
 LED2                        GPB6 L5
 LED3 GPB7   K7
 LED4  GPB8 K5

   


GPIO 是通用输入输出口的英文简称,在S3C2440 芯片中,很多的端口都是可复用的,GPIO 只是其中的一个功能,比如这里的GPB5 端口,既可以用作普通的GPIO,也可以用作专用功能nXBACK,因此我们需要设置相应的端口寄存器,以改变引脚的用途。
在这里,四个LED 是采用GPBCON 寄存器上的4 组2bit 位来配置对应引脚的用途。4组2bit 位的功能都一样:00 表示输入,01 表示输出,10 为特殊功能,11 是保留的。见下面的截图:

在mini2440 开发板中,
LED1 对应的是GPB5, GPB5 使用[11:10]位
LED2 对应的是GPB6, GPB6 使用[12:13]位
LED3 对应的是GPB7, GPB7 使用[14:15]位
LED4 对应的是GPB8, GPB8 使用[16:17]位
因此,驱动程序中需要先设置LED 为输出状态,也就是要把对应的GPBX 设置为01。

而 GPBDAT 寄存器用来对应4 个LED 的数值状态,GPBDAT5 就对应GPB5,GPBDAT6就对应GPB6,以此类推。根据原理图可以看出,当GPIO 输出为低电平时有效,就是说当GPBDAT 寄存器位置为0 时,GPB5,6,7,8 将输出低电平,对应的LED 就发光。

在软件中,要操作所用到的 IO 口,我们通常调用一些现成的函数或者宏,例如:s3c2410_gpio_cfgpin,为什么是S3C2410 的呢?因为三星出品的S3C2440 芯片所用的寄存器名称以及资源分配大部分和S3C2410 是相同的,在目前各个版本的Linux 系统中,也大都采用了相同的函数定义和宏定义。
它们从哪里定义?细心的用户或许很快就想到它们和体系结构有关,因此你可以在
linux-2.6.32.2/arch/arm/plat-s3c24x/gpio.c 文件中找到该函数的定义和实现,由于内核版本的变更,该函数的定义及实现有可能也会变动,但在此我们只要大概了解就可以了,因为该函数的名称一般是不会变的,我们可以在其他驱动源代码中找到包含该函数的包含头文件,然后依此照搬就可以了,在gpio.c 文件中,s3c2410_gpio_cfgpin 函数的实现如下所示:

void s3c2410_gpio_cfgpin(unsigned int pin, unsigned int function)
{
 void __iomem *base = S3C24XX_GPIO_BASE(pin);
 unsigned long mask;
 unsigned long con;
 unsigned long flags;

 if (pin < S3C2410_GPIO_BANKB) {
  mask = 1 << S3C2410_GPIO_OFFSET(pin);
 } else {
  mask = 3 << S3C2410_GPIO_OFFSET(pin)*2;
 }

 switch (function) {
 case S3C2410_GPIO_LEAVE:
  mask = 0;
  function = 0;
  break;

 case S3C2410_GPIO_INPUT:
 case S3C2410_GPIO_OUTPUT:
 case S3C2410_GPIO_SFN2:
 case S3C2410_GPIO_SFN3:
  if (pin < S3C2410_GPIO_BANKB) {
   function -= 1;
   function &= 1;
   function <<= S3C2410_GPIO_OFFSET(pin);
  } else {
   function &= 3;
   function <<= S3C2410_GPIO_OFFSET(pin)*2;
  }
 }

 /* modify the specified register wwith IRQs off */

 local_irq_save(flags);

 con  = __raw_readl(base + 0x00);
 con &= ~mask;
 con |= function;

 __raw_writel(con, base + 0x00);

 local_irq_restore(flags);
}

实际上,我们并不需要关心这些,写驱动时只要会使用他们就可以了,除非你所使用的CPU 体系平台尚没有被Linux 所支持,因为大部分常见的嵌入式平台都已经有了很完善的类似定义,你不需要自己去编写。在下面的驱动程序清单中,你可以看到s3c2410_gpio_cfgpin 被调用的情况。除此之外,你还需要调用一些和设备驱动密切相关的基本函数,如注册设备misc_register,填写驱动函数结构file_operations,以及像Hello,Module(见用户手册的例子)中那样的module_init 和
module_exit 函数等。有些函数并不是必须的,随着你对 Linux 驱动开发的进一步了解和阅读更多的代码,你自然明白。

这里LED将被注册成misc设备,代码放在drivers/misc 目录下,驱动程序文件mini2440_leds.c,内容如下:

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DEVICE_NAME "leds" //设备名(/dev/leds)
//LED 对应的GPIO 端口列表
static unsigned long led_table [] = {
 S3C2410_GPB(5),
 S3C2410_GPB(6),
 S3C2410_GPB(7),
 S3C2410_GPB(8),
};
//LED 对应端口将要输出的状态列表
static unsigned int led_cfg_table [] = {
 S3C2410_GPIO_OUTPUT,
 S3C2410_GPIO_OUTPUT,
 S3C2410_GPIO_OUTPUT,
 S3C2410_GPIO_OUTPUT,
};
/*ioctl 函数的实现
* 在应用/用户层将通过ioctl 函数向内核传递参数,以控制LED 的输出状态
*/
static int leds_ioctl(struct inode *inode,
          struct file *file,
          unsigned int cmd,
          unsigned long arg)
{
 switch(cmd) {
  case 0:
  case 1:
    if (arg > 4) {
     return -EINVAL;
    }
  //根据应用/用户层传递来的参数(取反),通过s3c2410_gpio_setpin 函数设置LED 对应的端口寄存器
  s3c2410_gpio_setpin(led_table[arg], !cmd);
   return 0;
  default:
    return -EINVAL;
 }
}
/*
* 设备函数操作集,在此只有ioctl 函数,通常还有read, write, open, close 等,因为本LED 驱动在下面已经
* 注册为misc 设备,因此也可以不用open/close
*/
static struct file_operations dev_fops = {
 .owner = THIS_MODULE,
 .ioctl = leds_ioctl,
};
/*
* 把LED 驱动注册为MISC 设备
*/
static struct miscdevice misc = {
 .minor = MISC_DYNAMIC_MINOR, //动态设备号
 .name = DEVICE_NAME,
 .fops = &dev_fops,
};
/*
* 设备初始化
*/
static int __init dev_init(void)
{
 int ret;
 int i;
 for (i = 0; i < 4; i++) {
  //设置LED 对应的端口寄存器为输出(OUTPUT)
  s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);
  //设置LED 对应的端口寄存器为低电平输出,在模块加载结束后,四个LED 应该是全部都是发光状态
  s3c2410_gpio_setpin(led_table[i], 0);
 }
 ret = misc_register(&misc); //注册设备

 if(ret < 0)
 {
        printk(DEVICE_NAME "register falid!\n");
        return ret;
  }

 printk (DEVICE_NAME "\tinitialized\n"); //打印初始化信息
 return 0;
}
static void __exit dev_exit(void)
{
 misc_deregister(&misc);
}
//模块初始化,仅当使用insmod/podprobe 命令加载时有用,如果设备不是通过模块方式加载,此处将不会被调用
module_init(dev_init); 
//卸载模块,当该设备通过模块方式加载后,可以通过rmmod 命令卸载,将调用此函数
module_exit(dev_exit); 
MODULE_LICENSE("GPL"); //版权信息
MODULE_AUTHOR("singleboy."); //开发者信息

【2】为内核添加LED 设备的内核配置选项,打开drivers/misc/Kconfig 文件,定位到16行附近,添加如下红色部分内容:

if MISC_DEVICES

config LEDS_MINI2440
 tristate "LED Support for Mini2440 GPIO LEDs"
 depends on MACH_MINI2440
 default y if MACH_MINI2440
 help
  This option enables support for LEDs connected to GPIO lines
  on Mini2440 boards.

【3】把驱动目标文件加入内核

接下来,再根据该驱动的配置定义,把对应的驱动目标文件加入内核中,打开linux-2.6.32.2/drivers/misc/Makefile 文件,定位到24行附近,添加如下红色部分内容:

obj-$(CONFIG_C2PORT)  += c2port/
obj-$(CONFIG_LEDS_MINI2440) += mini2440_leds.o
obj-$(CONFIG_MINI2440_ADC) += mini2440_adc.o
obj-y    += eeprom/

这样,我们就在内核中添加做好了LED 驱动

【4】配置编译新内核并测试LED

接上面的步骤,在内核源代码目录下执行:make menuconfig 重新配置内核,依次选择进入如下子菜单项:

Device Drivers --->
   [*] Misc devices  --->

           LED Support for Mini2440 GPIO LEDs   //选项默认是选中的,若没有选中,则按空格键选中它。

退出并保存内核配置。

然后退出保存所选配置, 在命令行执行: make uImage , 将会生成arch/arm/boot/uImage,然后将其复制到/nfsboot目录下后启动开发板。可以在看到串口终端中启动信息:

... ...

brd: module loaded
leds     initialized!
S3C24XX NAND Driver, (c) 2004 Simtec Electronics

... ...

【5】leds测试

在内核源代码根目录下执行;make uImage,然后把生成的uImage复制到/nfsboot/kernel目录下,然后重启开发板。

为了测试该驱动程序,我们还需要编写一个简单的测试程序,用来调用驱动程序中的ioctl 函数,以达到通过应用程序控制LED 的目的,在友善官方提供的光盘中已经提供了该测试程序的源代码,它位于\linux 示例代码\examples\leds 目录中,文件名为led.c。将其复制到主机/root/linux-test/codetest目录下,下面是其中的代码:

#include
#include
#include
#include

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;
}
在终端中进入到codetest目录,然后执行:

[root@localhost codetest]# ls
adc_test    adc_test.c~     backlight_test.c  led.c   tstest.c
adc_test.c  backlight_test  i2c               tstest
[root@localhost codetest]# arm-linux-gcc -o led led.c
[root@localhost codetest]# cp led /nfsboot/nfs
[root@localhost codetest]# 
将生成可执行目标文件led,复制到与开发板共享的nfsboot/nfs中,在开发板的命令行终端执行:
#./led 3 0 ;关闭LED3
#./led 3 1 ;打开LED3
其中第一个参数为要控制的LED 序号(0~3分别对应开发板上Led1~Led4),第二个参数代表关闭(0)或者打开(1)对应的LED。操作如下:

[root@mini2440 /]#ls -l /dev/leds
crw-rw----    1 root     root      10,  63 Jan  1 00:00 /dev/leds
[root@mini2440 nfs]#ls
adc_test        bigworld.wav    led             tstest
backlight_test  i2c             test1.wav       yesterday.mp3
[root@mini2440 nfs]#./led 0 0
[root@mini2440 nfs]#./led 0 1
[root@mini2440 nfs]#./led 1 0
[root@mini2440 nfs]#./led 1 1
[root@mini2440 nfs]#./led 2 0
[root@mini2440 nfs]#./led 2 1
[root@mini2440 nfs]#./led 3 0
[root@mini2440 nfs]#./led 3 1
[root@mini2440 nfs]#

可以看到开发板上Led1~Led4相应的灭和亮。

接下来,将要进行基于中断的按键驱动移植。




『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
关于89C52单片机11.0592M晶振产生115200波特率的方法