PDA

View Full Version : Teensy 3 Wire Lib Error



Lotus Pack
12-25-2012, 12:47 PM
Being under the impression that the Wire.h include would work ok on the Teensy 3. I put the following test i2c slave device into a Teensy 2 to check I have all the right libraries etc. Works fine on the T2 but got this compiling error on selecting T3 as the Board.

slave_sender.cpp.o: In function `setup':
C:\Users\Paul\Documents\PIC Programming\arduino-1.0.2/slave_sender.ino:18: undefined reference to `TwoWire:: onRequest(void (*)())'
collect2: ld returned 1 exit status


#include <Wire.h>

void setup()
{
Wire.begin();
Wire.onRequest(requestEvent);
}

void loop()
{
delay(100);
}

void requestEvent()
{
Wire.write("hello ");
}

:(
Any ideas?

Lotus Pack
12-29-2012, 12:04 PM
Ye I have replied to m own questio. Can anyone give it a ty for me please so I can either chnage my config or confirm it is the lib?

el_supremo
12-29-2012, 04:14 PM
onRequest isn't defined in the Teensy Beta9 Wire library.

Pete

Lotus Pack
12-29-2012, 04:59 PM
OK Thanks Pete.

PaulStoffregen
12-30-2012, 01:27 AM
I started working on this yesterday....

PaulStoffregen
12-30-2012, 09:23 AM
Warning: Early Alpha-quality code. Feedback needed...

http://www.pjrc.com/teensy/beta/Wire_slave_mode_30dec12.zip

I've implemented onRequest(). It might even work sometimes! There are probably subtle (or not so subtle) differences in how this handles shorter or longer than expected messages, and maybe other issues. I did get it to work with the Wire library examples and minor variations, but it's otherwise has very little testing.

So far, I have not managed to implement onReceive(). If you look in Wire.cpp, the interrupt routine is able to receive the bytes and put them into the buffer. But I haven't figured out how to get an interrupt after the last byte. Freescale doesn't seem to have an interrupt for the I2C stop condition. Maybe there's a way that I've just missed over and over today?

Anyway, here's the code. If you try this, please set your expectations for early alpha-quality code. I could really use some feedback about what works and what doesn't? And if anyone has an idea about how to detect the stop condition to make the onReceive callback, please speak up! I've verified it does receive properly into the buffer, so all that's needed is a way to make the callback after the last byte.

PaulStoffregen
01-03-2013, 01:28 AM
Has anyone tried this new code? Does it work for you?

ploveday
01-03-2013, 07:58 AM
Has anyone tried this new code? Does it work for you?

Well, I've just been trying to get this going without much luck.

It is quite possible I'm doing something wrong, though.

Hardware wise, I've wired up SCL/SDA between two Teensy3s, with 1.5k to 3.3v on each line.

Software is basically:

Master:
Wire.begin();
Wire.RequestFrom(1,1);

Slave:
Wire.begin(1);
Wire.onRequest(requestEvent);

...

void requestEvent()
{
Wire.write(1);
}

But the master is locking. As far as I can tell I'm not getting the IAAS interrupt on the slave. But it's quite possible I've broken something.

- Peter

ploveday
01-04-2013, 10:23 AM
Aha!


It works *MUCH* better if the SDA/SCL pins are set to open drain (PORT_PCR_ODE) in TwoWire::begin(). I also removed the pullup enable, though judging from the comment that's irrelevant.

OnRequest() is now working as expected for me, at least in simple tests so far.

Now to have a look at OnReceive() handling...

- Peter

ploveday
01-05-2013, 05:12 AM
So far, I have not managed to implement onReceive(). If you look in Wire.cpp, the interrupt routine is able to receive the bytes and put them into the buffer. But I haven't figured out how to get an interrupt after the last byte. Freescale doesn't seem to have an interrupt for the I2C stop condition. Maybe there's a way that I've just missed over and over today?


Well, unfortunately my findings mirror yours. I cannot find a way to detect stop from the ISR. I am seeing the IAAS interrupt, then subsequent interrupts for each data byte; but the final byte shows nothing special - not that I would expect it to, because it is presumably pre-ack, or during ack, and the stop won't have occurred yet. No interrupt occurs following the final data byte, and I've not been able to find any way to force it to, or to wait for TCF/BUSY or anything.

It is possible to poll BUSY from somewhere in loop, checking receive buffer status etc to see if a slave receive transfer has finished, but this is far more of a kludge than real solution. Any example code I can find for the K20 I2C in fact relies on the slave receive knowing the expected length, or handling it as a stream of bytes rather than a specific length receive.

While I think this is a pretty poor design on freescale's part, if indeed there is no way to detect STOP... I would also consider the Wire library to be pretty poorly thought out in this regard (why I'd have expected differently, I don't know, given the generally poor design of the other Arduino APIs). A better, more orthogonal approach might be to implement it much as a stream/FIFO as the Serial libraries do.

Nonetheless, we're stuck with what we have as far as Arduino goes... so what to do... we could poll BUSY during loop(), or poll BUSY on a timer, or implement a time-out on the transfer (assuming all bytes will be transmitted together, regardless of STOP being asserted). Obviously another IAAS would indicate a new transfer and should terminate any previous one because otherwise we could miss the STOP/START transition if it's a polled system. Any idea if we can get an edge interrupt on a pin that's MUXed for I2C (rather than GPIO), such that we could manually detect the stop condition between bytes that way?

All a bit of a hack really. A anyone have any other ideas?

- Peter

PaulStoffregen
01-05-2013, 11:49 AM
Yup, my thoughts exactly. I considered starting one of the interval timers to automatically poll the busy status. It's a real shame Freescale didn't provide an interrupt for that busy bit.

That's a good idea on the pin interrupts. I recall reading somewhere they work even if the mux isn't selected to GPIO mode. I'll try using a rising edge detect on the SDA pin. Each successive non-zero data byte will give an unnecessary interrupt, but compared with the disadvantages of polling from loop() or consuming a hardware timer, it seems like probably the least-bad approach.

Thanks for looking at this is such detail. I spend several hours reading that datasheet and trying random things, thinking there just has to be a way to detect the stop bit... that I must have just been looking right at it over and over but not seeing it. I'm still in a bit of disbelief that Freescale's hardware doesn't have this capability.

ploveday
01-05-2013, 12:11 PM
I briefly tried a rising edge on SDA Interrupt with little success, but it's all a bit trial an error at this level. Obviously one cannot use attachinterrupt, as it reconfigures the pin to gpio mux, so you have to do it direct in PORTB isr.

My thought was to enable the interrupt mode in portb 3 icr when the byte is received, but before writing to i2s_c1, so during the ack clock stretch. This way we're only getting a single interrupt per byte, rather than potentially every second bit. Detecting restart is also necessary, and a little more annoying than stop.

it really is one of those "surely there must be a way...' things :-/

- peter.

PaulStoffregen
01-16-2013, 06:32 PM
I implemented onReceive(). Here's the latest code.

http://www.pjrc.com/teensy/beta/Wire_slave_mode_16jan13.zip

Please give this a try and let me know if it works for you?

ploveday
01-16-2013, 10:49 PM
Awesome, that's working very nicely indeed.
Looks similar to what I was trying, not sure what I did wrong with the edge interrupt now.

Thanks,
- Peter

PaulStoffregen
01-17-2013, 12:56 AM
Glad it's working.

One small complication I ran into was a false triggering at the beginning of the ACK bit. I'm not sure if that's what you saw, but it was a bit tricky. Sometimes there would be a small delay from when the master releases the SDA line to when Teensy 3.0 begins the ACK bit. The I2C interrupt occurs before that time, so the first rising edge is sometimes that brief moment where SDA can rise enough to trigger the rising edge interrupt. Fortunately, I was viewing the waveforms on a good oscilloscope the while working, so it was easy to see (when it happens... not every time).

Lotus Pack
01-17-2013, 06:45 AM
Glad it's working.

One small complication I ran into was a false triggering at the beginning of the ACK bit. I'm not sure if that's what you saw, but it was a bit tricky. Sometimes there would be a small delay from when the master releases the SDA line to when Teensy 3.0 begins the ACK bit. The I2C interrupt occurs before that time, so the first rising edge is sometimes that brief moment where SDA can rise enough to trigger the rising edge interrupt. Fortunately, I was viewing the waveforms on a good oscilloscope the while working, so it was easy to see (when it happens... not every time).

I have tried the new lib and it compiles ok :D, not tried onRequest yet, but I have a missing peice of information. The T2 code uses pins 5 & 6 for SCL and SDA what pins are used on the T3? The test code below works but obviously needs these pins reasigned to get the i2c bus connected to the mem chips.

#include <LiquidCrystal.h>
#include <Wire.h>
// RS E D4 D5 D6 D7
LiquidCrystal lcd(2, 3, 4, 7, 8, 9);
const byte byte_size=256;

long chip_mem=0; //256000/8; // bytes in memory chip 256 kbit /8 =32k
const byte word_size=2; // stored value max 65534 at each address

byte mem_chips=3; // number of chips on th c2i bus max 8
const byte chip_base_address=80; // chip manufactuer c2i address
unsigned long total_address=0;
byte next_chip=0;

void setup()
{
Wire.begin(); // join i2c bus
//Wire.onRequest(receiveEvent); // register event not used yet
//Wire.onReceive(receiveEvent);
lcd.begin(20,4);
analogWrite(10,80);
pinMode(11,OUTPUT);
pinMode(5,OUTPUT);
pinMode(6,OUTPUT);
chip_mem=256000/8;
total_address=(chip_mem/word_size)*mem_chips;
lcd.setCursor(0,0);
lcd.print(total_address);
lcd.print(" Word Data");
delay(5000);
lcd.setCursor(0,0);
lcd.print(total_address*2);
lcd.print(" Bytes");
delay(5000);
}

int timer=0;
unsigned int x=0;
unsigned int c=0;

void loop()
{
unsigned long p=0;
lcd.clear();
timer=millis();
for (long m=0; m<total_address; m++)
{
switch (putmem(m,p))
{
case 0:
lcd.setCursor(0,0);
lcd.print(m);
lcd.print("<No Write ");
lcd.setCursor(0,1);
lcd.print(p);
lcd.print("<Same ");
break;
case 1:
lcd.setCursor(0,0);
lcd.print(m);
lcd.print("<Write ");
lcd.setCursor(0,1);
lcd.print(p);
lcd.print("<Value ");
break;
case 2:
lcd.setCursor(0,0);
lcd.print(m);
lcd.print(" Addrs error ");
break;
case 3:
lcd.setCursor(0,0);
lcd.print(m);
lcd.print(" Memory error ");
break;
}
p=p+2;
if(p>65000) p=0;
}

lcd.print((millis()-timer)/1000);

delay(4000);
lcd.clear();
for (long m=0; m<total_address; m++) //for (int m=0; m<=total_address; m++)
{
c=getmem(m);
lcd.setCursor(0,2);
lcd.print(m);
//lcd.print(" <Read Addrs ");
lcd.setCursor(0,3);
lcd.print(c);
}
delay(5000);
x++;
}

byte putmem(unsigned long address, unsigned int value)
{
int ret_flag=0;
int time_out=3000;
int tocount=0;
const unsigned int_max=65536;
unsigned long address1=address;
next_chip=int(address/(chip_mem/word_size));
if (getmem(address)!=value)
{
address=address-(next_chip*(chip_mem/word_size));
address=address*word_size;
if (address % word_size==0) // must be a logical address position
{
lcd.setCursor(19,0);
lcd.print(next_chip);

byte hib=int(address/256);
byte lob=(address-int(hib*256));
byte bca=chip_base_address+next_chip;
Wire.beginTransmission(bca);
Wire.send(hib); // address high byte
Wire.send(lob); // address low byte
hib=(value/256);
Wire.send(hib); // Data High (MSByte)
Wire.send(int(value-(hib*256))); // Data Low (LSByte)
Wire.endTransmission();
ret_flag=1;
}
else ret_flag=2;
if(value>0 && value<int_max)
while(getmem(address1)!=value){if(tocount++>time_out) {ret_flag=3;break;}}
// else delay(8);
}
return ret_flag;
}

unsigned int getmem(unsigned long address)
{
next_chip=int(address/(chip_mem/word_size));
address=address-(next_chip*(chip_mem/word_size));
address=(address*word_size);
unsigned int value[]={0,0};
unsigned int ret_val=0;
byte x=0;
byte lob=(address-int((address/byte_size)*256));
byte hib=int(address/256);
byte bca=chip_base_address+next_chip;
Wire.beginTransmission(bca);
Wire.send(hib); // address high byte
Wire.send(lob); // address low byte
Wire.endTransmission();
Wire.requestFrom(bca,word_size);
while(Wire.available()) // collect full value
value[x++] = Wire.read(); // receive byte
ret_val=int(value[0]*256) + value[1];
return ret_val;
}

ploveday
01-17-2013, 07:59 AM
The standard pins for I2C on a teensy3 are 18 (SDA) and 19 (SCL). Wire sets these up appropriately for you, so you don't need to change any pin config.

The reference card that came with your teensy is a good source of this kind of info.

- Peter

Lotus Pack
01-17-2013, 08:04 PM
Thanks Peter, I forgot about the reference card!

Lotus Pack
01-17-2013, 08:27 PM
Rewired the i2c bus removed the pin config added two more chips to the array and all working fine on 5x 24LC256 memory chips I now have 190k of storage (80k 2 byte word with the above code). Going to use this to share config data between three T3s.