Recently, I write a linux module to generate interrupt every 20us using watch dog. I use the global timer to test whether the interval between two interrupts is 20us. But I find the result is greater than 20us. So I change the value of watchdog counter in the interrupt function to regulate the error if the error is bigger enough. After I add the code of regulating error, the result is better than before in most interrupts, while there still exist some huge error between two interrpts, and the error is much lager than 20us.
Thank you for reading my question, I hope it can be solved as soon as possible.
This is interrupt handler code:
static irqreturn_t wd_interrupt(int irq, void *dev_id) {
long long regulate_value;
long load_value;
long long err;
tick_start = read_global_timer();
//the cmp_cycle is the value of global timer if the interrupt are not delayed
cmp_cycle += (long long)wd_load;
err = tick_start - cmp_cycle;
//if the err is biger than cmp_err, I will write a new value to watch dog to eliminate the error
if(err > cmp_err)
{
regulate_value = (long long)wd_load - err;
load_value = least_load;
// if the err is very big, the regulate_value may be too small
if(regulate_value > (long long)load_value)
load_value = (long)regulate_value;
__raw_writel(load_value, twd_base + TWD_WDOG_COUNTER);
}
if(err > max_err)
max_err = err;
return IRQ_HANDLED;
}
this is my code to start the watch dog and global timer
static int kthread_init(void *arg) {
unsigned long ctl;
int err;
if((err = request_irq(30, wd_interrupt, 0, NULL, NULL)) < 0)
{
printk("request_irq err:%d\n",err);
return 0;
}
gt_base = ioremap((OMAP44XX_LOCAL_TWD_BASE - 0X400), SZ_256);
cmp_cycle = 0;
// wd_load = twd_timer_rate / 50000; //20us
wd_load = twd_timer_rate / 500; //2000us
cmp_err = 1000;
least_load = wd_load - 2000;
tick_start = 0;
max_err = 0;
//init the global timer
__raw_writel(0x00, gt_base + GLOBAL_TIMER_CONTROL);
__raw_writel(0x00, gt_base + GLOBAL_TIMER_COUNTER_LOW);
__raw_writel(0x00, gt_base + GLOBAL_TIMER_COUNTER_UPPER);
//switch the watch dog to timer mode
__raw_writel(0x12345678, twd_base + TWD_WDOG_DISABLE);
__raw_writel(0x87654321, twd_base + TWD_WDOG_DISABLE);
//write the watch dog load register
__raw_writel(wd_load, twd_base + TWD_WDOG_LOAD);
//write the watch dog control register
ctl = TWD_TIMER_CONTROL_ENABLE | TWD_TIMER_CONTROL_IT_ENABLE | TWD_TIMER_CONTROL_PERIODIC;
//start the watch dog and global timer
__raw_writel(ctl, twd_base + TWD_WDOG_CONTROL);
__raw_writel(0x01, gt_base + GLOBAL_TIMER_CONTROL);
set_current_state(TASK_INTERRUPTIBLE);
schedule();
while(!kthread_should_stop())
{
set_current_state(TASK_INTERRUPTIBLE);
schedule();
}
__raw_writel(0x0, twd_base + TWD_WDOG_CONTROL);
printk("wd_load:%lu,max_err:%lld\n",wd_load,max_err);
return 0;
}
My guess is that your handler together with all the Linux wrapping code can't finish running in 20us, so the following interrupts get delayed. Try increasing the delay and see if that helps.
I think what you are seeing is other interrupts happening. When for example the network card sends an interrupt the kernel enters interrupt mode and process the network interrupt. When it returns from that the cpu sees that your interrupt is pending and triggers that. But you've lost the time the network interrupt took.
You would have to change any other interrupt handler that takes longer than say 10us to reenable interrupts and run the actual handler in SYS/SVC mode.
Related
I would like to design a time scheduler that would do some specific stuff at specific time using a timer/state machine.
I would like to do something like this :
As you can see on the picture above, I would need to do a request every 4 ms, this request would have a turnaround delay of 1ms and during this window of 4 ms, I would have 3ms to make 6 other request every 500microseconds. And at the end I would like to do this constantly.
What would be the best way to do it in C for STM32 ? Right now I did this :
void TIM2_Init (uint32_t timeout)
{
uint8_t retVal = false;
// Enable the TIM2 clock
__HAL_RCC_TIM2_CLK_ENABLE();
// Configure the TIM2 handle
htim2.Instance = TIM2;
htim2.Init.Prescaler = (uint32_t)(240000000 / 1000000) - 1;
htim2.Init.Period = timeout - 1;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.RepetitionCounter = 0;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_OK == HAL_TIM_Base_Init(&htim2))
{
// Enable update interrupt for TIM2
__HAL_TIM_ENABLE_IT(&htim2, TIM_IT_UPDATE);
// Enable the TIM2 interrupt in the NVIC
HAL_NVIC_EnableIRQ(TIM2_IRQn);
}
// Start the TIM2 counter
if (HAL_OK == HAL_TIM_Base_Start(&htim2))
{
retVal = true;
}
return retVal;
}
void changeTIM2Timeout (uint32_t timeout)
{
// Change the TIM2 period
TIM2->ARR = timeout - 1;
}
I created a timer with a timeout parameter to initialize it. Then I have a function to change the period, cause for example I can have a response timeout of 500us sometimes and also 1ms some other times.
Where I'm stuck is having the whole working, meaning first request is done, then 1ms later followed by second request (2), then (3), etc. I think it's on the state machine that would handle the different request with the timer that I need help.
You can create a queue data structure for the tasks and assign each task time there. When it's done, if it's a repetitive task, it can be added to the end of the queue when it's done with its current run and you're fetching the next task. You can have fixed maximum size queue or dynamic maximum size queue, it's up to you.
When the task is invoked, the timer is set to the value, associated with that task. So there will be no waste ticks. This also gives you tickless idle with a bit of effort, if you need it. Also, you can add some more parameters in that queue if you want to customize scheduling somehow, such as priority level.
Edit: this obviously makes the timer unusable as a time measurement device for any other purpose, because you change its settings all the time.
I would configure the timer to 500us on autoreload. Then just keep an index up to 8 that will increment with wrapping. Because 8 is a multiple of 2, you could use an unsigned number and forget about wrapping.
void do_stuff(int idx) {
switch (idx) {
case 0: request(1); break;
case 2: case 3: case 4: case 5: case 6: case 7: etc.
request(idx); break;
}
void TIM2_Init() {
// generate from STM32CubeFX autoreload with 500us
}
void TIM2_IRQHandler(void) {
static unsigned idx = 0;
do_stuff(idx++ % 8);
}
I want to configure timer1 of PIC24F16KA102 to count it. The clock source must be the internal clock of 8 MHz. I configured the register T1CON and set on high level the bit TON to start the timer. Timer1 is set to go in overflow every 100 us, then with a while cycle I wille increase the variable count. I'am not understanding because timer1 don't work, I observed that it does not increase. Why?
#include <xc.h>
#include "config.h"
int count = 0;
void main(void) {
TRISB = 0;
T1CON = 0; //TRM1 stopped, internal clock source, prescaler 1:1
_TON = 1;
TMR1 = 65135; //overflow of TM1 every 100 us (400 counts)
while (1) {
if (TMR1 == 65535) {
count++; // increase every 100 us
TMR1 = 65135;
}
}
}
Try setting the Timer 1 period register (PR1) and using an interrupt rather than trying to catch and reload TMR1 on its final count. You're trying to catch TMR1 on EXACTLY 65535, and that will almost never work because once TMR1 hits 65535, it's just going to overflow and begin counting from 0 again.
EDIT: Of course, this assumes it counts at all. I don't know what the behavior of a timer is when you leave the period register at 0. It may simply count to it's maximum of 65535 then reset to 0, or it may never count at all and continuously load PRx into TMRx since they match at 0
PRx is meant to define the period you want for a given timer, in this case 100uS. PR1 = 400. Once TMR1 = PR1, the timer will reset itself automatically and raise an interrupt to alert you that the timer has elapsed.
volatile unsigned int count = 0; //Vars that change in an ISR should be volatile
PR1 = 400; //Set Period for Timer1 (100us)
T1CON = 0x8000; //Enable Timer1
IEC0bits.T1IE = 1; //Enable Timer1 Interrupt
IPC0bits.T1IP = 0b011;
Pair this with an ISR function to increment count whenever the timer elapses:
void __attribute__ ((interrupt,no_auto_psv)) _T1Interrupt (void)
{
count++;
IFS0bits.T1IF = 0; //Make sure to clear the interrupt flag
}
You could also try something like this without any interrupts:
void main(void){
unsigned int count = 0;
TMR1 = 0;
T1CON = 0x8000; //TON = 1
while(1){
if (TMR1 >= 400){
count++;
TMR1=0;
}
}
}
However I would recommend using the PR register and an ISR. This is what it's meant to do.
EDIT: I would also recommend reading the PIC24F Reference Manual on timers:
Here
I'm developing an application for the NXP LPC1788 microcontroller using the IAR Embedded Workbench IDE and one of the requirements is to receive CAN messages off of the CAN1 port as fast as possible. My issue is that there seems to be a delay of ~270 microseconds between the start of one invocation of the CAN IRQ and the start of the next even though it only seems to take the microcontroller ~30 microseconds to handle the interrupt.
In addition to this interrupt, I also have regular timer interrupts scheduled every 50 microseconds which process received CAN and USB messages. However, this interrupt routine takes ~3 microseconds if there are no CAN or USB messages to process and only around ~80 microseconds if there are.
Because of this, I'm wondering why the CAN IRQ is not able to fire faster than every 270 microseconds and what I can do to fix that.
USB interrupts have negligible impact on performance since USB messages arrive much more infrequently than CAN messages, and I've determined that this problem occurs even when this interrupt is never fired.
Currently, my application seems to be able to handle CAN messages with an average inter-arrival time of ~300 us (tested for 3 messages generated at 1 ms intervals, with ~500,000 messages processed and a 0% drop rate).
This code initialises the CAN:
void CANHandlerInit()
{
// Configure CAN pins.
PINSEL_ConfigPin(0, 0, 1); // RD1.
PINSEL_ConfigPin(0, 1, 1); // TD1.
PINSEL_ConfigPin(0, 4, 2); // RD2.
PINSEL_ConfigPin(0, 5, 2); // TD2.
CAN_Init(CAN_1, CAN_BAUD_RATE);
CAN_Init(CAN_2, CAN_BAUD_RATE);
//printf("CAN Handler initialised\n");
}
This is used to run the CAN:
void CANHandlerRun()
{
// Enter reset mode.
LPC_CAN1->MOD |= 0x1;
LPC_CAN2->MOD |= 0x1;
#if CAN_SOURCE_PORT == CAN_PORT_1
SFF_GPR_Table[0].controller1 = SFF_GPR_Table[0].controller2 = CAN1_CTRL;
SFF_GPR_Table[0].disable1 = SFF_GPR_Table[0].disable2 = MSG_ENABLE;
SFF_GPR_Table[0].lowerID = 0x0;
SFF_GPR_Table[0].upperID = 0x7FF;
#else
SFF_GPR_Table[0].controller1 = SFF_GPR_Table[0].controller2 = CAN2_CTRL;
SFF_GPR_Table[0].disable1 = SFF_GPR_Table[0].disable2 = MSG_ENABLE;
SFF_GPR_Table[0].lowerID = 0x0;
SFF_GPR_Table[0].upperID = 0x7FF;
#endif
AFTable.FullCAN_Sec = NULL;
AFTable.FC_NumEntry = 0;
AFTable.SFF_Sec = NULL;
AFTable.SFF_NumEntry = 0;
AFTable.SFF_GPR_Sec = &SFF_GPR_Table[0];
AFTable.SFF_GPR_NumEntry = 1;
AFTable.EFF_Sec = NULL;
AFTable.EFF_NumEntry = 0;
AFTable.EFF_GPR_Sec = NULL;
AFTable.EFF_GPR_NumEntry = 0;
if(CAN_SetupAFLUT(&AFTable) != CAN_OK) printf("AFLUT error\n");
LPC_CANAF->AFMR = 0;
// Re-enter normal operational mode.
LPC_CAN1->MOD &= ~0x1;
LPC_CAN2->MOD &= ~0x1;
// Enable interrupts on transmitting and receiving messages.
#if CAN_SOURCE_PORT == CAN_PORT_1
LPC_CAN1->IER |= 0x1; /* RIE */
LPC_CAN1->IER |= (1 << 8); /* IDIE */
#else
LPC_CAN2->IER |= 0x1;
LPC_CAN2->IER |= (1 << 8);
#endif
NVIC_EnableIRQ(CAN_IRQn);
}
This is the CAN IRQ code:
void CAN_IRQHandler(void)
{
ITM_EVENT32_WITH_PC(1, 0xAAAAAAAA);
#if CAN_SOURCE_PORT == CAN_PORT_1
if(LPC_CAN1->SR & 0x1 /* RBS */)
{
CAN_ReceiveMsg(CAN_1, &recMessage);
COMMS_NotifyCANMessageReceived();
CAN_SetCommand(CAN_1, CAN_CMR_RRB);
}
#else
if(LPC_CAN2->SR & 0x1)
{
CAN_ReceiveMsg(CAN_2, &recMessage);
COMMS_NotifyCANMessageReceived();
CAN_SetCommand(CAN_2, CAN_CMR_RRB);
}
#endif
ITM_EVENT32_WITH_PC(1, 0xBBBBBBBB);
}
The COMMS_NotifyCANMessageReceived code:
void COMMS_NotifyCANMessageReceived()
{
COMMS_BUFFER_T commsBuffer;
#if MAX_MSG_QUEUE_LENGTH > 0
uint32_t length;
LIST_AcquireLock(canMessageList);
length = LIST_GetLength(canMessageList);
LIST_ReleaseLock(canMessageList);
if(length >= MAX_MSG_QUEUE_LENGTH)
{
ITM_EVENT32_WITH_PC(2, 0x43214321);
return;
}
#endif
commsBuffer.flags = COMMS_MODE_CAN;
COMMS_GetData(&commsBuffer);
LIST_AcquireLock(canMessageList);
LIST_AddItem(canMessageList, &commsBuffer);
LIST_ReleaseLock(canMessageList);
}
This is my timer interrupt code which is triggered every 50 microseconds:
void TIMER0_IRQHandler(void)
{
uint32_t canMessageCount, usbMessageCount;
if(TIM_GetIntStatus(LPC_TIM0, TIM_MR0_INT) == SET)
{
//ITM_EVENT32_WITH_PC(4, 0);
LIST_AcquireLock(canMessageList);
canMessageCount = LIST_GetLength(canMessageList);
LIST_ReleaseLock(canMessageList);
LIST_AcquireLock(usbMessageList);
usbMessageCount = LIST_GetLength(usbMessageList);
LIST_ReleaseLock(usbMessageList);
if(canMessageList != NULL && canMessageCount > 0)
{
LIST_AcquireLock(canMessageList);
if(!LIST_PopAtIndex(canMessageList, 0, &outCommsBuffer))
{
LIST_ReleaseLock(canMessageList);
goto TIMER_IRQ_END;
}
LIST_ReleaseLock(canMessageList);
ITM_EVENT32_WITH_PC(4, 0x88888888);
interpretMessage(outCommsBuffer);
ITM_EVENT32_WITH_PC(4, 0x99999999);
}
else if(usbMessageList != NULL && usbMessageCount > 0)
{
LIST_AcquireLock(usbMessageList);
if(!LIST_PopAtIndex(usbMessageList, 0, &outCommsBuffer))
{
LIST_ReleaseLock(usbMessageList);
goto TIMER_IRQ_END;
}
LIST_ReleaseLock(usbMessageList);
ITM_EVENT32_WITH_PC(4, 0xCCCCCCCC);
interpretMessage(outCommsBuffer);
ITM_EVENT32_WITH_PC(4, 0xDDDDDDDD);
}
//ITM_EVENT32_WITH_PC(4, 1);
}
TIMER_IRQ_END:
TIM_ClearIntPending(LPC_TIM0, TIM_MR0_INT);
}
Below is an excerpt of a typical event log while running the application with an average inter-arrival time between messages of 200 us:
4s 718164.69 us 0x00005E82 0xAAAAAAAA
4s 718175.27 us 0x00005EC4 0xBBBBBBBB
4s 718197.10 us 0x000056C4 0x88888888
4s 718216.50 us 0x00005700 0x99999999
4s 718438.69 us 0x00005E82 0xAAAAAAAA
4s 718449.40 us 0x00005EC4 0xBBBBBBBB
4s 718456.42 us 0x000056C4 0x88888888
4s 718476.56 us 0x00005700 0x99999999
4s 718707.04 us 0x00005E82 0xAAAAAAAA
4s 718717.54 us 0x00005EC4 0xBBBBBBBB
4s 718747.15 us 0x000056C4 0x88888888
4s 718768.00 us 0x000056C4 0x99999999
Where:
0xAAAAAAAA indicates the start of the CAN IRQ.
0xBBBBBBBB indicates the end of the CAN IRQ.
0x88888888 indicates that a CAN message is about to be processed by interpretMessage within the timer IRQ.
0x99999999 indicates that we've returned from interpretMessage.
You can see that, despite the fact that messages are arriving every 200 us, the CAN IRQ is only servicing them every ~270 us. This causes the receive queue to build up until eventually messages start getting dropped.
Any help would be appreciated.
EDIT 1
I should perhaps also mention that I have my CAN peripherals programmed to operate at a rate of 500 kBaud. In addition, my application involves echoing received CAN messages, so any messages arriving on port 1 are re-transmitted on port 2.
EDIT 2
The CAN and TIMER_0 interrupt routines have equal priority:
NVIC_SetPriority(USB_IRQn, 1);
NVIC_SetPriority(CAN_IRQn, 1);
NVIC_SetPriority(TIMER0_IRQn, 1);
EDIT 3
I can confirm that the problem remains and there is little/no change to the ~270 us interval between interrupts even when I disable timer interrupts and have the CAN IRQ do nothing but copy the received CAN message to RAM and release the receive buffer.
I'm an idiot. At 500 kBaud, it will take 271 us for a CAN message with an 11-bit standard identifier and max stuff bits to be transmitted. There's no apparent problem in my code, the bottleneck is simply how fast CAN messages can be transmitted on the bus at that point.
first code:
//------------------------------------------------------------------------------
/// Interrupt handlers for TC interrupts. Toggles the state of LEDs
//------------------------------------------------------------------------------
char token = 0;
void TC0_IrqHandler(void) {
volatile unsigned int dummy;
dummy = AT91C_BASE_TC0->TC_SR;
if(token == 1) {
PIO_Clear(&leds[0]);
PIO_Set(&leds[1]);
token = 0;
}
else {
PIO_Set(&leds[0]);
PIO_Clear(&leds[1]);
token = 1;
}
}
//------------------------------------------------------------------------------
/// Configure Timer Counter 0 to generate an interrupt every 250ms.
//------------------------------------------------------------------------------
void ConfigureTc(void) {
unsigned int div;
unsigned int tcclks;
AT91C_BASE_PMC->PMC_PCER = 1 << AT91C_ID_TC0; // Enable peripheral clock
TC_FindMckDivisor(1, BOARD_MCK, &div, &tcclks); // Configure TC for a 4Hz frequency and trigger on RC compare
TC_Configure(AT91C_BASE_TC0, tcclks | AT91C_TC_CPCTRG);
AT91C_BASE_TC0->TC_RC = (BOARD_MCK / div) / 1; // timerFreq / desiredFreq
IRQ_ConfigureIT(AT91C_ID_TC0, 0, TC0_IrqHandler); // Configure and enable interrupt on RC compare
AT91C_BASE_TC0->TC_IER = AT91C_TC_CPCS;
IRQ_EnableIT(AT91C_ID_TC0);
printf(" -- timer has started \n\r");
TC_Start(AT91C_BASE_TC0);
}
it's just interrupt timer and it's event (handler) but when I run some
while(1) {
// action
after ConfigureTc() it both cycle and interrupt timer are freezes... Why could that be? Should I add another timer and avoid while(1) ?
while(1) {
printf("hello");
}
-- this breaks (freeze) loops (yes, if I don't use timer it works as it must)
I'll venture an actual answer here. IME, 99% of the time my boards 'go out' with no response on any input and no 'heartbeat' LED-flash from the low-priority 'blinky' thread, the CPU has flown off to a prefetch or data abort handler. These handlers are entered by interrupt and most library-defined default handlers do not re-enable interrupts, so stuffing the entire system. Often, they're just endless loops and, with interrupts disabled, that's the end of the story:(
I have changed my default handlers to output suitable 'CRITICAL ERROR' messages to the UART, (by polling it - the OS/interrupts are stuft!).
I am using MSP430F5418 with IAR EW 5.10.
I want to use Timer B in up mode.
I want to use two interrupts.
TimerB0(1 ms) and TimerB1(1 second).
My Configuration is
TBCTL = MC__UP + TBSSEL__ACLK + TBCLR;
TB0CCTL0 = CCIE;
TB0CCR0 = 32;
TB0CCTL1 = CCIE;
TB0CCR1 = 32768;
On the ISR I just toggled two pins.
But only the pin for TB0CCR0 is toggling.
My Pin Configurations are correct.
Can anyone tell me why??
I suppose your problem is the timer period.
MC__UP Up mode: Timer counts up to TBxCL0
So your timer TBxR will reset to 0 when it reaches TBxCL0 which seems to be the value TB0CCR0.
So it can never reach the value of 32768.
You could switch TB0CCR0 with TB0CCR1, so your period will be 1 second.
And to get your 1ms interrupt you need to increment your TB0CCR1 each time.
INTERRUPT ISR_1MS()
{
TB0CCR1 = (TB0CCR1 + 32) & 0x7FFF;
}
But normally you don't need a second timer for a second intervall.
You could simply count 1000 times your 1ms intervall.
INTERRUPT ISR_1MS()
{
ms_count++;
if (ms_count >= 1000)
{
ms_count=0;
// Do your second stuff
}
}
And if you need more and different intervalls, you could change to another model.
To a system clock time and check only against this time.
volatile unsigned int absolute_time=0;
INTERRUPT ISR_1MS()
{
absolute_time++;
}
unsigned int systime_now(void)
{
unsigned int result;
di();
result = absolute_time;
ei();
return result;
}
uint8_t systime_reached(unsigned int timeAt)
{
uint8_t result;
result = (systime_now() - timeAt ) < 0x1000;
return result;
}