Why Is This Timer ISR Firing Here?

Go To Last Post
12 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

Using an ATSAMV71N19B, and am really struggling with it. All I'm trying to do is understand how to trigger an ISR to toggle an LED every X seconds, but all I can seem to do is change it between every 100mS and every 200mS or so. In debugging, I'm getting some strange results. This init sequence is directly from START:

 

int32_t _timer_init(struct _timer_device *const device, void *const hw)
{
	struct tc_configuration *cfg     = get_cfg(hw);
	uint32_t                 ch_mode = cfg->channel_mode;

	device->hw = hw;
	ASSERT(ARRAY_SIZE(_tcs));

	if (ch_mode & TC_CMR_WAVE) {
		/* Enable event control mode */
		ch_mode |= (0x02 << 13) | (0x01 << 16) | (0x02 << 18);
	}

	hri_tc_write_CMR_reg(hw, 0, ch_mode);
	hri_tc_write_RA_reg(hw, 0, cfg->ra);
	hri_tc_write_RC_reg(hw, 0, cfg->rc);
	hri_tc_set_IMR_reg(hw, 0, TC_IER_CPCS);
	hri_tc_write_FMR_reg(hw, cfg->fmr);

	_tc_init_irq_param(hw, device);
	NVIC_DisableIRQ(cfg->irq);
	NVIC_ClearPendingIRQ(cfg->irq);
	NVIC_EnableIRQ(cfg->irq);

	return ERR_NONE;
}

 

Ignore that you can't see the values I'm loading for RA and RC. But here is a capture of the Watch window from Studio when I hit the ISR:

 

 

There are a number of problems here.

1) TC_IER (interrupt enable reg) has nothing set, even though I set the RC compare interrupt here:

	hri_tc_set_IMR_reg(hw, 0, TC_IER_CPCS);

The code for this function from the START file:

static inline void hri_tc_set_IMR_reg(const void *const hw, uint8_t submodule_index, hri_tc_imr_reg_t mask)
{
	((Tc *)hw)->TcChannel[submodule_index].TC_IER = mask;
}

 

Why isn't it "accepting" the write to the IER reg? There's no mention of any other lock bit or anything needing to be set in the datasheet. However, I DO get the appropriate flag in the interrupt mask register (TC_IMR = 0x00000010) after I start debugging. In fact, that flag never seems to go away. 

 

2) Why is the value in the counter value register when the ISR triggers nowhere close to the compare value in TC_RC? Is this real, or an artifact of trying to observe using Watch? The status reg indicates I DID get a RC overflow (TC_SR=0x00070010). 

 

Last Edited: Mon. Jan 10, 2022 - 07:13 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This seems a bit low level if you are using the timer driver (which driver btw, Lite:TC:Timer or HAL:Driver:Timer?). I would expect you register a callback and not use TC0_Handler.

This is the example code when using HAL:Driver:Timer:

 

static struct timer_task TIMER_0_task1, TIMER_0_task2;

static void TIMER_0_task1_cb(const struct timer_task *const timer_task)
{
}

static void TIMER_0_task2_cb(const struct timer_task *const timer_task)
{
}

void TIMER_0_example(void)
{
    TIMER_0_task1.interval = 100;
    TIMER_0_task1.cb       = TIMER_0_task1_cb;
    TIMER_0_task1.mode     = TIMER_TASK_REPEAT;
    TIMER_0_task2.interval = 200;
    TIMER_0_task2.cb       = TIMER_0_task2_cb;
    TIMER_0_task2.mode     = TIMER_TASK_REPEAT;

    timer_add_task(&TIMER_0, &TIMER_0_task1);
    timer_add_task(&TIMER_0, &TIMER_0_task2);
    timer_start(&TIMER_0);
}

Your pin toggling code would be in a callback function if you used that driver. I never looked at Lite:TC:Timer (or indeed the actual timer TC hardware on SAMV71 in any detail) but there you have a complete example for SAMV71 Xplained Ultra which seems close to what you are doing:

 

TC LITE DRIVER EXAMPLE

This example use the TC lite driver to toggle LED0(using TC capture mode) and LED1(using TC waveform mode) at 1Hz frequency on SAMV71

/Lars 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Lajon wrote:

This seems a bit low level if you are using the timer driver (which driver btw, Lite:TC:Timer or HAL:Driver:Timer?). I would expect you register a callback and not use TC0_Handler.

