From 9d678fbace2e4931e39ab5b95b2ec7493a18f549 Mon Sep 17 00:00:00 2001 From: Blaise Thompson Date: Mon, 25 Jan 2021 12:15:05 -0600 Subject: digital-driver readme --- .../TinyWireS_Stress_Master.ino | 184 +++++++++++++++ .../TinyWireS_Stress_Slave.ino | 143 ++++++++++++ .../attiny85_i2c_analog/attiny85_i2c_analog.ino | 210 ++++++++++++++++++ .../attiny85_i2c_slave/attiny85_i2c_slave.ino | 152 +++++++++++++ .../attiny85_i2c_slave_task.ino | 246 +++++++++++++++++++++ 5 files changed, 935 insertions(+) create mode 100644 digital-driver/firmware/TinyWireS/examples/TinyWireS_Stress_Master/TinyWireS_Stress_Master.ino create mode 100644 digital-driver/firmware/TinyWireS/examples/TinyWireS_Stress_Slave/TinyWireS_Stress_Slave.ino create mode 100644 digital-driver/firmware/TinyWireS/examples/attiny85_i2c_analog/attiny85_i2c_analog.ino create mode 100644 digital-driver/firmware/TinyWireS/examples/attiny85_i2c_slave/attiny85_i2c_slave.ino create mode 100644 digital-driver/firmware/TinyWireS/examples/attiny85_i2c_slave_task/attiny85_i2c_slave_task.ino (limited to 'digital-driver/firmware/TinyWireS/examples') diff --git a/digital-driver/firmware/TinyWireS/examples/TinyWireS_Stress_Master/TinyWireS_Stress_Master.ino b/digital-driver/firmware/TinyWireS/examples/TinyWireS_Stress_Master/TinyWireS_Stress_Master.ino new file mode 100644 index 0000000..d50adb7 --- /dev/null +++ b/digital-driver/firmware/TinyWireS/examples/TinyWireS_Stress_Master/TinyWireS_Stress_Master.ino @@ -0,0 +1,184 @@ +// --------------------------------- +// Stress test program/example for TinyWireS I2C library. +// Run this master program on the Arduino Uno R3. +// Run the other slave program on the Attiny. +// --------------------------------- +// Written by Scott Hartog, 2/6/2016 +// This is the I2C master program which runs on on a regular Arduino +// (not a AtTiny). This program uses the regular Wire library from the Arduino IDE. +// +// It performs these steps in a loop: +// 1. picks a random number of bytes between 1 and 12 +// 2. sends that many bytes of random data to the AtTiny slave within +// a single Wire.beginTransmission() / Wire.write() / Wire.endTransmission() set +// 3. reads that same number of bytes back with a single Wire.requestFrom() call +// 4. compares the received data to the originally transmitted data +// 5. displays the number of requests, number of requests with mismatches, +// and enough of the data so that the operator can tell it's working. +// +#include + +// BREADBOARD SETUP: +// Arduino Uno R3 (D18/SDA) = I2C SDA +// connect to SDA on slave with external pull-up (~4.7K) +// Arduino Uno R3 (D19/SCL) = I2C SCL +// connect to SCL on slave with external pull-up (~4.7K) +// Arduino Uno R3 (D2) +// connect to !RST on slave +// Can alternatively connect !RST on slave to the Ardiuno "!RESET" pin + +#define I2C_SLAVE_ADDR 0x26 // i2c slave address (38, 0x26) + +#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 + +char c_buf[64]; // for creating messages + +void setup() +{ + // set pin modes + 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(9600); + + // print some useful pinnout info for the Arduino + //Serial.println(String("SCL:")+String(SCL)+String(", SDA:")+String(SDA)); + //Serial.println(String("MOSI:")+String(MOSI)+String(", SCK:")+String(SCK)); + + // 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); +} + +void loop() +{ + uint8_t i; + uint8_t req_rtn; // num bytes returned by requestFrom() call + uint8_t rand_byte_count; + uint8_t out_rand[16]; // data written from master + uint8_t in_rand[16]; // data read back from slave + + bool mismatch; + + // count total number of request + count++; + + // compute random number of bytes for this pass + 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 ( !txCount );". + if (count <= 3) rand_byte_count = 2; + + // generate, save, and send N random byte values + Wire.beginTransmission(I2C_SLAVE_ADDR); + for (i = 0; i < rand_byte_count; i++) + Wire.write(out_rand[i] = random(256)); + Wire.endTransmission(); + + // 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++) + in_rand[i] = Wire.read(); + + // compare in/out data values + mismatch = false; + for (i = 0; i < rand_byte_count; i++) + mismatch = mismatch || (out_rand[i] != in_rand[i]); + + // 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++; + 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 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); + Serial.println(c_buf); + + // display the random byte count, the number of bytes read back, and "MATCH"/"MISMATCH" + snprintf(c_buf, sizeof(c_buf), "size: %2d/%2d,%s", rand_byte_count, req_rtn, rand_byte_count != req_rtn?"MISMATCH <<--- !!!":"MATCH"); + Serial.println(c_buf); + + // display whether the data compare matched or mismatched + snprintf(c_buf, sizeof(c_buf), "data: %s", mismatch?"MISMATCH <<--- !!!":"MATCH"); + Serial.println(c_buf); + + // send up to three tx/rx bytes so that random data can be + // visually verified + if (rand_byte_count >= 1) + { + snprintf(c_buf, sizeof(c_buf), "rand[0]: %02x/%02x", out_rand[0], in_rand[0]); + Serial.println(c_buf); + } + + if (rand_byte_count >= 2) + { + snprintf(c_buf, sizeof(c_buf), "rand[1]: %02x/%02x", out_rand[1], in_rand[1]); + Serial.println(c_buf); + } + + if (rand_byte_count >= 3) + { + snprintf(c_buf, sizeof(c_buf), "rand[2]: %02x/%02x", out_rand[2], in_rand[2]); + Serial.println(c_buf); + } + + // delay 1 second so user can watch results + delay(1000); +} diff --git a/digital-driver/firmware/TinyWireS/examples/TinyWireS_Stress_Slave/TinyWireS_Stress_Slave.ino b/digital-driver/firmware/TinyWireS/examples/TinyWireS_Stress_Slave/TinyWireS_Stress_Slave.ino new file mode 100644 index 0000000..c7fbe41 --- /dev/null +++ b/digital-driver/firmware/TinyWireS/examples/TinyWireS_Stress_Slave/TinyWireS_Stress_Slave.ino @@ -0,0 +1,143 @@ +// --------------------------------- +// Stress test program/example for TinyWireS I2C library. +// Run this slave program on the Attiny. +// Run the other master program on the Arduino Uno R3. +// --------------------------------- +// // Written by Scott Hartog, 2/6/2016, to stress test the TinyWireS library. +// https://github.com/rambo/TinyWire +// +// This project uses the Tiny85 as an I2C slave. +// +// The slave program using TinyWireS, running on a Attiny85, receives +// N bytes of random data in a single receiveEvent() callback and +// stores that data in a global buffer. It then responds the first requestEvent() +// callback with that same data. The requestEvent() callback overwrites the data +// buffer with zeros after responding so it will only respond correctly to the +// first requestEvent() callback after each receiveEvent() callback. Subsequent +// requestEvent() will respond with 0xff for all data bytes. +// +// +// SETUP: +// AtTiny Pin 5 (PB0/SDA) = I2C SDA +// connect to SDA on master with external pull-up (~4.7K) +// AtTiny Pin 7 (PB0/SCL) = I2C SCL +// connect to SCL on master with external pull-up (~4.7K) +// AtTiny Pin 1 (PB5/!RST) +// connect to reset on master (or just pull-up) +// +// 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; + + // send the data buffer back to the master + for (i = 0; i < master_bytes; i++) + TinyWireS.send(master_data[i]); + + // corrupt the byte values in the data buffer + // 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 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 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*. (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. + // Making it small will allow a few requests to succeed before the tx buffer + // overflows and the usiTwiSlave.c hangs on the "while ( tmphead == txTail );" + // line + master_bytes = 2; +} + +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 + + // register the onReceive() callback function + TinyWireS.onReceive(receiveEvent); + + // 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() +{ + +#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/digital-driver/firmware/TinyWireS/examples/attiny85_i2c_analog/attiny85_i2c_analog.ino b/digital-driver/firmware/TinyWireS/examples/attiny85_i2c_analog/attiny85_i2c_analog.ino new file mode 100644 index 0000000..7190c93 --- /dev/null +++ b/digital-driver/firmware/TinyWireS/examples/attiny85_i2c_analog/attiny85_i2c_analog.ino @@ -0,0 +1,210 @@ +/** + * Example sketch for writing to and reading from a slave in transactional manner + * + * NOTE: You must not use delay() or I2C communications will fail, use tws_delay() instead (or preferably some smarter timing system) + * + * On write the first byte received is considered the register addres to modify/read + * On each byte sent or read the register address is incremented (and it will loop back to 0) + * + * You can try this with the Arduino I2C REPL sketch at https://github.com/rambo/I2C/blob/master/examples/i2crepl/i2crepl.ino + * If you have bus-pirate remember that the older revisions do not like the slave streching the clock, this leads to all sorts of weird behaviour + * Examples use bus-pirate semantics (like the REPL) + * + * The basic idea is: + * 1. Choose your ADC channel (0-X), use "byte ch = 1;" for example. + * 2. Combine the channel and conversion start flag to single calue: byte start_on_ch = (ch | _BV(7)); // This is 0x81 + * 3. Write start_on_ch to the first register on the attiny [ 8 0 81 ] + * 4. Come back later and check the first register [ 8 0 [ r ], if the value is same as ch then the conversion is complete, you can now read the value + * 5. read the value [ 8 2 [ r r ] (first one is low, second high byte) + * + * You need to have at least 8MHz clock on the ATTiny for this to work (and in fact I have so far tested it only on ATTiny85 @8MHz using internal oscillator) + * Remember to "Burn bootloader" to make sure your chip is in correct mode + */ + + +/** + * Pin notes by Suovula, see also http://hlt.media.mit.edu/?p=1229 + * + * DIP and SOIC have same pinout, however the SOIC chips are much cheaper, especially if you buy more than 5 at a time + * For nice breakout boards see https://github.com/rambo/attiny_boards + * + * Basically the arduino pin numbers map directly to the PORTB bit numbers. + * +// I2C +arduino pin 0 = not(OC1A) = PORTB <- _BV(0) = SOIC pin 5 (I2C SDA, PWM) +arduino pin 2 = = PORTB <- _BV(2) = SOIC pin 7 (I2C SCL, Analog 1) +// Timer1 -> PWM +arduino pin 1 = OC1A = PORTB <- _BV(1) = SOIC pin 6 (PWM) +arduino pin 3 = not(OC1B) = PORTB <- _BV(3) = SOIC pin 2 (Analog 3) +arduino pin 4 = OC1B = PORTB <- _BV(4) = SOIC pin 3 (Analog 2) + */ +#define I2C_SLAVE_ADDRESS 0x4 // the 7-bit address (remember to change this when adapting this example) +// Get this from https://github.com/rambo/TinyWire +#include +// The default buffer size, though we cannot actually affect it by defining it in the sketch +#ifndef TWI_RX_BUFFER_SIZE +#define TWI_RX_BUFFER_SIZE ( 16 ) +#endif +// For the ADC_xxx helpers +#include + +// The "registers" we expose to I2C +volatile uint8_t i2c_regs[] = +{ + 0x0, // Status register, writing (1<<7 & channel) will start a conversion on that channel, the flag will be set low when conversion is done. + 0x1, // Averaging count, make this many conversions in row and average the result (well, actually it's a rolling average since we do not want to have the possibility of integer overflows) + 0x0, // low byte + 0x0, // high byte +}; +const byte reg_size = sizeof(i2c_regs); +// Tracks the current register pointer position +volatile byte reg_position; +// Tracks wheter to start a conversion cycle +volatile boolean start_conversion; +// Counter to track where we are averaging +byte avg_count; +// Some temp value holders +int avg_temp1; +int avg_temp2; + +/** + * This is called for each read request we receive, never put more than one byte of data (with TinyWireS.send) to the + * send-buffer when using this callback + */ +void requestEvent() +{ + TinyWireS.send(i2c_regs[reg_position]); + // Increment the reg position on each read, and loop back to zero + reg_position++; + if (reg_position >= reg_size) + { + reg_position = 0; + } +} + +/** + * The I2C data received -handler + * + * This needs to complete before the next incoming transaction (start, data, restart/stop) on the bus does + * so be quick, set flags for long running tasks to be called from the mainloop instead of running them directly, + */ +void receiveEvent(uint8_t howMany) +{ + if (howMany < 1) + { + // Sanity-check + return; + } + if (howMany > TWI_RX_BUFFER_SIZE) + { + // Also insane number + return; + } + + reg_position = TinyWireS.receive(); + howMany--; + if (!howMany) + { + // This write was only to set the buffer for next read + return; + } + while(howMany--) + { + i2c_regs[reg_position] = TinyWireS.receive(); + if ( reg_position == 0 // If it was the first register + && bitRead(i2c_regs[0], 7) // And the highest bit is set + && !ADC_ConversionInProgress() // and we do not actually have a conversion running already + ) + { + start_conversion = true; + } + reg_position++; + if (reg_position >= reg_size) + { + reg_position = 0; + } + } +} + + +void setup() +{ + // TODO: Tri-state this and wait for input voltage to stabilize + pinMode(3, OUTPUT); // OC1B-, Arduino pin 3, ADC + digitalWrite(3, LOW); // Note that this makes the led turn on, it's wire this way to allow for the voltage sensing above. + + pinMode(1, OUTPUT); // OC1A, also The only HW-PWM -pin supported by the tiny core analogWrite + + /** + * Reminder: taking care of pull-ups is the masters job + */ + + TinyWireS.begin(I2C_SLAVE_ADDRESS); + TinyWireS.onReceive(receiveEvent); + TinyWireS.onRequest(requestEvent); + + + // Whatever other setup routines ? + + digitalWrite(3, HIGH); +} + +void loop() +{ + /** + * This is the only way we can detect stop condition (http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&p=984716&sid=82e9dc7299a8243b86cf7969dd41b5b5#984716) + * it needs to be called in a very tight loop in order not to miss any (REMINDER: Do *not* use delay() anywhere, use tws_delay() instead). + * It will call the function registered via TinyWireS.onReceive(); if there is data in the buffer on stop. + */ + TinyWireS_stop_check(); + + // Thus stuff is basically copied from wiring_analog.c + if (start_conversion) + { + //Avoid doubled starts + start_conversion = false; + byte adcpin = (i2c_regs[0] & 0x7f); // Set the channel from the control reg, dropping the highest bit. +#if defined( CORE_ANALOG_FIRST ) + if ( adcpin >= CORE_ANALOG_FIRST ) adcpin -= CORE_ANALOG_FIRST; // allow for channel or pin numbers +#endif + // NOTE: These handy helpers (ADC_xxx) are only present in the tiny-core, for other cores you need to check their wiring_analog.c source. + ADC_SetInputChannel( (adc_ic_t)adcpin ); // we need to typecast + ADC_StartConversion(); + // Reset these variables + avg_count = 0; + avg_temp2 = 0; + } + + if ( bitRead(i2c_regs[0], 7) // We have conversion flag up + && !ADC_ConversionInProgress()) // But the conversion is complete + { + // So handle it + avg_temp1 = ADC_GetDataRegister(); + // Rolling average + if (avg_count) + { + avg_temp2 = (avg_temp2+avg_temp1)/2; + } + else + { + avg_temp2 = avg_temp1; + } + avg_count++; + if (avg_count >= i2c_regs[1]) + { + // All done, set the bytes to registers + cli(); + i2c_regs[2] = lowByte(avg_temp2); + i2c_regs[3] = highByte(avg_temp2); + sei(); + // And clear the conversion flag so the master knows we're ready + bitClear(i2c_regs[0], 7); + } + else + { + // Re-trigger conversion + ADC_StartConversion(); + } + } + +} diff --git a/digital-driver/firmware/TinyWireS/examples/attiny85_i2c_slave/attiny85_i2c_slave.ino b/digital-driver/firmware/TinyWireS/examples/attiny85_i2c_slave/attiny85_i2c_slave.ino new file mode 100644 index 0000000..df2532f --- /dev/null +++ b/digital-driver/firmware/TinyWireS/examples/attiny85_i2c_slave/attiny85_i2c_slave.ino @@ -0,0 +1,152 @@ +/** + * Example sketch for writing to and reading from a slave in transactional manner + * + * NOTE: You must not use delay() or I2C communications will fail, use tws_delay() instead (or preferably some smarter timing system) + * + * On write the first byte received is considered the register addres to modify/read + * On each byte sent or read the register address is incremented (and it will loop back to 0) + * + * You can try this with the Arduino I2C REPL sketch at https://github.com/rambo/I2C/blob/master/examples/i2crepl/i2crepl.ino + * If you have bus-pirate remember that the older revisions do not like the slave streching the clock, this leads to all sorts of weird behaviour + * + * To read third value (register number 2 since counting starts at 0) send "[ 8 2 [ 9 r ]", value read should be 0xBE + * If you then send "[ 9 r r r ]" you should get 0xEF 0xDE 0xAD as response (demonstrating the register counter looping back to zero) + * + * You need to have at least 8MHz clock on the ATTiny for this to work (and in fact I have so far tested it only on ATTiny85 @8MHz using internal oscillator) + * Remember to "Burn bootloader" to make sure your chip is in correct mode + */ + + +/** + * Pin notes by Suovula, see also http://hlt.media.mit.edu/?p=1229 + * + * DIP and SOIC have same pinout, however the SOIC chips are much cheaper, especially if you buy more than 5 at a time + * For nice breakout boards see https://github.com/rambo/attiny_boards + * + * Basically the arduino pin numbers map directly to the PORTB bit numbers. + * +// I2C +arduino pin 0 = not(OC1A) = PORTB <- _BV(0) = SOIC pin 5 (I2C SDA, PWM) +arduino pin 2 = = PORTB <- _BV(2) = SOIC pin 7 (I2C SCL, Analog 1) +// Timer1 -> PWM +arduino pin 1 = OC1A = PORTB <- _BV(1) = SOIC pin 6 (PWM) +arduino pin 3 = not(OC1B) = PORTB <- _BV(3) = SOIC pin 2 (Analog 3) +arduino pin 4 = OC1B = PORTB <- _BV(4) = SOIC pin 3 (Analog 2) + */ +#define I2C_SLAVE_ADDRESS 0x4 // the 7-bit address (remember to change this when adapting this example) +// Get this from https://github.com/rambo/TinyWire +#include +// The default buffer size, Can't recall the scope of defines right now +#ifndef TWI_RX_BUFFER_SIZE +#define TWI_RX_BUFFER_SIZE ( 16 ) +#endif + + +volatile uint8_t i2c_regs[] = +{ + 0xDE, + 0xAD, + 0xBE, + 0xEF, +}; +// Tracks the current register pointer position +volatile byte reg_position; +const byte reg_size = sizeof(i2c_regs); + +/** + * This is called for each read request we receive, never put more than one byte of data (with TinyWireS.send) to the + * send-buffer when using this callback + */ +void requestEvent() +{ + TinyWireS.send(i2c_regs[reg_position]); + // Increment the reg position on each read, and loop back to zero + reg_position++; + if (reg_position >= reg_size) + { + reg_position = 0; + } +} + +// TODO: Either update this to use something smarter for timing or remove it alltogether +void blinkn(uint8_t blinks) +{ + digitalWrite(3, HIGH); + while(blinks--) + { + digitalWrite(3, LOW); + tws_delay(50); + digitalWrite(3, HIGH); + tws_delay(100); + } +} + +/** + * The I2C data received -handler + * + * This needs to complete before the next incoming transaction (start, data, restart/stop) on the bus does + * so be quick, set flags for long running tasks to be called from the mainloop instead of running them directly, + */ +void receiveEvent(uint8_t howMany) +{ + if (howMany < 1) + { + // Sanity-check + return; + } + if (howMany > TWI_RX_BUFFER_SIZE) + { + // Also insane number + return; + } + + reg_position = TinyWireS.receive(); + howMany--; + if (!howMany) + { + // This write was only to set the buffer for next read + return; + } + while(howMany--) + { + i2c_regs[reg_position] = TinyWireS.receive(); + reg_position++; + if (reg_position >= reg_size) + { + reg_position = 0; + } + } +} + + +void setup() +{ + // TODO: Tri-state this and wait for input voltage to stabilize + pinMode(3, OUTPUT); // OC1B-, Arduino pin 3, ADC + digitalWrite(3, LOW); // Note that this makes the led turn on, it's wire this way to allow for the voltage sensing above. + + pinMode(1, OUTPUT); // OC1A, also The only HW-PWM -pin supported by the tiny core analogWrite + + /** + * Reminder: taking care of pull-ups is the masters job + */ + + TinyWireS.begin(I2C_SLAVE_ADDRESS); + TinyWireS.onReceive(receiveEvent); + TinyWireS.onRequest(requestEvent); + + + // Whatever other setup routines ? + + digitalWrite(3, HIGH); +} + +void loop() +{ + /** + * This is the only way we can detect stop condition (http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&p=984716&sid=82e9dc7299a8243b86cf7969dd41b5b5#984716) + * it needs to be called in a very tight loop in order not to miss any (REMINDER: Do *not* use delay() anywhere, use tws_delay() instead). + * It will call the function registered via TinyWireS.onReceive(); if there is data in the buffer on stop. + */ + TinyWireS_stop_check(); +} diff --git a/digital-driver/firmware/TinyWireS/examples/attiny85_i2c_slave_task/attiny85_i2c_slave_task.ino b/digital-driver/firmware/TinyWireS/examples/attiny85_i2c_slave_task/attiny85_i2c_slave_task.ino new file mode 100644 index 0000000..4b3f269 --- /dev/null +++ b/digital-driver/firmware/TinyWireS/examples/attiny85_i2c_slave_task/attiny85_i2c_slave_task.ino @@ -0,0 +1,246 @@ +/** + * Example sketch for writing to and reading from a slave in transactional manner, it will also blink a led attached to pin 3 (which is the SOIC pin 2) + * (provided you're using one of my ATTiny85 boards from https://github.com/rambo/attiny_boards with the led soldered) + * + * NOTE: You must not use delay() or I2C communications will fail, use tws_delay() instead (or preferably some smarter timing system, like the Task library used in this example) + * + * On write the first byte received is considered the register addres to modify/read + * On each byte sent or read the register address is incremented (and it will loop back to 0) + * + * You can try this with the Arduino I2C REPL sketch at https://github.com/rambo/I2C/blob/master/examples/i2crepl/i2crepl.ino + * If you have bus-pirate remember that the older revisions do not like the slave streching the clock, this leads to all sorts of weird behaviour + * + * By default this blinks the SOS morse pattern and then has long on/off time to indicate end of pattern, send [ 8 0 32 ] (using the REPL/bus-pirate + * semantics) to make the delay per bit smaller (and thus blinking faster). The pattern lenght is calculated from the register size, it would be fairly + * trivial to make it yet another variable changeable via I2C. + * + * You need to have at least 8MHz clock on the ATTiny for this to work (and in fact I have so far tested it only on ATTiny85 @8MHz using internal oscillator) + * Remember to "Burn bootloader" to make sure your chip is in correct mode + */ + + +/** + * Pin notes by Suovula, see also http://hlt.media.mit.edu/?p=1229 + * + * DIP and SOIC have same pinout, however the SOIC chips are much cheaper, especially if you buy more than 5 at a time + * For nice breakout boards see https://github.com/rambo/attiny_boards + * + * Basically the arduino pin numbers map directly to the PORTB bit numbers. + * +// I2C +arduino pin 0 = not(OC1A) = PORTB <- _BV(0) = SOIC pin 5 (I2C SDA, PWM) +arduino pin 2 = = PORTB <- _BV(2) = SOIC pin 7 (I2C SCL, Analog 1) +// Timer1 -> PWM +arduino pin 1 = OC1A = PORTB <- _BV(1) = SOIC pin 6 (PWM) +arduino pin 3 = not(OC1B) = PORTB <- _BV(3) = SOIC pin 2 (Analog 3) +arduino pin 4 = OC1B = PORTB <- _BV(4) = SOIC pin 3 (Analog 2) + */ +#define I2C_SLAVE_ADDRESS 0x4 // the 7-bit address (remember to change this when adapting this example) +// Get this from https://github.com/rambo/TinyWire +#include +// The default buffer size, Can't recall the scope of defines right now +#ifndef TWI_RX_BUFFER_SIZE +#define TWI_RX_BUFFER_SIZE ( 16 ) +#endif +// Get this library from http://bleaklow.com/files/2010/Task.tar.gz +// and read http://bleaklow.com/2010/07/20/a_very_simple_arduino_task_manager.html for background and instructions +#include +#include + +// The led is connected so that the tiny sinks current +#define LED_ON LOW +#define LED_OFF HIGH + +// The I2C registers +volatile uint8_t i2c_regs[] = +{ + 150, // Delay between each position (ms, remeber that this isa byte so 255 is max) + B10101000, // SOS pattern + B01110111, + B01110001, + B01010000, + B00000000, + B11111111, // Long on and off to mark end of pattern + B00000000, +}; +// Tracks the current register pointer position +volatile byte reg_position; +const byte reg_size = sizeof(i2c_regs); + + +/** + * BEGIN: PatternBlinker task based on the Task library Blinker example + */ +// Timed task to blink a LED. +const byte pattern_lenght = (sizeof(i2c_regs)-1) * 8; // bits (first is the speed, rest is the pattern) +class PatternBlinker : public TimedTask +{ +public: + // Create a new blinker for the specified pin and rate. + PatternBlinker(uint8_t _pin); + virtual void run(uint32_t now); +private: + uint8_t pin; // LED pin. + uint8_t pattern_position; // Used to calcuate the register and bit offset +}; + +PatternBlinker::PatternBlinker(uint8_t _pin) +: TimedTask(millis()), + pin(_pin) +{ + pinMode(pin, OUTPUT); // Set pin for output. +} + +void PatternBlinker::run(uint32_t now) +{ + // Start by setting the next runtime + incRunTime(i2c_regs[0]); + + // Written out for clear code, the complier might optimize it to something more efficient even without it being unrolled into one line + byte reg = i2c_regs[1+(pattern_position/8)]; // Get the register where the bit pattern position is stored + byte shift_amount = 7 - (pattern_position % 7); // To have "natural" left-to-right pattern flow. + bool state = (reg >> shift_amount) & 0x1; + if (state) { + digitalWrite(pin, LED_ON); + } else { + digitalWrite(pin, LED_OFF); + } + // Calculate the next pattern position + pattern_position = (pattern_position+1) % pattern_lenght; +} +/** + * END: PatternBlinker task copied from the Task library example + */ +/** + * BEGIN: I2C Stop flag checker + * + * This task needs to run almost all the time due to the USI I2C implementation limitations + * + * So I2CStopCheck_YIELD_TICKS below is used to specify how often the task is run, not it's every 4 ticks + */ +#define I2CStopCheck_YIELD_TICKS 4 +class I2CStopCheck : public Task +{ +public: + I2CStopCheck(); + virtual void run(uint32_t now); + virtual bool canRun(uint32_t now); +private: + uint8_t yield_counter; // Incremented on each canRun call, used to yield to other tasks. +}; + +I2CStopCheck::I2CStopCheck() +: Task() +{ +} + +// We can't just return true since then no other task could ever run (since we have the priority) +bool I2CStopCheck::canRun(uint32_t now) +{ + yield_counter++; + bool ret = false; + if (yield_counter == I2CStopCheck_YIELD_TICKS) + { + ret = true; + yield_counter = 0; + } + return ret; +} + +void I2CStopCheck::run(uint32_t now) +{ + TinyWireS_stop_check(); +} +/** + * END: I2C Stop flag checker + */ + +// Create the tasks. +PatternBlinker blinker(3); +I2CStopCheck checker; + +// Tasks are in priority order, only one task is run per tick +Task *tasks[] = { &checker, &blinker, }; +TaskScheduler sched(tasks, NUM_TASKS(tasks)); + + +/** + * This is called for each read request we receive, never put more than one byte of data (with TinyWireS.send) to the + * send-buffer when using this callback + */ +void requestEvent() +{ + TinyWireS.send(i2c_regs[reg_position]); + // Increment the reg position on each read, and loop back to zero + reg_position++; + if (reg_position >= reg_size) + { + reg_position = 0; + } +} + +/** + * The I2C data received -handler + * + * This needs to complete before the next incoming transaction (start, data, restart/stop) on the bus does + * so be quick, set flags for long running tasks to be called from the mainloop instead of running them directly, + */ +void receiveEvent(uint8_t howMany) +{ + if (howMany < 1) + { + // Sanity-check + return; + } + if (howMany > TWI_RX_BUFFER_SIZE) + { + // Also insane number + return; + } + + reg_position = TinyWireS.receive(); + howMany--; + if (!howMany) + { + // This write was only to set the buffer for next read + return; + } + while(howMany--) + { + i2c_regs[reg_position] = TinyWireS.receive(); + reg_position++; + if (reg_position >= reg_size) + { + reg_position = 0; + } + } +} + + +void setup() +{ + // TODO: Tri-state this and wait for input voltage to stabilize + pinMode(3, OUTPUT); // OC1B-, Arduino pin 3, ADC + digitalWrite(3, LED_ON); // Note that this makes the led turn on, it's wire this way to allow for the voltage sensing above. + + pinMode(1, OUTPUT); // OC1A, also The only HW-PWM -pin supported by the tiny core analogWrite + + /** + * Reminder: taking care of pull-ups is the masters job + */ + + TinyWireS.begin(I2C_SLAVE_ADDRESS); + TinyWireS.onReceive(receiveEvent); + TinyWireS.onRequest(requestEvent); + + + // Whatever other setup routines ? + + digitalWrite(3, LED_OFF); +} + +void loop() +{ + // Run the scheduler - never returns. + sched.run(); +} -- cgit v1.2.3