Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add I2C support for esp32 #4259

Merged
merged 2 commits into from May 13, 2024
Merged

Add I2C support for esp32 #4259

merged 2 commits into from May 13, 2024

Conversation

jfreymuth
Copy link
Contributor

Mostly copied from machine_esp32c3_i2c.go, but there are some differences, especially with timeout handling (see line 165).

Tested on an M5Paper, where I could successfully communicate with multiple devices and recover from errors in a long-running program.

}

func (p Pin) pinReg() *volatile.Register32 {
return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.GPIO.PIN0)) + uintptr(p)*4)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not also use unsafe.Add() here in similar fashion to the implementation of nextAddress() function just below?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function is copied from machine_esp32c3.go#L114, which uses uintptr addition (but it seems to be an outlier in that file too). For consistency this function should probably be moved to machine_esp32.go?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency this function should probably be moved to machine_esp32.go

Agreed, but that can happen in a separate PR.

return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(reg), 4))
}

// CheckDevice does an empty I2C transaction at the specified address.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like a cool function, but I am just wondering if it really belongs in this PR? Perhaps it can either be added as something available to all i2c implementations, which as least right now it is not. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option would be to modify Tx so that Tx(addr, nil, nil) returns an error if there is no device with that address. This seems to be how other microcontroller libraries do it, e.g. Arduino:

WIRE.beginTransmission(address);
error = WIRE.endTransmission();

That behavior also matches the method comment better, which implies that the address is always written:

// It clocks out the given address, writes the bytes in w, reads back len(r)
// bytes and stores them in r, and generates a stop condition on the bus.

(currently the method just does nothing if both slices are empty)

Ideally that would be reflected in the generic i2c documentation and other implementations as well, which – I agree – is not part of this PR

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally that would be reflected in the generic i2c documentation and other implementations as well, which – I agree – is not part of this PR

That makes sense to me! 😄

@deadprogram
Copy link
Member

This is really great @jfreymuth thank you very much for working on it. Please see my comments inline.

@deadprogram
Copy link
Member

Oh yeah, one thing I almost forgot is we need to also add a smoke test for i2c on esp32.

@aykevl
Copy link
Member

aykevl commented May 13, 2024

Mostly copied from machine_esp32c3_i2c.go, but there are some differences, especially with timeout handling (see line 165).

That's going to increase the maintenance burden. Can you refactor the code so that most/all common code is shared and only the chip-specific things are in the appropriate chip specific files?
For example:

  • machine_esp32xx_i2c.go for the esp32 and esp32c3 with most of the I2C code
  • machine_esp32c3_i2c.go contains the esp32c3 specific bits (for example, constants or functions called from machine_esp32xx_i2c.go)
  • same for machine_esp32_i2c.go

This is also important because I expect other chips in the ESP32 family to be very similar, they can share code in a similar way.

@aykevl
Copy link
Member

aykevl commented May 13, 2024

We have something similar for the nrf chips:

@jfreymuth
Copy link
Contributor Author

I can try, but I don't have any esp32c3 devices, so I'm not sure how to verify I'm not breaking anything with the refactor

@deadprogram
Copy link
Member

deadprogram commented May 13, 2024

Perhaps we can merge this PR (with a smoketest added, please) and then refactor the i2c implementation as described by @aykevl in a separate PR. I have both boards and am willing to help out with this.

@aykevl
Copy link
Member

aykevl commented May 13, 2024

Sounds good to me!
(I'm just hoping we won't forget this...)

I'm not sure about the smoketest though: what would be gained from that? We already have a smoketest for the esp32, so any syntax or type errors would already be caught.

@deadprogram
Copy link
Member

I'm not sure about the smoketest though: what would be gained from that? We already have a smoketest for the esp32, so any syntax or type errors would already be caught.

Just to ensure that the i2c parts compile for the esp32 build tag.

@aykevl
Copy link
Member

aykevl commented May 13, 2024

In that case I'd suggest adding the ESP32 here:

//go:build atmega || nrf || sam || stm32 || fe310 || k210 || rp2040 || mimxrt1062 || (esp32c3 && !m5stamp_c3)

We already have plenty of smoke tests that show the machine package (including I2C) compiles for the ESP32:

tinygo/GNUmakefile

Lines 751 to 764 in 6384eca

ifneq ($(XTENSA), 0)
$(TINYGO) build -size short -o test.bin -target=esp32-mini32 examples/blinky1
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=nodemcu examples/blinky1
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target m5stack-core2 examples/serial
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target m5stack examples/serial
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target m5stick-c examples/serial
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target mch2022 examples/serial
@$(MD5SUM) test.bin
endif

@deadprogram
Copy link
Member

In that case I'd suggest adding the ESP32 here:

https://github.com/tinygo-org/tinygo/pull/4259/files#diff-0fff984543b29e2e510fb57be78ac8464fc10ba2d494ba94fa7b686e275b3ee0R1

That is in fact in this very PR already! :) In which case I think we can merge now.

@deadprogram
Copy link
Member

Now going to squash/merge. Thank you @jfreymuth for working on this and to @aykevl for review.

@deadprogram deadprogram merged commit 8890b57 into tinygo-org:dev May 13, 2024
16 checks passed
// timeout in microseconds.
const timeout = 40 // 40ms is a reasonable time for a real-time system.

cmd := make([]i2cCommand, 0, 8)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be added as a field to I2C to not risk heap allocation

 cmdbuf [8]i2cCommand

// timeout in microseconds.
const timeout = 40 // 40ms is a reasonable time for a real-time system.

cmd := []i2cCommand{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, we could store this memory in I2C.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants