Timer based PWM output for MAX326XXFTHR platforms

Dependents:   MAX32620FTHR_PwmBlinky

Fork of MAX326XXFTHR_PwmOut by Jesse Marroquin

This library provides PWM output using the MAX32620/MAX32630 32-bit timers.

The mbed PwmOut API implementation uses Pulse Train peripherals.

The tables below contain the available GPIO pins that can be connected to the six 32-bit timers (TMR0-5). Timer 0 is used for the microsecond ticker API and is not available for PWM output. On the MAX32630FTHR platform, timer 5 is used by the BLE API and will not be available for PWM output if the BLE API is used.

MAX32620FTHR 32-Bit Timer PWM Mapping

TimerGPIO Port and Pin
TMR1P0_1, P0_7, P1_5, P3_1, P4_5, P5_3
TMR2P0_2, P1_0, P1_6, P2_4, P3_2, P4_0, P4_6, P5_4
TMR3P0_3, P1_1, P1_7, P2_5, P3_3, P4_7, P5_5
TMR4P0_4, P1_2, P2_6, P3_4, P5_0, P5_6
TMR5P0_5, P1_3, P2_7, P3_5, P5_1, P5_7

MAX32630FTHR 32-Bit Timer PWM Mapping

TimerGPIO Port and Pin
TMR1P3_1, P5_3
TMR2P2_4, P3_2, P4_0, P5_4
TMR3P2_5, P3_3, P5_5
TMR4P2_6, P3_4, P5_0, P5_6
TMR5P3_5, P5_1

Note GPIO P2_4, P2_5 and P2_6 are connected to onboard LEDs 1, 2 and 3.

MAX326XXFTHR_PwmOut.cpp

Committer:
jessexm
Date:
2018-04-23
Revision:
0:bb005bb0a24c

File content as of revision 0:bb005bb0a24c:

/*******************************************************************************
 * Copyright (C) 2018 Maxim Integrated Products, Inc., All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES
 * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the name of Maxim Integrated
 * Products, Inc. shall not be used except as stated in the Maxim Integrated
 * Products, Inc. Branding Policy.
 *
 * The mere transfer of this software does not imply any licenses
 * of trade secrets, proprietary technology, copyrights, patents,
 * trademarks, maskwork rights, or any other form of intellectual
 * property whatsoever. Maxim Integrated Products, Inc. retains all
 * ownership rights.
 *******************************************************************************
 */

#include "MAX326XXFTHR_PwmOut.h"
#include "mbed_assert.h"
#include "pinmap.h"
#include "gpio_regs.h"
#include "tmr_regs.h"

#define PWM_TMR_PRESCALE  MXC_V_TMR_CTRL_PRESCALE_DIVIDE_BY_4
#define PWM_TICKS_PER_US  ((SystemCoreClock / 1000000) >> PWM_TMR_PRESCALE)
#define PWM_MAX_US        (0xFFFFFFFFUL / PWM_TICKS_PER_US)

#define MXC_GPIO_OUT_MODE_FIELD_WIDTH 4
#define MXC_GPIO_OUT_MODE_FIELD_MASK  ((uint32_t)0xFFFFFFFF >> (32 - MXC_GPIO_OUT_MODE_FIELD_WIDTH))
#define MXC_GPIO_FUNC_SEL_FIELD_WIDTH 4
#define MXC_GPIO_FUNC_SEL_FIELD_MASK  ((uint32_t)0xFFFFFFFF >> (32 - MXC_GPIO_FUNC_SEL_FIELD_WIDTH))

