Write an array of bytes to EEPROM from PROGMEM - arduino

I'm trying to store an array of LED patterns to EEPROM, to later write to an external flash module. Below is my header file containing the patterns.
#ifndef DefaultPatterns_h
#define DefaultPatterns_h
#define DEFAULT_SIZE 147
const byte defaultData[DEFAULT_SIZE] PROGMEM ={225,112,111,};
#define RAINBOWPIXEL_SIZE 972
const byte rainbowpixelData[RAINBOWPIXEL_SIZE] PROGMEM ={255,0,0,0,0,0,0};
#endif
I've cut some bytes out from the above patterns as they are too long for this post.
Below is my sketch that is taken from ESP8266 EEPROM example library and I'm trying to use it as a starting point. I've commented out some things I've tried. Remember the patterns are in a header file.
#include "EEPROM.h"
#include "defaultPatterns.h"
int addr = 0;// address of EEPROM to write to
void setup() {
EEPROM.begin(512);
}
void loop() {
Serial.begin(9600);
//const byte defaultData[] = PROGMEM(defaultData,DEFAULT_SIZE );
// write the value to the appropriate byte of the EEPROM.
// these values will remain there when the board is
// turned off.
// EEPROM.write(addr, val);
//EEPROM.write(addr,byte (defaultData)) sizeof(defaultData));
//EEPROM.write ( 0, (byte *)defaultData), sizeof(defaultData)));
const char pattern = defaultData ;
EEPROM.write(addr, pattern);
// advance to the next address. There are 512 bytes in
// the EEPROM, so go back to 0 when we hit 512.
// save all changes to the flash.
addr = addr + 1;
if (addr == 512) {
addr = 0;
EEPROM.commit();
}
delay(100);
}
I've tried all sorts of different ways, but I'm getting errors and syntax errors.
I've tried to do this myself but I'll be at it forever. Any help that would be great.
I'm trying to build a LED controller that can store lots of patterns in the form of the patterns in the header file and play them back via a WS2812B strip of LEDs on button press. I've a long way to go.
Why use flash you ask? Space, I need lots of space. I know flash wears out faster, but I can replace it for a few bucks.

Look at the definition of EEPROM.write() - it only writes one byte. You need to use a for loop or EEPROM.put().

Related

Reading multiple bytes in Arduino sent using Pyserial

