28.嵌入式 Linux LED 驱动开发实验

28.嵌入式 Linux LED 驱动开发实验

1.裸机开发的时候,直接操作6ull的寄存器

2.linux驱动开发也可以直接操作寄存器,linux不能直接对寄存器的物理地址进行读写操作,比如寄存器A的物理地址为0x1010101。裸机可以直接对这个物理地址进行操作,但是linux不行,因为linux使能了MMU

MMU

MMU 全称叫做 Memory Manage Unit,也就是内存管理单元。

在老版本的 Linux 中要求处理器必须有 MMU,但是现在Linux 内核已经支持无 MMU 的处理器了。 MMU 主要完成的功能如下

①、完成虚拟空间到物理空间的映射。

②、内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性。

虚拟空间到物理空间的映射,也叫做地址映射。

虚拟地址(VA,Virtual Address)、物理地址(PA, Physcical Address)。对于 32 位的处理器来说,虚拟地址范围是 2^32=4GB,我们的开发板上有 512MB 的 DDR3,这 512MB 的内存就是物理内存,经过 MMU 可以将其映射到整个 4GB 的虚拟空间,

肯定存在多个虚拟地址映射到同一个物理地址上去,虚拟地址范围比物理地址范围大的问题处理器自会处理,这里我们不要去深究

想要操作物理地址,需要通过操作虚拟地址来完成

Linux 内核启动的时候会初始化 MMU,设置好内存映射,设置好以后 CPU 访问的都是虚拟地 址 。 需要得到这个0x1010101物理地址在 Linux 系统里面对应的虚拟地址, 需要用到两个函数: ioremap 和 iounmap

用于物理内存和虚拟内存之间的转换。

ioremap

函 数 用 于 获 取 指 定 物 理 地 址 空 间 对 应 的 虚 拟 地 址 空 间 ,arch/arm/include/asm/io.h

c 复制代码
#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)
{
	return (void __iomem *)phys_addr;
}

有两个参数: cookie 和 size,真正起作用的是函数__arm_ioremap,此函数有三个参数和一个返回值,这些参数和返回值的含义如下:

phys_addr:要映射的物理起始地址。

size:要映射的内存空间大小。

mtype: ioremap 的类型,可以选择 MT_DEVICE、 MT_DEVICE_NONSHARED、MT_DEVICE_CACHED 和 MT_DEVICE_WC, ioremap 函数选择 MT_DEVICE。返回值: __iomem 类型的指针,指向映射后的虚拟空间首地址

假如我们要获取 I.MX6ULL 的 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 寄存器对应的虚拟地址,使用如下代码即可:

复制代码
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
static void __iomem* SW_MUX_GPIO1_IO03;
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);

宏 SW_MUX_GPIO1_IO03_BASE 是寄存器物理地址, SW_MUX_GPIO1_IO03 是映射后的虚拟地址。对于 I.MX6ULL 来说一个寄存器是 4 字节(32 位)的,因此映射的内存长度为 4。映射完成以后直接对 SW_MUX_GPIO1_IO03 进行读写操作即可

iounmap 函数

卸载驱动的时候需要使用 iounmap 函数释放掉 ioremap 函数所做的映射, iounmap 函数原型如下:

复制代码
#define iounmap				__arm_iounmap

iounmap 只有一个参数 addr,此参数就是要取消映射的虚拟地址空间首地址

取消掉 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 寄存器的地址映射

复制代码
iounmap(SW_MUX_GPIO1_IO03);

led.c的框架搭建

#include 复制代码
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/uaccess.h>
#include <linux/io.h>

#define LED_MAJOR   200
#define LED_NAME    "LED"


static int led_open (struct inode *inode, struct file *file){

    return 0;
}

