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?
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".
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.