Commit 3ae8c917 authored by Clément Foucher's avatar Clément Foucher
Browse files

Implement incremental encoder acquisition in Timer module using Timer 4.

parent 7754eb6e
/ {
soc {
pinctrl: pin-controller@48000000 {
tim4_etr_pb3: tim4_etr_pb3 {
pinmux = <STM32_PINMUX('B', 3, AF2)>;
};
};
};
};
\ No newline at end of file
/*
* 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);
......
/*
* 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,
......
/*
* 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();
......
#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";
};
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment