Curious MT3333 / TWI behaviour

I have been struggling with interfacing a Nano via TWI to MediaTek MT3333 GPS. Does this ring a bell with anyone? I am having the same problem with Nano Every.

Having visited various public libraries I want to examine what's going on at the lower level, TWI interface. I am using the Wire library.

When I call Wire.requestFrame() it always succeeds. Always says it can give me the number of characters requested provided that's not more than its own 32 byte block buffer. However, if it hasn't got anything meaningful to give me, it fills the rest with line feed characters. Is there any way I can poke it to stop it doing this? I have looked inside other libraries & haven't spotted anything.

Incidentally, I put the call to GPS_Ping in because I saw another library doing this on start up, but didn't originally. It makes no difference.

Here's my test program code:-

// Wire Test 1. Written by Greg Walker 30-Aug-2023

// Program to demonstrate low level TWI behaviour with MediaTek MT3333 GPS module
// As a consequence of difficulties I have encountered driving this using several public domain
// GPS libraries I have written this utility to examine what's going on at the "Wire" API.Wire
// To my surprise, Wire.requestFrom() always succeeds, reporting the requested number of bytes
// even when the GPS hasn't put anything new into the buffer.  When it (the GPS) has nothing 
// useful to say, it fills the buffer with <line feed> characters ('\n'). By observation a
// a meaningful sentence is never preceded by any of these filler characters although, of course,
// the last character of a sentence that the GPS is genuinely reporting is a \n, and it's entirely
// possible that this can appear as the first character of a requestFrame() block.

#include <Wire.h>

const byte TWI_INPUT_BUFFER = 32;
const long MONITOR_BAUDRATE = 19200;
const byte GPS_ID = 0x10;

void setup() 
{
  Serial.begin(MONITOR_BAUDRATE);
  delay(10);
  Serial.println("Wire Test 1");
  Wire.begin();
  GPS_Ping();
}

///////////////////////////////////////////////////////////////////////////////////////////////////

void loop() 
{
  char TWIBUF[TWI_INPUT_BUFFER];
  byte bDelivered = NULL;
  byte bOneChar;

  //bDelivered = Wire.requestFrom((int)GPS_ID, (int)TWI_INPUT_BUFFER, (int)true);
  bDelivered = Wire.requestFrom((int)GPS_ID, (int)TWI_INPUT_BUFFER );

  Serial.println(bDelivered);
  do
  {
    while(Wire.available())
    {
        bOneChar = Wire.read();
        Serial.print(bOneChar);
        Serial.print('|');
    }
    Serial.println();
    delay(100);
  } while(Wire.requestFrom((int)GPS_ID, (int)TWI_INPUT_BUFFER, (int)true));
  Serial.println(bDelivered);
  for(;;);
}

///////////////////////////////////////////////////////////////////////////////////////////////////

bool GPS_Ping()
{
  bool bReply = false;
  Wire.beginTransmission(GPS_ID);
  Wire.write(NULL);
  if(Wire.requestFrom((int)GPS_ID,(int)1)) bReply = true;
  return bReply;
}

///////////////////////////////////////////////////////////////////////////////////////////////////

And here's the output I get. I have deleted a lot of monotonous line feed reports (substituting a blank line). Before running this I have already configured the GPS to report an NMEA RMC sentence every 10 seconds & a GGA sentence every fifth RMC.

Wire Test 1
32
71|78|82|77|67|44|49|52|48|54|53|49|46|48|48|48|44|65|44|53|48|53|49|46|49|55|52|57|57|51|44|78|
44|48|48|49|48|48|46|52|57|55|55|55|50|44|87|44|48|46|51|53|44|50|54|49|46|53|48|44|51|48|48|56|
50|51|44|44|44|65|42|54|48|13|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|

10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|
36|71|78|82|77|67|44|49|52|48|55|48|49|46|48|48|48|44|65|44|53|48|53|49|46|49|55|53|50|55|49|44|
78|44|48|48|49|48|48|46|52|57|54|56|51|57|44|87|44|48|46|51|56|44|51|48|46|57|53|44|51|48|48|56|
50|51|44|44|44|65|42|53|49|13|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|
10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|