I'm constantly sending structs of int64 via Pyserial with:
with serial.Serial(port='COM4', baudrate=115200, timeout=.1) as arduino:
value = write_read(struct.pack(">q", int_array[1][i])) #this sends signed int.64 in bytes
print(value)
the struct.pack has this shape, for example:
b'\xff\xff\xff\xff\xff\xff\xff\xef'
and the function write_read consists of:
def write_read(x):
arduino.write((x))
data = arduino.readline()
#the idea is to receive an ACK from the Arduino after 8 bytes (the full
#number)
return data
The code I'm trying to develop in arduino is the following:
void loop() {
// send data only when you receive data:
if (Serial.available() \> 0) {
// read the incoming byte:
incomingByte = Serial.read();
//read 8 bytes and create the result
r= function_to_read_8_last_bytes // or similar
// say what you got:
Serial.print("I received: ");
Serial.printlesultn(r, DEC);
Serial.write("ACK");
}
}
I'm very curious how I could do a robust "read 8 bytes" function.
Should I add some especial character in the Python part to indentify when it ends one value?
Thanks! I'll appreciate any help :)
Given the discussion in the comments, it's hard to receive a stream of bytes and be sure that the receiver is completely synchronized. However let's make some assumptions to ease the problem:
The serial buffer is empty when you connect your laptop to Arduino. This ensures you won't receive spurious data with no meaning. I had this problem happens a lot when the serial connection was ended abruptly by any cause.
You are not constantly sending bytes, Arduino has time to process them until the start of the new sequence.
You only send this data, so there is no need to create a higher level protocol on top of it. Bare in mind that the serial communication is almost just an hardware stack, you receive bytes with no headers.
For assumption 1 you can write a simple piece of code to consume all the spurious bytes in the serial buffer as soon as your main starts from Arudino, so this will be done everytime you connect the serial (as this is also where the power supply comes from). Something like this:
void serialFlush(){
while(Serial.available() > 0) {
char t = Serial.read();
}
}
You can send a "READY" signal back to the Python interface, so that the program knows you are ready to receive data.
Going on with the solution you can implement an easy CRC in python, an additional byte which contains a XOR of all the previous bytes, and you check that in Arduino upon reception complete.
def xor_reduce_long_int(li):
res = 0;
for i in range(8):
mask = (0xFF)<<(i*8)
print(hex(mask))
masked = (li&mask)>>(i*8)
res ^= masked
return res
with serial.Serial(port='COM4', baudrate=115200, timeout=.1) as arduino:
crc=xor_reduce_long_int(int_array[1][i])
value = write_read(struct.pack(">qc", int_array[1][i],crc)) #this sends signed int.64 in bytes
print(value)
And with Arduino I would read 8 bytes when they are available and put them into an unsigned char buffer. I would then define a union that alias such buffer to interpret it as long long int.
typedef struct long_int_CRC
{
union
{
unsigned char bytes[8];
long int data;
};
unsigned char CRC;
}data_T;
// .. Later in main
data_T = received_data;
int received_bytes=0
unsigned char my_CRC = 0;
unsigned char rec_byte= 0;
while( received_bytes < 8 )
{
if(Serial.available() )
{
// Get the byte
rec_byte = Serial.read()
// Store the byte and calc CRC
received_data.bytes[received_bytes] = rec_byte;
my_CRC ^= rec_byte;
// Increment counter for next byte
received_bytes++;
}
}
// Reception complete, check CRC
unsigned char rec_CRC;
if(Serial.available() )
{
rec_CRC = Serial.read()
}
if( my_CRC != rec_CRC )
{
// Something was wrong!
}
// Now access your data as a long int
Serial.print("I received: ");
Serial.printlesultn(received_data.data, DEC);
Serial.write("ACK");

How to program own Wifi "Mute" Stomp Box to remote control a Behringer X32 Rack?

