diff --git a/hal/targets.json b/hal/targets.json index 374370dcb2..d9aaa50c6f 100644 --- a/hal/targets.json +++ b/hal/targets.json @@ -1773,6 +1773,6 @@ "NRF52_PAN_62", "NRF52_PAN_63" ], - "device_has": ["ANALOGIN", "ERROR_PATTERN", "I2C", "I2C_ASYNCH", "INTERRUPTIN", "LOWPOWERTIMER", "PORTIN", "PORTINOUT", "PORTOUT", "RTC", "SERIAL", "SERIAL_ASYNCH", "SLEEP", "SPI", "SPI_ASYNCH", "SPISLAVE"] + "device_has": ["ANALOGIN", "ERROR_PATTERN", "I2C", "I2C_ASYNCH", "INTERRUPTIN", "LOWPOWERTIMER", "PORTIN", "PORTINOUT", "PORTOUT", "PWMOUT", "RTC", "SERIAL", "SERIAL_ASYNCH", "SLEEP", "SPI", "SPI_ASYNCH", "SPISLAVE"] } } diff --git a/hal/targets/hal/TARGET_NORDIC/TARGET_NRF5/TARGET_MCU_NRF52832/pwmout_api.c b/hal/targets/hal/TARGET_NORDIC/TARGET_NRF5/TARGET_MCU_NRF52832/pwmout_api.c index c58d13a4d9..43cb0cad2d 100644 --- a/hal/targets/hal/TARGET_NORDIC/TARGET_NRF5/TARGET_MCU_NRF52832/pwmout_api.c +++ b/hal/targets/hal/TARGET_NORDIC/TARGET_NRF5/TARGET_MCU_NRF52832/pwmout_api.c @@ -44,49 +44,333 @@ #if DEVICE_PWMOUT -// TODO - provide an implementation. +#include "app_util_platform.h" +#include "nrf_drv_pwm.h" -//#include "nrf_pwm.h" +#define MAX_PWM_COUNTERTOP (0x7FFF) // 0x7FFF is the max of COUNTERTOP value for the PWM peripherial of the nRF52. +#define MAX_PWM_PERIOD_US (MAX_PWM_COUNTERTOP * 8) // PWM hw is driven by 16 MHz clock, hence the tick is 1_us/16, + // and 128 is the max prescaler value. +#define MAX_PWM_PERIOD_MS ((MAX_PWM_PERIOD_US / 1000) + 1) // approximations advance +#define MAX_PWM_PERIOD_S ((MAX_PWM_PERIOD_US / 1000000) + 1) // approximations advance + +#define PWM_INSTANCE_COUNT (PWM_COUNT) // import from the nrf_drv_config.h file + +///> instances of nRF52 PWM driver +static const nrf_drv_pwm_t m_pwm_driver[PWM_INSTANCE_COUNT] = +{ +#if PWM0_ENABLED + NRF_DRV_PWM_INSTANCE(0), +#endif +#if PWM1_ENABLED + NRF_DRV_PWM_INSTANCE(1), +#endif +#if PWM2_ENABLED + NRF_DRV_PWM_INSTANCE(2) +#endif +}; + +typedef struct +{ + uint32_t period_us; + uint32_t duty_us; + float duty; +} pwm_signal_t; /// PWM signal description type + +typedef struct +{ + nrf_drv_pwm_t * p_pwm_driver; + pwm_signal_t signal; + volatile nrf_pwm_values_common_t seq_values[1]; +} pwm_t; /// internal PWM instance support type + +static pwm_t m_pwm[PWM_INSTANCE_COUNT] = +{ +#if PWM0_ENABLED + {.p_pwm_driver = NULL}, +#endif +#if PWM1_ENABLED + {.p_pwm_driver = NULL}, +#endif +#if PWM2_ENABLED + {.p_pwm_driver = NULL} +#endif +}; /// Array of internal PWM instances. + +typedef struct +{ + uint16_t period_hwu; // unit related to pwm_clk + uint16_t duty_hwu; // unit related to pwm_clk + nrf_pwm_clk_t pwm_clk; +} pulsewidth_set_t; /// helper type for timing calculations + + +static void internal_pwmout_exe(pwmout_t *obj, bool new_period, bool initialization); + void pwmout_init(pwmout_t *obj, PinName pin) { + uint32_t i; + + for (i = 0; PWM_INSTANCE_COUNT; i++) + { + if (m_pwm[i].p_pwm_driver == NULL) // a driver instance not assigned to the obj? + { + obj->pin = pin; + + obj->pwm_channel = i; + + m_pwm[i].p_pwm_driver = (nrf_drv_pwm_t *) &m_pwm_driver[i]; + m_pwm[i].signal.period_us = 200000; // 0.02 s + m_pwm[i].signal.duty_us = 100000; + m_pwm[i].signal.duty = 0.5f; + + obj->pwm_struct = &m_pwm[i]; + + internal_pwmout_exe(obj, true, true); + + break; + } + } + + MBED_ASSERT(i != PWM_INSTANCE_COUNT); // assert if free instance was not found. } void pwmout_free(pwmout_t *obj) { + nrf_drv_pwm_uninit( (nrf_drv_pwm_t*) obj->pwm_struct ); + + m_pwm[obj->pwm_channel].p_pwm_driver = NULL; } void pwmout_write(pwmout_t *obj, float percent) { + + if (percent < 0) + { + percent = 0; + } + else if (percent > 1) + { + percent = 1; + } + + pwm_signal_t * p_pwm_signal = &(((pwm_t*)obj->pwm_struct)->signal); + + p_pwm_signal->duty = percent; + + int us = (((int)p_pwm_signal->period_us) * percent); + + pwmout_pulsewidth_us(obj, us); } float pwmout_read(pwmout_t *obj) { - return 0.0f; + pwm_signal_t * p_pwm_signal = &(((pwm_t*)obj->pwm_struct)->signal); + + return (float)p_pwm_signal->duty_us / (float)p_pwm_signal->period_us; } void pwmout_period(pwmout_t *obj, float seconds) { + // raught saturation < 0, quasi-max> + if (seconds > MAX_PWM_PERIOD_S) + { + seconds = MAX_PWM_PERIOD_S; + } + else if (seconds < 0) + { + seconds = 0; // f. pwmout_period_us will set period to min. value + } + + int us = seconds * 1000000; + + pwmout_period_us(obj, us); } void pwmout_period_ms(pwmout_t *obj, int ms) { + // reught saturation < 0, quasi-max> + if (ms > MAX_PWM_PERIOD_MS) + { + ms = MAX_PWM_PERIOD_MS; + } + else if (ms < 0) + { + ms = 0; // f. pwmout_period_us will set period to min. value + } + + int us = ms * 1000; + + pwmout_period_us(obj, us); } + void pwmout_period_us(pwmout_t *obj, int us) { + pwm_signal_t * p_pwm_signal = &(((pwm_t*)obj->pwm_struct)->signal); + + // saturation <1, real-max> + if (us > MAX_PWM_PERIOD_US) + { + us = MAX_PWM_PERIOD_US; + } + else if (us < 1) + { + us = 1; + } + + p_pwm_signal->duty_us = (int)((float)us * p_pwm_signal->duty); + + p_pwm_signal->period_us = us; + + internal_pwmout_exe(obj, true, false); } void pwmout_pulsewidth(pwmout_t *obj, float seconds) { + // raught saturation < 0, quasi-max> + if (seconds > MAX_PWM_PERIOD_S) + { + seconds = MAX_PWM_PERIOD_S; + } + else if (seconds < 0) + { + seconds = 0; + } + + int us = seconds * 1000000; + + pwmout_pulsewidth_us(obj,us); } void pwmout_pulsewidth_ms(pwmout_t *obj, int ms) { + // raught saturation < 0, quasi-max> + if (ms > MAX_PWM_PERIOD_MS) + { + ms = MAX_PWM_PERIOD_MS; + } + else if (ms < 0) + { + ms = 0; + } + + int us = ms * 1000; + + pwmout_pulsewidth_us(obj, us); } void pwmout_pulsewidth_us(pwmout_t *obj, int us) { + // saturation <0, real-max> + if (us > MAX_PWM_PERIOD_US) + { + us = MAX_PWM_PERIOD_US; + } + else if (us < 0) + { + us = 0; + } + + pwm_signal_t * p_pwm_signal = &(((pwm_t*)obj->pwm_struct)->signal); + + p_pwm_signal->duty_us = us; + p_pwm_signal->duty = us / p_pwm_signal->period_us; + + internal_pwmout_exe(obj, false, false); +} + + + + + + +static ret_code_t pulsewidth_us_set_get(int period_hwu, int duty_hwu, pulsewidth_set_t * p_settings) +{ + uint16_t div; + nrf_pwm_clk_t pwm_clk = NRF_PWM_CLK_16MHz; + + for(div = 1; div <= 128 ; div <<= 1) // 128 is the maximum of clock prescaler for PWM peripherial + { + if (MAX_PWM_COUNTERTOP >= period_hwu) + { + p_settings->period_hwu = period_hwu; // unit [us/16 * div] + p_settings->duty_hwu = duty_hwu; // unit [us/16 * div] + p_settings->pwm_clk = pwm_clk; + + return NRF_SUCCESS; + } + + period_hwu >>= 1; + duty_hwu >>= 1; + pwm_clk++; + } + + return NRF_ERROR_INVALID_PARAM; +} + + +static void internal_pwmout_exe(pwmout_t *obj, bool new_period, bool initialization) +{ + pulsewidth_set_t pulsewidth_set; + pwm_signal_t * p_pwm_signal; + nrf_drv_pwm_t * p_pwm_driver; + ret_code_t ret_code; + + p_pwm_signal = &(((pwm_t*)obj->pwm_struct)->signal); + + if (NRF_SUCCESS == pulsewidth_us_set_get(p_pwm_signal->period_us * 16, // base clk for PWM is 16 MHz + p_pwm_signal->duty_us * 16, // base clk for PWM is 16 MHz + &pulsewidth_set)) + { + p_pwm_driver = (((pwm_t*)obj->pwm_struct)->p_pwm_driver); + + const nrf_pwm_sequence_t seq = + { + .values.p_common = (nrf_pwm_values_common_t*) (((pwm_t*)obj->pwm_struct)->seq_values), + .length = 1, + .repeats = 0, + .end_delay = 0 + }; + + (((pwm_t*)obj->pwm_struct)->seq_values)[0] = pulsewidth_set.duty_hwu | 0x8000; + + if (new_period) + { + nrf_drv_pwm_config_t config0 = + { + .output_pins = + { + obj->pin | NRF_DRV_PWM_PIN_INVERTED, // channel 0 + NRF_DRV_PWM_PIN_NOT_USED, // channel 1 + NRF_DRV_PWM_PIN_NOT_USED, // channel 2 + NRF_DRV_PWM_PIN_NOT_USED, // channel 3 + }, + .irq_priority = APP_IRQ_PRIORITY_LOW, + .base_clock = pulsewidth_set.pwm_clk, + .count_mode = NRF_PWM_MODE_UP, + .top_value = pulsewidth_set.period_hwu, + .load_mode = NRF_PWM_LOAD_COMMON, + .step_mode = NRF_PWM_STEP_AUTO + }; + + if (!initialization) + { + nrf_drv_pwm_uninit(p_pwm_driver); + } + + ret_code = nrf_drv_pwm_init( p_pwm_driver, &config0, NULL); + + MBED_ASSERT(ret_code == NRF_SUCCESS); // assert if free instance was not found. + } + + nrf_drv_pwm_simple_playback(p_pwm_driver, &seq, 0, NRF_DRV_PWM_FLAG_LOOP); + } + else + { + MBED_ASSERT(0); // force assertion + } + } #endif // DEVICE_PWMOUT diff --git a/hal/targets/hal/TARGET_NORDIC/TARGET_NRF5/TARGET_MCU_NRF52832/sdk/driver_nrf/pwm/nrf_drv_pwm.c b/hal/targets/hal/TARGET_NORDIC/TARGET_NRF5/TARGET_MCU_NRF52832/sdk/driver_nrf/pwm/nrf_drv_pwm.c new file mode 100644 index 0000000000..df4ec49659 --- /dev/null +++ b/hal/targets/hal/TARGET_NORDIC/TARGET_NRF5/TARGET_MCU_NRF52832/sdk/driver_nrf/pwm/nrf_drv_pwm.c @@ -0,0 +1,350 @@ +/* Copyright (c) 2015 Nordic Semiconductor. All Rights Reserved. + * + * The information contained herein is property of Nordic Semiconductor ASA. + * Terms and conditions of usage are described in detail in NORDIC + * SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT. + * + * Licensees are granted free, non-transferable use of the information. NO + * WARRANTY of ANY KIND is provided. This heading must NOT be removed from + * the file. + * + */ + +#include +#include "nrf_drv_pwm.h" +#include "nrf_drv_common.h" +#include "nrf_gpio.h" +#include "app_util_platform.h" + +#if (PWM_COUNT == 0) + #error "No PWM instances enabled in the driver configuration file." +#endif + + +// Control block - driver instance local data. +typedef struct +{ + nrf_drv_pwm_handler_t handler; + nrf_drv_state_t volatile state; +} pwm_control_block_t; +static pwm_control_block_t m_cb[PWM_COUNT]; + +static nrf_drv_pwm_config_t const m_default_config[PWM_COUNT] = { +#if PWM0_ENABLED + NRF_DRV_PWM_DEFAULT_CONFIG(0), +#endif +#if PWM1_ENABLED + NRF_DRV_PWM_DEFAULT_CONFIG(1), +#endif +#if PWM2_ENABLED + NRF_DRV_PWM_DEFAULT_CONFIG(2), +#endif +}; + + +static void configure_pins(nrf_drv_pwm_t const * const p_instance, + nrf_drv_pwm_config_t const * p_config) +{ + uint32_t out_pins[NRF_PWM_CHANNEL_COUNT]; + uint8_t i; + + for (i = 0; i < NRF_PWM_CHANNEL_COUNT; ++i) + { + uint8_t output_pin = p_config->output_pins[i]; + if (output_pin != NRF_DRV_PWM_PIN_NOT_USED) + { + bool inverted = output_pin & NRF_DRV_PWM_PIN_INVERTED; + out_pins[i] = output_pin & ~NRF_DRV_PWM_PIN_INVERTED; + + if (inverted) + { + nrf_gpio_pin_set(out_pins[i]); + } + else + { + nrf_gpio_pin_clear(out_pins[i]); + } + + nrf_gpio_cfg_output(out_pins[i]); + } + else + { + out_pins[i] = NRF_PWM_PIN_NOT_CONNECTED; + } + } + + nrf_pwm_pins_set(p_instance->p_registers, out_pins); +} + + +ret_code_t nrf_drv_pwm_init(nrf_drv_pwm_t const * const p_instance, + nrf_drv_pwm_config_t const * p_config, + nrf_drv_pwm_handler_t handler) +{ + pwm_control_block_t * p_cb = &m_cb[p_instance->drv_inst_idx]; + + if (p_cb->state != NRF_DRV_STATE_UNINITIALIZED) + { + return NRF_ERROR_INVALID_STATE; + } + + if (p_config == NULL) + { + p_config = &m_default_config[p_instance->drv_inst_idx]; + } + + p_cb->handler = handler; + + configure_pins(p_instance, p_config); + + nrf_pwm_enable(p_instance->p_registers); + nrf_pwm_configure(p_instance->p_registers, + p_config->base_clock, p_config->count_mode, p_config->top_value); + nrf_pwm_decoder_set(p_instance->p_registers, + p_config->load_mode, p_config->step_mode); + + nrf_pwm_shorts_set(p_instance->p_registers, 0); + nrf_pwm_int_set(p_instance->p_registers, 0); + nrf_pwm_event_clear(p_instance->p_registers, NRF_PWM_EVENT_LOOPSDONE); + nrf_pwm_event_clear(p_instance->p_registers, NRF_PWM_EVENT_SEQEND0); + nrf_pwm_event_clear(p_instance->p_registers, NRF_PWM_EVENT_SEQEND1); + nrf_pwm_event_clear(p_instance->p_registers, NRF_PWM_EVENT_STOPPED); + + if (p_cb->handler) + { + nrf_drv_common_irq_enable(nrf_drv_get_IRQn(p_instance->p_registers), + p_config->irq_priority); + } + + p_cb->state = NRF_DRV_STATE_INITIALIZED; + + return NRF_SUCCESS; +} + + +void nrf_drv_pwm_uninit(nrf_drv_pwm_t const * const p_instance) +{ + pwm_control_block_t * p_cb = &m_cb[p_instance->drv_inst_idx]; + ASSERT(p_cb->state != NRF_DRV_STATE_UNINITIALIZED); + + nrf_drv_common_irq_disable(nrf_drv_get_IRQn(p_instance->p_registers)); + + nrf_pwm_disable(p_instance->p_registers); + + p_cb->state = NRF_DRV_STATE_UNINITIALIZED; +} + + +static void start_playback(nrf_drv_pwm_t const * const p_instance, + pwm_control_block_t * p_cb, + uint8_t flags, + nrf_pwm_task_t starting_task) +{ + p_cb->state = NRF_DRV_STATE_POWERED_ON; + + if (p_cb->handler) + { + // The notification about finished playback is by default enabled, but + // this can be suppressed. The notification that the peripheral has been + // stopped is always enable. + uint32_t int_mask = NRF_PWM_INT_LOOPSDONE_MASK | + NRF_PWM_INT_STOPPED_MASK; + + if (flags & NRF_DRV_PWM_FLAG_SIGNAL_END_SEQ0) + { + int_mask |= NRF_PWM_INT_SEQEND0_MASK; + } + if (flags & NRF_DRV_PWM_FLAG_SIGNAL_END_SEQ1) + { + int_mask |= NRF_PWM_INT_SEQEND1_MASK; + } + if (flags & NRF_DRV_PWM_FLAG_NO_EVT_FINISHED) + { + int_mask &= ~NRF_PWM_INT_LOOPSDONE_MASK; + } + + nrf_pwm_int_set(p_instance->p_registers, int_mask); + } + + nrf_pwm_event_clear(p_instance->p_registers, NRF_PWM_EVENT_STOPPED); + + nrf_pwm_task_trigger(p_instance->p_registers, starting_task); +} + + +void nrf_drv_pwm_simple_playback(nrf_drv_pwm_t const * const p_instance, + nrf_pwm_sequence_t const * p_sequence, + uint16_t playback_count, + uint32_t flags) +{ + pwm_control_block_t * p_cb = &m_cb[p_instance->drv_inst_idx]; + ASSERT(p_cb->state != NRF_DRV_STATE_UNINITIALIZED); + ASSERT(playback_count > 0); + ASSERT(nrf_drv_is_in_RAM(p_sequence->values.p_raw)); + + // To take advantage of the looping mechanism, we need to use both sequences + // (single sequence can be played back only once). + nrf_pwm_sequence_set(p_instance->p_registers, 0, p_sequence); + nrf_pwm_sequence_set(p_instance->p_registers, 1, p_sequence); + bool odd = (playback_count & 1); + nrf_pwm_loop_set(p_instance->p_registers, playback_count/2 + (odd ? 1 : 0)); + + uint32_t shorts_mask; + if (flags & NRF_DRV_PWM_FLAG_STOP) + { + shorts_mask = NRF_PWM_SHORT_LOOPSDONE_STOP_MASK; + } + else if (flags & NRF_DRV_PWM_FLAG_LOOP) + { + shorts_mask = odd ? NRF_PWM_SHORT_LOOPSDONE_SEQSTART1_MASK + : NRF_PWM_SHORT_LOOPSDONE_SEQSTART0_MASK; + } + else + { + shorts_mask = 0; + } + nrf_pwm_shorts_set(p_instance->p_registers, shorts_mask); + + start_playback(p_instance, p_cb, flags, odd ? NRF_PWM_TASK_SEQSTART1 + : NRF_PWM_TASK_SEQSTART0); +} + + +void nrf_drv_pwm_complex_playback(nrf_drv_pwm_t const * const p_instance, + nrf_pwm_sequence_t const * p_sequence_0, + nrf_pwm_sequence_t const * p_sequence_1, + uint16_t playback_count, + uint32_t flags) +{ + pwm_control_block_t * p_cb = &m_cb[p_instance->drv_inst_idx]; + ASSERT(p_cb->state != NRF_DRV_STATE_UNINITIALIZED); + ASSERT(playback_count > 0); + ASSERT(nrf_drv_is_in_RAM(p_sequence_0->values.p_raw)); + ASSERT(nrf_drv_is_in_RAM(p_sequence_1->values.p_raw)); + + nrf_pwm_sequence_set(p_instance->p_registers, 0, p_sequence_0); + nrf_pwm_sequence_set(p_instance->p_registers, 1, p_sequence_1); + nrf_pwm_loop_set(p_instance->p_registers, playback_count); + + uint32_t shorts_mask; + if (flags & NRF_DRV_PWM_FLAG_STOP) + { + shorts_mask = NRF_PWM_SHORT_LOOPSDONE_STOP_MASK; + } + else if (flags & NRF_DRV_PWM_FLAG_LOOP) + { + shorts_mask = NRF_PWM_SHORT_LOOPSDONE_SEQSTART0_MASK; + } + else + { + shorts_mask = 0; + } + nrf_pwm_shorts_set(p_instance->p_registers, shorts_mask); + + start_playback(p_instance, p_cb, flags, NRF_PWM_TASK_SEQSTART0); +} + + +bool nrf_drv_pwm_stop(nrf_drv_pwm_t const * const p_instance, + bool wait_until_stopped) +{ + ASSERT(m_cb[p_instance->drv_inst_idx].state != NRF_DRV_STATE_UNINITIALIZED); + + if (nrf_drv_pwm_is_stopped(p_instance)) + { + return true; + } + + nrf_pwm_task_trigger(p_instance->p_registers, NRF_PWM_TASK_STOP); + + do { + if (nrf_drv_pwm_is_stopped(p_instance)) + { + return true; + } + } while (wait_until_stopped); + + return false; +} + + +bool nrf_drv_pwm_is_stopped(nrf_drv_pwm_t const * const p_instance) +{ + pwm_control_block_t * p_cb = &m_cb[p_instance->drv_inst_idx]; + ASSERT(p_cb->state != NRF_DRV_STATE_UNINITIALIZED); + + // If the event handler is used (interrupts are enabled), the state will + // be changed in interrupt handler when the STOPPED event occurs. + if (p_cb->state != NRF_DRV_STATE_POWERED_ON) + { + return true; + } + // If interrupts are disabled, we must check the STOPPED event here. + if (nrf_pwm_event_check(p_instance->p_registers, NRF_PWM_EVENT_STOPPED)) + { + p_cb->state = NRF_DRV_STATE_INITIALIZED; + return true; + } + + return false; +} + + +static void irq_handler(NRF_PWM_Type * p_pwm, pwm_control_block_t * p_cb) +{ + ASSERT(p_cb->handler); + + // The SEQEND0 and SEQEND1 events are only handled when the user asked for + // it (by setting proper flags when starting the playback). + if (nrf_pwm_int_enable_check(p_pwm, NRF_PWM_INT_SEQEND0_MASK) && + nrf_pwm_event_check(p_pwm, NRF_PWM_EVENT_SEQEND0)) + { + nrf_pwm_event_clear(p_pwm, NRF_PWM_EVENT_SEQEND0); + p_cb->handler(NRF_DRV_PWM_EVT_END_SEQ0); + } + if (nrf_pwm_int_enable_check(p_pwm, NRF_PWM_INT_SEQEND1_MASK) && + nrf_pwm_event_check(p_pwm, NRF_PWM_EVENT_SEQEND1)) + { + nrf_pwm_event_clear(p_pwm, NRF_PWM_EVENT_SEQEND1); + p_cb->handler(NRF_DRV_PWM_EVT_END_SEQ1); + } + + // The LOOPSDONE event is handled by default, but this can be disabled. + if (nrf_pwm_int_enable_check(p_pwm, NRF_PWM_INT_LOOPSDONE_MASK) && + nrf_pwm_event_check(p_pwm, NRF_PWM_EVENT_LOOPSDONE)) + { + nrf_pwm_event_clear(p_pwm, NRF_PWM_EVENT_LOOPSDONE); + p_cb->handler(NRF_DRV_PWM_EVT_FINISHED); + } + + if (nrf_pwm_event_check(p_pwm, NRF_PWM_EVENT_STOPPED)) + { + nrf_pwm_event_clear(p_pwm, NRF_PWM_EVENT_STOPPED); + + p_cb->state = NRF_DRV_STATE_INITIALIZED; + + p_cb->handler(NRF_DRV_PWM_EVT_STOPPED); + } +} + + +#if PWM0_ENABLED +void PWM0_IRQHandler(void) +{ + irq_handler(NRF_PWM0, &m_cb[PWM0_INSTANCE_INDEX]); +} +#endif + +#if PWM1_ENABLED +void PWM1_IRQHandler(void) +{ + irq_handler(NRF_PWM1, &m_cb[PWM1_INSTANCE_INDEX]); +} +#endif + +#if PWM2_ENABLED +void PWM2_IRQHandler(void) +{ + irq_handler(NRF_PWM2, &m_cb[PWM2_INSTANCE_INDEX]); +} +#endif diff --git a/hal/targets/hal/TARGET_NORDIC/TARGET_NRF5/TARGET_MCU_NRF52832/sdk/driver_nrf/pwm/nrf_drv_pwm.h b/hal/targets/hal/TARGET_NORDIC/TARGET_NRF5/TARGET_MCU_NRF52832/sdk/driver_nrf/pwm/nrf_drv_pwm.h new file mode 100644 index 0000000000..f8b1c1bc97 --- /dev/null +++ b/hal/targets/hal/TARGET_NORDIC/TARGET_NRF5/TARGET_MCU_NRF52832/sdk/driver_nrf/pwm/nrf_drv_pwm.h @@ -0,0 +1,426 @@ +/* Copyright (c) 2015 Nordic Semiconductor. All Rights Reserved. + * + * The information contained herein is property of Nordic Semiconductor ASA. + * Terms and conditions of usage are described in detail in NORDIC + * SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT. + * + * Licensees are granted free, non-transferable use of the information. NO + * WARRANTY of ANY KIND is provided. This heading must NOT be removed from + * the file. + * + */ + +/**@file + * @addtogroup nrf_pwm PWM HAL and driver + * @ingroup nrf_drivers + * @brief @tagAPI52 Pulse Width Modulation (PWM) module APIs. + * + * @defgroup nrf_drv_pwm PWM driver + * @{ + * @ingroup nrf_pwm + * @brief @tagAPI52 Pulse Width Modulation (PWM) module driver. + */ + + +#ifndef NRF_DRV_PWM_H__ +#define NRF_DRV_PWM_H__ + +#include "nordic_common.h" +#include "nrf_drv_config.h" +#include "nrf_pwm.h" +#include "sdk_errors.h" + + +/** + * @brief PWM driver instance data structure. + */ +typedef struct +{ + NRF_PWM_Type * p_registers; ///< Pointer to the structure with PWM peripheral instance registers. + uint8_t drv_inst_idx; ///< Driver instance index. +} nrf_drv_pwm_t; + +/** + * @brief Macro for creating a PWM driver instance. + */ +#define NRF_DRV_PWM_INSTANCE(id) \ +{ \ + .p_registers = CONCAT_2(NRF_PWM, id), \ + .drv_inst_idx = CONCAT_3(PWM, id, _INSTANCE_INDEX), \ +} + + +/** + * @brief This value can be provided instead of a pin number for any channel + * to specify that its output is not used and therefore does not need + * to be connected to a pin. + */ +#define NRF_DRV_PWM_PIN_NOT_USED 0xFF + +/** + * @brief This value can be added to a pin number to inverse its polarity + * (set idle state = 1). + */ +#define NRF_DRV_PWM_PIN_INVERTED 0x80 + +/** + * @brief PWM driver configuration structure. + */ +typedef struct +{ + uint8_t output_pins[NRF_PWM_CHANNEL_COUNT]; ///< Pin numbers for individual output channels (optional). + /**< Use @ref NRF_DRV_PWM_PIN_NOT_USED + * if a given output channel is not needed. */ + uint8_t irq_priority; ///< Interrupt priority. + nrf_pwm_clk_t base_clock; ///< Base clock frequency. + nrf_pwm_mode_t count_mode; ///< Operating mode of the pulse generator counter. + uint16_t top_value; ///< Value up to which the pulse generator counter counts. + nrf_pwm_dec_load_t load_mode; ///< Mode of loading sequence data from RAM. + nrf_pwm_dec_step_t step_mode; ///< Mode of advancing the active sequence. +} nrf_drv_pwm_config_t; + +/** + * @brief PWM driver default configuration. + */ +#define NRF_DRV_PWM_DEFAULT_CONFIG(id) \ +{ \ + .output_pins = { CONCAT_3(PWM, id, _CONFIG_OUT0_PIN), \ + CONCAT_3(PWM, id, _CONFIG_OUT1_PIN), \ + CONCAT_3(PWM, id, _CONFIG_OUT2_PIN), \ + CONCAT_3(PWM, id, _CONFIG_OUT3_PIN) }, \ + .irq_priority = CONCAT_3(PWM, id, _CONFIG_IRQ_PRIORITY), \ + .base_clock = CONCAT_3(PWM, id, _CONFIG_BASE_CLOCK), \ + .count_mode = CONCAT_3(PWM, id, _CONFIG_COUNT_MODE), \ + .top_value = CONCAT_3(PWM, id, _CONFIG_TOP_VALUE), \ + .load_mode = CONCAT_3(PWM, id, _CONFIG_LOAD_MODE), \ + .step_mode = CONCAT_3(PWM, id, _CONFIG_STEP_MODE), \ +} + + +/** + * @brief PWM flags providing additional playback options. + */ +typedef enum +{ + NRF_DRV_PWM_FLAG_STOP = 0x01, /**< When the requested playback is finished, + the peripheral should be stopped. + @note The STOP task is triggered when + the last value of the final sequence is + loaded from RAM, and the peripheral stops + at the end of the current PWM period. + For sequences with configured repeating + of duty cycle values, this might result in + less than the requested number of repeats + of the last value. */ + NRF_DRV_PWM_FLAG_LOOP = 0x02, /**< When the requested playback is finished, + it should be started from the beginning. + This flag is ignored if used together + with @ref NRF_DRV_PWM_FLAG_STOP. */ + NRF_DRV_PWM_FLAG_SIGNAL_END_SEQ0 = 0x04, /**< The event handler should be + called when the last value + from sequence 0 is loaded. */ + NRF_DRV_PWM_FLAG_SIGNAL_END_SEQ1 = 0x08, /**< The event handler should be + called when the last value + from sequence 1 is loaded. */ + NRF_DRV_PWM_FLAG_NO_EVT_FINISHED = 0x10, /**< The playback finished event + (enabled by default) should be + suppressed. */ +} nrf_drv_pwm_flag_t; + + +/** + * @brief PWM driver event type. + */ +typedef enum +{ + NRF_DRV_PWM_EVT_FINISHED, ///< Sequence playback finished. + NRF_DRV_PWM_EVT_END_SEQ0, /**< End of sequence 0 reached. Its data can be + safely modified now. */ + NRF_DRV_PWM_EVT_END_SEQ1, /**< End of sequence 1 reached. Its data can be + safely modified now. */ + NRF_DRV_PWM_EVT_STOPPED, ///< The PWM peripheral has been stopped. +} nrf_drv_pwm_evt_type_t; + +/** + * @brief PWM driver event handler type. + */ +typedef void (* nrf_drv_pwm_handler_t)(nrf_drv_pwm_evt_type_t event_type); + + +/** + * @brief Function for initializing the PWM driver. + * + * @param[in] p_instance Pointer to the driver instance structure. + * @param[in] p_config Pointer to the structure with initial configuration. + * If NULL, the default configuration is used. + * @param[in] handler Event handler provided by the user. If NULL is passed + * instead, event notifications are not done and PWM + * interrupts are disabled. + * + * @retval NRF_SUCCESS If initialization was successful. + * @retval NRF_ERROR_INVALID_STATE If the driver was already initialized. + */ +ret_code_t nrf_drv_pwm_init(nrf_drv_pwm_t const * const p_instance, + nrf_drv_pwm_config_t const * p_config, + nrf_drv_pwm_handler_t handler); + +/** + * @brief Function for uninitializing the PWM driver. + * + * If any sequence playback is in progress, it is stopped immediately. + * + * @param[in] p_instance Pointer to the driver instance structure. + */ +void nrf_drv_pwm_uninit(nrf_drv_pwm_t const * const p_instance); + +/** + * @brief Function for starting a single sequence playback. + * + * To take advantage of the looping mechanism in the PWM peripheral, both + * sequences must be used (single sequence can be played back only once by + * the peripheral). Therefore, the provided sequence is internally set and + * played back as both sequence 0 and sequence 1. Consequently, if end of + * sequence notifications are required, events for both sequences should be + * used (that means that both the @ref NRF_DRV_PWM_FLAG_SIGNAL_END_SEQ0 flag + * and the @ref NRF_DRV_PWM_FLAG_SIGNAL_END_SEQ1 flag should be specified and + * the @ref NRF_DRV_PWM_EVT_END_SEQ0 event and the @ref NRF_DRV_PWM_EVT_END_SEQ1 + * event should be handled in the same way). + * + * @note The array containing the duty cycle values for the specified sequence + * must be in RAM and cannot be allocated on stack. + * For detailed information, see @ref nrf_pwm_sequence_t. + * + * @param[in] p_instance Pointer to the driver instance structure. + * @param[in] p_sequence Sequence to be played back. + * @param[in] playback_count Number of playbacks to be performed (must not be 0). + * @param[in] flags Additional options. Pass any combination of + * @ref nrf_drv_pwm_flag_t "playback flags", or 0 + * for default settings. + */ +void nrf_drv_pwm_simple_playback(nrf_drv_pwm_t const * const p_instance, + nrf_pwm_sequence_t const * p_sequence, + uint16_t playback_count, + uint32_t flags); + +/** + * @brief Function for starting a two-sequence playback. + * + * @note The array containing the duty cycle values for the specified sequence + * must be in RAM and cannot be allocated on stack. + * For detailed information, see @ref nrf_pwm_sequence_t. + * + * @param[in] p_instance Pointer to the driver instance structure. + * @param[in] p_sequence_0 First sequence to be played back. + * @param[in] p_sequence_1 Second sequence to be played back. + * @param[in] playback_count Number of playbacks to be performed (must not be 0). + * @param[in] flags Additional options. Pass any combination of + * @ref nrf_drv_pwm_flag_t "playback flags", or 0 + * for default settings. + */ +void nrf_drv_pwm_complex_playback(nrf_drv_pwm_t const * const p_instance, + nrf_pwm_sequence_t const * p_sequence_0, + nrf_pwm_sequence_t const * p_sequence_1, + uint16_t playback_count, + uint32_t flags); + +/** + * @brief Function for advancing the active sequence. + * + * This function only applies to @ref NRF_PWM_STEP_TRIGGERED mode. + * + * @param[in] p_instance Pointer to the driver instance structure. + */ +__STATIC_INLINE void nrf_drv_pwm_step(nrf_drv_pwm_t const * const p_instance); + +/** + * @brief Function for stopping the sequence playback. + * + * The playback is stopped at the end of the current PWM period. + * This means that if the active sequence is configured to repeat each duty + * cycle value for a certain number of PWM periods, the last played value + * might appear on the output less times than requested. + * + * @note This function can be instructed to wait until the playback is stopped + * (by setting @p wait_until_stopped to true). Note that, depending on + * the length of the PMW period, this might take a significant amount of + * time. Alternatively, the @ref nrf_drv_pwm_is_stopped function can be + * used to poll the status, or the @ref NRF_DRV_PWM_EVT_STOPPED event can + * be used to get the notification when the playback is stopped, provided + * the event handler is defined. + * + * @param[in] p_instance Pointer to the driver instance structure. + * @param[in] wait_until_stopped If true, the function will not return until + * the playback is stopped. + * + * @retval true If the PWM peripheral is stopped. + * @retval false If the PWM peripheral is not stopped. + */ +bool nrf_drv_pwm_stop(nrf_drv_pwm_t const * const p_instance, + bool wait_until_stopped); + +/** + * @brief Function for checking the status of the PWM peripheral. + * + * @param[in] p_instance Pointer to the driver instance structure. + * + * @retval true If the PWM peripheral is stopped. + * @retval false If the PWM peripheral is not stopped. + */ +bool nrf_drv_pwm_is_stopped(nrf_drv_pwm_t const * const p_instance); + +/** + * @brief Function for updating the sequence data during playback. + * + * @param[in] p_instance Pointer to the driver instance structure. + * @param[in] seq_id Identifier of the sequence (0 or 1). + * @param[in] p_sequence Pointer to the new sequence definition. + */ +__STATIC_INLINE void nrf_drv_pwm_sequence_update( + nrf_drv_pwm_t const * const p_instance, + uint8_t seq_id, + nrf_pwm_sequence_t const * p_sequence); + +/** + * @brief Function for updating the pointer to the duty cycle values + * in the specified sequence during playback. + * + * @param[in] p_instance Pointer to the driver instance structure. + * @param[in] seq_id Identifier of the sequence (0 or 1). + * @param[in] values New pointer to the duty cycle values. + */ +__STATIC_INLINE void nrf_drv_pwm_sequence_values_update( + nrf_drv_pwm_t const * const p_instance, + uint8_t seq_id, + nrf_pwm_values_t values); + +/** + * @brief Function for updating the number of duty cycle values + * in the specified sequence during playback. + * + * @param[in] p_instance Pointer to the driver instance structure. + * @param[in] seq_id Identifier of the sequence (0 or 1). + * @param[in] length New number of the duty cycle values. + */ +__STATIC_INLINE void nrf_drv_pwm_sequence_length_update( + nrf_drv_pwm_t const * const p_instance, + uint8_t seq_id, + uint16_t length); + +/** + * @brief Function for updating the number of repeats for duty cycle values + * in specified sequence during playback. + * + * @param[in] p_instance Pointer to the driver instance structure. + * @param[in] seq_id Identifier of the sequence (0 or 1). + * @param[in] repeats New number of repeats. + */ +__STATIC_INLINE void nrf_drv_pwm_sequence_repeats_update( + nrf_drv_pwm_t const * const p_instance, + uint8_t seq_id, + uint32_t repeats); + +/** + * @brief Function for updating the additional delay after the specified + * sequence during playback. + * + * @param[in] p_instance Pointer to the driver instance structure. + * @param[in] seq_id Identifier of the sequence (0 or 1). + * @param[in] end_delay New end delay value (in PWM periods). + */ +__STATIC_INLINE void nrf_drv_pwm_sequence_end_delay_update( + nrf_drv_pwm_t const * const p_instance, + uint8_t seq_id, + uint32_t end_delay); + +/** + * @brief Function for returning the address of a specified PWM task that can + * be used in PPI module. + * + * @param[in] p_instance Pointer to the driver instance structure. + * @param[in] task Requested task. + * + * @return Task address. + */ +__STATIC_INLINE uint32_t nrf_drv_pwm_task_address_get( + nrf_drv_pwm_t const * const p_instance, + nrf_pwm_task_t task); + +/**@brief Function for returning the address of a specified PWM event that can + * be used in PPI module. + * + * @param[in] p_instance Pointer to the driver instance structure. + * @param[in] event Requested event. + * + * @return Event address. + */ +__STATIC_INLINE uint32_t nrf_drv_pwm_event_address_get( + nrf_drv_pwm_t const * const p_instance, + nrf_pwm_event_t event); + + +#ifndef SUPPRESS_INLINE_IMPLEMENTATION + +__STATIC_INLINE void nrf_drv_pwm_step(nrf_drv_pwm_t const * const p_instance) +{ + nrf_pwm_task_trigger(p_instance->p_registers, NRF_PWM_TASK_NEXTSTEP); +} + +__STATIC_INLINE void nrf_drv_pwm_sequence_update( + nrf_drv_pwm_t const * const p_instance, + uint8_t seq_id, + nrf_pwm_sequence_t const * p_sequence) +{ + nrf_pwm_sequence_set(p_instance->p_registers, seq_id, p_sequence); +} + +__STATIC_INLINE void nrf_drv_pwm_sequence_values_update( + nrf_drv_pwm_t const * const p_instance, + uint8_t seq_id, + nrf_pwm_values_t values) +{ + nrf_pwm_seq_ptr_set(p_instance->p_registers, seq_id, values.p_raw); +} + +__STATIC_INLINE void nrf_drv_pwm_sequence_length_update( + nrf_drv_pwm_t const * const p_instance, + uint8_t seq_id, + uint16_t length) +{ + nrf_pwm_seq_cnt_set(p_instance->p_registers, seq_id, length); +} + +__STATIC_INLINE void nrf_drv_pwm_sequence_repeats_update( + nrf_drv_pwm_t const * const p_instance, + uint8_t seq_id, + uint32_t repeats) +{ + nrf_pwm_seq_refresh_set(p_instance->p_registers, seq_id, repeats); +} + +__STATIC_INLINE void nrf_drv_pwm_sequence_end_delay_update( + nrf_drv_pwm_t const * const p_instance, + uint8_t seq_id, + uint32_t end_delay) +{ + nrf_pwm_seq_end_delay_set(p_instance->p_registers, seq_id, end_delay); +} + +__STATIC_INLINE uint32_t nrf_drv_pwm_task_address_get( + nrf_drv_pwm_t const * const p_instance, + nrf_pwm_task_t task) +{ + return nrf_pwm_task_address_get(p_instance->p_registers, task); +} + +__STATIC_INLINE uint32_t nrf_drv_pwm_event_address_get( + nrf_drv_pwm_t const * const p_instance, + nrf_pwm_event_t event) +{ + return nrf_pwm_event_address_get(p_instance->p_registers, event); +} + +#endif // SUPPRESS_INLINE_IMPLEMENTATION + +#endif // NRF_DRV_PWM_H__ + +/** @} */ diff --git a/hal/targets/hal/TARGET_NORDIC/TARGET_NRF5/TARGET_MCU_NRF52832/sdk/nrf_drv_config.h b/hal/targets/hal/TARGET_NORDIC/TARGET_NRF5/TARGET_MCU_NRF52832/sdk/nrf_drv_config.h index 404f391325..127ef3dbdb 100644 --- a/hal/targets/hal/TARGET_NORDIC/TARGET_NRF5/TARGET_MCU_NRF52832/sdk/nrf_drv_config.h +++ b/hal/targets/hal/TARGET_NORDIC/TARGET_NRF5/TARGET_MCU_NRF52832/sdk/nrf_drv_config.h @@ -198,7 +198,7 @@ #define PWM0_INSTANCE_INDEX 0 #endif -#define PWM1_ENABLED 0 +#define PWM1_ENABLED 1 #if (PWM1_ENABLED == 1) #define PWM1_CONFIG_OUT0_PIN 2 @@ -215,7 +215,7 @@ #define PWM1_INSTANCE_INDEX (PWM0_ENABLED) #endif -#define PWM2_ENABLED 0 +#define PWM2_ENABLED 1 #if (PWM2_ENABLED == 1) #define PWM2_CONFIG_OUT0_PIN 2 diff --git a/hal/targets/hal/TARGET_NORDIC/TARGET_NRF5/sdk/drivers_nrf/hal/nrf_pwm.h b/hal/targets/hal/TARGET_NORDIC/TARGET_NRF5/sdk/drivers_nrf/hal/nrf_pwm.h new file mode 100644 index 0000000000..2dce14fd44 --- /dev/null +++ b/hal/targets/hal/TARGET_NORDIC/TARGET_NRF5/sdk/drivers_nrf/hal/nrf_pwm.h @@ -0,0 +1,665 @@ +/* Copyright (c) 2015 Nordic Semiconductor. All Rights Reserved. + * + * The information contained herein is property of Nordic Semiconductor ASA. + * Terms and conditions of usage are described in detail in NORDIC + * SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT. + * + * Licensees are granted free, non-transferable use of the information. NO + * WARRANTY of ANY KIND is provided. This heading must NOT be removed from + * the file. + * + */ + +/** + * @defgroup nrf_pwm_hal PWM HAL + * @{ + * @ingroup nrf_pwm + * + * @brief @tagAPI52 Hardware access layer for managing the Pulse Width Modulation (PWM) + * peripheral. + */ + +#ifndef NRF_PWM_H__ +#define NRF_PWM_H__ + +#ifdef NRF52 + +#include +#include +#include + +#include "nrf.h" +#include "nrf_assert.h" + + +/** + * @brief This value can be provided as a parameter for the @ref nrf_pwm_pins_set + * function call to specify that a given output channel shall not be + * connected to a physical pin. + */ +#define NRF_PWM_PIN_NOT_CONNECTED 0xFFFFFFFF + +/** + * @brief Number of channels in each PWM instance. + */ +#define NRF_PWM_CHANNEL_COUNT 4 + + +/** + * @brief PWM tasks. + */ +typedef enum +{ + /*lint -save -e30*/ + NRF_PWM_TASK_STOP = offsetof(NRF_PWM_Type, TASKS_STOP), ///< Stops PWM pulse generation on all channels at the end of the current PWM period, and stops the sequence playback. + NRF_PWM_TASK_SEQSTART0 = offsetof(NRF_PWM_Type, TASKS_SEQSTART[0]), ///< Starts playback of sequence 0. + NRF_PWM_TASK_SEQSTART1 = offsetof(NRF_PWM_Type, TASKS_SEQSTART[1]), ///< Starts playback of sequence 1. + NRF_PWM_TASK_NEXTSTEP = offsetof(NRF_PWM_Type, TASKS_NEXTSTEP) ///< Steps by one value in the current sequence if the decoder is set to @ref NRF_PWM_STEP_TRIGGERED mode. + /*lint -restore*/ +} nrf_pwm_task_t; + +/** + * @brief PWM events. + */ +typedef enum +{ + /*lint -save -e30*/ + NRF_PWM_EVENT_STOPPED = offsetof(NRF_PWM_Type, EVENTS_STOPPED), ///< Response to STOP task, emitted when PWM pulses are no longer generated. + NRF_PWM_EVENT_SEQSTARTED0 = offsetof(NRF_PWM_Type, EVENTS_SEQSTARTED[0]), ///< First PWM period started on sequence 0. + NRF_PWM_EVENT_SEQSTARTED1 = offsetof(NRF_PWM_Type, EVENTS_SEQSTARTED[1]), ///< First PWM period started on sequence 1. + NRF_PWM_EVENT_SEQEND0 = offsetof(NRF_PWM_Type, EVENTS_SEQEND[0]), ///< Emitted at the end of every sequence 0 when its last value has been read from RAM. + NRF_PWM_EVENT_SEQEND1 = offsetof(NRF_PWM_Type, EVENTS_SEQEND[1]), ///< Emitted at the end of every sequence 1 when its last value has been read from RAM. + NRF_PWM_EVENT_PWMPERIODEND = offsetof(NRF_PWM_Type, EVENTS_PWMPERIODEND), ///< Emitted at the end of each PWM period. + NRF_PWM_EVENT_LOOPSDONE = offsetof(NRF_PWM_Type, EVENTS_LOOPSDONE) ///< Concatenated sequences have been played the requested number of times. + /*lint -restore*/ +} nrf_pwm_event_t; + +/** + * @brief PWM interrupts. + */ +typedef enum +{ + NRF_PWM_INT_STOPPED_MASK = PWM_INTENSET_STOPPED_Msk, ///< Interrupt on STOPPED event. + NRF_PWM_INT_SEQSTARTED0_MASK = PWM_INTENSET_SEQSTARTED0_Msk, ///< Interrupt on SEQSTARTED[0] event. + NRF_PWM_INT_SEQSTARTED1_MASK = PWM_INTENSET_SEQSTARTED1_Msk, ///< Interrupt on SEQSTARTED[1] event. + NRF_PWM_INT_SEQEND0_MASK = PWM_INTENSET_SEQEND0_Msk, ///< Interrupt on SEQEND[0] event. + NRF_PWM_INT_SEQEND1_MASK = PWM_INTENSET_SEQEND1_Msk, ///< Interrupt on SEQEND[1] event. + NRF_PWM_INT_PWMPERIODEND_MASK = PWM_INTENSET_PWMPERIODEND_Msk, ///< Interrupt on PWMPERIODEND event. + NRF_PWM_INT_LOOPSDONE_MASK = PWM_INTENSET_LOOPSDONE_Msk ///< Interrupt on LOOPSDONE event. +} nrf_pwm_int_mask_t; + +/** + * @brief PWM shortcuts. + */ +typedef enum +{ + NRF_PWM_SHORT_SEQEND0_STOP_MASK = PWM_SHORTS_SEQEND0_STOP_Msk, ///< Shortcut between SEQEND[0] event and STOP task. + NRF_PWM_SHORT_SEQEND1_STOP_MASK = PWM_SHORTS_SEQEND1_STOP_Msk, ///< Shortcut between SEQEND[1] event and STOP task. + NRF_PWM_SHORT_LOOPSDONE_SEQSTART0_MASK = PWM_SHORTS_LOOPSDONE_SEQSTART0_Msk, ///< Shortcut between LOOPSDONE event and SEQSTART[0] task. + NRF_PWM_SHORT_LOOPSDONE_SEQSTART1_MASK = PWM_SHORTS_LOOPSDONE_SEQSTART1_Msk, ///< Shortcut between LOOPSDONE event and SEQSTART[1] task. + NRF_PWM_SHORT_LOOPSDONE_STOP_MASK = PWM_SHORTS_LOOPSDONE_STOP_Msk ///< Shortcut between LOOPSDONE event and STOP task. +} nrf_pwm_short_mask_t; + +/** + * @brief PWM modes of operation. + */ +typedef enum +{ + NRF_PWM_MODE_UP = PWM_MODE_UPDOWN_Up, ///< Up counter (edge-aligned PWM duty cycle). + NRF_PWM_MODE_UP_AND_DOWN = PWM_MODE_UPDOWN_UpAndDown, ///< Up and down counter (center-aligned PWM duty cycle). +} nrf_pwm_mode_t; + +/** + * @brief PWM base clock frequencies. + */ +typedef enum +{ + NRF_PWM_CLK_16MHz = PWM_PRESCALER_PRESCALER_DIV_1, ///< 16 MHz / 1 = 16 MHz. + NRF_PWM_CLK_8MHz = PWM_PRESCALER_PRESCALER_DIV_2, ///< 16 MHz / 2 = 8 MHz. + NRF_PWM_CLK_4MHz = PWM_PRESCALER_PRESCALER_DIV_4, ///< 16 MHz / 4 = 4 MHz. + NRF_PWM_CLK_2MHz = PWM_PRESCALER_PRESCALER_DIV_8, ///< 16 MHz / 8 = 2 MHz. + NRF_PWM_CLK_1MHz = PWM_PRESCALER_PRESCALER_DIV_16, ///< 16 MHz / 16 = 1 MHz. + NRF_PWM_CLK_500kHz = PWM_PRESCALER_PRESCALER_DIV_32, ///< 16 MHz / 32 = 500 kHz. + NRF_PWM_CLK_250kHz = PWM_PRESCALER_PRESCALER_DIV_64, ///< 16 MHz / 64 = 250 kHz. + NRF_PWM_CLK_125kHz = PWM_PRESCALER_PRESCALER_DIV_128 ///< 16 MHz / 128 = 125 kHz. +} nrf_pwm_clk_t; + +/** + * @brief PWM decoder load modes. + * + * The selected mode determines how the sequence data is read from RAM and + * spread to the compare registers. + */ +typedef enum +{ + NRF_PWM_LOAD_COMMON = PWM_DECODER_LOAD_Common, ///< 1st half word (16-bit) used in all PWM channels (0-3). + NRF_PWM_LOAD_GROUPED = PWM_DECODER_LOAD_Grouped, ///< 1st half word (16-bit) used in channels 0 and 1; 2nd word in channels 2 and 3. + NRF_PWM_LOAD_INDIVIDUAL = PWM_DECODER_LOAD_Individual, ///< 1st half word (16-bit) used in channel 0; 2nd in channel 1; 3rd in channel 2; 4th in channel 3. + NRF_PWM_LOAD_WAVE_FORM = PWM_DECODER_LOAD_WaveForm ///< 1st half word (16-bit) used in channel 0; 2nd in channel 1; ... ; 4th as the top value for the pulse generator counter. +} nrf_pwm_dec_load_t; + +/** + * @brief PWM decoder next step modes. + * + * The selected mode determines when the next value from the active sequence + * is loaded. + */ +typedef enum +{ + NRF_PWM_STEP_AUTO = PWM_DECODER_MODE_RefreshCount, ///< Automatically after the current value is played and repeated the requested number of times. + NRF_PWM_STEP_TRIGGERED = PWM_DECODER_MODE_NextStep ///< When the @ref NRF_PWM_TASK_NEXTSTEP task is triggered. +} nrf_pwm_dec_step_t; + + +/** + * @brief Type used for defining duty cycle values for a sequence + * loaded in @ref NRF_PWM_LOAD_COMMON mode. + */ +typedef uint16_t nrf_pwm_values_common_t; + +/** + * @brief Structure for defining duty cycle values for a sequence + * loaded in @ref NRF_PWM_LOAD_GROUPED mode. + */ +typedef struct { + uint16_t group_0; ///< Duty cycle value for group 0 (channels 0 and 1). + uint16_t group_1; ///< Duty cycle value for group 1 (channels 2 and 3). +} nrf_pwm_values_grouped_t; + +/** + * @brief Structure for defining duty cycle values for a sequence + * loaded in @ref NRF_PWM_LOAD_INDIVIDUAL mode. + */ +typedef struct +{ + uint16_t channel_0; ///< Duty cycle value for channel 0. + uint16_t channel_1; ///< Duty cycle value for channel 1. + uint16_t channel_2; ///< Duty cycle value for channel 2. + uint16_t channel_3; ///< Duty cycle value for channel 3. +} nrf_pwm_values_individual_t; + +/** + * @brief Structure for defining duty cycle values for a sequence + * loaded in @ref NRF_PWM_LOAD_WAVE_FORM mode. + */ +typedef struct { + uint16_t channel_0; ///< Duty cycle value for channel 0. + uint16_t channel_1; ///< Duty cycle value for channel 1. + uint16_t channel_2; ///< Duty cycle value for channel 2. + uint16_t counter_top; ///< Top value for the pulse generator counter. +} nrf_pwm_values_wave_form_t; + +/** + * @brief Union grouping pointers to arrays of duty cycle values applicable to + * various loading modes. + */ +typedef union { + nrf_pwm_values_common_t const * p_common; ///< Pointer to be used in @ref NRF_PWM_LOAD_COMMON mode. + nrf_pwm_values_grouped_t const * p_grouped; ///< Pointer to be used in @ref NRF_PWM_LOAD_GROUPED mode. + nrf_pwm_values_individual_t const * p_individual; ///< Pointer to be used in @ref NRF_PWM_LOAD_INDIVIDUAL mode. + nrf_pwm_values_wave_form_t const * p_wave_form; ///< Pointer to be used in @ref NRF_PWM_LOAD_WAVE_FORM mode. + uint16_t const * p_raw; ///< Pointer providing raw access to the values. +} nrf_pwm_values_t; + +/** + * @brief Structure for defining a sequence of PWM duty cycles. + * + * When the sequence is set (by a call to @ref nrf_pwm_sequence_set), the + * provided duty cycle values are not copied. The @p values pointer is stored + * in the peripheral's internal register, and the values are loaded from RAM + * during the sequence playback. Therefore, you must ensure that the values + * do not change before and during the sequence playback (for example, + * the values cannot be placed in a local variable that is allocated on stack). + * If the sequence is played in a loop and the values should be updated + * before the next iteration, it is safe to modify them when the corresponding + * event signaling the end of sequence occurs (@ref NRF_PWM_EVENT_SEQEND0 + * or @ref NRF_PWM_EVENT_SEQEND1, respectively). + * + * @note The @p repeats and @p end_delay values (which are written to the + * SEQ[n].REFRESH and SEQ[n].ENDDELAY registers in the peripheral, + * respectively) are ignored at the end of a complex sequence + * playback, indicated by the LOOPSDONE event. + * See the @linkProductSpecification52 for more information. + */ +typedef struct +{ + nrf_pwm_values_t values; ///< Pointer to an array with duty cycle values. This array must be in Data RAM. + /**< This field is defined as an union of pointers + * to provide a convenient way to define duty + * cycle values in various loading modes + * (see @ref nrf_pwm_dec_load_t). + * In each value, the most significant bit (15) + * determines the polarity of the output and the + * others (14-0) compose the 15-bit value to be + * compared with the pulse generator counter. */ + uint16_t length; ///< Number of 16-bit values in the array pointed by @p values. + uint32_t repeats; ///< Number of times that each duty cycle should be repeated (after being played once). Ignored in @ref NRF_PWM_STEP_TRIGGERED mode. + uint32_t end_delay; ///< Additional time (in PWM periods) that the last duty cycle is to be kept after the sequence is played. Ignored in @ref NRF_PWM_STEP_TRIGGERED mode. +} nrf_pwm_sequence_t; + +/** + * @brief Helper macro for calculating the number of 16-bit values in specified + * array of duty cycle values. + */ +#define NRF_PWM_VALUES_LENGTH(array) (sizeof(array)/sizeof(uint16_t)) + + +/** + * @brief Function for activating a specific PWM task. + * + * @param[in] p_pwm PWM instance. + * @param[in] task Task to activate. + */ +__STATIC_INLINE void nrf_pwm_task_trigger(NRF_PWM_Type * p_pwm, + nrf_pwm_task_t task); + +/** + * @brief Function for getting the address of a specific PWM task register. + * + * @param[in] p_pwm PWM instance. + * @param[in] task Requested task. + * + * @return Address of the specified task register. + */ +__STATIC_INLINE uint32_t nrf_pwm_task_address_get(NRF_PWM_Type const * p_pwm, + nrf_pwm_task_t task); + +/** + * @brief Function for clearing a specific PWM event. + * + * @param[in] p_pwm PWM instance. + * @param[in] event Event to clear. + */ +__STATIC_INLINE void nrf_pwm_event_clear(NRF_PWM_Type * p_pwm, + nrf_pwm_event_t event); + +/** + * @brief Function for checking the state of a specific PWM event. + * + * @param[in] p_pwm PWM instance. + * @param[in] event Event to check. + * + * @retval true If the event is set. + * @retval false If the event is not set. + */ +__STATIC_INLINE bool nrf_pwm_event_check(NRF_PWM_Type const * p_pwm, + nrf_pwm_event_t event); + +/** + * @brief Function for getting the address of a specific PWM event register. + * + * @param[in] p_pwm PWM instance. + * @param[in] event Requested event. + * + * @return Address of the specified event register. + */ +__STATIC_INLINE uint32_t nrf_pwm_event_address_get(NRF_PWM_Type const * p_pwm, + nrf_pwm_event_t event); + +/** + * @brief Function for enabling specified shortcuts. + * + * @param[in] p_pwm PWM instance. + * @param[in] pwm_shorts_mask Shortcuts to enable. + */ +__STATIC_INLINE void nrf_pwm_shorts_enable(NRF_PWM_Type * p_pwm, + uint32_t pwm_shorts_mask); + +/** + * @brief Function for disabling specified shortcuts. + * + * @param[in] p_pwm PWM instance. + * @param[in] pwm_shorts_mask Shortcuts to disable. + */ +__STATIC_INLINE void nrf_pwm_shorts_disable(NRF_PWM_Type * p_pwm, + uint32_t pwm_shorts_mask); + +/** + * @brief Function for setting the configuration of PWM shortcuts. + * + * @param[in] p_pwm PWM instance. + * @param[in] pwm_shorts_mask Shortcuts configuration to set. + */ +__STATIC_INLINE void nrf_pwm_shorts_set(NRF_PWM_Type * p_pwm, + uint32_t pwm_shorts_mask); + +/** + * @brief Function for enabling specified interrupts. + * + * @param[in] p_pwm PWM instance. + * @param[in] pwm_int_mask Interrupts to enable. + */ +__STATIC_INLINE void nrf_pwm_int_enable(NRF_PWM_Type * p_pwm, + uint32_t pwm_int_mask); + +/** + * @brief Function for disabling specified interrupts. + * + * @param[in] p_pwm PWM instance. + * @param[in] pwm_int_mask Interrupts to disable. + */ +__STATIC_INLINE void nrf_pwm_int_disable(NRF_PWM_Type * p_pwm, + uint32_t pwm_int_mask); + +/** + * @brief Function for setting the configuration of PWM interrupts. + * + * @param[in] p_pwm PWM instance. + * @param[in] pwm_int_mask Interrupts configuration to set. + */ +__STATIC_INLINE void nrf_pwm_int_set(NRF_PWM_Type * p_pwm, + uint32_t pwm_int_mask); + +/** + * @brief Function for retrieving the state of a given interrupt. + * + * @param[in] p_pwm PWM instance. + * @param[in] pwm_int Interrupt to check. + * + * @retval true If the interrupt is enabled. + * @retval false If the interrupt is not enabled. + */ +__STATIC_INLINE bool nrf_pwm_int_enable_check(NRF_PWM_Type const * p_pwm, + nrf_pwm_int_mask_t pwm_int); + +/** + * @brief Function for enabling the PWM peripheral. + * + * @param[in] p_pwm PWM instance. + */ +__STATIC_INLINE void nrf_pwm_enable(NRF_PWM_Type * p_pwm); + +/** + * @brief Function for disabling the PWM peripheral. + * + * @param[in] p_pwm PWM instance. + */ +__STATIC_INLINE void nrf_pwm_disable(NRF_PWM_Type * p_pwm); + +/** + * @brief Function for assigning pins to PWM output channels. + * + * Usage of all PWM output channels is optional. If a given channel is not + * needed, pass the @ref NRF_PWM_PIN_NOT_CONNECTED value instead of its pin + * number. + * + * @param[in] p_pwm PWM instance. + * @param[in] out_pins Array with pin numbers for individual PWM output channels. + */ +__STATIC_INLINE void nrf_pwm_pins_set(NRF_PWM_Type * p_pwm, + uint32_t out_pins[NRF_PWM_CHANNEL_COUNT]); + +/** + * @brief Function for configuring the PWM peripheral. + * + * @param[in] p_pwm PWM instance. + * @param[in] base_clock Base clock frequency. + * @param[in] mode Operating mode of the pulse generator counter. + * @param[in] top_value Value up to which the pulse generator counter counts. + */ +__STATIC_INLINE void nrf_pwm_configure(NRF_PWM_Type * p_pwm, + nrf_pwm_clk_t base_clock, + nrf_pwm_mode_t mode, + uint16_t top_value); + +/** + * @brief Function for defining a sequence of PWM duty cycles. + * + * @param[in] p_pwm PWM instance. + * @param[in] seq_id Identifier of the sequence (0 or 1). + * @param[in] p_seq Pointer to the sequence definition. + */ +__STATIC_INLINE void nrf_pwm_sequence_set(NRF_PWM_Type * p_pwm, + uint8_t seq_id, + nrf_pwm_sequence_t const * p_seq); + +/** + * @brief Function for modifying the pointer to the duty cycle values + * in the specified sequence. + * + * @param[in] p_pwm PWM instance. + * @param[in] seq_id Identifier of the sequence (0 or 1). + * @param[in] p_values Pointer to an array with duty cycle values. + */ +__STATIC_INLINE void nrf_pwm_seq_ptr_set(NRF_PWM_Type * p_pwm, + uint8_t seq_id, + uint16_t const * p_values); + +/** + * @brief Function for modifying the total number of duty cycle values + * in the specified sequence. + * + * @param[in] p_pwm PWM instance. + * @param[in] seq_id Identifier of the sequence (0 or 1). + * @param[in] length Number of duty cycle values. + */ +__STATIC_INLINE void nrf_pwm_seq_cnt_set(NRF_PWM_Type * p_pwm, + uint8_t seq_id, + uint16_t length); + +/** + * @brief Function for modifying the additional number of PWM periods spent + * on each duty cycle value in the specified sequence. + * + * @param[in] p_pwm PWM instance. + * @param[in] seq_id Identifier of the sequence (0 or 1). + * @param[in] refresh Number of additional PWM periods for each duty cycle value. + */ +__STATIC_INLINE void nrf_pwm_seq_refresh_set(NRF_PWM_Type * p_pwm, + uint8_t seq_id, + uint32_t refresh); + +/** + * @brief Function for modifying the additional time added after the sequence + * is played. + * + * @param[in] p_pwm PWM instance. + * @param[in] seq_id Identifier of the sequence (0 or 1). + * @param[in] end_delay Number of PWM periods added at the end of the sequence. + */ +__STATIC_INLINE void nrf_pwm_seq_end_delay_set(NRF_PWM_Type * p_pwm, + uint8_t seq_id, + uint32_t end_delay); + +/** + * @brief Function for setting the mode of loading sequence data from RAM + * and advancing the sequence. + * + * @param[in] p_pwm PWM instance. + * @param[in] dec_load Mode of loading sequence data from RAM. + * @param[in] dec_step Mode of advancing the active sequence. + */ +__STATIC_INLINE void nrf_pwm_decoder_set(NRF_PWM_Type * p_pwm, + nrf_pwm_dec_load_t dec_load, + nrf_pwm_dec_step_t dec_step); + +/** + * @brief Function for setting the number of times the sequence playback + * should be performed. + * + * This function applies to two-sequence playback (concatenated sequence 0 and 1). + * A single sequence can be played back only once. + * + * @param[in] p_pwm PWM instance. + * @param[in] loop_count Number of times to perform the sequence playback. + */ +__STATIC_INLINE void nrf_pwm_loop_set(NRF_PWM_Type * p_pwm, + uint16_t loop_count); + + +#ifndef SUPPRESS_INLINE_IMPLEMENTATION + +__STATIC_INLINE void nrf_pwm_task_trigger(NRF_PWM_Type * p_pwm, + nrf_pwm_task_t task) +{ + *((volatile uint32_t *)((uint8_t *)p_pwm + (uint32_t)task)) = 0x1UL; +} + +__STATIC_INLINE uint32_t nrf_pwm_task_address_get(NRF_PWM_Type const * p_pwm, + nrf_pwm_task_t task) +{ + return ((uint32_t)p_pwm + (uint32_t)task); +} + +__STATIC_INLINE void nrf_pwm_event_clear(NRF_PWM_Type * p_pwm, + nrf_pwm_event_t event) +{ + *((volatile uint32_t *)((uint8_t *)p_pwm + (uint32_t)event)) = 0x0UL; +} + +__STATIC_INLINE bool nrf_pwm_event_check(NRF_PWM_Type const * p_pwm, + nrf_pwm_event_t event) +{ + return (bool)*(volatile uint32_t *)((uint8_t *)p_pwm + (uint32_t)event); +} + +__STATIC_INLINE uint32_t nrf_pwm_event_address_get(NRF_PWM_Type const * p_pwm, + nrf_pwm_event_t event) +{ + return ((uint32_t)p_pwm + (uint32_t)event); +} + +__STATIC_INLINE void nrf_pwm_shorts_enable(NRF_PWM_Type * p_pwm, + uint32_t pwm_shorts_mask) +{ + p_pwm->SHORTS |= pwm_shorts_mask; +} + +__STATIC_INLINE void nrf_pwm_shorts_disable(NRF_PWM_Type * p_pwm, + uint32_t pwm_shorts_mask) +{ + p_pwm->SHORTS &= ~(pwm_shorts_mask); +} + +__STATIC_INLINE void nrf_pwm_shorts_set(NRF_PWM_Type * p_pwm, + uint32_t pwm_shorts_mask) +{ + p_pwm->SHORTS = pwm_shorts_mask; +} + +__STATIC_INLINE void nrf_pwm_int_enable(NRF_PWM_Type * p_pwm, + uint32_t pwm_int_mask) +{ + p_pwm->INTENSET = pwm_int_mask; +} + +__STATIC_INLINE void nrf_pwm_int_disable(NRF_PWM_Type * p_pwm, + uint32_t pwm_int_mask) +{ + p_pwm->INTENCLR = pwm_int_mask; +} + +__STATIC_INLINE void nrf_pwm_int_set(NRF_PWM_Type * p_pwm, + uint32_t pwm_int_mask) +{ + p_pwm->INTEN = pwm_int_mask; +} + +__STATIC_INLINE bool nrf_pwm_int_enable_check(NRF_PWM_Type const * p_pwm, + nrf_pwm_int_mask_t pwm_int) +{ + return (bool)(p_pwm->INTENSET & pwm_int); +} + +__STATIC_INLINE void nrf_pwm_enable(NRF_PWM_Type * p_pwm) +{ + p_pwm->ENABLE = (PWM_ENABLE_ENABLE_Enabled << PWM_ENABLE_ENABLE_Pos); +} + +__STATIC_INLINE void nrf_pwm_disable(NRF_PWM_Type * p_pwm) +{ + p_pwm->ENABLE = (PWM_ENABLE_ENABLE_Disabled << PWM_ENABLE_ENABLE_Pos); +} + +__STATIC_INLINE void nrf_pwm_pins_set(NRF_PWM_Type * p_pwm, + uint32_t out_pins[NRF_PWM_CHANNEL_COUNT]) +{ + uint8_t i; + for (i = 0; i < NRF_PWM_CHANNEL_COUNT; ++i) + { + p_pwm->PSEL.OUT[i] = out_pins[i]; + } +} + +__STATIC_INLINE void nrf_pwm_configure(NRF_PWM_Type * p_pwm, + nrf_pwm_clk_t base_clock, + nrf_pwm_mode_t mode, + uint16_t top_value) +{ + ASSERT(top_value <= PWM_COUNTERTOP_COUNTERTOP_Msk); + + p_pwm->PRESCALER = base_clock; + p_pwm->MODE = mode; + p_pwm->COUNTERTOP = top_value; +} + +__STATIC_INLINE void nrf_pwm_sequence_set(NRF_PWM_Type * p_pwm, + uint8_t seq_id, + nrf_pwm_sequence_t const * p_seq) +{ + ASSERT(p_seq != NULL); + + nrf_pwm_seq_ptr_set( p_pwm, seq_id, p_seq->values.p_raw); + nrf_pwm_seq_cnt_set( p_pwm, seq_id, p_seq->length); + nrf_pwm_seq_refresh_set( p_pwm, seq_id, p_seq->repeats); + nrf_pwm_seq_end_delay_set(p_pwm, seq_id, p_seq->end_delay); +} + +__STATIC_INLINE void nrf_pwm_seq_ptr_set(NRF_PWM_Type * p_pwm, + uint8_t seq_id, + uint16_t const * p_values) +{ + ASSERT(seq_id <= 1); + ASSERT(p_values != NULL); + p_pwm->SEQ[seq_id].PTR = (uint32_t)p_values; +} + +__STATIC_INLINE void nrf_pwm_seq_cnt_set(NRF_PWM_Type * p_pwm, + uint8_t seq_id, + uint16_t length) +{ + ASSERT(seq_id <= 1); + ASSERT(length != 0); + ASSERT(length <= PWM_SEQ_CNT_CNT_Msk); + p_pwm->SEQ[seq_id].CNT = length; +} + +__STATIC_INLINE void nrf_pwm_seq_refresh_set(NRF_PWM_Type * p_pwm, + uint8_t seq_id, + uint32_t refresh) +{ + ASSERT(seq_id <= 1); + ASSERT(refresh <= PWM_SEQ_REFRESH_CNT_Msk); + p_pwm->SEQ[seq_id].REFRESH = refresh; +} + +__STATIC_INLINE void nrf_pwm_seq_end_delay_set(NRF_PWM_Type * p_pwm, + uint8_t seq_id, + uint32_t end_delay) +{ + ASSERT(seq_id <= 1); + ASSERT(end_delay <= PWM_SEQ_ENDDELAY_CNT_Msk); + p_pwm->SEQ[seq_id].ENDDELAY = end_delay; +} + +__STATIC_INLINE void nrf_pwm_decoder_set(NRF_PWM_Type * p_pwm, + nrf_pwm_dec_load_t dec_load, + nrf_pwm_dec_step_t dec_step) +{ + p_pwm->DECODER = ((uint32_t)dec_load << PWM_DECODER_LOAD_Pos) | + ((uint32_t)dec_step << PWM_DECODER_MODE_Pos); +} + +__STATIC_INLINE void nrf_pwm_loop_set(NRF_PWM_Type * p_pwm, + uint16_t loop_count) +{ + p_pwm->LOOP = loop_count; +} + +#endif // SUPPRESS_INLINE_IMPLEMENTATION + +#endif // NRF52 + +#endif // NRF_PWM_H__ + +/** @} */