diff --git a/boards/stm32f4discovery/include/periph_conf.h b/boards/stm32f4discovery/include/periph_conf.h index 47c5694cef..5a5d95ff10 100644 --- a/boards/stm32f4discovery/include/periph_conf.h +++ b/boards/stm32f4discovery/include/periph_conf.h @@ -148,33 +148,40 @@ * @name PWM configuration * @{ */ -#define PWM_NUMOF (0U) /* TODO !!!!!!! */ -#define PWM_0_EN 0 -#define PWM_1_EN 0 +#define PWM_NUMOF (2U) +#define PWM_0_EN 1 +#define PWM_1_EN 1 +#define PWM_MAX_CHANNELS 4 /* PWM 0 device configuration */ #define PWM_0_DEV TIM1 #define PWM_0_CHANNELS 4 +#define PWM_0_CLK (168000000U) +#define PWM_0_CLKEN() (RCC->APB2ENR |= RCC_APB2ENR_TIM1EN) +#define PWM_0_CLKDIS() (RCC->APB2ENR &= ~RCC_APB2ENR_TIM1EN) /* PWM 0 pin configuration */ -#define PWM_0_PORT -#define PWM_0_PINS -#define PWM_0_PORT_CLKEN() -#define PWM_0_CH1_AFCFG() -#define PWM_0_CH2_AFCFG() -#define PWM_0_CH3_AFCFG() -#define PWM_0_CH4_AFCFG() +#define PWM_0_PORT GPIOE +#define PWM_0_PORT_CLKEN() (RCC->AHB1ENR |= RCC_AHB1ENR_GPIOEEN) +#define PWM_0_PIN_CH0 9 +#define PWM_0_PIN_CH1 11 +#define PWM_0_PIN_CH2 13 +#define PWM_0_PIN_CH3 14 +#define PWM_0_PIN_AF 1 /* PWM 1 device configuration */ #define PWM_1_DEV TIM3 -#define PWM_1_CHANNELS 4 +#define PWM_1_CHANNELS 3 +#define PWM_1_CLK (84000000U) +#define PWM_1_CLKEN() (RCC->APB1ENR |= RCC_APB1ENR_TIM3EN) +#define PWM_1_CLKDIS() (RCC->APB1ENR &= ~RCC_APB1ENR_TIM3EN) /* PWM 1 pin configuration */ -#define PWM_1_PORT -#define PWM_1_PINS -#define PWM_1_PORT_CLKEN() -#define PWM_1_CH1_AFCFG() -#define PWM_1_CH2_AFCFG() -#define PWM_1_CH3_AFCFG() -#define PWM_1_CH4_AFCFG() +#define PWM_1_PORT GPIOB +#define PWM_1_PORT_CLKEN() (RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN) +#define PWM_1_PIN_CH0 4 +#define PWM_1_PIN_CH1 5 +#define PWM_1_PIN_CH2 0 +#define PWM_1_PIN_CH3 1 +#define PWM_1_PIN_AF 2 /** @} */ diff --git a/cpu/stm32f4/periph/pwm.c b/cpu/stm32f4/periph/pwm.c new file mode 100644 index 0000000000..ffc5e7a446 --- /dev/null +++ b/cpu/stm32f4/periph/pwm.c @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2014 Freie Universität Berlin + * + * 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_stm32f4 + * @{ + * + * @file + * @brief Low-level GPIO driver implementation + * + * @author Hauke Petersen + * + * @} + */ + +#include +#include + +#include "cpu.h" +#include "periph/pwm.h" +#include "periph_conf.h" + +/* ignore file in case no PWM devices are defined */ +#if PWM_NUMOF + +int pwm_init(pwm_t dev, pwm_mode_t mode, unsigned int frequency, unsigned int resolution) +{ + TIM_TypeDef *tim = NULL; + GPIO_TypeDef *port = NULL; + uint32_t pins[PWM_MAX_CHANNELS]; + uint32_t af = 0; + int channels = 0; + uint32_t pwm_clk = 0; + + pwm_poweron(dev); + + switch (dev) { +#if PWM_0_EN + case PWM_0: + tim = PWM_0_DEV; + port = PWM_0_PORT; + pins[0] = PWM_0_PIN_CH0; + pins[1] = PWM_0_PIN_CH1; + pins[2] = PWM_0_PIN_CH2; + pins[3] = PWM_0_PIN_CH3; + af = PWM_0_PIN_AF; + channels = PWM_0_CHANNELS; + pwm_clk = PWM_0_CLK; + PWM_0_PORT_CLKEN(); + break; +#endif +#if PWM_1_EN + case PWM_1: + tim = PWM_1_DEV; + port = PWM_1_PORT; + pins[0] = PWM_1_PIN_CH0; + pins[1] = PWM_1_PIN_CH1; + pins[2] = PWM_1_PIN_CH2; + pins[3] = PWM_1_PIN_CH3; + af = PWM_1_PIN_AF; + channels = PWM_1_CHANNELS; + pwm_clk = PWM_1_CLK; + PWM_1_PORT_CLKEN(); + break; +#endif + } + + /* setup pins: alternate function */ + for (int i = 0; i < channels; i++) { + port->MODER &= ~(3 << (pins[i] * 2)); + port->MODER |= (2 << (pins[i] * 2)); + if (pins[i] < 8) { + port->AFR[0] &= ~(0xf << (pins[i] * 4)); + port->AFR[0] |= (af << (pins[i] * 4)); + } else { + port->AFR[1] &= ~(0xf << ((pins[i] - 8) * 4)); + port->AFR[1] |= (af << ((pins[i] - 8) * 4)); + } + } + + /* reset timer configuration registers */ + tim->CR1 = 0; + tim->CR2 = 0; + tim->CCMR1 = 0; + tim->CCMR2 = 0; + + /* set c/c register to initial 0 */ + tim->CCR1 = 0; + tim->CCR2 = 0; + tim->CCR3 = 0; + tim->CCR4 = 0; + + /* set prescale and auto-reload registers to matching values for resolution and frequency */ + if (resolution > 0xffff || (resolution * frequency) > pwm_clk) { + return -2; + } + tim->PSC = (pwm_clk / (resolution * frequency)) - 1; + tim->ARR = resolution - 1; + + /* set PWM mode */ + switch (mode) { + case PWM_LEFT: + tim->CCMR1 |= (TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2 | + TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2M_2); + tim->CCMR2 |= (TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3M_2 | + TIM_CCMR2_OC4M_1 | TIM_CCMR2_OC4M_2); + break; + case PWM_RIGHT: + tim->CCMR1 |= (TIM_CCMR1_OC1M_0 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2 | + TIM_CCMR1_OC2M_0 | TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2M_2); + tim->CCMR2 |= (TIM_CCMR2_OC3M_0 | TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3M_2 | + TIM_CCMR2_OC4M_0 | TIM_CCMR2_OC4M_1 | TIM_CCMR2_OC4M_2); + break; + case PWM_CENTER: + tim->CR1 |= (TIM_CR1_CMS_0 | TIM_CR1_CMS_1); + break; + } + + /* enable output on PWM pins */ + tim->CCER = (TIM_CCER_CC1E | TIM_CCER_CC2E | TIM_CCER_CC3E | TIM_CCER_CC4E); + + /* enable PWM outputs */ + tim->BDTR = TIM_BDTR_MOE; + + /* enable timer ergo the PWM generation */ + pwm_start(dev); + + return 0; +} + +int pwm_set(pwm_t dev, int channel, unsigned int value) +{ + TIM_TypeDef *tim = NULL; + + switch (dev) { +#if PWM_0_EN + case PWM_0: + tim = PWM_0_DEV; + break; +#endif +#if PWM_1_EN + case PWM_1: + tim = PWM_1_DEV; + break; +#endif + } + + /* norm value to maximum possible value */ + if (value > 0xffff) { + value = 0xffff; + } + + switch (channel) { + case 0: + tim->CCR1 = value; + break; + case 1: + tim->CCR2 = value; + break; + case 2: + tim->CCR3 = value; + break; + case 3: + tim->CCR4 = value; + break; + default: + return -1; + } + + return 0; +} + +void pwm_start(pwm_t dev) +{ + switch (dev) { +#if PWM_0_EN + case PWM_0: + PWM_0_DEV->CR1 |= TIM_CR1_CEN; + break; +#endif +#if PWM_1_EN + case PWM_1: + PWM_1_DEV->CR1 |= TIM_CR1_CEN; + break; +#endif + } +} + +void pwm_stop(pwm_t dev) +{ + switch (dev) { +#if PWM_0_EN + case PWM_0: + PWM_0_DEV->CR1 &= ~(TIM_CR1_CEN); + break; +#endif +#if PWM_1_EN + case PWM_1: + PWM_1_DEV->CR1 &= ~(TIM_CR1_CEN); + break; +#endif + } +} + +void pwm_poweron(pwm_t dev) +{ + switch (dev) { +#if PWM_0_EN + case PWM_0: + PWM_0_CLKEN(); + break; +#endif +#if PWM_1_EN + case PWM_1: + PWM_1_CLKEN(); + break; +#endif + } +} + +void pwm_poweroff(pwm_t dev) +{ + switch (dev) { +#if PWM_0_EN + case PWM_0: + PWM_0_CLKDIS(); + break; +#endif +#if PWM_1_EN + case PWM_1: + PWM_1_CLKDIS(); + break; +#endif + } +} + +#endif /* PWM_NUMOF */ diff --git a/tests/periph_pwm/Makefile b/tests/periph_pwm/Makefile new file mode 100644 index 0000000000..bc2e352ffe --- /dev/null +++ b/tests/periph_pwm/Makefile @@ -0,0 +1,11 @@ +export APPLICATION = periph_pwm +include ../Makefile.tests_common + +BOARD_BLACKLIST := chronos mbed_lpc1768 msb-430 msb-430h native qemu-i386 redbee-econotag telosb \ + wsn430-v1_3b wsn430-v1_4 z1 +# all listed boards: no periph_conf.h defined, + +USEMODULE += vtimer +DISABLE_MODULE += auto_init + +include $(RIOTBASE)/Makefile.include diff --git a/tests/periph_pwm/README.md b/tests/periph_pwm/README.md new file mode 100644 index 0000000000..23b0c052e0 --- /dev/null +++ b/tests/periph_pwm/README.md @@ -0,0 +1,8 @@ +Expected result +=============== +If everything is running as supposed to, you should see a 1KHz PWM with oscillating duty cycle +on each channel of the selected PWM device. + +Background +========== +Test for the low-level PWM driver. diff --git a/tests/periph_pwm/main.c b/tests/periph_pwm/main.c new file mode 100644 index 0000000000..a3e4b3c313 --- /dev/null +++ b/tests/periph_pwm/main.c @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2014 Freie Universität Berlin + * + * 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 tests + * @{ + * + * @file + * @brief Test for low-level PWM drivers + * + * This test initializes the given PWM device to run at 1KHz with a 1000 step resolution. + * + * The PWM is then continuously oscillating it's duty cycle between 0% to 100% every 1s on + * every channel. + * + * @author Hauke Petersen + * + * @} + */ + +#include + +#include "cpu.h" +#include "board.h" +#include "vtimer.h" +#include "periph/pwm.h" + +/* only compile this test if PWM devices are defined */ +#if PWM_NUMOF + +#define WAIT (10000) +#define STEP (10) + +#define DEV PWM_0 +#define CHANNELS PWM_0_CHANNELS +#define MODE PWM_LEFT + +#define FREQU (1000U) +#define STEPS (1000U) + + +int main(void) +{ + int res; + int state = 0; + int step = STEP; + + puts("\nRIOT PWM test"); + puts("Connect an LED or scope to PWM pins to see something\n"); + + res = pwm_init(DEV, MODE, FREQU, STEPS); + if (res == 0) { + puts("PWM successfully initialized.\n"); + } + else { + puts("Errors while initializing PWM"); + return -1; + } + + while (1) { + for (int i = 0; i < CHANNELS; i++) { + pwm_set(DEV, i, state); + } + + state += step; + if (state <= 0 || state >= STEPS) { + step = -step; + } + + vtimer_usleep(WAIT); + } + + return 0; +} + +#else + +int main(void) +{ + puts("\nRIOT PWM test"); + puts("There are no PWM devices defined for this board!"); + + return 0; +} + +#endif /* PWM_NUMOF */