I´m totally new to coding, this is even my first post here. Im tryng this because nobody sells what I want/need ;-).
I achived already quite a bit, but at this moment I´m getting lost with a lot of things (I read a lot about coding in general and in special with Arduino the last 8 dayas)... but let me explain first what my intention on this project is:
I want to build a "Stomp Box" to mute a Behringer X32 Rack (wireless) Channels/Mutegroups/Buses, just Mute On/Off.. nothing else.
This Box should have 4-6 "stompers" (buttons), each of this buttons should have a different Mute function.
Also the current state of the Channel/Mutegroup/Bus should be indicated by LED´s green if unmuted or red if muted.
Therfore the box needs to evaulate the current state of the designated Channel/Mutegroup/Bus, because it could change also from other remote devices.
And then switch to the opposite state when pressing/stomping on designated button.
I´d like to have code where I can easily change the action of a button, Like:
button1 = /ch/01/mix/on ,i 1
button2 = /config/mute/1 ,i 1
button3 = /dca/1/on ,i 1
so in case I need a differnt Channel/Mutegroup/Bus for another event simply edit and recode my ESP32 Node Kit
So here is my code I already have:
#include "WiFi.h"
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#include <SPI.h>
#include <OSCMessage.h> //https://github.com/CNMAT/OSC
#define WIFI_NETWORK "xxxxxxxxxx" //SSID of you Wifi
#define WIFI_PASSWORD "xxxxxxxxxxx" //Your Wifi Password
#define WIFI_TIMEOUT_MS 20000 // 20 second WiFi connection timeout
#define WIFI_RECOVER_TIME_MS 30000 // Wait 30 seconds after a failed connection attempt
int muteOn = 0;// 0=Mute
int muteOff = 1;// 1=Unmute
int input;
WiFiUDP Udp;
const IPAddress outIp (192, 168, 10, 129); //Mixers IP
const unsigned int outPort = 10023; //X32 Port
//variables for blinking an LED with Millis
const int led = 2; // ESP32 Pin to which onboard LED is connected
unsigned long previousMillis = 0; // will store last time LED was updated
const long interval = 300; // interval at which to blink (milliseconds)
int ledState = LOW; // ledState used to set the LED
void connectToWiFi(){
Serial.print("Zu WLAN verbinden...");
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_NETWORK, WIFI_PASSWORD);
unsigned long startAttemptTime = millis();
while(WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < WIFI_TIMEOUT_MS){
Serial.println(".");
delay(100);
}
if(WiFi.status() != WL_CONNECTED){
Serial.println("Nicht Verbunden!");
//optional take action
}else{
Serial.print("WLAN Verbunden mit ");
Serial.println(WIFI_NETWORK);
Serial.println(WiFi.localIP( ));
}
}
void setup() {
Serial.begin(115200);
connectToWiFi();
Udp.begin(8888);
pinMode(led, OUTPUT);
// Port defaults to 3232
// ArduinoOTA.setPort(3232);
// Hostname defaults to esp3232-[MAC]
// ArduinoOTA.setHostname("myesp32");
// No authentication by default
// ArduinoOTA.setPassword("admin");
// Password can be set with it's md5 value as well
// MD5(admin) = 21232f297a57a5a743894a0e4a801fc3
// ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3");
ArduinoOTA
.onStart([]() {
String type;
if (ArduinoOTA.getCommand() == U_FLASH)
type = "sketch";
else // U_SPIFFS
type = "filesystem";
// NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
Serial.println("Start updating " + type);
})
.onEnd([]() {
Serial.println("\nEnd");
})
.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
})
.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
else if (error == OTA_END_ERROR) Serial.println("End Failed");
});
ArduinoOTA.begin();
Serial.println("Ready");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}
void loop(){
ArduinoOTA.handle();
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
// save the last time you blinked the LED
previousMillis = currentMillis;
// if the LED is off turn it on and vice-versa:
ledState = not(ledState);
// set the LED with the ledState of the variable:
digitalWrite(led, ledState);
}
input=Serial.read();
if (input=='0'){
// welcher status hat der kanal?
// wenn Kanal gemutet dann unmute und umgekehrt
Serial.println("Mute!");
delay(100);
sendMute(); //send Mute to Mixer
Serial.println("...");
}
if (input=='1'){
Serial.println("UnMute!");
delay(100);
sendUnMute();
Serial.println("...");
}
}
void sendMute() {
//the message wants an OSC address as first argument
OSCMessage msg("/ch/01/mix/on");
msg.add(muteOn);
Udp.beginPacket(outIp, outPort);
msg.send(Udp); // send the bytes to the SLIP stream
Udp.endPacket(); // mark the end of the OSC Packet
msg.empty(); // free space occupied by message
delay(20);
}
void sendUnMute() {
//the message wants an OSC address as first argument
OSCMessage msg("/ch/01/mix/on");
msg.add(muteOff);
Udp.beginPacket(outIp, outPort);
msg.send(Udp); // send the bytes to the SLIP stream
Udp.endPacket(); // mark the end of the OSC Packet
msg.empty(); // free space occupied by message
delay(20);
}
So I testet this via serial Monitor, when I input "0" and click send, the mixer mutes channel 1 and on input "1" channel 1 becomes unmuted, so far so good... (OSCMessage msg("/ch/01/mix/on"); ... section.
What bothers me here in special is, I had to hardcode the command "/ch/01/mix/on", because I am not able to declare a variable? for this string? I am already so confused that I don´t know if I even have the terms right :-(
BTW: There are a lot solutions out there how to do it with MIDI, but MIDI is not wireles and I think for my project overkill. I also did some some research on github.com/CNMAT/OSC but I don´t get it... (crying)...
I found also a post here, but this didn´t helped either... :-(
Any advice on that how I can reach my goal?--
Any help is much apprceiated... even in German (my native language... )
PS: Yes I´m a begginner and I admit it. But at least I managed how to connect and flash this thing even via OTA in the last 8 days, so please be easy on me.
Not wanting to hardcode your commands is a good instinct.
The Arduino language is C++, which is (mostly) a superset of C. C and C++ use a preprocessor which lets you define constants and test for their presence.
For instance, you could write:
#define CHAN01_MIX_ON_COMMAND "/ch/01/mix/on"
and then use CHAN01_MIX_ON_COMMAND anywhere you want to use that constant, like so:
void sendMute() {
//the message wants an OSC address as first argument
OSCMessage msg(CHAN01_MIX_ON_COMMAND);
Then if you ever need to change the string "/ch/01/mix/on" you can just change it in one location and not worry about finding every instance of it in your code.
Writing the names in #define statements is a convention people usually follow in order to make it more clear that they're constants.
You have to write the #define line before you use the constant you defined, so putting it at the start of the file (after any #include lines and before your first function) is a good practice. Or if you have several you might put them all in their own file called something like commands.h (the .h means header file)and then include that at the start of any file that needs it like so:
#include "commands.h"
This #include statement would insert the contents of the file commands.h into the file that the statement is in.
When you have several #define statements, keeping them all together in one place (whether it's at the top of the file or in their own file) is also a good practice so that you have one central place to find them and update them if you need to.
Some people will assign the string constant to a variable like so:
char *channel01_mix_on_cmd = "/ch/01/mix/on";
Here char means "a character" - like one letter or number or symbol. The * means pointer to, which lets you use an array of characters. Simple strings in C and C++ are just arrays of characters (or a pointer to the first character), with a special hidden character at the end set to numeric value 0 (not the character '0'). C++ also has a string datatype called std::string and Arduino programs have String but those are both overkill here. They all let you work with strings; String is much easier to use than char * but both have strengths and weaknesses.
Like the #define, you'd also place that outside a function near the start of the file. It defines a global variable that would be available to any function that references it.
You'd also use the variable anywhere they want the string. It's the same idea as using #define, just done slightly differently. For instance:
void sendMute() {
//the message wants an OSC address as first argument
OSCMessage msg(channel01_mix_on_cmd);
Using a variable here is an attempt to save storage by not having multiple copies of the string. It's not necessary; C/C++ compilers have for a very long time detected this and stored only one copy of the string. It might save space if your code is split into multiple files.
Saving space on CPUs like the ESP32 and ESP8266 is important because they have so little memory. #define is fine here because the compiler does it automatically for you.
You can create the command string with sprintf.
so for example:
#define CHANNELON "on"
#define CHANNELOFF "off"
int channel;
int mute;
char messageString[100];
// some code that calculates the channel number and the mute state:
channel = 1;
mute = 1;
// then check the mute state and create the command string:
if (mute)
{
// to turn off a channel:
sprintf(messageString,"/ch/%02d/mix/%s",channel,CHANNELOFF);
}
else
{
// to turn on a channel:
sprintf(messageString,"/ch/%02d/mix/%s",channel,CHANNELON);
}
// send the command:
OSCMessage msg(messageString);
the %02d will substitute an integer with a zero in front,
if it's smaller than 10 and that is always 2 characters long.
so if channel is 1, the result would be 01

How do make code compatible to ESP32 board?

I'm trying to get a GY-US-42 ultrasonic sensor working on the ESP32. However, I keep getting an error while compiling. For and Arduino Board it is not a problem, but for the ESP32.
My code:
#include "Wire.h"
//The Arduino Wire library uses the 7-bit version of the address, so the code example uses 0x70 instead of the 8-bit 0xE0
#define SensorAddress byte(0x70)
//The sensors ranging command has a value of 0x51
#define RangeCommand byte(0x51)
//These are the two commands that need to be sent in sequence to change the sensor address
#define ChangeAddressCommand1 byte(0xAA)
#define ChangeAddressCommand2 byte(0xA5)
void setup() {
Serial.begin(115200); //Open serial connection at 9600 baud
Wire.begin();
// changeAddress(SensorAddress,0x40,0);
}
void loop(){
takeRangeReading(); //Tell the sensor to perform a ranging cycle
delay(50); //Wait for sensor to finish
word range = requestRange(); //Get the range from the sensor
Serial.print("Range: "); Serial.println(range); //Print to the user
}
//Commands the sensor to take a range reading
void takeRangeReading(){
Wire.beginTransmission(SensorAddress); //Start addressing
Wire.write(RangeCommand); //send range command
Wire.endTransmission(); //Stop and do something else now
}
//Returns the last range that the sensor determined in its last ranging cycle in centimeters. Returns 0 if there is no communication.
word requestRange(){
Wire.requestFrom(SensorAddress, byte(2));
if(Wire.available() >= 2){ //Sensor responded with the two bytes
byte HighByte = Wire.read(); //Read the high byte back
byte LowByte = Wire.read(); //Read the low byte back
word range = word(HighByte, LowByte); //Make a 16-bit word out of the two bytes for the range
return range;
}
else {
return word(0); //Else nothing was received, return 0
}
}
Error:
sketch/GY-US42_I2C.ino.cpp.o:(.literal._Z12requestRangev+0x0): undefined reference to `makeWord(unsigned short)'
sketch/GY-US42_I2C.ino.cpp.o: In function `requestRange()':
/Users/Arduino/GY-US42_I2C/GY-US42_I2C.ino:42: undefined reference to `makeWord(unsigned short)'
collect2: error: ld returned 1 exit status
The word() is for casting a variable or literal into a 16-bit word, it does not add two bytes into a 16-bit word as you do word(HighByte, LowByte), I'm actually surprise this even compiled in Arduino.
To get the range value, you could do:
int range = HighByte * 256 + LowByte;
or:
int range = ((int)HighByte) << 8 | LowByte; //cast HighByte to int, then shift left by 8 bits.
But since Wire.read() is returning an int instead of a byte(you can see its function prototype definition here), therefore you code can actually be written like this:
int reading = Wire.read(); //read the first data
reading = reading << 8; // shift reading left by 8 bits, equivalent to reading * 256
reading |= Wire.read(); // reading = reading | Wire.read()
By the way, when you use #define, you don't need to specifically cast the const value into specific data type, the compiler will take care of the optimization and the right data type, so:
#define SensorAddress byte(0x70)
would be just fine by defining like this:
#define SensorAddress 0x70
You also do not need to cast const value with byte(2) or return word(0). In the latter case, your function prototype already expect the return would be a data type of word.

Raspi I2C communication with Arduino issues with wiringPi

I'm experiencing unexpected results using wiringPi's wiringPiI2CWriteReg16() function, and I'm not sure if it's due to incorrect usage, or something else. This is the declaration for the function:
extern int wiringPiI2CWriteReg16 (int fd, int reg, int data);
There are notes within the wiringPiI2C.c file that state it resembles Linux's SMBus code, if that helps.
On my Arduino (both an Uno R3 and a Trinket Pro), I am running this pared-down sketch:
#include <Wire.h>
#define SLAVE_ADDR 0x04
void receive_data (int num_bytes){
Serial.print("bytes in: ");
Serial.println(num_bytes);
while(Wire.available()){
int data = Wire.read(); // tried char, uint8_t etc
Serial.println(data);
}
Serial.print("\n");
}
void setup() {
Serial.begin(9600);
Wire.begin(SLAVE_ADDR);
Wire.onReceive(receive_data);
}
void loop() {
delay(500);
}
I would think that Wire.read() would break things apart at the byte boundary, but that's not occurring in my case. Perhaps this is my issue... a misunderstanding.
Nonetheless, I have this C code (requires wiringPi v2.36+ to be installed):
// word.c
#include <wiringPiI2C.h>
void main (){
int fd = wiringPiI2CSetup(0x04);
wiringPiI2CWriteReg16(fd, 0x00, 255);
wiringPiI2CWriteReg16(fd, 0x01, 256);
}
Compiled like this:
gcc -o word word.c -lwiringPi
When run, ./word, I receive the following on my Arduino's serial output:
bytes in: 3
0
255
0
bytes in: 3
1
0
1
In the first call to wiringPiI2CWriteReg16(), I expect the first byte in the output to be zero (0x00) as that's the register address I'm requesting. The second byte (255) is also correct. The third byte (0) is meaningless from what I can tell (as I'm only sending in one byte as data).
However, in the second call to that function, I do get the correct output for the register (first byte as 0x01 == 1), but the second byte is zero, and the third byte has what appears to be the correct remainder (255 == one byte, + 1). The problem is, is that the second byte is 0.
The exact same effect happens if I pass in 511 or for that matter, any number as the data in the call.
My question is whether I'm missing something glaringly obvious (I'm relatively new to C and Arduino), and/or if I can get some pointers on how to troubleshoot this more thoroughly.
I found that the problem was in my Arduino code. In the official Raspi forums, Gordon tipped me off that the bytes are read in separately, LSB first. Throughout all of my searching, I hadn't come across that, and really didn't quite understand what was happening. After changing my I2C read loop code in my Arduino sketch to this:
while(Wire.available()){
Wire.read(); // throw away register byte
int16_t data = Wire.read(); // LSB
data += Wire.read() << 8; // MSB
Serial.print("data: ");
Serial.println(data);
}
...everything works. In effect, that's at least one way to read two-byte values over I2C on the Arduino and put the bytes back together.

How to send 4 Pot values via i2c from Arduino to Arduino? How to differentiate these values while receiving them?

I have one Arduino with 4 Pots. The other Arduino receives these 4 values via i2c and prints them on a Display. The problem is that I don't know how to send these 4 values that the Slave is able to know which value belongs to which Pot.
Slave Code:
#include <Wire.h>
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
void setup()
{
Wire.begin(5);
Wire.onReceive(receiveEvent);
Serial.begin(9600);
lcd.begin(16,2);
}
void loop()
{
}
void receiveEvent(int)
{
while(Wire.available())
{
//How to create this part? How does the Slave know which value belongs to which pot?
}
}
Master Code:
#include <Wire.h>
void setup()
{
Serial.begin(9600);
Wire.begin();
delay(2000);
}
void loop()
{
int sensor1 = analogRead(A1);
Wire.beginTransmission(5);
Wire.write(sensor1);
Serial.print(sensor1);
Wire.endTransmission();
delay(100);
int sensor2 = analogRead(A2);
Wire.beginTransmission(5);
Wire.write(sensor2);
Serial.print(sensor2);
Wire.endTransmission();
delay(500);
}
Ahh what we have here is a basic question on how to design I2C communication. Unfortunately Examples for I2C master and slave included in Arduino IDE are IMO too limited to provide clear guidance on this matter.
First of all in your examples the master and slaves roles are exchanged and should be switched. Slave should read values from analog inputs and master should request them. Why? Because it's master which should decide when to request values and properly decode the request. Slave should provide proper answer to a given request eliminating the problem of data interpretation.
I2C communication is based on requestFunction-(wait)-requestResponse sequence controlled by the master.
Plese refer to the range finder example on arduino page. In a nutshell:
First: master requests a function to measure distance:
// step 3: instruct sensor to return a particular echo reading
Wire.beginTransmission(112); // transmit to device #112
Wire.write(byte(0x02)); // sets register pointer to echo #1 register (0x02)
Wire.endTransmission(); // stop transmitting
(sometimes slaves need some time e.g. 10 - 50 ms to process requests but in the example I'm refering to master doesn't delay read)
Second: master requests response:
// step 4: request reading from sensor
Wire.requestFrom(112, 2); // request 2 bytes from slave device #112
Third: master tries to read and analyze response.
You should design reliable I2C communication in a similar way.
Here is how I do it; you can follow my pattern and get extensible slave implementation which will support one function: read analog inputs but can be easily extended by adding additional function codes and required processing implementation to the slave main loop
Initial remarks
some kind of a simple protocol is needed to control slave - e.g. it should support requesting functions. Supporting functions requests is not absolutely needed in such simmple scenario as reading four analog inputs but what I'm describing is a more general pattern you may use in other projects.
Slave should not perform any additional actions (like reading inputs) on request response as I2C communication may break (due to delays) and you will get partial responses etc. This is very important requirement which affect the slave design.
response (and also request if needed) can contain CRC as if master waits not long enough it may get empty response. If nobody else is going to use your code such countermeasures are not needed and will not be described here. Other important thing is Wire library buffer limitation which is 32 bytes and implementing CRC checksum without modifying the buffer length limits the available data length by two bytes (if crc16 is used).
slave:
#include <WSWire.h> // look on the web for an improved wire library which improves reliability by performing re-init on lockups
// >> put this into a header file you include at the beginning for better clarity
enum {
I2C_CMD_GET_ANALOGS = 1
};
enum {
I2C_MSG_ARGS_MAX = 32,
I2C_RESP_LEN_MAX = 32
};
#define I2C_ADDR 0
#define TWI_FREQ_SETTING 400000L // 400KHz for I2C
#define CPU_FREQ 16000000L // 16MHz
extern const byte supportedI2Ccmd[] = {
1
};
// << put this into a header file you include at the beginning for better clarity
int argsCnt = 0; // how many arguments were passed with given command
int requestedCmd = 0; // which command was requested (if any)
byte i2cArgs[I2C_MSG_ARGS_MAX]; // array to store args received from master
int i2cArgsLen = 0; // how many args passed by master to given command
uint8_t i2cResponse[I2C_RESP_LEN_MAX]; // array to store response
int i2cResponseLen = 0; // response length
void setup()
{
// >> starting i2c
TWBR = ((CPU_FREQ / TWI_FREQ_SETTING) - 16) / 2;
Wire.begin(I2C_ADDR); // join i2c bus
Wire.onRequest(requestEvent); // register event
Wire.onReceive(receiveEvent);
// << starting i2c
}
void loop()
{
if(requestedCmd == I2C_CMD_GET_ANALOGS){
// read inputs and save to response array; example (not tested) below
i2cResponseLen = 0;
// analog readings should be averaged and not read one-by-one to reduce noise which is not done in this example
i2cResponseLen++;
i2cResponse[i2cResponseLen -1] = analogRead(A0);
i2cResponseLen++;
i2cResponse[i2cResponseLen -1] = analogRead(A1);
i2cResponseLen++;
i2cResponse[i2cResponseLen -1] = analogRead(A2);
i2cResponseLen++;
i2cResponse[i2cResponseLen -1] = analogRead(A3);
// now slave is ready to send back four bytes each holding analog reading from a specific analog input; you can improve robustness of the protocol by including e.g. crc16 at the end or instead of returning just 4 bytes return 8 where odd bytes indicate analog input indexes and even bytes their values; change master implementation accordingly
requestedCmd = 0; // set requestd cmd to 0 disabling processing in next loop
}
else if (requestedCmd != 0){
// log the requested function is unsupported (e.g. by writing to serial port or soft serial
requestedCmd = 0; // set requestd cmd to 0 disabling processing in next loop
}
}
// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent(){
Wire.write(i2cResponse, i2cResponseLen);
}
// function that executes when master sends data (begin-end transmission)
// this function is registered as an event, see setup()
void receiveEvent(int howMany)
{
//digitalWrite(13,HIGH);
int cmdRcvd = -1;
int argIndex = -1;
argsCnt = 0;
if (Wire.available()){
cmdRcvd = Wire.read(); // receive first byte - command assumed
while(Wire.available()){ // receive rest of tramsmission from master assuming arguments to the command
if (argIndex < I2C_MSG_ARGS_MAX){
argIndex++;
i2cArgs[argIndex] = Wire.read();
}
else{
; // implement logging error: "too many arguments"
}
argsCnt = argIndex+1;
}
}
else{
// implement logging error: "empty request"
return;
}
// validating command is supported by slave
int fcnt = -1;
for (int i = 0; i < sizeof(supportedI2Ccmd); i++) {
if (supportedI2Ccmd[i] == cmdRcvd) {
fcnt = i;
}
}
if (fcnt<0){
// implement logging error: "command not supported"
return;
}
requestedCmd = cmdRcvd;
// now main loop code should pick up a command to execute and prepare required response when master waits before requesting response
}
master:
#include <WSWire.h>
#define I2C_REQ_DELAY_MS 2 // used for IO reads - from node's memory (fast)
#define I2C_REQ_LONG_DELAY_MS 5 //used for configuration etc.
#define TWI_FREQ_SETTING 400000L
#define CPU_FREQ 16000000L
enum {
I2C_CMD_GET_ANALOGS = 1
};
int i2cSlaveAddr = 0;
void setup(){
// joining i2c as a master
TWBR = ((CPU_FREQ / TWI_FREQ_SETTING) - 16) / 2;
Wire.begin();
}
void loop(){
//requesting analogs read:
Wire.beginTransmission(i2cSlaveAddr);
Wire.write((uint8_t)I2C_CMD_GET_ANALOGS);
Wire.endTransmission();
delay(I2C_REQ_DELAY_MS);
// master knows slave should return 4 bytes to the I2C_CMD_GET_ANALOGS command
int respVals[4];
Wire.requestFrom(i2cSlaveAddr, 4);
uint8_t respIoIndex = 0;
if(Wire.available())
for (byte r = 0; r < 4; r++)
if(Wire.available()){
respVals[respIoIndex] = (uint8_t)Wire.read();
respIoIndex++;
}
else{
// log or handle error: "missing read"; if you are not going to do so use r index instead of respIoIndex and delete respoIoIndex from this for loop
break;
}
// now the respVals array should contain analog values for each analog input in the same order as defined in slave (respVals[0] - A0, respVals[1] - A1 ...)
}
I hope my example will help. It's based on code working for weeks making 40 reads a second from multiple slaves however I have not compiled it to test the function you require.
Please use WSWire library as the Wire (at least as for Arduino 1.0.3) may occasionally freeze your master if for some reason slave will not respond to request.
EDIT: The WSWire lib requires external pull-up resistors for I2C unless you modify the source and enable internal pull-ups like Wire does.
EDIT: instead of creating i2c slave implementation you may try the EasyTransfer library. I haven't tried it but it may be easier to use it if sending four bytes is everything you need.
EDIT[12.2017]: There is a new player on the block - PJON - a library suited for easy multi-master communication ideal to exchange pot values (and much more). It's been around for some time but gained a substantial development speed in recent months. I'm partially involved in its development and switched all field-level and local buses I've used so far (I2C, MODBUS RTU) to PJON over single wire, hardware serial or RF.
Check out GitHub-I2CBus, I've done the exact same thing. Hope it can help

Resources