嵌入式Linux中的LED驱动控制

在前面“嵌入式Linux中字符型驱动程序的基本框架”一文中,讨论了一个字符型驱动的基本框架,但没有实现具体的驱动任务。这里就以野火i.MX6ULL开发板为例,通过编写一个实际的驱动程序去控制开发板上三个LED的亮灭。

先来看一下LED部分的电路原理图,如下所示。

111

从上图中可以看到,三个RGB的二极管采用共阳接法, 因此在给i.MAX6ULL的GPIO_4、CSI_HSYNC、CSI_VSYNC端口输出高电平时,三个发光管匀熄灭,输出低电平时,三个发光管匀点亮。

下面给出了完整的驱动程序代码,文件名为led.c。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
//以下定义总线及寄存器的物理地址
#define CCM_BASE_ADDR         (0x20C4000)
#define CCM_CCGR1_ADDR        (CCM_BASE_ADDR + 0x6C)
#define CCM_CCGR2_ADDR        (CCM_BASE_ADDR + 0x70)
#define CCM_CCGR3_ADDR        (CCM_BASE_ADDR + 0x74)
#define GPIO1_BASE_ADDR       (0x209C000)
#define GPIO1_DR_ADDR         (GPIO1_BASE_ADDR + 0x0000)
#define GPIO1_GDIR_ADDR       (GPIO1_BASE_ADDR + 0x0004)
#define GPIO1_PSR_ADDR        (GPIOB_BASE_ADDR + 0x0008)
#define GPIO4_BASE_ADDR       (0x20A8000)
#define GPIO4_DR_ADDR         (GPIO4_BASE_ADDR + 0x0000)
#define GPIO4_GDIR_ADDR       (GPIO4_BASE_ADDR + 0x0004)
#define GPIO4_PSR_ADDR        (GPIOC_BASE_ADDR + 0x0008)
#define SW_MUX_CTL_ADDR       (0x20E0000)
#define IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04_ADDR   (SW_MUX_CTL_ADDR + 0x6C)
#define IOMUXC_SW_MUX_CTL_PAD_CSI_HSYNC_ADDR    (SW_MUX_CTL_ADDR + 0x1E0)
#define IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC_ADDR    (SW_MUX_CTL_ADDR + 0x1DC)
#define IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04_ADDR   (SW_MUX_CTL_ADDR + 0x2F8)
#define IOMUXC_SW_PAD_CTL_PAD_CSI_HSYNC_ADDR    (SW_MUX_CTL_ADDR + 0x46C)
#define IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC_ADDR    (SW_MUX_CTL_ADDR + 0x468)
//以下定义时钟控制寄存器名称
static void __iomem *CCM_CCGR1;
static void __iomem *CCM_CCGR3;
static void __iomem *IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04;
static void __iomem *IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04;
static void __iomem *IOMUXC_SW_MUX_CTL_PAD_CSI_HSYNC;
static void __iomem *IOMUXC_SW_PAD_CTL_PAD_CSI_HSYNC;
static void __iomem *IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC;
static void __iomem *IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC;
static void __iomem *GPIO1_GDIR;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO4_GDIR;
static void __iomem *GPIO4_DR;
//声明一个字符型设备结构体led_dev
struct led_dev
{
    dev_t devid;                   //设备号
    struct cdev chrdev;            //字符设备结构体
    struct class *class;           //类结构体
    struct device *device;         //设备结构体
};
//定义一个led_dev类型的结构体,名称为led
struct led_dev led;
//实现open函数,为file_oprations结构体成员函数
static int led_open(struct inode *inode, struct file *filp)
{ 
    unsigned int tmp;
    //以下使能GPIO1、GPIO4的端口时钟
    tmp = ioread32(CCM_CCGR1);
    tmp |= 0xC000000;
    iowrite32(tmp, CCM_CCGR1);
    tmp = ioread32(CCM_CCGR3);
    tmp |= 0x3000;
    iowrite32(tmp, CCM_CCGR3);
    filp->private_data = &led;    //把led结构体保存在file结构体的私有变量中
    return 0;
}
//实现write函数,为file_oprations结构体成员函数
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    unsigned long ret;
    unsigned char value = 7;
    unsigned int tmp;
    ret = copy_from_user(&value, buf, cnt);    //从应用空间获取值
    if(ret < 0)
      {
          printk("kernel write failed!\r\n");
          return -EFAULT;
      }
    switch(value)    //根据应用空间的值判断具体操作
     {
        case 0:        //全部点亮三个LED
        tmp = ioread32(GPIO1_DR);
        tmp &= ~(1<<4);
        iowrite32(tmp, GPIO1_DR);
        tmp = ioread32(GPIO4_DR);
        tmp &= ~(1<<20 | 1<<19);
        iowrite32(tmp, GPIO4_DR);
        break;
        case 1:        //点亮红色LED
        tmp = ioread32(GPIO1_DR);
        tmp &= ~(1<<4);
        iowrite32(tmp, GPIO1_DR);
        break;
        case 2:        //点亮绿色LED
        tmp = ioread32(GPIO4_DR);
        tmp &= ~(1<<20);
        iowrite32(tmp, GPIO4_DR);
        break;
        case 3:        //点亮蓝色LED
        tmp = ioread32(GPIO4_DR);
        tmp &= ~(1<<19);
        iowrite32(tmp, GPIO4_DR);
        break;
        case 4:        //熄灭红色LED
        tmp = ioread32(GPIO1_DR);
        tmp |= (1<<4);
        iowrite32(tmp, GPIO1_DR);
        break;
        case 5:        //熄灭绿色LED
        tmp = ioread32(GPIO4_DR);
        tmp |= (1<<20);
        iowrite32(tmp, GPIO4_DR);
        break;
        case 6:        //熄灭蓝色LED
        tmp = ioread32(GPIO4_DR);
        tmp |= (1<<19);
        iowrite32(tmp, GPIO4_DR);
        break;
        case 7:        //全部熄灭三个LED
        tmp = ioread32(GPIO1_DR);
        tmp |= (1<<4);
        iowrite32(tmp, GPIO1_DR);
        tmp = ioread32(GPIO4_DR);
        tmp |= (1<<20 | 1<<19);
        iowrite32(tmp, GPIO4_DR);
        break;
        default:
        break;
     }
       return 0;
}
//实现release函数,为file_oprations结构体函数
static int led_release(struct inode *inode, struct file *filp)
{
    unsigned int tmp;
    //以下禁止GPIO1、GPIO4的端口时钟
    tmp = ioread32(CCM_CCGR1);
    tmp &= ~0xC000000;
    iowrite32(tmp, CCM_CCGR1);
    tmp = ioread32(CCM_CCGR3);
    tmp &= ~0x3000;
    iowrite32(tmp, CCM_CCGR3);
    return 0;
}
//填充一个file_oprations类型的结构体,名为led_dev_fops,包含上述声明的成员函数
static struct file_operations led_dev_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .write = led_write,
    .release = led_release,
};
//初始化函数,此处为驱动模块的入口函数
static int __init led_init(void)
{
    unsigned int tmp;
    //以下实现各个寄存器的地址映射
    CCM_CCGR1 = ioremap(CCM_CCGR1_ADDR, 4);
    CCM_CCGR3 = ioremap(CCM_CCGR3_ADDR, 4);    
    IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04 = ioremap(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04_ADDR, 4);
    IOMUXC_SW_MUX_CTL_PAD_CSI_HSYNC = ioremap(IOMUXC_SW_MUX_CTL_PAD_CSI_HSYNC_ADDR, 4);
    IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC = ioremap(IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC_ADDR, 4);
    IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04 = ioremap(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04_ADDR, 4);
    IOMUXC_SW_PAD_CTL_PAD_CSI_HSYNC = ioremap(IOMUXC_SW_PAD_CTL_PAD_CSI_HSYNC_ADDR, 4);
    IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC = ioremap(IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC_ADDR, 4);    
    GPIO1_GDIR = ioremap(GPIO1_GDIR_ADDR, 4);
    GPIO4_GDIR = ioremap(GPIO4_GDIR_ADDR, 4);    
    GPIO1_DR = ioremap(GPIO1_DR_ADDR, 4);
    GPIO4_DR = ioremap(GPIO4_DR_ADDR, 4);
    //以下使能GPIO1、GPIO4的端口时钟
    tmp = ioread32(CCM_CCGR1);
    tmp |= 0xC000000;
    iowrite32(tmp, CCM_CCGR1);
    tmp = ioread32(CCM_CCGR3);
    tmp |= 0x3000;
    iowrite32(tmp, CCM_CCGR3);
    //以下把GPIO1_4、GPIO4_19、GPIO4_20端口配置为GPIO模式
    tmp = ioread32(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04);
    tmp = 0x05;
    iowrite32(tmp, IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04);
    tmp = ioread32(IOMUXC_SW_MUX_CTL_PAD_CSI_HSYNC);
    tmp = 0x05;
    iowrite32(tmp, IOMUXC_SW_MUX_CTL_PAD_CSI_HSYNC);
    tmp = ioread32(IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC);
    tmp = 0x05;
    iowrite32(tmp, IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC);
    //以下把GPIO1_4、GPIO4_19、GPIO4_20端口配置为上拉、低速、等模式
    tmp = ioread32(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04);
    tmp = 0xF838;
    iowrite32(tmp, IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04);    
    tmp = ioread32(IOMUXC_SW_PAD_CTL_PAD_CSI_HSYNC);
    tmp = 0xF838;
    iowrite32(tmp, IOMUXC_SW_PAD_CTL_PAD_CSI_HSYNC);
    tmp = ioread32(IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC);
    tmp = 0xF838;
    iowrite32(tmp, IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC);
    //以下把GPIO1_4、GPIO4_19、GPIO4_20端口配置为输出
    tmp = ioread32(GPIO1_GDIR);
    tmp |= 0x10;
    iowrite32(tmp, GPIO1_GDIR);
    tmp = ioread32(GPIO4_GDIR);
    tmp |= 0x180000;
    iowrite32(tmp, GPIO4_GDIR);
    //以下设定GPIO1_4、GPIO4_19、GPIO4_20端口的初始值
    tmp = ioread32(GPIO1_DR);
    tmp |= (1<<4);
    iowrite32(tmp, GPIO1_DR);
    tmp = ioread32(GPIO4_DR);
    tmp |= (1<<20 | 1<<19);
    iowrite32(tmp, GPIO4_DR);
    //以下禁止GPIO1、GPIO4的端口时钟
    tmp = ioread32(CCM_CCGR1);
    tmp &= ~0xC000000;
    iowrite32(tmp, CCM_CCGR1);
    tmp = ioread32(CCM_CCGR3);
    tmp &= ~0x3000;
    iowrite32(tmp, CCM_CCGR3);
    //申请主设备号
    if(alloc_chrdev_region(&led.devid, 0, 1, "led") < 0)
      {
        printk("Couldn't alloc_chrdev_region!\r\n");
        return -1;
      }
    led.chrdev.owner = THIS_MODULE;
    //绑定前面声明的file_oprations类型的结构体到字符设备
    cdev_init(&led.chrdev, &led_dev_fops);
    //填充上面申请到的主设备号到字符设备    
    if(cdev_add(&led.chrdev,led.devid, 1) < 0)
      {
        printk("Couldn't add chrdev!\r\n");
        return -1;
      }
  //创建类
    led.class = class_create(THIS_MODULE, "led_dev");
    //根据创建的类生成一个设备节点
    led.device = device_create(led.class, NULL, led.devid, NULL, "led");
    return 0;
}
//退出函数,此处为驱动模块的出口函数
static void __exit led_exit(void)
{
    //以下实现各个寄存器的解除映射
    iounmap(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04);
    iounmap(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04);
    iounmap(IOMUXC_SW_MUX_CTL_PAD_CSI_HSYNC);
    iounmap(IOMUXC_SW_PAD_CTL_PAD_CSI_HSYNC);    
    iounmap(IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC);
    iounmap(IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC);
    iounmap(GPIO1_GDIR);
    iounmap(GPIO1_DR);
    iounmap(GPIO4_GDIR);
    iounmap(GPIO4_DR);    
    iounmap(CCM_CCGR1);
    iounmap(CCM_CCGR3);
    //删除字符设备
    cdev_del(&led.chrdev);
    //释放主设备号
    unregister_chrdev_region(led.devid, 1);
    //销毁设备节点
    device_destroy(led.class, led.devid);
    //销毁类
    class_destroy(led.class);
}
module_init(led_init);                //模块入口声明
module_exit(led_exit);                //模块出口声明
MODULE_LICENSE("GPL");                //GPL协议声明
MODULE_INFO(intree,"Y");

