-
Help with STM32duino timers and frequency measurement
Posted by
timdf911
on 01 Dec, 2022 17:12
-
Hi guys
1st post but long term lurker !
I've been trying to update a design I originally did 7 years ago which used a Teensy processor and was written entirely in C using the Atmel environmanr.
I've recently discovered the delights of an STM32F401re and have started to port the code using stm32duino environment - but I've run into a road block and despite lots of googling I've made little progress due to being overwhelmed with all the registers associated with the timers and what I suspect is limited timer functionality in the stm32duino environment.
Here's what I need to do - basically a one shot measurement of a frequency between 1 and 30MHz to a resolution of 10kHz.
Here's how I think it should be done
1. Use timer 2 with the signal using ETR input with possibly the prescaler if it can't handle 30MHz directly
2. Set the timer2 counter to zero
3. Open the gate for 1mS using a second timer (say timer3) then read the timer 2 to see how many times it's been incremented. The resulting count should be between 1000 and 30000
To do the above seems to need a bunch of registers setting, I've had some limited success using some snippets of code from Andrewbcn GPSDO project, but frankly my attempt is a mess and I'd be ashamed to post it !
I'm hoping someone can give me a few pointers on key registers and settings which will link the two timers together and operate as above.
I've tried reading the data and using CubeMx but managed to get myself wrapped round the axle there aswell.
Regards Tim
-
-
-
#2 Reply
Posted by
timdf911
on 01 Dec, 2022 19:15
-
Thanks for those links, however during my extensive googling I had already come across them, but maybe I need to give them a second look / chance.
I was deterred from following them as some used CubeMX and / or did not explicitly use the ETR input for maximum frequency operation.
Maybe I need to practice figuring out how to use CubeMX more efficiently and how to embed the code it generates into mine.
Regards Tim
-
#3 Reply
Posted by
paulca
on 01 Dec, 2022 19:55
-
CubeMX will guide you in what way you 'can' configure all the timers, making it quick to try things.
There is nothing preventing you from creating the basic project in a rapid throw away fashion using CubMX generated code and then put your own further register based code into an isolated C file. You can re-run the code generation when ever you want if you do it right.
I did this when I wanted to use a single timer to do two different things based on a sequence of interrupt cascades. CubeMX would set the timer up for me in the first mode, I then take over using the registers (and a few HAL functions and macros) to reconfigure and restart the timer in PWM DMA mode or basic counter delay timer.
-
#4 Reply
Posted by
timdf911
on 01 Dec, 2022 21:20
-
Thanks for the ideas -funnily enough the timers will need to be reconfigured depending on what's happening with the device.
However given that my code size is already 150kB for all the other stuff going on before I add this frequency measurement part and the other code which goes round it (stepper motor control) I suspect it might be easier to integrate the CubeMX code into my code base rather than the other way around.
I had tried with CubeMX to generate the timer code but came a bit unstuck with exactly how to interconnect the timers such that the gating timer could gate the signal into the main counter for counting and what modes thy needed to be in etc - if that makes sense.
Basically how to configure using a two timer approach to count the incoming cycles over a fixed period of time.
Tomorrows another day and I'll have another crack at CubeMX by first getting the gating timer going at 1mS and checking with a scope it's operating as intended.
So much to learn and so few brain cells left :-)
Regards Tim
-
#5 Reply
Posted by
paulca
on 01 Dec, 2022 22:02
-
I've not slaved timers before. However I believe that is the area you want to be looking. That or "Triggers". One timer can be slaved to another.
Use CubeMX for what it's good for. Rapid prototyping. Try the timer idea out on it's own using Cube to get the bulk of the code written. Then it will give you a base to work from and port back to your actual app.
On code size. I need to look for a way to split the symbol file in CubeMX. It seems those it makes the elf with debug symbols in it. There should, hopefully be an option to save it externally and load it into the debugger, rather than flash the damn thing to the MCU flash memory!
-
#6 Reply
Posted by
Kleinstein
on 01 Dec, 2022 22:19
-
The ELF and similar output file is not directly giving the flash usage. The files tend to be a lot larger.
In the debug mode the compiler uses not optimization and this can make the code a lot longer. The prodution type make creates shorter code that is however harder to debug with the debugger. One can still use it for debugging: decalre the variables in question volatile, which restricts much of the optimizations where those variables are used.
The CubeMx code adds a bit to the code - to a large part for the initialization of the HW parts used. So when adding the actual frequency measurement this would not add that much code. It is more like some 16 kB to start with for the init part.
-
#7 Reply
Posted by
langwadt
on 01 Dec, 2022 22:20
-
I've not slaved timers before. However I believe that is the area you want to be looking. That or "Triggers". One timer can be slaved to another.
Use CubeMX for what it's good for. Rapid prototyping. Try the timer idea out on it's own using Cube to get the bulk of the code written. Then it will give you a base to work from and port back to your actual app.
On code size. I need to look for a way to split the symbol file in CubeMX. It seems those it makes the elf with debug symbols in it. There should, hopefully be an option to save it externally and load it into the debugger, rather than flash the damn thing to the MCU flash memory!
surely the debugger only flashes the srec/hex/bin extracted from the elf
-
#8 Reply
Posted by
langwadt
on 01 Dec, 2022 22:24
-
Thanks for those links, however during my extensive googling I had already come across them, but maybe I need to give them a second look / chance.
I was deterred from following them as some used CubeMX and / or did not explicitly use the ETR input for maximum frequency operation.
Maybe I need to practice figuring out how to use CubeMX more efficiently and how to embed the code it generates into mine.
you might want to change the cube code generation to use the LL instead of HAL
-
#9 Reply
Posted by
ozcar
on 02 Dec, 2022 06:22
-
I had tried with CubeMX to generate the timer code but came a bit unstuck with exactly how to interconnect the timers such that the gating timer could gate the signal into the main counter for counting and what modes thy needed to be in etc - if that makes sense.
Basically how to configure using a two timer approach to count the incoming cycles over a fixed period of time.
I have mostly only used CubeMX to set up clock trees - it seems quite useful for that. Anyway, I tried to generate some code using the old version of CubeMX I have installed, and I sort-of got it to work. It was really just a matter of setting the more-or-less obvious things for the timers on the Pinout and Configuration tabs (myself, I don't quite follow the logic of what is under the one tab vs the other). I could not get it to enable the interrupt to grab the timer counter so in the end just gave up and set the required flag in the DIER register directly in the generated code.
I set it up using TIM3 (16 bit) to gate TIM2 (32 bit), with TIM2 counting the signal to measure. For 84MHz timer clock I set TIM3 prescaler to divide by 300, channel count of 28,000 and ARR = 28,280, for gate time of 100ms, repeated every 101ms, and it gives plausible results. I did not put any code in to display the result, I just ran it under the debugger to see what it got.
For what it is worth, this is the resulting main.c. I enabled both the MCO outputs and also got it to blink the user LED on the Nucleo board in the interrupt routine as part of my sanity checking. Except for the few lines added to main.c, I inserted a call to the TIM3_IRQHandler_main() into the generated stm32f4xx_it.c.
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
** This notice applies to any and all portions of this file
* that are not between comment pairs USER CODE BEGIN and
* USER CODE END. Other portions of this file, whether
* inserted by the user or by software development tools
* are owned by their respective copyright owners.
*
* COPYRIGHT(c) 2022 STMicroelectronics
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of STMicroelectronics nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f4xx_hal.h"
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private variables ---------------------------------------------------------*/
TIM_HandleTypeDef htim2;
TIM_HandleTypeDef htim3;
/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
volatile uint32_t TimeCount=0;
uint32_t TimeCountm=0;
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM2_Init(void);
static void MX_TIM3_Init(void);
/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/
/* USER CODE END PFP */
/* USER CODE BEGIN 0 */
void TIM3_IRQHandler_main(void)
{
// at this time, TIM2 counter is stopped
TimeCount = TIM2->CNT;
TIM2->CNT = 0; // reset
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // Nucleo LED
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
*
* @retval None
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration----------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM2_Init();
MX_TIM3_Init();
/* USER CODE BEGIN 2 */
TIM2->CNT = 0;
TIM3->DIER |= TIM_DIER_CC1IE; // enable interrupt for channel 1 compare
HAL_TIM_Base_Start(&htim3); // turns on CEN in CR1
HAL_TIM_Base_Start(&htim2); // turns on CEN in CR1
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
TimeCountm = TimeCount;
if ( TimeCountm )
HAL_Delay(1);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct;
RCC_ClkInitTypeDef RCC_ClkInitStruct;
/**Configure the main internal regulator output voltage
*/
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE2);
/**Initializes the CPU, AHB and APB busses clocks
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 8;
RCC_OscInitStruct.PLL.PLLN = 336;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV4;
RCC_OscInitStruct.PLL.PLLQ = 7;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
/**Initializes the CPU, AHB and APB busses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
HAL_RCC_MCOConfig(RCC_MCO1, RCC_MCO1SOURCE_HSE, RCC_MCODIV_1);
HAL_RCC_MCOConfig(RCC_MCO2, RCC_MCO2SOURCE_SYSCLK, RCC_MCODIV_4);
/**Configure the Systick interrupt time
*/
HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);
/**Configure the Systick
*/
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
/* SysTick_IRQn interrupt configuration */
HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}
/* TIM2 init function */
static void MX_TIM2_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig;
TIM_SlaveConfigTypeDef sSlaveConfig;
TIM_MasterConfigTypeDef sMasterConfig;
htim2.Instance = TIM2;
htim2.Init.Prescaler = 0;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 0xffffffff;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_ETRMODE2;
sClockSourceConfig.ClockPolarity = TIM_CLOCKPOLARITY_NONINVERTED;
sClockSourceConfig.ClockPrescaler = TIM_CLOCKPRESCALER_DIV1;
sClockSourceConfig.ClockFilter = 0;
if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sSlaveConfig.SlaveMode = TIM_SLAVEMODE_GATED;
sSlaveConfig.InputTrigger = TIM_TS_ITR2;
if (HAL_TIM_SlaveConfigSynchronization(&htim2, &sSlaveConfig) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
}
/* TIM3 init function */
static void MX_TIM3_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig;
TIM_MasterConfigTypeDef sMasterConfig;
TIM_OC_InitTypeDef sConfigOC;
htim3.Instance = TIM3;
htim3.Init.Prescaler = 299;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 28280;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
if (HAL_TIM_PWM_Init(&htim3) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC1REF;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 28000;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
}
/** Configure pins as
* Analog
* Input
* Output
* EVENT_OUT
* EXTI
PC9 ------> RCC_MCO_2
PA8 ------> RCC_MCO_1
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
/*Configure GPIO pin : PA5 */
GPIO_InitStruct.Pin = GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pin : PC9 */
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF0_MCO;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
/*Configure GPIO pin : PA8 */
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF0_MCO;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @param file: The file name as string.
* @param line: The line in file as a number.
* @retval None
*/
void _Error_Handler(char *file, int line)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
while(1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t* file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
/**
* @}
*/
/**
* @}
*/
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
Edit:
This is from the CubeMX "report" showing the settings there:
PERIPHERALS MODES FUNCTIONS PINS
RCC Crystal/Ceramic Resonator RCC_OSC_IN PH0 - OSC_IN
RCC Crystal/Ceramic Resonator RCC_OSC_OUT PH1 - OSC_OUT
RCC Clock-out-1 RCC_MCO_1 PA8
RCC Clock-out-2 RCC_MCO_2 PC9
SYS SysTick SYS_VS_Systick VP_SYS_VS_Systick
TIM2 Gated Mode TIM2_VS_ControllerModeGated VP_TIM2_VS_ControllerModeGated
TIM2 ITR2 TIM2_VS_ClockSourceITR VP_TIM2_VS_ClockSourceITR
TIM2 ETR2 TIM2_ETR PA0-WKUP
TIM3 Internal Clock TIM3_VS_ClockSourceINT VP_TIM3_VS_ClockSourceINT
TIM3 PWM Generation No Output TIM3_VS_no_output1 VP_TIM3_VS_no_output1
-
#10 Reply
Posted by
timdf911
on 02 Dec, 2022 09:10
-
Hi guys,
many thanks for your useful tips - I didn't know that CubeMX could be configured to use LL rather than HAL - I'll give that a go.
Thanks ozcar for your code snippets and tips, I'll have a play around and experiment, hopefully learning a few things along the way as I customize it for my exact needs. I seem to learn best by doing, testing and repeating until I get what I'm aiming for.
I wish I'd posted here last week rather going down rabbit holes and getting totally lost ! At least now I've got a starting point.
I'll keep you posted as to how it comes along and maybe a bit more info about the completed project.
Here's a summary.
The first part is in next months RadCom describing how it controls an antenna rotator.
The second part describes how it uses a Goertzel filter to facilitate doppler radio direction finding and the third and final part (where this timer issues comes in ) is to automatically tune a magnetic loop antenna.
System uses a 480x320 touch SPI LCD and parts 1 & 2 work really well. Part 3 was going to be a port of my Teensy code, but is now looking for like a complete re-write.
Again many thanks to everyone.
Regards Tim
-
#11 Reply
Posted by
paulca
on 02 Dec, 2022 10:07
-
The ELF and similar output file is not directly giving the flash usage. The files tend to be a lot larger.
In the debug mode the compiler uses not optimization and this can make the code a lot longer. The prodution type make creates shorter code that is however harder to debug with the debugger. One can still use it for debugging: decalre the variables in question volatile, which restricts much of the optimizations where those variables are used.
Interesting. I thought the elf had the symbols in it and it went onto the device. I recall hitting 101% memory on an F030F4 and changing to Release mode dropped that to 40% or something. I assumed it was symbols. I write my code pretty verbosely. Maybe, it's so verbose the compiler removes 50% of it during Release compile, but that sounds excessive! Granted the total memory in that device was only 4K.
Then again, it does ask for the ELF file if you want to attach to a running MCU (which doesn't work by the way). So maybe on flash it filters them out, or maybe it does a really, really poor job of filtering them out.
-
#12 Reply
Posted by
timdf911
on 02 Dec, 2022 19:50
-
Hi Guys,
making some good progress manipulating registers to control timers and irqs etc - but have run into another problem. I'm using the Arduino environment which is C++ and previously I've attached an interupt by simply doing something like:
tim1Hz->attachInterrupt(Timer_ISR_1Hz)
now I'm using :
NVIC_EnableIRQ(TIM9_IRQn);
with matching irqhandler, it compiles ok but the handler doesn't execute.
Preceding the isr with extern "C" doesn't fix it and the complier complains of multiple definitions so I'm wondering if there's anotherway to associated isr with a timer generated irq within the stm32arduino environment ?
Anyway having lots of fun exploring all the registers and things are making much more sense than a couple of days ago.
Regards Tim
-
#13 Reply
Posted by
ozcar
on 02 Dec, 2022 23:25
-
-
#14 Reply
Posted by
timdf911
on 03 Dec, 2022 08:21
-
Hi ozcar,
yes that's the way I have been attaching interupts within Arduino, very easy to do but somewhat limited.
In my quest to learn and write better code I was hoping to avoid using attachinterupt and to achieve my goal by setting registers (which I largely understand now) and using nvic. The latter seems to conflict with arduino environment, so in the short term I'll use registers to chain the timers together and attachinterupt to check the timings until I find a better way.
Thanks again for your support and guidance
Regards im
-
#15 Reply
Posted by
timdf911
on 03 Dec, 2022 11:58
-
I've been making progress of sorts using TIM2 as a slave gated by TIM3 every 100mS
Here's my settings for TIM2 with what I think I'm doing
TIM2->DIER |= TIM_DIER_CC3IE; // enable capture compare irq for channel 3
TIM2->ARR = 0xffffffff; // count to 2^32, then wraparound (approximately every 429 seconds)
TIM2->SMCR |= TIM_SMCR_ECE; // select external clock source mode 2 by writing ECE=1 in the TIM2_SMCR register
TIM2->SMCR |= TIM_TS_ITR2 ; // input trigger from timer 3
TIM2->CR1 |= 0x01; //Enable counting
Here's my settings for TIM3 with what I think I'm doing
TIM3 -> PSC = 300; // divides clock 84MHz to 280kHz
TIM3 -> ARR = 28280 ; // generates a 100mS overflow
TIM3->DIER |= 0x01; //Enable interrupt
TIM3->SMCR |= TIM_TS_ITR2 ; // generate output trigger
TIM3->CR2 |= TIM_TRGO_OC1REF ; // Master mode selection generate TRGO for OC1REF to trigger capture compare on timer 2
TIM3->CR1 |= 0x01; //Enable counting
TIM3 -> CCMR2 |= TIM_CCMR2_OC3M ; // output compare mode 3
TIM3 -> CCMR2 |= TIM_CCMR2_OC3PE ; // Output compare 3 preload enable
TIM3 -> CCER |= TIM_CCER_CC3P ; //Capture/Compare 3 output Polarity
TIM3 -> CCER |= TIM_CCER_CC3E ; //Capture/Compare 3 output enable
In the main loop I check this every second
Freq_Raw = TIM2->CCR3;
then serial print, but always shows 0.
I know my 100mS irq is running because I can see it if I attach an interupt to TIM3 and toggle a GPIO, so I suspect I have a disconnect / wrong settting between the two timers.
Any ideas ?
Regards Tim
-
#16 Reply
Posted by
ozcar
on 04 Dec, 2022 00:38
-
Hi ozcar,
yes that's the way I have been attaching interupts within Arduino, very easy to do but somewhat limited.
In my quest to learn and write better code I was hoping to avoid using attachinterupt and to achieve my goal by setting registers (which I largely understand now) and using nvic. The latter seems to conflict with arduino environment, so in the short term I'll use registers to chain the timers together and attachinterupt to check the timings until I find a better way.
Thanks again for your support and guidance
Regards im
I thought you were saying that something that worked with TeensyDuino (or whatever Arduino for Teensy is called - I'm not familiar with it at all), did not work for stm32duino.
If that works but is not good enough, I've seen some mention over on stm32duino.com about using a header file called hal_conf_extra.h to selectively disable parts of the core - eg here
https://www.stm32duino.com/viewtopic.php?t=1072. However I have never had occasion to use that, so I'm not sure if that will help in any way. Best place to ask about that would be over there, where the guys who wrote the stm32duino code hang out. I see you have posted your original question there.
-
#17 Reply
Posted by
ozcar
on 04 Dec, 2022 00:43
-
I've been making progress of sorts using TIM2 as a slave gated by TIM3 every 100mS
Here's my settings for TIM2 with what I think I'm doing
TIM2->DIER |= TIM_DIER_CC3IE; // enable capture compare irq for channel 3
TIM2->ARR = 0xffffffff; // count to 2^32, then wraparound (approximately every 429 seconds)
TIM2->SMCR |= TIM_SMCR_ECE; // select external clock source mode 2 by writing ECE=1 in the TIM2_SMCR register
TIM2->SMCR |= TIM_TS_ITR2 ; // input trigger from timer 3
TIM2->CR1 |= 0x01; //Enable counting
Here's my settings for TIM3 with what I think I'm doing
TIM3 -> PSC = 300; // divides clock 84MHz to 280kHz
TIM3 -> ARR = 28280 ; // generates a 100mS overflow
TIM3->DIER |= 0x01; //Enable interrupt
TIM3->SMCR |= TIM_TS_ITR2 ; // generate output trigger
TIM3->CR2 |= TIM_TRGO_OC1REF ; // Master mode selection generate TRGO for OC1REF to trigger capture compare on timer 2
TIM3->CR1 |= 0x01; //Enable counting
TIM3 -> CCMR2 |= TIM_CCMR2_OC3M ; // output compare mode 3
TIM3 -> CCMR2 |= TIM_CCMR2_OC3PE ; // Output compare 3 preload enable
TIM3 -> CCER |= TIM_CCER_CC3P ; //Capture/Compare 3 output Polarity
TIM3 -> CCER |= TIM_CCER_CC3E ; //Capture/Compare 3 output enable
In the main loop I check this every second
Freq_Raw = TIM2->CCR3;
then serial print, but always shows 0.
I know my 100mS irq is running because I can see it if I attach an interupt to TIM3 and toggle a GPIO, so I suspect I have a disconnect / wrong settting between the two timers.
Any ideas ?
Regards Tim
I've lost track of what you are doing in terms of generating code using CubeMX, or using stm32duino instead, or a bit of both, or maybe neither of those.
I also don't know if you are now just trying to duplicate what I posted before without using HAL, or if you have come up with a better way. I get the feeling you that you are maybe trying to use input capture somehow? Short of wiring the two timers together externally I don't know how you would do that, but then there are more ways in heaven and STM32 timers than in my philosophy.
The HAL code I posted before results in the timers ending up as shown below (just stopped at some random point).
Besides deciding what gate time would be best, with 84MHz timer clock, you'd have to set the external trigger prescaler to divide by 2 to reach 30MHz (ETPS = 01 in SMCR).
-
#18 Reply
Posted by
ozcar
on 04 Dec, 2022 01:36
-
I just noticed that you set TIM3 PSC to 300, saying that divides 84MHz down to 280KHz. Close, but not quite right. PSC=0 divides by 1, and PSC=299 divides by 300.
Also, it is the CCR1 value of 28,000 in my attempt that sets the gate time to 100ms. ARR=28,280 is a bit arbitrary, and serves only to give a little time for the ISR to grab the then frozen count.
-
#19 Reply
Posted by
timdf911
on 04 Dec, 2022 21:43
-
Hi ozcar
thanks for taking the time to reply again - I've not had as much spare time today as I would have like but I've come to the conclusion that setting all the required registers manually is probably not the smartest thing to try, on the plus side I now have a much better understanding how the registers work and control the timers. Surprising what you learn when you study the data sheets !
Anyway I'm going to use cubemx and cubeide to get something going using HAL then try the samething using LL before inserting the resulting code into the STM32duino environment where the rest of my code is developed.
OK on your comments about my register setting - at this point I'd be happy to get anything working
Regards Tim
-
#20 Reply
Posted by
paulca
on 05 Dec, 2022 08:37
-
Once they gave me a working IDE and a working (to professional level) debugger et. al. Eclipse (aka CubeMXIDE) I couldn't go back to the "maker" IDEs.
As the previous screenshot highlights. CubeMXIDe will allow you to not only breakpoint the core, but also view the live registers and .... even modify them as you please from the debugger. You can even browse and modify memory while it's running.
It makes the Arduino IDE look like Notepad.
-
#21 Reply
Posted by
paulca
on 05 Dec, 2022 08:41
-
From my memory of baremetal programming the timers. There is a gotcha. The EGR register has an "update" bit. It's like the "commit bit" on the timer registers. It forces the timer to update based on it's registers. If you don't set that bit the timer will carry on doing whatever it was doing until it hits an update event. Setting the bit forces the timer to do that NOW.
-
#22 Reply
Posted by
ozcar
on 05 Dec, 2022 10:08
-
From my memory of baremetal programming the timers. There is a gotcha. The EGR register has an "update" bit. It's like the "commit bit" on the timer registers. It forces the timer to update based on it's registers. If you don't set that bit the timer will carry on doing whatever it was doing until it hits an update event. Setting the bit forces the timer to do that NOW.
The deferred update, aka "preload" applies to the ARR, CCRn and PSC registers, but for ARR and CCRn preload is optional, and is actually turned off out of reset (controlled by CR1/ARPE and CCMRn/OCnPE flags).
-
#23 Reply
Posted by
timdf911
on 05 Dec, 2022 18:06
-
paulca
you've convinced me to invest more time learning how to drive cubemx / ide properly - certainly stm32duino enables quick results but as I'm discovering it lacks the fine control I desire, hence my attempts at baremetal programming.
I think I'll go and find a simple project to strat with which wil enable me to explore the debug facilities that way I can hopefully answer my own questions with having to post here.
Regards Tim
-
#24 Reply
Posted by
DavidAlfa
on 06 Dec, 2022 03:04
-
Seems like a job for DMA.
16bit TimerA:
- Pwm mode, one channel, no output.
- Timebase slightly larger than 100ms.
- Channel 1 would be "Frequency reading", adjust for 100ms.
Enable capture ch1 DMA, peripheral to memory, both Word size, no increment, circular mode.
- Enable TRGO output, use update flag.
32bit TimerB:
- Setup external clock, prescaler(I think 30MHz is fine), etc...
- Slave in Reset Mode, configure ITR to take TRGO from Timer A
- Disable interrupts for all timers and their dmas.
Workflow:
Start TimerA Ch1 DMA: From TimerB->CNT to a 32-bit variable.
Start TimerA, start TimerB.
At 100ms, TimerA pwm will trigger a DMA request and transfer the counter value from TimerB into your variable.
Shortly after, TimerA will overflow and reset, the TRGO signal will also reset TimerB.
Now you can read your variable at any time to check the latest reading.