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