From 9d4accfe1ac375183c9a485b987fff852acba756 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Poulhi=C3=A8s?= Date: Tue, 3 May 2016 13:54:58 +0200 Subject: [PATCH] initial support for PWM output ATM, only on timer1/PB4, but more can be easily added (and will be) Signed-off-by: Marc --- boards/ek-lm4f120xl/Makefile.features | 1 + cpu/lm4f120/include/periph_cpu.h | 16 ++ cpu/lm4f120/periph/pwm.c | 302 ++++++++++++++++++++++++++ 3 files changed, 319 insertions(+) create mode 100644 cpu/lm4f120/periph/pwm.c diff --git a/boards/ek-lm4f120xl/Makefile.features b/boards/ek-lm4f120xl/Makefile.features index e7ffe1be3..6f8769e4c 100644 --- a/boards/ek-lm4f120xl/Makefile.features +++ b/boards/ek-lm4f120xl/Makefile.features @@ -2,6 +2,7 @@ FEATURES_PROVIDED += periph_adc FEATURES_PROVIDED += periph_gpio FEATURES_PROVIDED += periph_spi +FEATURES_PROVIDED += periph_pwm FEATURES_PROVIDED += periph_timer FEATURES_PROVIDED += periph_uart diff --git a/cpu/lm4f120/include/periph_cpu.h b/cpu/lm4f120/include/periph_cpu.h index bc36fc368..03a75df50 100644 --- a/cpu/lm4f120/include/periph_cpu.h +++ b/cpu/lm4f120/include/periph_cpu.h @@ -89,6 +89,22 @@ enum { PORT_F = 5, /**< port F */ }; +/** + * @name PWM configuration + * @{ + */ +#define PWM_NUMOF (8) +#define PWM_0_EN 1 +#define PWM_1_EN 1 +#define PWM_2_EN 1 +#define PWM_3_EN 1 +#define PWM_4_EN 1 +#define PWM_5_EN 1 +#define PWM_6_EN 1 +#define PWM_7_EN 1 +#define PWM_MAX_CHANNELS 1 +/** @} */ + /** * @brief Override resolution options */ diff --git a/cpu/lm4f120/periph/pwm.c b/cpu/lm4f120/periph/pwm.c new file mode 100644 index 000000000..bbf588741 --- /dev/null +++ b/cpu/lm4f120/periph/pwm.c @@ -0,0 +1,302 @@ +/* + * 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 + * + * @} + */ + +#include +#include + +#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 */