您的位置:首页 > 其它

【龙印】把龙芯1c的pwm用作定时器并产生中断

2016-08-31 09:43 453 查看
本文为在用龙芯1c做3D打印机过程中的笔记。龙芯1c做的3d打印机简称“龙印”
3d打印机固件marlin巧妙运用定时器让整个固件不必依赖实时操作系统,即把对实时性要求较高的部分巧妙的用定时器中断来实现了。
marlin固件的原理分析请参考《3D打印机:FPGA+Nios_ii移植Marlin固件二:Marlin固件的详细分析》 http://blog.sina.com.cn/s/blog_679933490102vv8z.html 由此可见定时器中断在marlin中的重要性。这里把龙芯1c的pwm用作定时器,而不输出pwm脉冲,并定时产生中断。不多说了上源码
源码“ls1c_pwm_timer.c”
/*
* drivers\misc\ls1c_pwm_timer.c
* 把龙芯1c的pwm用作定时器,定时产生中断
*/

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/err.h>
#include <linux/miscdevice.h>
#include <linux/gpio.h>
#include <linux/ls1c_3dprinter_motor.h>
#include <linux/delay.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/time.h>
#include <linux/errno.h>
#include <linux/clk.h>
#include <linux/mutex.h>
#include <linux/kfifo.h>

// 定时器默认的定时时间(单位ns)
#define PWM_TIMER_DEF_TIME_NS (1*1000*1000*1000) // 1s

// 寄存器偏移
#define REG_PWM_CNTR 0x00
#define REG_PWM_HRC 0x04
#define REG_PWM_LRC 0x08
#define REG_PWM_CTRL 0x0c

// pwm控制寄存器的每个bit
#define LS1C_PWM_INT_LRC_EN (11) // 低脉冲计数器中断使能
#define LS1C_PWM_INT_HRC_EN (10) // 高脉冲计数器中断使能
#define LS1C_PWM_CNTR_RST (7) // CNTR计数器清零
#define LS1C_PWM_INT_SR (6) // 中断状态位
#define LS1C_PWM_INTEN (5) // 中断使能位
#define LS1C_PWM_OE (3) // 脉冲输出使能控制位
#define LS1C_PWM_CNT_EN (0) // CNTR使能位

static void __iomem *pwm_timer_reg_base = NULL; // 映射后的寄存器基地址
static struct workqueue_struct *pwm_timer_queue; // 定时器中断的下半部--工作队列
static struct work_struct pwm_timer_work;
static unsigned long long pwm_timer_clk_rate; // pwm的计数器的时钟频率
static DEFINE_MUTEX(pwm_timer_lock);

// 设置定时器时间
// @period_ns 定时器时间,单位(ns)
static void pwm_timer_set_time(unsigned long period_ns)
{
unsigned long long tmp = 0;

tmp = pwm_timer_clk_rate * period_ns;
do_div(tmp, 1000000000);
writel(tmp, pwm_timer_reg_base+REG_PWM_LRC);
printk(KERN_DEBUG "[%s] pwm_timer_clk_rate=%llu, REG_PWM_LRC's value=%llu\n",
__FUNCTION__,
pwm_timer_clk_rate,
tmp);

return ;
}

static void pwm_timer_enable(void)
{
unsigned int tmp = 0;

tmp = readl(pwm_timer_reg_base+REG_PWM_CTRL);
tmp |= (1<<LS1C_PWM_CNT_EN);
writel(tmp, pwm_timer_reg_base+REG_PWM_CTRL);

return ;
}

static void pwm_timer_disable(void)
{
unsigned int tmp = 0;

tmp = readl(pwm_timer_reg_base+REG_PWM_CTRL);
tmp &= ~(1<<LS1C_PWM_CNT_EN);
writel(tmp, pwm_timer_reg_base+REG_PWM_CTRL);

return ;
}

// 重新启动定时器
// 专门为定时器中断封装的函数,
// pwm定时器中断后,如果不调此函数重启定时器,则整个系统卡死
static void pwm_timer_restart(void)
{
writel(0x829, pwm_timer_reg_base+REG_PWM_CTRL);

return ;
}

// 定时器中断处理函数(上半部)
static irqreturn_t pwm_timer_irq_handler(int irq, void *devid)
{
// 重新启动定时器
// 如果不重启,则整个系统卡死
pwm_timer_restart();

queue_work(pwm_timer_queue, &pwm_timer_work);

return IRQ_HANDLED;
}

// 定时器中断的下半部
static void pwm_timer_queue_handler(struct work_struct *work)
{
static int count = 0;

// 这里仅仅为了演示,所以打印一条消息到串口
// 实际实用中,在中断处理函数中使用打印函数应该非常谨慎,
// 哪怕是在中断程序的下半部,
// 比如,如果定时时间比打印该消息的时间还短,就有问题,
// 所以这里为了演示,定时时间设为1s
// 中断处理程序的上半部严禁使用打印函数,
printk(KERN_DEBUG "[%s] one irq. count=%d\n", __FUNCTION__, count);
count++;

return ;
}

static int pwm_timer_open(struct inode *inode, struct file *filp)
{
return 0;
}

