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

RP2040 I2C Target mode doubles first byte sent in Reply #4331

Closed
sparques opened this issue Jul 9, 2024 · 4 comments
Closed

RP2040 I2C Target mode doubles first byte sent in Reply #4331

sparques opened this issue Jul 9, 2024 · 4 comments
Labels
bug Something isn't working rp2040 RP2040 (Pi Pico, RP2040 Feather, etc)

Comments

@sparques
Copy link
Contributor

sparques commented Jul 9, 2024

I'm using a Raspberry Pi Pico as an I2C Target. The host is a Raspberry Pi Zero 2W.

After one transfer, the first byte is always doubled up.

On the Host (Pi Zero):

/home/sparques # i2cget -y 1 0x2c 0x20 i 4 # read from i2c bus 1; device id 0x2c; register 0x20, block of data 4 bytes long
0x1e 0x1e 0x00 0x00

I have the Pico dumping output on USB serial. It shows the below, which is correct.

host request against 0x20
Sending to host: [ 1e 0 0 1 18 0 1 0 2 0 b 0 3 0 0 0 4 0 5 0 ad de ef be 1 0 0 0 0 0] 

And here is a trace with a logic analyzer. The pico sends the first byte; pauses, then sends the first byte again along with the rest.

image

Any idea's what's going on here? I based my code on https://github.com/tinygo-org/tinygo/tree/release/src/examples/i2c-target

It seems like something's wrong with the Pico's I2C peripheral (or how it's configued, or how it's called, etc), or the code implementing Reply().

I have an http-like router for handling I2C r/w requests to registers. I've tried to pare down the code to just what's relevant. This seems to work fine but 🤷🏽 if it's related to the issue I'm seeing.

type Router struct {
	bus      I2C
	addr     uint16
	register uint16
	maxTx    int

	regMap map[uint16]Handler

	Finish func()
}

// called in main(); HIDDescRegister = 0x20; 
router.Handle(HIDDescRegister, func([]byte) (rx []byte) {
	return HIDDesc // this is just a []byte = 1e 0 0 1 18 0 1 0 2 0 b 0 3 0 0 0 4 0 5 0 ad de ef be 1 0 0 0 0 0
})

// the meat of Router
func (r *Router) Handle(register uint16, f Handler) {
	r.regMap[register] = f
	if f == nil {
		delete(r.regMap, register)
	}
}

func (r *Router) route(register uint16, buf []byte) {
	handler, ok := r.regMap[register]
	if !ok {
		// HID spec says if a request is made to a non-supported register, we should reply with zero-byte response
		// not sure if I understood this part of the spec right or if this is good for other I2C protocols
		r.bus.Reply([]byte{0, 0})
		return
	}
	rep := handler(buf)
	if rep != nil {
		fmt.Println("Sending to host:", asHex(rep), "\r")
		r.bus.Reply(rep)
	}
}
func (r *Router) Serve() {
	buf := make([]byte, r.maxTx)

	err := r.bus.Listen(r.addr)
	if err != nil {
		panic("could not listen on i2c")
	}

	for {
		buf = buf[:r.maxTx]
		evt, n, err := r.bus.WaitForEvent(buf)
		if err != nil {
			panic(err)
		}
		buf = buf[:n]

		fmt.Println("Host sent", asHex(buf), "\r")

		switch evt {
		case machine.I2CReceive:
			// Host is sending us data
			if n == 0 {
				fmt.Println("Host wrote... nothing?\r")
				continue
			}
			if n == 1 {
				r.register = uint16(buf[0])
				continue
			}

			if n >= 2 {
				r.register = binary.LittleEndian.Uint16(buf[0:2])
			}

			if n > 2 {
				r.route(r.register, buf[2:])
			}
		case machine.I2CRequest:
			// host is requesting data, hand off to HID state machine
			fmt.Printf("host request against 0x%x\r\n", r.register)
			r.route(r.register, nil)

		case machine.I2CFinish:
			r.Finish()

		default:
		}
	}
}

