# Source: https://github.com/TraoreMorike/Raspberry-Pico---I2C-Slave

### 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()
