2020-08-14 04:57 AM
Hi,
I am implementing DFU over I2C to an STM32L476, trying to follow AN4221 and AN2606.
For now I am focused on just getting some DFU command to work successfully over I2C, and I started with "Get Version" to get the bootloader version. I am able to get the ACK byte back (0x79), but not the rest of the data.
I first tried to write the command (and the following for the checksum), and then try to receive the 3 bytes that are specified. ACK, $version, ACK. In pseudo code:
beginTransmission(0x43); // slave address, 7bit
write(0x01); // cmd = GetVersion
write(0xFE); // complimentary of cmd, for XOR checksumming
endTransmission(STOP);
data = read(3, STOP);
In this case I get an error indicating that less than 3 bytes was received.
Is it not correct to expect the protocol to be able to work like this?
Since the documentation flowcharts indicates for host to "wait for ACK" (although the device flowchart does not mention it). I also tried to first read 1 byte for the ACK, check it, and then wait for the remaining 2 bytes. In this case I get the ACK byte successfully, but then nothing for. I have tried to omit the STOP condition.
beginTransmission(0x43); // slave address, 7bit
write(0x01); // cmd = GetVersion
write(0xFE); // complimentary of cmd, for XOR checksumming
endTransmission(STOP);
ack = read(1, STOP); // or NOSTOP, no change
if ack == 0x79
data = read(2, STOP);
At this point I am unsure what else to try in terms of protocol changes. Any tips and tricks welcomed.
Is there any reference implementation of this available?
More details:
The device is put into the sytem bootloader using the BOOT0/RESET pins. I always do this, then wait 1 second, before attempt I2C communication. The ACK byte is reliably transmitted, 5/5 times per each test-run.
The host board, which will do the programming, is a Particle P1 (STM32F2xx I think). There are other I2C devices on the bus, like a screen and I/O expander, etc. I have check and there should be no conflict for the address.
The Arduino-style `Wire` I2C library provided by Particle is used for communication (it uses STM32 HAL underneath, implementation can be found here https://github.com/particle-iot/device-os/blob/develop/hal/src/stm32f2xx/i2c_hal.c). This is used successfully with the other pheripherals, is in production for several years now.
I have also tested to program the slave board over I2C using the stm32flash utility from an Embedded Linux machine, and that worked OK.
2020-08-14 06:18 AM
DFU is a USB protocol. You're just using the I2C bootloader.
The response will be 3 bytes. You'll need to read them without generating a stop condition between any of them.
> although the device flowchart does not mention it
The device flowchart definitely shows two ACKs.
I'd examine the lines with a logic analyzer to show what's actually going on.
> data = read(3, STOP);
I'm guessing you need to start communication with the device before you can read data from it.
2020-08-26 10:24 AM
I've been struggling to implement this too and think I finally cracked the code. The AN4221 document isn't very clear on what "wait for ACK or NACK frame" really means. But as you discovered, you can't just read three bytes as AN4221 section 2.2 would suggest. You actually have to do a read (with I2C start and stop conditions) to get the first ACK/NACK byte, a second read to get the data byte, and a third and final read to get the final ACK/NACK byte. I figured this out doing a lot of playing with the Bus Pirate tool; in Bus Pirate commands, it looks like this:
[0x94 0x01 0xfe][0x95 r][0x95 r][0x95 r]
where "[" indicates an I2C start condition, "]" indicates an I2C stop condition, hex numbers specify a byte written, and "r" specifies a byte read. Don't get too confused by the addresses here; Bus Pirate needs you to specify the address left-shifted with the read/write bit, so 0x94 really is a write to 0x4A (the bootloader I2C address in my STM32L452 device) and 0x95 is a read from 0x4A. So to re-write your pseudocode, try this:
beginTransmission(0x43); // slave address, 7bit
write(0x01); // cmd = GetVersion
write(0xFE); // complimentary of cmd, for XOR checksumming
endTransmission(STOP);
ack = read(1, STOP); // or NOSTOP, no change
if ack == 0x79
{
data = read(1, STOP); // Get data byte
ack = read(1, STOP); // Get final ACK/NACK
}
Hopefully this helps, and you'll be able to properly interpret the other command flowcharts. Figuring out how to do something complex like a memory read or write is a bit of a chore, and the documentation is less than clear about the details. It really helps to have a tool that allows you to "play" with the I2C bus readily, and a good scope so you can see the actual bus traffic.
Brian