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

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

相关推荐
majingming12316 小时前
ubuntu下的交叉编译
linux·运维·ubuntu
shchojj16 小时前
ubuntu 因为写错pam.d文件引起的sudo权限丢失
linux·运维·ubuntu
小康小小涵16 小时前
WSL2安装与移植到F盘
运维·服务器
funfan051716 小时前
【运维】Linux/Debian系统时间校准方法简记
linux·运维·debian
InfiSight智睿视界16 小时前
门店智能体技术如何破解美容美发连锁的“标准执行困境”
大数据·运维·人工智能
慕容雪_17 小时前
运维笔记-网络共享
运维·笔记·网络共享
Florence2317 小时前
cuFuncSetCacheConfig
linux·运维·服务器
Petal99091217 小时前
内核调试:Linux编译内核源码为deb并更新内核
linux·运维·服务器
用户4672445449917 小时前
Linux 进程管理
linux
Wpa.wk17 小时前
性能测试-初识性能测试基础(性能测试流程,计划等)
java·运维·经验分享·测试工具·性能测试