本文共 17557 字,大约阅读时间需要 58 分钟。
龙芯1c库是把龙芯1c的常用外设的常用功能封装为一个库,类似于STM32库。Git地址:https://gitee.com/caogos/OpenLoongsonLib1c
本文通过“龙芯1c库”中提供的PWM接口,实现了在4路pwm的输出连续的pwm脉冲和单个脉冲。
在讲解了pwm接口之后,才开始详细介绍寄存器,以及怎样使用这些寄存器来封装接口,最后给出了封装的pwm接口代码清单。提供三个接口函数,分别是
void pwm_init(pwm_info_t *pwm_info);void pwm_disable(pwm_info_t *pwm_info);void pwm_enable(pwm_info_t *pwm_info);
其中函数pwm_init()是最核心的接口,调用后会立刻在指定引脚上产生需要的波形。初始化之后,可以在需要的时候调用pwm_disable()停止输出pwm波形,在disable后还可以调用pwm_enable()重新使能,指定引脚上又继续输出pwm波形。
这三个接口都是一了相同的入参,这个入参到底长啥样,马上揭晓。// 硬件pwm信息typedef struct{ unsigned int gpio; // PWMn所在的gpio unsigned int mode; // 工作模式(单脉冲、连续脉冲) float duty; // pwm的占空比 unsigned long period_ns; // pwm周期(单位ns)}pwm_info_t;
我想这个应该一看就知道说的是啥。
这里重点解释一下为什么这里是gpio,而不是PWMn。因为每路pwm都可以复用到至少另外的一个引脚上,却不存在一个引脚可能发生支持多个PWM复用的情况,这里直接传入gpio,代码中会自动根据gpio判断是使用那个PWM,是否需要使用复用,如果需要复用,自动复用。 除了gpio之外,还有工作模式为啥还有个单脉冲模式。当工作在单脉冲模式时,调用一次pwm_init()产生一个脉冲。这对于常用于步进电机的控制,比如我之前做的3D打印机(龙印)就用到了,通过控制脉冲个数,达到控制步进电机行走距离的目的。龙芯1C300B支持FPU(硬件浮点运算单元),并且龙芯1c库中已经默认开启了FPU,所以这里直接采用浮点数形式的占空比。
据此,构造了三个测试用例:
用例一,测试PWM正常模式(连续输出PWM)。 通过调用pwm_init()产生连续单脉冲,然后调用pwm_disable()停止输出脉冲,最后调用pwm_enable()又继续输出连续脉冲。为了便于使用示波器观察,禁止输出一会后,又使能,再禁止,再使能,反复不停的循环。用例二,测试PWM单脉冲模式(每次只输出一个脉冲) 通过调用pwm_init()产生一个脉冲,再延时一会之后(延时时间必须大于等于pwm周期),再调用pwm_init(),再延时一会,再调用pwm_init(),再延时,再输出一定数量的脉冲之后停止输出。通用为了便于用示波器观察,可以将上述过程反复重复。用例三。将pwm默认的引脚作用普通GPIO,同时复用其它引脚来用作pwm。 PWM0的默认引脚GPIO06用作普通gpio,而GPIO04上输出pwm波形,
龙芯1c有4路pwm,每路都可以复用。但只有pwm0和pwm1才有默认引脚(当然也可以复用其它引脚),pwm2和pwm3都需要复用。这里每路选取了两个引脚,如下
// pwm引脚定义#define LS1C_PWM0_GPIO06 (6) // gpio06用作pwm0#define LS1C_PWM0_GPIO04 (4) // gpio04复用为pwm0#define LS1C_PWM1_GPIO92 (92) // gpio92用作pwm1#define LS1C_PWM1_GPIO05 (5) // gpio05复用为pwm1#define LS1C_PWM2_GPIO52 (52) // gpio52复用为pwm2#define LS1C_PWM2_GPIO46 (46) // gpio46复用为pwm2#define LS1C_PWM3_GPIO47 (47) // gpio47复用为pwm3#define LS1C_PWM3_GPIO53 (53) // gpio53复用为pwm3
每个引脚都可以输出连续pwm脉冲和单个脉冲。
// 测试硬件pwm产生连续的pwm波形void test_pwm_normal(void){ pwm_info_t pwm_info; int i; pwm_info.gpio = LS1C_PWM0_GPIO06; // pwm引脚位gpio06// pwm_info.gpio = LS1C_PWM0_GPIO04; pwm_info.mode = PWM_MODE_NORMAL; // 正常模式--连续输出pwm波形 pwm_info.duty = 0.25; // pwm占空比 pwm_info.period_ns = 5*1000*1000; // pwm周期5ms // pwm初始化,初始化后立即产生pwm波形 pwm_init(&pwm_info); while (1) { // 延时100ms for (i=0; i<100; i++) { delay_1ms(); } // 禁止pwm pwm_disable(&pwm_info); // 延时100ms for (i=0; i<100; i++) { delay_1ms(); } // 使能pwm pwm_enable(&pwm_info); } return ;}
从图上可以看出,有脉冲和没脉冲的时间都是100ms,与代码相符。下面放大了再来看看连续脉冲的详细信息,如下
占空比为0.25,pwm周期为5ms,与代码相符。
// 测试硬件pwm产生pwm脉冲void test_pwm_pulse(void){ int i; pwm_info_t pwm_info; pwm_info.gpio = LS1C_PWM0_GPIO06; // 输出pwm波形的引脚 pwm_info.mode = PWM_MODE_PULSE; // 单脉冲模式,每次调用只发送一个脉冲,调用间隔必须大于pwm周期 pwm_info.duty = 0.25; // pwm占空比 pwm_info.period_ns = 1*1000*1000; // pwm周期1ms // 为了便于用示波器观察,这里选择每个1s就发送10个脉冲 while (1) { // 发送10个脉冲 for (i=0; i<10; i++) { pwm_init(&pwm_info); delay_1ms(); delay_1ms(); } // 延时10ms for (i=0; i<10; i++) { delay_1ms(); } } return ;}
连续发了10个,然后延时10ms,再发,与代码相符。再来看看每个脉冲的详细情况,如下
每个脉冲周期为2ms与延时的时间相同,大于初始化时的1ms。这是对的,与代码相符。
但是当把gpio92用于pwm输出时(PWM1的默认引脚就是GPIO92),只有gpio92引脚有pwm波形,而gpio05引脚没有。
把gpio05复用为pwm1,而pwm1默认的引脚gpio92处于上电默认状态(未操作引脚相应寄存器)时,gpio05引脚和gpio92引脚都会出现相同的pwm波形。
PWM0也有类似情况,当把gpio04复用为PWM0时,也会出现gpio04和gpio06都出现相同的pwm波形。
/* * 测试gpio04复用为pwm,gpio06作为普通gpio使用 * PWM0的默认引脚位GPIO06,但也可以复用为GPIO04 * 当gpio06还是保持默认为pwm时,复用gpio04为pwm0,那么会同时在两个引脚输出相同的pwm波形 * 本函数旨在证明可以在gpio04复用为pwm0时,还可以将(默认作为pwm0的)gpio06作为普通gpio使用 */void test_pwm_gpio04_gpio06(void){ pwm_info_t pwm_info; unsigned int gpio = 6; // 在gpio04引脚输出pwm波形 pwm_info.gpio = LS1C_PWM0_GPIO04; // gpio04引脚作为pwm使用 pwm_info.mode = PWM_MODE_NORMAL; // 输出连续的pwm波形 pwm_info.duty = 0.25; // 占空比0.25 pwm_info.period_ns = 1*1000*1000; // pwm周期1ms pwm_init(&pwm_info); // gpio06引脚作为普通gpio使用 gpio_init(gpio, gpio_mode_output); gpio_set(gpio, gpio_level_low); while (1) ; return ;}
Gpio06输出低电平
Gpio04输出pwm波形
// 测试硬件pwm的测试用例头文件#ifndef __OPENLOONGSON_TEST_PWM_H#define __OPENLOONGSON_TEST_PWM_H// 测试硬件pwm产生连续的pwm波形void test_pwm_normal(void);// 测试硬件pwm产生pwm脉冲void test_pwm_pulse(void);/* * 测试gpio04复用为pwm,gpio06作为普通gpio使用 * PWM0的默认引脚位GPIO06,但也可以复用为GPIO04 * 当gpio06还是保持默认为pwm时,复用gpio04为pwm0,那么会同时在两个引脚输出相同的pwm波形 * 本函数旨在证明可以在gpio04复用为pwm0时,还可以将(默认作为pwm0的)gpio06作为普通gpio使用 */void test_pwm_gpio04_gpio06(void);#endif
// 测试硬件pwm的测试用例源文件#include "../lib/public.h"#include "../lib/gpio.h"#include "../lib/ls1c_regs.h"#include "../lib/sys_tick.h"#include "../lib/pwm.h"// 测试硬件pwm产生连续的pwm波形void test_pwm_normal(void){ pwm_info_t pwm_info; int i; pwm_info.gpio = LS1C_PWM0_GPIO06; // pwm引脚位gpio06// pwm_info.gpio = LS1C_PWM0_GPIO04; pwm_info.mode = PWM_MODE_NORMAL; // 正常模式--连续输出pwm波形 pwm_info.duty = 0.25; // pwm占空比 pwm_info.period_ns = 5*1000*1000; // pwm周期5ms // pwm初始化,初始化后立即产生pwm波形 pwm_init(&pwm_info); while (1) { // 延时100ms for (i=0; i<100; i++) { delay_1ms(); } // 禁止pwm pwm_disable(&pwm_info); // 延时100ms for (i=0; i<100; i++) { delay_1ms(); } // 使能pwm pwm_enable(&pwm_info); } return ;}// 测试硬件pwm产生pwm脉冲void test_pwm_pulse(void){ int i; pwm_info_t pwm_info; pwm_info.gpio = LS1C_PWM0_GPIO06; // 输出pwm波形的引脚 pwm_info.mode = PWM_MODE_PULSE; // 单脉冲模式,每次调用只发送一个脉冲,调用间隔必须大于pwm周期 pwm_info.duty = 0.25; // pwm占空比 pwm_info.period_ns = 1*1000*1000; // pwm周期1ms // 为了便于用示波器观察,这里选择每个1s就发送10个脉冲 while (1) { // 发送10个脉冲 for (i=0; i<10; i++) { pwm_init(&pwm_info); delay_1ms(); delay_1ms(); } // 延时10ms for (i=0; i<10; i++) { delay_1ms(); } } return ;}/* * 测试gpio04复用为pwm,gpio06作为普通gpio使用 * PWM0的默认引脚位GPIO06,但也可以复用为GPIO04 * 当gpio06还是保持默认为pwm时,复用gpio04为pwm0,那么会同时在两个引脚输出相同的pwm波形 * 本函数旨在证明可以在gpio04复用为pwm0时,还可以将(默认作为pwm0的)gpio06作为普通gpio使用 */void test_pwm_gpio04_gpio06(void){ pwm_info_t pwm_info; unsigned int gpio = 6; // 在gpio04引脚输出pwm波形 pwm_info.gpio = LS1C_PWM0_GPIO04; // gpio04引脚作为pwm使用 pwm_info.mode = PWM_MODE_NORMAL; // 输出连续的pwm波形 pwm_info.duty = 0.25; // 占空比0.25 pwm_info.period_ns = 1*1000*1000; // pwm周期1ms pwm_init(&pwm_info); // gpio06引脚作为普通gpio使用 gpio_init(gpio, gpio_mode_output); gpio_set(gpio, gpio_level_low); while (1) ; return ;}
#include#include #include #include "../lib/ls1c_public.h"#include "../lib/ls1c_irq.h"#include "../lib/ls1c_gpio.h"#include "../lib/ls1c_delay.h"#include "../lib/ls1c_mipsregs.h"#include "../lib/ls1c_uart.h"#include "../lib/ls1c_sys_tick.h"#include "../lib/ls1c_clock.h"#include "../example/test_gpio.h"#include "../example/test_pwm.h"#include "../example/test_delay.h"#include "../example/test_simulate_i2c.h"#include "../example/test_timer.h"#include "../example/test_fpu.h"#include "../example/test_i2c.h"#include "../example/test_uart.h"#include "../example/test_sys_tick.h"#include "../example/test_spi.h"// 硬浮点初始化void fpu_init(void){ unsigned int c0_status = 0; unsigned int c1_status = 0; // 使能协处理器1--FPU c0_status = read_c0_status(); c0_status |= (ST0_CU1 | ST0_FR); write_c0_status(c0_status); // 配置FPU c1_status = read_c1_status(); c1_status |= (FPU_CSR_FS | FPU_CSR_FO | FPU_CSR_FN); // set FS, FO, FN c1_status &= ~(FPU_CSR_ALL_E); // disable exception c1_status = (c1_status & (~FPU_CSR_RM)) | FPU_CSR_RN; // set RN write_c1_status(c1_status); return ;}void bsp_init(void){ // 初始化调试串口 uart2_init(); // 硬浮点初始化 fpu_init(); // 初始化异常 exception_init(); // 显示时钟信息 clk_print_all(); return ;}int main(void){ bsp_init(); // 注意内存的大小,在文件sdram_cfg.h中配置内存大小, // 源文件中已经有8M和32M的代码,只需要 “#if 0” 或者 “#if 1” 来选择 // -------------------------测试gpio---------------------- /* * 测试库中gpio作为输出时的相关接口 * led闪烁10次 */// test_gpio_output(); /* * 测试库中gpio作为输入时的相关接口 * 按键按下时,指示灯点亮,否则,熄灭 */// test_gpio_input(); /* * 测试库中外部中断(gpio输入中断)的相关接口 * 按键被按下后,会产生一个中断 */// test_gpio_key_irq(); // ------------------------测试串口----------------------- // 通过串口2打印helloworld// test_uart2_print_helloworld(); // 测试串口2的收发功能是否正常// test_uart2_send_recv(); // 测试串口2的收发功能是否正常// test_uart1_send_recv(); // 测试串口3的收发功能是否正常// test_uart3_send_recv(); // 测试串口8的收发功能是否正常,注意龙芯1C300A没有串口8,龙芯1C300B才有串口5到串口11这几个串口// test_uart8_send_recv(); // 测试printf()函数// test_printf(); // ------------------------测试PWM-------------------------------- // 测试硬件pwm产生连续的pwm波形 test_pwm_normal(); // 测试硬件pwm产生pwm脉冲// test_pwm_pulse(); /* * 测试gpio04复用为pwm,gpio06作为普通gpio使用 * PWM0的默认引脚位GPIO06,但也可以复用为GPIO04 * 当gpio06还是保持默认为pwm时,复用gpio04为pwm0,那么会同时在两个引脚输出相同的pwm波形 * 本函数旨在证明可以在gpio04复用为pwm0时,还可以将(默认作为pwm0的)gpio06作为普通gpio使用 */// test_pwm_gpio04_gpio06(); // ------------------------测试软件延时-------------------------------- // 测试延时函数delay_1ms()// test_delay_1ms(); // 测试延时函数delay_1us()// test_delay_1us(); // 测试延时函数delay_1s()// test_delay_1s(); // ------------------------测试模拟I2C------------------------------ // 测试模拟I2C// test_simulate_i2c_am2320(); // ------------------------测试硬件I2C--------------------------- // 用温湿度传感器测试硬件i2c// test_i2c_am2320(); // ------------------------测试硬件定时器--------------------------- // 测试硬件定时器的定时功能(读取中断状态位的方式判断是否超时)// test_timer_poll_time_out(); // 测试硬件定时器的中断// test_timer_irq(); // 测试硬件定时器的计时// test_timer_get_time(); // ------------------------测试硬浮点(FPU)--------------------------- // 测试使用硬浮点进行浮点数的加减乘除// test_fpu(); // ------------------------测试滴答定时器--------------------------- // 通过获取当前tick值来测试滴答定时器,默认已经使能了滴答定时器,每秒1000个tick// test_sys_tick(); // ------------------------测试硬件SPI--------------------------- // 用tm7705测试硬件SPI // 具体为tm7705+ntc热敏电阻实现温度测量(3d打印机就可以采用此方案测量温度)// test_spi_tm7705(); // ------------------------测试CAN--------------------------- // 详情请参考文件test_can.c while (1) ; return(0);}
我认为芯片手册中,对PWM的介绍已经非常详细,编程也足够了,下面一起来读芯片手册
4路PWM,每路的工作和控制方式都一样。代码中也是使用一样的代码,只需要改变PWM寄存器基地址就可以切换在几路PWM中来回切换。
计数器CNTR、HRC和LRC都是24位的,这也就限定了计数器的最大值为0xFFFFFF,也就限定了最大PWM周期。芯片手册说有三种工作模式,我想应该是指连续脉冲,单脉冲和定时器这三种吧。这里只考虑输出连续脉冲和单脉冲,定时器另外单独用一篇文章来讨论。
这里先不考虑中断,只是输出波形。
其实PWM原理很清楚,首先需要设置相应引脚为PWM功能,而非普通GPIO。然后就是清零主计数器,写高低电平计数器,最后就是根据工作模式,是否需要中断等将控制信息吸入控制寄存器。
#ifndef __OPENLOONGSON_PWM_H#define __OPENLOONGSON_PWM_H// pwm引脚定义#define LS1C_PWM0_GPIO06 (6) // gpio06用作pwm0#define LS1C_PWM0_GPIO04 (4) // gpio04复用为pwm0#define LS1C_PWM1_GPIO92 (92) // gpio92用作pwm1#define LS1C_PWM1_GPIO05 (5) // gpio05复用为pwm1#define LS1C_PWM2_GPIO52 (52) // gpio52复用为pwm2#define LS1C_PWM2_GPIO46 (46) // gpio46复用为pwm2#define LS1C_PWM3_GPIO47 (47) // gpio47复用为pwm3#define LS1C_PWM3_GPIO53 (53) // gpio53复用为pwm3// ...还有一些gpio可以复用为gpio的,有需要可以自己添加// 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) // 主计数器使能// 硬件pwm工作模式enum{ // 正常模式--连续输出pwm波形 PWM_MODE_NORMAL = 0, // 单脉冲模式,每次调用只发送一个脉冲,调用间隔必须大于pwm周期 PWM_MODE_PULSE};// 硬件pwm信息typedef struct{ unsigned int gpio; // PWMn所在的gpio unsigned int mode; // 工作模式(单脉冲、连续脉冲) float duty; // pwm的占空比 unsigned long period_ns; // pwm周期(单位ns)}pwm_info_t;/* * 初始化PWMn * @pwm_info PWMn的详细信息 */void pwm_init(pwm_info_t *pwm_info);/* * 禁止pwm * @pwm_info PWMn的详细信息 */void pwm_disable(pwm_info_t *pwm_info);/* * 使能PWM * @pwm_info PWMn的详细信息 */void pwm_enable(pwm_info_t *pwm_info);#endif
// 封装硬件pwm接口#include "public.h"#include "pin.h"#include "pwm.h"#include "clock.h"#include "ls1c_regs.h"#include "sys_tick.h"// pwm的最大周期#define PWM_MAX_PERIOD (0xFFFFFF) // 计数器的值/* * 根据gpio获取相应pwm的基地址 * @gpio pwm引脚 * @ret pwm基地址 */unsigned int pwm_get_reg_base(unsigned int gpio){ unsigned int reg_base = 0; switch (gpio) { case LS1C_PWM0_GPIO06: case LS1C_PWM0_GPIO04: reg_base = LS1C_REG_BASE_PWM0; break; case LS1C_PWM1_GPIO92: case LS1C_PWM1_GPIO05: reg_base = LS1C_REG_BASE_PWM1; break; case LS1C_PWM2_GPIO52: case LS1C_PWM2_GPIO46: reg_base = LS1C_REG_BASE_PWM2; break; case LS1C_PWM3_GPIO47: case LS1C_PWM3_GPIO53: reg_base = LS1C_REG_BASE_PWM3; break; } return reg_base;}/* * 禁止pwm * @pwm_info PWMn的详细信息 */void pwm_disable(pwm_info_t *pwm_info){ unsigned int pwm_reg_base = 0; // 检查入参 if (NULL == pwm_info) { return ; } pwm_reg_base = pwm_get_reg_base(pwm_info->gpio); reg_write_32(0, (volatile unsigned int *)(pwm_reg_base + LS1C_PWM_CTRL)); return ;}/* * 使能PWM * @pwm_info PWMn的详细信息 */void pwm_enable(pwm_info_t *pwm_info){ unsigned int pwm_reg_base = 0; unsigned int ctrl = 0; // 检查入参 if (NULL == pwm_info) { return ; } // 获取基地址 pwm_reg_base = pwm_get_reg_base(pwm_info->gpio); // 清零计数器 reg_write_32(0, (volatile unsigned int *)(pwm_reg_base + LS1C_PWM_CNTR)); // 设置控制寄存器 ctrl = (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) | (0 << LS1C_PWM_OE) | (1 << LS1C_PWM_CNT_EN); if (PWM_MODE_PULSE == pwm_info->mode) // 单脉冲 { ctrl |= (1 << LS1C_PWM_SINGLE); } else // 连续脉冲 { ctrl &= ~(1 << LS1C_PWM_SINGLE); } reg_write_32(ctrl, (volatile unsigned int *)(pwm_reg_base + LS1C_PWM_CTRL)); return ;}/* * 初始化PWMn * @pwm_info PWMn的详细信息 */void pwm_init(pwm_info_t *pwm_info){ unsigned int gpio; unsigned int mode; unsigned long pwm_clk = 0; // pwm模块的时钟频率 unsigned long tmp = 0; unsigned int pwm_reg_base = 0; unsigned long period = 0; // 判断入参 if (NULL == pwm_info) { // 入参非法,则直接返回 return ; } gpio = pwm_info->gpio; mode = pwm_info->mode; // 配置相应引脚用作pwm,而非gpio pin_set_purpose(gpio, PIN_PURPOSE_OTHER); // 复用 switch (gpio) { // 不需要复用 case LS1C_PWM0_GPIO06: case LS1C_PWM1_GPIO92: break; case LS1C_PWM0_GPIO04: // gpio04的第三复用 pin_set_remap(LS1C_PWM0_GPIO04, PIN_REMAP_THIRD); break; case LS1C_PWM1_GPIO05: // gpio05的第三复用 pin_set_remap(LS1C_PWM1_GPIO05, PIN_REMAP_THIRD); break; case LS1C_PWM2_GPIO52: // gpio52的第四复用 pin_set_remap(LS1C_PWM2_GPIO52, PIN_REMAP_FOURTH); break; case LS1C_PWM2_GPIO46: // gpio46的第四复用 pin_set_remap(LS1C_PWM2_GPIO46, PIN_REMAP_FOURTH); break; case LS1C_PWM3_GPIO47: // gpio47的第四复用 pin_set_remap(LS1C_PWM3_GPIO47, PIN_REMAP_FOURTH); break; case LS1C_PWM3_GPIO53: // gpio53的第四复用 pin_set_remap(LS1C_PWM3_GPIO53, PIN_REMAP_FOURTH); break; default: break; } // 根据占空比和pwm周期计算寄存器HRC和LRC的值 // 两个64位数相乘,只能得到低32位,linux下却可以得到64位结果, // 暂不清楚原因,用浮点运算代替 pwm_clk = clk_get_apb_rate(); period = (1.0 * pwm_clk * pwm_info->period_ns) / 1000000000; period = MIN(period, PWM_MAX_PERIOD); // 限制周期不能超过最大值 tmp = period - (period * pwm_info->duty); // 写寄存器HRC和LRC pwm_reg_base = pwm_get_reg_base(gpio); reg_write_32(--tmp, (volatile unsigned int *)(pwm_reg_base + LS1C_PWM_HRC)); reg_write_32(--period, (volatile unsigned int *)(pwm_reg_base + LS1C_PWM_LRC)); // 写主计数器 pwm_enable(pwm_info); return ;}
感谢能够耐心看完。