This is the example code when using HAL:Driver:Timer:

 

static struct timer_task TIMER_0_task1, TIMER_0_task2;

static void TIMER_0_task1_cb(const struct timer_task *const timer_task)
{
}

static void TIMER_0_task2_cb(const struct timer_task *const timer_task)
{
}

void TIMER_0_example(void)
{
    TIMER_0_task1.interval = 100;
    TIMER_0_task1.cb       = TIMER_0_task1_cb;
    TIMER_0_task1.mode     = TIMER_TASK_REPEAT;
    TIMER_0_task2.interval = 200;
    TIMER_0_task2.cb       = TIMER_0_task2_cb;
    TIMER_0_task2.mode     = TIMER_TASK_REPEAT;

    timer_add_task(&TIMER_0, &TIMER_0_task1);
    timer_add_task(&TIMER_0, &TIMER_0_task2);
    timer_start(&TIMER_0);
}

Your pin toggling code would be in a callback function if you used that driver. I never looked at Lite:TC:Timer (or indeed the actual timer TC hardware on SAMV71 in any detail) but there you have a complete example for SAMV71 Xplained Ultra which seems close to what you are doing:

 

TC LITE DRIVER EXAMPLE

This example use the TC lite driver to toggle LED0(using TC capture mode) and LED1(using TC waveform mode) at 1Hz frequency on SAMV71

/Lars 

 

Thanks for the reply! Just curious: have you actually tried the HAL:Driver:Timer code? I was using this code originally but was having an issue with hard faults being thrown when the ISR fired. To try and simplify, I bypassed establishing any "tasks" and went directly to the TCO Handler. I was wondering if it was something I was doing wrong (probably). But otherwise, my code is the same code as the HAL:Driver:Timer example: all the same lower-level functions, etc.  

 

