蓝牙芯片nRF52832之PWM的使用

nRF52832蓝牙芯片,自带了3路PWM硬件模块,每个模块支持4路,总共支持12路PWM。在SDK15中提供了简化的API,能够快速、便捷的实现PWM功能。以驱动LED灯为例,介绍一下nRF5283在SDK15中PWM的使用步骤以及注意的问题。

SDK15 PWM的使用步骤

1. SDK 驱动配置中enable PWM模块

图1

2. 初始化PWM实例

    #include "nrf_drv_pwm.h"
    static nrf_drv_pwm_t m_pwm0 = NRF_DRV_PWM_INSTANCE(0);

3. 初始化配置

    nrf_drv_pwm_config_t const config0 =
    {
        .output_pins =
        {
            BSP_LED_0 | NRF_DRV_PWM_PIN_INVERTED, // channel 0
            BSP_LED_2 | NRF_DRV_PWM_PIN_INVERTED, // channel 1
            BSP_LED_3 | NRF_DRV_PWM_PIN_INVERTED, // channel 2
            BSP_LED_1 | NRF_DRV_PWM_PIN_INVERTED // channel 3
        },
        .irq_priority = APP_IRQ_PRIORITY_LOWEST,
        .base_clock = NRF_PWM_CLK_125kHz,
        .count_mode = NRF_PWM_MODE_UP,
        .top_value = 15625,
        .load_mode = NRF_PWM_LOAD_INDIVIDUAL,
        .step_mode = NRF_PWM_STEP_AUTO
    };
    APP_ERROR_CHECK(nrf_drv_pwm_init(&m_pwm0, &config0, NULL));

每路PWM硬件模块支持4个channel,nrf_drv_pwm_config_t中:

  • output_pins:映射PWM输出的pin脚号(spec上硬件的定义如P0.00,P0.01,00,01就是pin脚号,开发板中4个LED用的p0.17 - p0.20端口, 对应的pin脚号就是17-20);如果对应的通道没有硬件连接就使用NRF_DRV_PWM_PIN_NOT_USED。每个通道可以使用NRF_DRV_PWM_PIN_INVERTED来设置空闲引脚的电平为高电平,不使用时空闲引脚的电平为低电平
  • irq_priority:定义中断优先级,一般不使用;
  • base_clock定义PWM的时钟,从16M的clock分频而来,最高16MHZ,最低125kHz, 这个非常重要,每个clock多长时间来计算cycle持续多长时间;
  • count_mode:有两种,一种是NRF_PWM_MODE_UP和NRF_PWM_MODE_UP_AND_DOWN。NRF_PWM_MODE_UP边沿对齐,NRF_PWM_MODE_UP_AND_DOWN中心对齐。两者区别如图2和图3所示;
  • top_value:定义PWM的每个duty cycle中clock的最大个数;
  • load_mode:后面sequnce的装载方式,是每个通道独立加载,还是所有通道共享一个,还是分组;
  • step_mode:定义下一个cycle的进行方式,一般用NRF_PWM_STEP_AUTO,还有一种trigger模式。
图2

图3

4. 定义sequence数据

    static nrf_pwm_values_individual_t /*const*/ seq_values[] =
    {
        { 0x8000, 0, 0, 0 },
        { 0, 0x8000, 0, 0 },
        { 0, 0, 0x8000, 0 },
        { 0, 0, 0, 0x8000 }
    };

先定义每个通道的占空比,通过clock数来定义,不能超过top_value。如果四个通道均使用,则每个数组均有4个人duty cycle数据,至少有一组数据,如{ 0x8000, 0, 0, 0 },通道1的cycle数是0x8000,2-4均为0。根据需要,可以使用不同的占空比来形成一个序列,示例中就定义了4个序列,seq_values的size为4。每个通道使用独立数据,则在config中用.load_mode = NRF_PWM_LOAD_INDIVIDUAL。

如果所有的通道共用一个sequence数据,则需要使用nrf_pwm_values_common_t,实际就是uint16_t,则在config中用.load_mode = NRF_PWM_LOAD_COMMON。

    nrf_pwm_sequence_t const seq =
    {
        .values.p_individual = seq_values,
        .length = NRF_PWM_VALUES_LENGTH(seq_values),
        .repeats = 0,
        .end_delay = 0
    };