驱动程序属于内核态程序,所以还需要配套一个Makefile文件,其内容如下。

KERNEL_DIR=/opt/ebf_linux_kernel/build_image/build
ARCH=arm
CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH CROSS_COMPILE
obj-m := led.o
all:
        $(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules
clean:
        $(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean

上述Makefile文件中的第一行指定了开发板的内核源代码位置,若放到了其他路径可自行修改。另外,该内容在复制粘贴后,需要修改命令两行(倒数第1、3行)前面的空余部分,修改为Tab键开头。

以下是测试用的应用程序代码,文件名为app.c。

#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
   int fd;
   unsigned char val = 0;
   fd = open("/dev/led", O_RDWR);        //打开设备节点
   if( fd < 0 )
      printf("can`t open\n");
   if( argc != 3 )                        //命令参数不对时提示
    {
       printf("Usage :\n");
       printf("%s <all|red|green|blue> <on|off>\n", argv[0]);
       return 0;
    }
   if(strcmp(argv[1], "all") == 0)
    {
      if(strcmp(argv[2], "on") == 0)
         val = 0;                        //值为0时全部点亮
      else
         val = 7;                        //值为7时全部熄灭
    }
   else if(strcmp(argv[1], "red") == 0)
    {
      if(strcmp(argv[2], "on") == 0)
        val = 1;                        //值为1时红色点亮
      else
        val = 4;                        //值为4时红色熄灭
    }
   else if(strcmp(argv[1], "green") == 0)
    {
      if(strcmp(argv[2], "on") == 0)
        val = 2;                        //值为2时绿色点亮
      else
        val = 5;                        //值为5时绿色熄灭
    }
   else if(strcmp(argv[1], "blue") == 0)
    {
      if(strcmp(argv[2], "on") == 0)
        val = 3;                        //值为3时蓝色点亮
      else
        val = 6;                        //值为6时蓝色熄灭
    }
   write(fd, &val, 1);            //把值写入设备节点
   close(fd);                     //关闭设备节点
   return 0;
}

完成后,先执行make命令编译驱动程序,若成功会生成名为led.ko的驱动模块文件。然后对应用程序进行交叉编译,执行“arm-linux-gnueabihf-gcc app.c -o app”即可。

完成后,把编译生成的驱动模块文件led.ko和应用程序文件app一起拷贝到NFS共享目录下 。然后在开发板上执行“insmod led.ko”,把模块插入到内核中,可执行lsmod命令查看一下是否加载成功,并查看一下/dev目录是否已经生成了设备节点文件led。此外,还可以执行“cat /proc/devices”查看一下led设备的主设备号。

以上都正常后,就可以执行应用程序来进行测试了。输入“./app all on”并回车,可看到开发板上三个LED全部点亮,如下图所示。

111

再输入“./app all off”并回车,可看到它们又全部熄灭,如下图所示。

111

还可以执行其他命令,来点亮或熄灭单独的LED,比如,单独点亮红色的LED,可执行命“./app red on”,熄灭执行命令“./app red off”等等,以此类推进行检查验证。

--待续--

posted @ 2026-06-11 22:17  fxzq  阅读(56)  评论(0)    收藏  举报