MT7688双摄像头双电机驱动小车(3)驱动实现

主目录:

http://blog.csdn.net/DFSAE/article/details/78715815


本篇是电机驱动的实现,因为摄像头为免驱,而串口openwrt也是自带驱动。


本篇目录:

一.驱动框架

二.电机驱动实现

1.实现目标

2.硬件连线

3.寄存器配置

4.代码说明

5.makefile讲解

三.编译

四.安装及加载模块

1.安装

2.加载模块

五.开机加载


正文:


一.驱动框架

由于驱动程序编写这部分涉及的范围比较大有空这部分的资料补上
参考:(补充。。)
简单的说,驱动可以用对文件操作的方式进行访问,比如open,write,read等。因此只要实现对应的接口即可,剩下的按照模板套用。

二.电机驱动实现

1.实现目标

电机驱动模块负责控制2个电机的转速和方向。
硬件上设计为每一个电机由一路PWM和一路方向控制线控制:PWM控制速度,另一个IO控制电机转动方向。

定义软件接口
(1).写入控制接口:
MT7688双摄像头双电机驱动小车(3)驱动实现_第1张图片

(2).读取接口:
在传入数组中获得2个PWM的占空比

2.硬件连线

看MT7688用户手册:MT7688拥有4路PWM输出,我们可以用到其中的两路CH0,CH1。分别对应引脚18,19脚。
这里写图片描述

决定选用硬件连接如下:
硬件接线为:PWM(ch0,ch1)GPIO#18,19,IO控制采用GPIO#2和GPIO#11脚。

3.寄存器配置

首先需要看手册来进行寄存器的配置。

首先是时钟配置寄存器CLKCFG1:
使用PWM要开启PWM的时钟
MT7688双摄像头双电机驱动小车(3)驱动实现_第2张图片

下面是GPIO1模式配置寄存器,地址为10000060,现需要配置IO口为PWM输出模式(注意这里URAT1和PWM是引脚复用的)。我们需要输出2路PWM(CH0,CH1),所以需要置位第24-25位。
MT7688双摄像头双电机驱动小车(3)驱动实现_第3张图片
MT7688双摄像头双电机驱动小车(3)驱动实现_第4张图片

控制PWM具体输出的主要在PWM寄存器中,这里寄存器比较多,主要有这几个,以PWMX的有0-3个通道的寄存器:
PWM_ENABLE寄存器是PWM使能寄存器,可以控制PWM的开关。
MT7688双摄像头双电机驱动小车(3)驱动实现_第5张图片

PWMX_CON控制PWM输出的一些配置,比如时钟等。
MT7688双摄像头双电机驱动小车(3)驱动实现_第6张图片
不清楚老PWM模式和新PWM模式什么区别,这里就用老PWM模式;GUARD_VALUE和IDLE_VALUE是用来不同时段的电平,时钟源和分频是用来控制PWM的周期的。

PWMX_WAVE_NUM寄存器位脉冲重复的次数,这里设为0,表示循环不停输出。
MT7688双摄像头双电机驱动小车(3)驱动实现_第7张图片

老PWM模式下的周期次数,及持续时间,两个数值的比值就是占空比。
MT7688双摄像头双电机驱动小车(3)驱动实现_第8张图片
MT7688双摄像头双电机驱动小车(3)驱动实现_第9张图片


对于普通IO口的配置,注意,因为这里有个控制线占用的是I2S_WS的IO引脚,所以要在GPIO1_MODE寄存器中关闭I2S功能。
MT7688双摄像头双电机驱动小车(3)驱动实现_第10张图片
该寄存器是控制IO口输入输出方向的。我们需要改为输出
这里写图片描述
GPIO_DATA_0这个寄存器是存储(输入状态)或者设定(输出状态)IO口的值的。

4.代码说明

首先是封装PWM寄存器的基础操作集合。中间又分为PWM使能,失能,配置,设置占空比,获得占空比的操作。pwm_cfg 结构体包括了PWM的一些配置属性。那些操作函数主要根据结构体里的参数对寄存器进行配置,从而控制PWM输出。

