41 led驱动

一、地址映射

  • 1、裸机led实验 就是 操作 6ull的寄存器
  • 2、linux驱动开发也可以操作寄存器,但不能直接对寄存器的物理地址进行读写操作。
    比如寄存器A 物理地址 为0x01010101,裸机时可以直接对0x01010101这个物理地址进行操作,但在linux下是不行的。
    因为linux会使能mmu。
    在linux里面操作的都是虚拟地址,所以需要先得到0x01010101这个物理地址对应的虚拟地址。
    获取物理地址对应的虚拟地址使用宏 ioremap(cokkie, size)第一个参数物理地址起始地址第二个参数要转换的字节数量 ,返回 映射后的虚拟地址 。因此不仅可以对 一个物理地址 进行转换,也可以对 一段物理地址 进行转换。使用示例:ioremap(0x01010101, 10)
    卸载驱动时使用 iounmap 来释放掉 ioremap 函数所做的映射。void iounmap (volatile void __iomem *addr)
    使用 ioremap 函数将 寄存器的物理地址 映射到 虚拟地址 以后,我们就可以直接通过 指针 访问这些地址,但是 Linux 内核不建议这么做,而是推荐使用一组 读写操作函数 来对 映射后的内存 进行读写操作。
读操作函数有如下几个:分别对应 8bit、 16bit 和 32bit 读操作.
参数 addr 就是要读取写内存地址,返回值就是读取到的数据。
u8 readb(const volatile void __iomem *addr)  8bit
u16 readw(const volatile void __iomem *addr) 16bit
u32 readl(const volatile void __iomem *addr) 32bit

写操作函数有如下几个:分别对应 8bit、 16bit 和 32bit 写操作.
参数 value 是要写入的数值, addr 是要写入的地址。
void writeb(u8 value, volatile void __iomem *addr)	8bit
void writew(u16 value, volatile void __iomem *addr)	16bit
void writel(u32 value, volatile void __iomem *addr)	32bit
  • 3、mmu的两个功能
    内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性。
    完成虚拟空间到物理空间的映射。对于 32 位的处理器来说,虚拟地址范围是 2^32=4GB,我们的开发板上有 512MB 的 DDR3,这 512MB 的内存就是物理内存,经过 MMU 可以将其映射到整个 4GB 的虚拟空间,如下图所示:
    在这里插入图片描述

二、LED灯字符设备驱动框架搭建

  • 1、初始化时钟,IO等等,可以放在led_init 也可以放在led_open 里面。
  • 2、初始化完成后进行测试,若用的是教程提供的内核,此时led默认被配置为心跳灯,因此必须要先关闭心跳灯。l
  • 3、uboot下载系统失败的话,要检查 开发板 的 ipubuntu 的 ip 是唯一的。检测到ubuntu的ip冲突,则重新设置ubuntu的ip,然后重启网卡(图标点一下即可),然后 ping www.baidu.com。然后改uboot的环境变量:serverip,bootargs(不改此环境变量无法挂载根文件系统)。

三、驱动源码

#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include <linux/fs.h>
//#include<linux/slab.h>
#include<linux/io.h>
#include<linux/uaccess.h>

#define LED_MAJOR   200
#define LED_NAME    "led"

// led 相关寄存器的物理地址
#define CCM_CCGR1_BASE (0X020C406C) 			// 时钟
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)				// 输出高低电平
#define GPIO1_GDIR_BASE (0X0209C004)			// 方向

// 用于存放映射后的寄存器虚拟地址
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

#define LED_OFF 0
#define LED_ON 1

static void led_switch(u8 state)
{
    u32 val;
    if(state == LED_ON)
    {
    	// 操作 DR 寄存器来实现开灯操作
        val = readl(GPIO1_DR);
        val &= (~(1<<3)); 
        writel(val, GPIO1_DR);
    }
    else if(state == LED_OFF)
    {
    	// 操作 DR 寄存器来实现关灯操作
        val = readl(GPIO1_DR);
        val |= (1<<3); // turn off led
        writel(val, GPIO1_DR);
    }
}

// @param – inode : 传递给驱动的 inode
// @param - filp : 设备文件, file 结构体有个叫做 private_data 的成员变量.一般在 open 的时候将 private_data 指向设备结构体
static int led_open(struct inode *inode, struct file *filp)
{
    return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
   
    return 0;
}

