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类的自启动程序,分隔符‘-’后面接驱动模块名称
- MT7688双摄像头双电机驱动小车(3)驱动实现
- MT7688双摄像头双电机驱动小车(6)应用软件
- MT7688双摄像头双电机驱动小车(4)驱动测试
- MT7688双摄像头双电机驱动小车(1)环境搭建
- MT7688双摄像头双电机驱动小车(2)整体方案
- MT7688双摄像头+驱动双电机(5)上位机测试软件
- 步进电机作小车驱动
- 电动小车的电机驱动及控制
- 小车电机驱动及相关电路设计
- 驱动电机
- MT7688 wifi 驱动添加方法
- USB摄像头驱动3
- 摄像头驱动实现源码分析
- 摄像头驱动实现源码分析
- 摄像头驱动实现源码分析
- C# 无驱动摄像头实现
- Ubuntu系统驱动双USB摄像头
- 摄像头驱动
- 关于json_encode一些坑
- CentOS6.5增加挂载点容量
- mybatis if条件判断字符串类型是否一致
- AndroidStudio导入项目的简单设置
- JS显示后台日期出现时差问题
- MT7688双摄像头双电机驱动小车(3)驱动实现
- seq2seq.py
- c#操作MySql数据库
- Hexo+Github/Coding 搭建自己的博客
- HDU1012
- tomcat能正常start启动,debug模式时,加载到某个类的时候不动了,一直不动,最后超时了,设置了timeout也没用
- java装饰着模式
- Nginx缓存的两种方式
- debug时 出现source not found