VL53L0X and AVR32DA32 - Page 1
Forum section: electronics / general

author: hpmaxim
author_rank: Regular Contributor
timestamp: May 08, 2025, 03:04:13 pm
content: I'm trying to connect a VL53L0X to an AVR32DA32 and am having a ridiculous number of problems, probably of my own makings. The board is custom designed by me and was manufactured at JLCPCB. I put 20k pull-up resistors on the SDA and SCL lines. Other than that, it was pretty straightforward. I am progrmaming it with a UPDI Friend, the board also has a linear LDO and an LED. My objective is to turn a distance to a voltage out using the built in DAC. This is pretty different from the other AVRs I've used, but I'm not sure that has anything to do with it.I'm using the following code snippet:void i2c_init() {PORTMUX.TWIROUTEA = 2;// Enable pull-ups on PD1 (SDA) and PD0 (SCL)PORTC.DIRCLR = PIN2_bm | PIN3_bm;PORTC.PIN2CTRL |= PORT_PULLUPEN_bm;PORTC.PIN3CTRL |= PORT_PULLUPEN_bm;TWI0.MCTRLA = 0;TWI0.MBAUD = TWI_BAUD(F_CPU, SCL_FREQ); // Set baud rateTWI0.MCTRLA = TWI_ENABLE_bm; // Enable TWI masterTWI0.MSTATUS = TWI_BUSSTATE_IDLE_gc; // Force idle state}uint8_t i2c_start(uint8_t addr) {TWI0.MADDR = addr;while (!(TWI0.MSTATUS & (TWI_WIF_bm | TWI_RIF_bm | TWI_ARBLOST_bm | TWI_BUSERR_bm))); // Wait for transmit or errorif (TWI0.MSTATUS & (TWI_ARBLOST_bm | TWI_BUSERR_bm)) return 0;return !(TWI0.MSTATUS & TWI_RXACK_bm); // Check for ACK}void i2c_stop(void) {TWI0.MCTRLB = TWI_MCMD_STOP_gc;}uint8_t i2c_write(uint8_t data) {TWI0.MDATA = data;while (!(TWI0.MSTATUS & TWI_WIF_bm));return !(TWI0.MSTATUS & TWI_RXACK_bm); // ACK received}uint8_t i2c_read_nack(void) {TWI0.MCTRLB = TWI_ACKACT_bm | TWI_MCMD_RECVTRANS_gc; // Send NACKwhile (!(TWI0.MSTATUS & TWI_RIF_bm));return TWI0.MDATA;}void main (void) {i2c_init(); // Only run once at the beginningi2c_start(0x29 << 1); // write modei2c_write(0xC0); // This is the ID bytei2c_start((0x29 << 1) | 1); // read mode (NOTE: THIS SAID | 0, but was an error I've corrected it)response = i2c_read_nack(); // Should be 0xee,i2c_stop();}This is essentially a simple test to read out data out of a register. However, it hangs up on the while loop in i2c_read_nack() and never lets me out.I noticed I did something stupid and wired the SDA to PD1, and SCL to PD0. This doesn't seem to be valid, but I was able to short SDA to PC2 using a wire-wrap wire, and SCL to PC3 with a solder bridge which according to the datasheet is correct. I also tried bit banging the pins before hacking board with similar results.Looking at a scope trace, I see both SDA and SCL swinging rail to rail (the rail is about 3.1V), looking fairly normal, but the read "ends" wth SCL stuck low, and SDA stuck high after a low-going pulse. I was running it at 100kHz, but I tried slowing it down to 20kHz with no change.The GPIO pin is pulled up by the AVR. The XSHUT pin is driven high by the AVR. Pins C2 and C3 have a pull-up turned on in addition to the 20k resistor. I tried with and without PD1/PD0 tied high since they are shorted.I'm not sure what else to try at this point. Any suggestions?

author: cv007
author_rank: Frequent Contributor
timestamp: May 08, 2025, 09:34:04 pm
content: Code:i2c_start((0x29 << 1) | 0); // read modeThat leaves bit 0 as 0, which is a write. I would also check the return value of start() since it doesn't make sense to continue the transaction if the address was nack'd. You also will need to be aware that a read (start) results in a RIF if the address was ack'd, and you will have skipped any check for the address ack and already have your first read byte when RIF is set.I have some twi c code for avr0/1 (twi0 only), which with some changes to twiPins.h and pmux.h for this mcu should work, or at least get some ideas from reading the code-https://github.com/cv007/Avr01Dx_Twithere is also a c++ version in the repository where the same code would work for either twi0/twi1.These both use interrupts, but can also poll with the same code. The example in main.c sets up a master/slave pair using the same pins, where the communications between master/slave is on the same mcu. The intention was to get both master/slave in use on the same mcu as a way to test the basic workings of twi. If no way to probe/watch the transactions then the blinking led will at least indicate the communications are working.

author: octillion
author_rank: Contributor
timestamp: May 09, 2025, 03:20:35 am
content: Just a sanity check, is 0x29 the correct address? The datasheet here says it is 0x52https://www.st.com/resource/en/datasheet/vl53l0x.pdfAnd as cv007 states, you should be appending a 1 to the LSB for read mode, not 0.

author: cv007
author_rank: Frequent Contributor
timestamp: May 09, 2025, 07:45:48 am
content: QuoteSome of these manufacturers seem to think a 7bit i2c address field includes the rw bit, so they describe their gizmo as having 2 addresses. In this case they say the address is 0x52, then later buried in the datasheet they also say by the way 0x53 is the address for a read. It doesn't take long to realize that you better find out what these datasheets actually mean when it gives an address, as many a manufacturer seem to think they need to include the rw bit when giving out the address. The actual 7bit address is 0x29, and resides in the 7bit address field. The rw bit describes a direction, not an address.

author: hpmaxim
author_rank: Regular Contributor
timestamp: May 09, 2025, 03:59:05 pm
content: Thanks so much for the reply, cv007.I think I should point out a few things here:1) I just modified a second board to move it from PD0/1 to PC2/3. The behavior was identical to the first.2) I think you are absolutely right about the i2c_start ((0x29<<1)|1). Unfortunately, and not unexpectedly, it made no difference. The reason I said not unexpectedly is because this is test code (that I may have improperly modified) that ChatGPT suggested. I only went there after the regular code (which likely didnt have the mistake) didn't work.3) It also suggested a scanner to look for devices:for (uint8_t addr = 0; addr < 127; addr++) {if (i2c_start((addr << 1) | I2C_WRITE)) {i2c_stop();// Blink LED or write to DAC to indicate found addressif (addr==0x29) DAC0.DATA = 512<<6; else DC0.DATA=768<<6;_delay_ms(500);}}This code did detect the 0x29 and only the 0x29 which is as it should have been. I presume this means i2c_star()t and i2c_stop() work.4) FWIW, I'm not that familiar with this AVR, and it feels like I have to relearn everyhing everytime I do it. I am using the VL53L0X _mod library which isn't designed for this AVR. Essentially, there are a number of i2c_* methods in the library which are used to communicate with the VL53L0X -- none of them work with this AVR (which I selected because of the DAC) -- but they are very simple and straightforward. I put the code into ChatGPT and asked it to translate to the AVR I'm using. So I'm at the mercy of ChatGPT which seems to frequently make mistakes (for example at one point it told me I could use the PD pins and then it decided I couldn't). It also made other mistakes. It's very possible there is another mistake in the code I'm not aware of. However, there isn't a lot here. Ill take a look through your code and see if I can figure it out. I don't really need it to be interrupt driven since all I am doing is periodically reading data from the VL53L0X, performing some quick calculations which essentially amount to:If (measurement<min) meaurement=min; elseif (measurement>max) measurement=max;code = measurement * m + b;DAC0.DATA=(code&0x3fff)<<6;Not really much need to do interrupts except to initiate the read.Octillian, as cv007 pointed out I'm pretty sure the address is 0x29, that is what responds to the scanner. 0x52 = 0x29 << 1, so I'm pretty sure if it's defined as a 7-bit value the correct number is 0x29. That said I think it's stupid that I2C defines it this wasy rather than 0x52-0x53 which avoids the need to have this discussion about how the address is actually (0x29<<1) and (0x29)<<1+1

