I know one can enable a UART receive interrupt using
HAL_UART_Receive_IT(&huart2, (uint8_t *)rx_buffer, expectedNumberOfBytes)
But once started how does one stop it, "manually"?
We can disable the UART interrupt using HAL_NVIC_DisableIRQ() (ex: HAL_NVIC_DisableIRQ(USART1_IRQn)). This will prevent it from raising an interrupt, but the state set by the function HAL_UART_Receive_IT which is HAL_UART_STATE_BUSY_RX needs to be set back to HAL_UART_STATE_READY for the uart handle to go back to a state that can accept a new HAL_UART_Receive_IT() call.
Question
How do I reset the state of the UART interrupt if I wish to disable a Rx interrupt after some time?
Stack Overflow questions do not address how to reset the state; I have referred to these questions:
Disabling interrupt in interrupt handler STM32F407
https://electronics.stackexchange.com/questions/100073/stm32-usart-rx-interrupts
I could use USART_ClearITPendingBit() or USART_ITConfig() but these are defined as private functions by STM's HAL library. So should I use them?
How [do I] reset the state of the UART interrupt if [I] wish to disable a Rx interrupt after some time[?]
(See it's usage in "stm32f4xx_hal_uart.c", for example.)
The huart->RxState member of a uart handle struct is really only used internally by the HAL library when doing things such as HAL_UART_Receive(), HAL_UART_Receive_IT(), HAL_UART_Receive_DMA(), (and many other internal functions like this), etc. If you manually implement your own interrupt-based and ring-buffer-based UART Tx and Rx calls, however, which is the preferred way to do it, this member is completely meaningless and it doesn't matter what you do with it, as it is used only inside HAL library function calls and HAL ISR handlers (neither of which you have to use), and really has nothing to do with the register-level interrupts and things directly.
By digging around the source code in stm32f4xx_hal_uart.c (for example), however, here are a couple valid options you can use:
1. How to reset huart->RxState to HAL_UART_STATE_READY:
Call HAL_UART_Init(). By inspecting its source code, you'll see it calls huart->RxState= HAL_UART_STATE_READY; before returning.
Just manually set huart->RxState = HAL_UART_STATE_READY; So long as you know you have properly stopped the interrupt-based receive in the middle of its processing, this is perfectly valid.
Let's take this further, however.
Imagine you are using UART7 on an STM32F4. Therefore, in your stm32f4xx_it.c interrupt handler file, you'll see the following code auto-generated by STM32CubeMX:
/**
* #brief This function handles UART7 global interrupt.
*/
void UART7_IRQHandler(void)
{
/* USER CODE BEGIN UART7_IRQn 0 */
/* USER CODE END UART7_IRQn 0 */
HAL_UART_IRQHandler(&huart7);
/* USER CODE BEGIN UART7_IRQn 1 */
/* USER CODE END UART7_IRQn 1 */
}
Let's go over some layers of disabling/enabling interrupts.
2. From broadest to narrowest scope, here are several ways to disable/enable the USART Rx interrupt:
You can disable/enable ALL interrupts, including this UART7_IRQHandler(), using these ARM-core CMSIS calls:
__disable_irq();
__enable_irq();
Source: https://stm32f4-discovery.net/2015/06/how-to-properly-enabledisable-interrupts-in-arm-cortex-m/
So, you could do the following to disable the interrupt, reset the RxState, and then start up the interrupt-based receive again when ready:
__disable_irq();
huart7->RxState= HAL_UART_STATE_READY;
__enable_irq();
HAL_UART_Receive_IT(&huart7, (uint8_t *)rx_buffer, expectedNumberOfBytes);
You can disable/enable ONLY the UART7_IRQHandler() interrupts (all 10 types of uart7 interrupts connected to this interrupt vector, including Tx-related, Rx-related, error-related, etc), using these STM32 HAL calls:
HAL_NVIC_DisableIRQ(UART7_IRQn);
HAL_NVIC_EnableIRQ(UART7_IRQn);
Then, do the same as just above except use these calls to disable/enable the interrupts instead.
If you dig down into the implementation of HAL_UART_IRQHandler(), however, which is called by UART7_IRQHandler(), you'll see that it only calls the interrupt-based receive handler, UART_Receive_IT(), if both the USART_SR_RXNE bit ("Receive Not Empty", inside the USART Status Register) and the USART_CR1_RXNEIE bit ("Receive Not Empty Interrupt Enable", inside the USART Control Register 1), are both set. The RXNE bit is set whenever a byte comes in, and is cleared whenever you read the data register or write a zero to it. The interrupt-enable bit is something you have full control over to disable this UART receive interrupt, and if you clear this bit manually, you will disable the receive interrupt withOUT disabling any other type of interrupt associated with this USART. This is the best way to do it, as there are 10 interrupt sources associated with this UART. In other words, clearing this bit not only causes the check inside HAL_UART_IRQHandler() to fail, but it also prevents the receive interrupt from happening in the first place! Refer to the Reference Manual RM0090 Rev 16, for example:
p969:
p1009:
p1011:
p1015:
p1013:
So, to disable/enable the USART Receive Not Empty interrupt only, do the following. Refer to the Control Register (USART_CR1) on p1013, shown just above.
// Disable the USART Receive Not Empty interrupt
CLEAR_BIT(huart7.Instance.CR1, USART_CR1_RXNEIE);
// Enable the USART Receive Not Empty interrupt
SET_BIT(huart7.Instance.CR1, USART_CR1_RXNEIE);
Now, you could do the following to disable the USART Receive interrupt, reset the HAL RxState, and then start up the interrupt-based receive again when ready:
CLEAR_BIT(huart7.Instance.CR1, USART_CR1_RXNEIE);
huart7->RxState= HAL_UART_STATE_READY;
SET_BIT(huart7.Instance.CR1, USART_CR1_RXNEIE); // This call isn't actually necessary, as this bit is set inside `HAL_UART_Receive_IT()` as well
HAL_UART_Receive_IT(&huart7, (uint8_t *)rx_buffer, expectedNumberOfBytes);
3. How to (awkwardly) use HAL_UART_Receive_IT() for continual interrupt-based receiving.
TODO
4. Why HAL_UART_Receive_IT() really isn't a very useful function after-all.
TODO
5. How to manually configure your own interrupt-based UART Tx and Rx ISRs and functions.
TODO
You can use HAL_UART_Abort_IT.
Most UARTs clear any pending Receive interrupt when the program reads from the holding register. So my answer would be: simply read the data register after disabling interrupts, and ignore the result.
I haven't had a chance to try this on my STM32 yet, but...
There is a function static void UART_EndRxTransfer(UART_HandleTypeDef *huart)
in the HAL library that does the following:
Disable RXNE, PE and ERR interrupts
restore huart->RxState to Ready
I found that function in the stm32f7xx_hal_uart.c file. However, it is defined as static, so I just copied over the definition into the file where I used it. It might be a bit hacky but it worked for me.
Related
I am writing code with the STM323 ide and using the STM32f303re nucleo board. I configured pin PA7 as an interrupt, however it does not have its own interrupt handler like previous development boards i've worked with.
As you can see, this one interrupt handler handles interrupts from pins 9-5, thus if any of these pins are triggered they will call the same interrupt. I need to be able to perform different functions depending on which pin is triggered. Is there a way to know which specific pin was causing the interrupt?
You can use EXTI_GetITStatus, to check which line causing interrupt.
/* Handle PA7 interrupt */
void EXTI9_5_IRQHandler(void) {
/* Make sure that interrupt flag is set */
if (EXTI_GetITStatus(EXTI_Line7) != RESET) {
/* Do your stuff when PA7 is changed */
/* Clear interrupt flag */
EXTI_ClearITPendingBit(EXTI_Line7);
}
}
Do not forget to clear flag after.
For the cases when multiple pins share an interrupt, when the interrupt fires, you need to check what pin specifically caused an interrupt. Unlike pin 1 interrupt, where the interrupt itself means it was pin 1 and you can process it right away, in this case an interrupt means "it's either pin 5, or 6, or 7, or 8, or 9", so in the ISR you need to check "was it pin 5? or 6?..."
I think it's a good opportunity to look directly into the registers of EXTI peripheral.
If you open the reference manual of your MCU on page 299, section 14.3.6, you can see this EXTI_PR1 register holds pending bits for lines 0..31. Those bits are marked as rc_w1, which from the start of the document means (reference manual, page 46):
read/clear (rc_w1)
Software can read as well as clear this bit by writing 1. Writing ‘0’
has no effect on the bit value.
So the logic is the following: if an interrupt of lines 5..9 occurred, you need to check what bit specifically in that register is set to 1, and then you write 1 there to reset it. This will clear the flag.
void EXTI9_5_IRQHandler(void)
{
if(EXTI_PR1 & (1U<<5U)) //check if it's line 5, returns 0 if PR5 is 0, otherwise returns non-zero, which is true
{
EXTI_PR1 |= (1U<<5U); //write 1 to that bit to clear it so interrupt doesn't fire again once ISR is finished
do_stuff_if_it's_pin5();
}
}
Alternatively, instead of 1U<<5U you should be able to use EXTI_PR1_PR5, so the code would look a little easier to read, like this:
void EXTI9_5_IRQHandler(void)
{
if(EXTI_PR1 & EXTI_PR1_PR5) //check if it's line 5, returns 0 if PR5 is 0, otherwise returns non-zero, which is true
{
EXTI_PR1 |= EXTI_PR1_PR5; //write 1 to that bit to clear it so interrupt doesn't fire again once ISR is finished
do_stuff_if_it's_pin5();
}
}
This is what the functions provided by #Lime7 do behind the scenes, I suspect (can't check, but it makes logical sense).
I don't have that microcontroller to test it, but it should work.
Other answers already covered the interrupt flags. I'm going to add some info regarding CubeMX and HAL, since it looks like you are using them.
When you enable a GPIO interrupt in CubeMX, the code generator adds a line in the appropriate IRQ handler function. When multiple interrupts are enabled, the generated code looks like so (comments removed):
void EXTI9_5_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_5);
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_6);
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_7);
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_8);
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_9);
}
The HAL_GPIO_EXTI_IRQHandler() function checks the interrupt status for the given pin and calls HAL_GPIO_EXTI_Callback() function if the flag is set.
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
/* EXTI line interrupt detected */
if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != RESET)
{
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
HAL_GPIO_EXTI_Callback(GPIO_Pin);
}
}
In the HAL flow, you don't typically write code directly in the IRQ handler, but implement the HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) function. The pin that triggered the interrupt is provided as a parameter for your convenience.
I am using two STM32H743 connected via RS232. These two modules connected to same power.
They use UART with DMA. When I turn on modules at the same time, UART and DMA starts correctly.
But when i restart one of the modules while other is awake, the reset module's UART and DMA does not start therefore they cannot communicate with each other.
This problem is also happened before with STM32F4 series. MCU is connected to FPGA and they communicate via UART. When FPGA starts before MCU, DMA and UART does not start properly. What could cause this problem?
Do i need to have a high-z or floating pin states before starting UART?
After lots of debugging hours, I finally found the cause and solution.
When first bytes reach to UART peripheral, due to clock mismatch, it triggers frame error then stops the DMA. This happens more than usual when UART datarate is very high. But I had added the ErrorCallback function to handle the interrupt. Unfortunately, I misused the function.
My use :
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
HAL_UART_MspDeInit();
HAL_UART_Receive_DMA(...);
}
HAL_UART_MspDeInit does not clear structs and initializations therefore Receive_DMA function cannot start it again. So, my communication stops.
Correct use :
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
HAL_UART_DeInit();
HAL_UART_Receive_DMA(...);
}
Thanks to three typos in my code, it caused to me a lot of time. But finally, it resolved.
The UART and DMA peripherals usually have an error detector, thus has it's flags into the status register. When an error happen, the STM32 HAL will stop any transfer ongoing and wait until you treat this fail. You can check, with the debug module, the HAL status registers to troubleshoot the problem, and add the treatment to it in you code. At first you could reset the peripheral by run DeInit() and right after run Init() routine of the peripheral with error, and reset any other piece of code e.g. state machines and stuff that uses the data from this peripheral.
I'm implementing a simple UART receive-transmit protocol on STM32F103, the library/boilerplate code I'm using here is LL, not HAL (as HAL includes insane amounts of overhead)
My problem is that after successfully entering the interrupt handler "USART1_IRQHandler" it keeps cycling on forever.
My code is here:
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
int ii = 0;
for(ii=0; ii<4; ii++){
LL_mDelay(40);
LL_GPIO_TogglePin(GPIOA, LL_GPIO_PIN_5);
LL_mDelay(40);
LL_GPIO_TogglePin(GPIOA, LL_GPIO_PIN_5);
}
uint8_t cc = LL_USART_ReceiveData8(USART1);
LL_USART_TransmitData8(USART1, cc);
LL_mDelay(130);
//LL_USART_ClearFlag_RXNE(USART1);
//NVIC_ClearPendingIRQ( USART1_IRQn );
/* USER CODE END USART1_IRQn 0 */
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
and in main.c I have:
LL_USART_EnableIT_RXNE(USART1);
while (1)
{
LL_mDelay(300);
LL_GPIO_TogglePin(GPIOA, LL_GPIO_PIN_5);
LL_mDelay(300);
LL_GPIO_TogglePin(GPIOA, LL_GPIO_PIN_5);
//LL_USART_EnableIT_TC(USART1);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
The GPIO_Toggle commands are just there to blink an led so I know what's going on. Here's waht happens:
When I power the MC up, it enters the main loop and blinks slowly.
When I send something (~10 bytes) through UART, the led starts blinking fast, indicating that it has entered the interrupt handler. PProblem is that it never stops and keeps spinning around in the interrupt handler.
I've tried using the now commented functions
LL_USART_ClearFlag_RXNE(USART1);
NVIC_ClearPendingIRQ( USART1_IRQn );
either alone or in combination but they have absolutely no effect on anything.
What am I doing wrong?
How can I exit the handler?
Actually everything in your USART interrupt handler is wrong.
You do not check what has caused the interrupt.
If it is RXNE flag you should just load the value from the DR register. You do not need to clear any flags. If it is TXE flag you can store the data to the DR register. You cant clear this flag any other way. If you do not have any data to send you need to disable the TXE interrupt. Otherwise it will trigger continuously.
You can't just read and write the data register when you want to. You need to know if you are allowed to
You should also control the error statuses.
You must not use any delays in the interrupt routines. Keep it as fast as possible.
Do not touch NVIC exept for enabling and disabling the interrupts, as for now you do not know what it is for.
The system time utilised for controlling delays is updated by a periodic sysTick interrupt. If the RXNE interrupt has a higher priority than the sysTick interrupt it won't be handled while you're inside your RXNE IRQ handler, so the time will never increment and your delay end time will never be reached. Depending on how your delay is implemented, it may just put the CPU in a spinlock that can never exit.
I'm developing software for an NXP LPC1788 microcontroller, and I'm using the embOS RTOS. Whenever a message is received over USB, I want to use the OS_PutMailCond() function to store the USB message in a mailbox which a handler function is waiting on. In other words, I want to make message handling interrupt-driven.
The embOS user manual can be found here. Page 145 describes the OS_PutMailCond() function.
Whenever a USB message is received, it triggers the USB interrupt service routine on the LPC, but to let embOS know that it's an ISR I have to place OS_EnterInterrupt() and OS_LeaveInterrupt() at the start and end of the ISR respectively. This is necessary if I want to call embOS functions within it, including OS_PutMailCond().
The problem is that if I put OS_EnterInterrupt()/OS_LeaveInterrupt() anywhere within the USB ISR, the USB stops functioning properly and Windows informs me that the device has malfunctioned.
I have no idea why this is the case. We've tried something similar for handling messages over CAN, as shown below, and it works fine.
void CAN_IRQHandler(void)
{
OS_EnterInterrupt();
...
if (MBfieldCANframeInitialised)
OS_PutMailCond (&MBfieldCANframe, &recMessage);
OS_LeaveInterrupt();
}
OS_EnterInterrupt() and OS_LeaveInterrupt() are described on pages 252 and 253 of the linked manual. From the additional information section of the former:
If OS_EnterInterrupt() is used, it should be the first function to be
called in the interrupt handler. It must be used with
OS_LeaveInterrupt() as the last function called. The use of this
function has the following effects, it:
disables task switches
keeps interrupts in internal routines disabled
EDIT
I've investigated further and found out that using OS_EnterInterrupt() and OS_LeaveInterrupt() within the USB ISR (and other ISR's like the one for the GPIO when a rising or falling edge is detected on a pin) causes an OS error. The error value is 166, which means "OS-function called from ISR with high priority".
I'll update if I find out anything else.
Problem solved. It turns out the guy that made this work for the CAN ISR changed the code of one of the embOS source files to set the CAN ISR priority level from 0 to 29 (higher level = lower priority). I did the same thing for the USB ISR:
void OS_InitHW(void) {
OS_IncDI();
//
// We assume, the PLL and core clock was already set by the SystemInit() function
// which was called from the startup code
// Therefore, we don't have to initailize any hardware here,
// we just ensure that the system clock variable is updated and then
// set the periodic system timer tick for embOS.
//
SystemCoreClockUpdate(); // Update the system clock variable (might not have been set before)
if (SysTick_Config (OS_PCLK_TIMER / OS_TICK_FREQ)) { // Setup SysTick Timer for 1 msec interrupts
while (1); // Handle Error
}
//
// Initialize NVIC vector base address. Might be necessary for RAM targets or application not running from 0
//
NVIC_VTOR = (OS_U32)&__Vectors;
//
// Set the interrupt priority for the system timer to 2nd lowest level to ensure the timer can preempt PendSV handler
//
NVIC_SetPriority(SysTick_IRQn, (1u << __NVIC_PRIO_BITS) - 2u);
NVIC_SetPriority(CANActivity_IRQn, (1u << __NVIC_PRIO_BITS) - 3u);
NVIC_SetPriority(CAN_IRQn, (1u << __NVIC_PRIO_BITS) - 3u);
NVIC_SetPriority(USB_IRQn, (1u << __NVIC_PRIO_BITS) - 3u);
OS_COM_INIT();
OS_DecRI();
}
I found this in the embOS documentation:
Why can a high priority ISR not use the OS API ?
embOS disables low priority interrupts when embOS data structures are modified. During this time high priority ISR are enabled. If they would call an embOS function, which also modifies embOS data, the embOS data structures would be corrupted.
I have read the ARM document about Cortex-M3 (or M0) and it say it can be used as level sensetive or pulse (edge) interrupt within the NVIC controller. The problem that it rather vague on how to do this, if this is done by software.
I fails to see any kind of register within the NVIC or such that control the type of the interrupt (to select edge or level by adjusting the register bits). So something must be done by software within handler but again it vague in this field.
I like to hear anyone having a way to make it edge or level trigger interrupt by software.
Please demonstrate within the handler code (if this control it) that the make it detect for level or pulse.
If this is level detect, I can hold interrupt active and disable by the handler, until restore by external code for which it re-excute the interrupt. This is what I'm trying to do, but it will not work if this is pulse detect type.
Thx
A document that describes how the Cortex-M3 NIVC handles level or edge (pulse) triggered interrupts can be found here:
Cortex-M3 Devices Generic User Guide, 4.2.9. Level-sensitive and pulse interrupts
This may well be the document you refer to in your question. Joseph Yiu's book, "The Definitive Guide to the ARM Cortex-M3" also has a pretty good description.
There is no particular configuration of the NVIC for these two interrupt signal types - it handles either kind. Essentially, when an interrupt is asserted (whterh level-based or edge triggered) the NVIC latches that status in the SETPENDx register. When the ISR for that interrupt is vectored to, the corresponding bit in the ACTIVEx register will be set and the bit in the SETPENDx register will be cleared.
While the interrupt is active, if the interrupt line transitions from inactive to active, the pending bit will be turned on again, and upon return from the current active ISR instance, the interrupt will be handled again. This handles the edge triggered interrupt case.
Also, when the ISR returns (and the NVIC clears the 'active' bit), the NIVC will reexamine the state of the interrupt line - if it's still asserted it will set the pending bit again (even if there hasn't been a a transition from inactive to active). This handles the case where an interrupt is level triggered, and the ISR didn't manage to cause the interrupt to be de-asserted (maybe a second device on a shared IRQ line asserted its interrupt at just the critical moment so there was no time when the interrupt line was inactive).
If this is level detect, I can hold interrupt active and disable by the handler, until restore by external code for which it re-execute the interrupt.
I'm not sure I really understand what you're after here, but I think that you might be able to do what you want using the NVIC's SETENAx and CLRENAx registers to enable/disable the interrupt. These work independently of the pending bits, so an interrupt can be pending (or become pending) even if the interrupt is disabled. So you can hold off handling an interrupt for as long as you want.
If that's not quite enough, also note that you can cause an interrupt to pend via software by simply setting the pending bit in the corresponding SETPENDx register - the CPU will vector to the ISR just as if a hardware interrupt were asserted (assuming the interrupt is enabled in the SETENAx register). you can also use the "Software Trigger Interrupt Register" (STIR) to trigger an interrupt by software.