提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
提示:这里可以添加本文要记录的大概内容:
会对着手册操作点亮LED对应的寄存器,裸机部分有说。
提示:以下是本篇文章正文内容,下面案例可供参考
一、点亮、熄灭LED
1、物理地址与虚拟地址
物理地址就是寄存器地址,Linux 内核启动的时候会初始化 MMU,设置好内存映射,设置好以后 CPU 访问的都是虚拟 地 址 。 比 如 I.MX6ULL 的 GPIO1_IO03 引 脚 的 复 用 寄 存 器IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 的地址为 0X020E0068。如果没有开启 MMU 的话直接向 0X020E0068 这个寄存器地址写入数据就可以配置 GPIO1_IO03 的复用功能。现在开启了 MMU,并且设置了内存映射,因此就不能直接向 0X020E0068 这个地址写入数据了。我们必须得到 0X020E0068 这个物理地址在 Linux 系统里面对应的虚拟地址,这里就涉及到了物理内存和虚拟内存之间的转换,需要用到两个函数: ioremap 和 iounmap。
1、 ioremap 函数
ioremap 函 数 用 于 获 取 指 定 物 理 地 址 空 间 对 应 的 虚 拟 地 址 空 间
#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 arch_ioremap_caller(phys_addr, size, mtype,__builtin_return_address(0));
}
ioremap 是个宏,有两个参数: 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 进行读写操作即可。
2、 iounmap 函数
卸载驱动的时候需要使用 iounmap 函数释放掉 ioremap 函数所做的映射, iounmap 函数原型如下:
void iounmap (volatile void __iomem *addr)
iounmap 只有一个参数 addr,此参数就是要取消映射的虚拟地址空间首地址。假如我们现在要取消掉 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 寄存器的地址映射,使用如下代码即可:
iounmap(SW_MUX_GPIO1_IO03);
第一个参数就是物理地址其实大小,第二个参数就是要转化的字节数量。0X01010101,开始10个地址进行转换,va=ioremap(0X01010101, 10)
2、I/O 内存访问函数
这里说的 I/O 是输入/输出的意思,并不是我们学习单片机的时候讲的 GPIO 引脚。这里涉及到两个概念: I/O 端口和 I/O 内存。当外部寄存器或内存映射到 IO 空间时,称为 I/O 端口。当外部寄存器或内存映射到内存空间时,称为 I/O 内存。但是对于 ARM 来说没有 I/O 空间这个概念,因此 ARM 体系下只有 I/O 内存(可以直接理解为内存)。使用 ioremap 函数将寄存器的物理地址映射到虚拟地址以后,我们就可以直接通过指针访问这些地址,但是 Linux 内核不建议这么做,而是推荐使用一组操作函数来对映射后的内存进行读写操作。
1、读操作函数
读操作函数有如下几个:
1 u8 readb(const volatile void __iomem *addr)
2 u16 readw(const volatile void __iomem *addr)
3 u32 readl(const volatile void __iomem *addr)
readb、 readw 和 readl 这三个函数分别对应 8bit、 16bit 和 32bit 读操作,参数 addr 就是要读取写内存地址,返回值就是读取到的数据。
2、写操作函数
写操作函数有如下几个:
1 void writeb(u8 value, volatile void __iomem *addr)
2 void writew(u16 value, volatile void __iomem *addr)
3 void writel(u32 value, volatile void __iomem *addr)
writeb、 writew 和 writel 这三个函数分别对应 8bit、 16bit 和 32bit 写操作,参数 value 是要写入的数值, addr 是要写入的地址。
3、代码梳理
1、寄存器物理地址
/*寄存器物理地址*/
#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)
2、将物理地址映射到虚拟地址
/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_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;
3、在led_init()对led初始化
1、寄存器地址映射
假如我们要获取 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 进行读写操作即可
/* 1、寄存器地址映射 */
IMX6U_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);
2、使能GPIO1时钟
/* 2、使能GPIO1时钟 */
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3 << 26); /* 清楚以前的设置 */
val |= (3 << 26); /* 设置新值 */
writel(val, IMX6U_CCM_CCGR1);
3、设置复用,设置电气属性
/* 3、设置GPIO1_IO03的复用功能,将其复用为
* GPIO1_IO03,最后设置IO属性。
*/
writel(0x5, SW_MUX_GPIO1_IO03);
/*寄存器SW_PAD_GPIO1_IO03设置IO属性
*bit 16:0 HYS关闭
*bit [15:14]: 00 默认下拉
*bit [13]: 0 kepper功能
*bit [12]: 1 pull/keeper使能
*bit [11]: 0 关闭开路输出
*bit [7:6]: 10 速度100Mhz
*bit [5:3]: 110 R0/6驱动能力
*bit [0]: 0 低转换率
*/
writel(0x10B0, SW_PAD_GPIO1_IO03);
4、设置GPIO1_IO03为输出功能
/* 4、设置GPIO1_IO03为输出功能 */
val = readl(GPIO1_GDIR);
// val &= ~(1 << 3); /* 清除以前的设置 */
val |= (1 << 3); /* 设置为输出 */
writel(val, GPIO1_GDIR);
5、开启LED
/* 5、默认开启LED */
val = readl(GPIO1_DR);
val &= ~(1 << 3);
writel(val, GPIO1_DR);
4、注销字符驱动
iounmap 只有一个参数 addr,此参数就是要取消映射的虚拟地址空间首地址。假如我们现在要取消掉 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 寄存器的地址映射,使用如下代码即可:
iounmap(SW_MUX_GPIO1_IO03);
int val = 0;
/* 关闭LED , 关闭一定要在取消映射前*/
val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val, GPIO1_DR);
有物理地址对应的虚拟地址映射,就一定要在注销字符驱动模块里取消映射
/* 取消映射 */
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
4、编译烧录
虚拟机下
make
sudo cp led.ko /home/zero/linux/nfs/rootfs/lib/modules/4.1.15/
xshell
depmod
modprobe // 加载驱动,灯亮
rmmod // 卸载驱动,灯灭
完整led.c代码
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define LED_MAJOR 200 /* 主设备号 */
#define LED_NAME "led" /* 设备名字 */
static int led_open(struct inode *innode,struct file *filp)
{
return 0;
}
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
static ssize_t led_write (struct file *file,const char __user *buf,size_t cnt,loff_t *offt)
{
return 0;
}
static int led_release (struct inode *inode,struct file *filp)
{
return 0;
}
/*定义结构体变量并初始化
语法:
struct 结构体类型 变量名 = {
.成员1 = 初始值1,
.成员2 = 初始值2 ,
...
};
*/
static struct file_operations led_fops={
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_read,
.release = led_release,
};
static int __init led_init(void)
{
/*注册字符驱动 原型 :
* static inline int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
* 归根到底还是结构体变量与结构体指针(指向结构体变量的指针) 之间的关系
0:fops = &led_fops将结构体变量的地址存放在一个指针变量中,那么指针变量就指向该结构体变量。
1:结构体变量 led_fops
2:结构体指针 fops
3:参数要求是传入一个 struct file_operations 类型的指针fops
4:将指针变量 led_fops取地址& 得到&led_fops,
5:赋值,fops = &led_fops,即满足要求 (结构体变量的起始地址(取地址)就是结构体变量的指针)
6:引用,
结构体变量引用结构体成员用. 即 led_fops.read led_fops.write
指针变量引用结构体成员用-> 即 fops->read fops->write
*/
int ret =0;
ret = register_chrdev(LED_MAJOR, LED_NAME,&led_fops); //调用函数,不用把函数类型带上。有返回值
printk("led_init\r\n");
return 0;
}
static void __exit led_exit(void) // void类型函数没有返回值
{
/*卸载字符驱动*/
// static inline void unregister_chrdev(unsigned int major, const char *name)
unregister_chrdev(LED_MAJOR,LED_NAME); //调用函数,不用把函数类型带上。
printk("led_exit\r\n");
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zero");
二、完善驱动与APP编写
copy_to_user()这个函数的完整形态为
unsigned long copy_to_user(void *to, const void *from, unsigned long n);
这个函数的作用是将内核空间的数据复制到用户空间。其中
to:目标地址(用户空间)
from:源地址(内核空间)
n:将要拷贝数据的字节数
返回:成功返回0,失败返回没有拷贝成功的数据字节数
copy_from_user()这个函数的完整形态为
unsigned long copy_from_user(void *to, const void *from, unsigned long n);
这个函数的作用是将用户空间的数据复制到内核空间。其中
to:目标地址(内核空间)
from:源地址(用户空间)
n:将要拷贝数据的字节数
返回:成功返回0,失败返回没有拷贝成功的数据字节数
使用这个两个函数就可以实现内核空间和用户空间的数据交互了
完整代码:
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#define LEDOFF 0
#define LEDON 1
/*
* @description : main主程序
* @param - argc : argv数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename;
unsigned char databuf[1];
if(argc != 3){
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
/* 打开led驱动 */
fd = open(filename, O_RDWR);
if(fd < 0){
printf("file %s open failed!\r\n", argv[1]);
return -1;
}
databuf[0] = atoi(argv[2]); /* 要执行的操作:打开或关闭 */
/* 向/dev/led文件写入数据 */
retvalue = write(fd, databuf, sizeof(databuf));
if(retvalue < 0){
printf("LED Control Failed!\r\n");
close(fd);
return -1;
}
retvalue = close(fd); /* 关闭文件 */
if(retvalue < 0){
printf("file %s close failed!\r\n", argv[1]);
return -1;
}
return 0;
}
输入如下命令编译测试 ledApp.c 这个测试程序:
arm-linux-gnueabihf-gcc ledApp.c -o ledApp
sudo cp ledApp /home/zero/linux/nfs/rootfs/lib/modules/4.1.15/
编译成功以后就会生成 ledApp 这个应用程序。