The code snippets below come from the manufacturer. I am completely confused as to what they are trying to do. In particular, anything to do with InterruptHandler.
Please comment on the code in the globals section. Does this look right?
Also the various APIs included all have sticking points for me. Are they trying to use a void pointer? ... Plenty to ask, please comment mainly on how they are suggesting the timer2 would work.
...
/**
Section: Global Variables Definitions
*/
void (*TMR2_InterruptHandler)(void);
...
/**
Section: TMR2 APIs
*/
void TMR2_Initialize(void)
{
// Set TMR2 to the options selected in the User Interface
// T2CKPS 1:2; T2OUTPS 1:2; TMR2ON off;
T2CON = 0x11;
// T2CS LFINTOSC;
T2CLKCON = 0x04;
// T2PSYNC Not Synchronized; T2MODE Software control One shot; T2CKPOL Rising Edge; T2CKSYNC Not Synchronized;
T2HLT = 0x08;
// T2RSEL TMR4_postscaled;
T2RST = 0x02;
// PR2 8;
T2PR = 0x08;
// TMR2 0;
T2TMR = 0x00;
// Clearing IF flag before enabling the interrupt.
PIR4bits.TMR2IF = 0;
// Enabling TMR2 interrupt.
PIE4bits.TMR2IE = 1;
// Set Default Interrupt Handler
TMR2_SetInterruptHandler(TMR2_DefaultInterruptHandler);
// Start TMR2
TMR2_Start();
}
void TMR2_ISR(void)
{
// clear the TMR2 interrupt flag
PIR4bits.TMR2IF = 0;
if(TMR2_InterruptHandler)
{
TMR2_InterruptHandler();
}
}
void TMR2_SetInterruptHandler(void* InterruptHandler)
{
TMR2_InterruptHandler = InterruptHandler;
}
void TMR2_DefaultInterruptHandler(void)
{
// add your TMR2 interrupt custom code
// or set custom function using TMR2_SetInterruptHandler()
}
/**
End of File
*/
Thanks,
Steve
edit:
The manufacturer code includes a routine (I can't seem to add it here) for managing high-level interrupts that calls TMR2_ISR if (PIE4bits.TMR2IE == 1 && PIR4bits.TMR2IF == 1) This would seem to negate any TMR2_DefaultInterruptHandler would it not?
Thanks again for your help.
The comments of Garr Godfrey and Martin James provided helpful hints. Thus, I will try to fill the possible gaps:
void (*TMR2_InterruptHandler)(void); is a global variable with a function pointer providing the current interrupt handler.
I would read TMR2_ISR() as "interrupt service routine". From the presented code, it's not clear from where it is called. Suspecting from it's name, it's reasonable to assume that it's called when the interrupt is triggered. It checks whether the global interrupt handler function pointer TMR2_InterruptHandler is not NULL and calls the pointed function in case of success.
If TMR2_Initialize() has been called before then TMR2_InterruptHandler points to function TMR2_DefaultInterruptHandler(). Thus, in this case, the latter is called from TMR2_ISR().
void TMR2_SetInterruptHandler(void* InterruptHandler) is used in TMR2_Initialize(). It sets the current interrupt handler. (Surprise.)
I'm a little bit puzzled because I would make it:
void TMR2_SetInterruptHandler(void (*InterruptHandler)())
This is because my daily work with C++ drilled me for "clean typing" but I remember that C is much more lax about this. (void* is compatible to any pointer.)
As Garr Godfrey already said: "put your code in TMR2_DefaultInterruptHandler"
where it is recommended by the comment
// add your TMR2 interrupt custom code
Related
I have code written by someone else (for a dsPIC33FJ128MC706A) - which initializes UART1 & PWMs and uses a busy wait in the U1Rx interrupt to get 14 bytes, and timer1 interrupts to update PWM registers if new data arrives (debugged - it enters the _U1RXInterrupt when data is sent)
I tried modifying it to use DMA instead of busy wait. I changed nothing in the initialization, but called the following function after initialization of UART:
unsigned char received_data1 [0x0f] __attribute__((space(dma)));
unsigned char received_data2 [0x0f] __attribute__((space(dma)));
void dma_init(void)
{
DMA0REQ = 0x000b; // IRQSEL = 0b0001011 (UART1RX))
DMA0PAD = (volatile unsigned int) &U1RXREG;
DMA0STA = __builtin_dmaoffset(received_data1);
DMA0STB = __builtin_dmaoffset(received_data2);
DMA0CNT = 0xe;//15 bytes in
DMA0CON = 0x0002; //continuous ping-pong + post-increment
IFS0bits.DMA0IF = 0;
IEC0bits.DMA0IE = 1;
DMA0CONbits.CHEN = 1;
}
This is based on example 22-10 in the FRM with a few slight changes (UART1 receiver instead of UART2). I can't seem to find other examples that don't use library functions (which I didn't even know existed up until I came across those examples).
(Also, to be consistent with the code I got, I put in _ISR after void instead of making interrupt an attribute, but that didn't change anything)
In the example, there was no interrupt for the UART1 receiver - but according to the FRM, it should be enabled. Made sense to me that I don't need to clear the interrupt flag myself (it would kind-of defeat the purpose of DMA if I needed to use the CPU for it).
And yet, when I send data, the breakpoint on DMA0Interrupt doesn't trigger, but the one on the default interrupt does, and I have PWMIF=1 and U1RXIF=1. I also tried commenting out the default ISR and adding a _U1ErrInterrupt even though examining the flags I didn't notice any error flags being raised, just the normal U1RXIF.
I don't understand why this would happen. I haven't changed the PWM code at all, and the PWM flag isn't raised in the original code when I place a breakpoint in the _U1RxInterrupt. I don't even understand how a DMA set-up could cause a PWM error.
The U1RXIF flag seems to be telling me the DMA didn't handle the incoming byte (I assume it's the DMA that clears the flag), which once again relates to the question "what did I do wrong in this DMA setup"
UART initialization code:
in a general initialization function (called by main())
TRISFbits.TRISF2 = 1; /* U1RX */
TRISFbits.TRISF3 = 0; /* U2TX */
also called by main:
void UART1_init_USB (void)
{
/* Fcy 7.3728 MHZ */
U1MODE = 0x8400; /* U1ATX Transmit status and control register */
U1STA = 0x2040; /* Receve status and control register Transmit disabled*/
U1BRG = 0x000f; /* Baund rate control register 115200 bps */
IPC2bits.U1RXIP = 3; /* Receiver Interrupt Priority bits */
IEC0bits.U1RXIE = 1; /* Enable RS232 interrupts */
IFS0bits.U1RXIF = 0;
}
(comments by original code author)
I've been trying to configure & run hardware timer over SAML21 MCU to generate a 100ms delay i.e. ISR is supposed to hit at every 100ms. But it is observed that after starting the timer ISR is hitting at every 10us and changing the Prescaler & Compare register values isn't creating any difference in the 10us interval. Please review my code and let me know where I'm doing wrong.
I'm trying to configure Timer1(TC1) in 16bit mode, using GCLK_GENERATOR_1 as its clock source running at 8MHz frequency(CPU Main Clock:16MHz). The timer is expected to cause overflow interrupt every 100ms.
TcCount16 *tc_hw1 = NULL; /* Pointer to TC1 hardware registers Initilized later */
void init_timer1(void)
{
struct tc_module tc_inst1;
struct tc_config conf_tc1;
tc_get_config_defaults(&conf_tc1);
conf_tc1.clock_source = GCLK_GENERATOR_1;
conf_tc1.clock_prescaler = TC_CLOCK_PRESCALER_DIV64; /* 8MHz/64 = 125KHz*/
conf_tc1.reload_action = TC_RELOAD_ACTION_GCLK;
conf_tc1.counter_size = TC_COUNTER_SIZE_16BIT;
conf_tc1.count_direction = TC_COUNT_DIRECTION_UP;
conf_tc1.counter_16_bit.value = 0x0000;
/** Rest of the settings are used as defaults **/
while (tc_init(&tc_inst1, TC1, &conf_tc1) != STATUS_OK){
}
tc_set_top_value(&tc_inst1, 12500); /* Set counter compare top value */
/* Enable interrupt & Set Priority */
tc_hw1 = &(tc_inst1.hw->COUNT16); /* Initialize pointer to TC1 hardware register */
tc_hw1->INTENSET.reg |= TC_INTFLAG_OVF; /* Enable Overflow Interrupt */
NVIC_SetPriority(TC1_IRQn, 2);
NVIC_EnableIRQ(TC1_IRQn);
tc_enable(&tc_inst1); /*Start The TIMER*/
}
void TC1_Handler(void)
{
if((tc_hw1->INTFLAG.reg) & (TC_INTFLAG_OVF))
{
port_pin_toggle_output_level(PIN_PB03);
}
system_interrupt_clear_pending(SYSTEM_INTERRUPT_MODULE_TC1);
}
Debugger Information: I can see that the timer register is configured correctly but the COUNT register is not incrementing itself every time I pause to capture the debug info it shows only 0x0000 values.
Please help. Thanks!
I resolved the issue actually it's kind of mandatory to clear the timer OVF bit in the INTFLAG register. So the interrupt handler should've been like this:
void TC1_Handler(void)
{
if((tc_hw1->INTFLAG.reg) & (TC_INTFLAG_OVF))
{
tc_hw1->INTFLAG.reg = TC_INTFLAG_OVF; /*Clears the flag by writing 1 to it*/
port_pin_toggle_output_level(PIN_PB03);
}
system_interrupt_clear_pending(SYSTEM_INTERRUPT_MODULE_TC1); /*Not necessarily needed*/
}
So, I am doing a very simple time triggered pattern based on ARM Cortex M3.
The idea is:when SysTick is serviced, the task array index is incremented at systick, and so is a function pointer to the task. PendSV handler is called, and calls the task. I am using a Atmel ICE JTAG to debug it.
What happens is that it stucks at first task, and does not even increment the counter. It does not go anywhere.
Code pattern:
#include <asf.h> // atmel software framework. cmsis and board support package.
#define NTASKS 3
typedef void (*TaskFunction)(void);
void task1(void);
void task2(void);
void task3(void);
TaskFunction run = NULL;
uint32_t count1 = 0; //counter for task1
uint32_t count2 = 0; // 2
uint32_t count3 = 0; // 3
TaskFunction tasks[NTASKS] = {task1, task2, task3};
volatile uint8_t tasknum = 0;
void task1(void)
{
while(1)
{
count1++;
}
}
void task2(void)
{
while(1)
{
count2++;
}
}
void task3(void)
{
while(1)
{
count3++;
}
}
void SysTick_Handler(void)
{
tasknum = (tasknum == NTASKS-1) ? 0 : tasknum+1;
run = tasks[tasknum];
SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
}
void PendSV_Handler(void)
{
run();
}
int main(void)
{
sysclk_init();
board_init();
pmc_enable_all_periph_clk();
SysTick_Config(1000);
while(1);
}
This design pattern is fundamentally flawed, I'm afraid.
At the first SysTick event, task1() will be called from within the PendSV handler, which will consequently not return. Further SysTick events will interrupt the PendSV handler and set the PendSV bit again, but unless the running task ends and the PendSV handler is allowed to return it can't possibly be invoked again.
The good news is that a proper context switch on the M3 is only a small amount of assembly language - perhaps 10 lines. You need to do some setup too, to get user mode code to use the process stack pointer and so on, and you need to set up a stack per task, but it's not really all that involved.
If you really want to cancel the running task when a SysTick arrives and launch another, they could all share the same stack; but it would be much easier if this was the process stack so that its stack pointer can be reset from within PendSV without affecting the return from handler mode. You'd also need to do some stack poking to convince PendSV to 'return' to the start of the next task you wanted to run.
for 4 days now, I am struggling to set up External interrupt on my STM32 and I have gone through tons of reading and other people's code to get it. But no luck.
I have two buttons and when pressing either one of them I expect to light up an LED, this example is only to get it working, I wanted to have something functional before proceeding and building rest of the code.
I am sorry if the code is a little messy, but I am working on neating my coding skills.
I've gone through manuals and datasheets but nothing seems to help.
Here is my main.c
#include "stm32l1xx_hal.h"
#include "buttons.h"
static void MX_GPIO_Init(void);
bool RightButtonFlag = 0;
bool LeftButtonFlag = 0;
int main(void)
{
HAL_Init();
SystemClock_Config();
controls_Interrupt_Init();
MX_GPIO_Init();
while (1)
{
if(RightButtonFlag){
Blue_ON();
}
if(LeftButtonFlag){
Green_ON();
}
}
}
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_10|GPIO_PIN_11;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
{
Followed by part of stm32l1xx_it.c
#include "stm32l1xx_hal.h"
#include "stm32l1xx.h"
#include "stm32l1xx_it.h"
#include "buttons.h"
extern volatile uint8_t RightButtonFlag;
extern volatile uint8_t RightButtonFlag;
void EXTI15_10_IRQHandler(void)
{
if(GPIOC->IDR & GPIO_IDR_IDR_10){
RightButtonFlag = 1;
EXTI->PR |= EXTI_PR_PR10;
}
if(GPIOC->IDR & GPIO_IDR_IDR_11){
LeftButtonFlag = 1;
EXTI->PR |= EXTI_PR_PR11;
}
}
buttons.c
void controls_Interrupt_Init(void){
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN; /* Enable System Configuration Register */
SYSCFG->EXTICR[3] |= SYSCFG_EXTICR3_EXTI11_PC; /* Set up External Interrupt for Pin 11 Port C */
SYSCFG->EXTICR[3] |= SYSCFG_EXTICR3_EXTI10_PC; /* Set up External Interrupt for Pin 10 Port C */
EXTI->IMR |= EXTI_IMR_MR11;
EXTI->IMR |= EXTI_IMR_MR10;
EXTI->FTSR |= EXTI_FTSR_TR11; /* Falling trigger Selection Reg. Trigger 11 */
EXTI->FTSR |= EXTI_FTSR_TR10; /* Falling trigger Selection Reg. Trigger 10 */
NVIC_SetPriority(EXTI15_10_IRQn,1); /* Set Interrupt priority for pins 10-15 */
NVIC_EnableIRQ(EXTI15_10_IRQn); /* Enable NVIC for Pins Between 10-15 */
}
and buttons.h
void controls_Interrupt_Init(void);
#define Blue_ON() (HAL_GPIO_WritePin(BLUE_LED_PORT, BLUE_LED_PIN, 1))
#define Green_ON() (HAL_GPIO_WritePin(GREEN_LED_PORT, GREEN_LED_PIN, 1))
I am fairly new to coding and with my poor experience I expect to have screwed up something very simple.
You have to declare the flags as volatile. That's a hint to the compiler that the variable can change any time, independent of the normal program flow.
volatile bool RightButtonFlag = 0;
volatile bool LeftButtonFlag = 0;
When there is no volatile there, the compiler can assume that the flags never change in the main loop, and optimize it that way, that the variables will be loaded only once, before the loop.
Declaring them volatile only in the scope of the interrupt handler does no good, because the optimization will still take place in main(), where the volatile declaration is not visible.
It is good practice to move the declaration to a header file, and include that header everywhere the variable is referenced. Then the compiler can check that the types are indeed compatible.
UPDATE
The interrupt handler makes little sense. You set EXTI to detect a falling edge, then check if the input is high. Checking the GPIO data register is not reliable anyway, due to the bouncing effect of mechanical buttons.
You should rather check EXTI->PR in the handler, and reset the pending bit with a simple assignment instead of |=, otherwise you could accidentally clear another pending bit too.
void EXTI15_10_IRQHandler(void)
{
if(EXTI->PR & EXTI_PR_PR10){
RightButtonFlag = 1;
EXTI->PR = EXTI_PR_PR10;
}
if(EXTI->PR & EXTI_PR_11){
LeftButtonFlag = 1;
EXTI->PR = EXTI_PR_PR11;
}
}
You can still check somewhere if GPIOC->IDR actually reflects the button state, to eliminate possible hardware problems.
You can also try setting EXTI->SWIER from the debugger, or in the code, to simulate a button press.
I am using Interrupt-On-Change on RC7 of PIC16LF1618. Here is the initialization bit that I use for I-O-C:
void I_O_C_Initialize (void) {
INTCONbits.IOCIF = 0;
IOCCFbits.IOCCF7 = 0;
INTCONbits.IOCIE = 1;
IOCCP = 0x80;
}
I am able to wake the PIC from a Power-Down Mode (SLEEP) using a positive trigger on RC7. However, I would like to have this trigger available during execution time as well, as if any positive trigger on RC7 should reset the PIC and go to the first line of the main() function.
Could you please let me know on how to achieve this?
P.S: Since the reset needs to happen as quick as possible and is crucial to the execution time, I am unable to add multiple if statements inside the main function to check for the positive trigger on RC7. Hence I am looking for an interrupt option to reset the PIC, even if it is executing a delay or function loops.
Thanks
In most 8-bit PIC devices, and assuming you're using XC8, there is a definition that invokes the required assembly command:
#define RESET() asm("reset")
So, in your interrupt handler, just insert this line of code:
RESET();
The issue has now been resolved. After enabling GIE bit whenever I needed the Interrupt On Change (IOC) during runtime and using the below function, the IOC worked during runtime as well as Power-Down Mode (SLEEP).
void interrupt ISR (void);
void interrupt ISR (void) {
if (RC7==1) {
asm("pagesel foobar");
asm("goto foobar");
}
else
return;
}
asm("foobar:");
while (1) {
IOCCFbits.IOCCF7 = 0;
INTCONbits.GIE = 1;
. //Do the calculations here
. //Here if any Interrupt On Change happens for RC7,
. //the ISR routine would stop all calculations and
. //would return to the start of the loop without
. //resetting any of the registers.
INTCONbits.GIE = 0;
IOCCFbits.IOCCF7 = 0;
SLEEP();
}