嵌入式Linux LED驱动开发实验

参考资料

[1] 【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.71

[2] https://bbs.csdn.net/topics/340234094

1 地址映射

在老版本的Linux中要求处理器必须有MMU(Memory Manage Unit,内存管理单元),但是现在Linux内核已经支持无MMU的处理器。MMU主要完成的功能如下:

  • 完成虚拟空间(VA,Virtual Address)到物理空间(PA,Physical Address)的映射
  • 内存保护,设置存储器的访问权限以及虚拟空间的缓冲特性

对于32位的处理器来说,虚拟地址范围是2^32=4GB。 Linux内核启动的时候会初始化MMU,设置好地址映射后,访问的都是虚拟地址。因此无法直接对物理地址进行读写操作。物理内存和虚拟内存之间的转换,需要用到以下两个函数,定义在arch/arm/include/asm/io.h:

#define ioremap(cookie,size)		__arm_ioremap((cookie), (size), MT_DEVICE)
void __iomem *__arm_ioremap(phys_addr_t phys_addr, size_t size, unsigned int mtype);


#define iounmap				__arm_iounmap
void __arm_iounmap(volatile void __iomem *addr);

 ioremap的返回值为void __iomem类型的指针,指向映射后的虚拟空间首地址,另外,cookie表示要映射的物理起始地址,size表示需要映射的内存空间大小。iounmap用于释放映射的虚拟内存,只需要填入需要取消的虚拟空间首地址即可。

注意,在使用时ioremap和iounmap要成对出现。

2 I/O内存与I/O端口

这里的I/O是输入/输出的意思。关于I/O内存和I/O端口的区别可以参考IO端口和IO内存的区别及分别使用的函数接口_insoonior的博客-CSDN博客

I/O内存读操作函数通过宏定义替换如下:

u8 readb(const volatile void __iomem *addr);
u16 readw(const volatile void __iomem *addr);
u32 readl(const volatile void __iomem *addr);

三个函数分别表示8bit、16bit、32bit读操作,入口参数表示需要读取的内存地址,返回值为读取到的数据。

I/O内存写操作函数通过宏定义替换如下:

void writeb(u8 value, volatile void __iomem *addr);
void writew(u16 value, volatile void __iomem *addr);
void writel(u32 value, volatile void __iomem *addr);

三个函数分别表示8bit、16bit、32bit写操作,入口参数分别表示要写入的值和写入的内存地址。

3 工程验证

驱动代码如下:

#include <linux/module.h>
#include <linux/printk.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>

#define DEVICE_MAJOR   201
#define DEVICE_NAME    "leddevice"

// led 状态
#define LED_ON    1
#define LED_OFF   0

// 寄存器物理地址
#define CCM_CCGR1_BASE          (0x020C406C)
#define SW_MUX_GPIO1_IO3_BASE   (0x020E0068)
#define SW_PAD_GPIO1_IO3_BASE   (0x020E02F4)
#define GPIO1_GDIR_BASE         (0x0209C004)
#define GPIO1_DR_BASE           (0x0209C000)

// 虚拟内存地址
static void __iomem *ccm_ccgr1;
static void __iomem *mux_gpio1_io3;
static void __iomem *pad_gpio1_io3;
static void __iomem *gpio1_gdir;
static void __iomem *gpio1_dr;

void led_switch(u8 sta)
{
    u32 val = 0;
    if(LED_ON == sta)
    {
        val = readl(gpio1_dr);
        val &= ~(1 << 3);
        writel(val, gpio1_dr);
    }
    else if(LED_OFF == sta)
    {
        val = readl(gpio1_dr);
        val |= (1 << 3);
        writel(val, gpio1_dr);
    }
    
}

int led_open(struct inode *inode, struct file *file)
{
    return 0;
}

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

ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    int ret = 0;
    char databuf[1];
    ret = copy_from_user(databuf, buf, count);
    if(ret != 0)
    {
        printk("receive faild\r\n");
    }
    else
    {
        printk("receive data:%d\r\n", databuf[0]);
    }

    if(databuf[0] == LED_ON)
    {
        led_switch(LED_ON);
    }
    else if(databuf[0] == LED_OFF)
    {
        led_switch(LED_OFF);
    }

    return 0;
}

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

static int __init leddevice_init(void)
{
    u8 ret = 0;
    u32 readval = 0;

    // 地址映射
    ccm_ccgr1 = ioremap(CCM_CCGR1_BASE, 4);
    mux_gpio1_io3 = ioremap(SW_MUX_GPIO1_IO3_BASE, 4);
    pad_gpio1_io3 = ioremap(SW_PAD_GPIO1_IO3_BASE, 4);
    gpio1_gdir = ioremap(GPIO1_GDIR_BASE, 4);
    gpio1_dr = ioremap(GPIO1_DR_BASE, 4);

    // 使能时钟,因为此寄存器控制着其他外设时钟,防止影响,采用读改写
    readval = readl(ccm_ccgr1);
    readval &= ~(3 << 26);
    readval |= (3 << 26);
    writel(readval, ccm_ccgr1);

    // 引脚复用,此寄存器只控制GPIO1_IO3,因此不需要读改写
    writel(0x5, mux_gpio1_io3);

    // 引脚属性
    writel(0x10B0, pad_gpio1_io3);

    // 引脚为输出
    readval = readl(gpio1_gdir);
    readval &= ~(1 << 3);
    readval |= (1 << 3);
    writel(readval, gpio1_gdir);

    // 输出高电平
    readval = readl(gpio1_dr);
    readval &= ~(1 << 3);
    readval |= (1 << 3);
    writel(readval, gpio1_dr);

    printk("leddevice_init\r\n");

    ret = register_chrdev(DEVICE_MAJOR, DEVICE_NAME, &fops);
    if(ret < 0)
    {
        printk("register_chrdev failed\r\n");
    }


    return 0;
}

static void __exit leddevice_exit(void)
{
    // 释放虚拟内存
    iounmap(ccm_ccgr1);
    iounmap(mux_gpio1_io3);
    iounmap(pad_gpio1_io3);
    iounmap(gpio1_gdir);
    iounmap(gpio1_dr);

    printk("leddevice_exit\r\n");

    unregister_chrdev(DEVICE_MAJOR, DEVICE_NAME);


}

// 注册模块加载和卸载函数
module_init(leddevice_init);
module_exit(leddevice_exit);

MODULE_LICENSE("GPL");

APP代码如下:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

// ./leddeviceAPP /dev/leddevice 1      open
// ./leddeviceAPP /dev/leddevice 0      close

const char app_data[] = "app";

int main(int argc, char *argv[])
{
    int fd = 0, ret = 0;
    char *filename = argv[1], writebuf[100];
    char readbuf[100];

    if(argc != 3)
    {
        printf("Error usage!\r\n");
        return -1;
    }

    fd = open(filename, O_RDWR);
    if(fd < 0)
    {
        printf("Can't open file %s\r\n", filename);
        return -1;
    }
    else
    {

    }

    writebuf[0] = atoi(argv[2]);
    ret = write(fd, writebuf, 1);
    if(ret < 0)
    {
        printf("write file %s failed\r\n", filename);
        return -1;
    }
    else
    {

    }

    ret = close(fd);
    if(ret < 0)
    {
        printf("close file %s failed\r\n", filename);
        return -1;
    }
    else
    {

    }

    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值