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

原创 2016年08月31日 09:43:33

本文为在用龙芯1c做3D打印机过程中的笔记。龙芯1c做的3d打印机简称“龙印”,交流论坛地址是“http://www.openloongson.org/”

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.


版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

【C++】vs2010调试方法

1 导言 在软件开发周期中,测试和修正缺陷(defect,defect与bug的区别:Bug是缺陷的一种表现形式,而一个缺陷是可以引起多种Bug的)的时间远多于写代码的时间。通常,debug是指发现...

VS2010断点调试Release代码设置(C/C++/C#)

有时候,经常会遇到这样的问题:Debug模式,代码是没有问题的,但是,Release版本下代码却出现错误,如何调试Release代码?1,C#工程打开工程属性-Debug-勾选"Enable unma...

【龙印】用龙芯1c的硬件pwm产生单个脉冲来驱动步进电机

以步进电机驱动芯片A4988为例,给A4988一个脉冲,A4988就会驱动步进电机“走”一步(假设细分为1),在1秒内脉冲个数就决定了步进电机的速度。在marlin源码中,是通过在定时器中断里面将IO...
  • caogos
  • caogos
  • 2016年12月14日 16:08
  • 774

【龙芯1c库】封装硬件pwm接口和使用示例

本文通过“龙芯1c库”中提供的PWM接口,实现了在4路pwm的输出连续的pwm脉冲和单个脉冲。还测试了最大的pwm周期,得到的一些经验、体会都一一记录了下来,供大家参考; 在讲解了pwm接口之后,才开...
  • caogos
  • caogos
  • 2017年05月12日 18:14
  • 285

【龙芯1c库】封装硬件定时器接口和使用示例

本文首先给展示了为硬件定时器封装好的几个函数接口,然后用两个测试用例来测试,以此说明封装的接口功能正常。然后才是这几个接口封装的细节。 其中一个用例用来测试定时器的定时功能,另一个用来测试定时器的计时...
  • caogos
  • caogos
  • 2017年06月22日 15:09
  • 431

用龙芯1c库在RT-Thread下实现硬件定时器中断

用龙芯1c库中的硬件定时器接口和RT-Thread中的中断接口一起实现硬件定时器中断...
  • caogos
  • caogos
  • 2017年07月19日 12:05
  • 307

【龙印】龙芯1c的gpio作为输入时的linux驱动

本文为在用龙芯1c做3D打印机过程中的笔记。龙芯1c做的3d打印机简称“龙印”,交流论坛地址是“http://www.openloongson.org/” 和GPIO作为输出一样,作为输入也经常用到。...
  • caogos
  • caogos
  • 2016年09月01日 16:26
  • 525

【龙印】在龙芯1c上用TM7705+NTC热敏电阻实现温度测量

在龙芯1c上用TM7705+NTC热敏电阻实现温度测量
  • caogos
  • caogos
  • 2016年11月11日 10:41
  • 924

【龙印】用龙芯1c实现3D打印机的总体思路

简述了用龙芯1c实现热熔型3D打印机的大致方案,重点放在怎样用1c上现有硬件资源实现FDM 3d打印机...
  • caogos
  • caogos
  • 2016年12月16日 15:55
  • 1068

【龙印】龙芯1c上双路16位AD芯片TM7705的linux驱动

在龙芯1c上实现TM7705的linux驱动
  • caogos
  • caogos
  • 2016年11月04日 14:07
  • 1297
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:【龙印】把龙芯1c的pwm用作定时器并产生中断
举报原因:
原因补充:

(最多只允许输入30个字)