10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|
36|71|78|71|71|65|44|49|52|48|55|49|49|46|48|48|48|44|53|48|53|49|46|49|55|52|56|56|56|44|78|44|
48|48|49|48|48|46|52|57|54|55|50|54|44|87|44|49|44|56|44|49|46|49|56|44|50|54|46|50|53|57|44|77|
44|52|55|46|51|52|48|44|77|44|44|42|54|69**|13|10|**36|71|78|82|77|67|44|49|52|48|55|49|49|46|48|48|
48|44|65|44|53|48|53|49|46|49|55|52|56|56|56|44|78|44|48|48|49|48|48|46|52|57|54|55|50|54|44|87|
44|48|46|48|57|44|49|57|52|46|52|49|44|51|48|48|56|50|51|44|44|44|65|42|54|56|13|10|10|10|10|10|

10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|
36|71|78|82|77|67|44|49|52|48|55|50|49|46|48|48|48|44|65|44|53|48|53|49|46|49|55|52|53|55|53|44|
78|44|48|48|49|48|48|46|52|57|54|53|57|55|44|87|44|49|46|50|49|44|50|49|53|46|49|54|44|51|48|48|
56|50|51|44|44|44|65|42|54|70|13|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|

10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|
36|71|78|82|77|67|44|49|52|48|55|51|49|46|48|48|48|44|65|44|53|48|53|49|46|49|55|51|55|55|51|44|
78|44|48|48|49|48|48|46|52|57|55|50|52|57|44|87|44|48|46|51|53|44|50|49|53|46|52|53|44|51|48|48|
56|50|51|44|44|44|65|42|54|65|13|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|

10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|
36|71|78|82|77|67|44|49|52|48|55|52|49|46|48|48|48|44|65|44|53|48|53|49|46|49|55|51|50|57|49|44|
78|44|48|48|49|48|48|46|52|57|55|50|50|55|44|87|44|48|46|49|57|44|50|52|52|46|49|49|44|51|48|48|
56|50|51|44|44|44|65|42|54|55|13|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|

10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|
36|71|78|82|77|67|44|49|52|48|55|53|49|46|48|48|48|44|65|44|53|48|53|49|46|49|55|50|54|54|54|44|
78|44|48|48|49|48|48|46|52|57|55|51|57|48|44|87|44|48|46|53|52|44|49|57|52|46|54|56|44|51|48|48|
56|50|51|44|44|44|65|42|54|70|13|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|

10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|
36|71|78|71|71|65|44|49|52|48|56|48|49|46|48|48|48|44|53|48|53|49|46|49|55|50|48|52|55|44|78|44|
48|48|49|48|48|46|52|57|55|49|50|56|44|87|44|49|44|57|44|49|46|49|53|44|50|52|46|51|57|57|44|77|
44|52|55|46|51|52|48|44|77|44|44|42|54|55|13|10|36|71|78|82|77|67|44|49|52|48|56|48|49|46|48|48|
48|44|65|44|53|48|53|49|46|49|55|50|48|52|55|44|78|44|48|48|49|48|48|46|52|57|55|49|50|56|44|87|
44|48|46|49|48|44|49|57|52|46|54|56|44|51|48|48|56|50|51|44|44|44|65|42|54|49|13|10|10|10|10|10|

10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|
36|71|78|82|77|67|44|49|52|48|56|49|49|46|48|48|48|44|65|44|53|48|53|49|46|49|55|50|54|53|50|44|
78|44|48|48|49|48|48|46|52|57|54|55|54|49|44|87|44|48|46|54|53|44|50|52|55|46|52|53|44|51|48|48|
56|50|51|44|44|44|65|42|54|56|13|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|
10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|10|

Thanks

You can ignore the data :wink:

