How to configure Precise Hardware timer in SAML21 - Page 1
Forum section: electronics / general

author: muthukural001
author_rank: Regular Contributor
timestamp: April 29, 2017, 12:23:33 pm
content: Hello,My application needs to trigger at exactly 20ms. Right now, I am using TC3 in 8bit mode with 500Hz timer clock.Clock source for this timer is 32Khz . I have divided it by Pre-scalar value of 64 to get 500Hz clock. with this clock source, it takes 2ms for one count. For 20ms, I had set period value to 10. But I was getting trigger at 25ms instead 20ms. I have used this timer in interval mode not in periodic mode. Please suggest a solution.Timer code:void configure_tc(void){struct tc_config config_tc;tc_get_config_defaults(&config_tc);config_tc.counter_size = TC_COUNTER_SIZE_8BIT;config_tc.clock_source = GCLK_GENERATOR_1; //32KHZ-32.768 internal oscillatorconfig_tc.clock_prescaler = TC_CLOCK_PRESCALER_DIV64;config_tc.counter_8_bit.period = 10;tc_init(&tc_instance, TC3, &config_tc);}void configure_tc_callbacks(void){tc_register_callback(&tc_instance, tc_callback_to_toggle_led,TC_CALLBACK_OVERFLOW);tc_enable_callback(&tc_instance, TC_CALLBACK_OVERFLOW);}Thanks,Muthu

author: ataradov
author_rank: Super Contributor
timestamp: April 29, 2017, 05:50:17 pm
content: What is the source of your 32 kHz clock? If it is one of the internal ones, then they are never exactly 32 kHz, they are 32768 Hz, but in this case that would actually improve things, not make them worse.What do you mean by "I have used this timer in interval mode not in periodic mode."?Please remove commented code, it is hard to read.

author: muthukural001
author_rank: Regular Contributor
timestamp: April 30, 2017, 05:31:15 am
content: Hi Alex,Thanks. Yes it is internal 32.768Khz oscillator.What do you mean by "I have used this timer in interval mode not in periodic mode."?It will not be one time start. I will use start and stop timer repetitively.My application:I am trying to send sensor from multiple LWMESH nodes to Receiver in time slot mode. As LWMESH timers not precise, I want to try with Hardware timer availble in MCU. First I am sending , Broadcast frame to start ADC conversion in nodes. Right now, I am trying with 3 nodes to 1 Receiver configuration. So, All transmitters will start ADC conversion at same time, after 200ms I will get one ADC packet to transmit. At 200ms, I am starting Hardware timer with 20ms,40ms and 60ms expiry time in first,second and third transmitters respectively . After this 20 ms, I am sending that ADC packet. So first transmitter, transmit its packets at 220ms ,420ms,620ms ,820ms and so on.Second transmitter with 40 ms hardware timer. It will transmit packets at 240ms,440ms,640ms and so on.Third transmitter with 60ms hardware timer.It will transit packets at 260ms,460ms,660ms and so on.What do you mean by "I have used this timer in interval mode not in periodic mode."?It will not be one time start. I will use start and stop timer repetitively.After 20 ms expiry time, I will stop timer and again start when new ADC packet available.

author: ataradov
author_rank: Super Contributor
timestamp: April 30, 2017, 05:37:20 am
content: Quote from: muthukural001 on April 30, 2017, 05:31:15 amHow do you measure interval right now? Do you account for the delays in starting or stopping it?Quote from: muthukural001 on April 30, 2017, 05:31:15 amThat's not how time-synchronized networks are designed. You need to implement this timer on the lowest level - on the border between NWK and PHY layer.Application runs as normal, and makes a request to send data when it has data. PHY will hold on to the frame, until it is its turn to send.I'm not saying that your way will not work, but it is not a traditional way, so it is hard to predict what sort of issues you will run into. But for starters, you need to disable routing.

author: muthukural001
author_rank: Regular Contributor
timestamp: April 30, 2017, 10:46:34 am
content: Hi Alex,Please refer the captured oscilloscope images.Test setup- 2 transmitters to 1 ReceiverFirst image (Sync image) has been captured at start time:Here, Receiver will send start beacon (basically broacast frame) to transmitters in the network. When Transmitters receives beacon, both two transmitters will start ADC conversion at same time. One packet acquiring time from ADCs of both transmitters are 200ms. First transmitter will send packet at 220 ms and second transmitter will send at 240ms. In the image, Blue is First transmitter and Yellow is second transmitter. Both rising edge at 200ms indicates that packet is available and falling edge of both indicates that packet transmission.For first and second transmitter, falling edge (transmissiom) is happening at 20ms and 40ms after rising edge.This means that Packet transmission happening at 220ms for First transmitter and 240 ms for Second transmitter. It is going fine up to some time but not for the entire time. Please refer the following second image description.Second image (Async image) has been capture at Mid-time:Here, I see Asynchronous rising edge (packet available to send) between first transmitter and second transmitter instead sync edge like first image. Though, consecutive rising edge of both transmitters are happening at every 200ms and packet sending at 20ms and 40ms after rising edge.I don't know why I am seeing drift between transmitters rising edge in the Mid-duration.

author: BrianHG
author_rank: Super Contributor
timestamp: April 30, 2017, 11:05:25 am
content: I know this is not my processor, but when I do precision internal interrupt timing for ADCs & DACs when I program in my PIC, when the timer hits, the first thing I do is either reset the timer, or, add my timer period to the timer counter and let it run immediately. Once that has been put to bed, I do my ADC/DAC transfers, while the timer is running. I never stop the timer. This way, if my SPI interface or anything else happens during the transfer, the timer wont loose it's timing.The advantage in adding (actually subtracting...) the desired timer period instead resetting the timer is if there are timer increments before the interrupt is serviced, the 1 or 2 clock offset error is kept and corrected for. This means if my timer interrupt is set to trigger at 50, and by the time I service it, it's now at 52, subtracting the desired 50 means that 2 offset is maintained and the global clock loop will always be the 50 no matter what. Even if my sample is at a slight delay due to a slow CPU, it wont change that perfect fixed 50 clock. That delay should always remain constant.Ooops, wrong concept for networking devices, but, maybe it can give you ideas.

author: ataradov
author_rank: Super Contributor
timestamp: April 30, 2017, 06:14:56 pm
content: Quote from: muthukural001 on April 30, 2017, 10:46:34 amThere is noting I can do here without looking an the actual code and debugging it. You are piling up a lot of complicated processes.Also, ADC conversion speed is not constant, and it actually depends on the converted values. So if you are relying on that to be constant, you may be in for a surprise.But once again, you are approaching this wrong. ADC needs to start when it starts, it has noting to do with the network. Your PHY layer should keep track of the time slots. This system is MUCH easier to debug, since you can forget about ADC for a while and debug your networking with dummy payloads first.

author: muthukural001
author_rank: Regular Contributor
timestamp: May 01, 2017, 02:09:32 am
content: Hi Alex,Following is the code which I have used./*Timer counter callback function */void tc_callback_to_toggle_led(struct tc_module *const module_inst){packet_send=true;tc_disable(&tc_instance);}/*Main loop */int main(void){configure_tc();configure_tc_callbacks();SYS_TaskHandler();APP_TaskHandler();while(!Timerstart) //------>waiting for Start command from receiver (Sync frame){SYS_TaskHandler();}while (1){if(Timerstart){port_pin_set_output_level(EXT1_PIN_10,true); //-------------->Start ADC conversionTimerstart=false;}if(acqptr==70) //check if packet(14 samples,1 sample=5bytes) available. It will happen at exactly 200ms after Start ADC conversion.{tc_enable(&tc_instance); //start 20ms timer (for 1st transmitter),start 40ms timer (for 2nd transmitter) and 60ms-3rd TXmemcpy(appDataReqBuffer,acqbuf,PACKETSIZE);appDataReqBuffer=Dummy_Byte; //appDataReqBuffer=0xFF(Dummy_Byte)appDataReqBuffer= Dummy_Byte;appDataReqBuffer=Dummy_Byte;appDataReqBuffer=Dummy_Byte;appDataReqBuffer=(packet_counter>>& 0xFF; //One second packet counterappDataReqBuffer=packet_counter & 0xFF;appDataReqBuffer=Dummy_Byte;appDataReqBuffer=Dummy_Byte;appDataReqBuffer=Dummy_Byte;appDataReqBuffer=Dummy_Byte;}if(packet_send==true) // check if transmit ready.This will get true in the TC-Callback function after 20ms timer expired.{packet_send=false;appSendData();configure_tc();configure_tc_callbacks();}}}

author: ataradov
author_rank: Super Contributor
timestamp: May 01, 2017, 02:12:39 am
content: This is a horrible unreadable mess. I don't really know what to do here.Figure out how to use state machines. Just assume that your main() function should never change and must remain the same as in the sample application. You can't just clobber a lot of flags together and expect things to work.

author: muthukural001
author_rank: Regular Contributor
timestamp: May 01, 2017, 04:47:48 am
content: Hi Alex,"This system is MUCH easier to debug, since you can forget about ADC for a while and debug your networking with dummy payloads first."Ok. I will try with Dummy payload.Following is the code in which I have used state machine.enum states{ADC_CONVERSION_START = 0x01,SAMPLE_ACQUIRING = 0x02,WIRELESS_TRANSMISSION = 0x03,};int main(void){configure_tc();configure_tc_callbacks();SYS_TaskHandler();APP_TaskHandler();while(!Timerstart) //------>waiting for Start command from receiver (Sync frame), updating in appDataInd(){SYS_TaskHandler();}while (1) {if(Timerstart) //Broadcast frame received{state=1;Timerstart=false;}if(send_spi == true) //Check if it has changed from Interrupt mode to SPI mode to read sample.{state=2;send_spi=false;/*Changing pin from interrupt mode to SPI mode to read sample from ADC*/struct system_pinmux_config pin_conf;pin_conf.mux_position = 0x03;system_pinmux_pin_set_config(PIN_PA04, &pin_conf);}if(packet_send==true) //Check if 20 ms timer expired for Wireless transmission{state=3;packet_send=false;}switch(state){case ADC_CONVERSION_START:{port_pin_set_output_level(EXT1_PIN_10,true);state=0;}break;case SAMPLE_ACQUIRING:{ret = AD7124_ReadData(ad7124_handler, &sample); //read ADC DATA register for sample valueif (ret < 0)return ret;Buffer_status=Buffer_fill();if(Buffer_status==BUFFER_FULL){tc_enable(&tc_instance); //start 20ms timeracqptr=0;}state=0;}break;case WIRELESS_TRANSMISSION:{appSendData();configure_tc();configure_tc_callbacks();state=0;}break;default:break;}if(transrev_complete_spi_master) //Change back to Interrupt mode to detect samples{/*Changing pin from SPI mode to Interrupt mode to detect ADC sample */transrev_complete_spi_master=false;struct system_pinmux_config pin_conf;pin_conf.mux_position = 0x00; // change back MUX position to EXTINT to detect next RDYsystem_pinmux_pin_set_config(PIN_PA04, &pin_conf);}SYS_TaskHandler();}}Coming to ADC operation, It shares MOSI pin for both data transfer and Interrupt Micro-controller to indicate sample ready. By default, I set MCU-MISO to detect ADC's sample ready interrupt. Once interrupt flagged(send_spito true) , I am changing MISO pin to SPI data transfer mode from Interrupt mode. Like that, after SPI transfer completed,transrev_complete_spi_masterwill flag to true at SPI_Callback to set it to interrupt mode.I am using multiple interrupts since we can't avoid Timer interrupt (packet_send),SPI transceiver interrupt(transrev_complete_spi_master), External ADC interrupt(send_spi) and appDataInd interrupt(Timerstart). All flags are volatile variable.Thanks,Muthu

author: ataradov
author_rank: Super Contributor
timestamp: May 01, 2017, 04:53:36 am
content: The code is still unreadable mess. Have you heard about formatting?You can forget about ADC for a long time. Figure out how to do reliable networking first.I also don't get that description about IRQ/SPI configuration. Do you have a schematic or something?

author: muthukural001
author_rank: Regular Contributor
timestamp: May 01, 2017, 05:23:23 am
content: ADC interfaced to Micro-controller over SPI interface.ADC's DOUT(MOSI) pin is multiifuctional pins. One is SPI function and other is Interrupt function.Let's say ADC set to 50 samples per second.So, One sample completion time is 20ms(1/50sps).Once sample conversion completed, it will interrupt MCU over SPI's MISO pin. Like this,for every 20ms, ADC will interrupt Micro-controller over the connected SPI's MISO pin to pick the sample.When ADC conversion started, I set SPI's MISO pin functionality to detect falling edge interrupt.After receiving the ADC interrupt over SPI's MISO pin, I am changing SPI's MISO pin to do SPI functionality to read sample from ADC.Please refer the attached image for details.ADC Datasheet link:http://www.analog.com/media/en/technical-documentation/data-sheets/AD7124-8.pdf

author: ataradov
author_rank: Super Contributor
timestamp: May 01, 2017, 05:28:21 am
content: Quote from: muthukural001 on May 01, 2017, 05:23:23 amOk, that makes sense.

author: muthukural001
author_rank: Regular Contributor
timestamp: May 01, 2017, 06:00:03 am
content: Hi Alex,If you don't mind, can you tell me how to format code ?Thanks,Muthu

author: ataradov
author_rank: Super Contributor
timestamp: May 01, 2017, 06:04:27 am
content: Quote from: muthukural001 on May 01, 2017, 06:00:03 amI thought it was pretty obvious.All those lines:Code:configure_tc();configure_tc_callbacks();SYS_TaskHandler();APP_TaskHandler();while(!Timerstart) //------>waiting for Start command from receiver (Sync frame), updating in appDataInd(){SYS_TaskHandler();}while (1) {should start on the same indentation level. It makes no difference to the compiler, but it makes difference to humans (including yourself). I personally have enough self respect to not read poorly formatted code.

author: muthukural001
author_rank: Regular Contributor
timestamp: May 01, 2017, 07:27:08 am
content: Hi Alex,Please find attached the formatted code. Please let me know if it is Ok.Thanks,Muthu

author: ataradov
author_rank: Super Contributor
timestamp: May 01, 2017, 07:28:04 am
content: There is no code attached. And I'm not really going to review it. If it works - fine, if it does not - debug.EDIT: Now there is code. In any case - figure out networking part first. ADC can wait.

author: muthukural001
author_rank: Regular Contributor
timestamp: May 01, 2017, 07:52:17 am
content: Hi Alex,Have you reviewed My code?Should I change anything?

author: ataradov
author_rank: Super Contributor
timestamp: May 01, 2017, 07:58:59 am
content: No, why would I review it? I looked at it, and it is not great. Not that it will help much to point individual problems, but you are no taking into account the time it takes to configure the timer/counter, for example.As I said, you need to do networking part first, and the rest will have to work around that. I doubt you will be able to do blocking reads from ADC, if you want to meet timing requirements.It is not about this particular piece of code, it is about system design. You need to plan first and write down your algorithm, and then implement what you have designed. With good design, implementation is the easy part.I can't teach you programming over the forum, you will have to do that yourself.

author: ataradov
author_rank: Super Contributor
timestamp: May 01, 2017, 08:06:06 am
content: Quote from: muthukural001 on May 01, 2017, 08:00:31 amNo. By the time you are done, there will be very little left of the LwMesh. You basically need to take PHY layer, remove the rest of the stack, since it is not useful for star topology. Then you need to make that PHY work in a time-synchronized mode.As it is, LwMesh is not going to work for your application. There is just too much stuff in a way. For example, default PHY configures automatic retries. This needs to be off. If you could not send the thing within the allocated slot, then you need to either disregard the data, or wait until the next time slot.Basically you will have to learn how networking works in details. There is no way around it, your requirements are pretty tight for the selected radio standard.But you can't do this by calling random functions and looking at the result. You need to clearly understand what you are doing.

author: muthukural001
author_rank: Regular Contributor
timestamp: May 01, 2017, 02:48:21 pm
content: Hi Alex,Thanks.I have designed My code in order to not miss ADC samples from ADC. For My application, both Sample acquiring and Sending it over network are same priority. Before while(1) loop, it doesn't matter with functions. In the while (1) loop, I have defined networking part (SYS_TaskHandler()) at last. Defining it at start of while(1) loop will not affect application. I am seeing the same performance for both .I will study and try with only physical layer to achieve My application.Thanks,Muthu

author: sokoloff
author_rank: Super Contributor
timestamp: May 01, 2017, 03:41:09 pm
content: I'm not deeply familiar with this processor, but from a quick read of thedatasheet, I think you want to be using a TCC, not a TC. (You're using TC3, which is available in PD1 and higher; the TCCs are also available in PD1 and higher, so no difference in power.)I think you want to be using the period or target count functions in a TCC and using that. You also probably don't need pre-scaling (and if you don't need pre-scaling, you don't want it.) You also probably don't want to be re-setting the timer (from user code) each time it triggers. (Otherwise, your timer period drifts by the execution time of your code.)Start here, which links tohere, which links tohereandhere. Please let us know how it turns out (and if I'm wrong, feel free to curse my name and flame me on EEVB...)

author: sokoloff
author_rank: Super Contributor
timestamp: May 01, 2017, 03:45:38 pm
content: The reason to avoid pre-scaling is that you want a 32678Hz timer to tick 655 times to get a 50Hz (20ms period) timer out of it.If you prescale by 64, then you're forced to trigger at either 10 (51.2 Hz, 19.5ms) or 11 (46.5 Hz, 21.5ms).Without pre-scaling, you can reduce the timing error significantly.

author: ataradov
author_rank: Super Contributor
timestamp: May 01, 2017, 05:16:08 pm
content: I will try to come up with an example, but it will only work in a simulator (https://github.com/ataradov/netsim), since I don't wan to bother with the real hardware now.It will also be based on a standalone version of the LwMesh (http://www.atmel.com/tools/lightweight_mesh.aspx). ASF is horrible, and I don't want to deal with it.

author: muthukural001
author_rank: Regular Contributor
timestamp: May 05, 2017, 11:53:23 am
content: Hi sokoloff,I can achieve timer accuracy after changing clock source to External 32.768Khz oscillator instead internal 32.768Khz. One doubt I have is dealing with 16 bit timer counter. When I worked with 8 bit timer counter, it had period register to load count cycle. But, in the 16-bit timer, I don't see any separate period register to count cycle. As you said , if I will have to count 655 count to achieve 20 ms, I have to use 16 bit timer.Where should I load this 655 in 16 -bit timer ?Thanks,Muthu

author: sokoloff
author_rank: Super Contributor
timestamp: May 05, 2017, 12:37:17 pm
content: In the TCC (TCC, not TC, of course) sample code I linked above, there are examples. Here are some snippets.From thebasic example:Code:config_tcc.compare.match = (0xFFFF / 4);From thetimer sample code:Code:config_tcc.counter.clock_source = GCLK_GENERATOR_1;config_tcc.counter.clock_prescaler = TCC_CLOCK_PRESCALER_DIV64;config_tcc.counter.period = 2000;config_tcc.compare.match = 900;config_tcc.compare.match = 930;config_tcc.compare.match = 1100;config_tcc.compare.match = 1250;I've never used that micro, but per the datasheet, all the TCCs are 16 or 24 bit, and all the examples above load values larger than uint8.

author: muthukural001
author_rank: Regular Contributor
timestamp: May 05, 2017, 12:46:58 pm
content: SAM L21 has both TCC and TC.Why don't I use TC for My application?

author: muthukural001
author_rank: Regular Contributor
timestamp: May 05, 2017, 01:00:28 pm
content: Ok. I got it. 16 bit counter have no Period register to load count.So, for 16 bit counter, we have to load Compare Channel 0 (CC0) register with 655 if I need 20ms interrupt.

author: ataradov
author_rank: Super Contributor
timestamp: May 05, 2017, 04:22:38 pm
content: You are overthinking it. For what you are doing, quite literally every single clock source inside the device is accurate enough.