static int pwm_timer_close(struct inode *inode, struct file *filp)
{
// 禁用定时器
pwm_timer_disable();

return 0;
}

static ssize_t pwm_timer_write(struct file *filp, const char __user *buf, size_t count, loff_t *offp)
{
int ret = 0;
unsigned long period_ns = 0; // 定时器定时时间

if (mutex_lock_interruptible(&pwm_timer_lock))
{
return -ERESTARTSYS;
}

ret = copy_from_user(&period_ns, buf, sizeof(period_ns));

mutex_unlock(&pwm_timer_lock);

if (ret)
{
printk(KERN_ERR "[%s] write count err. count=%d\n", __FUNCTION__, count);
return -1;
}
printk(KERN_DEBUG "[%s] period_ns=%lu\n", __FUNCTION__, period_ns);

// 设置定时器时间,并启动
pwm_timer_set_time(period_ns);
writel(0, pwm_timer_reg_base+REG_PWM_CNTR); // 计数器清零
pwm_timer_enable();

return sizeof(period_ns);
}

static struct file_operations ls1c_pwm_timer_ops = {
.owner = THIS_MODULE,
.open = pwm_timer_open,
.release = pwm_timer_close,
.write = pwm_timer_write,
};

static struct miscdevice ls1c_pwm_timer_miscdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "ls1c_pwm_timer",
.fops = &ls1c_pwm_timer_ops,
};

static int pwm_timer_probe(struct platform_device *pdev)
{
int ret = 0;
int irq = 0;
struct resource *res = NULL;
struct clk *pwm_clk = NULL;

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (NULL == res)
{
printk(KERN_ERR "[%s] no IO memory resource defined.\n", __FUNCTION__);
return -ENODEV;
}

res = request_mem_region(res->start, resource_size(res), pdev->name);
if (NULL == res)
{
printk(KERN_ERR "[%s] failed to request memory resource.\n", __FUNCTION__);
return -EBUSY;
}

pwm_timer_reg_base = ioremap(res->start, resource_size(res));
if (NULL == pwm_timer_reg_base)
{
printk(KERN_ERR "[%s] ioremap pwm register fail.\n", __FUNCTION__);
ret = -ENODEV;
goto fail_free_res;
}

irq = platform_get_irq(pdev, 0);
if (0 > irq)
{
printk(KERN_ERR "[%s] no IRQ resource defined.\n", __FUNCTION__);
ret = -ENXIO;
goto fail_free_io;
}

ret = request_irq(irq, pwm_timer_irq_handler, IRQF_DISABLED, pdev->name, NULL);
if (0 > ret)
{
printk(KERN_ERR "[%s] failed to request IRQ.\n", __FUNCTION__);
goto fail_free_io;
}

pwm_timer_queue = create_workqueue("pwm_timer_queue");
if (!pwm_timer_queue)
{
printk(KERN_ERR "[%s] failed to create workqueue.\n", __FUNCTION__);
ret = -EBUSY;
goto fail_free_irq;
}
INIT_WORK(&pwm_timer_work, pwm_timer_queue_handler);

// 获取pwm计数器的时钟
pwm_clk = clk_get(NULL, "apb");
if (IS_ERR(pwm_clk))
{
ret = PTR_ERR(pwm_clk);
pwm_clk = NULL;
printk(KERN_ERR "[%s] get pwm clk fail.\n", __FUNCTION__);
goto fail_destroy_workqueue;
}
pwm_timer_clk_rate = (unsigned long long)clk_get_rate(pwm_clk);
clk_put(pwm_clk);

// 设置定时器时间
pwm_timer_set_time(PWM_TIMER_DEF_TIME_NS);

// 设置控制寄存器
// 低脉冲计数器中断使能
// 高脉冲计数器中断禁止
// CNTR计数器正常工作
// 中断使能
// 屏蔽脉冲输出
// CNTR停止计数
writel(0x828, pwm_timer_reg_base+REG_PWM_CTRL);

return 0;

fail_destroy_workqueue:
destroy_workqueue(pwm_timer_queue);
fail_free_irq:
free_irq(irq, NULL);
fail_free_io:
iounmap(pwm_timer_reg_base);
fail_free_res:
release_mem_region(res->start, resource_size(res));

return ret;
}

static int pwm_timer_remove(struct platform_device *pdev)
{
int irq = 0;
struct resource *res = NULL;

destroy_workqueue(pwm_timer_queue);

irq = platform_get_irq(pdev, 0);
if (0 <= irq)
{
free_irq(irq, NULL);
}

iounmap(pwm_timer_reg_base);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (NULL != res)
{
release_mem_region(res->start, resource_size(res));
}

return 0;
}

static struct platform_driver ls1c_pwm_timer_driver = {
.driver = {
.name = "ls1c_pwm_timer",
.owner = THIS_MODULE,
},
.probe = pwm_timer_probe,
.remove = pwm_timer_remove,
};

static int __init pwm_timer_init(void)
{
if (misc_register(&ls1c_pwm_timer_miscdev))
{
printk(KERN_ERR "could not register pwm timer driver!\n");
return -EBUSY;
}

return platform_driver_register(&ls1c_pwm_timer_driver);
}