if(bOneChar != 0x0D && bOneChar != 0x0A)
{
  Serial.print(bOneChar);
  Serial.print('|');
} 

Indeed, I can. But I can do that forever.

When I call Wire.requestFrame() it always succeeds. Always says it can give me the number of characters requested provided that's not more than its own 32 byte block buffer.

I put the "delay(100)" into the loop to throttle the output.

My alternative explanation of the functions of the Wire library might be helpful.

You should not mix the functions for reading and writing.

The GPS_Ping function has no effect. I can remove the call & the behaviour is identical.

Actually, I copied that code fragment from the Sparkfun library because that was all I could see that might be a fix.

Without that, i can keep requesting 32 bytes and getting them. Since posting my message I have seen different spurious characters appearing. Not just \n.

I think the key issue in the main loop is that with nothing else going on, the following code never completes:-

while (Wire.requestFrame())
{
  while (Wire.available())
  {
     Wire.read();
  }
}

Without interacting with Wire between Wire.begin() and the above code, execution never proceeds through to the the end. This is not what I expect because the GPS is only feeding data into the buffer at a hundred or so characters per second. I expect the client code to exhaust the buffer & for requestFrame() to advise zero, or false, telling the client there's nothing left.

I have read your reference, which I had not seen before. I don't believe I have been in conflict with that. Incidentally, you refer to the method Wire.requestFrom(). I believe that's a typo. Do you agree? I think that should be requestFrame() unless the code I have is different.

Correction ... yes, I can see the code I posted has a beginTransmission that isn't matched by an endTransmission. I realise that is bad practice. However, if GPS_Ping isn't called that issue disappears. I have tried adding the endTransmission immediately after the write - to no effect.

My gut feeling is there is something I can send to the GPS that will stop it sending me endless line feeds, but can't for the life of me think what that can be. When I stimulate other TWI devices, they don't do that. When I use the Adafruit or Sparkfun wrapper, it doesn't seem to happen. What am I missing?

If the Master does a Wire.requestFrom() and the Slave is on the bus, then the Slave acknowledges to its address. So far so good.
But after that, the Slave can do nothing, but provide data or not. The Master will read all the data bytes, and will never know if the Slave actually has that data.

So if you do "Wire.requestFrom(slave_address, 32);" then you will always get 32 bytes. The Slave can have 0 bytes or 1000 bytes, it does not matter.

That is how the I2C protocol works. It is not a serial bus like the UART communication.
The I2C bus uses packages of data. For example reading 6 bytes with information from a sensor.

1 Like

Apologies for labouring the point, but you refer to requestFrom(). The function name I have is requestFrame(). Are we talking about the same entity?

I am looking at how to figure out when to stop digging for data & move on to do something else.

I thought I had spotted a situation where GPS was delivering something other than 0x0A when it hadn’t got anything for me, but cannot reproduce that. If I am wrong. If the way of signalling out of data is 0x0A, then finding a 0x0A in the data stream doesn’t crack it. But, thinking about it, two consecutive 0x0As does! As far as parsing the inbound messages, we don’t need the 0x0As. We can discard them, and we can discard the 0x0Ds (return) too. Once I find two consecutive 0x0As in the data stream I can deduce there are no further messages waiting in the buffer, and I can go away and do something else.

Curious that when I have looked at other TWI devices, i thought I was getting requestFrame to give me the answer zero. It also begs the question “why do I bother figuring out how many bytes requestFrame has found. Once I know that at design time, I will also get the same answer every time. And what does the true/false third parameter for the requestFrame do for me? How does behaviour change depending upon how I set it?

No matter. I now have what I see as a work around that allows me to move on. Throw away the 0x0As and 0x0Ds. When I find two consecutive 0x0As, stop digging & move on.

I don't know what requestFrame() is. Where does it come from ?
According to google, you are the only person in the world that uses "Wire.requestFrame" here in this topic.

My error. Yes, my history does show I have been using requestFrom()

The Master should know how much data should be requested before doing a Wire.requestFrom().

