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

来源:互联网 发布:行尸走肉网络剧撕裂 编辑:程序博客网 时间:2024/06/05 08:04

主目录:

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).写入控制接口:
这里写图片描述

(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的时钟
这里写图片描述

下面是GPIO1模式配置寄存器,地址为10000060,现需要配置IO口为PWM输出模式(注意这里URAT1和PWM是引脚复用的)。我们需要输出2路PWM(CH0,CH1),所以需要置位第24-25位。
这里写图片描述
这里写图片描述

控制PWM具体输出的主要在PWM寄存器中,这里寄存器比较多,主要有这几个,以PWMX的有0-3个通道的寄存器:
PWM_ENABLE寄存器是PWM使能寄存器,可以控制PWM的开关。
这里写图片描述

PWMX_CON控制PWM输出的一些配置,比如时钟等。
这里写图片描述
不清楚老PWM模式和新PWM模式什么区别,这里就用老PWM模式;GUARD_VALUE和IDLE_VALUE是用来不同时段的电平,时钟源和分频是用来控制PWM的周期的。

PWMX_WAVE_NUM寄存器位脉冲重复的次数,这里设为0,表示循环不停输出。
这里写图片描述

老PWM模式下的周期次数,及持续时间,两个数值的比值就是占空比。
这里写图片描述
这里写图片描述


对于普通IO口的配置,注意,因为这里有个控制线占用的是I2S_WS的IO引脚,所以要在GPIO1_MODE寄存器中关闭I2S功能。
这里写图片描述
该寄存器是控制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<<MOTER1_DIR_IO_BIT);    *GPIO_DATA |= (1<<MOTER2_DIR_IO_BIT);    return 0;}//closestatic 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<<MOTER1_DIR_IO_BIT);    *GPIO_DATA |= (1<<MOTER2_DIR_IO_BIT);    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<<MOTER1_DIR_IO_BIT);        printk("pwm_ch0 forward\n");    }    else if (buffer[0] == 2)  //back    {           oper_ch = &pwm_ch1;        *GPIO_DATA &= ~(1<<MOTER1_DIR_IO_BIT);        printk("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<<MOTER2_DIR_IO_BIT);        printk("pwm_ch1 forward\n");    }    else if (buffer[2] == 2)  //back    {           oper_ch = &pwm_ch2;        *GPIO_DATA &= ~(1<<MOTER2_DIR_IO_BIT);        printk("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_pwmPKG_RELEASE:=1

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

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

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

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)/endefdefine Build/Compile    $(MAKE) -C "$(LINUX_DIR)" \    $(MAKE_OPTS) modulesendef$(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 选中成 * (后面可以编译进内核中,该部分会直接配置在生成的固件中)

这里写图片描述

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类的自启动程序,分隔符‘-’后面接驱动模块名称