diff --git a/TinyWireS/TinyWireS.cpp b/TinyWireS/TinyWireS.cpp index 88fbcdb..783e8e7 100644 --- a/TinyWireS/TinyWireS.cpp +++ b/TinyWireS/TinyWireS.cpp @@ -59,28 +59,18 @@ void USI_TWI_S::onRequest( void (*function)(void) ) usi_onRequestPtr = function; } +// This routine is no longer used now that the usiTwiSlave is completely +// interrupt driven. The function is maintained here so that programs +// written for the pre-interrupt driven version will still compile and function. void TinyWireS_stop_check() { - if (!usi_onReceiverPtr) - { - // no onReceive callback, nothing to do... - return; - } - if (!(USISR & ( 1 << USIPF ))) - { - // Stop not detected - return; - } - uint8_t amount = usiTwiAmountDataInReceiveBuffer(); - if (amount == 0) - { - // no data in buffer - return; - } - usi_onReceiverPtr(amount); + // empty functions } // Implement a delay loop that checks for the stop bit (basically direct copy of the stock arduino implementation from wiring.c) +// I don't think this function is necessary now that the +// usiTwiSlave is completely interrupt driven. I'm not sure, and the function is +// behaviorally harmless because TinyWireS_stop_check() is empty, so I'm leaving it alone. void tws_delay(unsigned long ms) { uint16_t start = (uint16_t)micros(); diff --git a/TinyWireS/examples/TinyWireS_Stress_Master/TinyWireS_Stress_Master.ino b/TinyWireS/examples/TinyWireS_Stress_Master/TinyWireS_Stress_Master.ino index 75a5d29..d50adb7 100644 --- a/TinyWireS/examples/TinyWireS_Stress_Master/TinyWireS_Stress_Master.ino +++ b/TinyWireS/examples/TinyWireS_Stress_Master/TinyWireS_Stress_Master.ino @@ -29,7 +29,22 @@ #define I2C_SLAVE_ADDR 0x26 // i2c slave address (38, 0x26) -#define SLAVE_RESET_PIN 2 +#if defined(ESP8266) + // pins that work for Monkey Board ESP8266 12-E + // SCL=5, SDA=4 + #define SLAVE_RESET_PIN 2 + #define ALL_OK_LED_PIN 16 + #define OK_LED_PIN 14 + #define ERROR_LED_PIN 13 +#else + // pins that work for Micro Pro, Uno, Mega 2560 + // reference documentation for SCL and SDA pin locations + // Uno SDA=D18, SCL=D19 + #define SLAVE_RESET_PIN 6 + #define ALL_OK_LED_PIN 9 + #define OK_LED_PIN 7 + #define ERROR_LED_PIN 8 +#endif uint16_t count = 0; // total number of passes so far uint16_t error_count = 0; // total errors encountered so far @@ -39,10 +54,13 @@ char c_buf[64]; // for creating messages void setup() { // set pin modes - pinMode(SLAVE_RESET_PIN,OUTPUT); + pinMode(SLAVE_RESET_PIN,OUTPUT); // active low reset to slave device + pinMode(OK_LED_PIN,OUTPUT); // indicates last transaction matched + pinMode(ALL_OK_LED_PIN,OUTPUT); // indicates all transactions so far have matched + pinMode(ERROR_LED_PIN,OUTPUT); // indicates last transaction mismatched // init the serial port - Serial.begin(115200); + Serial.begin(9600); // print some useful pinnout info for the Arduino //Serial.println(String("SCL:")+String(SCL)+String(", SDA:")+String(SDA)); @@ -50,11 +68,18 @@ void setup() // init the Wire object (for I2C) Wire.begin(); + + // init the i2c clock + // default is 100kHz if not changed + // Wire.setClock(400000L); // 400kHz // reset the slave digitalWrite(SLAVE_RESET_PIN, LOW); delay(10); digitalWrite(SLAVE_RESET_PIN, HIGH); + + // set the all okay pin high + digitalWrite(ALL_OK_LED_PIN, HIGH); // wait for slave to finish any init sequence delay(2000); @@ -74,11 +99,11 @@ void loop() count++; // compute random number of bytes for this pass - rand_byte_count = random(12) + 1; + rand_byte_count = random(16) + 1; // force the first three requests to be small so that the tx buffer doesn't overflow // instantly and the user can see at least one successful transaction and some - // mismtaches before the usiTwiSlave.c library hangs on the line "while ( tmphead == txTail );". + // mismtaches before the usiTwiSlave.c library hangs on the line "while ( !txCount );". if (count <= 3) rand_byte_count = 2; // generate, save, and send N random byte values @@ -87,8 +112,12 @@ void loop() Wire.write(out_rand[i] = random(256)); Wire.endTransmission(); - //delay (10); // optional delay if required by slave (like sample ADC) - + // delay 20 milliseconds to accomodate slave onReceive() callback + // function. The actual time that slave takes is application dependent, but + // just storing the master's transmitted data does not take + // anywhere near 20ms. + delay(20); + // read N bytes from slave req_rtn = Wire.requestFrom(I2C_SLAVE_ADDR, (int)rand_byte_count); // Request N bytes from slave for (i = 0; i < req_rtn; i++) @@ -101,9 +130,22 @@ void loop() // increment the error counter if the number of byte variables don't match or // if the data itself doesn't match - if (mismatch || (rand_byte_count != req_rtn)) error_count++; + if (mismatch || (rand_byte_count != req_rtn)) + { + error_count++; + digitalWrite(ERROR_LED_PIN, HIGH); + digitalWrite(OK_LED_PIN, LOW); + // If there's ever an error, reset the ALL_OK_LED + // and it is not set again until the master resets. + digitalWrite(ALL_OK_LED_PIN, LOW); + } + else + { + digitalWrite(ERROR_LED_PIN, LOW); + digitalWrite(OK_LED_PIN, HIGH); + } - // The rest of the program just displays the results + // The rest of the program just displays the results to the serial port // display total requests so far and error count so far snprintf(c_buf, sizeof(c_buf), "req: %3d,err: %3d", count, error_count); diff --git a/TinyWireS/examples/TinyWireS_Stress_Slave/TinyWireS_Stress_Slave.ino b/TinyWireS/examples/TinyWireS_Stress_Slave/TinyWireS_Stress_Slave.ino index be9f49f..c7fbe41 100644 --- a/TinyWireS/examples/TinyWireS_Stress_Slave/TinyWireS_Stress_Slave.ino +++ b/TinyWireS/examples/TinyWireS_Stress_Slave/TinyWireS_Stress_Slave.ino @@ -28,30 +28,42 @@ // Please see credits and usage for usiTwiSlave and TinyWireS in the .h files of // those libraries. +#include +#include #include "TinyWireS.h" // wrapper class for I2C slave routines #define I2C_SLAVE_ADDR 0x26 // i2c slave address (38, 0x26) +// turns on code that makes the Tiny85 sleep between transactions +// This is optional. The Tiny85 current drops from +// about 2mA to about 20uA when the CPU is put into +// PowerDown sleep mode. +#define USE_CPU_SLEEP + // global buffer to store data sent from the master. uint8_t master_data[16]; // global variable to number of bytes sent from the master. uint8_t master_bytes; // Gets called when the ATtiny receives an i2c write slave request +// This routine runs from the usiTwiSlave interrupt service routine (ISR) +// so interrupts are disabled while it runs. void receiveEvent(uint8_t num_bytes) { uint8_t i; - + // save the number of bytes sent from the master master_bytes = num_bytes; // store the data from the master into the data buffer for (i = 0; i < master_bytes; i++) master_data[i] = TinyWireS.receive(); - + } // Gets called when the ATtiny receives an i2c read slave request +// This routine runs from the usiTwiSlave interrupt service routine (ISR) +// so interrupts are disabled while it runs. void requestEvent() { uint8_t i; @@ -61,20 +73,20 @@ void requestEvent() TinyWireS.send(master_data[i]); // corrupt the byte values in the data buffer - // so that subsequent call won't match + // so that any subsequent call won't match for (i = 0; i < master_bytes; i++) master_data[i] += 0x5a; - // corrupt length of the request, but dont' make it zero + // corrupt length of the request, but don't make it zero // if the usiTwiSlave.c is working fine, then this number is completely irrelevant // because the requestEvent() callback will not be called again until // after the next receiveEvent() callback, so the master_data and - // master_bytes variables will be set by that call. + // master_bytes variables will be overwritten by that call. // If the usiTwiSlave.c has the issue of calling the requestFrom() callback // for each byte sent, the buffer will accumulate by this amount *for each byte - // in the original request*. + // in the original request*. (This problem is fixed in the recent version.) // // Making it zero will obscure the 1-byte send issue in the usiTwiSlave.c // that is being tested. @@ -86,6 +98,8 @@ void requestEvent() void setup() { + //pinMode(1,OUTPUT); // This pin can be used for rudimentary debug + // initialize the TinyWireS and usiTwiSlave libraries TinyWireS.begin(I2C_SLAVE_ADDR); // init I2C Slave mode @@ -94,11 +108,36 @@ void setup() // register the onRequest() callback function TinyWireS.onRequest(requestEvent); + + // disable the watchdog timer so that it doesn't + // cause power-up, code is from datasheet + // Clear WDRF in MCUSR – MCU Status Register + // MCUSR provides information on which reset source caused an MCU Reset. + MCUSR = 0x00; + // WDTCR - Watchdog Timer Control Register + // Write logical one to WDCE and WDE (must be done before disabling) + WDTCR |= ( _BV(WDCE) | _BV(WDE) ); + // Turn off WDT + WDTCR = 0x00; + +#ifdef USE_CPU_SLEEP + // enable power down sleep mode + set_sleep_mode(SLEEP_MODE_PWR_DOWN); // sleep mode + sleep_enable(); +#endif + + sei(); // enable interrupts + } void loop() { - // This needs to be here - TinyWireS_stop_check(); - // otherwise empty loop + +#ifdef USE_CPU_SLEEP + // optionally put the CPU to sleep. It will be woken by a USI interrupt + // when it sees a "start condition" on the I2C bus. Everything interesting + // happens in the usiTwiSlave ISR. + sleep_cpu(); +#endif + } diff --git a/TinyWireS/usiTwiSlave.c b/TinyWireS/usiTwiSlave.c index ecb0365..a263184 100644 --- a/TinyWireS/usiTwiSlave.c +++ b/TinyWireS/usiTwiSlave.c @@ -30,9 +30,12 @@ Change Activity: 26 Apr 2007 Fixed ACK of slave address on a read. 04 Jul 2007 Fixed USISIF in ATtiny45 def 12 Dev 2009 Added callback functions for data requests - 06 Feb 2015 Minor change to allow mutli-byte requestFrom() from master. - 10 Feb 2015 Simplied RX/TX buffer code and allowed use of full buffer. + 06 Feb 2016 Minor change to allow mutli-byte requestFrom() from master. + 10 Feb 2016 Simplied RX/TX buffer code and allowed use of full buffer. + 13 Feb 2016 Made USI_RECEIVE_CALLBACK() callback fully interrupt-driven 12 Dec 2016 Added support for ATtiny167 + 23 Dec 2017 Fixed repeated restart (which broke when making receive callback + interrupt-driven) ********************************************************************************/ @@ -167,7 +170,10 @@ Change Activity: # define USI_OVERFLOW_VECTOR USI_OVERFLOW_vect #endif - +// These macros make the stop condition detection code more readable. +#define USI_PINS_SCL_SDA ( ( 1 << PIN_USI_SDA ) | ( 1 << PIN_USI_SCL ) ) +#define USI_PINS_SDA ( 1 << PIN_USI_SDA ) +#define USI_PINS_SCL ( 1 << PIN_USI_SCL ) /******************************************************************************** @@ -177,7 +183,7 @@ Change Activity: #define SET_USI_TO_SEND_ACK( ) \ { \ - /* prepare ACK */ \ + /* prepare ACK, ack is a zero */ \ USIDR = 0; \ /* set SDA as output */ \ DDR_USI |= ( 1 << PORT_USI_SDA ); \ @@ -259,18 +265,8 @@ Change Activity: } \ } -#define ONSTOP_USI_RECEIVE_CALLBACK() \ -{ \ - if (USISR & ( 1 << USIPF )) \ - { \ - USI_RECEIVE_CALLBACK(); \ - } \ -} - - #define USI_REQUEST_CALLBACK() \ { \ - USI_RECEIVE_CALLBACK(); \ if(usi_onRequestPtr) usi_onRequestPtr(); \ } @@ -299,6 +295,8 @@ typedef enum ********************************************************************************/ static uint8_t slaveAddress; +static uint8_t sleep_enable_bit; +static uint8_t in_transaction; static volatile overflowState_t overflowState; @@ -320,15 +318,9 @@ static volatile uint8_t txCount; ********************************************************************************/ - - // flushes the TWI buffers -static -void -flushTwiBuffers( - void -) +static void flushTwiBuffers( void ) { rxTail = 0; rxHead = 0; @@ -346,16 +338,11 @@ flushTwiBuffers( ********************************************************************************/ - - // initialise USI for TWI slave mode -void -usiTwiSlaveInit( - uint8_t ownAddress -) +void usiTwiSlaveInit( uint8_t ownAddress ) { - + // initialize the TX and RX buffers to empty flushTwiBuffers( ); slaveAddress = ownAddress; @@ -394,6 +381,10 @@ usiTwiSlaveInit( USISR = ( 1 << USI_START_COND_INT ) | ( 1 << USIOIF ) | ( 1 << USIPF ) | ( 1 << USIDC ); + // The 'in_transaction' variable remembers if the usiTwiSlave driver is in the middle of + // an i2c transaction. Initialize it to zero + in_transaction = 0; + } // end usiTwiSlaveInit @@ -408,14 +399,9 @@ bool usiTwiDataInTransmitBuffer(void) // put data in the transmission buffer, wait if buffer is full -void -usiTwiTransmitByte( - uint8_t data -) +void usiTwiTransmitByte( uint8_t data ) { - uint8_t tmphead; - // wait for free space in buffer while ( txCount == TWI_TX_BUFFER_SIZE) ; @@ -427,15 +413,9 @@ usiTwiTransmitByte( } // end usiTwiTransmitByte - - - // return a byte from the receive buffer, wait if buffer is empty -uint8_t -usiTwiReceiveByte( - void -) +uint8_t usiTwiReceiveByte( void ) { uint8_t rtn_byte; @@ -453,15 +433,12 @@ usiTwiReceiveByte( } // end usiTwiReceiveByte - uint8_t usiTwiAmountDataInReceiveBuffer(void) { return rxCount; } - - /******************************************************************************** USI Start Condition ISR @@ -470,15 +447,34 @@ uint8_t usiTwiAmountDataInReceiveBuffer(void) ISR( USI_START_VECTOR ) { + uint8_t usi_pins; + // http://www.atmel.com/webdoc/AVRLibcReferenceManual/group__avr__interrupts.html + + // Notes about ISR. The compiler in the Arduino IDE handles some of the + // basic ISR plumbing (unless the "ISR_NAKED" attribute is applied). + // * The AVR processor resets the SREG.I bit when jumping into an ISR + // * The compiler automatically adds code to save SREG + // * < user's ISR code goes here > + // * The compiler automatically adds code to restore SREG + // * The compiler automatically uses the RETI instruction to return from the ISR. + // The RETI instruction enables interrupts after the return from ISR. + // The compiler behavior can be altered with attributes into the ISR declaration; + // however, the description above is the default. + + // cli() call is not necessary. Processor disables interrupts when + // jumping to an ISR + + // no need to save the SREG. The compiler does this automatically when using the + // ISR construct without modifying attributes. + + if ( !in_transaction ) + { + // remeber the sleep enable bit when entering the ISR + sleep_enable_bit = MCUCR & ( 1 << SE ); - /* - // This triggers on second write, but claims to the callback there is only *one* byte in buffer - ONSTOP_USI_RECEIVE_CALLBACK(); - */ - /* - // This triggers on second write, but claims to the callback there is only *one* byte in buffer - USI_RECEIVE_CALLBACK(); - */ + // clear the sleep enable bit to prevent the CPU from entering sleep mode while executing this ISR. + MCUCR &= ~( 1 << SE ); + } // set default starting conditions for new TWI package overflowState = USI_SLAVE_CHECK_ADDRESS; @@ -486,23 +482,27 @@ ISR( USI_START_VECTOR ) // set SDA as input DDR_USI &= ~( 1 << PORT_USI_SDA ); + // the start condition is that the master pulls SDA low. + // wait for SCL to go low to ensure the Start Condition has completed (the // start detector will hold SCL low ) - if a Stop Condition arises then leave // the interrupt to prevent waiting forever - don't use USISR to test for Stop // Condition as in Application Note AVR312 because the Stop Condition Flag is // going to be set from the last TWI sequence - while ( - // SCL his high - ( PIN_USI & ( 1 << PIN_USI_SCL ) ) && - // and SDA is low - !( ( PIN_USI & ( 1 << PIN_USI_SDA ) ) ) - ); + // while SCL is high and SDA is low + while ( ( usi_pins = PIN_USI & USI_PINS_SCL_SDA ) == USI_PINS_SCL ); - if ( !( PIN_USI & ( 1 << PIN_USI_SDA ) ) ) + // if SDA line was low at SCL edge, then start condition occurred + if ( !( usi_pins & USI_PINS_SDA ) ) { - // a Stop Condition did not occur + + // Execute callback if this is a repeated start + if (in_transaction) + { + USI_RECEIVE_CALLBACK(); + } USICR = // keep Start Condition Interrupt enabled to detect RESTART @@ -516,6 +516,9 @@ ISR( USI_START_VECTOR ) ( 1 << USICS1 ) | ( 0 << USICS0 ) | ( 0 << USICLK ) | // no toggle clock-port pin ( 0 << USITC ); + + //remember that the USI is in a valid i2c transaction + in_transaction = 1; } else @@ -535,6 +538,11 @@ ISR( USI_START_VECTOR ) // no toggle clock-port pin ( 0 << USITC ); + //no longer in valid i2c transaction + in_transaction = 0; + // restore the sleep enable bit + MCUCR |= sleep_enable_bit; + } // end if USISR = @@ -545,6 +553,11 @@ ISR( USI_START_VECTOR ) // set USI to sample 8 bits (count 16 external SCL pin toggles) ( 0x0 << USICNT0); + // no need to restore the SREG. The compiler does this automatically when using the + // ISR construct without modifying attributes. + + // The compiler automatically uses an RETI instruction to return when using the + // ISR construct without modifying attributes. } // end ISR( USI_START_VECTOR ) @@ -562,6 +575,36 @@ Only disabled when waiting for a new Start Condition. ISR( USI_OVERFLOW_VECTOR ) { + uint8_t finished; + uint8_t usi_pins; + + // http://www.atmel.com/webdoc/AVRLibcReferenceManual/group__avr__interrupts.html + + // Notes about ISR. The compiler in the Arduino IDE handles some of the + // basic ISR plumbing. + // * The AVR processor resets the SREG.I bit when jumping into an ISR + // * The compiler automatically adds code to save the SREG + // * < user's ISR code goes here > + // * The compiler automatically adds code to restore the SREG + // * The compiler automatically uses the RETI instruction to return from the ISR. + // The RETI insturction enables interrupts after the return from ISR. + // The compiler behavior can be altered with attributes into the ISR declaration; + // however, the description above is the default. + + // cli() call is not necessary. Processor disables interrupts when + // jumping to an ISR + + // no need to save the SREG. The compiler does this automatically when using the + // ISR construct without modifying attributes. + + // The ISR is only ever entered because the ISR(USI_START_VECTOR) interrupt + // routine ran first. That routine saved the sleep mode and disabled sleep. + + // Most of the time this routine exits, it has setup the USI to shift in/out bits + // and is expected to re-entered because of the USI overflow interrupt. Track whether or + // not the transaction is completely finished. + finished = 0; + switch ( overflowState ) { @@ -573,29 +616,36 @@ ISR( USI_OVERFLOW_VECTOR ) { if ( USIDR & 0x01 ) { - USI_REQUEST_CALLBACK(); overflowState = USI_SLAVE_SEND_DATA; } else { overflowState = USI_SLAVE_REQUEST_DATA; } // end if + + // ack the start frame + // sets up the USI to pull SDA low and clock one bit (two edges) SET_USI_TO_SEND_ACK( ); } else { SET_USI_TO_TWI_START_CONDITION_MODE( ); + finished = 1; } break; - // Master write data mode: check reply and goto USI_SLAVE_SEND_DATA if OK, + // master-read / slave-send: check reply and goto USI_SLAVE_SEND_DATA if OK, // else reset USI case USI_SLAVE_CHECK_REPLY_FROM_SEND_DATA: + // Execute request callback for each byte requested, as this is the intended + // behavior of this library + USI_REQUEST_CALLBACK(); if ( USIDR ) { // if NACK, the master does not want more data SET_USI_TO_TWI_START_CONDITION_MODE( ); - return; + finished = 1; + break; } // from here we just drop straight into USI_SLAVE_SEND_DATA if the // master sent an ACK @@ -609,16 +659,16 @@ ISR( USI_OVERFLOW_VECTOR ) USIDR = txBuf[ txTail ]; txTail = ( txTail + 1 ) & TWI_TX_BUFFER_MASK; txCount--; + + overflowState = USI_SLAVE_REQUEST_REPLY_FROM_SEND_DATA; + SET_USI_TO_SEND_DATA( ); } else { // the buffer is empty SET_USI_TO_READ_ACK( ); // This might be neccessary sometimes see http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&p=805227#805227 SET_USI_TO_TWI_START_CONDITION_MODE( ); - return; } // end if - overflowState = USI_SLAVE_REQUEST_REPLY_FROM_SEND_DATA; - SET_USI_TO_SEND_DATA( ); break; // set USI to sample reply from master @@ -628,11 +678,35 @@ ISR( USI_OVERFLOW_VECTOR ) SET_USI_TO_READ_ACK( ); break; - // Master read data mode: set USI to sample data from master, next + // master-send / slave-receive: set USI to sample data from master, next // USI_SLAVE_GET_DATA_AND_SEND_ACK case USI_SLAVE_REQUEST_DATA: overflowState = USI_SLAVE_GET_DATA_AND_SEND_ACK; SET_USI_TO_READ_DATA( ); + + // with the SET_USI_TO_READ_DATA() macro call above, the USI has + // been setup to catch the next byte if the master sends one. + // while that's going on, look for a stop condition here which + // is when the SDA line goes high after the SCL line; + + // wait until SCL goes high + while ( ! ( ( usi_pins = PIN_USI & USI_PINS_SCL_SDA ) & USI_PINS_SCL ) ); + + // if SDA line was high at SCL edge, then not a stop condition + if ( usi_pins & USI_PINS_SDA ) + break; + + // wait until SCL goes low or SDA goes high + while ( ( usi_pins = PIN_USI & USI_PINS_SCL_SDA ) == USI_PINS_SCL ); + + // if both SCL and SDA are high, then stop condition occurred + if ( usi_pins == USI_PINS_SCL_SDA ) + { + USI_RECEIVE_CALLBACK(); + SET_USI_TO_TWI_START_CONDITION_MODE( ); + finished = 1; + } + break; // copy data from USIDR and send ACK @@ -656,4 +730,19 @@ ISR( USI_OVERFLOW_VECTOR ) } // end switch + if (finished) + { + //no longer in valid i2c transaction + in_transaction = 0; + // restore the sleep enable bit + // note that this allows sleep -- it does not cause sleep + MCUCR |= sleep_enable_bit; + } + + // no need to restore the SREG. The compiler does this automatically when using the + // ISR construct without modifying attributes. + + // The compiler automatically uses an RETI instruction to return when using the + // ISR construct without modifying attributes. + } // end ISR( USI_OVERFLOW_VECTOR ) diff --git a/TinyWireS/usiTwiSlave.h b/TinyWireS/usiTwiSlave.h index 8ffdda2..88b9445 100644 --- a/TinyWireS/usiTwiSlave.h +++ b/TinyWireS/usiTwiSlave.h @@ -43,6 +43,7 @@ Change Activity: ********************************************************************************/ #include +#include @@ -55,8 +56,6 @@ Change Activity: void usiTwiSlaveInit( uint8_t ); void usiTwiTransmitByte( uint8_t ); uint8_t usiTwiReceiveByte( void ); -bool usiTwiDataInReceiveBuffer( void ); -void (*_onTwiDataRequest)(void); bool usiTwiDataInTransmitBuffer(void); uint8_t usiTwiAmountDataInReceiveBuffer(void); // on_XXX handler pointers