static int  led_write(struct file *file, const char __user *buf, size_t len, loff_t *pos){

    return 0;

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

static const struct file_operations fops={
    .owner = THIS_MODULE,
    .write = led_write,
    .open  = led_open,
    .release = led_close,
};
//入口函数
static int __init led_init(void){

    int ret = 0;
    //1.注册字符设备
    
    ret = register_chrdev(LED_MAJOR,LED_NAME,&fops);
    if (ret<0)
    {
        printk("regiester failed !\r\n");
        return -EIO;
    }
    
    printk("led_init\r\n");
    return 0;
}

//出口
static void __exit led_exit(void){
    unregister_chrdev(LED_MAJOR,LED_NAME);
    printk("led_exit\r\n");
}

//注册和卸载
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

驱动程序编写

​ 1.初始化(可以在注册时初始化,也可以在open时初始化)

​ 2.使用虚拟地址来操作寄存器

复制代码

ioremap返回值为

复制代码
void __iomem *__arm_ioremap(phys_addr_t phys_addr, size_t size,
			    unsigned int mtype)
{
	return (void __iomem *)phys_addr;
}

# define __iomem	__attribute__((noderef, address_space(2)))

声明

复制代码
//物理地址
#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 *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;

注册时,对引脚进行配置

复制代码
	int val =0;
    //0.初始化led灯  地址映射  一个寄存器32位,4个字节
    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(CCM_CCGR1);
    val &=~(3<<26);//清除bit26 27
    val |= (3<<26);//置位bit26 27
    writel(val,CCM_CCGR1);
   
    writel(0x5,SW_MUX_GPIO1_IO03);
    writel(0x10B0,SW_PAD_GPIO1_IO03);

    val = readl(GPIO1_GDIR);
    val |= (1<<3);//置位bit26 27
    writel(val,GPIO1_GDIR);

    val = readl(GPIO1_DR);
    val &=~(1<<3);      //打开LED灯
    writel(val,GPIO1_GDIR);

注销设备时,取消地址映射

复制代码
//出口
static void __exit led_exit(void){
    //取消地址映射
    iounmap(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");
}

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

/*
    args:应用程序参数个数
    argv[]:具体的参数内容,字符串形式
    ./ledapp <filename> <0:1> 0表示关灯,1表示开灯
    ./ledapp /dev/led 0 关灯
    ./ledapp /dev/led 1 开灯
 */
#define LED_OFF
#define LED_ON
int main(int argc,char *argv[]){
    int fd;
    char *filename; //要打开的设备名
    unsigned char databuf[1];
    unsigned int ret = 0;
    if (argc!=3)
    {
        printf("ERROR USAGE\r\n");
        return -1;
    }
    

    filename = argv[1];



    fd = open(filename,O_RDWR);


    if (fd<0)
    {
       printf("file %s failed open\r\n",filename);
    }
    
    databuf[0] = atoi(argv[2]);//将字符装换成数据
    ret  =write(fd,databuf,sizeof(databuf));
    if (ret<0)
    {
        printf("LED control Eroos\r\n");
        close(fd);
        return -1;
    }
    close(fd);
    return 0;
}

编译

复制代码
make
arm-linux-gnueabihf-gcc ledAPP.c -o ledapp
sudo cp led.ko ~/Desktop/nfs/rootfs/lib/modules/4.1.15/
sudo cp ledapp ~/Desktop/nfs/rootfs/lib/modules/4.1.15/

但此时LED被其他程序占用,会被干扰,需要先关闭相关的

复制代码
echo none > /sys/class/leds/sys-led/trigger // 改变 LED 的触发模式

depmod
modprobe led.ko
mknod /dev/led c 200 0
./ledapp /dev/led 1  
./ledapp /dev/led 0

本节主要学习地址映射,以及操作寄存器的方法

相关推荐
西风未眠2 小时前
高效编辑之vi/vim常用快捷键汇总
linux·编辑器·vim
_Stellar2 小时前
Linux 服务器管理 根目录文件夹权限设置 基于用户组实现安全共享
linux·服务器·安全
LUCIFER2 小时前
驱动开发:详细分析 DTB、DTS、DTSI、DTBO 的区别、用途及它们之间的关系
linux·服务器·驱动开发
BullSmall4 小时前
Test Matrix:测试矩阵(IT 领域定义 + 设计实践 + 华为场景应用)
运维·服务器
liuyao_xianhui4 小时前
版本控制器git--gdb/cgdb
linux·运维·服务器·git
CS_浮鱼4 小时前
【Linux进阶】mmap实战:文件映射、进程通信与LRU缓存
linux·运维·c++·缓存
chuxinweihui4 小时前
应用层协议 HTTP
linux·服务器·网络·网络协议·http
xu_yule5 小时前
Linux_16(多线程)信号量+基于环形队列的生成消费模型+自选锁+读写锁
linux·运维·服务器
行初心5 小时前
uos基础 systemctl 查看unit的详细配置
运维