From 3ae8c917d00043df267338d1ac4cc799f3d3396c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucher?= <cfoucher@laas.fr> Date: Fri, 4 Feb 2022 09:42:29 +0000 Subject: [PATCH] Implement incremental encoder acquisition in Timer module using Timer 4. --- zephyr/dts/pinout.dtsi | 9 + .../zephyr/public_include/timer.h | 72 +++++++- .../zephyr/src/stm32_timer_driver.c | 155 +++++++++++++++--- .../zephyr/src/stm32_timer_driver.h | 39 +++-- zephyr/nucleo_g474re.overlay | 6 + 5 files changed, 242 insertions(+), 39 deletions(-) create mode 100644 zephyr/dts/pinout.dtsi diff --git a/zephyr/dts/pinout.dtsi b/zephyr/dts/pinout.dtsi new file mode 100644 index 0000000..66bc802 --- /dev/null +++ b/zephyr/dts/pinout.dtsi @@ -0,0 +1,9 @@ +/ { + soc { + pinctrl: pin-controller@48000000 { + tim4_etr_pb3: tim4_etr_pb3 { + pinmux = <STM32_PINMUX('B', 3, AF2)>; + }; + }; + }; +}; \ No newline at end of file diff --git a/zephyr/modules/owntech_timer_driver/zephyr/public_include/timer.h b/zephyr/modules/owntech_timer_driver/zephyr/public_include/timer.h index 73b7aa9..75a1719 100644 --- a/zephyr/modules/owntech_timer_driver/zephyr/public_include/timer.h +++ b/zephyr/modules/owntech_timer_driver/zephyr/public_include/timer.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 LAAS-CNRS + * Copyright (c) 2021-2022 LAAS-CNRS * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -18,7 +18,20 @@ */ /** - * @author Clément Foucher <clement.foucher@laas.fr> + * @date 2022 + * @author Clément Foucher <clement.foucher@laas.fr> + * + * @brief This file is the public include file for the + * Zephyr Timer driver. It provides basic functionnality + * to handle STM32 Timers. Is is for now specific + * to certain capabilities of G4 series Timers, + * and mainly restricted to the use we do of + * the timers in the OwnTech project, but it aims + * at becoming more generic over time. + * + * This version suports: + * * Timer 6 and Timer 7: Periodic call of a callback function with period ranging from 2 to 6553 µs. + * * Timer 4: Incremental coder acquisition with pinout: reset=PB3; CH1=PB6; CH2=PB7. */ #ifndef TIMER_H_ @@ -39,16 +52,44 @@ extern "C" { typedef void (*timer_callback_t)(); +typedef enum +{ + no_pull, + pull_up, + pull_down +} pin_mode_t; + +/** + * timer_enable_irq : set to 1 to enable interrupt on timer overflow. + * timer_enable_encoder : set to 1 for timer to act as an incremental coder counter. + * + * *** IRQ mode (ignored if timer_enable_irq=0) *** + * - timer_irq_callback : pointer to a void(void) function that will be + * called on timer overflow. + * - timer_irq_t_usec : period of the interrupt in microsecond (2 to 6553 µs) + * + * *** Incremental code mode (ignored if timer_enable_encoder=0) *** + * - timer_pin_mode : Pin mode for incremental coder interface. + * + * NOTE: At this time, only irq mode is supported on TIM6/TIM7, and + * only incremental coder mode is suppported on TIM4, which makes this + * configuration structure almost pointless (except for callback definition). + * However, it is built this way with future evolutions of the driver + * in mind. + */ struct timer_config_t { - uint32_t timer_enable_irq : 1; - timer_callback_t timer_callback; + uint32_t timer_enable_irq : 1; + uint32_t timer_enable_encoder : 1; + timer_callback_t timer_irq_callback; + uint32_t timer_irq_t_usec; + pin_mode_t timer_enc_pin_mode; }; // API typedef void (*timer_api_config) (const struct device* dev, const struct timer_config_t* config); -typedef void (*timer_api_start) (const struct device* dev, uint32_t t_usec); +typedef void (*timer_api_start) (const struct device* dev); typedef uint32_t (*timer_api_get_count)(const struct device* dev); __subsystem struct timer_driver_api @@ -59,6 +100,12 @@ __subsystem struct timer_driver_api }; +/** + * Configure the timer dev using given configuration structure config. + * + * @param dev Zephyr device representing the timer. + * @param config Configuration holding the timer configuration. + */ static inline void timer_config(const struct device* dev, const struct timer_config_t* config) { const struct timer_driver_api* api = (const struct timer_driver_api*)(dev->api); @@ -66,13 +113,24 @@ static inline void timer_config(const struct device* dev, const struct timer_con api->config(dev, config); } -static inline void timer_start(const struct device* dev, uint32_t t_usec) +/** + * Configure the timer dev with given time t_usec in microseconds. + * + * @param dev Zephyr device representing the timer. + */ +static inline void timer_start(const struct device* dev) { const struct timer_driver_api* api = (const struct timer_driver_api*)(dev->api); - api->start(dev, t_usec); + api->start(dev); } +/** + * Get the current timer counter value. + * + * @param dev Zephyr device representing the timer. + * @return Current value of the timer internal counter. + */ static inline uint32_t timer_get_count(const struct device* dev) { const struct timer_driver_api* api = (const struct timer_driver_api*)(dev->api); diff --git a/zephyr/modules/owntech_timer_driver/zephyr/src/stm32_timer_driver.c b/zephyr/modules/owntech_timer_driver/zephyr/src/stm32_timer_driver.c index defa247..98bc4fe 100644 --- a/zephyr/modules/owntech_timer_driver/zephyr/src/stm32_timer_driver.c +++ b/zephyr/modules/owntech_timer_driver/zephyr/src/stm32_timer_driver.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 LAAS-CNRS + * Copyright (c) 2021-2022 LAAS-CNRS * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -18,13 +18,15 @@ */ /** - * @author Clément Foucher <clement.foucher@laas.fr> + * @date 2022 + * @author Clément Foucher <clement.foucher@laas.fr> */ // STM32 LL #include <stm32_ll_tim.h> #include <stm32_ll_bus.h> +#include <stm32_ll_gpio.h> // Current file header #include "stm32_timer_driver.h" @@ -37,7 +39,9 @@ static int timer_stm32_init(const struct device* dev) { TIM_TypeDef* tim_dev = ((struct stm32_timer_driver_data*)dev->data)->timer_struct; - if (tim_dev == TIM6) + if (tim_dev == TIM4) + init_timer_4(); + else if (tim_dev == TIM6) init_timer_6(); else if (tim_dev == TIM7) init_timer_7(); @@ -58,9 +62,9 @@ static void timer_stm32_callback(const void* arg) timer_stm32_clear(timer_dev); - if (data->timer_callback != NULL) + if (data->timer_irq_callback != NULL) { - data->timer_callback(); + data->timer_irq_callback(); } } @@ -80,26 +84,81 @@ void timer_stm32_config(const struct device* dev, const struct timer_config_t* c struct stm32_timer_driver_data* data = (struct stm32_timer_driver_data*)dev->data; TIM_TypeDef* tim_dev = data->timer_struct; - if (tim_dev != NULL) + if ( (tim_dev == TIM6) || (tim_dev == TIM7) ) { if (config->timer_enable_irq == 1) { - data->timer_callback = config->timer_callback; + data->timer_mode = periodic_interrupt; + data->timer_irq_callback = config->timer_irq_callback; + data->timer_irq_period_usec = config->timer_irq_t_usec; irq_connect_dynamic(data->interrupt_line, data->interrupt_prio, timer_stm32_callback, dev, 0); irq_enable(data->interrupt_line); } } + else if (tim_dev == TIM4) + { + if (config->timer_enable_encoder == 1) + { + data->timer_mode = incremental_coder; + + uint32_t pull = 0; + switch (config->timer_enc_pin_mode) + { + case no_pull: + pull = LL_GPIO_PULL_NO; + break; + case pull_up: + pull = LL_GPIO_PULL_UP; + break; + case pull_down: + pull = LL_GPIO_PULL_DOWN; + break; + } + + // Configure GPIO + LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOB); + + LL_GPIO_SetPinMode(GPIOB,LL_GPIO_PIN_3,LL_GPIO_MODE_ALTERNATE); + LL_GPIO_SetPinSpeed(GPIOB,LL_GPIO_PIN_3,LL_GPIO_SPEED_FREQ_LOW); + LL_GPIO_SetPinOutputType(GPIOB,LL_GPIO_PIN_3,LL_GPIO_OUTPUT_PUSHPULL); + LL_GPIO_SetPinPull(GPIOB,LL_GPIO_PIN_3,pull); + LL_GPIO_SetAFPin_0_7(GPIOB,LL_GPIO_PIN_3,LL_GPIO_AF_2); + + LL_GPIO_SetPinMode(GPIOB,LL_GPIO_PIN_6,LL_GPIO_MODE_ALTERNATE); + LL_GPIO_SetPinSpeed(GPIOB,LL_GPIO_PIN_6,LL_GPIO_SPEED_FREQ_LOW); + LL_GPIO_SetPinOutputType(GPIOB,LL_GPIO_PIN_6,LL_GPIO_OUTPUT_PUSHPULL); + LL_GPIO_SetPinPull(GPIOB,LL_GPIO_PIN_6,pull); + LL_GPIO_SetAFPin_0_7(GPIOB,LL_GPIO_PIN_6,LL_GPIO_AF_2); + + LL_GPIO_SetPinMode(GPIOB,LL_GPIO_PIN_7,LL_GPIO_MODE_ALTERNATE); + LL_GPIO_SetPinSpeed(GPIOB,LL_GPIO_PIN_7,LL_GPIO_SPEED_FREQ_LOW); + LL_GPIO_SetPinOutputType(GPIOB,LL_GPIO_PIN_7,LL_GPIO_OUTPUT_PUSHPULL); + LL_GPIO_SetPinPull(GPIOB,LL_GPIO_PIN_7,pull); + LL_GPIO_SetAFPin_0_7(GPIOB,LL_GPIO_PIN_7,LL_GPIO_AF_2); + } + } } -void timer_stm32_start(const struct device* dev, uint32_t t_usec) +void timer_stm32_start(const struct device* dev) { - TIM_TypeDef* tim_dev = ((struct stm32_timer_driver_data*)dev->data)->timer_struct; + struct stm32_timer_driver_data* data = (struct stm32_timer_driver_data*)dev->data; + TIM_TypeDef* tim_dev = data->timer_struct; - if (tim_dev != NULL) + if ( (tim_dev == TIM6) || (tim_dev == TIM7) ) { - LL_TIM_SetAutoReload(tim_dev, (t_usec*10) - 1); - LL_TIM_EnableIT_UPDATE(tim_dev); - LL_TIM_EnableCounter(tim_dev); + if (data->timer_mode == periodic_interrupt) + { + LL_TIM_SetAutoReload(tim_dev, (data->timer_irq_period_usec*10) - 1); + LL_TIM_EnableIT_UPDATE(tim_dev); + LL_TIM_EnableCounter(tim_dev); + } + } + else if (tim_dev == TIM4) + { + if (data->timer_mode == incremental_coder) + { + LL_TIM_EnableCounter(tim_dev); + } } } @@ -124,6 +183,37 @@ uint32_t timer_stm32_get_count(const struct device* dev) ///// // Per-timer inits +void init_timer_4() +{ + // Configure Timer in incremental coder mode + + // Peripheral clock enable + LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM4); + + LL_TIM_InitTypeDef TIM_InitStruct = {0}; + TIM_InitStruct.Prescaler = 0; + TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP; + TIM_InitStruct.Autoreload = 65535; + TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1; + + LL_TIM_Init(TIM4, &TIM_InitStruct); + LL_TIM_EnableARRPreload(TIM4); + LL_TIM_SetEncoderMode(TIM4, LL_TIM_ENCODERMODE_X4_TI12); + LL_TIM_IC_SetActiveInput(TIM4, LL_TIM_CHANNEL_CH1, LL_TIM_ACTIVEINPUT_DIRECTTI); + LL_TIM_IC_SetPrescaler(TIM4, LL_TIM_CHANNEL_CH1, LL_TIM_ICPSC_DIV1); + LL_TIM_IC_SetFilter(TIM4, LL_TIM_CHANNEL_CH1, LL_TIM_IC_FILTER_FDIV16_N5); + LL_TIM_IC_SetPolarity(TIM4, LL_TIM_CHANNEL_CH1, LL_TIM_IC_POLARITY_RISING); + LL_TIM_IC_SetActiveInput(TIM4, LL_TIM_CHANNEL_CH2, LL_TIM_ACTIVEINPUT_DIRECTTI); + LL_TIM_IC_SetPrescaler(TIM4, LL_TIM_CHANNEL_CH2, LL_TIM_ICPSC_DIV1); + LL_TIM_IC_SetFilter(TIM4, LL_TIM_CHANNEL_CH2, LL_TIM_IC_FILTER_FDIV1); + LL_TIM_IC_SetPolarity(TIM4, LL_TIM_CHANNEL_CH2, LL_TIM_IC_POLARITY_RISING); + LL_TIM_SetTriggerOutput(TIM4, LL_TIM_TRGO_RESET); + LL_TIM_DisableMasterSlaveMode(TIM4); + LL_TIM_ConfigETR(TIM4, LL_TIM_ETR_POLARITY_NONINVERTED, LL_TIM_ETR_PRESCALER_DIV1, LL_TIM_ETR_FILTER_FDIV1); + LL_TIM_ConfigIDX(TIM4, LL_TIM_INDEX_ALL|LL_TIM_INDEX_POSITION_DOWN_DOWN|LL_TIM_INDEX_UP_DOWN); + LL_TIM_EnableEncoderIndex(TIM4); +} + void init_timer_6() { LL_TIM_InitTypeDef TIM_InitStruct = {0}; @@ -164,15 +254,38 @@ void init_timer_7() ///// // Device definitions +// Timer4 +#if DT_NODE_HAS_STATUS(TIMER4_NODELABEL, okay) + +struct stm32_timer_driver_data timer4_data = +{ + .timer_struct = TIM4, + .interrupt_line = TIMER4_INTERRUPT_LINE, + .interrupt_prio = TIMER4_INTERRUPT_PRIO, + .timer_irq_callback = NULL +}; + +DEVICE_DT_DEFINE(TIMER4_NODELABEL, + timer_stm32_init, + NULL, + &timer4_data, + NULL, + PRE_KERNEL_1, + CONFIG_KERNEL_INIT_PRIORITY_DEVICE, + &timer_funcs + ); + +#endif // Timer 4 + // Timer 6 #if DT_NODE_HAS_STATUS(TIMER6_NODELABEL, okay) struct stm32_timer_driver_data timer6_data = { - .timer_struct = TIM6, - .interrupt_line = TIMER6_INTERRUPT_LINE, - .interrupt_prio = TIMER6_INTERRUPT_PRIO, - .timer_callback = NULL + .timer_struct = TIM6, + .interrupt_line = TIMER6_INTERRUPT_LINE, + .interrupt_prio = TIMER6_INTERRUPT_PRIO, + .timer_irq_callback = NULL }; DEVICE_DT_DEFINE(TIMER6_NODELABEL, @@ -192,10 +305,10 @@ DEVICE_DT_DEFINE(TIMER6_NODELABEL, struct stm32_timer_driver_data timer7_data = { - .timer_struct = TIM7, - .interrupt_line = TIMER7_INTERRUPT_LINE, - .interrupt_prio = TIMER7_INTERRUPT_PRIO, - .timer_callback = NULL + .timer_struct = TIM7, + .interrupt_line = TIMER7_INTERRUPT_LINE, + .interrupt_prio = TIMER7_INTERRUPT_PRIO, + .timer_irq_callback = NULL }; DEVICE_DT_DEFINE(TIMER7_NODELABEL, diff --git a/zephyr/modules/owntech_timer_driver/zephyr/src/stm32_timer_driver.h b/zephyr/modules/owntech_timer_driver/zephyr/src/stm32_timer_driver.h index 988b352..d6afa02 100644 --- a/zephyr/modules/owntech_timer_driver/zephyr/src/stm32_timer_driver.h +++ b/zephyr/modules/owntech_timer_driver/zephyr/src/stm32_timer_driver.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 LAAS-CNRS + * Copyright (c) 2021-2022 LAAS-CNRS * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -18,7 +18,8 @@ */ /** - * @author Clément Foucher <clement.foucher@laas.fr> + * @date 2022 + * @author Clément Foucher <clement.foucher@laas.fr> */ #ifndef STM32_TIMER_DRIVER_H_ #define STM32_TIMER_DRIVER_H_ @@ -36,6 +37,10 @@ extern "C" { #endif +#define TIMER4_NODELABEL DT_NODELABEL(timers4) +#define TIMER4_INTERRUPT_LINE DT_IRQN(TIMER4_NODELABEL) +#define TIMER4_INTERRUPT_PRIO DT_IRQ_BY_IDX(TIMER4_NODELABEL, 0, priority) + #define TIMER6_NODELABEL DT_NODELABEL(timers6) #define TIMER6_INTERRUPT_LINE DT_IRQN(TIMER6_NODELABEL) #define TIMER6_INTERRUPT_PRIO DT_IRQ_BY_IDX(TIMER6_NODELABEL, 0, priority) @@ -44,30 +49,42 @@ extern "C" { #define TIMER7_INTERRUPT_LINE DT_IRQN(TIMER7_NODELABEL) #define TIMER7_INTERRUPT_PRIO DT_IRQ_BY_IDX(TIMER7_NODELABEL, 0, priority) +typedef enum +{ + periodic_interrupt, + incremental_coder +} timer_mode_t; + + /** * Members of this structure marked with a "§" * have to be set when calling DEVICE_DEFINE. * - * timer_struct§: store the STM32 LL timer structure - * interrupt_line§: interrupt line number (if interrupt has to be enabled) - * interrupt_prio$: interrupt priority (if interrupt has to be enabled) - * timer_callback: user-defined, set by the timer_config call (if interrupt has to be enabled). - * Should be set to NULL in DEVICE_DEFINE + * timer_struct§: stores the STM32 LL timer structure + * interrupt_line§: interrupt line number (if interrupt has to be enabled) + * interrupt_prio$: interrupt priority (if interrupt has to be enabled) + * timer_mode: Mode in which the timer is configured. + * timer_irq_callback: user-defined, set by the timer_config call (if interrupt has to be enabled). + * Should be set to NULL in DEVICE_DEFINE + * timer_irq_period_usec : period of the irq in microseconds. */ struct stm32_timer_driver_data { TIM_TypeDef* timer_struct; - unsigned int interrupt_line; - unsigned int interrupt_prio; - timer_callback_t timer_callback; + unsigned int interrupt_line; + unsigned int interrupt_prio; + timer_mode_t timer_mode; + timer_callback_t timer_irq_callback; + uint32_t timer_irq_period_usec; }; static int timer_stm32_init(const struct device* dev); void timer_stm32_config(const struct device* dev, const struct timer_config_t* config); -void timer_stm32_start(const struct device* dev, uint32_t t_usec); +void timer_stm32_start(const struct device* dev); uint32_t timer_stm32_get_count(const struct device* dev); void timer_stm32_clear(const struct device* dev); +void init_timer_4(); void init_timer_6(); void init_timer_7(); diff --git a/zephyr/nucleo_g474re.overlay b/zephyr/nucleo_g474re.overlay index 780a9fc..9c5124f 100644 --- a/zephyr/nucleo_g474re.overlay +++ b/zephyr/nucleo_g474re.overlay @@ -1,3 +1,4 @@ +#include "dts/pinout.dtsi" #include "dts/hrtim.dtsi" #include "dts/adc-channels.dtsi" #include "dts/ngnd.dtsi" @@ -68,6 +69,11 @@ /* Timer */ /*********/ +&timers4 { + pinctrl-0 = <&tim4_etr_pb3 &tim4_ch1_pb6 &tim4_ch2_pb7 >; + status = "okay"; +}; + &timers6 { status = "okay"; }; -- GitLab