//pwm结构 
typedef struct pwm_cfg {  
    int no;  
    PWM_CLK_SRC    clksrc;    //时钟源
    PWM_CLK_DIV    clkdiv;    //时钟分频
    unsigned char  idelval;  
    unsigned char  guardval;  
    unsigned short guarddur;  
    unsigned short wavenum;   //脉冲产生次数,0为无限
    unsigned short datawidth;   //数据宽度,即周期时间
    unsigned short threshold;  //阈值,即跳变时间。该数值和datawidth的比值即为占空比
}pwm_cfg;  

//PWM命令操作
static int pwm_ctl(struct pwm_cfg *cfg, unsigned int cmd)
{
    switch (cmd) 
    {  
        case PWM_ENABLE:  
            pwm_enable(((struct pwm_cfg *)cfg)->no);  
            break;  
        case PWM_DISABLE:  
            pwm_disable(((struct pwm_cfg *)cfg)->no);  
            break;  
        case PWM_CONFIGURE:  
            pwm_config((struct pwm_cfg *)cfg);  
            break;  
        case PWM_GETSNDNUM:  
            pwm_getsndnum((struct pwm_cfg *)cfg);  
            break; 
        case PWM_DUTYSET:  
            pwm_dutySet((struct pwm_cfg *)cfg);  
            break;  
        default:  
            return -1;  
    }
    return 1;
}

首先是使能和失能:
spin_lock_irqsave和spin_unlock_irqrestore是为保证安全进行原子操作,具体可见:
https://www.cnblogs.com/aaronLinux/p/5890924.html
该函数是对PWM_ENABLE寄存器进行赋值操作。

static void pwm_enable(int no)  
{  
    unsigned long  flags;  
    printk("enable pwm%d\n", no);  

    spin_lock_irqsave(&pwm_lock, flags);  
    *PWM_ENABLE_REG  |= PWM_ENABLE_VALUE(no);    
    spin_unlock_irqrestore(&pwm_lock, flags);  

}  
static void pwm_disable(int no)  
{  
    unsigned long  flags;  

    printk("disable pwm%d\n", no);  

    spin_lock_irqsave(&pwm_lock, flags);  
    *PWM_ENABLE_REG  &= ~PWM_ENABLE_VALUE(no);      
    spin_unlock_irqrestore(&pwm_lock, flags);  

}  

接着是PWM配置函数,就是配置前面几个寄存器,详细见前面寄存器说明。

//配置pwm  
static void pwm_config(struct pwm_cfg *cfg)  
{  
    u32 value = 0;  
    unsigned long  flags;  
    u32 basereg;  
    volatile unsigned long *tReg;
    printk("config PWM%d\n", cfg->no); 

    basereg = PWM_REG[cfg->no];   

    spin_lock_irqsave(&pwm_lock, flags);     

    /* 1.设置PWM控制寄存器 */  
    tReg = (volatile unsigned long *)ioremap((PREG_PWM_BASE + basereg + PWM_REG_CON),4);

    /* old PWM模式 */  
    value |= PWM_GPIO_MODE_VALUE;  

    /*设置空闲值和输出值*/  
    value &= ~((1 << PWM_IVAL_BIT) | (1 << PWM_GVAL_BIT));  
    value |= ((cfg->idelval & 0x1) << PWM_IVAL_BIT);  
    value |= ((cfg->guardval & 0x1) << PWM_GVAL_BIT);  

    /* 设置信号源时钟*/  
    if (cfg->clksrc == PWM_CLK_100KHZ) {  
        value &= ~(1<<3);             //设置时钟为100KHZ
    } else {  
        value |= (1<<3);             //设置时钟为100MHZ
    }  
    /* 设置时钟分频 */  
    value &= ~0x7;  
    value |= (0x7 & cfg->clkdiv);  

     *tReg = value;   
     iounmap(tReg); //解映射

    /* 2. 设置guard duration值 */
    tReg = (volatile unsigned long *)ioremap((PREG_PWM_BASE + basereg + PWM_REG_GDUR),4);
    value = 0;
    value |= (cfg->guarddur & 0xffff);  
    *tReg = value;   
    iounmap(tReg); //解映射


    /* 3.设置脉冲产生次数,为0则一直不断产生,直到是失能 */  
    tReg = (volatile unsigned long *)ioremap((PREG_PWM_BASE + basereg + PWM_REG_WNUM),4); 
    value = 0;  
    value |= (cfg->wavenum & 0xffff);  
    *tReg = value;   
    iounmap(tReg); //解映射 


    /* 4.设置数据宽度值 */  
    tReg = (volatile unsigned long *)ioremap((PREG_PWM_BASE + basereg + PWM_REG_DWID),4); 
    value = 0; 
    value |= (cfg->datawidth & 0x1fff);  
    *tReg = value;   
    iounmap(tReg); //解映射    


    /* 5. 设置thresh值*/  
   tReg = (volatile unsigned long *)ioremap((PREG_PWM_BASE + basereg + PWM_REG_THRE),4); 
    value = 0; 
    value |= (cfg->threshold & 0x1fff);  
    *tReg = value;   
    iounmap(tReg); //解映射       


    spin_unlock_irqrestore(&pwm_lock, flags); 

} 

