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

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

相关推荐
高山有多高11 小时前
【Linux笔记】Socket编程基础
linux
吴声子夜歌11 小时前
Nginx应用与运维——Nginx Web服务应用实战(Python网站的搭建)
运维·前端·nginx
资深技术分享员11 小时前
Geejing WebBuilder 数据库连接配置与在线 SQL 工具,运维的左右手
运维·数据库·sql·低代码
高山有多高11 小时前
【Linux笔记】UpdSocket
linux·运维·笔记
皓月盈江11 小时前
Linux系统grep、sed 、awk介绍下,使用方法及区别?
linux·运维·服务器·sed·grep·awk
云栖梦泽12 小时前
Linux内存管理
linux·运维·服务器
吴声子夜歌12 小时前
Nginx应用与运维——Nginx Web服务应用实战(静态文件服务器的搭建)
运维·前端·nginx
Lucis__12 小时前
编译链接全链路解析:ELF形成到可执行加载的发展历程
linux·程序·elf文件
海宇服务12 小时前
零信任架构实战:基于海宇身份证OCR构建自动化自助终端核验网关
运维·人工智能·架构·自动化
ken223212 小时前
ubuntu 24.04, audacity 4 AppImage 加载 libavdevice.so.62 /.61 /.63 失败:依赖缺失?
linux·运维·ubuntu