// @param - filp : 设备文件,表示打开的文件描述符
// @param - buf : 要写给设备写入的数据
// @param - count : 要写入的数据长度
// @param - offt : 相对于文件首地址的偏移
static ssize_t led_write(struct file *filp, const char __user *buf,
			 size_t count, loff_t *ppos)
{
    int retValue;
    unsigned char databuf[1];
    retValue = copy_from_user(databuf, buf, count);
    if(retValue < 0)
    {
        printk("Kernel write failed.\r\n");
        return -EIO;
    }

    led_switch(databuf[0]);
    return 0;
}

static const struct file_operations led_fops = 
{
    .owner = THIS_MODULE,
    .write = led_write,
    .open = led_open,
    .release = led_release,
};

// 驱动入口函数,调用 modprobe 时会执行该函数
static int __init led_init(void)
{
    int ret = 0;
    unsigned int val = 0;
 
    // 寄存器地址映射
    IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
    SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
    SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
    GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
    GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);

    // 初始化相关寄存器
    val = readl(IMX6U_CCM_CCGR1);
    val &= (~(3<<26));
    val |= (3<<26);
    writel(val, IMX6U_CCM_CCGR1);
   
    writel(0X5, SW_MUX_GPIO1_IO03);// io 复用
    writel(0X10B0, SW_PAD_GPIO1_IO03); // 设置电气属性
   
    val = readl(GPIO1_GDIR); // 设置方向
    val |= (1<<3); 
    writel(val, GPIO1_GDIR);

    val = readl(GPIO1_DR); // 设置高低电平
    val &= (~(1<<3));
    writel(val, GPIO1_DR);


    // 注册字符设备
    ret = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
    if(ret < 0)
    {
        printk("Register led_dev fail.\r\n");
        return -EIO;
    }
    printk("led_init\r\n");
    return 0;
}

// 驱动出口函数
static void __exit led_exit(void)
{
    unsigned int val = 0;
    val = readl(GPIO1_DR);
    val |= (1<<3); // turn off led
    writel(val, GPIO1_DR);
    // 取消地址映射
    iounmap(IMX6U_CCM_CCGR1);
    iounmap(SW_MUX_GPIO1_IO03);
    iounmap(SW_PAD_GPIO1_IO03);
    iounmap(GPIO1_DR);
    iounmap(GPIO1_GDIR);
    // 注销字符设备驱动
    unregister_chrdev(LED_MAJOR, LED_NAME);
    printk("led_exit\r\n");
}

//向内核注册一个模块加载函数(入口函数),指定 led 设备驱动加载函数
module_init(led_init);
//向内核注册一个模块卸载函数(出口函数),指定 led 设备驱动卸载函数
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("J");
  • Makefile
KERNELDIR := /home/jl/linux/imx6ull/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENT_PATH := $(shell pwd)
obj-m := led.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules  ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

四、测试APP源码

  • 注意,APP在Makefile里面没有对其进行编译,需要自己在shell下编译。
    arm-linux-gnueabihf-gcc ledAPP.c -o ledAPP
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
// ./ledAPP filename  0/1
// 0 -> turn off led
// 1 -> turn on led

#define LED_OFF 0
#define LED_ON 1
int main(int argc, char **argv)
{
    int ret = 0;
    int fd = 0;
    char *filename;
    unsigned char databuf[1];

    if(argc != 3)
    {
        printf("Error usage!\r\n");// user room
        return -1;
    }
    filename = argv[1];
    fd = open(filename, O_RDWR);
    if(fd < 0)
    {
        printf("Can't open file \"%s\"\r\n", filename);
        return -1;
    }
    databuf[0] = atoi(argv[2]);
    ret = write(fd, databuf, sizeof(databuf));
    if(ret < 0)
    {
        printf("Led control failed.\r\n");
        ret = close(fd);
        if(ret < 0)
        {
            printf("Close file \"%s\" error!\r\n", filename);
            return -1;
        }
        return -1;
    }
    ret = close(fd);
    if(ret < 0)
    {
        printf("Close file \"%s\" error!\r\n", filename);
        return -1;
    }
    else
    {
        ;
    }

    return 0;
}

五、测试

1、depmod
2、modprobe led.ko
3、cat /proc/devices 查看主设备号;lsmod 查看驱动模块是否存在
4、mknod /dev/led c 200 0 创建设备结点:c字符设备,200主设备号,0次设备号
5、./ledAPP /dev/led ( 0 / 1 ) 使用测试app进行开灯关灯
5、rmmod led.ko

  • 最重要的地址映射,内核提供的两个映射函数,内存读写函数
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值