嵌入式学习——初步驱动led

驱动开发笔记
1;环境搭建
1)ping通
修改网络配置:vi /etc/network/interfaces 修改成static ip为192.168.1.141
修改网络虚拟编辑器:为有线网卡
重启网卡(ifdown eth0 ifup eth0)或者重启ubuntu(shutdown -r now )
2)设置bootcmd使开发板通过tftp下载自己建立的内核源码树编译得到的
set bootcmd ‘tftp 0x30008000 zImage;bootm 0x30008000’(这个是tftp下载的)
bootcmd=movi read kernel 30008000; bootm 30008000(只是烧录到inand中启动的)
我的tftp目录在/tftpboot下,编译好的zImage要复制到这来,每次开发板启动都是动态到这来下载内核

3)将我们配置的rootfs放到我们配置的nfs服务器的目录下
showmount localhost -e查看我们nfs配置的目录

4)make menuconfig 修改配置 支持nfs

5)修改开发板上环境变量
serverip 要保证为我们tftp服务器ip
bootcmd 设置为tftp下载
bootarg 设置为rootfs从nfs下载
setenv bootargs=console=ttySAC2,115200 root=/dev/mmcblk0p2 rw init=/linuxrc rootfstype=ext3

setenv bootargs root=/dev/nfs nfsroot=192.168.1.141:/root/rootfs/rootfs ip=192.168.1.20:192.168.1.141:192.168.1.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC2,115200

6)修改开发板上命令行密码,将密码去掉
root/rootfs/rootfs/etc/shadow文件

7)rootfs的文件都在开发板下的根目录下 cd /即可,将建立的app可执行程序和.ko模块都放在根文件下
剩下的就是insmod lmod modinfo

8)创建设备文件
mknod /dev/xxx c 主设备号 次设备号

9)应用程序中通过操作设备文件来调用驱动中对应函数

10)驱动文件中操作硬件寄存器应才用虚拟地址
两种方法 静态虚拟映射表和动态虚拟地址映射表,将地址转换一下,操作跟逻辑里面的可以一样

注意事项
1;编译.ko模块的内核要和等下去运行的内核要是同一个

代码

makefile基本是模版不用改

#ubuntu的内核源码树,如果要编译在ubuntu中安装的模块就打开这2个
#KERN_VER = $(shell uname -r)
#KERN_DIR = /lib/modules/$(KERN_VER)/build 


# 开发板的linux内核的源码树目录
KERN_DIR = /root/derive/kernel

#-m表示编译成模块
obj-m   += module_test.o    

#-C表示进入目录  M记录原路径便于返回
all:
    make -C $(KERN_DIR) M=`pwd` modules 
    arm-linux-gcc app.c -o app

cp:
    cp *.ko /root/rootfs/rootfs/driver_test
    cp app /root/rootfs/rootfs/driver_test

.PHONY: clean   
clean:
    make -C $(KERN_DIR) M=`pwd` modules clean
    rm app

总结
模块的makefile非常简单,本身并不能完成模块的编译,而是通过make -C进入到内核源码树下借用内核源码的体系来完成模块的编译链接的。这个Makefile本身是非常模式化的,3和4部分是永远不用动的,只有1和2需要动。1是内核源码树的目录,你必须根据自己的编译环境

/

驱动代码,尽量只包含驱动,操作寄存器代码,逻辑代码尽量放到应用层去

#include <linux/module.h>       // module_init  module_exit
#include <linux/init.h>         // __init   __exit
#include <linux/fs.h>           //包含file_operations
#include <asm/uaccess.h>
#include <mach/gpio-bank.h>
#include <mach/regs-gpio.h>
#include <linux/ioport.h>
#include <linux/io.h>
#define GPJ0CON     S5PV210_GPJ0CON
#define GPJ0DAT     S5PV210_GPJ0DAT

#define rGPJ0CON    *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT    *((volatile unsigned int *)GPJ0DAT)

#define GPJ0CON_pa      0xE0200240
unsigned int *rGPJ0CON_pa;