nrf_pwm_sequence_t中,

  • values:前面的定义的占空比数据,通道独立数据初始化values.p_individual ,共用一个数据则初始化.values.p_common;
  • length:通过宏NRF_PWM_VALUES_LENGTH自动计算数据长度
  • repeats:每个duty cycle重复的次数,注意是每个duty cycle!不是重复sequece。如{ 0x8000, 0, 0, 0 },在repeats为2的情况下最后PWM生效的数据为,channel 1的duty cycle为0x8000,0x8000,第2-4的duty cycle为0,0
  • end_delay: 每个sequence中,最后一个duty cycle重复的次数,如通道1的duty cycle为 {0x8000, 0},在end_delay为4的情况下,最后PWM的生效数据为:{0x8000, 0,0,0,0,0}

5. 触发PWM

    (void)nrf_drv_pwm_simple_playback(&m_pwm0, &seq, 1, NRF_DRV_PWM_FLAG_LOOP);

通过play_back来触发PWM,第三个参数为播放的次数,最后一个参数定义模式,NRF_DRV_PWM_FLAG_LOOP为永远重复,NRFX_PWM_FLAG_STOP播放定义的次数后结束,还有其他的模式定义起始seq等(最多可以有两个seq)。

6. 完整的代码

    static void pwm_demo(void)
    {
        nrf_drv_pwm_config_t const config0 =
        {
            .output_pins =
            {
                BSP_LED_0 | NRF_DRV_PWM_PIN_INVERTED, // channel 0
                BSP_LED_2 | NRF_DRV_PWM_PIN_INVERTED, // channel 1
                BSP_LED_3 | NRF_DRV_PWM_PIN_INVERTED, // channel 2
                BSP_LED_1 | NRF_DRV_PWM_PIN_INVERTED // channel 3
            },
            .irq_priority = APP_IRQ_PRIORITY_LOWEST,
            .base_clock = NRF_PWM_CLK_125kHz,
            .count_mode = NRF_PWM_MODE_UP,
            .top_value = 15625,
            .load_mode = NRF_PWM_LOAD_INDIVIDUAL,
            .step_mode = NRF_PWM_STEP_AUTO
        };
        APP_ERROR_CHECK(nrf_drv_pwm_init(&m_pwm0, &config0, NULL));
        static nrf_pwm_values_individual_t /*const*/ seq_values[] =
        {
            { 0x8000, 0, 0, 0 },
            { 0, 0x8000, 0, 0 },
            { 0, 0, 0x8000, 0 },
            { 0, 0, 0, 0x8000 }
        };
        nrf_pwm_sequence_t const seq =
        {
            .values.p_individual = seq_values,
            .length = NRF_PWM_VALUES_LENGTH(seq_values),
            .repeats = 0,
            .end_delay = 0
        };
        (void)nrf_drv_pwm_simple_playback(&m_pwm0, &seq, 1, NRF_DRV_PWM_FLAG_LOOP);
    }
    *注:copy自examples/peripheral/pwm_driver/main.c中的demo5

其他的各种使用demo参考SDK中examples/peripheral/pwm_driver/main.c

PWM输出波形的影响因素

1. 影响PWM输出的波形有config和sequence两个定义。

    nrf_drv_pwm_config_t const config =
    {
        .base_clock,
        .count_mode,
        .top_value,
    };
    
    nrf_pwm_sequence_t const seq =
    {
        .values,
        .repeats,
        .end_delay
    };

说明:

  • base_clock:定义基准始终,从16MHz到125kHz,每个clock的时间为 1/base_clock,如NRF_PWM_CLK_125kHz,则每个clock的时间为 1/(125 * 1000) = 0.008ms
  • count_mode:NRF_PWM_MODE_UP模式,则每个duty cycle的总时间为:top_value * ( 1 / base_clock),如果NRF_PWM_MODE_UP_AND_DOWN模式,则每个duty cycle的总时间为:2 * top_value * ( 1 / base_clock),如图2 图3
  • top_value:每个cycle总的clock数,一个cycle的总时间为:top_value * ( 1 / base_clock)
  • values: sequence的定义,每个duty cycle中,有效的clock数
  • repeats:每个duty cycle重复的次数
  • end_delay:最后一个duty cycle的重复的次数
    如一组定义:
    {
        .base_clock = NRF_PWM_CLK_125kHz,
        .count_mode = NRF_PWM_MODE_UP,
        .top_value = 1000,
        .values = {1000, 0} //一个100%的占空比,一个0%的占空比
        .repeats = 0,
        .end_delay = 0
    }