读取和设置PWM占空比函数。

//读取PWM占空比
static void pwm_getsndnum(struct pwm_cfg *cfg)  
{  
    u32 value;  
    unsigned long  flags;  
    u32 basereg = PWM_REG[cfg->no]; 
    volatile unsigned long *tReg; 

    printk("read output PWM%d number...\n", cfg->no);    
    spin_lock_irqsave(&pwm_lock, flags);  
    tReg = (volatile unsigned long *)ioremap((PREG_PWM_BASE + basereg + PWM_REG_SNDNUM),4);
    cfg->wavenum = *tReg;  
    iounmap(tReg); //解映射 
    spin_unlock_irqrestore(&pwm_lock, flags);  

} 

//设置PWM波占空比
static void pwm_dutySet(struct pwm_cfg *cfg)
{
    u32 value = 0;
    u32 basereg;  
    unsigned long  flags;  
     volatile unsigned long *tReg;
    basereg = PWM_REG[cfg->no];   
    /* 设置thresh值*/  
    //printk("set PWM%d duty\n", cfg->no);  
    spin_lock_irqsave(&pwm_lock, flags);  
    tReg = (volatile unsigned long *)ioremap((PREG_PWM_BASE + basereg + PWM_REG_THRE),4);
    *tReg &= ~(0x1fff);  
    value |= (cfg->threshold & 0x1fff);  
    *tReg = value;
    spin_unlock_irqrestore(&pwm_lock, flags);  

}

前面的都是基础函数,这里的是驱动调用时操作的函数。

/************************IO寄存器定义***********************************/
#define    PREG_CLKCFG                        0x10000030      
#define    PREG_CLKCFG_L                        4
#define    PREG_AGPIO_CFG                     0x1000003C
#define    PREG_AGPIO_CFG_L                     4
#define    PREG_GPIO_MODE                     0x10000060
#define    PREG_GPIO_MODE_L                     4
#define    PREG_PWM_BASE                      0x10005000
#define    PREG_PWM_ENABLE                    0x10005000
#define    PREG_PWM_ENABLE_L                    4
#define    PREG_GPIO_CTRL                     0x10000600
#define    PREG_GPIO_CTRL_L                     4
#define    PREG_GPIO_DATA                     0x10000620
#define    PREG_GPIO_DATA_L                     4


#define    PWM_MODE_BIT                         15   
#define    PWM_GVAL_BIT                         8   
#define    PWM_IVAL_BIT                         7    
#define    MOTER1_DIR_IO_BIT                    2          //GPIO#2        
#define    MOTER2_DIR_IO_BIT                    11         //GPIO#11


#define    PWM_CLKCFG_VALUE                   (1 << 31)
#define    PWM_AGPIO_CFG_VALUE                (0x0F<<17)
#define    PWM_GPIO_MODE_VALUE                ~(3 << 28 | 3 << 30)  //PWM1,PWM0
#define    PWM_ENABLE_VALUE(no)               (1 << no) 
#define    GPIO2_MODE_VALUE                   (0x01<<6)       //gpio#2 i2c

 #define PWM_NUM  4                 //PWM通道数 

