I have a circuit which needs to respond in around 0.5uS to an external interrupt. I built the circuit with an STM32F031K6 and a 20MHz oscillator set to run on the 2x PLL, giving a 40MHz clock. I was surprised to see that although one clock cycle would be 25nS, i could only toggle a pin at 300nS - im not exactly sure why it takes so long, i have some experience with 8 bit AVRs and although I wouldn't expect it to run in one clock cycle, 12 seems slow. The external interrupt takes 3uS to respond. how can i choose a chip to meet my requirement of 0.5uS?
I'm just assuming that i need to change the chip, if anyone has advice on how i might reduce the response time that would also be great
my full code is here, this is a blank program generated by cube, i stripped out some of the generated commenting to make it easier to read
int main(void)
{
MX_GPIO_Init();
MX_ADC_Init();
while (1)
{
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI14|RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSI14State = RCC_HSI14_ON;
RCC_OscInitStruct.HSI14CalibrationValue = 16;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL2;
RCC_OscInitStruct.PLL.PREDIV = RCC_PREDIV_DIV1;
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_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK)
{
Error_Handler();
}
}
static void MX_ADC_Init(void)
{
ADC_ChannelConfTypeDef sConfig = {0};
/** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
*/
hadc.Instance = ADC1;
hadc.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
hadc.Init.Resolution = ADC_RESOLUTION_12B;
hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc.Init.ScanConvMode = ADC_SCAN_DIRECTION_FORWARD;
hadc.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
hadc.Init.LowPowerAutoWait = DISABLE;
hadc.Init.LowPowerAutoPowerOff = DISABLE;
hadc.Init.ContinuousConvMode = DISABLE;
hadc.Init.DiscontinuousConvMode = DISABLE;
hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc.Init.DMAContinuousRequests = DISABLE;
hadc.Init.Overrun = ADC_OVR_DATA_PRESERVED;
if (HAL_ADC_Init(&hadc) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel to be converted.
*/
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = ADC_RANK_CHANNEL_NUMBER;
sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel to be converted.
*/
sConfig.Channel = ADC_CHANNEL_1;
if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
{
Error_Handler();
}
}
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOF_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5
|GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_RESET);
/*Configure GPIO pins : PA2 PA11 */
GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_11;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pins : PA3 PA4 PA12 PA15 */
GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_12|GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pins : PA6 PA7 */
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF1_TIM3;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pin : PB0 */
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF1_TIM3;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/*Configure GPIO pins : PB1 PB3 PB4 PB5
PB6 PB7 */
GPIO_InitStruct.Pin = GPIO_PIN_1|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5
|GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/*Configure GPIO pins : PA8 PA9 PA10 */
GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF2_TIM1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* EXTI interrupt init*/
HAL_NVIC_SetPriority(EXTI2_3_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI2_3_IRQn);
HAL_NVIC_SetPriority(EXTI4_15_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI4_15_IRQn);
}
void Error_Handler(void)
{
}
#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 */
void EXTI2_3_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_2);
}
void EXTI4_15_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_11);
GPIOB->ODR ^= 1<<1;
}
First of all, I recommend having a look at this ARM blog post for an in-depth introduction to interrupt latency of ARM Cortex-M processors.
As mentioned by #Colin the interrupt latency of a STM32F0 MCU with a Cortex-M0 core is 16 clock cycles starting when the signal on the EXTI line is asserted until entering the IRQ Handler with code reacting to the event. This clock cycle count cannot be reduced by firmware.
When selecting a MCU with a Cortex-M3 or M4 core (e.g. a STM32F3), this required number of clock cycles drops to 12. The resulting latency still depends on the clock frequency of the core. Selecting a STM32 MCU with higher max. clock rate allows for faster reaction times:
STM32F0: up to 48 MHz Cortex M0 => ISR enter latency 333 ns
STM32G0: up to 64 MHz Cortex M0+ => ISR enter latency 234 ns
STM32F3: up to 72 Mhz Cortex M4 => ISR enter latency 166 ns
STM32G4: up to 170 Mhz Cortex M4 => ISR enter latency 70 ns
These calculation do not solve your problem though, because we have to consider as well what happens after the MCU entered the service routine. Several things come to my mind here:
Backup of additional registers depending on the complexity of the ISR
Code for an application specific reaction to the event (e.g. toggling an GPIO)
Wait-states for FLASH / RAM / Peripheral accesses. The higher the core clock, the more wait-states are typically needed because external parts are clocked at lower frequency.
Code for acknowledging / clearing the interrupt request
The last point can be postponed behind the application specific response an thus does not necessarily count to the reaction time, but all other points can have a significant impact. In order to fulfill your requirement with a cost-efficient STM32 MCU (I suppose you have selected the STM32F0 for this reason) you need to have good control over the number of instructions in the ISR. I do not recommend to use assembler here, but you should not rely on the CubeMX HAL implementation.
Since you say you need only little code in the ISR lets do a quick estimation:
Saving two additional registers on the stack => 2 instructions
Toggle a GPIO with a read-modify-write sequence => 3 instructions
Let's assume that each instruction takes 2 cycles, we need another 10 clock cycles.
Using this best-case scenario we can have a look at our list with rather low-cost STM32 MCU's again:
STM32F0: 16 + 10 cycles at 48 MHz => 541 ns
STM32G0: 15 + 10 cycles at 64 MHz => 390 ns
STM32F3: 12 + 10 cycles at 72 MHz => 300 ns
STM32G4: 12 + 10 cycles at 170 MHz => 130 ns
With these numbers, a Cortex M0/M0+ looks not like the right choice. You will better go for a M3/M4 core with at least 64 MHz clock rate. I think the new G4 could be a good solution.
Anyway, I strongly recommend evaluating the real-world performance with the real-world requirement, since there are too many factors that can affect above latency calculations.
The Cortex-M processors push a stack frame on exception entry, for your Cortex M0 the minimum time from assertion of the exception to running the first instruction of the interrupt handler is 16 clock cycles (assuming zero wait state memory).
The only way to make this take less time is to use a higher clock speed.
Related
I'm trying to send an array of 10 bytes between 2 nucleo boards (NUCLEO-L432KCU) using SPI and DMA. My goal is to develop the code for the slave board using the Low Level APIs. The master board is used simply for testing and, when everything will work, it will be replaced with the real system.
Before continuing, here are some more details about the system: The sender is configured as master. The code for the master is developed using the HAL API. The Chip Select on the master board is implemented using a GPIO.
The receiver is configured as slave with the option Receive only slave enabled and Hardware NSS input. The initialization code is generated automaGically using the CubeMX tool.
With my current implementation I'm able to receive data on the slave board but only once: in practice is seems that the interrupt fires only once and I'm having hard time to figure out what I am missing!
I believe the error has something to do with clearing some interrupt flags. I went through the reference manual but I cannot see what I'm doing wrong.
Following is my code for both sender and receiver.
Code for the sender
Note: Concerning the sender I report only the main function since all the other code is auto-generated. Furthermore, I have checked with a logic analyzer that the code works. Please let me know if you need more details.
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_DMA_Init();
MX_SPI1_Init();
MX_SPI3_Init();
MX_USART2_UART_Init();
MX_TIM1_Init();
/* USER CODE BEGIN 2 */
uint8_t test[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A};
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port,SPI1_CS_Pin,RESET);
HAL_SPI_Transmit(&hspi1,test,sizeof(test),1000);
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port,SPI1_CS_Pin,SET);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
Code for the receiver
Note: The configuration of the DMA and the SPI is mostly done automatically by the CubeMX tool. The other initializations for my project are provided into the main function.
uint8_t aRxBuffer[10];
uint8_t received_buffer[100];
uint16_t cnt = 0;
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_DMA_Init();
MX_SPI1_Init();
MX_SPI3_Init();
MX_USART2_UART_Init();
MX_TIM1_Init();
/* USER CODE BEGIN 2 */
// Custom configuration of DMA (after calling function MX_SPI3_INIT()
// Configure address of the buffer for receiving data
LL_DMA_ConfigAddresses(DMA2, LL_DMA_CHANNEL_1, LL_SPI_DMA_GetRegAddr(SPI3), (uint32_t)aRxBuffer,LL_DMA_GetDataTransferDirection(DMA2, LL_DMA_CHANNEL_1));
// Configure data length
LL_DMA_SetDataLength(DMA2, LL_DMA_CHANNEL_1,10);
// Enable DMA Transfer complete interrupt
LL_DMA_EnableIT_TC(DMA2, LL_DMA_CHANNEL_1);
// LL_DMA_EnableIT_TE(DMA2, LL_DMA_CHANNEL_1);
// We Want the SPI3 to receive 8-bit data
// Therefore we trigger the RXNE interrupt when the FIFO level is greater than or equal to 1/4 (8bit)
// See pag. 1221 of the TRM
LL_SPI_SetRxFIFOThreshold(SPI3,LL_SPI_RX_FIFO_TH_QUARTER);
LL_SPI_EnableDMAReq_RX(SPI3);
// Enable SPI_3
LL_SPI_Enable(SPI3);
// Enable DMA_2,CHANNEL_1
LL_DMA_EnableChannel(DMA2, LL_DMA_CHANNEL_1);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
Following is the IRQ handler (the commented code represents the various attempts to make it working!):
void DMA2_Channel1_IRQHandler(void)
{
/* USER CODE BEGIN DMA2_Channel1_IRQn 0 */
// Transfer-complete interrupt management
if(LL_DMA_IsActiveFlag_TC1(DMA2))
{
//LL_DMA_ClearFlag_TC1(DMA2);
LL_DMA_ClearFlag_GI1(DMA2);
/* Call function Transmission complete Callback */
DMA1_TransmitComplete_Callback();
}
else if(LL_DMA_IsActiveFlag_TE1(DMA2))
{
/* Call Error function */
int _error = 0;
}
// Enable SPI_3
//LL_SPI_Disable(SPI3);
// Enable DMA_2,CHANNEL_1
//LL_DMA_DisableChannel(DMA2, LL_DMA_CHANNEL_1);
//LL_DMA_EnableIT_TC(DMA2, LL_DMA_CHANNEL_1);
// LL_DMA_EnableIT_TE(DMA2, LL_DMA_CHANNEL_1);
// We Want the SPI3 to receive 8-bit data
// Therefore we trigger the RXNE interrupt when the FIFO level is greater than or equal to 1/4 (8bit)
// See pag. 1221 of the TRM
//LL_SPI_SetRxFIFOThreshold(SPI3,LL_SPI_RX_FIFO_TH_QUARTER);
//LL_SPI_EnableDMAReq_RX(SPI3);
// Enable SPI_3
//LL_SPI_Enable(SPI3);
// Enable DMA_2,CHANNEL_1
LL_DMA_EnableChannel(DMA2, LL_DMA_CHANNEL_1);
// LL_DMA_EnableIT_TE(DMA2, LL_DMA_CHANNEL_1);
/* USER CODE END DMA2_Channel1_IRQn 0 */
/* USER CODE BEGIN DMA2_Channel1_IRQn 1 */
/* USER CODE END DMA2_Channel1_IRQn 1 */
}
Following is the initialization for the SPI and the DMA (auto-generated):
/* SPI1 init function */
void MX_SPI1_Init(void)
{
LL_SPI_InitTypeDef SPI_InitStruct;
LL_GPIO_InitTypeDef GPIO_InitStruct;
/* Peripheral clock enable */
LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SPI1);
/**SPI1 GPIO Configuration
PA1 ------> SPI1_SCK
PA7 ------> SPI1_MOSI
*/
GPIO_InitStruct.Pin = SCLK1_to_SpW_Pin|MOSI1_to_SpW_Pin;
GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
GPIO_InitStruct.Alternate = LL_GPIO_AF_5;
LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
SPI_InitStruct.TransferDirection = LL_SPI_FULL_DUPLEX;
SPI_InitStruct.Mode = LL_SPI_MODE_MASTER;
SPI_InitStruct.DataWidth = LL_SPI_DATAWIDTH_4BIT;
SPI_InitStruct.ClockPolarity = LL_SPI_POLARITY_LOW;
SPI_InitStruct.ClockPhase = LL_SPI_PHASE_1EDGE;
SPI_InitStruct.NSS = LL_SPI_NSS_SOFT;
SPI_InitStruct.BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV8;
SPI_InitStruct.BitOrder = LL_SPI_LSB_FIRST;
SPI_InitStruct.CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE;
SPI_InitStruct.CRCPoly = 7;
LL_SPI_Init(SPI1, &SPI_InitStruct);
LL_SPI_SetStandard(SPI1, LL_SPI_PROTOCOL_MOTOROLA);
LL_SPI_EnableNSSPulseMgt(SPI1);
}
/* SPI3 init function */
void MX_SPI3_Init(void)
{
LL_SPI_InitTypeDef SPI_InitStruct;
LL_GPIO_InitTypeDef GPIO_InitStruct;
/* Peripheral clock enable */
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_SPI3);
/**SPI3 GPIO Configuration
PA4 ------> SPI3_NSS
PB3 (JTDO-TRACESWO) ------> SPI3_SCK
PB5 ------> SPI3_MOSI
*/
GPIO_InitStruct.Pin = LL_GPIO_PIN_4;
GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
GPIO_InitStruct.Alternate = LL_GPIO_AF_6;
LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = SCLK_from_SpW_Pin|MOSI_from_SpW_Pin;
GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
GPIO_InitStruct.Alternate = LL_GPIO_AF_6;
LL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* SPI3 DMA Init */
/* SPI3_RX Init */
LL_DMA_SetPeriphRequest(DMA2, LL_DMA_CHANNEL_1, LL_DMA_REQUEST_3);
LL_DMA_SetDataTransferDirection(DMA2, LL_DMA_CHANNEL_1, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
LL_DMA_SetChannelPriorityLevel(DMA2, LL_DMA_CHANNEL_1, LL_DMA_PRIORITY_LOW);
LL_DMA_SetMode(DMA2, LL_DMA_CHANNEL_1, LL_DMA_MODE_NORMAL);
LL_DMA_SetPeriphIncMode(DMA2, LL_DMA_CHANNEL_1, LL_DMA_PERIPH_NOINCREMENT);
LL_DMA_SetMemoryIncMode(DMA2, LL_DMA_CHANNEL_1, LL_DMA_MEMORY_INCREMENT);
LL_DMA_SetPeriphSize(DMA2, LL_DMA_CHANNEL_1, LL_DMA_PDATAALIGN_BYTE);
LL_DMA_SetMemorySize(DMA2, LL_DMA_CHANNEL_1, LL_DMA_MDATAALIGN_BYTE);
/* SPI3 interrupt Init */
NVIC_SetPriority(SPI3_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),0, 0));
NVIC_EnableIRQ(SPI3_IRQn);
SPI_InitStruct.TransferDirection = LL_SPI_SIMPLEX_RX;
SPI_InitStruct.Mode = LL_SPI_MODE_SLAVE;
SPI_InitStruct.DataWidth = LL_SPI_DATAWIDTH_4BIT;
SPI_InitStruct.ClockPolarity = LL_SPI_POLARITY_LOW;
SPI_InitStruct.ClockPhase = LL_SPI_PHASE_1EDGE;
SPI_InitStruct.NSS = LL_SPI_NSS_HARD_INPUT;
SPI_InitStruct.BitOrder = LL_SPI_LSB_FIRST;
SPI_InitStruct.CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE;
SPI_InitStruct.CRCPoly = 7;
LL_SPI_Init(SPI3, &SPI_InitStruct);
LL_SPI_SetStandard(SPI3, LL_SPI_PROTOCOL_MOTOROLA);
LL_SPI_DisableNSSPulseMgt(SPI3);
}
Thank you.
I recently implemented a similar system, and I hope I can help. I have a few questions, comments, which can possibly solve your problem, but it is hard to do so without being there.
Do you know if it is the SPI or DMA that is fauly? Does an SPI interrupt occur on the slave? This would mean the DMA is faulty, and not the SPI. It is important to know exactly where the system fails.
LL_SPI_SetRxFIFOThreshold(SPI3,LL_SPI_RX_FIFO_TH_QUARTER); is necessary, but should be done during the init
The TCIF flag should be cleared (as you did) during the IRQ.
You should set the SPI to trigger the DMA (I don't see it in your code) using the SPI_CR2_RXDMAEN register. This you should also do during the init if you do not know when you will receive data.
For the same reason I think you should enable the DMA channel during the init, and keep it enabled.
I hope one of these comments help. If not, we will try again.
Edit: Good work. I am glad you got it running by solving most of the issues. With the information you provided I figured out what was the main problem with the buffer.
You set the DMA to receive 10 bytes with:
LL_DMA_SetDataLength(DMA2, LL_DMA_CHANNEL_1,10);
This sets the DMA internal counter to 10. For every byte that it receives the counter decreases by one, until it reaches zero. This is what enables it to count 10 bytes. In normal mode, if you want to receive another 10 bytes, then you need to send that command again. In circular mode this value will reset automatically to 10, which means that it can receive another 10 bytes.
Therefore, if you are expecting to always receive 10 bytes then the cicular mode should work just fine for you. If not, then you will have to use normal mode, and specify to the MCU how many bytes you expect (a little more complicated).
From the code
stm32l4xx_hal_spi.c: 55
Master Receive mode restriction:
(#) In Master unidirectional receive-only mode (MSTR =1, BIDIMODE=0, RXONLY=1)
or bidirectional receive mode (MSTR=1, BIDIMODE=1, BIDIOE=0), to ensure
that the SPI does not initiate a new transfer the following procedure has
to be respected:
(##) HAL_SPI_DeInit()
(##) HAL_SPI_Init()
So before you call HAL_SPI_Receive_DMA()
call HAL_SPI_DeInit and HAL_SPI_Init and it should work.
I found that if you just call
HAL_DMA_DeInit(HSPI_Handle->hdmatx) ;
HAL_DMA_Init(HSPI_Handle->hdmtx);
Also works and is only 70us vs 106us.
In my project, I am using Master SPI communication to get analog data from external ADC. My MCU is STM32F746ZGTX. My system needs to work real time so I used SPI DMA Receive and Transmit functions.
I am reading all external ADC data correctly with SPI polling without using DMA. In SPI polling, I am first sending control byte to external ADC, in that time program is waiting in while(SPI_Ready) loop and then starts to receive all ADC data. This scenario works perfectly.
But I do not want to wait in while(SPI_Ready) loop in my every ADC reading. Because It affects my real time calculations. That's why I switched my functions to DMA.
My new algorithm is like that in below:
Generate External GPIO Interrupt with falling edge trigger to sense data ready output of external ADC.
Make chip select pin low to start communication with external ADC
Send read command to External ADC with HAL_SPI_Transmit_DMA() function.
In HAL_SPI_TxCpltCallback function, trigger HAL_SPI_Receive_DMA()
In HAL_SPI_RxCpltCallback function, buffer received ADC data and make chip select pin high to terminate communication.
When I use this algorithm, I am getting always 0xFF values in my ADC buffer. It seems like even if ADC is not sending raw data, because of triggering receive DMA, My MCU sends clock and sense all logic high signal as a received data.
I am sharing my codes in below. If you have any sugggestion where I am wrong, please share your opinions.
/* SPI1 init function */
static void MX_SPI1_Init(void)
{
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_HIGH;
hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 7;
hspi1.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
hspi1.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
if (HAL_SPI_Init(&hspi1) != HAL_OK)
{
}
}
void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi)
{
GPIO_InitTypeDef GPIO_InitStruct;
if(hspi->Instance==SPI1)
{
/* USER CODE BEGIN SPI1_MspInit 0 */
/* USER CODE END SPI1_MspInit 0 */
/* Peripheral clock enable */
__HAL_RCC_SPI1_CLK_ENABLE();
/**SPI1 GPIO Configuration
PA5 ------> SPI1_SCK
PA6 ------> SPI1_MISO
PB5 ------> SPI1_MOSI
*/
GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* SPI1 DMA Init */
/* SPI1_RX Init */
hdma_spi1_rx.Instance = DMA2_Stream0;
hdma_spi1_rx.Init.Channel = DMA_CHANNEL_3;
hdma_spi1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_spi1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_spi1_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_spi1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_spi1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_spi1_rx.Init.Mode = DMA_NORMAL;
hdma_spi1_rx.Init.Priority = DMA_PRIORITY_LOW;
hdma_spi1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_spi1_rx) != HAL_OK)
{
}
__HAL_LINKDMA(hspi,hdmarx,hdma_spi1_rx);
/* SPI1_TX Init */
hdma_spi1_tx.Instance = DMA2_Stream3;
hdma_spi1_tx.Init.Channel = DMA_CHANNEL_3;
hdma_spi1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_spi1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_spi1_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_spi1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_spi1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_spi1_tx.Init.Mode = DMA_NORMAL;
hdma_spi1_tx.Init.Priority = DMA_PRIORITY_LOW;
hdma_spi1_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_spi1_tx) != HAL_OK)
{
}
__HAL_LINKDMA(hspi,hdmatx,hdma_spi1_tx);
/* SPI1 interrupt Init */
HAL_NVIC_SetPriority(SPI1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(SPI1_IRQn);
/* USER CODE BEGIN SPI1_MspInit 1 */
/* USER CODE END SPI1_MspInit 1 */
/* USER CODE BEGIN SPI1_MspInit 1 */
/* USER CODE END SPI1_MspInit 1 */
}
}
void HAL_SPI_MspDeInit(SPI_HandleTypeDef* hspi)
{
if(hspi->Instance==SPI1)
{
/* USER CODE BEGIN SPI1_MspDeInit 0 */
/* USER CODE END SPI1_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_SPI1_CLK_DISABLE();
/**SPI1 GPIO Configuration
PA5 ------> SPI1_SCK
PA6 ------> SPI1_MISO
PB5 ------> SPI1_MOSI
*/
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_5|GPIO_PIN_6);
HAL_GPIO_DeInit(GPIOB, GPIO_PIN_5);
/* USER CODE BEGIN SPI1_MspDeInit 1 */
/* Peripheral DMA DeInit*/
HAL_DMA_DeInit(hspi->hdmarx);
HAL_DMA_DeInit(hspi->hdmatx);
/* Peripheral interrupt Deinit*/
HAL_NVIC_DisableIRQ(SPI2_IRQn);
/* USER CODE END SPI1_MspDeInit 1 */
}
}
/* External Interrupt for data ready output of ADC */
void EXTI15_10_IRQHandler(void)
{
/* USER CODE BEGIN EXTI15_10_IRQn 0 */
/* USER CODE END EXTI15_10_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_15);
/* USER CODE BEGIN EXTI15_10_IRQn 1 */
adc_selectADC(); /* Make Chip Select pin low */
HAL_SPI_Transmit_DMA (&hspi1, &controlByte, 1);
}
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
if (hspi->Instance == hspi1.Instance)
{
/* Transmit is completed */
/* Trigger receive DMA to get raw data from external ADC */
HAL_SPI_Receive_DMA (&hspi1, (uint8_t*)adcRecBuffer, 24);
}
}
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
{
if (hspi->Instance == hspi1.Instance)
{
/* Receive is completed */
adc_deselectADC(); /* Make Chip Select pin high */
}
}
***Working Algorithm without using DMA or Interrupt:***
/* External Interrupt for data ready output of ADC */
void EXTI15_10_IRQHandler(void)
{
/* USER CODE BEGIN EXTI15_10_IRQn 0 */
/* USER CODE END EXTI15_10_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_15);
/* USER CODE BEGIN EXTI15_10_IRQn 1 */
adc_selectADC(); /* Make Chip Select pin low */
while(HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY);
HAL_SPI_Transmit(&hspi1, &controlByte, 1,1);
while(HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY);
while(HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY);
HAL_SPI_Receive(&hspi1, (uint8_t*)adcRecBuffer, 24, 1);
while(HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY);
adc_deselectADC(); /* Make Chip Select pin high*/
}
SPI normally is full-duplex, meaning that "reading" is actually master generating clocks and transmitting zeroes. In STM HAL implementation, "receive" function just sends the data you have in read buffer. Might be zeroes, might be garbage, which your ADC interprets as some commands and enters some sort of bad state.
Try doing TransmitReceive of 2 25 byte buffers with your command ID first ("control byte"), followed by 24 zero bytes in TX buffer. In response you should get RX buffer of size 25, where first byte can be discarded. This way you need to handle only RXCplt interrupt, where you release ADC CS pin.
I'm using an STM32F469 Discovery board and I'm trying to use the CAN features.
I understand that on this board CAN1 cannot be used at the same time as the touchscreen. Therefore I need to use CAN2, but in order to enable CAN2, CAN1 needs to be enabled.
My code for configuration/callback is as follows:
/* CAN1 Values */
#define CAN1_CLK_ENABLE() __HAL_RCC_CAN1_CLK_ENABLE()
#define CAN1_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE()
#define CAN1_FORCE_RESET() __HAL_RCC_CAN1_FORCE_RESET()
#define CAN1_RELEASE_RESET() __HAL_RCC_CAN1_RELEASE_RESET()
#define CAN1_TX_PIN GPIO_PIN_9
#define CAN1_TX_GPIO_PORT GPIOB
#define CAN1_TX_AF GPIO_AF9_CAN1
#define CAN1_RX_PIN GPIO_PIN_8
#define CAN1_RX_GPIO_PORT GPIOB
#define CAN1_RX_AF GPIO_AF9_CAN1
#define CAN1_RX_IRQn CAN1_RX0_IRQn
#define CAN1_RX_IRQHandler CAN1_RX0_IRQHandler
/* CAN2 Values */
#define CAN2_CLK_ENABLE() __HAL_RCC_CAN2_CLK_ENABLE()
#define CAN2_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE()
#define CAN2_FORCE_RESET() __HAL_RCC_CAN2_FORCE_RESET()
#define CAN2_RELEASE_RESET() __HAL_RCC_CAN2_RELEASE_RESET()
#define CAN2_TX_PIN GPIO_PIN_13
#define CAN2_TX_GPIO_PORT GPIOB
#define CAN2_TX_AF GPIO_AF9_CAN2
#define CAN2_RX_PIN GPIO_PIN_5
#define CAN2_RX_GPIO_PORT GPIOB
#define CAN2_RX_AF GPIO_AF9_CAN2
#define CAN2_RX_IRQn CAN2_RX0_IRQn
#define CAN2_RX_IRQHandler CAN2_RX0_IRQHandler
CAN_HandleTypeDef CanHandle1;
CAN_HandleTypeDef CanHandle2;
static uint8_t Message_Data[8];
static void CAN1_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
CAN_FilterConfTypeDef CAN_FilterInitStructure;
static CanTxMsgTypeDef TxMessage;
static CanRxMsgTypeDef RxMessage;
/* CAN1 peripheral clock enable */
CAN1_CLK_ENABLE();
CAN1_GPIO_CLK_ENABLE();
/* CAN1 TX GPIO pin configuration */
GPIO_InitStruct.Pin = CAN1_TX_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FAST;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Alternate = CAN1_TX_AF;
HAL_GPIO_Init(CAN1_TX_GPIO_PORT, &GPIO_InitStruct);
/* CAN1 RX GPIO pin configuration */
GPIO_InitStruct.Pin = CAN1_RX_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FAST;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Alternate = CAN1_RX_AF;
HAL_GPIO_Init(CAN1_RX_GPIO_PORT, &GPIO_InitStruct);
/* NVIC configuration for CAN1 reception complete interrupt */
HAL_NVIC_SetPriority(CAN1_RX_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(CAN1_RX_IRQn);
CanHandle1.Instance = CAN1;
CanHandle1.pTxMsg = &TxMessage;
CanHandle1.pRxMsg = &RxMessage;
/* CAN peripheral init */
CanHandle1.Init.TTCM = DISABLE;
CanHandle1.Init.ABOM = DISABLE;
CanHandle1.Init.AWUM = DISABLE;
CanHandle1.Init.NART = DISABLE;
CanHandle1.Init.RFLM = DISABLE;
CanHandle1.Init.TXFP = DISABLE;
CanHandle1.Init.Mode = CAN_MODE_LOOPBACK;
CanHandle1.Init.SJW = CAN_SJW_1TQ;
CanHandle1.Init.BS1 = CAN_BS1_6TQ;
CanHandle1.Init.BS2 = CAN_BS2_8TQ;
CanHandle1.Init.Prescaler = 2;
HAL_CAN_Init(&CanHandle1);
/* CAN filter init */
CAN_FilterInitStructure.FilterNumber = 0;
CAN_FilterInitStructure.FilterMode = CAN_FILTERMODE_IDMASK;
CAN_FilterInitStructure.FilterScale = CAN_FILTERSCALE_32BIT;
CAN_FilterInitStructure.FilterIdHigh = 0x0000;
CAN_FilterInitStructure.FilterIdLow = 0x0000;
CAN_FilterInitStructure.FilterMaskIdHigh = 0x0000;
CAN_FilterInitStructure.FilterMaskIdLow = 0x0000;
CAN_FilterInitStructure.FilterFIFOAssignment = 0;
CAN_FilterInitStructure.FilterActivation = ENABLE;
CAN_FilterInitStructure.BankNumber = 0;
HAL_CAN_ConfigFilter(&CanHandle1, &CAN_FilterInitStructure);
/* Configure transmission */
CanHandle1.pTxMsg->StdId = 0x7DF;
CanHandle1.pTxMsg->ExtId = 0x7DF;
CanHandle1.pTxMsg->RTR = CAN_RTR_DATA;
CanHandle1.pTxMsg->IDE = CAN_ID_STD;
CanHandle1.pTxMsg->DLC = 8;
}
static void CAN2_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
CAN_FilterConfTypeDef CAN_FilterInitStructure;
static CanTxMsgTypeDef TxMessage;
static CanRxMsgTypeDef RxMessage;
/* CAN2 peripheral clock enable */
CAN2_CLK_ENABLE();
CAN2_GPIO_CLK_ENABLE();
/* CAN2 TX GPIO pin configuration */
GPIO_InitStruct.Pin = CAN2_TX_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FAST;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Alternate = CAN2_TX_AF;
HAL_GPIO_Init(CAN2_TX_GPIO_PORT, &GPIO_InitStruct);
/* CAN2 RX GPIO pin configuration */
GPIO_InitStruct.Pin = CAN2_RX_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FAST;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Alternate = CAN2_RX_AF;
HAL_GPIO_Init(CAN2_RX_GPIO_PORT, &GPIO_InitStruct);
/* NVIC configuration for CAN2 reception complete interrupt */
HAL_NVIC_SetPriority(CAN2_RX_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(CAN2_RX_IRQn);
CanHandle2.Instance = CAN2;
CanHandle2.pTxMsg = &TxMessage;
CanHandle2.pRxMsg = &RxMessage;
/* CAN peripheral init */
CanHandle2.Init.TTCM = DISABLE;
CanHandle2.Init.ABOM = DISABLE;
CanHandle2.Init.AWUM = DISABLE;
CanHandle2.Init.NART = DISABLE;
CanHandle2.Init.RFLM = DISABLE;
CanHandle2.Init.TXFP = DISABLE;
CanHandle2.Init.Mode = CAN_MODE_LOOPBACK;
CanHandle2.Init.SJW = CAN_SJW_1TQ;
CanHandle2.Init.BS1 = CAN_BS1_6TQ;
CanHandle2.Init.BS2 = CAN_BS2_8TQ;
CanHandle2.Init.Prescaler = 2;
HAL_CAN_Init(&CanHandle2);
/* CAN filter init */
CAN_FilterInitStructure.FilterNumber = 0; //14 enables CAN1;
CAN_FilterInitStructure.FilterMode = CAN_FILTERMODE_IDMASK;
CAN_FilterInitStructure.FilterScale = CAN_FILTERSCALE_32BIT;
CAN_FilterInitStructure.FilterIdHigh = 0x0000;
CAN_FilterInitStructure.FilterIdLow = 0x0000;
CAN_FilterInitStructure.FilterMaskIdHigh = 0x0000;
CAN_FilterInitStructure.FilterMaskIdLow = 0x0000;
CAN_FilterInitStructure.FilterFIFOAssignment = 0;
CAN_FilterInitStructure.FilterActivation = ENABLE;
CAN_FilterInitStructure.BankNumber = 0; // 14 enables CAN1
HAL_CAN_ConfigFilter(&CanHandle2, &CAN_FilterInitStructure);
/* Configure transmission */
CanHandle2.pTxMsg->StdId = 0x7DF;
CanHandle2.pTxMsg->ExtId = 0x7DF;
CanHandle2.pTxMsg->RTR = CAN_RTR_DATA;
CanHandle2.pTxMsg->IDE = CAN_ID_STD;
CanHandle2.pTxMsg->DLC = 8;
}
void HAL_CAN_RxCpltCallback(CAN_HandleTypeDef* CanHandle)
{
EwBspYellowLedOn();
Message_Data[0] = CanHandle->pRxMsg->Data[0];
Message_Data[1] = CanHandle->pRxMsg->Data[1];
Message_Data[2] = CanHandle->pRxMsg->Data[2];
Message_Data[3] = CanHandle->pRxMsg->Data[3];
Message_Data[4] = CanHandle->pRxMsg->Data[4];
Message_Data[5] = CanHandle->pRxMsg->Data[5];
Message_Data[6] = CanHandle->pRxMsg->Data[6];
Message_Data[7] = CanHandle->pRxMsg->Data[7];
if (HAL_CAN_Receive_IT(CanHandle, CAN_FIFO0) != HAL_OK)
{
EwBspRedLedOn();
}
}
CAN_Transmit_Message(void)
{
CanHandle2.pTxMsg->StdId = 0x7DF;
CanHandle2.pTxMsg->ExtId = 0x7DF;
CanHandle2.pTxMsg->Data[0] = 0x02;
CanHandle2.pTxMsg->Data[1] = 0x01;
CanHandle2.pTxMsg->Data[2] = 0x0D;
CanHandle2.pTxMsg->Data[3] = 0x55;
CanHandle2.pTxMsg->Data[4] = 0x55;
CanHandle2.pTxMsg->Data[5] = 0x55;
CanHandle2.pTxMsg->Data[6] = 0x55;
CanHandle2.pTxMsg->Data[7] = 0x55;
if (HAL_CAN_Transmit(&CanHandle, 10) != HAL_OK)
{
EwBspOrangeLedOn();
}
HAL_Delay(10);
}
I then run the following in my main function to configure the CAN1, CAN2 and the interrupt:
/* Configure interrupt for CAN transmission */
CAN1_Config();
CAN2_Config();
HAL_CAN_Receive_IT(&CanHandle2, CAN_FIFO0);
And then I run the CAN_Transmit_Message().
When doing this I have verified the message successfully transmits (the orange LED does not turn on), the receive interrupt handler is then executed (yellow LED turns on) and the message is successfully received (red LED does not turn on).
However, on the second transmission of a message (another call to CAN_Transmit_Message()), the transmit once again succeeds, but the receive fails (red LED turns on).
I created this code by following the structure in the CAN_Networking example code, but I cannot figure out why it is failing at the HAL_CAN_Receive_IT function on the second message (after the first message is successfully received).
Note: After reading the stm32f4xx_HAL_CAN library file, I noticed there are two types of receive/transmit:
HAL_CAN_Transmit_IT/HAL_CAN_Receive_IT
HAL_CAN_Transmit/HAL_CAN_Receive
It says that 1. is nonblocking - I take it this means that another interrupt can be triggered whilst this transmit/receive is still running?
In my case I want to make sure that I receive the response data after sending the transmit to request it, so should I use function 2.? I.e. I would call HAL_CAN_Transmit with a suitable timeout, and then after it completes call HAL_CAN_Receive, again with a suitable timeout.
Do you call HAL_CAN_Receive_IT each time you get a response?
It's one shot. To keep receiving, call it again in your interrupt handler.
From the Reference Manual:
When a message has been received, it is available to the software in the FIFO output mailbox. Once the software has handled the message (e.g. read it) the software must release the FIFO output mailbox by means of the RFOM bit in the CAN_RFR register to make the next incoming message available.
HAL_CAN_Receive_IT contains lines that enable the interrupts and release the FIFO...
/* Enable interrupts: */
/* - Enable Error warning Interrupt */
/* - Enable Error passive Interrupt */
/* - Enable Bus-off Interrupt */
/* - Enable Last error code Interrupt */
/* - Enable Error Interrupt */
/* - Enable Transmit mailbox empty Interrupt */
__HAL_CAN_ENABLE_IT(hcan, CAN_IT_EWG |
CAN_IT_EPV |
CAN_IT_BOF |
CAN_IT_LEC |
CAN_IT_ERR |
CAN_IT_TME );
/* Process unlocked */
__HAL_UNLOCK(hcan);
if (FIFONumber == CAN_FIFO0)
{
/* Enable FIFO 0 message pending Interrupt */
__HAL_CAN_ENABLE_IT(hcan, CAN_IT_FMP0);
}
else
{
/* Enable FIFO 1 message pending Interrupt */
__HAL_CAN_ENABLE_IT(hcan, CAN_IT_FMP1);
}
I'am using STM32F4 board with CMSIS library and I want setup an interrupt driven SPI, it means an interrupt is triggered each time a byte is sent by the SPI peripheral. The initiaisation function is as below:
void init_SPI1(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
GPIO_InitTypeDef GPIO_InitStruct;
SPI_InitTypeDef SPI_InitStruct;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_6 | GPIO_Pin_5|GPIO_Pin_4;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// connect SPI1 pins to SPI alternate function
//GPIO_PinAFConfig(GPIOA, GPIO_PinSource4, GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1);
//Set chip select high
GPIOA->BSRRL |= GPIO_Pin_4; // set PE4 high
// enable peripheral clock
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
/* configure SPI1 in Mode 0
* CPOL = 0 --> clock is low when idle
* CPHA = 0 --> data is sampled at the first edge
*/
SPI_StructInit(&SPI_InitStruct); // set default settings
SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // set to full duplex mode, seperate MOSI and MISO lines
SPI_InitStruct.SPI_Mode = SPI_Mode_Master; // transmit in master mode, NSS pin has to be always high
SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b; // one packet of data is 8 bits wide
SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low; // clock is low when idle
SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge; // data sampled at first edge
SPI_InitStruct.SPI_NSS = SPI_NSS_Soft ; // set the NSS management to internal and pull internal NSS high
SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; // SPI frequency is APB2 frequency / 4
SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;// data is transmitted MSB first
SPI_Init(SPI1, &SPI_InitStruct);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = SPI1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* Enable SPI1*/
SPI_Cmd(SPI1, ENABLE);
return;
}
Then i just loopback SPI_MOSI to SPI_MISO and use a function that transmit the data (a very basic function that takes data from a buffer and then uses CMSIS functions for the transmission). The problem is that when the SPI interrupt is triggered, the program won't get out from the handler. the handler function looks lihe this:
void SPI1_IRQHandler()
{
int a;
a++;
SPI_I2S_ClearITPendingBit(SPI1,SPI_I2S_IT_TXE);
return;
}
Is it a problem in the CMSIS library, or I am not configuring the SPI interrupt in the good way? Please guide me to the right point.
EDIT
This is the function i use for data transmission
void write_SPI1()
{
int i;
for (i=0;i<SPI_TX_MAX; i++)
{
SPI_I2S_SendData(SPI1,spiTxBuff[i]);
SPI_I2S_ITConfig(SPI1,SPI_I2S_IT_RXNE,ENABLE);
}
}
and the interruption deals with the data reception, it just fill spiRxBuff when receiving new data.
void SPI1_IRQHandler()
{
while (SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)== RESET);
spiRxBuff[spiRxCount]= SPI_I2S_ReceiveData(SPI1);
spiRxCount++;
}
The variable used for Reception / Transmission are declared as below :
uint8_t spiTxBuff[SPI_TX_MAX] = {0x01,0x02,0x03,0x04,0x05,0x06};
uint8_t spiRxBuff[SPI_RX_MAX];
static volatile int spiRxCount= 0; // used in SPI1_IRQHandler
what is strange now is that i'am having {0x01,0x02,0x03,0x05,0x06} in spiRxBuff instead of {0x01,0x02,0x03,0x04,0x05,0x06}, but using debug mode the data in spiRxBuff are correct, what goes wrong in your opinion ?
You did not show the function doing the transmit, so I don't know exactly what are you trying to accomplish
Transmitting in a loop
If you are transmitting from a function (in a loop), then you don't need interrupts at all, just make sure that the TXE flag is set before you transmit. Note that you have to interleave sending and receiving somehow.
void SPI1_Transmit(uint8_t *send, uint8_t *receive, int count) {
while(count-- > 0) {
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE)!=SET) {
if(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE)==SET)
*receive++ = SPI_I2S_ReceiveData(SPI1);
}
SPI_I2S_SendData(SPI1, *send++);
}
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE)!=SET) {
/* wait for the last incoming byte */
}
*receive++ = SPI_I2S_ReceiveData(SPI1);
}
Transmitting from interrupt
The TXE interrupt flag is set as long as the SPI device is not busy sending. If you don't do something about it in the interrupt handler, it will trigger an interrupt immediately again and again. You can't clear it manually, but by transmitting another byte, and resetting the transmit interrupt enable flag before sending the last byte.
volatile int spi1_tx_count, spi1_rx_count;
uint8_t *spi1_tx_ptr;
volatile uint8_t *spi1_rx_ptr;
/* set these global variables before enabling interrupts */
void SPI1_IRQHandler() {
if (SPI_I2S_GetITStatus(SPI1, SPI_I2S_IT_TXE) == SET) {
if(--spi1_tx_count < 1)
SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_TXE, DISABLE);
SPI_I2S_SendData(SPI1, *spi1_tx_ptr++);
}
if(SPI_I2S_GetITStatus(SPI1, SPI_I2S_IT_RXNE) == SET) {
*spi_rx_ptr++ = SPI_I2S_ReceiveData(SPI1);
spi1_rx_count++;
}
}
Using DMA
The above examples are using processor power and cycles for a task that can be handled by the DMA conroller alone. A lot of (if not all) processor cycles, if you are talking to a peripheral at 2 MBit/s.
See Project/STM32F4xx_StdPeriph_Examples/SPI/SPI_TwoBoards in the library for an example.
Sorry, I haven't noticed at all that you've amended the question. Look like notifications are sent on new comments or answers, but not on edits.
There are multiple problems with your code. In write_SPI1(), I'd enable RX interrupt only once before the loop, there is no need to do it again and again. Also, you should definitely check whether the TX register is available before sending.
void write_SPI1() {
int i;
SPI_I2S_ITConfig(SPI1,SPI_I2S_IT_RXNE,ENABLE);
for (i=0;i<SPI_TX_MAX; i++) {
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE)!=SET)
;
SPI_I2S_SendData(SPI1,spiTxBuff[i]);
}
}
It is however a bad idea to wait on a flag in the interrupt handler. If RXNE is the only possible interrupt source, then you can proceed straight to receiving.
I have been working with this code for days and cannot figure out why my interrupts are not being triggered. I know data is coming through successfully because I used a probe on a logic analyzer, also my baud rate is correct as I can transmit with UART successfully.
At this point I'm lost, I've read the datasheet over and over and can't figure out my problem. I will try to include only the relative code but enough that you can see how things work in my project.
Please let me know if you see issues with this code.
Thank you!
Code snippets from main.c:
// USART RX interrupt priority
IPR1bits.RCIP = 0;
IPR1bits.TXIP = 0;
// configure the hardware USART device
OpenUSART(USART_TX_INT_OFF & USART_RX_INT_ON & USART_ASYNCH_MODE & USART_EIGHT_BIT &
USART_CONT_RX & USART_BRGH_LOW, 14);
Code snippets from interrupts.c
//----------------------------------------------------------------------------
// Low priority interrupt routine
// this parcels out interrupts to individual handlers
#pragma code
#pragma interruptlow InterruptHandlerLow
// This works the same way as the "High" interrupt handler
void InterruptHandlerLow() {
// check to see if we have an interrupt on USART RX
if (PIR1bits.RCIF) {
PIR1bits.RCIF = 0; //clear interrupt flag
uart_recv_int_handler();
}
// check to see if we have an interrupt on USART TX
if (PIR1bits.TXIF && PIE1bits.TXIE) {
// cannot clear TXIF, this is unique to USART TX
// so just call the handler
uart_tx_int_handler();
}
}
UART RX Interrupt Handler snippet:
void uart_recv_int_handler() {
int msgLen;
//if (DataRdyUSART()) {
uc_ptr->buffer[uc_ptr->buflen] = RCREG;
//uc_ptr->buffer[uc_ptr->buflen] = ReadUSART();
uc_ptr->buflen++;
}
}
Did you
- Set trisC6/7 correctly?
- if you have a part with analog inputs multiplexed on those pins, did you disable them?
- Is your BRG value validated for this part and these oscillator settings?
See also
http://www.piclist.com/techref/microchip/rs232.htm
I migrated to dspic, but I used to do the serial receive under interrupt. This I had in the interrupt (serialin1 is a power of two circular buffer, lastserialin1 the pointer into it, and ser1bufinmask is size of buffer-1)
if (PIR1bits.RCIF == 1) /* check if RC interrupt (receive USART) must be serviced
{
while (PIR1bits.RCIF == 1) /* flag becomes zero if buffer/fifo is empty */
{
lastserialin1=(lastserialin1+1)&ser1bufinmask;
serialin1[lastserialin1]=RCREG;
}
}
To initialize the uart I had:
// Configure USART
TXSTA = 0x20; // transmit enable
RCSTA = 0x90; // spen en cren
RCONbits.IPEN = 1; /* Interrupt Priority Enable Bit. Enable priority levels on interrupts */
INTCONbits.GIE = 1; /* Set GIE. Enables all high priority unmasked interrupts */
INTCONbits.GIEL = 1; /* Set GIEL. Enables all low priority unmasked interrupts */
TRISCbits.TRISC6 = 0; // page 237
TRISCbits.TRISC7 = 1; // page 237
Open1USART (
USART_TX_INT_OFF
&
USART_RX_INT_ON &
USART_ASYNCH_MODE &
USART_EIGHT_BIT & // 8-bit transmit/receive
USART_CONT_RX & // Continuous reception
// USART_BRGH_HIGH, 155); // High baud rate, 155 eq 19k2
USART_BRGH_HIGH, brgval); // High baud rate, 25 eq 115k2
IPR1bits.RCIP = 0;
PIR1bits.RCIF = 0;
with brgval calculated using
#define GetInstructionClock() (GetSystemClock()/4)
#define GetPeripheralClock() GetInstructionClock()
// See if we can use the high baud rate setting
#if ((GetPeripheralClock()+2*BAUD_RATE)/BAUD_RATE/4 - 1) <= 255
#define BRGVAL ((GetPeripheralClock()+2*BAUD_RATE)/BAUD_RATE/4 - 1)
#define BRGHVAL (1)
#else // Use the low baud rate setting
#define BRGVAL ((GetPeripheralClock()+8*BAUD_RATE)/BAUD_RATE/16 - 1)
#define BRGHVAL (0)
#endif