Issue with frequency limitation on the Timers on STM32F7 - timer

I'm having issues setting the timers on the STM32F7 dissovery board to 500 Khz. I seem to top around around 370kHz for some reason. 'm toggling a GPIO pin with a scope to the input and simply changing the Period on the timer to monitor what's happening.
I'm using CubeMX to generate my project files and I initialise my timer:
static void MX_TIM1_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim1.Instance = TIM1;
htim1.Init.Prescaler = 0;
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = 108;
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC1;
sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_OC1REF;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
}
I then start the timer in interrupt mode:
if(HAL_TIM_Base_Start_IT(&htim1) != HAL_OK)
{
Error_Handler();
}
and then toggle a GPIO pin when the period has elapsed:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM1)
{
HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_6);
}
}
The GPIO pin is set as:
GPIO_InitStruct.Pin = GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
However, I've played around with the Period on the timer and I've gotten the following results:
539 = 100KHz
239 = 300KHz
215 = 330KHz
108 = 369Khz
I'd expect to get 500Khz with a Period of 215 but this isn't the case.Is there anything wrong with my settings?

The timer settings are right. The interrupt code is too slow.
The HAL library is not suited for timing critical applications. HAL tries (and fails) to handle every possible use case in one-size-fits-all functions, which means lots of unnecessary processing with associated delays. Use a simple interrupt handler instead of the TIM1_IRQHandler() supplied by HAL that just clears the interrupt status and inverts a bit directly in GPIOG->ODR. This should do:
void TIM1_IRQHandler(void) {
TIM1->SR = ~TIM_SR_UIF;
GPIOG->ODR ^= (1 << 6);
}
just 2 lines of code, instead of the 100+ lines of HAL_TIM_IRQHandler(). Should work up to 1 MHz, maybe more.
Toggling output pins in a timer interrupt handler is fine as an embedded programming exercise, but it wastes a significant amount of CPU cycles to achieve what a timer can do alone, delaying and possibly blocking other tasks.
A timer can output a square wave (PWM signal) on its output channels with frequencies up to the half of its source clock. Look for PWM edge-aligned mode in the Reference Manual.

Related

How to generate exact 1us interrupt on STM32f7xx using Hardware Timers

I am new to interrupt-based programming.
In my current project, I need the interrupt generated exactly at 1us interval.
Below is the screenshot from the Clock Configuration tab in CubeMX.
I am using the TIM3 timer as it can generate the clock frequency of 1us.
Below is the TIM3 configuration code.
static void MX_TIM3_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig;
TIM_MasterConfigTypeDef sMasterConfig;
htim3.Instance = TIM3;
htim3.Init.Prescaler = 1-1 ;//0x00;// 0x36; || 0x00//1-1
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 0xffff-1; //0x64; || 0xd7 //0xffff-1
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
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__);
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
}
I am calling the timer
HAL_TIM_IRQHandler(&htim3);
/* USER CODE BEGIN TIM3_IRQn 1 */
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_6);
I see that the interrupt of duration 1.2ms is generated.
Can anyone let me why is this happening and how can I reduce the interrupt to 1us duration?
Any change required in the timer frequency?
I am also using freeRTOS and other applications are also running on the microcontroller.
Any help in this is highly appreciated.
Thanks in Advance
If your requirement output an accurate 500KHz 1:1 mark/space signal (i.e. 1us high, 1us low), then doing that through interrupts while expecting your system to do other useful work is both impractical an unnecessary. The general purpose timers have a output-compare function that can drive a GPIO pin directly without interrupts or software overhead.
Only certain pins are connected to the Timer OC channels, so to drive PB6 in this case you would need to use TIM4 Channel 1.
Also rather than determining and hard-coding timer reload and pulse, you should use the available HAL RCC clock functions (HAL_RCC_GetPCLK1Freq() in this case) to calculate the values to avoid erros, and so that the code will be portable to other systems or will work correctly if you change your clock configuration.
static void MX_TIM4_Init(void)
{
cost uint32_t PULSE_WIDTH = HAL_RCC_GetPCLK1Freq() * 2 / 1000000 ;
htim4.Instance = TIM4 ;
htim4.Init.Prescaler = 0;
htim4.Init.CounterMode = TIM_COUNTERMODE_UP ;
htim4.Init.Period = PULSE_WIDTH * 2 ;
htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1 ;
HAL_TIM_PWM_Init( &htim4 ) ;
TIM_MasterConfigTypeDef sMasterConfig ;
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig);
TIM_OC_InitTypeDef sConfigOC ;
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = PULSE_WIDTH ;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&htim4, &sConfigOC, TIM_CHANNEL_1);
}
Then elsewhere you need to configure PB6 as an output and start the timer:
LL_GPIO_InitTypeDef GPIO_InitStruct = {0} ;
GPIO_InitStruct.Pin = GPIO_PIN_6 ;
GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE ;
GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW ;
GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL ;
GPIO_InitStruct.Pull = LL_GPIO_PULL_NO ;
GPIO_InitStruct.Alternate = LL_GPIO_AF_2 ;
LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOB)
LL_GPIO_Init(GPIOB, &GPIO_InitStruct);
HAL_TIM_PWM_Start( &htim4, TIM_CHANNEL_1 ) ;
Thereafter the signal will be maintained indefinitely on PB6 with no GPIO access or interrupt handling.