//初始化
static void pwm_init(void)
{
    int i = 0;
    //io映射
    CLKCFG = (volatile unsigned long *)ioremap(PREG_CLKCFG,PREG_GPIO_MODE_L);
    AGPIO_CFG = (volatile unsigned long *)ioremap(PREG_AGPIO_CFG,PREG_AGPIO_CFG_L);
    GPIO_MODE = (volatile unsigned long *)ioremap(PREG_GPIO_MODE,PREG_GPIO_MODE_L);
    PWM_ENABLE_REG = (volatile unsigned long *)ioremap(PREG_PWM_ENABLE,PREG_PWM_ENABLE_L);

    PWM_BASE = PWM_ENABLE_REG;

    //pwm ch0ch1
    *CLKCFG |= PWM_CLKCFG_VALUE;                       //使能时钟
    *AGPIO_CFG |= PWM_AGPIO_CFG_VALUE;                 //设置为AGPIO的物理配置
    *GPIO_MODE &= PWM_GPIO_MODE_VALUE;                 //设置PWM模式  

    //失能所有pwm
    for (i = 0; i < PWM_NUM; i++) 
    {  
            pwm_disable(i);    
    }  
}

static  void pwm_exit(void)
{
    //解除GPIO映射
    iounmap(CLKCFG);
    iounmap(AGPIO_CFG);
    iounmap(GPIO_MODE);
    iounmap(PWM_ENABLE_REG);
}

首先在驱动模块被加载时会自动调用pwm_init这个过程。中间包括将寄存器转化到虚拟地址,以及寄存器值的初始化,再试卸载时调用的pwm_exit,用来解除寄存器的映射。GPIO的配置更简单,这里省略。

