You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
302 lines
8.9 KiB
302 lines
8.9 KiB
/* |
|
* Copyright (C) 2015 Marc Poulhiès |
|
* |
|
* This file is subject to the terms and conditions of the GNU Lesser General |
|
* Public License v2.1. See the file LICENSE in the top level directory for more |
|
* details. |
|
*/ |
|
|
|
/** |
|
* @ingroup cpu_lm4f120 |
|
* @{ |
|
* |
|
* @file |
|
* @brief Low-level PWM driver implementation |
|
* |
|
* @author Marc Poulhiès <dkm@kataplop.net> |
|
* |
|
* @} |
|
*/ |
|
|
|
#include <stdint.h> |
|
#include <string.h> |
|
|
|
#include "log.h" |
|
#include "cpu.h" |
|
#include "board.h" |
|
#include "periph/pwm.h" |
|
#include "periph/gpio.h" |
|
#include "periph_conf.h" |
|
|
|
#define ENABLE_DEBUG (0) |
|
#include "debug.h" |
|
|
|
/* ignore file in case no PWM devices are defined */ |
|
#if PWM_NUMOF |
|
|
|
typedef struct pwm_chan_s { |
|
const unsigned long timer_sysctl; |
|
const unsigned long timer_base; |
|
const unsigned long gpio_sysctl; |
|
const unsigned long gpio_port; |
|
const uint8_t gpio_pin_num; |
|
const unsigned long timer_cfg; |
|
const unsigned long timer_side; |
|
const unsigned long ccp; |
|
} pwm_dev_t; |
|
|
|
typedef struct pwm_settings_s { |
|
unsigned long ticks; |
|
unsigned long resolution; |
|
} pwm_settings_t; |
|
|
|
|
|
static pwm_settings_t pwm_settings[PWM_NUMOF]; |
|
|
|
static const pwm_dev_t pwm_devs[PWM_NUMOF] = { |
|
{ /* 0 */ |
|
.timer_sysctl = SYSCTL_PERIPH_TIMER0, |
|
.timer_base = TIMER0_BASE, |
|
.gpio_sysctl = SYSCTL_PERIPH_GPIOB, |
|
.gpio_port = GPIO_PORTB_BASE, |
|
.gpio_pin_num = GPIO_PIN_6, |
|
.timer_side = TIMER_A, |
|
.timer_cfg = TIMER_CFG_A_PWM, |
|
.ccp = GPIO_PB6_T0CCP0, |
|
}, |
|
{ /* 1 */ |
|
.timer_sysctl = SYSCTL_PERIPH_TIMER0, |
|
.timer_base = TIMER0_BASE, |
|
.gpio_sysctl = SYSCTL_PERIPH_GPIOB, |
|
.gpio_port = GPIO_PORTB_BASE, |
|
.gpio_pin_num = GPIO_PIN_7, |
|
.timer_side = TIMER_B, |
|
.timer_cfg = TIMER_CFG_B_PWM, |
|
.ccp = GPIO_PB7_T0CCP1, |
|
}, |
|
{ /* 2 */ |
|
.timer_sysctl = SYSCTL_PERIPH_TIMER1, |
|
.timer_base = TIMER1_BASE, |
|
.gpio_sysctl = SYSCTL_PERIPH_GPIOB, |
|
.gpio_port = GPIO_PORTB_BASE, |
|
.gpio_pin_num = GPIO_PIN_4, |
|
.timer_cfg = TIMER_CFG_A_PWM, |
|
.timer_side = TIMER_A, |
|
.ccp = GPIO_PB4_T1CCP0, |
|
}, |
|
{ /* 3 */ |
|
.timer_sysctl = SYSCTL_PERIPH_TIMER1, |
|
.timer_base = TIMER1_BASE, |
|
.gpio_sysctl = SYSCTL_PERIPH_GPIOB, |
|
.gpio_port = GPIO_PORTB_BASE, |
|
.gpio_pin_num = GPIO_PIN_5, |
|
.timer_cfg = TIMER_CFG_B_PWM, |
|
.timer_side = TIMER_B, |
|
.ccp = GPIO_PB5_T1CCP1, |
|
}, |
|
|
|
/* Other PWM that can be used, but current PWM interface only allows for 4 devices */ |
|
{ /* 4 */ |
|
.timer_sysctl = SYSCTL_PERIPH_TIMER2, |
|
.timer_base = TIMER2_BASE, |
|
.gpio_sysctl = SYSCTL_PERIPH_GPIOB, |
|
.gpio_port = GPIO_PORTB_BASE, |
|
.gpio_pin_num = GPIO_PIN_0, |
|
.timer_cfg = TIMER_CFG_A_PWM, |
|
.timer_side = TIMER_A, |
|
.ccp = GPIO_PB0_T2CCP0, |
|
}, |
|
{ /* 5 */ |
|
.timer_sysctl = SYSCTL_PERIPH_TIMER1, |
|
.timer_base = TIMER1_BASE, |
|
.gpio_sysctl = SYSCTL_PERIPH_GPIOB, |
|
.gpio_port = GPIO_PORTB_BASE, |
|
.gpio_pin_num = GPIO_PIN_1, |
|
.timer_cfg = TIMER_CFG_B_PWM, |
|
.timer_side = TIMER_B, |
|
.ccp = GPIO_PB1_T2CCP1, |
|
}, |
|
{ /* 6 */ |
|
.timer_sysctl = SYSCTL_PERIPH_TIMER2, |
|
.timer_base = TIMER2_BASE, |
|
.gpio_sysctl = SYSCTL_PERIPH_GPIOB, |
|
.gpio_port = GPIO_PORTB_BASE, |
|
.gpio_pin_num = GPIO_PIN_2, |
|
.timer_cfg = TIMER_CFG_A_PWM, |
|
.timer_side = TIMER_A, |
|
.ccp = GPIO_PB2_T3CCP0, |
|
}, |
|
{ /* 7 */ |
|
.timer_sysctl = SYSCTL_PERIPH_TIMER2, |
|
.timer_base = TIMER2_BASE, |
|
.gpio_sysctl = SYSCTL_PERIPH_GPIOB, |
|
.gpio_port = GPIO_PORTB_BASE, |
|
.gpio_pin_num = GPIO_PIN_3, |
|
.timer_cfg = TIMER_CFG_B_PWM, |
|
.timer_side = TIMER_B, |
|
.ccp = GPIO_PB3_T3CCP1, |
|
} |
|
}; |
|
|
|
static void pwm_start(pwm_t dev) |
|
{ |
|
const unsigned long timer_base = pwm_devs[dev].timer_base; |
|
const unsigned long timer_ab = pwm_devs[dev].timer_side; |
|
|
|
ROM_TimerEnable(timer_base, timer_ab); |
|
} |
|
|
|
uint32_t pwm_init(pwm_t dev, pwm_mode_t mode, |
|
uint32_t frequency, uint16_t resolution) |
|
{ |
|
if (dev >= PWM_NUMOF) { |
|
return -1; |
|
} |
|
|
|
pwm_poweron(dev); |
|
|
|
const unsigned long timer_pwm_cfg = pwm_devs[dev].timer_cfg; |
|
const unsigned long timer_base = pwm_devs[dev].timer_base; |
|
const unsigned long gpio_pin = pwm_devs[dev].gpio_pin_num; |
|
const unsigned long ccp = pwm_devs[dev].ccp; |
|
const unsigned long timer_ab = pwm_devs[dev].timer_side; |
|
const unsigned long gpio_sysctl = pwm_devs[dev].gpio_sysctl; |
|
const unsigned long gpio_port_base = pwm_devs[dev].gpio_port; |
|
|
|
ROM_SysCtlPeripheralEnable(gpio_sysctl); |
|
|
|
ROM_GPIOPinConfigure(ccp); |
|
ROM_GPIOPinTypePWM(gpio_port_base, gpio_pin); |
|
|
|
ROM_TimerDisable(timer_base, timer_ab); |
|
ROM_TimerConfigure(timer_base, TIMER_CFG_SPLIT_PAIR | timer_pwm_cfg); |
|
|
|
const unsigned long clock = ROM_SysCtlClockGet(); |
|
|
|
const unsigned long ticks = clock / frequency; |
|
const unsigned long real_freq = frequency; |
|
|
|
pwm_settings[dev].resolution = resolution; |
|
pwm_settings[dev].ticks = ticks; |
|
|
|
unsigned long ticks_high = ticks >> 16; |
|
unsigned long ticks_low = ticks & 0xFFFF; |
|
|
|
if (ticks_high & ~0xFF) { |
|
DEBUG("Frequency too low\n"); |
|
return -1; |
|
} |
|
|
|
DEBUG("Prescaler/Load set at 0x%lx/0x%lx\n", ticks_high, ticks_low); |
|
|
|
ROM_TimerPrescaleSet(timer_base, timer_ab, ticks_high); |
|
ROM_TimerLoadSet(timer_base, timer_ab, ticks_low); |
|
|
|
DEBUG("Setting ticks at %lu (reqf: %" PRIu32 ", freq cpu: %lu)\n", ticks, frequency, clock); |
|
DEBUG("Real freq: %lu\n", real_freq); |
|
DEBUG("Resolution: %" PRIu16 "\n", resolution); |
|
ROM_TimerPrescaleMatchSet(timer_base, timer_ab, 0); |
|
ROM_TimerMatchSet(timer_base, timer_ab, 0); |
|
|
|
/* set PWM mode */ |
|
unsigned int level; |
|
switch (mode) { |
|
case PWM_LEFT: |
|
level = 0; |
|
break; |
|
case PWM_RIGHT: |
|
level = 1; |
|
break; |
|
case PWM_CENTER: |
|
default: |
|
return -1; |
|
} |
|
ROM_TimerControlLevel(timer_base, timer_ab, level); |
|
|
|
pwm_start(dev); |
|
|
|
return real_freq; |
|
} |
|
|
|
uint8_t pwm_channels(pwm_t dev) { |
|
return 1; |
|
} |
|
|
|
static void dump_pwm(pwm_t dev, int channel){ |
|
#if DEBUG_ENABLE |
|
if (channel >= PWM_MAX_CHANNELS) { |
|
return; |
|
} |
|
|
|
const unsigned long timer_base = pwm_devs[dev].timer_base; |
|
const unsigned long timer_ab = pwm_devs[dev].timer_side; |
|
const unsigned long ticks_low = ROM_TimerLoadGet(timer_base, timer_ab); |
|
const unsigned long ticks_high = ROM_TimerPrescaleGet(timer_base, timer_ab); |
|
const unsigned long long ticks = (ticks_high << 16) | ticks_low; |
|
const unsigned long duty_ticks_low = ROM_TimerMatchGet(timer_base, timer_ab); |
|
const unsigned long duty_ticks_high = ROM_TimerPrescaleMatchGet(timer_base, timer_ab); |
|
const unsigned long long duty = (duty_ticks_high << 16) | duty_ticks_low; |
|
|
|
DEBUG("MATCH Prescaler/Load set at 0x%lx/0x%lx\n", duty_ticks_high, duty_ticks_low); |
|
DEBUG("LOAD Prescaler/Load set at 0x%lx/0x%lx\n", ticks_high, ticks_low); |
|
DEBUG("LOAD duty cycle : %lu%%\n", |
|
(unsigned long) ((unsigned long long)(ticks - duty) * 100ULL / ticks)); |
|
#endif |
|
} |
|
|
|
|
|
void pwm_set(pwm_t dev, uint8_t channel, uint16_t value) |
|
{ |
|
if (channel >= PWM_MAX_CHANNELS) { |
|
return; |
|
} |
|
|
|
const unsigned long timer_base = pwm_devs[dev].timer_base; |
|
const unsigned long timer_ab = pwm_devs[dev].timer_side; |
|
|
|
const unsigned long real_resolution = pwm_settings[dev].ticks; |
|
const unsigned long requested_value = pwm_settings[dev].resolution - value; |
|
const unsigned long requested_resolution = pwm_settings[dev].resolution; |
|
/* scale value wrt resolution */ |
|
const unsigned long scaled_value = (real_resolution / requested_resolution) * requested_value; |
|
|
|
DEBUG("Req resolution %lu\n", requested_resolution); |
|
DEBUG("Req value %lu\n", requested_value); |
|
DEBUG("Real resolution %lu\n", real_resolution); |
|
|
|
DEBUG("Set, scaled (for %u) %lu\n", value, scaled_value); |
|
const unsigned long ticks_high = (scaled_value & 0xFFFF0000) >> 16; |
|
const unsigned long ticks_low = scaled_value & 0x0000FFFF; |
|
|
|
dump_pwm(dev, channel); |
|
|
|
ROM_TimerPrescaleMatchSet(timer_base, timer_ab, ticks_high); |
|
ROM_TimerMatchSet(timer_base, timer_ab, ticks_low); |
|
|
|
dump_pwm(dev, channel); |
|
} |
|
|
|
|
|
/* static void pwm_stop(pwm_t dev) */ |
|
/* { */ |
|
/* const unsigned long timer_base = pwm_devs[dev].timer_base; */ |
|
/* const unsigned long timer_ab = pwm_devs[dev].timer_side; */ |
|
|
|
/* ROM_TimerDisable(timer_base, timer_ab); */ |
|
/* } */ |
|
|
|
void pwm_poweron(pwm_t dev) |
|
{ |
|
const unsigned long timer_sysctl = pwm_devs[dev].timer_sysctl; |
|
|
|
ROM_SysCtlPeripheralEnable(timer_sysctl); |
|
} |
|
|
|
void pwm_poweroff(pwm_t dev) |
|
{ |
|
const unsigned long timer_sysctl = pwm_devs[dev].timer_sysctl; |
|
|
|
ROM_SysCtlPeripheralDisable(timer_sysctl); |
|
} |
|
|
|
#endif /* PWM_NUMOF */
|
|
|