### i2cSlave.py from machine import mem32 from RP2040_I2C_Registers import* class i2c_slave: """ RP2040 I2C Slave implementation using direct register access. This class implements an I2C slave interface for the RP2040 microcontroller, allowing it to receive and transmit data as an I2C peripheral device. """ I2C0_BASE = 0x40044000 I2C1_BASE = 0x40048000 IO_BANK0_BASE = 0x40014000 # Atomic Register Access mem_rw = 0x0000 # Normal read/write access mem_xor = 0x1000 # XOR on write mem_set = 0x2000 # Bitmask set on write mem_clr = 0x3000 # Bitmask clear on write def get_Bits_Mask(self, bits, register): """ This function return the bit mask based on bit name """ bits_to_clear = bits bit_mask = sum([key for key, value in register.items() if value in bits_to_clear]) return bit_mask def RP2040_Write_32b_i2c_Reg(self, register, data, atr=0): """ Write RP2040 I2C 32bits register """ # < Base Addr > | < Atomic Register Access > | < Register > mem32[self.i2c_base | atr | register] = data def RP2040_Set_32b_i2c_Reg(self, register, data): """ Set bits in RP2040 I2C 32bits register """ # < Base Addr > | 0x2000 | < Register > self.RP2040_Write_32b_i2c_Reg(register, data, atr=self.mem_set) def RP2040_Clear_32b_i2c_Reg(self, register, data): """ Clear bits in RP2040 I2C 32bits register """ # < Base Addr > | 0x3000 | < Register > self.RP2040_Write_32b_i2c_Reg(register, data, atr=self.mem_clr) def RP2040_Read_32b_i2c_Reg(self, offset): """ Read RP2040 I2C 32bits register """ return mem32[self.i2c_base | offset] def RP2040_Get_32b_i2c_Bits(self, offset, bit_mask): return mem32[self.i2c_base | offset] & bit_mask def __init__(self, i2cID=0, sda=0, scl=1, slaveAddress=0x41, enable_clock_stretch=True): self.scl = scl self.sda = sda self.slaveAddress = slaveAddress self.i2c_ID = i2cID if self.i2c_ID == 0: self.i2c_base = self.I2C0_BASE else: self.i2c_base = self.I2C1_BASE """ I2C Slave Mode Intructions https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf """ # 1. Disable the DW_apb_i2c by writing a ‘0’ to IC_ENABLE.ENABLE self.RP2040_Clear_32b_i2c_Reg(I2C_OFFSET["I2C_IC_ENABLE"], self.get_Bits_Mask("ENABLE", I2C_IC_ENABLE)) # 2. Write to the IC_SAR register (bits 9:0) to set the slave address. # This is the address to which the DW_apb_i2c responds. self.RP2040_Clear_32b_i2c_Reg(I2C_OFFSET["I2C_IC_SAR"], self.get_Bits_Mask("IC_SAR", I2C_IC_SAR)) self.RP2040_Set_32b_i2c_Reg(I2C_OFFSET["I2C_IC_SAR"], self.slaveAddress & self.get_Bits_Mask("IC_SAR", I2C_IC_SAR)) # 3. Write to the IC_CON register to specify which type of addressing is supported (7-bit or 10-bit by setting bit 3). # Enable the DW_apb_i2c in slave-only mode by writing a ‘0’ into bit six (IC_SLAVE_DISABLE) and a ‘0’ to bit zero # (MASTER_MODE). # Disable Master mode self.RP2040_Clear_32b_i2c_Reg(I2C_OFFSET["I2C_IC_CON"], self.get_Bits_Mask("MASTER_MODE", I2C_IC_CON)) # Enable slave mode self.RP2040_Clear_32b_i2c_Reg(I2C_OFFSET["I2C_IC_CON"], self.get_Bits_Mask("IC_SLAVE_DISABLE", I2C_IC_CON)) # Enable clock strech if enable_clock_stretch: self.RP2040_Set_32b_i2c_Reg(I2C_OFFSET["I2C_IC_CON"], self.get_Bits_Mask("RX_FIFO_FULL_HLD_CTRL", I2C_IC_CON)) # 4. Enable the DW_apb_i2c by writing a ‘1’ to IC_ENABLE.ENABLE. self.RP2040_Set_32b_i2c_Reg(I2C_OFFSET["I2C_IC_ENABLE"], self.get_Bits_Mask("IC_ENABLE", I2C_IC_ENABLE)) # Reset GPIO0 function mem32[ self.IO_BANK0_BASE | self.mem_clr | ( 4 + 8 * self.sda) ] = 0x1f # Set GPIO0 as IC0_SDA function mem32[ self.IO_BANK0_BASE | self.mem_set | ( 4 + 8 * self.sda) ] = 0x03 # Reset GPIO1 function mem32[ self.IO_BANK0_BASE | self.mem_clr | ( 4 + 8 * self.scl) ] = 0x1f # Set GPIO1 as IC0_SCL function mem32[ self.IO_BANK0_BASE | self.mem_set | ( 4 + 8 * self.scl) ] = 3 class I2CStateMachine: I2C_RECEIVE = 0 I2C_REQUEST = 1 I2C_FINISH = 2 I2C_START = 3 I2C_IDLE = 4 class I2CTransaction: def __init__(self, address: int, data_byte: list): self.address = address self.data_byte = data_byte def handle_event(self): """Optimized event detection by reading interrupt status register once""" # Read entire interrupt status register in one operation intr_stat = self.RP2040_Read_32b_i2c_Reg(I2C_OFFSET["I2C_IC_INTR_STAT"]) # Check for restart condition if intr_stat & self.get_Bits_Mask("R_RESTART_DET", I2C_IC_INTR_STAT): self.RP2040_Read_32b_i2c_Reg(I2C_OFFSET["I2C_IC_CLR_RESTART_DET"]) # Handle restart logic here # Check other conditions using the same intr_stat value # I2C Master has abort the transactions if (intr_stat & self.get_Bits_Mask("R_TX_ABRT", I2C_IC_INTR_STAT)): # Clear int self.RP2040_Read_32b_i2c_Reg(I2C_OFFSET["I2C_IC_CLR_TX_ABRT"]) return i2c_slave.I2CStateMachine.I2C_FINISH # Last byte transmitted by I2C Slave but NACK from I2C Master if (intr_stat & self.get_Bits_Mask("R_RX_DONE", I2C_IC_INTR_STAT)): # Clear int self.RP2040_Read_32b_i2c_Reg(I2C_OFFSET["I2C_IC_CLR_RX_DONE"]) return i2c_slave.I2CStateMachine.I2C_FINISH # Start condition detected by I2C Slave if (intr_stat & self.get_Bits_Mask("R_START_DET", I2C_IC_INTR_STAT)): # Clear start detection self.RP2040_Read_32b_i2c_Reg(I2C_OFFSET["I2C_IC_CLR_START_DET"]) return i2c_slave.I2CStateMachine.I2C_START # Stop condition detected by I2C Slave if (intr_stat & self.get_Bits_Mask("R_STOP_DET", I2C_IC_INTR_STAT)): # Clear stop detection self.RP2040_Read_32b_i2c_Reg(I2C_OFFSET["I2C_IC_CLR_STOP_DET"]) return i2c_slave.I2CStateMachine.I2C_FINISH # Check if RX FIFO is not empty if (self.RP2040_Get_32b_i2c_Bits(I2C_OFFSET["I2C_IC_STATUS"], self.get_Bits_Mask("RFNE", I2C_IC_STATUS))): return i2c_slave.I2CStateMachine.I2C_RECEIVE # Check if Master is requesting data if (intr_stat & self.get_Bits_Mask("R_RD_REQ", I2C_IC_INTR_STAT)): # Shall Wait until transfer is done, timing recommended 10 * fastest SCL clock period # for 100 Khz = (1/100E3) * 10 = 100 uS # for 400 Khz = (1/400E3) * 10 = 25 uS return i2c_slave.I2CStateMachine.I2C_REQUEST # Add at the end return i2c_slave.I2CStateMachine.I2C_IDLE def is_Master_Req_Read(self): """ Return status if I2C Master is requesting a read sequence """ # Check RD_REQ Interrupt bit (master wants to read data from the slave) status = self.RP2040_Get_32b_i2c_Bits(I2C_OFFSET["I2C_IC_RAW_INTR_STAT"], self.get_Bits_Mask("RD_REQ", I2C_IC_RAW_INTR_STAT)) if status : return True return False """ def is_Master_Req_Seq_Write(self): # Return true if I2C Master is requesting a sequential data writing # Check whether is FIRST_DATA_BYTE bit is active in IC_DATA_CMD. first_data_byte_stat = self.RP2040_Get_32b_i2c_Bits(I2C_OFFSET["I2C_IC_DATA_CMD"], self.get_Bits_Mask("FIRST_DATA_BYTE", I2C_IC_DATA_CMD)) # Check whether is STOP_DET_IFADDRESSED bit is active in IC_CON. stop_stat = self.RP2040_Get_32b_i2c_Bits(I2C_OFFSET["I2C_IC_CON"], self.get_Bits_Mask("STOP_DET_IFADDRESSED", I2C_IC_CON)) if (stop_stat): # Clear stop bit int self.RP2040_Read_32b_i2c_Reg(I2C_OFFSET["I2C_IC_CLR_STOP_DET"]) # Master sequential write true if FIRST_DATA_BYTE bit is active and no stop condition detected. if first_data_byte_stat and not(stop_stat): return True return False """ def Slave_Write_Data(self, data): """ Write 8bits of data at destination of I2C Master """ # Send data self.RP2040_Write_32b_i2c_Reg(I2C_OFFSET["I2C_IC_DATA_CMD"], data & self.get_Bits_Mask("DAT", I2C_IC_DATA_CMD)) self.RP2040_Read_32b_i2c_Reg(I2C_OFFSET["I2C_IC_CLR_RD_REQ"]) def Available(self): """ Return true if data has been received from I2C Master """ # Get RFNE Bit (Receive FIFO Not Empty) return self.RP2040_Get_32b_i2c_Bits(I2C_OFFSET["I2C_IC_STATUS"], self.get_Bits_Mask("RFNE", I2C_IC_STATUS)) def Read_Data_Received(self): """ Return data from I2C Master """ return self.RP2040_Read_32b_i2c_Reg(I2C_OFFSET["I2C_IC_DATA_CMD"]) & self.get_Bits_Mask("DAT", I2C_IC_DATA_CMD) def deinit(self): """Disable the I2C slave and release pins""" # Disable I2C interface self.RP2040_Clear_32b_i2c_Reg(I2C_OFFSET["I2C_IC_ENABLE"], self.get_Bits_Mask("ENABLE", I2C_IC_ENABLE)) # Reset GPIO pins back to default state mem32[self.IO_BANK0_BASE | self.mem_clr | (4 + 8 * self.sda)] = 0x1f mem32[self.IO_BANK0_BASE | self.mem_clr | (4 + 8 * self.scl)] = 0x1f if __name__ == "__main__": import machine from machine import mem32 def main(): data_buf = [] addr = 0x00 # Create I2C slave instance s_i2c = i2c_slave(0, sda=0, scl=1, slaveAddress=0x41, enable_clock_stretch=True) state = i2c_slave.I2CStateMachine.I2C_IDLE currentTransaction = i2c_slave.I2CTransaction(addr, data_buf) counter = 0 print("I2C Slave test") try: while True: state = s_i2c.handle_event() if state == s_i2c.I2CStateMachine.I2C_START: pass if state == s_i2c.I2CStateMachine.I2C_RECEIVE: if currentTransaction.address == 0x00: # First byte received is the register address currentTransaction.address = s_i2c.Read_Data_Received() # Read all data byte received until RX FIFO is empty while (s_i2c.Available()): currentTransaction.data_byte.append(s_i2c.Read_Data_Received()) # Virtually Increase register address # s_i2c.I2CTransaction.address += 1 if state == s_i2c.I2CStateMachine.I2C_REQUEST: # Send some dummy data back while (s_i2c.is_Master_Req_Read()): counter += 1 s_i2c.Slave_Write_Data(counter) # Virtually Increase register address # s_i2c.I2CTransaction.address += 1 print ("Sendind data : ", counter) if state == s_i2c.I2CStateMachine.I2C_FINISH: print ("Register : ", currentTransaction.address ,"Received : ", currentTransaction.data_byte) currentTransaction.address = 0x00 currentTransaction.data_byte = [] state= s_i2c.I2CStateMachine.I2C_IDLE except KeyboardInterrupt: s_i2c.deinit() # Clean up when done print("I2C slave stopped") main()