static const PinMap PinMap_PWM[] = {
#if defined(TARGET_MAX32620FTHR)
    // Timer 0 not available; used for micro-second ticker
    { P0_1, MXC_BASE_TMR1, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P0_2, MXC_BASE_TMR2, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P0_3, MXC_BASE_TMR3, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P0_4, MXC_BASE_TMR4, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P0_5, MXC_BASE_TMR5, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P0_7, MXC_BASE_TMR1, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P1_0, MXC_BASE_TMR2, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P1_1, MXC_BASE_TMR3, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P1_2, MXC_BASE_TMR4, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P1_3, MXC_BASE_TMR5, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P1_5, MXC_BASE_TMR1, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P1_6, MXC_BASE_TMR2, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P1_7, MXC_BASE_TMR3, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P2_4, MXC_BASE_TMR2, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P2_5, MXC_BASE_TMR3, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P2_6, MXC_BASE_TMR4, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P2_7, MXC_BASE_TMR5, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P3_1, MXC_BASE_TMR1, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P3_2, MXC_BASE_TMR2, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P3_3, MXC_BASE_TMR3, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P3_4, MXC_BASE_TMR4, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P3_5, MXC_BASE_TMR5, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P4_0, MXC_BASE_TMR2, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P4_5, MXC_BASE_TMR1, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P4_6, MXC_BASE_TMR2, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P4_7, MXC_BASE_TMR3, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P5_0, MXC_BASE_TMR4, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P5_1, MXC_BASE_TMR5, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P5_3, MXC_BASE_TMR1, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P5_4, MXC_BASE_TMR2, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P5_5, MXC_BASE_TMR3, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P5_6, MXC_BASE_TMR4, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P5_7, MXC_BASE_TMR5, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
#elif defined(TARGET_MAX32630FTHR)
    // Timer 0 not available; used for micro-second ticker
    // Timer 5 not available if using BLE
    { P2_4, MXC_BASE_TMR2, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P2_5, MXC_BASE_TMR3, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P2_6, MXC_BASE_TMR4, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P3_1, MXC_BASE_TMR1, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P3_2, MXC_BASE_TMR2, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P3_3, MXC_BASE_TMR3, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P3_4, MXC_BASE_TMR4, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P3_5, MXC_BASE_TMR5, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P4_0, MXC_BASE_TMR2, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P5_0, MXC_BASE_TMR4, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P5_1, MXC_BASE_TMR5, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P5_3, MXC_BASE_TMR1, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P5_4, MXC_BASE_TMR2, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P5_5, MXC_BASE_TMR3, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
    { P5_6, MXC_BASE_TMR4, MXC_V_GPIO_FUNC_SEL_MODE_TMR },
#else
    // Platform not supported
#endif
    { NC,   NC,   0 }
};

typedef struct {
    mxc_tmr_regs_t *tmr;
    uint32_t term_cnt32;
    uint32_t pwm_cap32;
    unsigned int port;
    unsigned int port_pin;
} tmr_obj_t;

static tmr_obj_t tmr_obj[MXC_CFG_TMR_INSTANCES];

static void tmr_handler(tmr_obj_t *obj)
{
    mxc_tmr_regs_t *tmr = obj->tmr;

    // Assert GPIO control to have steady state low/zero percent duty-cycle
    // Update function required to restore timer control of pin
    if (obj->pwm_cap32 == 0) {
        MXC_GPIO->func_sel[obj->port] &= ~(MXC_GPIO_FUNC_SEL_FIELD_MASK << (MXC_GPIO_FUNC_SEL_FIELD_WIDTH * obj->port_pin));
    } else {
        tmr->term_cnt32 = obj->term_cnt32;
        tmr->pwm_cap32 = obj->pwm_cap32;
    }

    tmr->intfl = 1;
    tmr->inten = 0;
}

static void tmr1_handler(void) { tmr_handler(&tmr_obj[1]); };
static void tmr2_handler(void) { tmr_handler(&tmr_obj[2]); };
static void tmr3_handler(void) { tmr_handler(&tmr_obj[3]); };
static void tmr4_handler(void) { tmr_handler(&tmr_obj[4]); };
static void tmr5_handler(void) { tmr_handler(&tmr_obj[5]); };

void MAX326XXFTHR_PwmOut::pwmout_update(void)
{
    uint32_t tcnt = (pwm_period * PWM_TICKS_PER_US);
    uint32_t tcap = (pulse_width * PWM_TICKS_PER_US);

    // At init the timer will not be running
    if (!(tmr->ctrl & MXC_F_TMR_CTRL_ENABLE0)) {
        tmr->term_cnt32 = tcnt;
        tmr->pwm_cap32 = tcap;
    } else {
        tmr_obj[tmr_idx].term_cnt32 = tcnt;
        if (pulse_width == 0) {
            tmr_obj[tmr_idx].pwm_cap32 = 0;
            MXC_GPIO->out_val[tmr_obj[tmr_idx].port] &= ~(1 << tmr_obj[tmr_idx].port_pin);
        } else if (pulse_width == pwm_period) {
            tmr_obj[tmr_idx].pwm_cap32 = tcnt;
        } else {
            tmr_obj[tmr_idx].pwm_cap32 = tcap;
        }

        if ((last_pulse_width == 0) && (pulse_width != 0)) {
            MXC_GPIO->func_sel[tmr_obj[tmr_idx].port] |= (MXC_V_GPIO_FUNC_SEL_MODE_TMR << (MXC_GPIO_FUNC_SEL_FIELD_WIDTH * tmr_obj[tmr_idx].port_pin));
        }

        last_pulse_width = pulse_width;

        tmr->intfl = 1;
        tmr->inten = 1;
    }
}

void MAX326XXFTHR_PwmOut::pwmout_init(PinName pin)
{
    int i;

    // Make sure the pin is free for GPIO use
    unsigned int port = (unsigned int)pin >> PORT_SHIFT;
    unsigned int port_pin = (unsigned int)pin & ~(0xFFFFFFFF << PORT_SHIFT);
    MBED_ASSERT(MXC_GPIO->free[port] & (MXC_V_GPIO_FREE_AVAILABLE << port_pin));

    // Make sure the pin is not associated with a PT or TIMER
    int pin_func = (MXC_GPIO->func_sel[port] >> (MXC_GPIO_FUNC_SEL_FIELD_WIDTH * port_pin)) & MXC_GPIO_FUNC_SEL_FIELD_MASK;
    MBED_ASSERT(pin_func == MXC_V_GPIO_FUNC_SEL_MODE_GPIO);

    // Search through PinMap to find the pin
    for (i = 0; (PinMap_PWM[i].pin != NC) && (PinMap_PWM[i].pin != pin); i++);
    MBED_ASSERT(PinMap_PWM[i].pin != NC);

    // Check timer is available
    tmr = (mxc_tmr_regs_t*)PinMap_PWM[i].peripheral;
    MBED_ASSERT(!(tmr->ctrl & MXC_F_TMR_CTRL_ENABLE0));

    // Prep for interrupt handler
    tmr_idx = MXC_TMR_GET_IDX(tmr);
    switch (tmr_idx) {
        case 1:
            NVIC_SetVector(TMR1_0_IRQn, (uint32_t)tmr1_handler);
            NVIC_EnableIRQ(TMR1_0_IRQn);
            break;
        case 2:
            NVIC_SetVector(TMR2_0_IRQn, (uint32_t)tmr2_handler);
            NVIC_EnableIRQ(TMR2_0_IRQn);
            break;
        case 3:
            NVIC_SetVector(TMR3_0_IRQn, (uint32_t)tmr3_handler);
            NVIC_EnableIRQ(TMR3_0_IRQn);
            break;
        case 4:
            NVIC_SetVector(TMR4_0_IRQn, (uint32_t)tmr4_handler);
            NVIC_EnableIRQ(TMR4_0_IRQn);
            break;
        case 5:
            NVIC_SetVector(TMR5_0_IRQn, (uint32_t)tmr5_handler);
            NVIC_EnableIRQ(TMR5_0_IRQn);
            break;
        case 0:
        default:
            MBED_ASSERT(0);
    }

    tmr_obj[tmr_idx].tmr = tmr;
    tmr_obj[tmr_idx].term_cnt32 = 0;
    tmr_obj[tmr_idx].pwm_cap32 = 0;
    tmr_obj[tmr_idx].port = port;
    tmr_obj[tmr_idx].port_pin = port_pin;

    this->pin = pin;

    // Initial state
    pwm_period = -1;
    pulse_width = -1;
    last_pulse_width = -1;

    // Disable timer/clear settings
    tmr->ctrl = 0;

    // Set mode and polarity
    tmr->ctrl = MXC_S_TMR_CTRL_MODE_PWM |
                MXC_F_TMR_CTRL_POLARITY |
                (PWM_TMR_PRESCALE << MXC_F_TMR_CTRL_PRESCALE_POS);

    // Reset counts
    tmr->count32 = 1;
    tmr->term_cnt32 = 0;
    tmr->pwm_cap32 = 0;

    // Configure the pin
    PinMode mode = (PinMode)PullNone;
#ifdef OPEN_DRAIN_LEDS
    if ((pin == LED1) || (pin == LED2) || (pin == LED3) || (pin == LED4)) {
        mode = (PinMode)OpenDrain;
        MXC_IOMAN->use_vddioh_0 |= (1 << ((8 * port) + port_pin));
    }
#endif
    pin_mode(pin, mode);
    pin_function(pin, PinMap_PWM[i].function);

    // Default to 20ms: standard for servos, and fine for e.g. brightness control
    pwmout_period_us(20000);
    pwmout_write(0.0f);

    // Set the drive mode to normal
    MXC_SET_FIELD(&MXC_GPIO->out_mode[port],
                  (MXC_GPIO_OUT_MODE_FIELD_MASK << (port_pin * MXC_GPIO_OUT_MODE_FIELD_WIDTH)),
                  (MXC_V_GPIO_OUT_MODE_NORMAL << (port_pin * MXC_GPIO_OUT_MODE_FIELD_WIDTH)));

    // Enable timer
    tmr->ctrl |= MXC_F_TMR_CTRL_ENABLE0;
}

void MAX326XXFTHR_PwmOut::pwmout_free()
{
    // GPIO control of pin
    MXC_GPIO->func_sel[(unsigned int)pin >> PORT_SHIFT] &=
        ~(MXC_GPIO_FUNC_SEL_FIELD_MASK << (MXC_GPIO_FUNC_SEL_FIELD_WIDTH *
          (unsigned int)pin & ~(0xFFFFFFFF << PORT_SHIFT)));
    // Disable timer
    tmr->ctrl = 0;
}

void MAX326XXFTHR_PwmOut::pwmout_write(float percent)
{
    // Saturate percent if outside of range
    if (percent < 0.0f) {
        percent = 0.0f;
    } else if (percent > 1.0f) {
        percent = 1.0f;
    }

    // Resize the pulse width to set the duty cycle
    pwmout_pulsewidth_us((int)(percent * pwm_period));
}

float MAX326XXFTHR_PwmOut::pwmout_read()
{
    // Check for when pulsewidth or period equals 0
    if((pulse_width == 0) || (pwm_period == 0)) {
        return 0.0f;
    }

    // Return the duty cycle
    return ((float)pulse_width / (float)pwm_period);
}

void MAX326XXFTHR_PwmOut::pwmout_period(float seconds)
{
    pwmout_period_us((int)(seconds * 1000000.0f));
}

void MAX326XXFTHR_PwmOut::pwmout_period_ms(int ms)
{
    pwmout_period_us(ms * 1000);
}

void MAX326XXFTHR_PwmOut::pwmout_period_us(int us)
{
    // Check the range of the period
    MBED_ASSERT((us >= 0) && (us <= (int)PWM_MAX_US));

    // Set pulse width to half the period if uninitialized
    if (pulse_width == -1) {
        pulse_width = us / 2;
    }

    // Save the period
    pwm_period = us;

    // Update the registers
    pwmout_update();
}

void MAX326XXFTHR_PwmOut::pwmout_pulsewidth(float seconds)
{
    pwmout_pulsewidth_us((int)(seconds * 1000000.0f));
}

void MAX326XXFTHR_PwmOut::pwmout_pulsewidth_ms(int ms)
{
    pwmout_pulsewidth_us(ms * 1000);
}

void MAX326XXFTHR_PwmOut::pwmout_pulsewidth_us(int us)
{
    // Check the range of the pulsewidth
    MBED_ASSERT((us >= 0) && (us <= (int)PWM_MAX_US));

    // Initialize period to double the pulsewidth if uninitialized
    if (pwm_period == -1) {
        pwm_period = 2 * us;
    }

    // Save the pulsewidth
    pulse_width = us;

    // Update the register
    pwmout_update();
}