static int pwmdrv_open(struct inode *inode, struct file *file)
{
    printk("moter pwm drive open...\n");

    //PWM_ch1
    pwm_ch1.no        =   0;    //pwm0   
    pwm_ch1.clksrc    =   PWM_CLK_40MHZ;   
    pwm_ch1.clkdiv    =   PWM_CLK_DIV2;  
    pwm_ch1.idelval   =   0;    
    pwm_ch1.guardval  =   0;  
    pwm_ch1.guarddur  =   0;   
    pwm_ch1.wavenum   =   0;   //无限循环
    pwm_ch1.datawidth =   1000;  
    pwm_ch1.threshold =   0;   

    //PWM_ch2
    pwm_ch1.no        =   1;   //pwm1 
    pwm_ch2.clksrc    =   PWM_CLK_40MHZ;   
    pwm_ch2.clkdiv    =   PWM_CLK_DIV2;  
    pwm_ch2.idelval   =   0;    
    pwm_ch2.guardval  =   0;  
    pwm_ch2.guarddur  =   0;   
    pwm_ch2.wavenum   =   0;  //无限循环
    pwm_ch2.datawidth =   1000;  
    pwm_ch2.threshold =   0;   

    pwm_ctl(&pwm_ch1, PWM_CONFIGURE);
    pwm_ctl(&pwm_ch2, PWM_CONFIGURE);   
    pwm_ctl(&pwm_ch1, PWM_ENABLE);
    pwm_ctl(&pwm_ch2, PWM_ENABLE);

    //gpioctl
    *GPIO_DATA |= (1<1<return 0;
}
//close
static int pwmdrv_close(struct inode *inode , struct file *file)
{
    printk("moter pwm drive close...\n");

    pwm_ctl(&pwm_ch1, PWM_DISABLE);
    pwm_ctl(&pwm_ch2, PWM_DISABLE);

    //gpioctl
    *GPIO_DATA |= (1<1<return 0;
}

打开操作里将配置CH0,CH1两个结构体参数进行了赋值,40MHz的时钟源,2分频,设置为20kHz的PWM波,再对他进行配置和使能。

//write 写入速度和方向
static ssize_t pwmdrv_write( struct file *file , const char __user *buffer,
               size_t len , loff_t *offset )
{
        int ret_v = 0;
    pwm_cfg* oper_ch;
    printk("set car's dirction and change the speed\n");
    if (len != 4)
    {printk("input arg size error!\n");return 1;}
    /* left moter */
    if (buffer[0] == 1)    //forward
    {
        oper_ch = &pwm_ch1;
        *GPIO_DATA |= (1<"pwm_ch0 forward\n");
    }
    else if (buffer[0] == 2)  //back
    {   
        oper_ch = &pwm_ch1;
        *GPIO_DATA &= ~(1<"pwm_ch0 back\n");
    }
    else if (buffer[0] != 0)
    {  
        printk("left moter has bad arg!!\n");
        return -1;
    }
    if (buffer[1] <= 100 && buffer[1] >= 0)
    {
        printk("duty=%d\n", buffer[1]);
        oper_ch->threshold = ((long int)buffer[1]*(oper_ch->datawidth)/100);
        pwm_ctl(oper_ch, PWM_DUTYSET);
    }
    else
    {
        printk("left moter pwm duty has bad arg!!\n");
        return -1;
    }

    /*right moter */
    if (buffer[2] == 1)    //forward
    {
        oper_ch = &pwm_ch2;
        *GPIO_DATA |= (1<"pwm_ch1 forward\n");
    }
    else if (buffer[2] == 2)  //back
    {   
        oper_ch = &pwm_ch2;
        *GPIO_DATA &= ~(1<"pwm_ch1 back\n");
    }
    else if (buffer[2] != 0)
    {  
        printk("left moter has bad arg!!\n");
        return -1;
    }

    if (buffer[3] <= 100 && buffer[3] >= 0)
    {
        printk("duty=%d\n", buffer[3]);
        oper_ch->threshold = ((long int)buffer[3]*(oper_ch->datawidth)/100);
        pwm_ctl(oper_ch, PWM_DUTYSET);
    }
    else
    {
        printk("right moter pwm duty has bad arg!!\n");
        return -1;
    }
    return ret_v;
}

5.makefile讲解

这里有2个makefile。后面的应用软件或是测试驱动程序的makefile也是类似。

1>.源码同级的makefile

一个是和源码同级目录的makefile。这个Makefile都是有模板的,他上面的信息很多是用在make menuconfig配置那边的。

PKG_NAME:=moter_pwm
PKG_RELEASE:=1

这个是模块名称以及版本号。

define KernelPackage/moter_pwm
  SUBMENU:=Other modules
  TITLE:=moter_pwm
  FILES:=$(PKG_BUILD_DIR)/moter_pwm.ko
  KCONFIG:=
endef
define KernelPackage/moter_pwm/description
  This is a 4 channels drivers
endef

这里是在配置时候上面显示的模块名字,所在的位置,生成的文件等信息,还有描述文字。

MAKE_OPTS:= \
    ARCH="$(LINUX_KARCH)" \
    CROSS_COMPILE="$(TARGET_CROSS)" \
    SUBDIRS="$(PKG_BUILD_DIR)"

define Build/Prepare
    mkdir -p $(PKG_BUILD_DIR)
    $(CP) ./src/* $(PKG_BUILD_DIR)/
endef

define Build/Compile
    $(MAKE) -C "$(LINUX_DIR)" \
    $(MAKE_OPTS) modules
endef

$(eval $(call KernelPackage,moter_pwm))

这块都是编译相关的东西。

2>.模块内的makefile

模块内的makefile比较简单,就是把驱动加到编译目标中去。

obj-m += moter_pwm.o

三.编译

把写好的源文件需要进行编译。

1>.把源码放入指定目录:需要放到openwrt/package/kernel/下。这时在make menuconfig中就可以看到放进去的驱动了。

2>.具体在kernel modules->other modules->kmod-moter_pwm 选中成 * (后面可以编译进内核中,该部分会直接配置在生成的固件中)

MT7688双摄像头双电机驱动小车(3)驱动实现_第11张图片

3>.然后输入命令make package/kernel/mydrv/compile V=99即可显示编译信息。有错误编译的需要改。

4>.最后得到模块在目录openwrt/bin/ramips/packages/base/后缀为.ipk的安装包

四.安装及加载模块

1.安装

1>.把安装包传送到硬件上/package目录下。

2>.在使用opkg install /package/xxxxx.ipk –force-depends命令进行安装

(XXX为安装包的名字,名字太长了,省略。。)

3>.成功安装完成之后可以在/lib/modules/3.14.45/目录下看到moter_pwm.ko

注:3.14.45是你具体的编译器版本号

2.加载模块

加载命令为:insmod xxx.ko

这里我们需要跳转到moter_pwm.ko所在的目录,再执行这条命令,记得把xxx.ko替换成moter_pwm.ko。
另外补充一句:卸载命令是:rmmod xxx.ko

五.开机加载

为方便后面开发方便,以及后面需要自启动,需要把模块设为开机加载。操作步骤如下:

1>.首先进入到特定的目录: cd /etc/modules.d

2>.然后创建一个文件并且写入一些信息: vi 61-moter_pwm

注:61表示不使用外接usb类的自启动程序,分隔符‘-’后面接驱动模块名称

你可能感兴趣的:(MT7688双摄像头双电机驱动小车(3)驱动实现)