diff --git a/CMakeLists.txt b/CMakeLists.txt index 548f795e..d098d6aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,7 @@ target_sources(${PROJECT_NAME} PRIVATE src/core/io/GPIO.cpp src/core/io/I2C.cpp src/core/io/PWM.cpp + src/core/io/PWMInput.cpp src/core/io/UART.cpp src/core/io/SPI.cpp src/core/io/types/CANMessage.cpp @@ -57,6 +58,7 @@ if(COMPDEFS MATCHES "(.*)STM32F3xx(.*)") src/core/io/platform/f3xx/GPIOf3xx.cpp src/core/io/platform/f3xx/I2Cf3xx.cpp src/core/io/platform/f3xx/PWMf3xx.cpp + src/core/io/platform/f3xx/PWMInputf3xx.cpp src/core/io/platform/f3xx/UARTf3xx.cpp src/core/io/platform/f3xx/SPIf3xx.cpp src/core/dev/platform/f3xx/IWDGf3xx.cpp diff --git a/include/core/io/PWMInput.hpp b/include/core/io/PWMInput.hpp new file mode 100644 index 00000000..d1fec2f3 --- /dev/null +++ b/include/core/io/PWMInput.hpp @@ -0,0 +1,58 @@ + +#ifndef EVT_PWMINPUT_HPP +#define EVT_PWMINPUT_HPP + +#include + +namespace core::io { + +// Forward declarations: +// The different pins are hardware specific. Forward declaration to allow +// at compilation time the decision of which pins should be used. +enum class Pin; + +class PWMInput { + +public: + /** + * Setup the given pin for PWMInput usage. + * + * @param[in] pin The pin to setup for PWMInput + */ + PWMInput(Pin pin); + + /** + * Get the current duty cycle. + * + * @return The duty cycle of the PWM signal being read, as a percentage. + */ + virtual uint8_t getDutyCycle() = 0; + + /** + * Get the current period. + * + * @return The period the PWM signal being read is operating at in timer ticks. + */ + virtual uint32_t getPeriod() = 0; + + /** + * Get the current frequency. + * + * @return The frequency the PWM signal being read is operating at in Hz. + */ + virtual uint32_t getFrequency() = 0; + +protected: + /// The pin PWM input is on + Pin pin; + /// The duty cycle of the PWM input + uint8_t dutyCycle; + /// The period of the PWM input + uint32_t period; + /// The frequency of the PWM input + uint32_t frequency; +}; + +} // namespace core::io + +#endif diff --git a/include/core/io/platform/f3xx/PWMInputf3xx.hpp b/include/core/io/platform/f3xx/PWMInputf3xx.hpp new file mode 100644 index 00000000..30d2b320 --- /dev/null +++ b/include/core/io/platform/f3xx/PWMInputf3xx.hpp @@ -0,0 +1,51 @@ + +#ifndef EVT_PWMINPUTF3XX_HPP +#define EVT_PWMINPUTF3XX_HPP + +#include + +#include +#include +#include + +namespace core::io { + +/** + * PWM input for f3 + * Measures the duty cycle, frequency, and period of a PWM signal on a pin + */ +class PWMInputf3xx : public PWMInput { +public: + /** + * Setup the given pin for PWM Input usage. + * + * @param pin[in] The pin to setup for PWM Input Capture + */ + PWMInputf3xx(Pin pin); + + uint8_t getDutyCycle(); + + uint32_t getPeriod(); + + uint32_t getFrequency(); + + /// Called from HAL_TIM_IC_CaptureCallback to update duty cycle, period, and frequency + void handleCapture(TIM_HandleTypeDef* htim); + + /// Provides access to HAL handle for IRQ forwarding + TIM_HandleTypeDef* getHandle(); + +private: + /// HAL timer representation + TIM_HandleTypeDef halTIM; // hal timer handle + /// Channel for rising edge measurement + uint32_t directChannel; + /// Channel for falling edge measurement + uint32_t indirectChannel; + /// active channel + uint32_t activeChannel; +}; + +} // namespace core::io + +#endif diff --git a/include/core/manager.hpp b/include/core/manager.hpp index 2b6bd231..0c5cca53 100644 --- a/include/core/manager.hpp +++ b/include/core/manager.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -19,6 +20,7 @@ #define IWDG_SUPPORTED #define MCU_SUPPORTED #define PWM_SUPPORTED + #define PWM_INPUT_SUPPORTED #define RTC_SUPPORTED #define SPI_SUPPORTED #define UART_SUPPORTED @@ -31,6 +33,7 @@ #include #include #include + #include #include #include #include @@ -240,6 +243,21 @@ PWM& getPWM() { } #endif +/** + * Get an instance of a PWMInput pin. + * + * @param[in] pin The pin to attach to the PWMInput. + */ +#ifdef PWM_INPUT_SUPPORTED +template +PWMInput& getPWM_INPUT() { + #ifdef STM32F3xx + static PWMInputf3xx pwm_input(pin); + return pwm_input; + #endif +} +#endif + /** * Get an instance of a UART. * diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 2949ab70..883aa1a7 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -15,6 +15,7 @@ add_subdirectory(lcd) add_subdirectory(log) add_subdirectory(millis) add_subdirectory(pwm) +add_subdirectory(pwm_input) add_subdirectory(queue) add_subdirectory(read_write) add_subdirectory(rtc) diff --git a/samples/pwm_input/CMakeLists.txt b/samples/pwm_input/CMakeLists.txt new file mode 100644 index 00000000..93179064 --- /dev/null +++ b/samples/pwm_input/CMakeLists.txt @@ -0,0 +1,3 @@ +include(${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/evt-core_build.cmake) + +make_exe(pwm_input main.cpp) diff --git a/samples/pwm_input/main.cpp b/samples/pwm_input/main.cpp new file mode 100644 index 00000000..35ac7805 --- /dev/null +++ b/samples/pwm_input/main.cpp @@ -0,0 +1,39 @@ +/** + * Example of PWM input. + * This sample will measure the duty cycle, frequency, and period of a PWM signal. + */ + +#include +#include +#include +#include + +namespace io = core::io; +namespace time = core::time; + +int main(void) { + // Initialize system + core::platform::init(); + + // Setup UART + io::UART& uart = io::getUART(9600); + + uart.printf("Starting PWM input capture\n\r"); + + uint32_t Period = 0; // ICValue + uint32_t Frequency = 0; + uint8_t DutyCycle = 0; + + io::PWMInput& pwmInput = io::getPWM_INPUT(); + + while (1) { + Period = pwmInput.getPeriod(); + Frequency = pwmInput.getFrequency(); + DutyCycle = pwmInput.getDutyCycle(); + uart.printf("\n\rPeriod: %d\n\r", Period); + uart.printf("\n\rFrequency: %d\n\r", Frequency); + uart.printf("\n\rDuty: %2d%%\n\r", DutyCycle); + uart.printf("\n\r----------------------------------\n\r"); + time::wait(1000); + } +} diff --git a/src/core/io/PWMInput.cpp b/src/core/io/PWMInput.cpp new file mode 100644 index 00000000..5f538f98 --- /dev/null +++ b/src/core/io/PWMInput.cpp @@ -0,0 +1,12 @@ +#include + +namespace core::io { + +PWMInput::PWMInput(Pin pin) { // core::io::Pin pin + this->pin = pin; + this->dutyCycle = 0; + this->period = 0; + this->frequency = 0; +} + +} // namespace core::io diff --git a/src/core/io/platform/f3xx/PWMInputf3xx.cpp b/src/core/io/platform/f3xx/PWMInputf3xx.cpp new file mode 100644 index 00000000..f55969fd --- /dev/null +++ b/src/core/io/platform/f3xx/PWMInputf3xx.cpp @@ -0,0 +1,288 @@ + +#include +#include + +namespace core::io { + +// Global pointer to the active instance (one at a time) +static PWMInputf3xx* activePwmInput = nullptr; + +/** + * Get timer instance, direct channel, indirect channel, alternate function, + * triggerSource, irqNumber, and activeChannel associated with a pin. + * This information is pulled from the STM32F302x8 documentation. + * + * NOTE: PA_2 and PA_3 are used for UART Tx and Rx respectively + * + @param pin The pin to check the instance of +* @param instance The timer instance to assign to +* @param directChannel The direct channel to assign to +* @param indirectChannel The indirect channel to assign to +* @param alternateFunction The GPIO identifier for the function of the pin +* @param irqNumber IRQ number for NVIC +* @param activeChannel HAL enum for which channel will fire the interrupt + */ +static void getInputInstance(Pin pin, TIM_TypeDef** instance, uint32_t* directChannel, uint32_t* indirectChannel, + uint32_t* alternateFunction, uint32_t* triggerSource, IRQn_Type* irqNumber, + uint32_t* activeChannel) { + switch (pin) { +#if defined(STM32F302x8) + // TIM 1 Channel 1 Direct, Channel 2 Indirect + case Pin::PC_0: + *instance = TIM1; + *directChannel = TIM_CHANNEL_1; + *indirectChannel = TIM_CHANNEL_2; + *alternateFunction = GPIO_AF2_TIM1; + *triggerSource = TIM_TS_TI1FP1; + *irqNumber = TIM1_CC_IRQn; + *activeChannel = HAL_TIM_ACTIVE_CHANNEL_1; + break; + case Pin::PA_8: + *instance = TIM1; + *directChannel = TIM_CHANNEL_1; + *indirectChannel = TIM_CHANNEL_2; + *alternateFunction = GPIO_AF6_TIM1; + *triggerSource = TIM_TS_TI1FP1; + *irqNumber = TIM1_CC_IRQn; + *activeChannel = HAL_TIM_ACTIVE_CHANNEL_1; + break; + // TIM 1 Channel 2 Direct, Channel 1 Indirect + case Pin::PC_1: + *instance = TIM1; + *directChannel = TIM_CHANNEL_2; + *indirectChannel = TIM_CHANNEL_1; + *alternateFunction = GPIO_AF2_TIM1; + *triggerSource = TIM_TS_TI2FP2; + *irqNumber = TIM1_CC_IRQn; + *activeChannel = HAL_TIM_ACTIVE_CHANNEL_2; + break; + // TIM 2 Channel 1 Direct, Channel 2 Indirect + case Pin::PA_0: + *instance = TIM2; + *directChannel = TIM_CHANNEL_1; + *indirectChannel = TIM_CHANNEL_2; + *alternateFunction = GPIO_AF1_TIM2; + *triggerSource = TIM_TS_TI1FP1; + *irqNumber = TIM2_IRQn; + *activeChannel = HAL_TIM_ACTIVE_CHANNEL_1; + break; + case Pin::PA_5: + *instance = TIM2; + *directChannel = TIM_CHANNEL_1; + *indirectChannel = TIM_CHANNEL_2; + *alternateFunction = GPIO_AF1_TIM2; + *triggerSource = TIM_TS_TI1FP1; + *irqNumber = TIM2_IRQn; + *activeChannel = HAL_TIM_ACTIVE_CHANNEL_1; + break; + case Pin::PA_15: + *instance = TIM2; + *directChannel = TIM_CHANNEL_1; + *indirectChannel = TIM_CHANNEL_2; + *alternateFunction = GPIO_AF1_TIM2; + *triggerSource = TIM_TS_TI1FP1; + *irqNumber = TIM2_IRQn; + *activeChannel = HAL_TIM_ACTIVE_CHANNEL_1; + break; + // TIM 2 Channel 2 Direct, Channel 1 Indirect + case Pin::PB_3: + *instance = TIM2; + *directChannel = TIM_CHANNEL_2; + *indirectChannel = TIM_CHANNEL_1; + *alternateFunction = GPIO_AF1_TIM2; + *triggerSource = TIM_TS_TI2FP2; + *irqNumber = TIM2_IRQn; + *activeChannel = HAL_TIM_ACTIVE_CHANNEL_2; + break; + // TIM 15 Channel 1 Direct, Channel 2 Indirect + case Pin::PA_2: // PA2 is UART tx, don't use if using UART + *instance = TIM15; + *directChannel = TIM_CHANNEL_1; + *indirectChannel = TIM_CHANNEL_2; + *alternateFunction = GPIO_AF9_TIM15; + *triggerSource = TIM_TS_TI1FP1; + *irqNumber = TIM1_BRK_TIM15_IRQn; + *activeChannel = HAL_TIM_ACTIVE_CHANNEL_1; + break; + case Pin::PB_14: + *instance = TIM15; + *directChannel = TIM_CHANNEL_1; + *indirectChannel = TIM_CHANNEL_2; + *alternateFunction = GPIO_AF1_TIM15; + *triggerSource = TIM_TS_TI1FP1; + *irqNumber = TIM1_BRK_TIM15_IRQn; + *activeChannel = HAL_TIM_ACTIVE_CHANNEL_1; + break; + // TIM 15 Channel 2 Direct, Channel 1 Indirect + case Pin::PA_3: // PA3 is UART rx, don't use if using UART + *instance = TIM15; + *directChannel = TIM_CHANNEL_2; + *indirectChannel = TIM_CHANNEL_1; + *alternateFunction = GPIO_AF9_TIM15; + *triggerSource = TIM_TS_TI2FP2; + *irqNumber = TIM1_BRK_TIM15_IRQn; + *activeChannel = HAL_TIM_ACTIVE_CHANNEL_2; + break; + case Pin::PB_15: + *instance = TIM15; + *directChannel = TIM_CHANNEL_2; + *indirectChannel = TIM_CHANNEL_1; + *alternateFunction = GPIO_AF1_TIM15; + *triggerSource = TIM_TS_TI2FP2; + *irqNumber = TIM1_BRK_TIM15_IRQn; + *activeChannel = HAL_TIM_ACTIVE_CHANNEL_2; + break; +#endif + default: + *instance = NULL; + *directChannel = -1; + *indirectChannel = -1; + *alternateFunction = -1; + *triggerSource = -1; + *irqNumber = (IRQn_Type) -1; + *activeChannel = -1; + } +} + +PWMInputf3xx::PWMInputf3xx(Pin pin) : PWMInput(pin) { + TIM_TypeDef* instance; + uint32_t alternateFunction; + uint32_t triggerSource; + IRQn_Type irqNumber; + + getInputInstance(pin, + &instance, + &directChannel, + &indirectChannel, + &alternateFunction, + &triggerSource, + &irqNumber, + &activeChannel); + + TIM_ClockConfigTypeDef sClockSourceConfig = {0}; + TIM_SlaveConfigTypeDef sSlaveConfig = {0}; + TIM_IC_InitTypeDef sConfigIC = {0}; + TIM_MasterConfigTypeDef sMasterConfig = {0}; + + if (instance == TIM1) { + __HAL_RCC_TIM1_CLK_ENABLE(); + } else if (instance == TIM2) { + __HAL_RCC_TIM2_CLK_ENABLE(); + } else if (instance == TIM15) { + __HAL_RCC_TIM15_CLK_ENABLE(); + } + + // initilize timer base + halTIM.Instance = instance; + halTIM.Init.Prescaler = 0; + halTIM.Init.CounterMode = TIM_COUNTERMODE_UP; + halTIM.Init.Period = 4294967295; + halTIM.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; + halTIM.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; + HAL_TIM_Base_Init(&halTIM); + + // configure clock src + sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; + HAL_TIM_ConfigClockSource(&halTIM, &sClockSourceConfig); + HAL_TIM_IC_Init(&halTIM); + + // slave reset mode, used to measure period + sSlaveConfig.SlaveMode = TIM_SLAVEMODE_RESET; + sSlaveConfig.InputTrigger = triggerSource; + sSlaveConfig.TriggerPolarity = TIM_INPUTCHANNELPOLARITY_RISING; + sSlaveConfig.TriggerPrescaler = TIM_ICPSC_DIV1; + sSlaveConfig.TriggerFilter = 0; + HAL_TIM_SlaveConfigSynchro(&halTIM, &sSlaveConfig); + + // configure direct, rising channel + sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING; + sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; + sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; + sConfigIC.ICFilter = 0; + HAL_TIM_IC_ConfigChannel(&halTIM, &sConfigIC, directChannel); + + // configure indirect, falling channel + sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING; + sConfigIC.ICSelection = TIM_ICSELECTION_INDIRECTTI; + HAL_TIM_IC_ConfigChannel(&halTIM, &sConfigIC, indirectChannel); + + sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; + sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; + HAL_TIMEx_MasterConfigSynchronization(&halTIM, &sMasterConfig); + + // setup GPIO + GPIO_InitTypeDef gpioInit = {0}; + Pin myPins[] = {pin}; + uint8_t numOfPins = 1; + + GPIOf3xx::gpioStateInit( + &gpioInit, myPins, numOfPins, GPIO_MODE_AF_PP, GPIO_NOPULL, GPIO_SPEED_FREQ_LOW, alternateFunction); + + // configure NVIC + HAL_NVIC_SetPriority(irqNumber, 0, 0); + HAL_NVIC_EnableIRQ(irqNumber); + // enable interrupt src + HAL_TIM_IC_Start_IT(&halTIM, directChannel); // main channel + HAL_TIM_IC_Start(&halTIM, indirectChannel); // indirect channel + + activePwmInput = this; // Register this instance as active +} + +TIM_HandleTypeDef* PWMInputf3xx::getHandle() { + return &halTIM; +} + +extern "C" void TIM1_CC_IRQHandler(void) { + if (activePwmInput) { + HAL_TIM_IRQHandler(activePwmInput->getHandle()); + } +} + +extern "C" void TIM2_IRQHandler(void) { + if (activePwmInput) { + HAL_TIM_IRQHandler(activePwmInput->getHandle()); + } +} + +extern "C" void TIM1_BRK_TIM15_IRQHandler(void) { + if (activePwmInput) { + HAL_TIM_IRQHandler(activePwmInput->getHandle()); + } +} + +// Global HAL callback -> forwards to the single active instance +extern "C" void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef* htim) { + if (activePwmInput) { + activePwmInput->handleCapture(htim); + } +} + +void PWMInputf3xx::handleCapture(TIM_HandleTypeDef* htim) { + if (htim->Channel == activeChannel) // If the interrupt is triggered by the active channel + { + // Read the IC value (period) + uint32_t inputCaptureValue = HAL_TIM_ReadCapturedValue(htim, directChannel); + + if (inputCaptureValue != 0) // first value is zero, non zero is period + { + // signal high (time/ period) * 100 = duty cycle as percent + dutyCycle = (HAL_TIM_ReadCapturedValue(htim, indirectChannel) * 100) / inputCaptureValue; + frequency = SystemCoreClock / inputCaptureValue; + period = inputCaptureValue; + } + } +} + +uint8_t PWMInputf3xx::getDutyCycle() { + return dutyCycle; +} + +uint32_t PWMInputf3xx::getPeriod() { + return period; +} + +uint32_t PWMInputf3xx::getFrequency() { + return frequency; +} + +} // namespace core::io