当我们要操控一个硬件时,需要用应用程序去操控,我们可以编写对应的驱动程序去对硬件进行操作。所以这次的实例中包含led应用程序和驱动程序的编写。
相关的硬件
先看开发板的原理图可知,当GPIO5_3为低电平时led点亮,否则不亮
由上图可知,点亮led需要使用GPIO5_3引脚,使用应该通过CCM给GPIO5使能,查阅芯片手册可知,GPIO5默认使能,无需设置
一个引脚可能被复用,所以设置GPIO5_IO03为GPIO模式,阅读芯片手册可知,将该寄存器的MUX_MODE部分设置为101则为GPIO模式
GPIO有输出也有输入模式,由开发板的原理图可知应该将GPIO设置为输出模式,由芯片手册可知,将GPIO5_GDIR寄存器的对应位设置为1则为输出模式
当准备好以上工作时就要给GPIO设置输出电平,由原理图可知,对寄存器GPIO5_DR对应位设置为1或0就可以设置其输出电平
重要函数介绍
ioremap函数
建立物理地址和虚拟地址之间的映射关系,让内核可以通过虚拟地址来访问硬件设备的物理地址空间,从而实现对硬件设备的控制和数据交互
void __iomem *ioremap(resource_size_t phys_addr, unsigned long size);
phys_addr:这是一个 resource_size_t 类型的参数,表示要映射的硬件设备的物理起始地址。这个地址通常可以从硬件设备的数据手册中获取。
size:这是一个 unsigned long 类型的参数,表示要映射的物理地址空间的大小,单位是字节。
iounmap函数
在使用完 ioremap 映射的虚拟地址后,必须调用 iounmap 函数解除映射,以避免内存泄漏。
void iounmap(void __iomem *addr);
addr:这是一个 void __iomem * 类型的指针,它指向之前通过 ioremap 函数映射得到的虚拟地址。__iomem 是一个内核定义的标记,用于表明该指针指向的是 I/O 内存区域。
代码示例
驱动程序
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <asm/io.h>
static int major; //设备号
static struct class *led_class;
static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
static volatile unsigned int *GPIO5_GDIR;
static volatile unsigned int *GPIO5_DR;
static ssize_t led_drv_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos){
char val;
int ret;
ret=copy_from_user(&val, buf, 1);
if(val){ //不为零点亮
*GPIO5_DR &= ~(1<<3);
}else{
*GPIO5_DR |= (1<<3);
}
return 1;
}
static int led_drv_open(struct inode *node, struct file *file){
//设置为gpio模式
*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3&=~0xf; //先清零
*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3|=0x5;
*GPIO5_GDIR |= (1<<3); //将该位置为1为输出模式
return 0;
}
static struct file_operations led_fods = {
.owner = THIS_MODULE,
.open = led_drv_open,
.write = led_drv_write,
};
//出入口函数
static int __init led_init(void)
{
major=register_chrdev(0,"myled",&led_fods);
//将物理地址转化为虚拟地址使用
IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3=ioremap(0x02290000 + 0x14,4);
GPIO5_GDIR=ioremap(0x020AC004,4);
GPIO5_DR=ioremap(0x020AC000,4);
led_class=class_create(THIS_MODULE, "myled");
device_create(led_class, NULL, MKDEV(major, 0), NULL, "myled");
return 0;
}
static void __exit led_exit(void)
{
//解除之前通过 ioremap 建立的物理地址到虚拟地址的映射关系
iounmap(IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3);
iounmap(GPIO5_GDIR);
iounmap(GPIO5_DR);
device_destroy(led_class,MKDEV(major, 0));
class_destroy(led_class);
unregister_chrdev(major,"myled");
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
当我们定义指针是加入了一个类型修饰符, volatile是一个类型修饰符,用于告诉编译器该变量的值可能会以编译器无法预知的方式被改变,因此编译器在使用这个变量时每次都要从内存中读取其值,而不是使用之前缓存的副本。
i = 1;
i = 2;
当有以上例子时,如果不加volatile,编译器会直接读取i=2,直接跳过了i=1,但是软件和硬件出现这种情况区别很大。
应用程序
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
// ledtest /dev/myled on
// ledtest /dev/myled off
int main(int argc, char **argv)
{
int fd;
char status = 0;
if (argc != 3)
{
printf("Usage: %s <dev> <on|off>\n", argv[0]);
printf(" eg: %s /dev/myled on\n", argv[0]);
printf(" eg: %s /dev/myled off\n", argv[0]);
return -1;
}
// open
fd = open(argv[1], O_RDWR);
if (fd < 0)
{
printf("can not open %s\n", argv[0]);
return -1;
}
// write
if (strcmp(argv[2], "on") == 0)
{
status = 1;
}
write(fd, &status, 1);
return 0;
}
Makfile的编写
在之前的hello驱动程序进行更改
KERN_DIR=/home/book/100ask_imx6ull-sdk/Linux-4.9.88
all:
make -C $(KERN_DIR) M=$(shell pwd) modules
$(CROCROSS_COMPILESS)gcc -o led_test led_test.c
clean:
make -C $(KERN_DIR) M=$(shell pwd) modules clean
rm -rf modules.order
rm -r led_test
obj_m +=led_drv.o
ps:以上代码参考韦东山老师的教程