该组的波形为:

图3

如一组定义:

    {
        .base_clock = NRF_PWM_CLK_125kHz,
        .count_mode = NRF_PWM_MODE_UP,
        .top_value = 1000,
        .values = {1000, 0} //一个100%的占空比,一个0%的占空比
        .repeats = 2,
        .end_delay = 0
    }

该组的波形为:

图4

如一组定义:

    {
        .base_clock = NRF_PWM_CLK_125kHz,
        .count_mode = NRF_PWM_MODE_UP,
        .top_value = 1000,
        .values = {1000, 0} //一个100%的占空比,一个0%的占空比
        .repeats = 0,
        .end_delay = 2
    }

该组的波形为:

图5

2. 官方demo中duty cycle 值为0x8000的调查

官方SDK中examples/peripheral/pwm_driver/main.c示例中,nrf_pwm_sequence_t中的duty cycle数值均用0x8000,一直百思不得其解。不同的例子,有的LED为200ms,有的为250ms,但duty cycle均采用0x8000,并且都能工作。why?

看到nrf_pwm_configure的源码中有个assert,NRFX_ASSERT(top_value <= PWM_COUNTERTOP_COUNTERTOP_Msk),进一步调查发现PWM_COUNTERTOP_COUNTERTOP_Msk定义为0x7FFF。也就是COUNTERTOP是个14位的寄存器,查看官方spec验证了这一想法。

实际上top_value的最大值是0x7FFF,所以nrf_pwm_sequence_t的每个duty cycle值也不应该大于0x7FFF。0x8000刚好比最大值大1,无论top_value是多少,都是占空比100%。猜测,如果设置的值大于0x7FFF,系统应该会自动将其置为0x7FFF。在示例中,将0x8000替换为0x9000,0xA000均能工作,算是做了一个验证。

    __STATIC_INLINE void nrf_pwm_configure(NRF_PWM_Type * p_reg,
                                           nrf_pwm_clk_t  base_clock,
                                           nrf_pwm_mode_t mode,
                                           uint16_t       top_value)
    {
        NRFX_ASSERT(top_value <= PWM_COUNTERTOP_COUNTERTOP_Msk);
    
        p_reg->PRESCALER  = base_clock;
        p_reg->MODE       = mode;
        p_reg->COUNTERTOP = top_value;
    }
    
    #define PWM_COUNTERTOP_COUNTERTOP_Msk (0x7FFFUL << PWM_COUNTERTOP_COUNTERTOP_Pos) /*!< Bit mask of COUNTERTOP field. */

Spec中COUNTERTOP的寄存器定义:

图6

3. COUNTERTOP的限制进一步思考

既然top_value的最大值是0x7FFF(十进制32768),sequence中的值也应该均比0x7FFF小。对于一个base_clock为 NRF_PWM_CLK_125kHz的PWM来说,最大的cycle时间为:32768 * ( 1 / 125000) = 262.144ms。如果需要一个cycle time为1秒的波形该怎么办呢?这时得动用repeats来拓展了。将top_value设为250ms极为 250 / 0.008 = 31250。将repeats设为4,即为250*4 = 1秒了。也可以组合end_delay实现各种不同的波形。

在官方的例子中都是一个占空比100%(values值0x8000),一个占空比为0%的波形(values值0)。可以将top_value设置为小于32768,values中的值设置成0-32768,即可调整占空比。

总结

PWM虽然简单,但是对一个平台来说都有自己的特色。调试下来,对文档中说得不够清楚的,有些古怪的地方还是要调查一下,会对这个点的理解更深入一些。

(原创yanweichuan@163.com

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容