Timer interrupt & i2c

tcsaba101
Posts: 92
Joined: Mon Dec 30, 2013 5:05 am
Location: Hungary

Timer interrupt & i2c

Post by tcsaba101 » Sat Sep 13, 2014 4:10 pm

This post connected to my "SD card worst case write time" post (viewtopic.php?f=24&t=3035).

It has been turned out the solution for SD card handling is the separate sensor reading + buffering and writing the buffer to the SD. And the appropriat size of the buffer.

I plan to read the sensor in a 10msec Timer interrupt, fill the buffer, then flush the buffer content to the SD in the loop() task.

I have read somewhere the i2c will not work inside an interrupt cycle. It happened to be true, if I comment out the i2c reading lines from the code the cycling works.
If the i2c reading included in the valid code it stucks.

I use Matt's Timer library: https://github.com/majenkotech/Timer

The code:

Code: Select all

void __attribute__((interrupt)) isr() 
{
	timer_task_start = micros();

	
	reg3 = ina.readBusVoltage();
	uBat_ms10_tot_mV += reg3;
	uBat_ms10_mV = (int)(reg3*1000.0);
	reg3 = ina.readShuntCurrent();
	iBat_ms10_tot_mA += reg3;
	iBat_ms10_mA = (int)(reg3*1000.0);

/*
 * 	
Here is the buffer filling
 */
	
	ms10_samples++;
	ms10_cnt += 1;
	if(ms10_cnt % 10 == 0) ms100_flag = true;
	if(ms10_cnt % 6000 == 0) min1_flag = true;

	timer_task_delay = micros()- timer_task_start;
		
	clearIntFlag(_TIMER_4_IRQ);
}
What is the solution to handle i2c in interrupt cycles?

User avatar
majenko
Site Admin
Posts: 2164
Joined: Wed Nov 09, 2011 7:51 pm
Location: UK
Contact:

Re: Timer interrupt & i2c

Post by majenko » Sat Sep 13, 2014 4:33 pm

I2C uses interrupts itself to handle the transfers.

It is possible to use interrupts inside interrupts, but only if you do it right. Basically when you are in an interrupt the only interrupts that are allowed to interrupt it are those of a higher priority.

I2C's interrupt runs at priority 3. If you ensure the timer's interrupt runs at a priority than the I2C's interrupt then it should work.

You can specify the priority for the interrupt in the constructor:

Code: Select all

Timer4 timer(2); // Configure timer 4 for priority 2.
Why not visit my shop? http://majenko.co.uk/catalog
Universal IDE: http://uecide.org
"I was trying to find out if it was possible to only eat one Jaffa Cake. I had to abandon the experiment because I ran out of Jaffa Cakes".

tcsaba101
Posts: 92
Joined: Mon Dec 30, 2013 5:05 am
Location: Hungary

Re: Timer interrupt & i2c

Post by tcsaba101 » Sat Sep 13, 2014 8:29 pm

Thanks!

It started to work.

tcsaba101
Posts: 92
Joined: Mon Dec 30, 2013 5:05 am
Location: Hungary

Re: Timer interrupt & i2c

Post by tcsaba101 » Sun Sep 14, 2014 4:30 pm

I share the most important things I have learned:

I have two i2c devices:
the INA226 what producing the data to be measured and
an DS3231 RTC.

All the i2c devices have to be handled inside the interrupt cyle, or the timer interrupt should be disabled during I2c communication.
The timer interrupt could interrupt the i2c cycle and begin a new i2c cycle, that makes strange situations.

Using the INA226 library the minimum timer interrupt cycle duration is between 2-3 msec. I need 10ms sampling so this is fine for me at the moment.

The ina226 capable to sample at 140usec, but reaching speed close to that, the i2c bus have to be handled at wire lib level or lower. I didn't experimented.

The card write latencies:
I have tried four cards:
- A: SanDisk 32G Class10
- B: SanDisk 4G Class6
- C: Maxell 32G Class10
- D: Kingston 32G Class 10

Data packets:
- D1: 1100 bytes
- D2: 10010 bytes

The maximum write cycle duration (msec):
D1_test D2_test
A: 91 295
B: 57 325
C: 95 307
D: 50 212

