Skip to content

Instantly share code, notes, and snippets.

@Rawze
Last active June 22, 2023 21:59
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Rawze/61df2bd9e86b131faeb5140005507520 to your computer and use it in GitHub Desktop.
Save Rawze/61df2bd9e86b131faeb5140005507520 to your computer and use it in GitHub Desktop.
Arduino Attiny85 as 2-channel ana in + 1 chan pwm I2C slave device.
/*
ATtiny85 as 2-channel Analog + 1 PWM Output I2C Slave by Rawze. 05-16-2017
Default I2C adress = 0x04.
Program written and tested with ATTiny85 Arduino 8MHz internal clock bootloader.
Don't forget to select 8-Mhz internal clock and flash the bootloader on new hardware before first use.
Using this program...
Master sends a request to the I2C buss at address for slave (default address is 0x04). The slave will respond
with 3 16-bit words (6-byte) message. The words are as follows...
* first 16-bit word = The value of the A2 pin on the attiny85. This is hardware pin #3.
* second 16-bit word = The value of the A3 pin on the attiny85. This is hardware pin #2.
* third 16-bit word = The value of the pwm output that was last asigned to hardware pin #6 pwm pin on the attiny85.
The program will also recognize simple commands. A command consists of 3 bytes as follows...
* byte #1 is the command. 8-bit.
* Byte #2 is the high order for 16-bit data that is associated with the command.
* Byte#3 is the low order for the 16-bit data associated with the command.
Commands...
cmd byte = 0xC1 -- Assign a new I2c device adress and store it into EEPROM. The valid address is from 0x04 - 0x7E.
The lower order of the 16-bit data block contains the address. An example command to change the address to
0x10 would be the data sequence of "0xC1,0x00,0x10".
cmd byte = 0xC2 -- Assign pwm output to hardware pin#6. Valid values are from 0 -255. The lower order of the 16-bit data block
contains the value.An example command to set the pwm to 0x03 would be a data sequence of "0xC2,0x00,0x03".
Here is an axample service routine that can be used on the master device for accessing the slave ...
-------------------------------------------------------------------------------------------------------------
namespace ATTINY85_DATA{
enum{
a2_data = 0x00,
a3_data = 0x01,
pwm_data = 0x02,
msg_len = 0x03,
byte_len = 0x06,
slave_device_id = 0x04,
cmd_set_pwm_output = 0xc2,
update_uinterval = 300 //update interval in milliseconds.
};
volatile uint32_t last_update = 0;
volatile uint16_t buffer[ATTINY85_DATA::msg_len] = {0};
bool update(uint32_t _now) { //returns true only when a new data packet is received, otherwise returns false.
if ((_now - ATTINY85_DATA::last_update) > ATTINY85_DATA::update_uinterval){
ATTINY85_DATA::last_update = _now;
Wire.requestFrom(ATTINY85_DATA::slave_device_id, (ATTINY85_DATA::byte_len) ); //request x bytes from slave device.
//delay(1); //optional: allow extra time for slave to answer?
uint8_t i = 0;
unsigned char *pointer = (unsigned char*)&ATTINY85_DATA::buffer;
if (!Wire.available()) return false; //no data return false.
while(Wire.available()){ // slave may send less than requested
uint8_t next_byte = Wire.read(); //receive next byte
if(i < ATTINY85_DATA::byte_len) {
pointer[i] = next_byte; //copy into buffer;
}
++i;
}
return true; //new info has been received. NOTE: This does not qualify it as valid data. Must be checked separately.
}
return false;
}
void set_pwm_output(uint8_t new_pwm_value){
char tx_data[3] = {0};
tx_data[0] = ATTINY85_DATA::cmd_set_pwm_output; //assign command.
tx_data[1] = 0; //upper 16-bit of word always 0 for this command.
tx_data[2] = new_pwm_value;
//send the command off.
Wire.beginTransmission(ATTINY85_DATA::slave_device_id);
Wire.write(tx_data, 3);
Wire.endTransmission();
}
}
-------------------------------------------------------------------------------------------------------------
*/
//includes...
#include <EEPROM.h>
#include <TinyWireS.h>
//hardware assignments...
#define PWM_OUTPUT_PIN 1 //hardware pin#6 on the Attiny85
#define DEFAULT_I2C_DEVICE_ID 0x04 //The default i2c device id.
//===========================================================================
namespace TX_DATA {
enum {
a2_data = 0x00,
a3_data = 0x01,
pwm_data = 0x02,
max_len = 0x03,
byte_len = 0x06,
update_uinterval = 300 //update interval in milliseconds.
};
volatile uint32_t last_update = 0;
volatile uint16_t buffer[TX_DATA::max_len] = {0};
void update(uint32_t _now) {
if ((_now - TX_DATA::last_update) > TX_DATA::update_uinterval){
TX_DATA::last_update = _now;
// Read analog voltages and store to tx buffer.
TX_DATA::buffer[TX_DATA::a2_data] = analogRead(A2);
TX_DATA::buffer[TX_DATA::a3_data] = analogRead(A3);
//NOTE: pwm_data is set externally by the master. Nothing to update.
}
}
}
//===========================================================================
namespace EEPROM_DATA {
enum {
device_id_address = 0x10,
default_device_id = DEFAULT_I2C_DEVICE_ID
};
bool device_id_valid(uint8_t id) { return (id > 0x04 && id < 0x7E); }
bool store_device_id(uint8_t new_device_id) {
if (!device_id_valid(new_device_id)) return false; //cancel if out of bounds. Assume corrupt data.
EEPROM.write(EEPROM_DATA::device_id_address, new_device_id); //Set new i2c address into EEPROM
return true;
}
uint8_t get_device_id() {
uint8_t _id = EEPROM.read(EEPROM_DATA::device_id_address);
if (!device_id_valid(_id)) {
_id = EEPROM_DATA::default_device_id;
store_device_id(_id); //update EEPROM with a valid/default device id.
}
return _id;
}
}
//===========================================================================
namespace COMMANDS {
enum {
assign_new_device_id = 0xC1,
set_pwm_output = 0xc2
};
bool do_command(uint8_t cmd, uint16_t cmd_data) {
//interpret command.
switch (cmd) {
case COMMANDS::assign_new_device_id: //set new i2c slave adress. ATTENTION: After command is performed, the chip will need a hard reset.
uint8_t new_device_id;
new_device_id = uint8_t(cmd_data & 0x007F); //ensure the address is 7-bit.
return EEPROM_DATA::store_device_id(new_device_id); //return true of saved, false if invalid address.
break;
case COMMANDS::set_pwm_output: // set pwm level for pwm output pin. Valid values are 0 - 255.
if (cmd_data > 0x00FF) break; //ignore out of range data. Assume corrupt packet.
TX_DATA::buffer[TX_DATA::pwm_data] = cmd_data;
analogWrite(PWM_OUTPUT_PIN, TX_DATA::buffer[TX_DATA::pwm_data] & 0x00FF);
break;
case 0xC3: //??? - not yet used.
break;
case 0xC4: //??? - not yet used.
break;
}
}
}
//===========================================================================
namespace I2C{
void requestEvent(){
/*
* Gets called when the ATtiny receives a general i2c data request.
* Tx_stop is sent after end of call, so all bytes need to be sent before leaving.
*/
unsigned char *pointer = (unsigned char*)&TX_DATA::buffer;
for (uint8_t i = 0; i < TX_DATA::byte_len; ++i) {
TinyWireS.send( uint8_t(pointer[i]) );
}
}
void receiveEvent(uint8_t byte_count) {
/*
* Gets called when the ATtiny recieves an i2c message THAT CONTAINS DATA from another device.
*/
if (!TinyWireS.available() || byte_count != 3 ) return; //qualify/validate call. first byte is command, next 2 bytes is data for command. Total must equal 3 bytes.
//retrieve the command and 16-bit data. Incomming data is ordered CMD,Data-MSB,Data-LSB.
uint8_t cmd = TinyWireS.receive();
uint16_t cmd_data = (TinyWireS.receive() << 8);
cmd_data += TinyWireS.receive();
COMMANDS::do_command(cmd, cmd_data);
}
}
//===========================================================================
/*
* Gets called once uppon boot.
*/
void setup()
{
uint32_t _now = millis(); //Master clock used for keeping events in proper sequence.
//get and validate slave address.
uint8_t _device_id = EEPROM_DATA::get_device_id();
TinyWireS.begin(_device_id); // join i2c network
TinyWireS.onReceive(I2C::receiveEvent); // Triggered when data has been sent to this device.
TinyWireS.onRequest(I2C::requestEvent); //Triggered when data has been requested from this device.
// Establish hardware pin states.
pinMode(A2, INPUT);
pinMode(A3, INPUT);
pinMode(PWM_OUTPUT_PIN, OUTPUT);
TX_DATA::update(_now);
}
//===========================================================================
/*
* MAin Program Loop. Gets called repeatedly and non-stop.
*/
void loop()
{
uint32_t _now = millis(); //Master clock used for keeping events in proper sequence.
TX_DATA::update(_now); //keep tx data up to date.
TinyWireS_stop_check(); // Required for I2C.
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment