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

来源:互联网 发布:数据维护工具v3.8 编辑:程序博客网 时间:2024/06/05 19:58

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

以步进电机驱动芯片A4988为例,给A4988一个脉冲,A4988就会驱动步进电机“走”一步(假设细分为1),在1秒内脉冲个数就决定了步进电机的速度。在marlin源码中,是通过在定时器中断里面将IO口拉高然后延时再拉低来产生一个脉冲的。很显然,通过这种延时的方式来产生脉冲会消耗大量的cpu资源,恰好龙芯1c的硬件pwm可以产生单个脉冲,这样就不必在定时器中断中延时了,大大降低了cpu占有率,当步进电机速度越快时,效果越明显。

硬件说明

龙芯1c共有4个pwm,其中pwm0和pwm1可以直接使用,pwm2和pwm3需要复用。pwm2和pwm3可以在多个引脚上复用,比如pwm2可以与CAMDATA2/GPIO52复用,也可以与CAMPCLKIN/GPIO46复用。由于智龙v2.1的板子上,CAMDATA2/GPIO52接有led,所以选择将pwm2与CAMPCLIN/GPIO46复用,pwm3类似,选择与CAMCLKOUT/GPIO47复用。

所以源码中有

// PWNn所在gpio#define LS1C_PWM0_GPIO06                        (6)#define LS1C_PWM1_GPIO92                        (92)#define LS1C_PWM2_GPIO46_CAMPCLKIN              (46)    // 第四复用#define LS1C_PWM3_GPIO47_CAMCLKOUT              (47)    // 第四复用


源码

应用程序

应用程序通过write()接口写入脉冲个数

test.c

#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>int main(void){    int fd = 0;    int ret = 0;    int pulse_num = 0;    fd = open("/dev/ls1c_pwm_pulse", O_RDWR);    if (-1 == fd)    {        printf("[%s] open device file.\n", __FUNCTION__);        return -1;    }    while (1)    {        pulse_num = 20;        ret = write(fd, &pulse_num, sizeof(pulse_num));        if (sizeof(pulse_num) != ret)        {            close(fd);            printf("[%s] write fail. ret=%d\n", __FUNCTION__, ret);            return -1;        }                sleep(1);    }}

Makefile

HEADER_FILE = $(wildcard *.h)SRC = $(wildcard *.c)OBJ = $(SRC:.c=.o)DEST = testCC = mipsel-linux-gccall:$(DEST)$(DEST):$(OBJ)$(CC) $^ -o $@cp $@ /nfsramdisk/LS1xrootfs-demo/test$(OBJ):$(SRC) $(HEADER_FILE)$(CC) -c $^clean:rm -f *.o $(DEST)



驱动