I don't think the figureas are relevant in absolute value, but hopefully somebody can use them in the relative sense.
Kingston looks to be the best, but I made only one test round, i could work out different in a longer test.

I havent developed further the code for speed, because it fulfills my requirements. I give it just for reference.

the interrupt task:

Code: Select all

void __attribute__((interrupt)) isr() 
{
	timer_task_start = micros();

	reg3 = ina.readBusVoltage();
	uBat_ms10_tot_mV += reg3;
	uBat_ms10_mV = (int)(reg3*1000.0);
	reg3 = ina.readShuntCurrent();
	iBat_ms10_tot_mA += reg3;
	iBat_ms10_mA = (int)(reg3*1000.0);		 
	
  int next = ringNext(head);

		if (next != tail) 
		{
	//	ms10_buffer[head].timestamp = log_timestamp;
			ms10_buffer[head].uBat_samp_mV = uBat_ms10_mV;
			ms10_buffer[head].iBat_samp_mA = iBat_ms10_mA;
	    head = next;
  	}
  	else 
  	{
    over ++;    // overrun the buffer
  	}
  
	ms10_cnt ++;
	if(ms10_cnt % 10 == 0) ms100_flag = true;
	if(ms10_cnt % 100 == 0)
	{
				s1_flag = true;
				dateTime = clock.getDateTime();
	}

	timer_task_delay = micros()- timer_task_start;
	if(timer_task_start > micros()) timer_task_delay = micros()+ (0xFFFFFFFF- timer_task_start); //in case of micros() counter overflov
	timer_del_total += timer_task_delay;
	no_of_cyc++;
	
	clearIntFlag(_TIMER_4_IRQ);
}

the flush task code:

Code: Select all

				if(recAvailable() > MS10_BUFSIZE/2)	
				{
						log_task_start = millis();
						data_logged	= 0;
						
						ms10FileName.toCharArray(filename, 13);  
					  logFile = SD.open(filename, FILE_WRITE);
					  if (! logFile)
							  {
					    		reason = DATA_FAIL;  	
							    printError("Data write failed");
							  }
						
					  //number of records
						int n = recAvailable();
						log_task_start = millis();
						data_logged	= 0;
									
						for(int i=0; i<n; i++)
						{
					     dataString =  
					//				String(ms10_buffer[i].timestamp) + ", " + 
									String(ms10_buffer[tail].uBat_samp_mV) + ", " + 
									String(ms10_buffer[tail].iBat_samp_mA);

								data_logged += dataString.length();
							  
							  if (logFile)
							  {
							    logFile.println(dataString);
								}
								else
							  {
					    		reason = DATA_FAIL;  	
							    printError("Ms10 write failed");
							  }					
							  tail = ringNext(tail);						  
						}
						logFile.close();
						
						log_elaps_ms = millis() - log_task_start;
						log_elaps_max = log_elaps_max < log_elaps_ms ? log_elaps_ms : log_elaps_max;
}
I list useful links on this topic what found based on pito's post:
https://code.google.com/p/beta-lib/downloads/list
http://forum.arduino.cc/index.php?topic=109954.0;wap2

Thanks fot the help of matt, pito and the referenced sources to get to this result.

pito
Posts: 193
Joined: Sun May 22, 2011 9:37 pm

Re: Timer interrupt & i2c

Post by pito » Tue Sep 16, 2014 7:37 pm

The basic paradigm of using interrupts is to keep them as short as possible..
That is not the case in the above code, imho.. :)

tcsaba101
Posts: 92
Joined: Mon Dec 30, 2013 5:05 am
Location: Hungary

Re: Timer interrupt & i2c

Post by tcsaba101 » Wed Sep 17, 2014 5:23 am

The interrupt cycle time is max 3,5msec, average below 2msec. What looks to be short enough when the timer hits in every 10msec.
It will decrease a bit when I remove the delay calculation codes.

I could make it shorter if that would be necessary, but this is why I like PIC32 :D let me to be lazy in some areas: lot of code and Ram space, and speed.

Changing the ina226 library to read raw values from the chip would decrease dramatically the lenght.
If I have resources I plan to make a faster logger in this setup. Now I want to finish the present project.

