Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.arch.embedded > #26462

Re: A timer driver for Cortex-M0+... it rarely doesn't work

From David Brown <david.brown@hesbynett.no>
Newsgroups comp.arch.embedded
Subject Re: A timer driver for Cortex-M0+... it rarely doesn't work
Date 2017-04-27 15:39 +0200
Organization A noiseless patient Spider
Message-ID <odss4s$4ti$1@dont-email.me> (permalink)
References <odr8bp$op3$1@dont-email.me> <ods8a4$3gr$1@dont-email.me> <odse1j$kc4$1@dont-email.me> <odsieb$30k$1@dont-email.me> <odslqt$ef9$1@dont-email.me>

Show all headers | View raw


On 27/04/17 13:51, pozz wrote:
> Il 27/04/2017 12:54, David Brown ha scritto:
> 
>> You are sure you are not calling ticks() from within another interrupt
>> function?
> 
> Yes, sure.  I am very careful during writing interrupts code, even if it
> wasn't sufficient in this case.
> 
> 
>>> ticks() are called only by TimerSet() and TimerExpired() and those two
>>> functions are called only in normal background (not interrupt) code.
>>> This means ticks() always runs in a lower priority than TC0 ISR.
>>> Moreover, I never disable interrupts in any part of the code.
>>>
>>> I don't understand what do you mean with "[...] or interrupts take a few
>>> cycles to work through the system". Is it possible to read a rolled-over
>>> hw counter value (0x00000003) and a not incremented _ticks_high?
>>
>> There is always a delay between an overflow in the timer, and the actual
>> interrupt function being started.  Depending on details of the chip, it
>> is possible there will be several cpu cycles of the current instruction
>> stream executed before the interrupt function is run.  If I remember
>> rightly, the M3/M4 has a 5 stage pipeline.  When the interrupt is
>> registered by the core, it effectively means that a "jump to interrupt
>> vector" instruction is squeezed into the instruction stream - but these
>> current 5 instructions must be completed before the interrupt call takes
>> effect.  You will also have a cycle or two delay in the NVIC, and
>> perhaps a cycle or two delay getting the signal out of the timer block
>> (especially if the timer does not run at full core speed).  Cortex M
>> interrupts are handled quickly - but not immediately.
> 
> Really?  So, the core is able to read the register of a peripheral
> (TC0->COUNT32.COUNT register, in my case) with a *new* (i.e., rolled)
> value, but the ISR hasn't run yet?  In other words, a bus access can be
> done (TC0 is on the bus) while interrupt request is pending?
> 

I cannot say for sure that this can happen.  But unless I can say for
sure that it /cannot/ happen, I prefer to assume the worst is possible.