Pardon the question: where do I find these Lite examples? I downloaded the ASF framework (though for whatever reason can't seem to get Studio to link to it); I don't recall seeing these Lite drivers. 

 

EDIT: Sorry, disregard, I see the Lite drivers in START. 

Last Edited: Mon. Jan 10, 2022 - 08:44 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have tried the timer driver but not with a SAMV71 or anything close. From looking at the datasheet what you get with TC_IER is 100% expected, it is a write only register used to control what gets enabled in the TC_IMR register (which is read only). Similar for TC_IDR, it's used to disable interrupts so writing it will clear bits in TC_IMR.

/Lars

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi Lars,

 

as others wrote the TC_IER register ist a write only register. So you cannot read back its value. If you have the need you can read the TC_IMR register. That one tells you which interrupt is enabled. Please note, that there is also a TC_IDR register to disable interrupts. So make sure that if you are enableing an interrupt that it is not disable also. I never tried to enable and disable the same timer interrupt and see what will happen. Maybe someone else has experience about this.

 

I'm using Embedded Studio from SEGGER. As far as I can see it is a bit more solid compared to Atmel Studio (better say Microchip Studio) if it comes to low level debugging (for the price of having not much comfort for peripheral programming). Even this tool fails to stop the program execution in 'real time'. I always have a different value in TC_CV as expected if I set a breakpoint inside the corresponding interrupt. This is simply due to the fact that the debugger needs some time to stop the µC and its peripherals. I also think the ARM architecture is not well suited for this. I have some experience with Infineon Tricore µC. They are performing significant better in this topic. Maybe things change if you have a 'full size JTAG' debugger. I have only SWD (single wire debugger) in use. 

You might lower the timer frequency significantly to have a check. It should come closer to your expectation if you lower the frequency.

 

So from what I can see everything is fine, at least concerning your questions.

 

One word on the drivers you are using. 

I only use the lite drivers because of their performance. Certainly it is convenient to use the callback functionality. However, one should keep in mind that this adds another jump (include stack consumption) in your code. Usually you are interested in quick response if you are setting up an interrupt and therefore the call of a function inside an ISR is a contradiction for me. 

 

Best Regards

Markus

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Markus Krug wrote:
Hi Lars,

Hi Markus, a bit of identity mixup there, I'm not the OP.

/Lars

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Marcus and Lars,

Thanks for the replies.

 

So I'm trying the Lite:TC:Timer example, and I'm still confused. I spec'd the parameters in START. The code it generates (even when checking the CPCTRG checkbox in START) loads no value into RC. So I added this with a value I thought would give me (about) a 1s tick (75Mhz clock w/ 128 clock prescaler), and added a LED toggle to the TC0 Handler, built the project, and ran it. The LED toggled at a rate I would estimate at 500mS (scope is in the workshop). So I added a "0" to my RC value, expecting this would slow the LED down by an order of magnitude. And instead, it now toggles faster than before. Added another "0", toggles even faster. OK, so remove a "0" from my original value, and: no difference. Removed ANOTHER "0", and now it's toggling faster than ever. 

 

What am I doing wrong? The results I'm seeing make no sense.    

 

#include "tc_lite.h"
#include "atmel_start_pins.h"

#define TC_CHANNEL_0 0
#define TC_CHANNEL_1 1
#define TC_CHANNEL_2 2
#define TC_CHANNEL_COUNT 3

/**
 * \brief TC IRQ callback type
 */
typedef void (*tc_channel_ptr)(uint32_t);

/* TC0 channel interrupt callback array */
tc_channel_ptr tc0_channel_cb[TC_CHANNEL_COUNT];

/**
 * \brief Initialize TC interface
 */
int8_t TIMER_0_init()
{

	/* TC0 Channel 0 configuration */

	hri_tc_write_CMR_reg(TC0, TC_CHANNEL_0, 3 << TC_CMR_TCCLKS_Pos | 1 << TC_CMR_CAPTURE_CPCTRG_Pos);

	hri_tc_write_IMR_reg(TC0, TC_CHANNEL_0, 1 << TC_IMR_CPCS_Pos);

	hri_tc_write_RC_reg(TC0, TC_CHANNEL_0,  586000);

	tc0_channel_cb[TC_CHANNEL_0] = NULL;
	NVIC_DisableIRQ(TC0_IRQn);
	NVIC_ClearPendingIRQ(TC0_IRQn);
	NVIC_EnableIRQ(TC0_IRQn);

	return 0;
}

void start_timer(const void *hw, uint8_t channel)
{
	if (channel < TC_CHANNEL_COUNT) {
		hri_tc_write_CCR_reg(hw, channel, TC_CCR_CLKEN | TC_CCR_SWTRG);
	}
}

void stop_timer(const void *hw, uint8_t channel)
{
	if (channel < TC_CHANNEL_COUNT) {
		hri_tc_write_CCR_reg(hw, channel, TC_CCR_CLKDIS);
	}
}

void tc_register_callback(void *hw, uint8_t channel, void *cb)
{
	ASSERT(hw && (channel < TC_CHANNEL_COUNT));
	if (hw == TC0) {
		tc0_channel_cb[channel] = cb;
	}
}

/* TC0 Channel 0 interrupt handler */
void TC0_Handler(void)
{
	uint32_t status;
	status = hri_tc_get_SR_reg(TC0, TC_CHANNEL_0, TC_SR_Msk);
	if (tc0_channel_cb[TC_CHANNEL_0] != NULL) {
		tc0_channel_cb[TC_CHANNEL_0](status);
	}

	gpio_toggle_pin_level(LED);
}

 

Last Edited: Tue. Jan 11, 2022 - 02:44 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The TC channels are 16-bit so that will not work. You can look at the TC LITE DRIVER EXAMPLE for how to use one channel to generate a slower clock for use in other channels.

/Lars

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Lajon wrote:

The TC channels are 16-bit so that will not work. You can look at the TC LITE DRIVER EXAMPLE for how to use one channel to generate a slower clock for use in other channels.

/Lars

 

Jeez, I am embarrassed. I read that warning in the datasheet 10 times and kept saying to myself, well that doesn't apply to ME. 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

one more point: The TC_IMR register is 'read only'. So it makes no sense to write to it. Where did you get the function for this?

 

Best Regards

Markus

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Markus Krug wrote:

one more point: The TC_IMR register is 'read only'. So it makes no sense to write to it. Where did you get the function for this?

 

Best Regards

Markus

 

This comes directly from START. The actual function writes to the IER reg; no idea why they opted to name the function "write_IMR_reg". 

 

static inline void hri_tc_write_IMR_reg(const void *const hw, uint8_t submodule_index, hri_tc_imr_reg_t data)
{
	((Tc *)hw)->TcChannel[submodule_index].TC_IER = data;
	((Tc *)hw)->TcChannel[submodule_index].TC_IDR = ~data;
}

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Note that the function also writes to IDR, the end result is that IMR contains the supplied 'data' value.
/Lars