#ifndef _I2C_DEFINED #define _I2C_DEFINED //I2C driver for P2 //Copyright 2021 Raymond Allen //Based in large part on jm_i2c.spin2 by Jon "JonnyMac" McPhalen, found on Parallax OBEX on Github //Higher level functions based on Spin2 version of my DS1339 code //See MIT license info at end of this file /*-------------------------------------------------*/ /* I2C pins and speed configuration and setup */ enum PullupOptions { PU_NONE = 0, PU_1K5, PU_3K3, PU_15K }; enum AckOrNak { ACK = 0, NAK }; //I2C responses typedef struct I2C_Port {//I2C port instance int sclpin, sdapin; //i2c bus pins int clktix; //system ticks in 1/4 period } i2c_Port; void i2c_Setup(i2c_Port* pPort, int scl, int sda, int khz, int pullup) {//set up pins and delays for I2C and return pointer to port structure //pullup options defined in PullupOptions enum above int tix; // Define I2C SCL(clock) and SDA(data) pins // --khz is bus frequency : 100 (standard), 400 (full), 1000 (fast) // * circuit / connections will affect maximum bus speed // --pullup controls high level drive configuration of SCL and SDA // save pins pPort->sclpin = scl; pPort->sdapin = sda; pPort->clktix = tix = (_clkfreq / (khz * 1000)) >> 2; // calculate ticks in 1/4 period switch (pullup) { case PU_NONE: pullup = P_HIGH_FLOAT; // use external pull-up break; case PU_1K5: pullup = P_HIGH_1K5; // 1.5k break; case PU_3K3: pullup = P_HIGH_1MA; // acts like ~3.3k break; default: pullup = P_HIGH_15K; // 15K break; } _wrpin(scl, pullup); _wrpin(sda, pullup); _pinh(scl); // both high _pinh(sda); _waitx(tix); _waitx(tix); for (int i = 0; i < 10; i++) {// bus clear (if SDA stuck low) if (_pinr(sda)) // sample sda break; // abort loop if sda == 1 _pinl(scl); // scl low _waitx(tix); _waitx(tix); _pinh(scl); // scl high _waitx(tix); _waitx(tix); } } //RJA some higher level functions here void ScanI2CBus(i2c_Port* pPort) { //I2C Scanner (adapted from P2 OBEX file jm_i2c_scanner.spin2 ) int count = 0; printf("I2C Scan:\n"); printf("-- dddd_aaa_0 (8-bit) format\n\n"); printf(" 00 02 04 06 08 0A 0C 0E\n"); for (int type = 0b0001; type <= 0b1110; type++) // %0000 and %1111 not valid { printf("%2X ", type << 4); for (int addr = 0b000; addr <= 0b111; addr++) // %0000 and %1111 not valid { int devid = (type << 4) | (addr << 1); if (i2c_Present(pPort, devid)) { printf("%2X ", devid); count++; } else { printf(".. "); } _waitms(1); } printf("\n"); } printf("#Devices Found= %d\n\n", count); } void i2c_ReadRegisters(i2c_Port* pi2c, uint8_t add, int reg, int n, uint8_t* pBuff) {//read a block of registers //send starting register i2c_Start(pi2c); i2c_Write(pi2c, add); //write address i2c_Write(pi2c, reg); i2c_Stop(pi2c); //read registers i2c_Start(pi2c); i2c_Write(pi2c, add+1);//read address i2c_RdBlock(pi2c, pBuff, n, NAK); //send NAK with last byte i2c_Stop(pi2c); } void i2c_WriteRegisters(i2c_Port* pi2c, uint8_t add, int reg, int n, uint8_t* pBuff) {//write a block of registers i2c_Start(pi2c); i2c_Write(pi2c, add); //write address i2c_Write(pi2c, reg); i2c_WrBlock(pi2c, pBuff, n); i2c_Stop(pi2c); } //Note: Some things like cameras have 16-bit addresses void i2c_ReadRegisters16(i2c_Port* pi2c, uint16_t add, int reg, int n, uint8_t* pBuff) {//read a block of 16-bit numbered registers //send starting register i2c_Start(pi2c); i2c_Write(pi2c, add); i2c_Write(pi2c, reg>>8); //upper i2c_Write(pi2c, reg); //lower i2c_Stop(pi2c); //read registers i2c_Start(pi2c); i2c_Write(pi2c, add + 1);//read address i2c_RdBlock(pi2c, pBuff, n, NAK); //send NAK with last byte i2c_Stop(pi2c); } void i2c_WriteRegisters16(i2c_Port* pi2c, uint16_t add, int reg, int n, uint8_t* pBuff) {//write a block of 16-bit numbered registers i2c_Start(pi2c); i2c_Write(pi2c, add); i2c_Write(pi2c, reg >> 8); //upper i2c_Write(pi2c, reg); //lower i2c_WrBlock(pi2c, pBuff, n); i2c_Stop(pi2c); } /*-------------------------------------------------*/ /* Low Level I2C functions */ int i2c_Present(i2c_Port* pPort, int slaveid) {// Pings device, returns true if device on bus. int result; i2c_Start(pPort); result = (i2c_Write(pPort, slaveid) == ACK); return result; } void i2c_Wait(i2c_Port* pPort, int slaveid) {// Waits for device to be ready for new command. // -- Note: Use present() to detect device before using wait() int ackbit; do { i2c_Start(pPort); ackbit = i2c_Write(pPort, slaveid); } while (ackbit != ACK); } void i2c_Start(i2c_Port* pPort) {// Create I2C start sequence int scl, sda, tix; //copy pins & timing scl = pPort->sclpin; sda = pPort->sdapin; tix= pPort->clktix; _pinh(sda); //both high _pinh(scl); _waitx(tix); while (!_pinr(scl)) {} //verify scl released _pinl(sda); //start sequence _waitx(tix); _pinl(scl); _waitx(tix); } int i2c_Write(i2c_Port* pPort, uint8_t i2cbyte) {// Write byte to I2C bus // -- leaves SCL low int ackbit = 0; int scl, sda, tix; //copy pins & timing scl = pPort->sclpin; sda = pPort->sdapin; tix = pPort->clktix; uint32_t i2clong= i2cbyte; __asm { shl i2clong, #24 // align i2cbyte.[7] to i2cbyte.[31] .wr_byte rep #8, #8 // output 8 bits, msbfirst shl i2clong, #1 wc // msb --> c drvc sda // c -- sda waitx tix // let sda settle drvh scl // scl high waitx tix waitx tix drvl scl // scl low waitx tix .get_ack drvh sda // pull-up sda waitx tix drvh scl // scl high waitx tix testp sda wc // sample sda (ack bit) muxc ackbit, #1 // update return value waitx tix drvl scl // scl low waitx tix waitx tix //end } return ackbit; } int i2c_WrBlock(i2c_Port* pPort, uint8_t* p_block, int count) {// Write count bytes from p_block to I2C bus // -- p_block is pointer to bytes // -- leaves SCL low int ackbit = 0; int scl, sda, tix, i2cbyte; //copy pins & timing scl = pPort->sclpin; sda = pPort->sdapin; tix = pPort->clktix; __asm { .get_byte rdbyte i2cbyte, p_block // read byte from block add p_block, #1 // increment block pointer shl i2cbyte, #24 // align i2cbyte.[7] to i2cbyte.[31] .wr_byte rep #8, #8 // output 8 bits, msbfirst shl i2cbyte, #1 wc // msb --> c drvc sda // c -- sda waitx tix // let sda settle drvh scl // scl high waitx tix waitx tix drvl scl // scl low waitx tix .get_ack drvh sda // pull-up sda waitx tix drvh scl // scl high waitx tix testp sda wc // sample sda (ack bit) if_c or ackbit, #1 // update return value waitx tix drvl scl // scl low waitx tix waitx tix djnz count, #.get_byte //end } return ackbit; } int i2c_Read(i2c_Port* pPort, int ackbit) {// Read byte from I2C bus // -- ackbit is state of ack bit // * usually NAK for last byte read int i2cbyte, scl, sda, tix; //copy pins & timing scl = pPort->sclpin; sda = pPort->sdapin; tix = pPort->clktix; __asm { drvh sda // pull-up sda waitx tix //RJA added for Nunchuk waitx tix //RJA added for Nunchuk waitx tix //RJA added for Nunchuk waitx tix //RJA added for Nunchuk .rd_byte rep #9, #8 // read 8 bits, msb first waitx tix drvh scl // scl high waitx tix testp sda wc // sample sda shl i2cbyte, #1 // make room for new bit muxc i2cbyte, #1 // sda --> i2cbyte.[0] waitx tix drvl scl // scl low waitx tix .put_ack testb ackbit, #0 wc // ackbit.[0] --> c drvc sda // c --> sda waitx tix // let sda settle drvh scl // scl high waitx tix waitx tix drvl scl // scl low waitx tix waitx tix //end } return i2cbyte; } void i2c_RdBlock(i2c_Port* pPort, uint8_t* p_block, int count, int ackbit) {// Read count bytes from I2C bus // -- p_block is pointer to storage location for bytes // -- ackbit is state of ack for final byte read int i2cbyte, scl, sda, tix; //copy pins & timing scl = pPort->sclpin; sda = pPort->sdapin; tix = pPort->clktix; __asm { .rd_block drvh sda // pull-up sda mov i2cbyte, #0 // clear workspace .rd_byte rep #9, #8 // read 8 bits, msb first waitx tix drvh scl // scl high waitx tix testp sda wc // sample sda shl i2cbyte, #1 // make room for new bit muxc i2cbyte, #1 // sda --> i2cbyte.[0] waitx tix drvl scl // scl low waitx tix .put_ack cmp count, #1 wz // last byte? if_nz drvl sda // 0 --> sda (ack) if not last byte if_z testb ackbit, #0 wc // last byte, ackbit.[0] --> c if_z drvc sda // last byte, c --> sda waitx tix // let sda settle drvh scl // scl high waitx tix waitx tix drvl scl // scl low waitx tix waitx tix .put_byte wrbyte i2cbyte, p_block // write byte to block add p_block, #1 // increment block pointer djnz count, #.rd_block //end } } void i2c_Stop(i2c_Port* pPort) {// Create I2C stop sequence // -- allows for clock stretch int scl, sda, tix; //copy pins & timing scl = pPort->sclpin; sda = pPort->sdapin; tix = pPort->clktix; __asm { drvl sda // hold sda low drvh scl // release scl waitx tix .waitscl testp scl wc // wait for scl to release if_nc jmp #.waitscl //$-1 waitx tix drvh sda // finish stop sequence //end } } /* Terms of Use: MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #endif //_I2C_DEFINED