char kernelbuf[100];
static int test_open(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "chrdev_init helloworld init\n");
    rGPJ0CON = 0x11111111;
    //rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
    return 0;
}

static int test_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "chrdev_exit helloworld exit\n");
    //rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
    return 0;
}
ssize_t test_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
    printk(KERN_INFO "test_read\n");
    copy_to_user(buf, kernelbuf, size);
}
//buf是应用层的要需要写入的buf,size需要写入的大小
static ssize_t test_write(struct file *filp, const char __user *buf,size_t size, loff_t *ppos)
{
    printk(KERN_INFO "test_write\n");
    copy_from_user(kernelbuf, buf, size);//这里的大小一般是两个buf中取小的
    if(kernelbuf[0] == '1')
    {
        rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
    }
    else if(kernelbuf[0] == '0')
    {
        rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
    }
}
//第一步定义file_operations结构体  查内核复制
static const struct file_operations test_fops = {
    .owner      = THIS_MODULE,   
    .open       = test_open,
    .release    = test_release,
    .read       = test_read,
    .write      = test_write,
};
int myret = -1;
//第二步定义注册注销             查内核复制



// 模块安装函数
static int __init chrdev_init(void)
{   

    myret = register_chrdev(0, "test_modu", &test_fops);   //margin传0表示由内核自动分配
    if(myret < 0)
    {
        printk(KERN_ERR "chrdev_init helloworld init\n");  //关于打印级别或者错误编号意义
        return  -EINVAL; //errno-base.h中定义了错误的编号    如果不知道可以去搜一个然后追
                                                        //定义所有的头文件
    }
    printk(KERN_INFO "open major %d\n", myret);
    //test_open();
    //动态映射  
    if (!request_mem_region(GPJ0CON_pa, 8, "GPJ0_LED")) 
    {
        printk(KERN_ERR "request_mem_region error\n");
        return -EINVAL;
    }
    printk(KERN_INFO "request_mem_region success\n");
    rGPJ0CON_pa = ioremap(GPJ0CON_pa, 8);

    *rGPJ0CON_pa = 0x11111111;
    *(rGPJ0CON_pa + 1) = ((0<<3) | (0<<4) | (0<<5));

    return 0;
}

// 模块卸载函数
static void __exit chrdev_exit(void)
{
    unregister_chrdev(myret, "test_modu");
    //test_release();
    *(rGPJ0CON_pa + 1) = ((1<<3) | (1<<4) | (1<<5));
    iounmap(rGPJ0CON_pa);
    release_mem_region(GPJ0CON_pa, 8);
}


module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");              // 描述模块的许可证
MODULE_AUTHOR("aston");             // 描述模块的作者
MODULE_DESCRIPTION("module test");  // 描述模块的介绍信息
MODULE_ALIAS("alias xxx");          // 描述模块的别名信息








应用层

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define Myname "/dev/test"  //mknod的那个对应的设备文件
char appbuf[100];
int main()
{
    int ret = -1;
    int i = 0;
    ret = open(Myname, O_RDWR);

    if(ret < 0)
    {
        printf("open %s error\n", Myname);
        return -1;
    }
    printf("open %s success\n", Myname);

    //读写文件间接操作驱动的读写函数
    //write(ret, "hellowhold", 10);
    //(ret, appbuf, 10);
    //sleep(10);

    printf("下面驱动led灯  输入on  or off or flash\n");
    while(1)
    {
        scanf("%s",appbuf);
        if(!strcmp(appbuf, "on"))
        {
            write(ret, "1", 1);
        }
        else if(!strcmp(appbuf, "off"))
        {
            write(ret, "0", 1);
        }
        else if(!strcmp(appbuf, "flash"))
        {
            for(i = 0; i < 3; i++)
            {
                write(ret, "1", 1);
                sleep(1);
                write(ret, "0", 1);
                sleep(1);
            }
        }
    }

    //关闭
    close(ret);
    printf("read appbuf = %s\n", appbuf);
    return 0;
}

