深圳市住房和建设局官方网站,ps自学网官方网站,铜川北京网站建设,做影视网站赚钱一、Linux 下的 LED 驱动原理 Linux 下的任何驱动#xff0c;最后都是要配置相应的硬件寄存器。
1. 地址映射 MMU 全称叫做 MemoryManage Unit#xff0c;也就是内存管理单元。 现在的 Linux 支持无 MMU 处理器。MMU 主要完成的功能为#xff1a; 1、完成虚拟空间到物理空间…一、Linux 下的 LED 驱动原理 Linux 下的任何驱动最后都是要配置相应的硬件寄存器。
1. 地址映射 MMU 全称叫做 MemoryManage Unit也就是内存管理单元。 现在的 Linux 支持无 MMU 处理器。MMU 主要完成的功能为 1、完成虚拟空间到物理空间的映射。 2、内存保护设置存储器的访问权限设置虚拟存储空间的缓冲特性。 虚拟空间到物理空间的映射其实就是 地址映射。 虚拟地址(VA,Virtual Address)、物理地址(PA Physcical Address)。对于 32位的处理器来说虚拟地址范围是 2^324GB我们的开发板上有 1GB 的 DDR3这 1GB 的内存就是物理内存经过 MMU 可以将其映射到整个 4GB 的虚拟空间 Linux 内核启动的时候会初始化 MMU设置好内存映射设置好以后 CPU 访问的都是虚拟地址。比如 STM32MP157 的 PI0 引脚的端口模式寄存器 GPIOI_MODER 物理地址为0x5000A000。如果没有开启 MMU 的话直接向 0x5000A000 这个寄存器地址写入数据就可以配置 PI0 的引脚功能(输入、输出、复用或模拟等)。 那有人会有疑惑那为什么要开启MMU呢开启 MMU 使得操作系统能够更好地管理内存资源、提供虚拟内存、实施内存保护和权限控制并提供了更高效、安全、灵活的内存访问方式。总而言之开启 MMU 的好处大大滴。 现在开启了 MMU并且设置了内存映射因此就不能直接向 0x5000A000 这个地址写入数据了。我们必须得到 0x5000A000 这个物理地址在Linux 系统里面对应的虚拟地址这里就涉及到了物理内存和虚拟内存之间的转换需要用到 两个函数 ioremap 和 iounmap。 ① ioremap 函数
/** description : 获取指定物理地址空间对应的虚拟地址空间* param - res_cookie : 映射的物理起始地址* param - size : 映射的内存空间大小* return : __iomem 类型的指针,指向映射后的虚拟空间首地址* __iomen:可以修饰指针类型将其标记为 I/O 内存指针 I/O 内存指针目的是告知编译器该指针指向的内存区域用于与 I/O 设备进行直接交互需要采取特殊的读写方式和对齐规则*/
void __iomem *ioremap(resource_size_t res_cookie, size_t size)
{return arch_ioremap_caller(res_cookie, size, MT_DEVICE, __builtin_return_address(0));
} 如果需要 STM32MP157-ATK 的 GPIOI_MODER 寄存器对应的虚拟地址 代码如下
#define GPIOI_MODER (0X5000A000)
static void __iomen *GPIO_MODER_PI;
GPIO_MODER_PI ioremap(GPIOI_MODER, 4); 宏 GPIOI_MODER 是寄存器物理地址 GPIO_MODER_PI 是映射后的虚拟地址。对于 STMP32MP157 来说一个寄存器是 4 字节(32 位)因此映射的内存长度为 4。映射完成以后直接对 GPIO_MODER_PI 进行读写操作即可。 ② iounmap 函数 卸载驱动的时候需要使用 iounmap 函数释放掉 ioremap 函数所做的映射
/** description : 释放 ioremap 所做的映射* param - addr : 取消映射的虚拟地址空间首地址*/
void iounmap (volatile void __iomem *addr); 比如现在要卸载 GPIO_MODER_PI 寄存器的地址映射
iounmap(GPIO_MODER_PI); 2. I/O 内存访问函数 I/O 是输入/输出的意思 这里涉及到两个概念 I/O 端口和 I/O 内存。当外部寄存器或内存映射到 IO 空间时称为 I/O 端口。当外部寄存器或内存映射到内存空间时称为 I/O 内存。 但 ARM 空间只有 I/O 内存。Linux 内核建议使用一组操作函数来对映射后的内存进行读写操作。
① 读操作函数
u8 readb(const volatile void __iomem *addr);
u16 readw(const volatile void __iomem *addr);
u32 readl(const volatile void __iomem *addr);/*
readb、 readw 和 readl 这三个函数分别对应 8bit、 16bit 和 32bit 读操作。
参数 addr 就是要读取写内存地址返回值就是读取到的数据。*/ ②写操作函数
void writeb(u8 value, volatile void __iomem *addr);
void writew(u16 value, volatile void __iomem *addr);
void writel(u32 value, volatile void __iomem *addr);/*
writeb、 writew 和 writel 这三个函数分别对应 8bit、 16bit 和 32bit 写操作。
参数 value 是要写入的数值 addr 是要写入的地址。*/ 二、硬件原理图分析 LED0 接到了 PI0 上 PI0 就是 GPIOI 组的第 0 个引脚当 PI0 输出低电平(0)的时候发光二极管 LED0 就会导通点亮当 PI0 输出高电平(1)的时候发光二极管LED0 不会导通因此 LED0 也就不会点亮。所以 LED0 的亮灭取决于 PI0 的输出电平输出 0 就亮输出 1 就灭。 三、实验程序编写
1. LED 驱动程序编写
#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 /* 设备名字 */#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 *//* 寄存器物理地址 */
#define PERIPH_BASE (0x40000000)
#define MPU_AHB4_PERIPH_BASE (PERIPH_BASE 0x10000000)
#define RCC_BASE (MPU_AHB4_PERIPH_BASE 0x0000)
#define RCC_MP_AHB4ENSETR (RCC_BASE 0XA28)
#define GPIOI_BASE (MPU_AHB4_PERIPH_BASE 0xA000)
#define GPIOI_MODER (GPIOI_BASE 0x0000)
#define GPIOI_OTYPER (GPIOI_BASE 0x0004)
#define GPIOI_OSPEEDR (GPIOI_BASE 0x0008)
#define GPIOI_PUPDR (GPIOI_BASE 0x000C)
#define GPIOI_BSRR (GPIOI_BASE 0x0018)/* 映射后的寄存器虚拟地址指针 */
static void __iomem *MPU_AHB4_PERIPH_RCC_PI;
static void __iomem *GPIOI_MODER_PI;
static void __iomem *GPIOI_OTYPER_PI;
static void __iomem *GPIOI_OSPEEDR_PI;
static void __iomem *GPIOI_PUPDR_PI;
static void __iomem *GPIOI_BSRR_PI;void led_switch(u8 sta)
{u32 val 0;if(sta LEDON) {val readl(GPIOI_BSRR_PI);val | (1 16);writel(val, GPIOI_BSRR_PI);}else if(sta LEDOFF) {val readl(GPIOI_BSRR_PI);val | (1 0);writel(val, GPIOI_BSRR_PI);}
}void led_unmap(void)
{iounmap(MPU_AHB4_PERIPH_RCC_PI);iounmap(GPIOI_MODER_PI);iounmap(GPIOI_OTYPER_PI);iounmap(GPIOI_OSPEEDR_PI);iounmap(GPIOI_PUPDR_PI);iounmap(GPIOI_BSRR_PI);
}static int led_open(struct inode *inode, 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 *filp, const char __user *buf,size_t cnt, loff_t *offt)
{int retvalue;unsigned char databuf[1];unsigned char ledstat;retvalue copy_from_user(databuf, buf, cnt);if(retvalue 0) {printk(kernel write failed!\r\n);return -EFAULT;}ledstat databuf[0];if(ledstat LEDON) {led_switch(LEDON);} else if(ledstat LEDOFF) {led_switch(LEDOFF);}return 0;
}static int led_release(struct inode *inode, struct file *filp)
{return 0;
}static struct file_operations led_fops {.owner THIS_MODULE,.open led_open,.read led_read,.write led_write,.release led_release,
};static int __init led_init(void)
{int retvalue 0;u32 val 0;MPU_AHB4_PERIPH_RCC_PI ioremap(RCC_MP_AHB4ENSETR, 4);GPIOI_MODER_PI ioremap(GPIOI_MODER, 4);GPIOI_OTYPER_PI ioremap(GPIOI_OTYPER, 4);GPIOI_OSPEEDR_PI ioremap(GPIOI_OSPEEDR, 4);GPIOI_PUPDR_PI ioremap(GPIOI_PUPDR, 4);GPIOI_BSRR_PI ioremap(GPIOI_BSRR, 4);val readl(MPU_AHB4_PERIPH_RCC_PI);val ~(0X1 8);val | (0X1 8);writel(val, MPU_AHB4_PERIPH_RCC_PI);val readl(GPIOI_MODER_PI);val ~(0X3 0);val | (0X1 0);writel(val, GPIOI_MODER_PI);val readl(GPIOI_OTYPER_PI);val ~(0X1 0);writel(val, GPIOI_OTYPER_PI);val readl(GPIOI_OSPEEDR_PI);val ~(0X3 0);val | (0x2 0);writel(val, GPIOI_OSPEEDR_PI);val readl(GPIOI_PUPDR_PI);val ~(0X3 0);val | (0x1 0);writel(val,GPIOI_PUPDR_PI);val readl(GPIOI_BSRR_PI);val | (0x1 0);writel(val, GPIOI_BSRR_PI);retvalue register_chrdev(LED_MAJOR, LED_NAME, led_fops);if(retvalue 0){printk(register chrdev failed!\r\n);goto fail_map;}return 0;fail_map:led_unmap();return -EIO;
}static void __exit led_exit(void)
{led_unmap();unregister_chrdev(LED_MAJOR, LED_NAME);
}module_init(led_init);
module_exit(led_exit);MODULE_LICENSE(GPL);
MODULE_AUTHOR(LXS);
MODULE_INFO(intree, Y); 2. 测试 APP
#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 1int 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];fd open(filename, O_RDWR);if(fd 0){printf(file %s open failed!\r\n, argv[1]);return -1;}databuf[0] atoi(argv[2]);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;
} 三、运行测试
1. 编译驱动程序 依然是利用 Makefile 文件把 chrdevbase.c 编译为 chrdevbase.ko 模块
KERNELDIR : /home/alientek/linux/atk-mpl/linux/my_linux/linux-5.4.31 # Linux内核源码路径
CURRENT_PATH : $(shell pwd) # 获取当前所处路径
obj-m : led.o build: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M$(CURRENT_PATH) clean 之后在 /linux/atk-mpl/Drivers/2_led 路径下输入
make -j32 2. 编译测试 APP 因为要在 ARM 上面运行所以要用交叉编译器
arm-none-linux-gnueabihf-gcc ledApp.c -o ledApp 3. 运行测试 将 led.ko 和 ledApp 拷贝到 /linux/nfs/rootfs/lib/modules/5.4.31 路径下
sudo cp ledApp led.ko /home/alientek/linux/nfs/rootfs/lib/modules/5.4.31/ -f 加载 led.ko 驱动模块ls
depmod
modprobe led # 加载驱动
lsmod # 查看加载的驱动 驱动加载成功后创建 /dev/led 设备节点
mknod /dev/led c 200 0 输入以下命令测试 LED
# 打开 LED
./ledApp /dev/led 1# 关闭 LED
./ledApp /dev/led 0 如果开发版的红色 LED 能打开和关闭说明测试成功。卸载驱动
rmmod led.ko