RP2040 DMA I2C?
Is there any script/functions to use RP2040 DMA to read the I2C bus? Like how this script reads the SPI using DMA?
This isn't something I've tried doing myself, but https://github.com/raspberrypi/pico-sdk/releases/tag/1.3.0 says "Added i2c_get_dreq() function to facilitate configuring DMA transfers to/from an I2C instance." (added in https://github.com/raspberrypi/pico-sdk/pull/603 ), so I guess in theory adding DMA to I2C shouldn't be that different from adding DMA to SPI? :shrug: The best place to ask for help might be https://forums.raspberrypi.com/viewforum.php?f=143
Trying to figure it out right now. I'm only able to use the SPI-DMA example as a reference to an extent as i need to somehow provide the I2C DMA instance from an address on the I2C bus to read from, which isnt the case for SPI.
i need to somehow provide the I2C DMA instance from an address on the I2C bus to read from
With the current I2C SDK functions, it looks like i2c_write_blocking_internal https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/hardware_i2c/i2c.c#L135 and i2c_read_blocking_internal https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/hardware_i2c/i2c.c#L260 are the only functions that set the device-address, and if you're using DMA I guess you probably don't want to use the blocking functions!
So I guess for now you might need to study Section 4.3.15 of https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf and bypass the SDK wrapper-functions and write directly to the I2C registers https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2040/hardware_structs/include/hardware/structs/i2c.h ?
@kilograham Is this a "gap" in the hardware_i2c part of the SDK?
Another alternative would be to use the existing SDK functions to read the I2C bus in a blocking manner, but running on the second core?
Another alternative would be to use the existing SDK functions to read the I2C bus in a blocking manner, but running on the second core?
That is a possible way. But I'm trying to use both cores to do other tasks which require data from a sensor on the I2C bus. Hence, I thought DMA would solve this problem.
i need to somehow provide the I2C DMA instance from an address on the I2C bus to read from
With the current I2C SDK functions, it looks like
i2c_write_blocking_internalhttps://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/hardware_i2c/i2c.c#L135 andi2c_read_blocking_internalhttps://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/hardware_i2c/i2c.c#L260 are the only functions that set the device-address, and if you're using DMA I guess you probably don't want to use theblockingfunctions! So I guess for now you might need to study Section 4.3.15 of https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf and bypass the SDK wrapper-functions and write directly to the I2C registers https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2040/hardware_structs/include/hardware/structs/i2c.h ?@kilograham Is this a "gap" in the
hardware_i2cpart of the SDK?Another alternative would be to use the existing SDK functions to read the I2C bus in a blocking manner, but running on the second core?
There is i2c_set_slave_mode() which absolutely lets you set the address. You need to configure the I2C before you do the DMA which just transfers data.
I got the impression that @nsk126 was trying to use the RP2040 as the I2C master, reading data from an external I2C sensor? :shrug:
@lurch Yes, I am. @kilograham I was using i2c_set_slave_mode(), but still getting it wrong somewhere.
int rx_1 = dma_claim_unused_channel(true);
uint8_t addr = 0x68;
uint8_t reg = 0x75;
uint8_t rx_buff[1];
i2c_set_slave_mode(i2c_default,false,addr);
dma_channel_config c = dma_channel_get_default_config(rx_1);
channel_config_set_transfer_data_size(&c, DMA_SIZE_8);
channel_config_set_read_increment(&c, false);
channel_config_set_write_increment(&c, true);
channel_config_set_dreq(&c, i2c_get_dreq(i2c_default, false));
dma_channel_configure(rx_1, &c,
rx_buff, // read address
®, // write address
1, // element count (each element is of size transfer_data_size)
true); // don't start yet
I assumed setting the slave arg in i2c_set_slave_mode() false should make it master mode.
i2c_set_slave_mode() https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/hardware_i2c/i2c.c#L114 only seems to set the RP2040 slave-address register (i2c->hw->sar) when slave-mode is true, it doesn't set the external-device-address register (i2c->hw->tar) when slave-mode is false. Which agrees with what it says at https://raspberrypi.github.io/pico-sdk-doxygen/group__hardware__i2c.html#ga42571b2db5d1ed0dc083385c230dc9e8
I tried to set the i2c->hw->tar register manually. And instead tried to read from the IC_DATA_CMD register. I set the data size to DMA_SIZE_16 since the data_cmd register is also 16 bits(hope this is the correct way). Not sure what's wrong but I'm getting a 0x18 reading, instead of a 0x68.
For context, I'm trying to read the 0x75 register of the MPU-6050(WHOAMI register) to test the DMA transfers. It should have an 8bit value 0x68.
int rx_1 = dma_claim_unused_channel(true);
uint8_t addr = 0x68;
uint8_t reg = 0x75;
uint16_t rx_buff[1];
i2c_set_slave_mode(i2c_default,false,addr);
dma_channel_config c = dma_channel_get_default_config(rx_1);
channel_config_set_transfer_data_size(&c, DMA_SIZE_16);
channel_config_set_read_increment(&c, false);
channel_config_set_write_increment(&c, true);
channel_config_set_dreq(&c, i2c_get_dreq(i2c_default, false));
i2c0->hw->enable = 0;
i2c0->hw->tar = reg;
i2c0->hw->enable = 1;
dma_channel_configure(rx_1, &c,
rx_buff, // read address
&i2c_default->hw->data_cmd, // write address
1, // element count (each element is of size transfer_data_size)
true); // don't start yet
while (1)
{
uint8_t reg8 = rx_buff[0] >> 8;
printf("0x%x\r\n",reg8);
sleep_ms(500);
}
dma_channel_unclaim(rx_1);
From my limited understanding, I think i2c0->hw->tar = reg; should be i2c0->hw->tar = addr; ?
Shouldn't i2c0->hw->sar be the slave address and i2c0->hw->tar be the target register address to read/write from? That is what I understand from tar and sar.
Nonetheless, I've tried changing i2c0->hw->tar = reg and even swapping addr and reg. Nothing has changed, reg8 still contains a value 0x18.
My understanding is largely from looking at the hardware_i2c/i2c.c file that I mentioned earlier. You can see that i2c0->hw->sar is only used inside i2c_set_slave_mode when slave is true, and i2c0->hw->tar is used inside i2c_write_blocking_internal and i2c_read_blocking_internal (which are used when the RP2040 is being used as the !2C master).
https://github.com/raspberrypi/pico-examples/blob/master/i2c/mpu6050_i2c/mpu6050_i2c.c shows how to talk to the MPU-6050 using the "normal" I2C SDK functions, so my assumption (which could be wrong) is that you need to replace i2c_write_blocking (which is what sends the reg value?) with DMA writes, and replace the i2c_read_blocking with DMA reads?
However this is about as far as my I2C knowledge goes, so you may need to ask on the forums for further help.
Thanks for the tip. I didn't see the source code for i2c_set_slave_mode.
you need to replace i2c_write_blocking (which is what sends the reg value?) with DMA writes, and replace the i2c_read_blocking with DMA reads?
I tried working on this today. Used 2 channels, one to read and one to write to the target register.
int rx_1 = dma_claim_unused_channel(true);
int rx_2 = dma_claim_unused_channel(true);
uint8_t addr = 0x68;
uint8_t reg = 0x75;
uint16_t rx_buff[1];
// i2c_set_slave_mode(i2c_default,false,reg);
dma_channel_config c = dma_channel_get_default_config(rx_1);
channel_config_set_transfer_data_size(&c, DMA_SIZE_8);
channel_config_set_read_increment(&c, true);
channel_config_set_write_increment(&c, false);
channel_config_set_dreq(&c, i2c_get_dreq(i2c_default, true));
i2c_default->hw->enable = 0;
i2c_default->hw->tar = addr;
i2c_default->hw->enable = 1;
dma_channel_configure(rx_1, &c,
&i2c_default->hw->data_cmd,
®,
1,
false);
c = dma_channel_get_default_config(rx_2);
channel_config_set_transfer_data_size(&c, DMA_SIZE_16);
channel_config_set_read_increment(&c, false);
channel_config_set_write_increment(&c, true);
channel_config_set_dreq(&c, i2c_get_dreq(i2c_default, false));
i2c_default->hw->enable = 0;
i2c_default->hw->tar = addr;
i2c_default->hw->enable = 1;
dma_channel_configure(rx_2, &c,
rx_buff,
&i2c_default->hw->data_cmd,
1,
false);
dma_start_channel_mask((1u << rx_1) | (1u << rx_2));
dma_channel_unclaim(rx_1);
dma_channel_unclaim(rx_2);
while (1)
{
uint8_t reg8 = rx_buff[0] >> 8;
printf("0x%x\r\n",reg8);
sleep_ms(500);
}
But I'm not sure if I'm setting the dma_channel_configure(rx_1, &c,&i2c_default->hw->data_cmd,®,1,false); correctly.
Not sure where to move from here. I don't think starting the channels at once would be an issue either. I tried asking at https://forums.raspberrypi.com/ but no one responded.
I've not written any DMA code myself, so can't really offer any guidance, but one thing that immediately jumps out at me is that you're unclaiming the DMA channels before your while loop, which doesn't seem right. https://raspberrypi.github.io/pico-sdk-doxygen/group__hardware__dma.html#gac50200739b88a2fd52316f4150533035
EDIT: Also, there's probably no need to set i2c_default->hw->tar twice?
I stumbled into this today as well: how to read from i2c with dma.
Using the pico as a master and have it write to another i2c device with DMA is pretty straight forward: inflate the byte buffer you want to send to uint16 and set the appropriate control bits (like STOP or RESTART) on the first/last byte and then just DMA-write them to the i2c-command register. Works.
Having the Pico read from another device seems much harder. If you read i2c_read_blocking_internal() carefully and compare with the documentation, you'll see that for every byte you want to read you will have to write a control-command as well. So you'd have to ping-pong bounce between two 1-byte DMA transfers all the time: one writes the control command, the other reads the data, rince repeat. No straight forward bulk read afaict.
[...] for every byte you want to read you will have to write a control-command as well. So you'd have to ping-pong bounce between two 1-byte DMA transfers all the time: one writes the control command, the other reads the data, rince repeat. No straight forward bulk read afaict.
https://github.com/raspberrypi/pico-examples/blob/master/dma/control_blocks/control_blocks.c should be a good example for how to wire this up. Haven't tried it yet myself...
You just need to use two DMA channels in parallel (you don't need to ping pong). see https://github.com/kilograham/rp2040-doom/blob/c77136a24837a41fe1561778d4563d22679850fb/src/pico/piconet.c#L535
You just need to use two DMA channels in parallel (you don't need to ping pong). see https://github.com/kilograham/rp2040-doom/blob/c77136a24837a41fe1561778d4563d22679850fb/src/pico/piconet.c#L535
hm... for some reason i thought we'd have to coordinate access to i2c's data_cmd register. Synchronise via the DMA controller. But I guess there already is inherent sync: the write channel puts read-more-data control words into data_cmd, paced by DREQ_I2C0_TX; and the read channel tries to read as fast as DREQ_I2C0_RX allows; and concurrent read/write is coordinated by the bus and allows only one at a time.
It's likely the wrong place to ask here, but if at all possible could this be updated in the datasheet of the rp2040? The datasheet says nothing about any of this as far as I can see. The way the i2c and the DMA interact is fundamentally a hardware thing and having at least some guidance on how to read write with DMA sounds quite useful
Sounds like a reasonable idea. Can you open an issue on raspberrypi/pico-feedback and link it back to this issue?