总结:
1;完成MODULE_xxx的一些宏(描述模块的一下基本信息
2;module_init(chrdev_init);module_exit(chrdev_exit);绑定模块建立和撤销时对应insmod和rmmod调用的对应函数
3;注册驱动调用register_chrdev,
关键参数
主设备号若传0则表示内核自动分配有返回值返回,
file_operations结构体指针(里面只要是一些函数指针)用来挂接驱动和内核api的,就是应用层调用这个函数则会调用该结构体指向的对应函数,从内核复制并修改,并且完成内核函数指针指向的函数进行定义。
4;创建应用程序,主要是io操作,操作的就是mknod建立的设备文件,这样才会完成在应用层对该文件操作,在驱动层则可以接收,如应用层open设备文件,则在驱动层相当于调用file_operations结构体中open元素指向的函数。
5;在驱动层操作寄存器的虚拟地址来进行读写操作来操作硬件

采用驱动新接口,注册注销时都分两步,设备号注册和驱动注册两步
代码,只需要修改init绑定的函数

// 模块安装函数
static int __init chrdev_init(void)
{   
/*
    旧接口的注册
    myret = register_chrdev(0, "test_modu", &test_fops);   //margin传0表示由内核自动分配
    if(myret < 0)
    {
        printk(KERN_ERR "chrdev_init helloworld init\n");  //关于打印级别或者错误编号意义
        return  -EINVAL; //errno-base.h中定义了错误的编号    如果不知道可以去搜一个然后追
                                                        //定义所有的头文件
    }
    printk(KERN_INFO "open major %d\n", myret);
*/
    //新接口注册
    //第一步注册设备号   先MKDEV得到设备号,再利用register_chrdev_region来注册
    int retval;
    //mydev = MKDEV(200,0);//将主设备号和次设备号的起始序列合并成设备号
    //retval = register_chrdev_region(mydev, MYCOUNT, "test_modu");
    //分配设备号
    retval = alloc_chrdev_region(&mydev, 0, MYCOUNT, "test_modu");
    if (retval < 0) {
        printk(KERN_ERR "Unable to register minors for test_modu\n");
        goto flag1;
    }
    printk(KERN_INFO "register_chrdev_region success\n");
    printk(KERN_INFO "major %d  minors %d\n", MAJOR(mydev), MINOR(mydev));


    //绑定注册驱动
    pmycdev = cdev_alloc();  //相当对内核里面申请堆内存
    if (!pmycdev)
    {
        printk(KERN_ERR "cdev_alloc error\n");
        goto flag2;
    }

    cdev_init(pmycdev, &test_fops);//绑定初始化mycdev
    retval = cdev_add(pmycdev, mydev, MYCOUNT);//注册将file_operation结构体与设备号绑定
    if (retval) {
        printk(KERN_ERR "Unable to get testmodu major \n");
        goto flag3;
    }


    //test_open();
    //动态映射  
    if (!request_mem_region(GPJ0CON_pa, 8, "GPJ0_LED")) 
    {
        printk(KERN_ERR "request_mem_region error\n");
        goto flag4;
    }
    printk(KERN_INFO "request_mem_region success\n");
    rGPJ0CON_pa = ioremap(GPJ0CON_pa, 8);

    *rGPJ0CON_pa = 0x11111111;
    *(rGPJ0CON_pa + 1) = ((0<<3) | (0<<4) | (0<<5));

    return 0;

//一个中途出错的倒影式错误处理方法
flag4:
    cdev_del(pmycdev);
flag3:
    cdev_del(pmycdev);
flag2:
    unregister_chrdev_region(mydev, MYCOUNT);
flag1:
    return -EINVAL;

}

// 模块卸载函数
static void __exit chrdev_exit(void)
{
/*
旧的注销方法
    unregister_chrdev(myret, "test_modu");
    */
    //驱动注销
    cdev_del(pmycdev);
    //设备号注销
    unregister_chrdev_region(mydev, MYCOUNT);

    //test_release();
    *(rGPJ0CON_pa + 1) = ((1<<3) | (1<<4) | (1<<5));
    iounmap(rGPJ0CON_pa);
    release_mem_region(GPJ0CON_pa, 8);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值