This blog begins with a description of the solution, and record my discovery process and the structure of the Pico I2C code ZhongUncle explored.
First let me explain: Pico has two I2C, namely two sets of SDA and SCL. You can see this from the names in the pin diagram. For example, Pin 4 and Pin 5 in below are for I2C1
, while the default Pin 6 and Pin 7 are for I2C0
.
By default, only the first I2C (I2C0
) is enabled. It means, even if you modify the pin, but not in I2C0
, it still doesn’t work.
Declare three variables or macros to facilitate development. It is recommended to use macros, which is more fit the style of Raspberry Pi:
#define I2C i2c0
#define I2C_SDA_PIN 4
#define I2C_SCL_PIN 5
If macro goes wrong, use variables.
Initializing I2C is setting which I2C controller, SDA and SCL pins to use. According to the above setting, the first I2C controller I2C0
is used here, SDA uses GP4
, SCL uses GP5
, and the frequency is 1000000
:
i2c_init(I2C, 1000000);
gpio_set_function(I2C_SDA_PIN, GPIO_FUNC_I2C);
gpio_set_function(I2C_SCL_PIN, GPIO_FUNC_I2C);
gpio_pull_up(I2C_SDA_PIN);
gpio_pull_up(I2C_SCL_PIN);
Since there are two I2C controllers, you can use two sets of SDA
and SCL
pins at the same time, but they must be the pins of I2C0
and I2C1
, not the same controller.
This part is not necessary to read. Here is a record of how I knew it was handled this way. I also learned about the structure and data transmission process, so that I don’t have to spend time looking through it if I need it in the future.
First, I analyze the problem: To define a pin, I need to know how the value of the pin is used. And I will know how to transfer and process this value.
Generally, during initialization, you set which I2C controller to use and the SDA and SCL pins. The code is generally as follows:
i2c_init(i2c_default, CLK);
gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C);
gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C);
gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN);
gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN);
When I wrote Modify the default SDA and SCL pins of I2C using C/C++, I knew default pin set in pico.h
. There are 3 variables about it, PICO_DEFAULT_I2C
,PICO_DEFAULT_I2C_SDA_PIN
and PICO_DEFAULT_I2C_SCL_PIN
, so just tracing them can find the answer.
But it’s hard to find, there are too many references. So I tried to start from the other side: I2C is initialized through the i2c_init()
function, as follows:
i2c_init(i2c_default, SSD1306_I2C_CLK);
I just need first parameter i2c_default
, because this parameter passes some information. The second parameter uint baudrate
is to pass the rate and has nothing about how pin work.
So what is the content of i2c_init()
? Only knowing this, we can know what type returned from i2c_default
and what processing is done internally.
i2c_init()
is declared in pico-sdk/src/rp2_common/hardware_i2c/i2c.c
. The declaration of function is:
uint i2c_init(i2c_inst_t *i2c, uint baudrate) {
i2c_reset(i2c);
i2c_unreset(i2c);
i2c->restart_on_next = false;
i2c->hw->enable = 0;
...
// Re-sets i2c->hw->enable upon returning:
return i2c_set_baudrate(i2c, baudrate);
}
So what type is i2c_inst_t
? I keep looking for it.
In line 52 of pico-sdk/src/rp2_common/hardware_i2c/include/hardware/i2c.h
, you can see i2c_inst_t
is typedef
of i2c_inst
structure:
typedef struct i2c_inst i2c_inst_t;
Keeping looking for i2c_inst
, this structure in line 135 at the same file:
struct i2c_inst {
i2c_hw_t *hw;
bool restart_on_next;
};
The end is variable i2c_hw_t *hw
looked for at beginning, because only it may pass the value of the pin, so keep looking for what data returned from i2c_hw_t
.
The declaration of this type is in pico-sdk/src/rp2040/hardware_structs/include/hardware/structs/i2c.h
. In other words, this file is for i2c_hw_t
structure:
This structure stores a lot of I2C information, but I still couldn’t find the pin information, so I went back to the beginning to search.
At first, I looked for the type i2c_inst_t
of the first parameter of i2c_init()
, but I didn’t find enough. But I haven’t found its value yet, so this time I start from the parameter value i2c_default
. Where is this value defined?
The required things were found in pico-sdk/src/rp2_common/hardware_i2c/include/hardware/i2c.h
where i2c_inst_t
declaration and definition are in:
#ifdef PICO_DEFAULT_I2C_INSTANCE
#define i2c_default PICO_DEFAULT_I2C_INSTANCE
#endif
What is PICO_DEFAULT_I2C_INSTANCE
? Look upward:
#if !defined(PICO_DEFAULT_I2C_INSTANCE) && defined(PICO_DEFAULT_I2C)
#define PICO_DEFAULT_I2C_INSTANCE (__CONCAT(i2c,PICO_DEFAULT_I2C))
#endif
Finally, I see a required value: PICO_DEFAULT_I2C
. As before we know, this defaults to 0
.
(__CONCAT(i2c,PICO_DEFAULT_I2C))
conjuncts the values of i2c
and PICO_DEFAULT_I2C
, which is i2c0
by default. In other words, the parameter i2c_default
is i2c0
.
This skill is very good, but some compilers cannot use it. For example, when I use Clang x86_64-apple-darwin21.6.0, I cannot extend
PICO_DEFAULT_I2C
.
But what is i2c0
here? What type is it?
This is a part of code in the pico-sdk/src/rp2_common/hardware_i2c/include/hardware/i2c.h
(as shown above):
#define i2c0 (&i2c0_inst) ///< Identifier for I2C HW Block 0
#define i2c1 (&i2c1_inst) ///< Identifier for I2C HW Block 1
You can see i2c0
is the address of i2c0_inst
, and the comment says this is identifier for I2C HW Block 0
.
extern i2c_inst_t i2c0_inst;
extern i2c_inst_t i2c1_inst;
From before, we can see i2c0_inst
and i2c1_inst
are external variables, and their type is i2c_inst_t
. I have seen the structure defined before:
struct i2c_inst {
i2c_hw_t *hw;
bool restart_on_next;
};
Where is i2c0_inst
declared?
It is in pico-sdk/src/rp2_common/hardware_i2c/i2c.c
:
i2c_inst_t i2c0_inst = {i2c0_hw, false};
i2c_inst_t i2c1_inst = {i2c1_hw, false};
What is i2c0_hw
? Where is it declare?
It is in pico-sdk/src/rp2040/hardware_structs/include/hardware/structs/i2c.h
:
#define i2c0_hw ((i2c_hw_t *)I2C0_BASE)
#define i2c1_hw ((i2c_hw_t *)I2C1_BASE)
i2c0_hw
represents ((i2c_hw_t *)I2C0_BASE)
, means I2C0_BASE
is a pointer to i2c_hw_t
, declared in pico-sdk/src/rp2040/hardware_regs/include/hardware/regs/addressmap.h
:
#define I2C0_BASE _u(0x40044000)
#define I2C1_BASE _u(0x40048000)
In other words, I2C0_BASE
is 0x40044000
, and the address of i2c0_hw
is 0x40044000
.
In addition, _()
here means unsigned integer, which is defined in pico-sdk/src/rp2040/hardware_regs/include/hardware/platform_defs
:
#ifndef _u
#ifdef __ASSEMBLER__
#define _u(x) x
#else
#define _u(x) x ## u
#endif
#endif
I hope these will help someone in need~