static void __exit pwm_timer_exit(void)
{
misc_deregister(&ls1c_pwm_timer_miscdev);
platform_driver_unregister(&ls1c_pwm_timer_driver);
}

module_init(pwm_timer_init);
module_exit(pwm_timer_exit);


把源文件“ls1c_pwm_timer.c”放到目录“drivers\misc”下
在“arch\mips\loongson\ls1x\ls1c\platform.c”中增加

#ifdef CONFIG_LS1C_PWM_TIMER

static struct resource ls1c_pwm_timer_resources[] = {
    {
        .start  = LS1X_PWM3_BASE,
        .end    = LS1X_PWM3_BASE + 0x10 -1,
        .flags  = IORESOURCE_MEM,
    },{
        .start  = LS1X_PWM3_IRQ,
        .end    = LS1X_PWM3_IRQ,
        .flags  = IORESOURCE_IRQ,
    }
};

static struct platform_device ls1c_pwm_timer = {
    .name   = "ls1c_pwm_timer",
    .resource       = ls1c_pwm_timer_resources,
    .num_resources   = ARRAY_SIZE(ls1c_pwm_timer_resources),
};

#endif //End of CONFIG_LS1C_PWM_TIMER

在“static struct platform_device *ls1b_platform_devices[] __initdata”中增加
#ifdef CONFIG_LS1C_PWM_TIMER
    &ls1c_pwm_timer,
#endif

在“drivers\misc\Kconfig”中增加
config LS1C_PWM_TIMER
    tristate "ls1c pwm timer"
    depends on LS1C_MACH
    help
     Say Y here if you want to build a pwm timer driver for ls1c
    
在“drivers\misc\Makefile”中增加
obj-$(CONFIG_LS1C_PWM_TIMER)    += ls1c_pwm_timer.o

配置
make menuconfig
Device Drivers  --->
  [*] Misc devices  --->
    <*> ls1c pwm timer

测试用的应用程序“main.c”
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(void)
{
int fd = 0;
unsigned long period_ns = 100000000; // 定时1s
int ret = 0;

fd = open("/dev/ls1c_pwm_timer", O_RDWR);
if (-1 == fd)
{
printf("[%s] open device file /dev/ls1c_pwm_timer fail.\n", __FUNCTION__);
return -1;
}

ret = write(fd, &period_ns, sizeof(period_ns));
if (sizeof(period_ns) != ret)
{
close(fd);
printf("[%s] write fail. ret=%d\n", __FUNCTION__, ret);
return -1;
}

while (1)
{
sleep(1);
}
}


测试效果



测试时需要用命令“echo 8 > /proc/sys/kernel/printk”把打印级别调到调试模式,才能看到打印。
 本示例是将定时器时间设置为1s,并通过在中断下半部中打印一条信息来演示的。再次提醒中断程序的上半部中不能使用打印函数,因为打印函数太耗时了。下半部中使用打印函数也要非常谨慎。
在marlin的实现中,定时器中断中需要产生一个高电平脉冲给步进电机驱动芯片,步进电机驱动芯片收到一个脉冲就让步进电机就走一步,这样就实现了控制步进电机的目的。以A4988步进电机驱动芯片为例,A4988需要一个脉冲的高电平时间和低电平时间至少为1us。1us对于cpu频率为260Mhz的龙芯1c来说是很长的,特别是在中断中,所以这里使用了中断下半部来解决这个问题。在下半部中可以用udelay(2)来实现。注意udelay()是硬延时。

如果想在io口输出pwm波形,而不想用定时器,那就直接使用pwm功能,把中断禁止,使能脉冲输出。当然linux源码中已经封装好了,请查看文件“arch\mips\loongson\ls1x\pwm.c”

在使用龙芯1c的pwm时遇到如下麻烦,都找到了解决办法,讲经验记录与此

1,pwm的CTRL寄存器中INT_SR
1c的用户手册中说:在INT_SR中写入1,可以清中断。
在linux驱动的probe函数中,向INT_SR中写入了1,结果linux启动到写入1哪里后卡死。
我的理解是,系统刚刚启动,没有中断,而我写1清中断了,所以卡死。
行嘛,这个还可以理解,请继续往下看。

2,pwm的CTRL寄存器中CNTR_RST
1c的用户手册中说:置1时,CNTR计数器清零;置0时,CNTR计数器正常工作。
同样在linux驱动初始化时,我把CNTR_RST置了1,,结果始终没有产生中断,定位发现——CNTR计数器一直为0,根本没有正常计数。
大哥,你没说,清零后计数器不正常工作(“不计数”)好不好,不带这样玩的。

3,pwm作为定时器产生中断后成功进入中断,中断处理程序中点亮led,然后返回,结果中断后卡死
没找到原因,刚开始以为需要手动清中断标志,向CTRL寄存器中的INT_SR写1,还是不能解决。
最后参考了linux源码中的“ls1x_pwm_audio.c”,发现中断处理程序中重新设置了一遍CTRL寄存器。就OK了。比如我重新写入0x829.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息