author: hpmaxim
author_rank: Regular Contributor
timestamp: May 09, 2025, 04:44:14 pm
content: Thinking about this more, the code is more altered than I originally thought. I'm not a fan of the Arduino framework, partially because it seems very ineffecient and silly, but also because I was having trouble getting it working in VSCode. But I've abandonded VSCode/PlatformIO because I couldn't get it to work with AVR32DA32 and am now using Arduino IDE. Maybe using the original library will actually work, but the relevant code is what was put in the original post (I corrected the error on the LSB of the address for read).I apologize for he messiness of all the code, I kind of hacked it together for posting here.For the sake of completeness here's the read_ack function:uint8_t i2c_read_ack(void) {TWI0.MCTRLB = TWI_MCMD_RECVTRANS_gc;while (!(TWI0.MSTATUS & TWI_RIF_bm));return TWI0.MDATA;}I

author: octillion
author_rank: Contributor
timestamp: May 09, 2025, 06:37:00 pm
content: For an "official" example of using I2C, check outhttps://github.com/microchip-pic-avr-examples/avr64dd32-getting-started-with-i2c-mplabx/tree/masterI do, however, think there is a bug in the way they force the bus into the idle state inhttps://github.com/microchip-pic-avr-examples/avr64dd32-getting-started-with-i2c-mplabx/blob/master/avr64dd32-cnano-i2c-send-receive-mplabx.X/i2c.c, using the|=operator to apply theTWI_BUSSTATE_IDLE_gcgroup configuration enum toTWI0.MSTATUS.In other words, they do (in several places):TWI0.MSTATUS |= TWI_BUSSTATE_IDLE_gcbut should instead do:TWI0.MSTATUS = TWI_BUSSTATE_IDLE_gcFor this specific register, writing zeros to the flags (all other bits in the register) have no effect, so the group configuration can be directly applied with=.

