aboutsummaryrefslogtreecommitdiff
path: root/digital-driver-board/firmware/TinyWire-master/TinyWireS/examples/attiny85_i2c_slave_task/attiny85_i2c_slave_task.ino
blob: 4b3f26932086491ac9eebc89ee06c874eaeb82c1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
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 <TinyWireS.h>
// 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 <Task.h>
#include <TaskScheduler.h>

// 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();
}