Last active
November 14, 2024 08:02
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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