author: hpmaxim
author_rank: Regular Contributor
timestamp: May 10, 2025, 05:14:38 am
content: I got a scope on both with i2c decode. It shows:Addr: 0x29 (Write)Ack: 0Data: 0xCEAck: 0PauseAddr: 0x29 (Read)Ack: 0Data: 0x01Ack: 1Couple things I noted that struck me as odd:Clock pulse width is probably about 22us, about 50% duty cycle.10-90 rise time is approximately 2us10-90 fall time is approximately 250nsData changes on the falling edge of the clock.Comments:1) I would have guessed rise time would be on the order of 1us, while the 20k ohm resistor on the board is a lot, it's cut down by multiple enabled pull ups. I was assuming about a 50pF load. Maybe resistance is around 8k, and capacitance is much higher than I thought. That would explain why fall time is only 8-10x faster than rise, because I'd assume the pull-down is on the order of 100 ohms.2) 22us is significantly longer than I was expecting. I was expecting it to be on the order of 5 us, not 22.3) Every waveform diagram I've seen shows data transitioning in the middle of the low of the clock, not at the falling edge. If you sample on the rising edge of the clock, this is probably a non-issue. If you sample on the falling edge of the clock this would be a disaster.I'm inclined to try manually implementing my own bit banger to see if adjusting when the SDA transition makes a difference, and to affirmatively determine who is pulling SCL low at the end.

author: hpmaxim
author_rank: Regular Contributor
timestamp: May 10, 2025, 06:01:17 am
content: Welp, I think I fgured out the problem. Left image is from the VL53L0X datasheet. Right image is the footprint I got from SnapEDA, which I used. Anyone see a problem here?

author: krho
author_rank: Regular Contributor
timestamp: May 10, 2025, 06:55:17 am
content: Why the hell they didn't provide top view. Now I have to figure out how to properly flip this thing.

author: hpmaxim
author_rank: Regular Contributor
timestamp: May 10, 2025, 07:35:03 am
content: Ok, now I'm utterly confused. These two pictures are from later in the manual.I presume the PWB Solder Pattern is what I should be targeting. This seems to show Pad #1 in the lower right. We can then run clockwise7 8 9 10 116 125 4 3 2 1If you rotate that layout counter clockwise you get the pad configuration I used. So perhaps it is right afterall?

author: cv007
author_rank: Frequent Contributor
timestamp: May 10, 2025, 11:13:47 am
content: If you are getting acks for the address, you are communicating so its hard to imagine you have a footprint problem. You do appear to have XSHUT in use and would also assume you have other code that deals with XSHUT (I assume otherwise no address ack if in shutdown).

