/* I2C.c * Jonathan Valvano * June 9, 2024 * Derived from i2c_controller_rw_multibyte_fifo_poll_LP_MSPM0G3507_nortos_ticlang Educational BoosterPack MKII (BOOSTXL-EDUMKII) J1.8 ambient light (OPT3001) interrupt (digital) PA27 J1.9 ambient light (OPT3001) and temperature sensor (TMP006) I2C SCL (I2C) PB2_I2C1-SCL J1.10 ambient light (OPT3001) and temperature sensor (TMP006) I2C SDA (I2C) PB3_I2C1-SDA J2.11 temperature sensor (TMP006) interrupt (digital) PB16 The TMP006 was discontinued in 2017. Therefore, it is no longer populated on the board. Documentation of the TMP006 is unchanged in this user's guide to accommodate users who still have the device populated on the board SSD1306 oLED VCC to +3.3V I2C SCL: PB2 is the SSD1306 SCL, with 1.5k pullup to 3.3V I2C SDA: PB3 is the SSD1306 SDA GND to GND */ //#define CONFIG_MSPM0G350X #include #include "../inc/I2C.h" #include "../inc/Clock.h" #define PA27INDEX 59 #define PB2INDEX 14 #define PB3INDEX 15 #define PB16INDEX 32 // assume 80MHz bus clock // I2C in power domain PD0, so // for 32MHz bus clock, SYSCLK clock is 32MHz // for 40MHz bus clock, SYSCLK clock is ULPCLK 20MHz // for 80MHz bus clock, SYSCLK clock is ULPCLK 40MHz // initialize I2C for 400kHz clock // busy-wait synchronization void I2C_Init(void){ // assumes GPIOA and GPIOB are reset and powered previously // RSTCLR to I2C1 peripheral // bits 31-24 unlock key 0xB1 // bit 1 is Clear reset sticky bit // bit 0 is reset gpio port I2C1->GPRCM.RSTCTL = 0xB1000003; // Enable power to I2C1 peripheral // PWREN // bits 31-24 unlock key 0x26 // bit 0 is Enable Power I2C1->GPRCM.PWREN = 0x26000001; // configure PB3 and PB2 as alternate IC2 function // bit 18 INENA // bit 25 hiZ // bit 7 PC peripheral connect // bits 4-0 I2C IOMUX->SECCFG.PINCM[PB3INDEX] = 0x02040084; // I2C SDA IOMUX->SECCFG.PINCM[PB2INDEX] = 0x02040084; // I2C SCL Clock_Delay(24); // time for gpio to power up I2C1->CLKSEL = 8; // SYSCLK // bit 3 SYSCLK // bit 2 MFCLK I2C1->CLKDIV = 0; // divide by 1 I2C1->MASTER.MCTR = 0x00; // period=clockPeriod*(1+MTPR)*10 // bus clock=32MHz, make MTPR=7, frequency = 32MHz/80 = 400kHz // bus clock=40MHz, make MTPR=4, frequency = 20MHz/50 = 400kHz // bus clock=80MHz, make MTPR=9, frequency = 40MHz/100 = 400kHz I2C1->MASTER.MTPR = 9; /* Set frequency to 400000 Hz*/ // bits 6-0 TPR (0 to 63), divide by TPR+1 I2C1->MASTER.MCR = 4; // bit 8 LPBK=0 no loop back // bit 2 CLKSTRETCH=1 allow stretch // bit 1 MMST=0 disable multicontroller mode // bit 0 ACTIVE 0 for disable, 1 for enable // not using interrupts, FIFO triggers not used I2C1->MASTER.MFIFOCTL = 0; I2C1->MASTER.MCR = 5; } uint32_t I2C_error; // receives two bytes from specified slave uint16_t I2C_Recv2(int8_t slave){ uint8_t data1, data2; while((I2C1->MASTER.MSR & 0x20) == 0){}; // wait until idle I2C1->MASTER.MSA = (slave<<1)|0x0001; // bit 7-1 address // bit 0 direction=1 receive I2C1->MASTER.MCTR = 0x00020007; // bits 27-16 MBLEN =2 (length) // bit 3 ACK // bit 2 STOP=1 // bit 1 START=1 // bit 0 BURSTRUN=1 while((I2C1->MASTER.MFIFOSR & 0x000F) == 0){}; //wait for received data data1 = I2C1->MASTER.MRXDATA; while((I2C1->MASTER.MFIFOSR & 0x000F) == 0){}; //wait for received data data2 = I2C1->MASTER.MRXDATA; return (data1<<8)+data2; } // receives N bytes from specified slave into caller buffer // Returns 1 if successful, 0 on error int I2C_RecvN(int8_t slave, uint8_t *buf, uint32_t count){ if(count == 0 || count > 4095) return 0; while((I2C1->MASTER.MSR & 0x20) == 0){}; // wait until idle I2C1->MASTER.MSA = (slave<<1) | 0x0001; // bit 7-1 address // bit 0 direction=1 receive I2C1->MASTER.MCTR = (count << 16) | 0x00000007; // bits 27-16 MBLEN = count (length) // bit 2 STOP=1 // bit 1 START=1 // bit 0 BURSTRUN=1 for(uint32_t i = 0; i < count; i++){ while((I2C1->MASTER.MFIFOSR & 0x000F) == 0){}; // wait for received data buf[i] = I2C1->MASTER.MRXDATA; } return 1; } int static IC2FillTxFifo(uint8_t *buffer, uint16_t count){ for(int i=0; iMASTER.MFIFOSR & 0x0F00) == 0) return 0; // fail TxFifo can't take data I2C1->MASTER.MTXDATA = buffer[i]; } return 1; } // sends one byte to specified slave // Returns 0 if successful, nonzero if error int I2C_Send1(int8_t slave, uint8_t data1){ if(IC2FillTxFifo(&data1,1) == 0) return 0; while((I2C1->MASTER.MSR & 0x20) == 0){}; // wait until idle I2C1->MASTER.MSA = (slave<<1); // bit 7-1 address // bit 0 direction=0 transmit I2C1->MASTER.MCTR = 0x00010007; // bits 27-16 MBLEN =1 (length) // bit 3 ACK // bit 2 STOP=1 // bit 1 START=1 // bit 0 BURSTRUN=1 while((I2C1->MASTER.MSR & 0x01) == 0x01){}; // wait until not busy // check for error if(I2C1->MASTER.MSR & 0x12){ // lost arbitration or no ack I2C_error = I2C1->MASTER.MSR; // help debugging return 0; // error } while((I2C1->MASTER.MSR & 0x20) == 0){}; // wait until idle return 1; } // sends two bytes to specified slave // Returns 0 if successful, nonzero if error int I2C_Send2(int8_t slave, uint8_t data1, uint8_t data2){ uint8_t data[2]; data[0] = data1; data[1] = data2; if(IC2FillTxFifo(data,2) == 0) return 0; while((I2C1->MASTER.MSR & 0x20) == 0){}; // wait until idle I2C1->MASTER.MSA = (slave<<1); // bit 7-1 address // bit 0 direction=0 transmit I2C1->MASTER.MCTR = 0x00020007; // bits 27-16 MBLEN =2 (length) // bit 3 ACK // bit 2 STOP=1 // bit 1 START=1 // bit 0 BURSTRUN=1 while((I2C1->MASTER.MSR&0x01) == 0x01){}; // wait until not busy // check for error if(I2C1->MASTER.MSR &0x12){ // lost arbitration or no ack I2C_error = I2C1->MASTER.MSR; // help debugging return 0; // error } while((I2C1->MASTER.MSR & 0x20) == 0){}; // wait until idle return 1; } // sends two bytes to specified slave // Returns 0 if successful, nonzero if error int I2C_Send3(int8_t slave, uint8_t data1, uint8_t data2, uint8_t data3){ uint8_t data[3]; data[0] = data1; data[1] = data2; data[2] = data3; if(IC2FillTxFifo(data,3) == 0) return 0; while((I2C1->MASTER.MSR & 0x20) == 0){}; // wait until idle I2C1->MASTER.MSA = (slave<<1); // bit 7-1 address // bit 0 direction=0 transmit I2C1->MASTER.MCTR = 0x00030007; // bits 27-16 MBLEN =3 (length) // bit 3 ACK // bit 2 STOP=1 // bit 1 START=1 // bit 0 BURSTRUN=1 while((I2C1->MASTER.MSR&0x01) == 0x01){}; // wait until not busy // check for error if(I2C1->MASTER.MSR &0x12){ // lost arbitration or no ack I2C_error = I2C1->MASTER.MSR; // help debugging return 0; // error } while((I2C1->MASTER.MSR & 0x20)==0){}; // wait until idle return 1; } // count must be less than 8, because it fills the FIFO before starting int I2C_Send(uint8_t slave, uint8_t *pData, uint32_t count){ if(count>8){ I2C_error = count; // help debugging return 0; // error } if(IC2FillTxFifo(pData,count) == 0) return 0; while((I2C1->MASTER.MSR & 0x20) == 0){}; // wait until idle I2C1->MASTER.MSA = (slave<<1); // bit 7-1 address // bit 0 direction=0 transmit I2C1->MASTER.MCTR = 0x00000007|(count<<16); // bits 27-16 MBLEN =count (length) // bit 3 ACK // bit 2 STOP=1 // bit 1 START=1 // bit 0 BURSTRUN=1 while((I2C1->MASTER.MSR & 0x01) == 0x01){}; // wait until not busy // check for error if(I2C1->MASTER.MSR & 0x12){ // lost arbitration or no ack I2C_error = I2C1->MASTER.MSR; // help debugging return 0; // error } while((I2C1->MASTER.MSR & 0x20) == 0){}; // wait until idle return 1; } // count must be less than or equal to 4095 int I2C_SendData(uint8_t slave, uint8_t *pData, uint32_t count){int i; if((I2C1->MASTER.MFIFOSR & 0x0F00) == 0) return 0; // fail TxFifo full I2C1->MASTER.MTXDATA = 0x40; // 0x40 means data if(count > 7){ if(IC2FillTxFifo(pData,7) == 0) return 0; // first 8 i = 7; }else{ if(IC2FillTxFifo(pData,count) == 0) return 0; // all data should fit i = count; } //I2C1->MASTER.MTXDATA = pData[0];// get first one queued while((I2C1->MASTER.MSR & 0x20) == 0){}; // wait until idle I2C1->MASTER.MSA = (slave<<1); // bit 7-1 address // bit 0 direction=0 transmit I2C1->MASTER.MCTR = 0x00000007|((count+1)<<16); // bits 27-16 MBLEN =count (length) // bit 3 ACK // bit 2 STOP=1 // bit 1 START=1 // bit 0 BURSTRUN=1 while(iMASTER.MFIFOSR & 0x0F00) == 0){}; // wait for room I2C1->MASTER.MTXDATA = pData[i];// get next one queued i++; } while((I2C1->MASTER.MSR&0x01)==0x01){}; // wait until not busy // check for error if(I2C1->MASTER.MSR &0x12){ // lost arbitration or no ack I2C_error = I2C1->MASTER.MSR; // help debugging return 0; // error } while((I2C1->MASTER.MSR &0x20)==0){}; // wait until idle return 1; } // ------------I2C_LightSensor_Init------------ // Initialize a GPIO pin for input, which corresponds // with BoosterPack pins J1.8 PA27 (Light Sensor interrupt). // Initialize two I2C pins, which correspond with // BoosterPack pins J1.9 (SCL) and J1.10 (SDA). // Input: none // Output: none void I2C_LightSensor_Init(void){ I2C_Init(); IOMUX->SECCFG.PINCM[PA27INDEX] = 0x00040081; // GPIO input (OPT3001) interrupt (digital) } // Send the appropriate codes to initiate a // measurement with an OPT3001 light sensor at I2C // slave address 'slaveAddress'. // Assumes: I2C_LightSensor_Init() has been called void lightsensorstart(uint8_t slaveAddress){ // configure Low Limit Register (0x02) for: // INT pin active after each conversion completes I2C_Send3(slaveAddress, 0x02, 0xC0, 0x00); // configure Configuration Register (0x01) for: // 15-12 RN range number 1100b = automatic full-scale setting mode // 11 CT conversion time 1b = 800 ms // 10-9 M mode of conversion 01b = single-shot // 8 OVF overflow flag field 0b (read only) // 7 CRF conversion ready field 0b (read only) // 6 FH flag high field 0b (read only) // 5 FL flag low field 0b (read only) // 4 L latch 1b = latch interrupt if measurement exceeds programmed ranges // 3 POL polarity 0b = INT pin reports active low // 2 ME mask exponent 0b = do not mask exponent (more math later) // 1-0 FC fault count 00b = 1 fault triggers interrupt I2C_Send3(slaveAddress, 0x01, 0xCA, 0x10); I2C_Recv2(slaveAddress); // read Configuration Register to reset conversion ready } // Send the appropriate codes to end a measurement // with an OPT3001 light sensor at I2C slave address // 'slaveAddress'. Return results (units 100*lux). // Assumes: I2C_LightSensor_Init() has been called and measurement is ready int32_t static lightsensorend(uint8_t slaveAddress){ uint16_t raw, config; I2C_Send1(slaveAddress, 0x00); // pointer register 0x00 = Result Register raw = I2C_Recv2(slaveAddress); // force the INT pin to clear by clearing and resetting the latch bit of the Configuration Register (0x01) I2C_Send1(slaveAddress, 0x01); // pointer register 0x01 = Configuration Register config = I2C_Recv2(slaveAddress);// current Configuration Register I2C_Send3(slaveAddress, 0x01, (config&0xFF00)>>8, (config&0x00FF)&~0x0010); I2C_Send3(slaveAddress, 0x01, (config&0xFF00)>>8, (config&0x00FF)|0x0010); return (1<<(raw>>12))*(raw&0x0FFF); } // ------------I2C_LightSensor_Input------------ // Query the OPT3001 light sensor for a measurement. // Wait until the measurement is ready, which may // take 800 ms. // Input: none // Output: light intensity (units 100*lux) // Assumes: I2C_LightSensor_Init() has been called #define LIGHTINT (1<<27) /* PA27 Input */ int LightBusy = 0; // 0 = idle; 1 = measuring uint32_t I2C_LightSensor_Input(void){ uint32_t light; LightBusy = 1; lightsensorstart(0x44); while((GPIOA->DIN31_0 & LIGHTINT) == LIGHTINT){}; // wait for conversion to complete light = lightsensorend(0x44); LightBusy = 0; return light; } // ------------I2C_LightSensor_Start------------ // Start a measurement using the OPT3001. // If a measurement is currently in progress, return // immediately. // Input: none // Output: none // Assumes: I2C_LightSensor_Init() has been called void I2C_LightSensor_Start(void){ if(LightBusy == 0){ // no measurement is in progress, so start one LightBusy = 1; lightsensorstart(0x44); } } // ------------I2C_LightSensor_End------------ // Query the OPT3001 light sensor for a measurement. // If no measurement is currently in progress, start // one and return zero immediately. If the measurement // is not yet complete, return zero immediately. If // the measurement is complete, store the result in the // pointer provided and return one. // Input: light is pointer to store light intensity (units 100*lux) // Output: one if measurement is ready and pointer is valid // zero if measurement is not ready and pointer unchanged // Assumes: I2C_LightSensor_Init() has been called int I2C_LightSensor_End(uint32_t *light){ uint32_t lightLocal; if(LightBusy == 0){ // no measurement is in progress, so start one LightBusy = 1; lightsensorstart(0x44); return 0; // measurement needs more time to complete } else{ // measurement is in progress if((GPIOA->DIN31_0 & LIGHTINT) == LIGHTINT){ return 0; // measurement needs more time to complete } else{ lightLocal = lightsensorend(0x44); *light = lightLocal; LightBusy = 0; return 1; // measurement is complete; pointer valid } } } // ------------I2C_TempSensor_Init------------ // Initialize a GPIO pin for input, which corresponds // with BoosterPack pin J2.11 PB16 (Temperature Sensor // interrupt). Initialize two I2C pins, which // correspond with BoosterPack pins J1.9 (SCL) and // J1.10 (SDA). // Input: none // Output: none void I2C_TempSensor_Init(void){ I2C_Init(); IOMUX->SECCFG.PINCM[PB16INDEX] = 0x00040081; // GPIO input, (TMP006) interrupt (digital) } // Send the appropriate codes to initiate a // measurement with a TMP006 temperature sensor at // I2C slave address 'slaveAddress'. // Assumes: I2C_TempSensor_Init() has been called void static tempsensorstart(uint8_t slaveAddress){ // configure Configuration Register (0x02) for: // 15 RST software reset bit 0b = normal operation // 14-12 MOD mode of operation 111b = sensor and die continuous conversion // 11-9 CR ADC conversion rate 010b = 1 sample/sec // 8 EN interrupt pin enable 1b = ~DRDY pin enabled (J2.11/P3.6) // 7 ~DRDY data ready bit 0b (read only, automatic clear) // 6-0 reserved 000000b (reserved) I2C_Send3(slaveAddress, 0x02, 0x75, 0x00); } // Send the appropriate codes to end a measurement // with a TMP006 temperature sensor at I2C slave // address 'slaveAddress'. Store the results at the // provided pointers. // Assumes: I2C_TempSensor_Init() has been called and measurement is ready void static tempsensorend(uint8_t slaveAddress, int32_t *sensorV, int32_t *localT){ int16_t raw; I2C_Send1(slaveAddress, 0x00); // pointer register 0x00 = Sensor Voltage Register raw = I2C_Recv2(slaveAddress); *sensorV = raw*15625; // 156.25 nV per LSB I2C_Send1(slaveAddress, 0x01); // pointer register 0x01 = Local Temperature Register raw = I2C_Recv2(slaveAddress); *localT = (raw>>2)*3125; // 0.03125 C per LSB } // ------------I2C_TempSensor_Input------------ // Query the TMP006 temperature sensor for a // measurement. Wait until the measurement is ready, // which may take 4 seconds. // Input: sensorV is signed pointer to store sensor voltage (units 100*nV) // localT is signed pointer to store local temperature (units 100,000*C) // Output: none // Assumes: I2C_TempSensor_Init() has been called #define TEMPINT (1<<16) /* Port PB16 Input */ int TempBusy = 0; // 0 = idle; 1 = measuring void I2C_TempSensor_Input(int32_t *sensorV, int32_t *localT){ int32_t volt, temp; TempBusy = 1; tempsensorstart(0x40); while((GPIOB->DIN31_0 & TEMPINT) == TEMPINT){}; // wait for conversion to complete tempsensorend(0x40, &volt, &temp); *sensorV = volt; *localT = temp; TempBusy = 0; } // ------------I2C_TempSensor_Start------------ // Start a measurement using the TMP006. // If a measurement is currently in progress, return // immediately. // Input: none // Output: none // Assumes: I2C_TempSensor_Init() has been called void I2C_TempSensor_Start(void){ if(TempBusy == 0){ // no measurement is in progress, so start one TempBusy = 1; tempsensorstart(0x40); } } // ------------I2C_TempSensor_End------------ // Query the TMP006 temperature sensor for a // measurement. If no measurement is currently in // progress, start one and return zero immediately. // If the measurement is not yet complete, return // zero immediately. If the measurement is complete, // store the result in the pointers provided and // return one. // Input: sensorV is signed pointer to store sensor voltage (units 100*nV) // localT is signed pointer to store local temperature (units 100,000*C) // Output: one if measurement is ready and pointers are valid // zero if measurement is not ready and pointers unchanged // Assumes: I2C_TempSensor_Init() has been called int I2C_TempSensor_End(int32_t *sensorV, int32_t *localT){ int32_t volt, temp; if(TempBusy == 0){ // no measurement is in progress, so start one TempBusy = 1; tempsensorstart(0x40); return 0; // measurement needs more time to complete } else{ // measurement is in progress if((GPIOB->DIN31_0 & TEMPINT) == TEMPINT){ return 0; // measurement needs more time to complete } else{ tempsensorend(0x40, &volt, &temp); *sensorV = volt; *localT = temp; TempBusy = 0; return 1; // measurement is complete; pointers valid } } }