一、定时器实现软件PWM
实现一个软件PWM,实际上是通过控制一个IO口的输出电平变化实现的,在nRF52832中,我们使用GPIOTE 的 任务模式 TASK_OUT,设置为翻转来实现的,一个周期需要2次翻转,第一次翻转是确定PWM的占空比,第二次翻转是确定PWM的周期:
利用PPI和定时器来实现这个PWM,就需要2路PPI来实现一个PWM输出,一路控制占空比,一路控制周期,定时器的话因为有6个CC寄存器,所以只需要使用一个即可,但是这样实现的PWM占空比是固定的,就是定时器CC寄存器的值固定了,占空比和周期就固定了,程序中是否可以随时更改,等下测试下,下面给出程序示例:
软件PWM实现(寄存器版本):
...
/*定时器初始化*/
void timer0_init(void)
{
/*
2^4 SysClk/2^PRESCALER 16分频成1M时钟源
1000 000HZ
计1S需要1000 000次,计1ms需要1000次
*/
NRF_TIMER0->PRESCALER = 4; //
NRF_TIMER0->MODE = TIMER_MODE_MODE_Timer; // 0 :timer模式
NRF_TIMER0->BITMODE = TIMER_BITMODE_BITMODE_32Bit; //3 : 32bit
NRF_TIMER0->CC[0] =200; //第一次翻转时间
NRF_TIMER0->CC[1] = 5000; //cc[1]这个作为周期
NRF_TIMER0->CC[2] =5000; //定时短的先翻转,定时长的后翻转,所以不用刻意定下前后顺序
NRF_TIMER0->CC[3] =4000; //
/*
// /* Bit 1 : Shortcut between CC[1] event and the CLEAR task. */
// #define TIMER_SHORTS_COMPARE1_CLEAR_Pos (1UL) /*!< Position of COMPARE1_CLEAR field. */
// #define TIMER_SHORTS_COMPARE1_CLEAR_Msk (0x1UL << TIMER_SHORTS_COMPARE1_CLEAR_Pos) /*!< Bit mask of COMPARE1_CLEAR field. */
// #define TIMER_SHORTS_COMPARE1_CLEAR_Disabled (0UL) /*!< Shortcut disabled. */
// #define TIMER_SHORTS_COMPARE1_CLEAR_Enabled (1UL) /*!< Shortcut enabled. */
// /* Bit 0 : Shortcut between CC[0] event and the CLEAR task. */
// #define TIMER_SHORTS_COMPARE0_CLEAR_Pos (0UL) /*!< Position of COMPARE0_CLEAR field. */
// #define TIMER_SHORTS_COMPARE0_CLEAR_Msk (0x1UL << TIMER_SHORTS_COMPARE0_CLEAR_Pos) /*!< Bit mask of COMPARE0_CLEAR field. */
// #define TIMER_SHORTS_COMPARE0_CLEAR_Disabled (0UL) /*!< Shortcut disabled. */
// #define TIMER_SHORTS_COMPARE0_CLEAR_Enabled (1UL) /*!< Shortcut enabled. */
*/
NRF_TIMER0->SHORTS = (TIMER_SHORTS_COMPARE1_CLEAR_Enabled << TIMER_SHORTS_COMPARE1_CLEAR_Pos);
//NRF_TIMER0->SHORTS = 1<<1; //这里清除到cc寄存器中最大的那个就可以,因为要保证一个完整的周期计数,\
所以短时间的cc寄存器触发了,也得继续计时等到计时到最大时间的cc寄存器值,\
才可以完成一个完整周期,再清0计数
NRF_TIMER0->TASKS_START = 1; //启动timer
}
//这里是2路PWM,我们定时器用了4个定时器时间,2个定时时间实现一路PWM
void gpiote_init(void){
NRF_GPIOTE->CONFIG[0] = ( GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos ) //作为task模式
| ( LED_0 << GPIOTE_CONFIG_PSEL_Pos) //设置PWM输出引脚
| ( GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos ) //设置task为翻转PWM引脚的电平
| ( GPIOTE_CONFIG_OUTINIT_High << GPIOTE_CONFIG_OUTINIT_Pos); //初始输出电平为高
NRF_GPIOTE->CONFIG[1] = ( 3 << 0 ) //作为task模式
| ( 18 << 8) //设置PWM输出引脚
| ( 3 << 16 ) //设置task为翻转PWM引脚的电平
| ( 1 << 20); //初始输出电平为高
}
void ppi_set(void){
NRF_PPI->CH[0].EEP = (uint32_t)(&NRF_TIMER0->EVENTS_COMPARE[0]);
NRF_PPI->CH[0].TEP = (uint32_t)(&NRF_GPIOTE->TASKS_OUT[0]);
NRF_PPI->CH[1].EEP = (uint32_t)(&NRF_TIMER0->EVENTS_COMPARE[1]);
NRF_PPI->CH[1].TEP = (uint32_t)(&NRF_GPIOTE->TASKS_OUT[0]);
NRF_PPI->CH[2].EEP = (uint32_t)(&NRF_TIMER0->EVENTS_COMPARE[2]);
NRF_PPI->CH[2].TEP = (uint32_t)(&NRF_GPIOTE->TASKS_OUT[1]);
NRF_PPI->CH[3].EEP = (uint32_t)(&NRF_TIMER0->EVENTS_COMPARE[3]);
NRF_PPI->CH[3].TEP = (uint32_t)(&NRF_GPIOTE->TASKS_OUT[1]);
NRF_PPI->CHEN = 0x0f; // 使能通道 0 1 2 3 低4位1111
}
int main(void){
int i = 0;
gpiote_init();
ppi_set();
timer0_init();
while(1){
for(i = 0; i< 50;i++){
NRF_TIMER0->CC[0] = i*100; //改变占空比,这是一个DEMO
nrf_delay_ms(25);
}
}
}
软件PWM实现(库函数版本):
/**@brief Macro for creating a PWM instance.
#define APP_PWM_INSTANCE(name, num) \
const nrf_drv_timer_t m_pwm_##name##_timer = NRF_DRV_TIMER_INSTANCE(num); \
app_pwm_cb_t m_pwm_##name##_cb; \
lint -e{545} \
const app_pwm_t name = { \
.p_cb = &m_pwm_##name##_cb, \
.p_timer = &m_pwm_##name##_timer, \
}
*/
APP_PWM_INSTANCE(PWM1,1); // 创建一个使用定时器1产生PWM波的实例
static volatile bool ready_flag; // 使用一个标志位表示PWM状态
void pwm_ready_callback(uint32_t pwm_id) // PWM回调功能
{
ready_flag = true;
}
int main(void)
{
ret_code_t err_code;
/*
/**@brief PWM instance default configuration (1 channel).
#define APP_PWM_DEFAULT_CONFIG_1CH(period_in_us, pin) \
{ \
.pins = {pin, APP_PWM_NOPIN}, \
.pin_polarity = {APP_PWM_POLARITY_ACTIVE_LOW, APP_PWM_POLARITY_ACTIVE_LOW}, \
.num_of_channels = 1, \
.period_us = period_in_us \
}
/**@brief PWM instance default configuration (2 channels).
#define APP_PWM_DEFAULT_CONFIG_2CH(period_in_us, pin0, pin1) \
{ \
.pins = {pin0, pin1}, \
.pin_polarity = {APP_PWM_POLARITY_ACTIVE_LOW, APP_PWM_POLARITY_ACTIVE_LOW}, \
.num_of_channels = 2, \
.period_us = period_in_us \
}
.
使用默认配置 初始化 软件PWM ,2个通道,5000us,LED1,LED2,极性APP_PWM_POLARITY_ACTIVE_LOW
*/
app_pwm_config_t pwm1_cfg = APP_PWM_DEFAULT_CONFIG_2CH(5000L, LED_1, LED_2);
/* 为了看个现象,两个灯状态相反,所以改变了一个PWM输出极性 */
pwm1_cfg.pin_polarity[1] = APP_PWM_POLARITY_ACTIVE_HIGH;
/* 初始化和使能PWM. */
err_code = app_pwm_init(&PWM1,&pwm1_cfg,pwm_ready_callback);
APP_ERROR_CHECK(err_code);
app_pwm_enable(&PWM1);//使能PWM
uint32_t value;
while(1)
{
for (uint8_t i = 0; i < 40; ++i)
{
value = (i < 20) ? (i * 5) : (100 - (i - 20) * 5);
ready_flag = false;
/*
need 1:
ret_code_t app_pwm_channel_duty_set(app_pwm_t const * const p_instance,
uint8_t channel, app_pwm_duty_t duty)
{
uint32_t ticks = ((uint32_t)app_pwm_cycle_ticks_get(p_instance) * (uint32_t)duty) / 100UL;
return app_pwm_channel_duty_ticks_set(p_instance, channel, ticks);
}
need 2:
ret_code_t app_pwm_channel_duty_ticks_set(app_pwm_t const * const p_instance,
uint8_t channel,
uint16_t ticks)
{
app_pwm_cb_t * p_cb = p_instance->p_cb;
app_pwm_channel_cb_t * p_ch_cb = &p_cb->channels_cb[channel];
ASSERT(channel < APP_PWM_CHANNELS_PER_INSTANCE);
ASSERT(p_ch_cb->initialized == APP_PWM_CHANNEL_INITIALIZED);
if (p_cb->state != NRFX_DRV_STATE_POWERED_ON)
{
return NRF_ERROR_INVALID_STATE;
}
if (ticks == p_ch_cb->pulsewidth)
{
if (p_cb->p_ready_callback)
{
p_cb->p_ready_callback(p_instance->p_timer->instance_id);
}
return NRF_SUCCESS; // No action required.
}
if (app_pwm_busy_check(p_instance))
{
return NRF_ERROR_BUSY; // PPI channels for synchronization are still in use.
}
m_pwm_busy[p_instance->p_timer->instance_id] = BUSY_STATE_CHANGING;
// Set new value.
pwm_transition(p_instance, channel, ticks);
if (p_instance->p_cb->p_ready_callback)
{
//PWM ready interrupt handler will be called after one full period.
m_pwm_ready_counter[p_instance->p_timer->instance_id][channel] = 2;
pwm_irq_enable(p_instance);
}
return NRF_SUCCESS;
}
设置占空比 - 不停设置直到PWM准备好.
app_pwm_channel_duty_set(&PWM1, 0, value)
第二个参数是通道????
*/
while (app_pwm_channel_duty_set(&PWM1, 0, value) == NRF_ERROR_BUSY);
/* 等待回调 */
while(!ready_flag);
APP_ERROR_CHECK(app_pwm_channel_duty_set(&PWM1, 1, value));
nrf_delay_ms(25);
}
}
}
app_pwm_channel_duty_set(&PWM1, 0, value) 第二个参数是通道??? 这个怎么理解呢?好像是PPI通道,内部是通过PPI实现的 ,最终找到了下面这个结构体:
/**
* @brief PWM channel instance
*
* This structure holds all data needed by a single PWM channel.
*/
typedef struct
{
uint32_t gpio_pin; //!< Pin that is used by this PWM channel.
uint32_t pulsewidth; //!< The copy of duty currently set (in ticks).
nrf_ppi_channel_t ppi_channels[2]; //!< PPI channels used by the PWM channel to clear and set the output.
app_pwm_polarity_t polarity; //!< The active state of the pin.
uint8_t initialized; //!< The internal information if the selected channel was initialized.
} app_pwm_channel_cb_t;
二、PPI 引用输入捕获
下面的例子用软件实现了一个PWM输出,然后通过GPTPTE 的电平变换事件,捕获变化的次数,用到了定时器,PPI,串口,PWM等功能:
nrf_ppi_channel_t my_ppi_channel=NRF_PPI_CHANNEL0 ;
//定义Timer0的驱动程序实例。驱动程序实例的ID对应Timer的ID,如NRF_DRV_TIMER_INSTANCE(0)对应Timer0
const nrf_drv_timer_t timer0 = NRF_DRV_TIMER_INSTANCE(0);
//IO口
#define INPUT 2
#define OUTPUT 3
APP_PWM_INSTANCE(PWM1,1); // 创建一个使用定时器1产生PWM波的实例
static volatile bool ready_flag; // 使用一个标志位表示PWM状态
void pwm_ready_callback(uint32_t pwm_id) // PWM回调功能
{
ready_flag = true;
}
void my_timer_event_handler(nrf_timer_event_t event_type, void* p_context){
}
void uart_error_handle(app_uart_evt_t * p_event){
if (p_event->evt_type == APP_UART_COMMUNICATION_ERROR){
APP_ERROR_HANDLER(p_event->data.error_communication);
}
else if (p_event->evt_type == APP_UART_FIFO_ERROR){
APP_ERROR_HANDLER(p_event->data.error_code);
}
}
/*PWM部分和上面的例程一样*/
void PWM_OUT(uint32_t value)
{
ret_code_t err_code;
/* PWM, 周期0.5S, 通过 OUTPUT 管脚输出. */
app_pwm_config_t pwm1_cfg = APP_PWM_DEFAULT_CONFIG_1CH(500000L, OUTPUT);
/* 初始化和使能PWM. */
err_code = app_pwm_init(&PWM1,&pwm1_cfg,pwm_ready_callback);
APP_ERROR_CHECK(err_code);
app_pwm_enable(&PWM1);//使能PWM
while (app_pwm_channel_duty_set(&PWM1, 0, value) == NRF_ERROR_BUSY);
}
//设置管脚为gpiote输入 作为事件
static void gpio_init(void)
{
ret_code_t err_code;
//GPIOE驱动初始化
err_code = nrf_drv_gpiote_init();
APP_ERROR_CHECK(err_code);
/*
Macro for configuring a pin to use a GPIO IN or PORT EVENT to detect high-to-low transition.
默认是高电平,event 事件 是 由 高到低,
所以采集的是第一次变化的事件
采集就是占空比变化的次数
*/
nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_HITOLO(1);//
in_config.pull = NRF_GPIO_PIN_PULLUP;
//GPIOE输入初始化,设置触发输入中断
err_code = nrf_drv_gpiote_in_init(INPUT, &in_config, NULL);
APP_ERROR_CHECK(err_code);
//设置GPIOE输入事件使能
nrf_drv_gpiote_in_event_enable(INPUT, 1);
}
void timer0_init(void)
{
// NRF_TIMER0->MODE = TIMER_MODE_MODE_Counter; // Set the timer in counter Mode.
// NRF_TIMER0->BITMODE = TIMER_BITMODE_BITMODE_24Bit; // 24-bit mode.
uint32_t err_code = NRF_SUCCESS;
//定义定时器配置结构体,并使用默认配置参数初始化结构体
nrfx_timer_config_t timer_cfg = NRFX_TIMER_DEFAULT_CONFIG;
//Timer0配置为计数模式
timer_cfg.mode = NRF_TIMER_MODE_COUNTER;
//初始化定时器,定时器工作于计数模式时,没有事件,所以无需回调函数
err_code = nrfx_timer_init(&timer0, &timer_cfg, my_timer_event_handler);
//err_code = nrfx_timer_init(&TIMER_COUNTER, &timer_cfg, NULL);
APP_ERROR_CHECK(err_code);
}
static void ppi_init(void)
{
uint32_t err_code = NRF_SUCCESS;
err_code = nrf_drv_ppi_init();
APP_ERROR_CHECK(err_code);
// Configure 2nd available PPI channel to start timer0 counter at TIMER2 COMPARE[0] match, which is every odd number of seconds.
err_code = nrf_drv_ppi_channel_alloc(&my_ppi_channel);
APP_ERROR_CHECK(err_code);
/*
一旦有了GPIOTE事件,
就触发计数器+1的任务
*/
err_code = nrf_drv_ppi_channel_assign(my_ppi_channel,
nrf_drv_gpiote_in_event_addr_get(INPUT),
nrf_drv_timer_task_address_get(&timer0, NRF_TIMER_TASK_COUNT));
APP_ERROR_CHECK(err_code);
err_code = nrf_drv_ppi_channel_enable(my_ppi_channel);
APP_ERROR_CHECK(err_code);
}
int main(void)
{
uint32_t err_code;
timer0_init(); // Timer used to blink the LEDs.
gpio_init();
ppi_init(); // PPI to redirect the event to timer start/stop tasks.
PWM_OUT(10);
const app_uart_comm_params_t comm_params =
{
RX_PIN_NUMBER,
TX_PIN_NUMBER,
RTS_PIN_NUMBER,
CTS_PIN_NUMBER,
UART_HWFC,
false,
NRF_UART_BAUDRATE_115200
NRF_UARTE_BAUDRATE_115200
};
APP_UART_FIFO_INIT(&comm_params,
UART_RX_BUF_SIZE,
UART_TX_BUF_SIZE,
uart_error_handle,
APP_IRQ_PRIORITY_LOWEST,
err_code);
APP_ERROR_CHECK(err_code);
/*
Enable constant latency mode
PPI模式下最好启用这个持续供电模式
*/
NRF_POWER->TASKS_CONSTLAT = 1;
//启动定时器
nrf_drv_timer_enable(&timer0);
while (1)
{
/*PWM周期为0.5S,所以计数器的值每1S会增加2次*/
printf("Current cout: %d", (int)nrf_drv_timer_capture(&timer0,NRF_TIMER_CC_CHANNEL1));
...
}
}