Any idea?

pito
Posts: 193
Joined: Sun May 22, 2011 9:37 pm

Re: Timer interrupt & i2c

Post by pito » Wed Sep 17, 2014 7:24 pm

A 1usec long interrupt with pic32 would be considered too long (at least by me) :)

tcsaba101
Posts: 92
Joined: Mon Dec 30, 2013 5:05 am
Location: Hungary

Re: Timer interrupt & i2c

Post by tcsaba101 » Wed Sep 17, 2014 7:55 pm

Please explain your solution in this application.

I appreciate your help, because this led me to solve the case in my way.
I am newbie in this area, and I am open to see the professional solutions.

Thanks.

User avatar
majenko
Site Admin
Posts: 2164
Joined: Wed Nov 09, 2011 7:51 pm
Location: UK
Contact:

Re: Timer interrupt & i2c

Post by majenko » Wed Sep 17, 2014 10:19 pm

pito wrote:A 1usec long interrupt with pic32 would be considered too long (at least by me) :)
Things aren't that cut and dried any more, Pito. When you had one, or maybe two, interrupts and many things get routed to it, then yes keeping your interrupts very short is essential.

Nowadays, with multiple interrupt priorities (7 in the case of the PIC32) it's not as critical to ensure that every interrupt is very short. As long as you get your interrupt priorities right. High priority interrupts should be kept very short, but low priority ones, as in the case of that timer interrupt, can be much longer, as other interrupts aren't blocked by it and can still execute at the right time. The lower priority interrupts become a sort of second thread to your process.

So I have nothing at all against how that interrupt is being used.
Why not visit my shop? http://majenko.co.uk/catalog
Universal IDE: http://uecide.org
"I was trying to find out if it was possible to only eat one Jaffa Cake. I had to abandon the experiment because I ran out of Jaffa Cakes".

tcsaba101
Posts: 92
Joined: Mon Dec 30, 2013 5:05 am
Location: Hungary

Re: Timer interrupt & i2c

Post by tcsaba101 » Thu Jul 02, 2015 9:59 am

I got back to this topic, because I had problem on i2c and timer irq conflict.

The SD Card data logger worked well for many hours as inteded and planned using the earlier info in this topic.
There were two i2c devices used: INA226, RTC DS3231.
I used as it was described earlier: timer on irq priority level 2, and we know i2c is on level 3.

Code: Select all

Timer4 timer(2);
then initialized:

Code: Select all

			timer.setFrequency(PRESCALE);
			timer.attachInterrupt(isr);
			timer.start();
The logger is reading and buffering the INA226 current and voltage sensor data within the timer interrupt task:

Code: Select all

void __attribute__((interrupt)) isr() 
{

	reg3 = ina.readBusVoltage();   //read the i2c sensor
	reg4 = ina.readShuntCurrent();

	//calculation and storing in the buffer code here
	
	clearIntFlag(_TIMER_4_IRQ);
}
the execution time of the irq task was max 2.5 ms, and the timer has 10ms interval.

I have had a paralel 20x4 lcd, now I had to change to i2c lcd due to the fact I need more io pins.
After installing the i2c LCD the system was freezing on the display operation.
If the irq has been disabled, the display was working.

In the earlier version the RTC was read every sec once, there was no other action on the i2c bus outside the irq task.
In the new version the LCD refresh time is 100ms, plus the RTC reading as was before.
I have to admit, there were sometimes veird characters on the display on the time displaying area, so it has problems also in the earlier version but was not freezing the system.

When I arranged the timer interrupt only to set a flag, and polling the flag in the main loop(), executing the same code when the flag set, then it started to work.

I suppose two i2c cycles are overlaping somehow. Or what is the answer?


In my case the polling solution doesn't make any functional problem, because I am reading averaged value from the INA226, and the PIC32 makes short loop().
So a little sampling jitter probaly makes no or minor difference.

But in applications where the sampling is critical you have to act immediately within the irq task.
What could be the solution if you have multiple i2c devices?

Otherwise in this case I don't need the timer interrupt anymore, a micros() polling would be simpler solution.

Post Reply