本文共 8528 字,大约阅读时间需要 28 分钟。
本文为在用龙芯1c做3D打印机过程中的笔记。龙芯1c做的3d打印机简称“龙印”,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
#includeMakefile#include #include #include #include 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); }}
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源码根目录\arch\mips\loongson\ls1x\ls1c\platform.c”中加入#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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_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_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_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 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");
#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 pulsemake
一次产生20个脉冲,再来看看每个脉冲的详细情况
代码中设置了一个脉冲的高电平和低电平都是2us,如下
// 脉冲宽度#define PWM_PULSE_HIGH_WIDTH_NS (2*1000) // 高电平2us#define PWM_PULSE_LOW_WIDTH_NS (2*1000) // 低电平2usA4988要求脉冲的高低电平至少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 然后就可以直接调用了。