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
本节主要学习地址映射,以及操作寄存器的方法