> If this is the case, it is a mess :-(
> 
> 
>>> - many times you need an additional "timer running/active" flag
>>>
>>> The second point is more important. If you want to switch on a LED after
>>> 10 minutes when a button is pressed:
>>>
>>> bool tmr_led_active;
>>> Timer32 tmr_led;
>>> void button_pressed_callback(void) {
>>>    TimerSet32(&tmr_led, 10 * 60 * 1000);
>>>    tmr_led_active = true;
>>> }
>>> void main_loop(void) {
>>>   ...
>>>   if (tmr_led_active && TimerExpired32(&tmr_led)) {
>>>     switch_on_led();
>>>     tmr_led_active = false;
>>>   }
>>> }
>>>
>>> If you don't check the timer flag, the led will switch on at random
>>> times.
>>
>> So you have to add a flag - big deal.  You are not using any more
>> memory, your code is smaller, and it is simpler to be sure that
>> everything is correct.
> 
> Yes, of course. My point here is that you have to **remember** that the
> timers you are using can roll-over at any time in the future, so they
> can change from "not expired" to "expired".

True.  But perhaps that can be baked into your "Set" and "Expired"
functions, or otherwise made "unforgettable".  The aim is to simplify
the code that /may/ have rare race conditions into something that cannot
possibly have such problems - even if it means other code is bigger or
less efficient.

> 
> After using TimerSet32() and after the timer expires, you could expect
> it stays "expired", until you arm it again with TimerSet32(). This is
> not true, because TimerExpired32() could returns "false" at a certain
> time in the future.
> 
> This is not a problem with timers that are repetitive (armed again as
> soon as they expire), but with one-shot timers.
> 
> 
>>> Yes, I will try your ideas.
>>
>> Good luck - it is not an easy task.  Don't forget to let us know the
>> source of the problem, and the solution!
> 
> I configured TC0 in 16-bis mode, so now TC0_Handler() is fired every
> 75ms.  I changed accordingly ticks() to create a 64-bits by shifting
> _ticks_high for 16-bits.
> 
>     if (h2 != h1) return ((uint64_t)h2 << 16) + 0;
>     else          return ((uint64_t)h1 << 16) + l1;
> 
> I was lucky because I can reproduce the problem more often... and
> incredibly the problem is the opposite.
> 
> Remember my state-machine in bus.c:
> 
>   switch(bus.state) {
>     case BUS_IDLE:
>       if (TimerExpired(&bus.tmr_answer)) {
>         /* Send new request on the bus */
>         ...
>         TimerSet(&bus.tmr_answer, timeout_answer);
>         bus.state = BUS_WAITING_ANSWER;
>       }
>       break;
> 
>     case BUS_WAITING_ANSWER:
>       if (TimerExpired(&bus.tmr_answer)) {
>         /* No reply */
>         bus.state = BUS_IDLE;
>         TimerSet(&bus.tmr_answer, 0);
>       } else {
>         if (reply_received() == true)    {
>           /* Analyze the reply */
>           bus.state = BUS_IDLE;
>           TimerSet(&bus.tmr_answer, 0);    [*]
>         }
>       }
>       break;
>   }
> 
> When the problem occurs (the bus blocks), bus.state is BUS_IDLE. The
> problem is with instruction [*].  That instructions should arm
> bus.tmr_answer timer such that it expires immediately and a new request
> is send on the bus (in the future, it will be simple to introduce a
> delay between the reply and the next request).
> 
> Sometimes the timer doesn't expire immediately, but after the hw counter
> roll-over again.  Indeed I see the bus blocked for 75ms.
> 
> We were thinking that _ticks_high hasn't incremented yet in task() when
> reading hw counter value.  But this would have produced an old already
> expired time, not a future not-expired time. Here the problem is with a
> wrong time in the *future*.
> In other words, when the problem occurs, ticks() reads a new corrected
> and incremented value for _ticks_high, but an old not-rolled hw counter
> value.
> 
> Why this?  I think it's the usual problem with register syncronization
> in Atmel SAM devices?  Before reading or writing certain registers, you
> need to check if the peripheral is in syncing.  I don't know what this
> exactly means, but it relates to the presence of different asyncronous
> clocks.  But I use only one reference clock (an external crystal) that
> is routed to the Cortex-M core and all peripherals... so I thought
> syncronization wasn't necessary.
> 
> In this case, it seems syncronization solve my problem, so my ticks()
> function is now:
> 
>   static inline uint64_t ticks(void) {
>     uint32_t h1 = volatileAccess(_ticks_high);
>     TC0->COUNT32.CTRLBSET.reg =
>          TC_CTRLBSET_CMD(TC_CTRLBSET_CMD_READSYNC_Val);
>     while(TC0->COUNT32.SYNCBUSY) {
>     }
>     uint32_t l1 = TC0->COUNT32.COUNT.reg;
>     uint32_t h2 = volatileAccess(_ticks_high);
>     if (h2 != h1) return ((uint64_t)h2 << 32) + 0;
>     else          return ((uint64_t)h1 << 32) + l1;
>   }
> 
> Datasheet says you need to write the CMD bits of TC0->CTRLB register
> with a known value (TC_CTRLBSET_CMD_READSYNC_Val), before reading COUNT
> register.  Why?  I don't know.  TC0->CTRLB is a **Write-Syncronized**
> register, i.e. the value you are writing will be really wrote after sync
> time.  Maybe the sync loop I added waits for time needed to the CTRLB
> command to be executed... otherwise the COUNT value you read immediately
> after could be wrong (IMHO!)
> 
> 
> After this... I think my code is always affected by the original problem
> if, as you explained, TC0 interrupt is delayed when I'm reading hw
> counter in ticks().  I don't have the expertise to understand this
> possibility happens really, so it's better to find another method.
> 
> I liked the idea to use a 64-bits counter for ticks that will never
> roll-over during the entire lifetime of the device.

If you can make your hardware timer function run every millisecond (or
whatever accuracy you need), then use this:

extern volatile uint64_t tickCounter_;

static inline uint64_t ticks(void) {
	uint64_t a = tickCounter_;
	while (true) {
		uint64_t b = tickCounter_;
		if (a == b) return a;
		a = b;
	}
}

void TC0_Handler(void) {
	if (TC0->COUNT32.INTFLAG.reg & TC_INTFLAG_OVF) {
		tickCounter++;
		TC0->COUNT32.INTFLAG.reg = TC_INTFLAG_OVF;
	}
}	

If ticks never accesses the timer hardware register, there cannot be a
problem with synchronisation.  There is no need to use the timer
peripherals complicated synchronisation and locking mechanism, nor any
concern about interrupt delays.  Re-reading the 64-bit value until you
have two identical reads is sufficient to ensure that you have a
consistent value even if a timer interrupt occurs in the middle of the
64-bit read.

Back to comp.arch.embedded | Previous | Next — Previous in thread | Next in thread | Find similar | Unroll thread


Thread

A timer driver for Cortex-M0+... it rarely doesn't work pozz <pozzugno@gmail.com> - 2017-04-27 00:55 +0200
  Re: A timer driver for Cortex-M0+... it rarely doesn't work Simon Clubley <clubley@remove_me.eisner.decus.org-Earth.UFP> - 2017-04-26 23:24 +0000
    Re: A timer driver for Cortex-M0+... it rarely doesn't work pozz <pozzugno@gmail.com> - 2017-04-27 09:32 +0200
      Re: A timer driver for Cortex-M0+... it rarely doesn't work Jack <jack4747@gmail.com> - 2017-04-27 01:15 -0700
  Re: A timer driver for Cortex-M0+... it rarely doesn't work John Speth <johnspeth@yahoo.com> - 2017-04-26 17:17 -0700
    Re: A timer driver for Cortex-M0+... it rarely doesn't work pozz <pozzugno@gmail.com> - 2017-04-27 09:37 +0200
      Re: A timer driver for Cortex-M0+... it rarely doesn't work David Brown <david.brown@hesbynett.no> - 2017-04-27 10:13 +0200
        Re: A timer driver for Cortex-M0+... it rarely doesn't work John Speth <johnspeth@yahoo.com> - 2017-04-27 10:24 -0700
          Re: A timer driver for Cortex-M0+... it rarely doesn't work David Brown <david.brown@hesbynett.no> - 2017-04-27 22:04 +0200
  Re: A timer driver for Cortex-M0+... it rarely doesn't work David Brown <david.brown@hesbynett.no> - 2017-04-27 10:01 +0200
    Re: A timer driver for Cortex-M0+... it rarely doesn't work David Brown <david.brown@hesbynett.no> - 2017-04-27 10:27 +0200
      Re: A timer driver for Cortex-M0+... it rarely doesn't work pozz <pozzugno@gmail.com> - 2017-04-27 11:53 +0200
        Re: A timer driver for Cortex-M0+... it rarely doesn't work Stef <stef33d@yahooI-N-V-A-L-I-D.com.invalid> - 2017-05-01 10:09 +0200
          Re: A timer driver for Cortex-M0+... it rarely doesn't work pozz <pozzugno@gmail.com> - 2017-05-02 09:03 +0200
            Re: A timer driver for Cortex-M0+... it rarely doesn't work Stef <stef33d@yahooI-N-V-A-L-I-D.com.invalid> - 2017-05-04 10:06 +0200
    Re: A timer driver for Cortex-M0+... it rarely doesn't work pozz <pozzugno@gmail.com> - 2017-04-27 11:38 +0200
      Re: A timer driver for Cortex-M0+... it rarely doesn't work David Brown <david.brown@hesbynett.no> - 2017-04-27 12:54 +0200
        Re: A timer driver for Cortex-M0+... it rarely doesn't work pozz <pozzugno@gmail.com> - 2017-04-27 13:51 +0200
          Re: A timer driver for Cortex-M0+... it rarely doesn't work David Brown <david.brown@hesbynett.no> - 2017-04-27 15:39 +0200
            Re: A timer driver for Cortex-M0+... it rarely doesn't work pozz <pozzugno@gmail.com> - 2017-04-28 09:24 +0200
              Re: A timer driver for Cortex-M0+... it rarely doesn't work David Brown <david.brown@hesbynett.no> - 2017-04-28 11:05 +0200
                Re: A timer driver for Cortex-M0+... it rarely doesn't work pozz <pozzugno@gmail.com> - 2017-04-28 12:18 +0200
                Re: A timer driver for Cortex-M0+... it rarely doesn't work David Brown <david.brown@hesbynett.no> - 2017-04-28 13:10 +0200
                ARM developing under Linux [was Re: A timer driver for Cortex-M0+... it rarely doesn't work] pozz <pozzugno@gmail.com> - 2017-05-02 09:14 +0200
                Re: ARM developing under Linux [was Re: A timer driver for Cortex-M0+... it rarely doesn't work] David Brown <david.brown@hesbynett.no> - 2017-05-02 11:34 +0200
                Re: ARM developing under Linux [was Re: A timer driver for Cortex-M0+... it rarely doesn't work] pozz <pozzugno@gmail.com> - 2017-05-03 09:04 +0200
                Re: ARM developing under Linux [was Re: A timer driver for Cortex-M0+... it rarely doesn't work] David Brown <david.brown@hesbynett.no> - 2017-05-03 10:59 +0200
  Re: A timer driver for Cortex-M0+... it rarely doesn't work Dimiter_Popoff <dp@tgi-sci.com> - 2017-04-27 11:02 +0300
    Re: A timer driver for Cortex-M0+... it rarely doesn't work David Brown <david.brown@hesbynett.no> - 2017-04-27 11:06 +0200
      Re: A timer driver for Cortex-M0+... it rarely doesn't work Dimiter_Popoff <dp@tgi-sci.com> - 2017-04-27 14:20 +0300
        Re: A timer driver for Cortex-M0+... it rarely doesn't work David Brown <david.brown@hesbynett.no> - 2017-04-27 16:16 +0200
      Re: A timer driver for Cortex-M0+... it rarely doesn't work Simon Clubley <clubley@remove_me.eisner.decus.org-Earth.UFP> - 2017-04-27 12:03 +0000
        Re: A timer driver for Cortex-M0+... it rarely doesn't work David Brown <david.brown@hesbynett.no> - 2017-04-27 16:18 +0200

csiph-web