? ? ? ? 人生第一份工作的第一个项目,要用到nRF52832的PWM驱动一个无源蜂鸣器。
一、通用装载模式——呼吸灯
????????首先分析一下官方SDK给的PWM实现呼吸灯的demo,如下(省去了和呼吸灯无关的部分)。
//PWM驱动程序实例ID,ID和外设编号对应,0:PWM0 1:PWM1 2:PWM2
#define PWM_INSTANCE 0
//定义名称为m_pwm0的PWM驱动程序实例,参数为0表示该实例对应的PWM外设为PWM0
static nrfx_pwm_t m_pwm0 = NRFX_PWM_INSTANCE(PWM_INSTANCE);
static uint16_t const m_top = 10000;
static uint16_t const m_step = 100;
//定义PWM序列(通用装载模式),该序列必须位于RAM中,因此要定义为static类型的
uint16_t step = m_top / m_step;
static nrf_pwm_values_common_t seq0_values[m_step*2];
uint16_t value = 0;
static void pwm_common_init(void)
{
//设置PWM序列中各个周期的值:逐步修改占空比,实现呼吸灯效果
for (uint8_t i = 0; i < m_step; ++i)
{
value += step;
seq0_values[i] = value;
seq0_values[m_step+i] = m_top - value;
}
//定义PWM初始化配置结构体并初始化参数
nrfx_pwm_config_t const config0 =
{
.output_pins =
{
BSP_LED_0 | NRFX_PWM_PIN_INVERTED, //通道0映射到P0.17(驱动开发板上的指示灯D1),空闲状态输出高电平
BSP_LED_1 | NRFX_PWM_PIN_INVERTED, //通道1映射到P0.18(驱动开发板上的指示灯D2),空闲状态输出高电平
BSP_LED_2 | NRFX_PWM_PIN_INVERTED, //通道2映射到P0.19(驱动开发板上的指示灯D3),空闲状态输出高电平
NRFX_PWM_PIN_NOT_USED //通道3不使用
},
.irq_priority = APP_IRQ_PRIORITY_LOWEST,//中断优先级
.base_clock = NRF_PWM_CLK_1MHz, //PWM时钟频率设置为1MHz
.count_mode = NRF_PWM_MODE_UP, //向上计数模式
.top_value = m_top, //计数最大值为10000
.load_mode = NRF_PWM_LOAD_COMMON, //通用装载模式
.step_mode = NRF_PWM_STEP_AUTO //序列中的周期自动推进
};
//初始化PWM
APP_ERROR_CHECK(nrfx_pwm_init(&m_pwm0, &config0, NULL));
}
//播放PWM
static void pwm_play(void)
{
//定义PWM播放序列,播放序列包含了PWM序列的起始地址、大小和序列播放控制描述
nrf_pwm_sequence_t const seq0 =
{
.values.p_common = seq0_values,//指向PWM序列
.length = NRF_PWM_VALUES_LENGTH(seq0_values),//PWM序列中包含的周期个数
.repeats = 0, //序列中周期重复次数为0
.end_delay = 0 //序列后不插入延时
};
//启动PWM序列播放,flags设置为NRFX_PWM_FLAG_LOOP:序列播放完成后,自动触发任务重新播放
//如改为NRFX_PWM_FLAG_STOP,则播放结束后,PWM停止
(void)nrfx_pwm_simple_playback(&m_pwm0, &seq0, 1,
NRFX_PWM_FLAG_LOOP);
}
int main(void)
{
//初始化开发板上的4个LED,即将驱动LED的GPIO配置为输出,
bsp_board_init(BSP_INIT_LEDS);
pwm_common_init();
pwm_play();
while(true)
{
}
}
????????实现的呼吸灯现象是:LED_0,LED_1,LED_2在1秒的时间里由暗到亮,再在1秒内由亮到暗,不断循环。具体每个变量和每一行是啥意思,SDK里都说得很清楚了,这里就不再赘述,但是有几个关键的变量:m_top = 10000 ; m_step = 100 ; step = m_top / m_step;seq0_values[m_step*2]?,没有解释清楚,让我这个初学者琢磨了好久。
? ? ? ? 先直接上结论:
????????①在初始化函数里找到.base_clock时钟频率为1MHz,那么周期T = m_top / 1M = 10000 / 1000000 = 0.01s = 10ms ,那么频率f自然就等于1 / T ,即这个m_top是用来决定PWM的周期和频率的。
? ? ? ? ②为了实现灯在1秒内的亮暗变化,令m_step = 100 ,从而100*10ms = 1s ,正好是一次由暗变亮或者一次由亮变暗的时间 ,即这个m_step决定了一次亮度的线性变化所花费的时间。
? ? ? ? ③?seq0_values[m_step*2]?这个数组的各个数值,决定的是各个小周期(10ms)的占空比,m_step*2则表示一个大周期(也就是暗→亮→暗)总共包含了m_step*2 个小周期(也就是10ms*200=2s)。第n个小周期的占空比 P =(10000 - seq0_values[n]) / 10000 。
? ? ? ? ④step决定的是单次占空比的变化量。seq0_values数组的前100个数的数值每次都+100,也就是占空比每次都减少1%,100个数正好减到0%;seq0_values数组的后100个数的数值每次都-100,也就是占空比每次都增加1%,100次正好加到100%?。
? ? ? ? 把这些个变量搞明白之后,驱动无源蜂鸣器简直手到擒来,但通用装载模式驱动波形不变的无源蜂鸣器太麻烦,用波形装载模式更好理解。? ? ? ? ??
二、波形装载模式——无源蜂鸣器
????????先看立创商城里给出的蜂鸣器信息:
? ? ? ?
? ? ? ??咱唯一要看的就是“频率2700Hz”,所需要的周期长度T = 1/2700 ≈ 370μs = 0.00037s 。保持时钟频率依旧是1MHz不变,根据结论①,m_top (也就是下文seq0_values[ ]的第四个数)= 0.00037 * 10^6 = 370?。
#define BUZZER 23
#define PWM_INSTANCE_BUZZER 0
static nrfx_pwm_t m_pwm0 = NRFX_PWM_INSTANCE(PWM_INSTANCE_BUZZER);
static nrf_pwm_values_wave_form_t seq0_values[] =
{
37*8, //通道0,占空比20%
370, //通道1,占空比0
370, //通道2,占空比0
370 //最大比较值
};
static void pwm0_common_init(void) //初始化
{
//定义PWM初始化配置结构体并初始化参数
nrfx_pwm_config_t const config0 =
{
.output_pins =
{
BUZZER | NRFX_PWM_PIN_INVERTED, //通道0映射到蜂鸣器口23
NRFX_PWM_PIN_NOT_USED, //通道1未使用
NRFX_PWM_PIN_NOT_USED, //通道2未使用
NRFX_PWM_PIN_NOT_USED //通道3在波形装载模式下从不使用
},
.irq_priority = APP_IRQ_PRIORITY_LOWEST,//中断优先级
.base_clock = NRF_PWM_CLK_1MHz, //PWM时钟频率设置为1MHz
.count_mode = NRF_PWM_MODE_UP, //向上计数模式
.top_value = 0, //使用波形装载模式时,该值被忽略
.load_mode = NRF_PWM_LOAD_WAVE_FORM, //波形装载模式
.step_mode = NRF_PWM_STEP_AUTO //序列中的周期自动推进
};
//初始化PWM
APP_ERROR_CHECK(nrfx_pwm_init(&m_pwm0, &config0, NULL));
}
static void pwm0_play(void) //播放
{
//定义PWM播放序列,播放序列包含了PWM序列的起始地址、大小和序列播放控制描述
nrf_pwm_sequence_t const seq0 =
{
.values.p_wave_form = seq0_values,//指向PWM序列
.length = NRF_PWM_VALUES_LENGTH(seq0_values),//PWM序列中包含的周期个数
.repeats = 0, //序列中周期重复次数为0
.end_delay = 0 //序列后不插入延时
};
//启动PWM序列播放,flags设置为NRFX_PWM_FLAG_LOOP:序列播放完成后,自动触发任务重新播放
//如改为NRFX_PWM_FLAG_STOP,则播放结束后,PWM停止
(void)nrfx_pwm_simple_playback(&m_pwm0, &seq0, 1,
NRFX_PWM_FLAG_LOOP);
}
????????seq0_values数组中的前三个数是用来决定占空比的,一一对应config0.output_pins中的前三个通道的占空比,第四个数370和呼吸灯demo里的m_top是一个意思,是决定周期的数值,所以第四个通道不开(就算开了估计占空比也是0,没意义)。
|