It is possible to request more than the Slave has, no one gets hurt. But that is not what the I2C bus is intended for.

@Koepel - you’re watching me going through the learning curve. You have made a big contribution to that. I am catching up and you are making the key points that help me to recognise the error of my ways. Lately I am satisfied that I have been encountering real errors in existing libraries that wrap the programming interface to devices that I am trying to get on top of. It has been quite a surprise to me that mods are needed to established software. The time you have been prepared to commit, and your ability to communicate relevant and succinct information has been exactly what I have needed. Thank you. I do appreciate it.

Can you use the Serial/UART interface ?

I probably can use the serial interface, but I don’t want to. I want to get to grips with the TWI because of other objectives. Having purchased the GPS that is sold as a TWI capable device it doesn’t come easily to simply give up on that because it’s easier to use a different facility. When I walked into this GPS problem, I was surprised how common it was to find people who thought 25% of messages lost was OK. Just figure out which ones are OK and use those. Not the way I do things at all. I knew data transfer to a TWI LCD is rock solid. I figured out it had to be a software problem and am satisfied that has been the case.

The GPS is an interesting device. It can be set up to send lots of data to the microcontroller, but this is not on a handshake type basis. Controller asks for something. Target provides the answer. Basically the controller just has to field a stream of unsolicited messages. I know the GPS has a biggish ( 1K byte) buffer for messages. So the system can cope with quite a large elapsed time between a fix report being prepared by the GPS and it being collected by the host. Conversely, if the controller has processing power to spare, I want to use a fast clock rate to grab the data with minimum latency. A scenario that’s well suited to TWI. OK, SPI is probably better, but I didn’t find an SPI device in my search. With a serial connection it tends to be a bit of a compromise. A data rate matched to aggregate throughput doesn’t really lend itself to latency minimisation, but can capitalise on the big gps buffer. With a fast clock, latency can be minimised when processing power is available, but buffering needs to be put into the controller to cope with the situation where the controller has other demands to attend to. Maybe my detailed knowledge of the serial properties is lacking. After all, hardware and software flow control might get over this. But that involves me in another learning curve about the detail characteristics of the serial intercommunication. But, as I said, I do have another agenda to build my TWI understanding.

Seems to me the basic requirement for a low level applications programming interface should be straightforward. The common theme is a GPS talks and listens “in NMEA sentences ”.

NMEA specifies the basic structure of a compliant sentence is dollar, body, star, checksum, crlf - although a receiver can happily discard the crlf. Maximum sentence is 82 characters. So, for starters the application wants to poll the GPS, asking “what have you got for me”, and to be told - nothing, or here’s a checksum validated message, or, arguably, Well I have received this, but it doesn’t check (probably limited by NMEA length but terminated if new line comes before that). Conversely, because this feed comes at the convenience of the GPS, a callback option would be good.

As for the application talking to the gps - that’s in NMEA compliant sentences. For the convenience of the application, wouldn’t it be nice if it could just form the body of the message & leave it to the wrapper to figure out the checksum and top and tail the dollar, star and crlf, although why not have the wrapper smart enough to accept a fully formed message or just the body, shape it as necessary & send it on. The option of a blocking call or non blocking with flow control by callback or poll would be good.

What’s left? Startup to get things going & validate the connection. That’s pretty much it.

The next level up us parsing the message, for which there’s plenty of established code around, and can be treated as a separate problem.

It is what the SparkFun library basically does. It ignores all 0x0A (cr>).

I could not clearly find what the MT3333 exactly gives you but I suspect that 'no data' is represented by 0x0A; in which case you have to live with that.

Problem is, it doesn’t! It corrupts messages. That’s its main flaw.

I believe you are guessing.

0x0A is line feed. 0x0D is carriage return.

That might be as I don't have your setup.

From the library:

Do I see it wrong?

@Koepel - can I send you a private message? It’s just that I’ve got a little to say to you that I don’t want to put out on the forum for the world to see. All good.