http://blog.csdn.net/DFSAE/article/details/78715815
由于驱动程序编写这部分涉及的范围比较大有空这部分的资料补上
参考:(补充。。)
简单的说,驱动可以用对文件操作的方式进行访问,比如open,write,read等。因此只要实现对应的接口即可,剩下的按照模板套用。
电机驱动模块负责控制2个电机的转速和方向。
硬件上设计为每一个电机由一路PWM和一路方向控制线控制:PWM控制速度,另一个IO控制电机转动方向。
(2).读取接口:
在传入数组中获得2个PWM的占空比
看MT7688用户手册:MT7688拥有4路PWM输出,我们可以用到其中的两路CH0,CH1。分别对应引脚18,19脚。
决定选用硬件连接如下:
硬件接线为:PWM(ch0,ch1)GPIO#18,19,IO控制采用GPIO#2和GPIO#11脚。
首先是时钟配置寄存器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口的值的。
首先是封装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;
}
这里有2个makefile。后面的应用软件或是测试驱动程序的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))
这块都是编译相关的东西。
模块内的makefile比较简单,就是把驱动加到编译目标中去。
obj-m += moter_pwm.o
把写好的源文件需要进行编译。
(XXX为安装包的名字,名字太长了,省略。。)
注:3.14.45是你具体的编译器版本号
这里我们需要跳转到moter_pwm.ko所在的目录,再执行这条命令,记得把xxx.ko替换成moter_pwm.ko。
另外补充一句:卸载命令是:rmmod xxx.ko
为方便后面开发方便,以及后面需要自启动,需要把模块设为开机加载。操作步骤如下:
注:61表示不使用外接usb类的自启动程序,分隔符‘-’后面接驱动模块名称