@sparques
Copy link
Contributor Author

sparques commented Jul 9, 2024

Upon further testing, it does appear to be something with my code; a much simplified i2c target works as expected. Will test a bit more and then close bug if that's the case.

Works as expected:

package main

import (
	"fmt"
	"machine"
	"time"
)

func main() {
	time.Sleep(time.Second)
	fmt.Println("started test...\r")

	machine.I2C0.Configure(machine.I2CConfig{
		SDA:  machine.GPIO0,
		SCL:  machine.GPIO1,
		Mode: machine.I2CModeTarget,
	})

	err := machine.I2C0.Listen(0x2c)
	if err != nil {
		panic(err)
	}

	for {
		register := 0
		buf := make([]byte, 64)
		evt, n, err := machine.I2C0.WaitForEvent(buf)
		if err != nil {
			panic(err)
		}

		switch evt {
		case machine.I2CReceive:
			if n == 1 {
				register = int(buf[0])
				fmt.Println("Set register", register, "\r")
				continue
			}
		case machine.I2CRequest:
			machine.I2C0.Reply([]byte{1, 2, 3, 4})
		}
	}
}

@sparques
Copy link
Contributor Author

sparques commented Jul 9, 2024

Ah ha. If the reply is small, like 4 bytes in the above example, the doubling of the first byte problem doesn't show up. But a 10 byte reply does reproduce the issue. From reading the rp2040 datasheet and looking over the tinygo i2c code for the rp2040, I can't see why this would make a difference. The way *I2C.Reply() is written, it doesn't seem like it should matter how many bytes are sent back.

@sparques
Copy link
Contributor Author

Well after much fiddling around, I tried rewriting the Reply() method in machine_rp2040_i2c.go

func (i2c *I2C) Reply(buf []byte) error {
	txPtr := 0	
	stat := i2c.Bus.IC_RAW_INTR_STAT.Get()

	if stat&rp.I2C0_IC_INTR_MASK_M_RD_REQ == 0 {
		return ErrI2CWrongMode
	}
	i2c.Bus.IC_CLR_RD_REQ.Get() // clear restart

	// Clear any dangling TX abort
	if stat&rp.I2C0_IC_INTR_MASK_M_TX_ABRT != 0 {
		i2c.Bus.IC_CLR_TX_ABRT.Get()
	}

	for txPtr < len(buf) {
		
		if i2c.Bus.GetIC_RAW_INTR_STAT_TX_EMPTY() != 0 {
			i2c.Bus.SetIC_DATA_CMD_DAT(uint32(buf[txPtr]))
			txPtr++
			for i2c.Bus.GetIC_RAW_INTR_STAT_TX_EMPTY() == 0 {
			} 
		}
			 
		// This Tx abort is a normal case - we're sending more
		// data than controller wants to receive
		if i2c.Bus.GetIC_RAW_INTR_STAT_TX_ABRT() != 0 {
			i2c.Bus.SetIC_CLR_TX_ABRT_CLR_TX_ABRT(0)
			return nil
		}

		gosched()

	}

	return nil
}

And now I2C works as expected. Not sure what I did differently. I'm doing some further testing and if it seems good, I'll submit a PR for the fix.

@deadprogram deadprogram added bug Something isn't working rp2040 RP2040 (Pi Pico, RP2040 Feather, etc) labels Jul 14, 2024
@deadprogram deadprogram added the next-release Will be part of next release label Jul 21, 2024
@deadprogram
Copy link
Member

Reopening, will close on next release.

@deadprogram deadprogram reopened this Jul 21, 2024
@deadprogram deadprogram removed the next-release Will be part of next release label Aug 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working rp2040 RP2040 (Pi Pico, RP2040 Feather, etc)
Projects
None yet
Development

No branches or pull requests

2 participants