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

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

本文为在用龙芯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.


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

通用定时器(中断功能和PWM输出)

目录: 1:概述 2:常用中断功能 3:PWM输出  1:概述 在开发中,定时器的应用很广泛,简单总结为三个方面: 1.1:中断功能的应用,常用的是利用定时器中断,实现定时、记时、延时、超时判断,...
  • Golf_research
  • Golf_research
  • 2016年11月21日 00:40
  • 1912

【龙芯1c库】龙芯1c的中断分析

结合《see mips run》和linux源码分析龙芯1c的中断处理流程,尤其是设置异常总入口后需要回写dcache和作废icache与其它cpu不太一样。...
  • caogos
  • caogos
  • 2017年04月10日 15:27
  • 563

定时器产生PWM

1 用两个定时器/计数器产生矩形波这种方法的基本原理就是用T0作为矩形波的周期的定时器,每一周期产生一次中断,用T1作为矩形波的高电平的计时器,每到T0的定时中断,输出矩形波的引脚输出高电平,而到T1...
  • star871016
  • star871016
  • 2010年05月08日 16:49
  • 1706

stm32 高级定时器产生PWM

用stm32的高级定时器TIM1和TIM8产生PWM,需要注意: 1.都有TIM1,但只有flash容量大于256K的大容量单片机才有TIM8 2.高级定时器相对于通用定时器,多了TIM_Ctrl...
  • qlexcel
  • qlexcel
  • 2017年03月15日 19:44
  • 2285

CC2530定时器T1产生PWM

最近搞PWM波输出,参考了一下网上的代码自己总结出CC2530 PWM的配置过程:(此处以T1为例) 配置PWM模式需参考CC2530用户手册中"表7-1 外部设备I/O引脚映射"来进行配置     ...
  • lyp461340781
  • lyp461340781
  • 2015年06月27日 16:58
  • 3188

【龙芯1c库】封装模拟I2C接口和使用示例

本文使用普通GPIO模拟I2C,实现与温湿度传感器AM2320正常通信。 先展示如何使用模拟I2C接口,然后再来看看怎么封装这些接口的。...
  • caogos
  • caogos
  • 2017年06月12日 11:51
  • 458

PWM定时器timer0学习笔记

PWM定时器timer0学习笔记: 一、定时器timer0代码中用到的寄存器有哪些?这些寄存器功能是什么?初始化中涉及到这些寄存器的位的含义是什么?: TCFG0 功能是:定时器的配置寄存器,可...
  • itsir_cheng
  • itsir_cheng
  • 2014年05月30日 13:45
  • 1410

【龙芯1c库】龙芯1c上通过pmon引导实现裸机程序点亮led

在龙芯1c上跑裸机程序来点亮led,没有跑RT-Thread和linux等操作系统。
  • caogos
  • caogos
  • 2016年10月31日 12:33
  • 902

MSP430 定时器输出PWM波形

硬件介绍: MSP430系列单片机的TimerA结构复杂,功能强大,适合应用于工业控制,如数字化电机控制,电表和手持式仪表的理想配置。它给开发人员提供了较多灵活的选择余地。当PWM 不需要修改占空比...
  • u011392772
  • u011392772
  • 2016年07月20日 19:25
  • 2051

STM8S---TIM2产生PWM与TIM1定时器周期中断的时钟问题

1 问题  在下面的测试程序中,如果将Init_CLK()函数中的 CLK_CKDIVR |= 0x08;去掉’|‘,则TIM1的功能实现跟预设定相同(10ms中断一次),但是TIM2的PWM频率就变...
  • FreeApe
  • FreeApe
  • 2015年07月08日 12:48
  • 4150
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:【龙印】把龙芯1c的pwm用作定时器并产生中断
举报原因:
原因补充:

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