DMA callback return the same value every loop

I am currently working with a STM32f407G-DISC1 and trying to catch a buffer with the ADC and the DMA callback.
A Frequency generator is connected to the pin A0 and the board is grounded.
I verified that my wires weren't broken with an oscilloscope.
Now the problem is that after setting up my project and compiling it the DMA callback is called every time my buffer is filled. My problem is that the buffer is filled with the same values each call.
I've done exactly the same project on a STM32F401RE board. The code is approximately the same except the code generated with CUBEMX.
At first I though I was making a mistake in CUBEMX so I tried to generate another project using another ADC on the board. But I got exactly the same result.
Also, I tried to use another board (I have two of them); Same result.
ADC_HandleTypeDef hadc1;
volatile uint32_t ADCValue1[1];
void main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_ADC1_Init();
HAL_ADC_Start_DMA(&hadc1, (uint32_t *) ADCValue1, 1);
while (1) {}
return;
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = 8;
RCC_OscInitStruct.PLL.PLLN = 84;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 4;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** 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();
}
}
static void MX_ADC1_Init(void)
{
ADC_ChannelConfTypeDef sConfig = {0};
/** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
*/
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.ScanConvMode = DISABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 1;
hadc1.Init.DMAContinuousRequests = ENABLE;
hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
*/
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
}
static void MX_DMA_Init(void)
{
/* DMA controller clock enable */
__HAL_RCC_DMA2_CLK_ENABLE();
/* DMA interrupt init */
/* DMA2_Stream0_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
}
static void MX_GPIO_Init(void)
{
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
}
/* This is the callback called each time my buffer is filled */
void DMA2_Stream0_IRQHandler(void)
{
if ((DMA2->LISR & DMA_LISR_TCIF0) && (DMA2_Stream0->CR & DMA_SxCR_TCIE))
DMA2->LIFCR = DMA_LIFCR_CTCIF0; // acknowledge interrupt
DMA_IRQHandler(); // The function where I want to process my buffer
return;
/* transmission complete interrupt */
}
void DMA_IRQHandler(void)
{
return;
}
Each time I enter the callback my buffer is filled with the exact same values instead of different values.
If you have any ideas...
If you need more code just ask and I'll provide it.
If the code that you didn't bother to post is working correctly, then the buffer would contain the most recent DMA readings every time the interrupt handler is called.
But the compiler has no way of knowing that the buffer is modified by the DMA hardware, because it's not declared as volatile.
There are two obvious problems:
DMA buffers must always be declared volatile or the compiler might do strange optimizations when it realizes that no software updates the variable. Why ST's bloatware doesn't take a volatile qualified parameter is an excellent question to ST - probably yet another bug in their libs.
The function declaration is
HAL_ADC_Start_DMA (ADC_HandleTypeDef *hadc, uint32_t *pData, uint32_t Length)
Meaning you must naturally pass a uint32_t * and not something else. Instead you pass a unsigned short* which you brute force convert to uint32_t*. This gives 2 bugs: the callback might write out of bounds and/or misaligned to your 16 bit variable. And it is also a strict aliasing violation.

Configuring ADC with DMA on Nucleo-F401RE gives erratic values

I want to configure ADC with DMA on STM32(Nucleo-F401RE) and transmit the values through SPI to Basys 3 FPGA. Before transmission through SPI, when i read the values in memory realtime using STMSTudio, it is erratic.
In the past,I have tried increasing the sampling cycles, the issue persists.
Configured ADC without DMA with HAL_ADC_Start function and transferred the values to PC through UART, unable to retrieve the original signal. I'm unable to isolate where the problem lies.
uint32_t ADC1ConvertedValues[100];
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_ADC1_Init();
MX_SPI1_Init();
while (1) {
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_9,GPIO_PIN_SET);
if (HAL_ADC_Start_DMA(&hadc1, (uint32_t*)ADC1ConvertedValues, 100) == HAL_OK) {
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_9,GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1,(uint8_t*)(ADC1ConvertedValues),4,1);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_9,GPIO_PIN_SET);
}
}
}
void SystemClock_Config(void) {
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE2);
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = 16;
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();
}
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();
}
}
static void MX_ADC1_Init(void) {
ADC_ChannelConfTypeDef sConfig = {0};
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
hadc1.Init.Resolution = ADC_RESOLUTION_8B;
hadc1.Init.ScanConvMode = ENABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 1;
hadc1.Init.DMAContinuousRequests = ENABLE;
hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
if (HAL_ADC_Init(&hadc1) != HAL_OK) {
Error_Handler();
}
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
Error_Handler();
}
}
static void MX_SPI1_Init(void) {
/* SPI1 parameter configuration*/
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 10;
if (HAL_SPI_Init(&hspi1) != HAL_OK) {
Error_Handler();
}
}
static void MX_DMA_Init(void) {
__HAL_RCC_DMA2_CLK_ENABLE();
HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
}
static void MX_GPIO_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9, GPIO_PIN_RESET);
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
void Error_Handler(void) {
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line) {
#endif /* USE_FULL_ASSERT */
EDIT 1: I used the arduino IDE to program NUCLEO-f401 RE and below is the code used :
#include <f401reMap.h>
float analogPin = pinMap(31); //PA0
float val = 0; // variable to store the value read
void setup() {
Serial.begin(115200); // setup serial
analogReadResolution(12);
}
void loop() {
val = analogRead(analogPin); // read the input pin
Serial.println(val); // debug value
}
It works for input signal frequency below 100Hz. How do I increase the throughput rate? My project requires conversion of analog signal between 500KHz to 900Khz.
Tried changing the DMA buffer size/speed uint32_t ADC1ConvertedValues[100]; reading about the DMA for this chip for my project I found that this sets the size of memory direct memory access allocated samples per clock? If it was I2C or if you would like to read about the timing concepts keep reading You need to find the ADC registers that set the spi baud rate and account for the setup requirements or re-initialization.
hadc1.Instance = ADC1;// this selects analog to digital circuit one
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; //"skip" 3 out of 4 clock steps in sync with time scale read on...
hadc1.Init.Resolution = ADC_RESOLUTION_8B; //use 8 bits to pack the numbers to send to the intergrated CPU of the f401
Background on the math
ADC and DMA are often classifyed in read rate at the spi level not at the analog level. So if the chip can do 8khz spi using 8 bits then we can calculate in bigO (8n+n) time that we should get just under 1khz read speed. HOWEVER you need to write 8 bits to receive 8bits so bigO time is now bigO(n16+n) . But because of continuous register I believe it could be as low as bigO (8n+n+8) or (8n+n+8setupbits). So using that we know the time consumed by the intermediate operations in terms of clock cycles note that the term n alone is to account for assumptions of unknown internal clock trigger conditions and should have a scalar that relative to theta if scale resolution is a absolute requirement. Also keep in mind that these frequencies you may be experiencing noise from impedance resistance and capacitance.

STM32F4 as I2C Slave. Why "void I2C1_ER_IRQHandler(void)" is executed after "HAL_I2C_Slave_Transmit_DMA"?

I am using I2C bus as SLAVE mode in a STM32F411RE. The master is an arduino board.
The configuration is working well because I see thougth master serial (arduino) and STstudio (STM32F411) that all the frames are OK and thrue oscilloscope.
I noticed that I2C1_ER_IRQHandler function is triggered every time the slave finishes its TX transmission (Master receive that transmision and finishes with NACK and STOP BIT). In the next link:
https://drive.google.com/file/d/1-W5Z2nsvLNj6PE1TT9eDCDdYvFpnis8g/view?usp=sharing
https://drive.google.com/file/d/14JkeAw2If3v0A71V9-KQasH9rK3PRm3H/view?usp=sharing
https://drive.google.com/file/d/1Te2F8aNnvkqUSnfRK5UOO-qKabLXXv1D/view?usp=sharing
you can download pictures, you can see the SDA signal and GPIO PIN 2 which toggles within I2C1_ER_IRQHandler function. ¿could be related to the Slave (stm32) is receiving NACK at the end of thanssmission? see pictures
The SLAVE main function and calls are as follows:
#define BUFFERSIZE_RX 0x03 // Master sends 3 bytes
#define BUFFERSIZE_TX 0x04 //Master is waiting for 4 bytes
uint8_t aRxBuffer[BUFFERSIZE_RX];
uint8_t aTxBuffer[BUFFERSIZE_TX];
int main(void)
{
…uC INITIALIZATION
if(HAL_I2C_Slave_Receive_DMA(&hi2c1, (uint8_t *)aRxBuffer, BUFFERSIZE_RX) != HAL_OK)
{
Error_Handler();
}
while (1)
{}
}
void HAL_I2C_SlaveTxCpltCallback(I2C_HandleTypeDef *hi2c)
{
if(HAL_I2C_GetState(&hi2c1) == HAL_I2C_STATE_READY)
{
if(HAL_I2C_Slave_Receive_DMA(&hi2c1, (uint8_t *)aRxBuffer, BUFFERSIZE_RX) != HAL_OK)
{
Error_Handler();
}
}
}
void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c)
{
if(HAL_I2C_GetState(&hi2c1) == HAL_I2C_STATE_READY)
{
if(HAL_I2C_Slave_Transmit_DMA(&hi2c1, (uint8_t*)aTxBuffer, BUFFERSIZE_TX)!= HAL_OK)
{
Error_Handler();
}
}
}
void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_SET);
}
void I2C1_ER_IRQHandler(void)
{
HAL_I2C_ER_IRQHandler(&hi2c1);
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_2);
}
I2C and DMA configuration is as the STM32Cube_FW_F4_V1.24.0 examples, but if you need them I can send them as well.
The Arduino Master only sends the following functions:
void loop()
{
Wire.beginTransmission(address);
Wire.write((uint8_t)M_TX_1);
Wire.write((uint8_t)M_TX_2);
Wire.write((uint8_t)M_TX_3);
Wire.endTransmission();
delay(1);
Wire.requestFrom(address, (uint8_t)4);
M_RX_1 = Wire.read();
M_RX_2 = Wire.read();
M_RX_3 = Wire.read();
M_RX_4 = Wire.read();
… Serial prints and so on…
}
I have tested I2C in interruption mode and the same thing happens… communications works but always the I2C1_ER_IRQHandler is called.
I am completely lost, any help or comment are really appreciate!!!
Sorry for the long post.
P.D. HAL_I2C_ErrorCallback never called, so i suppose that it is OK.
Best regards.
Alejandro
PD2: SPI GPIO and DMA configuration:
static void MX_I2C1_Init(void)
{
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 100000;
//hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_16_9; // Modificacion
hi2c1.Init.OwnAddress1 = SLAVEADDRESS << 1; // Modificacion
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.OwnAddress2 = 0;
//hi2c1.Init.OwnAddress2 = 0x06; // Modificacion
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
if (HAL_I2C_Init(&hi2c1) != HAL_OK)
{
Error_Handler();
}
}
static void MX_DMA_Init(void)
{
__HAL_RCC_DMA1_CLK_ENABLE();
HAL_NVIC_SetPriority(DMA1_Stream0_IRQn, 0, 1);
HAL_NVIC_EnableIRQ(DMA1_Stream0_IRQn);
HAL_NVIC_SetPriority(DMA1_Stream1_IRQn, 0, 2);
HAL_NVIC_EnableIRQ(DMA1_Stream1_IRQn);
}
void HAL_I2C_MspInit(I2C_HandleTypeDef* hi2c)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(hi2c->Instance==I2C1)
{
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
__HAL_RCC_I2C1_CLK_ENABLE();
hdma_i2c1_rx.Instance = DMA1_Stream0;
hdma_i2c1_rx.Init.Channel = DMA_CHANNEL_1;
hdma_i2c1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_i2c1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_i2c1_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_i2c1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_i2c1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_i2c1_rx.Init.Mode = DMA_NORMAL;
hdma_i2c1_rx.Init.Priority = DMA_PRIORITY_LOW;
hdma_i2c1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
hdma_i2c1_rx.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
hdma_i2c1_rx.Init.MemBurst = DMA_MBURST_INC4;
hdma_i2c1_rx.Init.PeriphBurst = DMA_PBURST_INC4;
if (HAL_DMA_Init(&hdma_i2c1_rx) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(hi2c,hdmarx,hdma_i2c1_rx);
hdma_i2c1_tx.Instance = DMA1_Stream1;
hdma_i2c1_tx.Init.Channel = DMA_CHANNEL_0;
hdma_i2c1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_i2c1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_i2c1_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_i2c1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_i2c1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_i2c1_tx.Init.Mode = DMA_NORMAL;
hdma_i2c1_tx.Init.Priority = DMA_PRIORITY_HIGH;
hdma_i2c1_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
hdma_i2c1_tx.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
hdma_i2c1_tx.Init.MemBurst = DMA_MBURST_INC4;
hdma_i2c1_tx.Init.PeriphBurst = DMA_PBURST_INC4;
if (HAL_DMA_Init(&hdma_i2c1_tx) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(hi2c,hdmatx,hdma_i2c1_tx);
HAL_NVIC_SetPriority(I2C1_EV_IRQn, 0, 3);
HAL_NVIC_EnableIRQ(I2C1_EV_IRQn);
HAL_NVIC_SetPriority(I2C1_ER_IRQn, 0, 2);
HAL_NVIC_EnableIRQ(I2C1_ER_IRQn);
}
}
To start with, Is it possible that this is an event IRQ (EV) to indicate a slave has finished transferring data and is not an error IRQ? You may have configured this to show that the slave is done with data transfer on the bus.
If that is not the case, definitely take a little better look at the I2C standard of communication. In your case, a NACK just indicates the stop of the transfer all together from the master, or it could mean that the transfer failed, but if you say all frames are successful, then it is definitely the end of transfer condition.
That being said, it is important to know how this was all configured. I would check the initialization sequence of the I2C on the F4 and make sure that it is in compliance with what is expected from the Arduino. It should be something like this.
Setup the associated clocks with I2C and GPIOs
Map the GPIOs
Enable the Interrupt services needed through the Nested Vectored Interrupt Controller (NVIC)
Set the settings of the I2C to the desired framing
Enable the I2C peripheral and interrupts
These steps will allow you to make sure you are covering your ground here.
Now you also must make sure you have the right features enabled if you are using DMA with I2C as well. The F4 does some packet error checking after the end of each message. Do you have the PEC enabled? If so, make sure there is not something causing this to get the IRQ interrupt to fire.
You also have event flags to read from in a debugger to see what error specifically triggered the I2C IRQ error line. According to the STMF4 reference manual, the event flags to generate an interrupt are:
0: Error interrupt disabled
1: Error interrupt enabled
This interrupt is generated when:
– BERR = 1
– ARLO = 1
– AF = 1
– OVR = 1
– PECERR = 1
– TIMEOUT = 1
– SMBALERT = 1
Although, you mentioned that the Error Callback function was not active, so these might not be present.
Finally, just to be safe, ensure the IRQ line isn't being used elsewhere in software. it could just be that the line is controlled by something else and being pulled high, causing the IRQ handler function to be called. This is unlikely if the code you posted is the only thing running, but it is worth a thought.

Is a TIM interrupt also blocking background counting on STM32?

As the title says, with an STM32 if an interrupt lasts for 1 microsecond should one compensate the CCR by 1 microsecond converted in counter cycles in order to achieve the correct duty cycle?
For example with a period of 100Khz and an overflow interrupt of 1 microsecond should i use a CCR = 50Khz to achieve duty cycle of 50% or should i use a CCR = 50kHz - interrupt delay? Keep in mind that i'm using shadow register to preload the next period/duty cycle combination dynamically.
Oscilloscope readings tells me the second.
Does a flag for automatic compensation exist?
Below some code snippets
void MX_TIM9_Init(void){
TIM_OC_InitTypeDef sConfigOC;
htim9.Instance = TIM9;
htim9.Init.Prescaler = PSC; // Get clock to <freq> Hz
htim9.Init.CounterMode = TIM_COUNTERMODE_UP;
htim9.Init.Period = 250;
htim9.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_PWM_Init(&htim9);
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 125;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_ENABLE;
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
HAL_TIM_PWM_ConfigChannel(&htim9, &sConfigOC, TIM_CHANNEL_1);
__HAL_TIM_ENABLE_IT(&htim9, TIM_IT_UPDATE);
TIM9->CR1 |= TIM_CR1_ARPE;
}
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef* htim_pwm){
GPIO_InitTypeDef GPIO_InitStruct;
if(htim_pwm->Instance==TIM9){
__TIM9_CLK_ENABLE();
/* Peripheral interrupt init*/
HAL_NVIC_SetPriority(TIM1_BRK_TIM9_IRQn, 1, 1);
HAL_NVIC_EnableIRQ(TIM1_BRK_TIM9_IRQn);
GPIO_InitStruct.Pin = GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
GPIO_InitStruct.Alternate = GPIO_AF3_TIM9;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
}
}
void TIM1_BRK_TIM9_IRQHandler(void){
if(__HAL_TIM_GET_FLAG(&htim9, TIM_FLAG_UPDATE) != RESET){
if(__HAL_TIM_GET_IT_SOURCE(&htim9, TIM_IT_UPDATE) !=RESET){
__HAL_TIM_CLEAR_IT(&htim9, TIM_IT_UPDATE);
myFunction(void);
}
}
}
void myFunction(){
//perform logics with if
tim9->ARR = r; // r is a value coming from the logics above
tim9->CCR1 = r / 2;
}
No - interrupts have not anything with the timers. When the interrupt line is set by the timer, it counts as normally - and the interrupt is dealt by the interrupt controller & the core. You do not have to compensate anything.

Resources