Linux驱动
晴空霹雳一阵响,开始写自己的技术博客了。深思熟虑后编写此系列文章记录自己学习过程中遇到的问题和今后复习所用。Linux驱动IO输出高低电平和单片机类似,也是通过控制寄存器控制高低电平,不过在linux中就进行了分层。将程序分成了驱动和应用层程序,驱动负责底层操作和控制,应用层负责逻辑,侧重于怎么使用。驱动在驱动层将控制的接口写好,注册驱动时将IO口寄存器进行配置。应用层直接操作设备节点,使用文件IO控制LED灯。
基本步骤:
1、 开发环境介绍:迅为开发板:iTop4412,Ubuntu14
2、 查看芯片的技术手册,确定LED连接的IO口控制寄存器。
3、 编写驱动代码。
4、 编写应用代码。
5、 编写Makefile文件,编译驱动程序和应用程序。
6、 导入板子运行测试。
1、电路图如下:
![]()
通过查询官方给出的技术手册和硬件电路连接图可知具体的寄存器如下:
LED2配置寄存器和数据寄存器
![]()
LED3配置寄存器和数据寄存器
![]()
2、编写驱动代码:
#include 《linux/init.h》
#include 《linux/module.h》
#include 《linux/fs.h》
#include 《linux/device.h》
#include 《linux/uaccess.h》
#include 《asm/io.h》
//LED2
#define CPX2_CON 0x11000100
#define GPX2_SIZE 2
//LED3
#define CPX3_CON 0x11000060
#define GPX3_SIZE 2
//指针存放虚拟地址
volatile unsigned long *gpx2conf;
volatile unsigned long *gpx2dat;
volatile unsigned long *gpx3conf;
volatile unsigned long *gpx3dat;
static unsigned int dev_major = 125;
static struct class *devcls;
static struct device *dev;
static int kernel_val = 555;
int chr_dev_open(struct inode *inode,struct file *filp)
{
printk(“******dev %s***********n”,__FUNCTION__);
return 0;
}
//read(fd,buffer,size);
//fd --》 filp
//buffer --》 buffer
//size --》 count
//必须通过copy_to_user进行传递数据。
ssize_t chr_dev_read(struct file *filp, char __user *buffer,size_t count,loff_t *fpos)
{
int ret = copy_to_user(buffer,&kernel_val,count);
printk(“******%s**********n”,__FUNCTION__);
if(ret 》 0){
printk(“ copy_to_user error ret:%dn”,ret);
return -EFAULT;
}
return 0;
}
ssize_t chr_dev_write(struct file *filp, const char __user *buffer,unsigned int count ,loff_t *fpos)
{
int value;
int ret = copy_from_user(&value,buffer,count);
if(ret 》 0){
printk(“ copy_from_user error ret:%dn”,ret);
return -EFAULT;
}
printk(“******%s*********n”,__FUNCTION__);
printk(“ value = %dn”,value);
if(value){
*gpx2dat |= 0x01; //led2点灯
*gpx3dat |= 0x02; //led3点灯
}else{
*gpx2dat &= ~(0x01); //led2灭灯
*gpx3dat &= ~(0x02);//led3灭灯
}
return 0;
}
int chr_dev_close(struct inode *inode,struct file *filp)
{
printk(“******%s*********n”,__FUNCTION__);
return 0;
}
const struct file_operations my_fops = {
.open = chr_dev_open,
.read = chr_dev_read,
.write = chr_dev_write,
.release = chr_dev_close,
};
static int __init chr_dev_init(void)
{
//申请设备号
int ret;
ret = register_chrdev(dev_major,“chr_dev_test”,&my_fops);
if(ret == 0){
printk(“register okn”);
}else{
printk(“register failedn”);
return -EINVAL;
}
// /dev/chr2 创建类
devcls = class_create(THIS_MODULE,“chr_dev_test”);
if(devcls != NULL){
printk(“register class ok!n”);
}else
{
printk(“register class faild!n”);
}
//创建设备节点 以device_create()最后一个参数为准。
dev = device_create(devcls,NULL,MKDEV(dev_major,0),NULL,“chr_dev_cls2”);
if(dev != NULL){
printk(“register dev okn”);
}else{
printk(“register dev failedn”);
}
//LED2 地址映射。
gpx2conf = ioremap(CPX2_CON,GPX2_SIZE);
gpx2dat = gpx2conf + 1;
//LED3
gpx3conf = ioremap(CPX3_CON,GPX3_SIZE);
gpx3dat = gpx3conf +1;
//LED2配置
*gpx2conf &= ~(0xFF);
*gpx2conf |= (0x1);
//LED3 配置
*gpx3conf &= ~(0xFF);
*gpx3conf |= (0x10);
return 0;
}
static void __exit chr_dev_exit(void)
{
//一般释放资源
//和注册时顺序相反
iounmap(gpx3conf);
iounmap(gpx2conf);
device_destroy(devcls,MKDEV(dev_major,0));
class_destroy(devcls);
unregister_chrdev(dev_major,“chr_dev_cls2”);
printk(“*************chr_dev_exit OK!********n”);
}
module_init(chr_dev_init);
module_exit(chr_dev_exit);
MODULE_LICENSE(“GPL”);
驱动的Makefile编写:
KERN_DIR = /home/usr/iTop4412_Kernel_3.0
obj-m +=chr_dev.o
all:
make -C $(KERN_DIR) M=$(PWD) modules
clean:
make -C $(KERN_DIR) M=$(PWD) modules clean
rm -rf modules.order
应用层程序编写:
#include 《fcntl.h》
#include 《unistd.h》
#include 《string.h》
#include 《stdlib.h》
#include 《stdio.h》
#include 《sys/types.h》
#include 《sys/stat.h》
int main(int argc,char *argv[])
{
int fd = open(“/dev/chr_dev_cls2”,O_RDWR); //路径和驱动设备节点名字保持一致
int value = 0;
if(fd 《 0){
perror(“open failed”);
exit(0);
}
read(fd,&value,4);
printf(“__USER__ value : %dn”,value);
//重新给value赋值
value = 666;
write(fd,&value,4);
while(1)
{
value = 0;
write(fd,&value,4);
sleep(1);
value = 1;
write(fd,&value,4);
sleep(1);
}
close(fd);
return 0;
}
3、将编译好的驱动程序和引用程序在arm上运行