author: hpmaxim
author_rank: Regular Contributor
timestamp: May 10, 2025, 04:25:16 pm
content: Quote from: cv007 on May 10, 2025, 11:13:47 amI agree. At this point, I think I can say a few things with certainty:1) When I switch to bit banging the clock staying low problem stops. This seems pretty conclusive something is wrong on the AVR side. Unsurprisingly, the clock is way cleaner when I use the hardware interface vs bit banging. Oddly, it seems to operate at half the frequency it should be (i.e. selecting a 100kHz SCL yields 10us pulse widths). However, the i2c bus decode on my scope says the same thing regardless of bit banging or hardware.2) When I send in 0x52, 0xc0, 0x53, I get back a result of 0x01. This is wrong. However, the VL53L0X acknowledges each of my transmissions. If I change the address to 0x50 (i.e i2c=0x28) it does not acknowledge the transmissions, and I read back a 0xff, which indicates the VL53L0X is, correctly, ignoring me. I also tried the original sequence that yielded the 0x01 with XSHUT tied low, and the VL53L0X, correctly, ignores me.So what's the bottom line: It looks like it likely is hooked up correctly, and there appear to be issues on both the VL53L0X and the AVR which are unrelated. I can work around the AVR issues by bitbanging with somewhat worse, but usable results (it also allows me to use the goofed up wiring of PD0/PD1) -- I cannot work around the VL53L0X at this point unless something else is going on.It sure looks like, according to this:https://github.com/pololu/vl53l0x-arduino/tree/masterIn their example they do:void setup(){Serial.begin(9600);Wire.begin();sensor.setTimeout(500);if (!sensor.init()){Serial.println("Failed to detect and initialize sensor!");while (1) {}}sensor.startContinuous();}Here, sensor.init() is:bool VL53L0X::init(bool io_2v8){// check model ID register (value specified in datasheet)if (readReg(IDENTIFICATION_MODEL_ID) != 0xEE) { return false; }Where, IDENTIFICATION_MODEL_ID is defined as:IDENTIFICATION_MODEL_ID = 0xC0,FWIW, I had another setup where I bought a cheap-o VL53L0X board from AliExpress and wired it to a Arduino Nano. I believe I was able to talk to it and get through that, although I didn't test it nearly as thoroughly.Is it possible that the VL53L0X needs to be programmed somehow, and that hasn't be done, or is it possible JLCPCB is using couterfeit parts?For giggles, I tried reading out the entire sequence recommended in the datasheet (0xc0, 0xc1, 0xc2, 0x51, 0x61), it read out as :1, 0, 0, 0, 0 in that order.I then tried changing the order, so that c0 wasn't read out first. The other addresses were still 0, but c0 changed to a different value (0x10, I think). So I tried reading out 0xc0 repeatedly. It seemed to produce a random sequence of values, never giving me the expected 0xee, and then eventually settled on reading out all 0's.I also figured maybe it needs a clear reset, so I brought XSHUT low for a few hundred ms, then brought it high, and waited a few hundred ms before proceeding with reading the registers. It made no difference.

author: hpmaxim
author_rank: Regular Contributor
timestamp: May 10, 2025, 05:16:27 pm
content: Ugh... Okay, I'm an idiot. It was indeed a "counterfeit" VL53L0X -- sometimes referred to as a VL53L3CX. Pin compatible, but with a completely different register map. Switching to checking 0x10f for 0xea, it works like a charm when bit banging. The AVR is still not releasing the SCL at the end of the read though.

author: cv007
author_rank: Frequent Contributor
timestamp: May 12, 2025, 02:54:47 am
content: QuoteThe avr will always leave scl low/stretched when its waiting for the user to do something. Your read function is doing a command first, which is not the correct sequence. The command comes after the read- using either an ack+read or nack+stop. The ack/nack is for the preceding data, and the command is to either clock in another byte or produce a stop.here is the sequence for a read after a start read was done-readwait for RIF or WIF (could get a WIF if address was nack'd)if status bits == (RIF|CLKHOLD|OWNER), then we have a byte to read (and if first byte after start, address was also ack'd)read mdataif want more data, mctrlb=2 (ack+read), else mctrlb=7 (nack+stop)else (status bits are any other combo)error (or address was nack'd after start byte)mctrlb=7 (nack+stop)