/* * drivers\misc\ls1c_pwm_pulse.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/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>enum {    LS1C_PWM_0 = 0,    LS1C_PWM_1 = 1,    LS1C_PWM_2 = 2,    LS1C_PWM_3 = 3,};// gpio配置寄存器#define LS1C_GPIO_CFG0                          (0xbfd010c0)        // 控制gpio[31:0]#define LS1C_GPIO_CFG1                          (0xbfd010c4)        // 控制gpio[63:32]#define LS1C_GPIO_CFG2                          (0xbfd010c8)        // 控制gpio[95:64]#define LS1C_GPIO_CFG3                          (0xbfd010cc)        // 控制gpio[127:96]// 复用寄存器#define LS1C_CBUS_FOURTH1                       (0xbfd011f4)        // 控制gpio[63:32]的第四复用// PWNn所在gpio#define LS1C_PWM0_GPIO06                        (6)#define LS1C_PWM1_GPIO92                        (92)#define LS1C_PWM2_GPIO46_CAMPCLKIN              (46)    // 第四复用#define LS1C_PWM3_GPIO47_CAMCLKOUT              (47)    // 第四复用// 寄存器偏移#define REG_PWM_CNTR0x00#define REG_PWM_HRC0x04#define REG_PWM_LRC0x08#define REG_PWM_CTRL0x0c// 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_SINGLE                         (4)     // 单脉冲控制位#define LS1C_PWM_OE                             (3)     // 脉冲输出使能控制位#define LS1C_PWM_CNT_EN                         (0)     // CNTR使能位// 脉冲宽度#define PWM_PULSE_HIGH_WIDTH_NS                 (2*1000)    // 高电平2us#define PWM_PULSE_LOW_WIDTH_NS                  (2*1000)    // 低电平2usstatic void __iomem *pwm_pulse_reg_base = NULL;         // 映射后的寄存器基地址static unsigned long long pwm_pulse_clk_rate;           // pwm计数器的时钟频率static DEFINE_MUTEX(pwm_pulse_lock);// 初始化PWMnstatic void pwm_pulse_PWMn_init(int PWMn){    unsigned long long tmp = 0;    unsigned long pulse_high_width_ns = PWM_PULSE_HIGH_WIDTH_NS;    unsigned long pulse_low_width_ns = PWM_PULSE_LOW_WIDTH_NS;    unsigned int cntr_reg_data = 0;     // 写入控制寄存器的数据    unsigned int data = 0;    void __iomem *reg_base = NULL;    void __iomem *addr = NULL;    // 配置gpio引脚为pwm,而非gpio    switch (PWMn)    {        case LS1C_PWM_0:            addr = (void *)LS1C_GPIO_CFG0;            data = readl(addr);            data &= ~(1<<LS1C_PWM0_GPIO06);            writel(data, addr);            break;        case LS1C_PWM_1:            addr = (void *)LS1C_GPIO_CFG2;            data = readl(addr);            data &= ~(1<<(LS1C_PWM1_GPIO92-64));            writel(data, addr);            break;        case LS1C_PWM_2:            addr = (void *)LS1C_GPIO_CFG1;            data = readl(addr);            data &= ~(1<<(LS1C_PWM2_GPIO46_CAMPCLKIN-32));            writel(data, addr);            break;        case LS1C_PWM_3:            addr = (void *)LS1C_GPIO_CFG1;            data = readl(addr);            data &= ~(1<<(LS1C_PWM3_GPIO47_CAMCLKOUT-32));            writel(data, addr);            break;    }    // 复用    switch (PWMn)    {        // pwm0和pwm1不需要复用        case LS1C_PWM_0:        case LS1C_PWM_1:            break;        // pwm2开启第四复用        case LS1C_PWM_2:            addr = (void *)LS1C_CBUS_FOURTH1;            data = readl(addr);            data |= (1<<(LS1C_PWM2_GPIO46_CAMPCLKIN-32));            writel(data, addr);            break;        case LS1C_PWM_3:            addr = (void *)LS1C_CBUS_FOURTH1;            data = readl(addr);            data |= (1<<(LS1C_PWM3_GPIO47_CAMCLKOUT-32));            writel(data, addr);            break;    }        reg_base = pwm_pulse_reg_base+(PWMn<<4);        // 写寄存器HRC    tmp = pwm_pulse_clk_rate * pulse_low_width_ns;    do_div(tmp, 1000000000);    writel(--tmp, reg_base+REG_PWM_HRC);        // 写寄存器LRC    tmp = pwm_pulse_clk_rate * (pulse_high_width_ns+pulse_low_width_ns);    do_div(tmp, 1000000000);    writel(--tmp, reg_base+REG_PWM_LRC);    // 写主计数器    writel(0, reg_base+REG_PWM_CNTR);    // 写控制寄存器    cntr_reg_data = (0 << LS1C_PWM_INT_LRC_EN)                    | (0 << LS1C_PWM_INT_HRC_EN)                    | (0 << LS1C_PWM_CNTR_RST)                    | (0 << LS1C_PWM_INT_SR)                    | (0 << LS1C_PWM_INTEN)                    | (1 << LS1C_PWM_SINGLE)                    | (0 << LS1C_PWM_OE)                    | (0 << LS1C_PWM_CNT_EN);    addr = reg_base+REG_PWM_CTRL;    writel(cntr_reg_data, addr);    return ;}// 在PWMn引脚上产生一个脉冲static void pwm_pulse_one_pulse(int PWMn){    unsigned int cntr_reg_data = 0;     // 写入控制寄存器的数据    void __iomem *reg_base = NULL;    reg_base = pwm_pulse_reg_base+(PWMn<<4);        // 写主计数器    writel(0, reg_base+REG_PWM_CNTR);        // 写控制寄存器    cntr_reg_data = (0 << LS1C_PWM_INT_LRC_EN)                    | (0 << LS1C_PWM_INT_HRC_EN)                    | (0 << LS1C_PWM_CNTR_RST)                    | (0 << LS1C_PWM_INT_SR)                    | (0 << LS1C_PWM_INTEN)                    | (1 << LS1C_PWM_SINGLE)                    | (0 << LS1C_PWM_OE)                    | (1 << LS1C_PWM_CNT_EN);    writel(cntr_reg_data, reg_base+REG_PWM_CTRL);    return ;}static int pwm_pulse_open(struct inode *inode, struct file *filp){    return 0;}static int pwm_pulse_close(struct inode *inode, struct file *filp){    return 0;}static ssize_t pwm_pulse_write(struct file *filp, const char __user *buf, size_t count, loff_t *offp){    int ret = 0;    unsigned int pulse_num = 0;         // 脉冲个数    unsigned tmp;        if (mutex_lock_interruptible(&pwm_pulse_lock))    {        return -ERESTARTSYS;    }    ret = copy_from_user(&pulse_num, buf, sizeof(pulse_num));    mutex_unlock(&pwm_pulse_lock);    if (ret)    {        printk(KERN_ERR "[%s] write err. pulse_num=%u\n", __FUNCTION__, pulse_num);        return -1;    }    // 产生指定个数的脉冲    for (tmp=0; tmp<pulse_num; tmp++)    {        pwm_pulse_one_pulse(LS1C_PWM_0);        pwm_pulse_one_pulse(LS1C_PWM_1);        pwm_pulse_one_pulse(LS1C_PWM_2);        pwm_pulse_one_pulse(LS1C_PWM_3);        udelay(10);    }    return sizeof(pulse_num);}static struct file_operations ls1c_pwm_pulse_ops = {    .owner      = THIS_MODULE,    .open       = pwm_pulse_open,    .release    = pwm_pulse_close,    .write      = pwm_pulse_write,};static struct miscdevice ls1c_pwm_pulse_miscdev = {    .minor  = MISC_DYNAMIC_MINOR,    .name   = "ls1c_pwm_pulse",    .fops   = &ls1c_pwm_pulse_ops,};static int pwm_pulse_probe(struct platform_device *pdev){    int ret = 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_pulse_reg_base = ioremap(res->start, resource_size(res));    if (NULL == pwm_pulse_reg_base)    {        printk(KERN_ERR "[%s] ioremap pwm register fail.\n", __FUNCTION__);        ret = -ENODEV;        goto fail_free_res;    }    // 获取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_free_io;    }    pwm_pulse_clk_rate = (unsigned long long)clk_get_rate(pwm_clk);    clk_put(pwm_clk);    // 初始化PWMn    pwm_pulse_PWMn_init(LS1C_PWM_0);    pwm_pulse_PWMn_init(LS1C_PWM_1);    pwm_pulse_PWMn_init(LS1C_PWM_2);    pwm_pulse_PWMn_init(LS1C_PWM_3);    return 0;fail_free_io:    iounmap(pwm_pulse_reg_base);fail_free_res:    release_mem_region(res->start, resource_size(res));    return ret;}static int pwm_pulse_remove(struct platform_device *pdev){    struct resource *res = NULL;        iounmap(pwm_pulse_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_pulse_driver = {    .driver = {        .name = "ls1c_pwm_pulse",        .owner = THIS_MODULE,    },    .probe = pwm_pulse_probe,    .remove = pwm_pulse_remove,};static int __init pwm_pulse_init(void){    if (misc_register(&ls1c_pwm_pulse_miscdev))    {        printk(KERN_ERR "could not register pwm pulse driver!\n");        return -EBUSY;    }    return platform_driver_register(&ls1c_pwm_pulse_driver);}static void __exit pwm_pulse_exit(void){    misc_deregister(&ls1c_pwm_pulse_miscdev);    platform_driver_unregister(&ls1c_pwm_pulse_driver);}module_init(pwm_pulse_init);module_exit(pwm_pulse_exit);MODULE_AUTHOR("勤为本");MODULE_DESCRIPTION("使用ls1c的硬件pwm产生单个脉冲");MODULE_LICENSE("GPL");

在“linux源码根目录\arch\mips\loongson\ls1x\ls1c\platform.c”中加入

#ifdef CONFIG_LS1C_PWM_PULSEstatic struct resource ls1c_pwm_pulse_resources[] = {    {        .start  = LS1X_PWM0_BASE,        .end    = LS1X_PWM0_BASE + 0x10*4 -1,       // pwm0-3        .flags  = IORESOURCE_MEM,    }};static struct platform_device ls1c_pwm_pulse = {    .name           = "ls1c_pwm_pulse",    .resource       = ls1c_pwm_pulse_resources,    .num_resources  = ARRAY_SIZE(ls1c_pwm_pulse_resources),};#endif // End of CONFIG_LS1C_PWM_PULSE

在变量“static struct platform_device *ls1b_platform_devices[] __initdata”中加入

#ifdef CONFIG_LS1C_PWM_PULSE    &ls1c_pwm_pulse,#endif
在“linux源码根目录\drivers\misc\Kconfig”中加入
config LS1C_PWM_PULSE    tristate "ls1c pwm pulse"    depends on LS1C_MACH    help     Say Y here if you want to build a pwm pulse driver for ls1c

在“linux源码根目录\drivers\misc\Makefile”中加入

obj-$(CONFIG_LS1C_PWM_PULSE)            += ls1c_pwm_pulse.o


配置

make menuconfig
  Device Drivers  --->
    [*] Misc devices  --->
      <*>   ls1c pwm pulse

make

运行效果

一次产生20个脉冲,再来看看每个脉冲的详细情况

代码中设置了一个脉冲的高电平和低电平都是2us,如下

// 脉冲宽度#define PWM_PULSE_HIGH_WIDTH_NS                 (2*1000)    // 高电平2us#define PWM_PULSE_LOW_WIDTH_NS                  (2*1000)    // 低电平2us
A4988要求脉冲的高低电平至少1us,这里留了点余量,设置为2us。驱动的write函数中,每产生一个脉冲后,就延迟了10us,所以看到以上结果。


测试时,发现pwm0,pwm2,和pwm3都能正常输出单脉冲,唯独pwm1的波形有点异常,高电平没有上升到想要的高度,大约上升到了1v左右,如下

经过仔细查看原理图后,发现pwm1的引脚gpio92上接有一个按键和电容,原理图如下

电容c83是为了按键消抖用的,可是我这里不需要在gpio92(pwm1)上接按键,所以果断把c83用烙铁取下来,如下

测试,一切正常。果然是这个电容影响了。


拓展

如果想产生标准的pwm波形,只需要把pwm的控制寄存器的第4位SINGLE置0就可以了,其它的配置完全一样。1c的linux源码中文件“arch\mips\loongson\ls1x\pwm.c”已经封装了好几个函数,只需要在make menuconfig时,选上

Machine selection  --->
  [*] Enable PWM
然后就可以直接调用了。

0 0
原创粉丝点击