I would like to use an Arduino as an i2c slave. But I require that the Arduino acts as multiple devices by registering itself with multiple i2c addresses.
This is probably not something one would normally do, but here is my reason for doing it:
I want to use an Arduino to act as Telemetry sensors for Spektrum Telemetry. The Telemetry receiver has a few i2c plugs which connects to multiple sensors (current 0x02, voltage 0x03, airspeed 0x11, etc) each that have a fixed i2c address which the Telemetry receiver expects.
I would like to use one Arduino to act as all these devices by registering itself with all of the above addresses, and responding appropriately with the readings.
I could use one Arduino per sensor, which seems silly as I can perform all these readings with one Arduino pro-mini.
I know you can register the Arduino using
Wire.begin(0x02);
But I require something similar to this (pseudo code)
Wire.begin(0x02, 0x03, 0x11);
And when a request is received, I need to know with what address the Arduino was queried.
For example (pseudo code)
void receiveEvent(byte address, int bytesReceived){
if(address == 0x02){
// Current reading
}
else if(address == 0x03){
// Voltage reading
}
else if(address == 0x11){
// Airspeed reading
}
}
Any advice would be appreciated.
It is not possible to make the Arduino listen to to multiple slave addresses by using the Wire library since Wire.begin() only allows to pass a single slave address.
Even the Atmel ATmega microcontroller on which most Arduinos are based only allows its hardware 2-wire serial interface (TWI) to be set to a single 7-bit address via its 2-wire address register TWAR. However, it is possible to work around this limitation by masking one or more address bits using the TWI address mask register TWAMR as documented (somewhat briefly) in e.g. this ATmega datasheet section 22.9.6:
The TWAMR can be loaded with a 7-bit Salve (sic!) Address mask. Each of the bits in TWAMR can mask (disable) the corresponding address bits in the TWI address Register (TWAR). If the mask bit is set to one then the address match logic ignores the compare between the incoming address bit and the corresponding bit in TWAR.
So we would first have to set up the mask bits based on all I2C addresses we want to respond to by OR'ing them and shifting right to match the TWAMR register layout (TWAMR holds mask in bit7:1, bit0 is unused):
TWAMR = (sensor1_addr | sensor2_addr | sensor3_addr) << 1;
The main problem from here on will be to find out which particular I2C address was queried (we only know it was one that matches the address mask).
If I interpret section 22.5.3 correctly, stating
The TWDR contains the address or data bytes to be transmitted, or the address or data bytes received.
we should be able to retrieve the unmasked I2C address from the TWDR register.
ATmega TWI operation is interrupt-based, more specifically, it utilizes a single interrupt vector for a plethora of different TWI events indicated by status codes in the TWSR status register.
In the TWI interrupt service routine, we'll have to
make sure the reason why we've entered the ISR is because we've been queried. This can be done by checking TWSR for status code 0xA8 (own SLA+R has been received)
decide which sensor data to send back to the master based on what I2C address was actually queried by checking the last byte on the bus in TWDR.
This part of the ISR could look something like this (untested):
if (TWSR == 0xA8) { // read request has been received
byte i2c_addr = TWDR >> 1; // retrieve address from last byte on the bus
switch (i2c_addr) {
case sensor1_addr:
// send sensor 1 reading
break;
case sensor2_addr:
// send sensor 2 reading
break;
case sensor3_addr:
// send sensor 3 reading
break;
default:
// I2C address does not match any of our sensors', ignore.
break;
}
}
Thanks for asking this interesting question!
I really do like vega8's answer, but I'd also like to mention that if your I2C master isn't going to clock things incredibly fast, using a software-based implementation of I2C would also be feasible and give you the freedom you want.
You might want to consider that approach if rough calculation shows that the time spent in the TWI ISR is too high and interrupts might start to overlap.
void setup()
{
Wire.begin(0x11 | 0x12); // Adr 11 and 12 are used for Alt and Speed by Spectrum DX
Wire.onRequest(requestEvent); // register callback function
TWAMR = (0x11 | 0x12) << 1; // set filter for given adr
}
void requestEvent() {
int adr = TWDR >> 1; // move 1 bit to align I2C adr
if (adr == 0x12) // check for altitude request at adr 12
Wire.write(tmpSpektrumDataAlt, 16); // send buffer
if (adr == 0x11) // check for speed request at adr 11
Wire.write(tmpSpektrumDataSpd, 16); // send buffer
}
This works with a Spectrum DX8 with telemetry module.
The Spectrum interface was made public on Sectrums home page. Technical documents.
There could be other devices on the I2C bus, the TWAMR should be set with as less bits as possible. So I think the better way to calculate the mask is:
AddrOr = Addr1 | Addr2 | Addr3 | Addr4 ...
AddrAnd = Addr1 & Addr2 & Addr3 & Addr4 ...
TWAMR = (AddrOr ^ AddrAnd) << 1
while TWAR can be set as either AddrOr or AddrAnd
In this way we can limit the possibility of address conflict to the minimum
Related
I'm pretty new to Arduino and especially ESP32. But - before I receive the tip "use an Arduino" - I decided to go for the ESP32 because of the size and the capability to connect it to the WLAN.
However, I am trying to build some control box for my terrarium which should - in the first design - steer various lamps and the rain pump via remote controlled outlets. For this I got an ESP32 NodeMCU, a RTC time module (which seems to work quite fine) and a 433 Hz receiver/sender set.
I followed several tutorials regarding the wiring and uploaded the example files to the ESP32. No matter which pin I connect the Receiver to (I need to connect the receiver first in order to read out the signals of the 433 Hz control which came with the outlets) I won't receive any signals on the receiver.
I embedded the library RCSwitch and I tried to configure my switch as follows (here with PIN 13 as example - I tried several other pins as well):
mySwitch.enableReceive(13)
As I read in some other blog, there might be the need to convert the pin number to its interrupt address, so I tried the following:
mySwitch.enableReceive(digitalPinToInterrupt(13))
The result is always the same: dead silence on the serial monitor (except the boot messages, etc.).
Am I using the wrong library or what am I doing wrong here?
I read that there should be a library called RFSwitch, but the only version I found only features the 433 Hz sender, not the receiver.
I would be really grateful for any hint concerning this issue - I'm pretty stuck here for many hours now...
I know this is pretty old and maybe you've resolved the issue by now but maybe it will help others. I had the same issue and what helped me was to set the pinMode:
pinMode(GPIO_NUM_35, INPUT);
mySwitch.enableReceive(digitalPinToInterrupt(GPIO_NUM_35));
Have been successful with RCSwitch today on ESP32 Dev Board and a 433MHZ receiver and sender. Here is what I have been stumbling on my journey.
Connecting the receiver (requires 5V)
You can use the ESP32-VIN for 5V if the Micro-USB is used to supply power
You may connect the Receiver-DATA to any ESP-32-Input-PIN BUT you might damage your ESP32 since it only allows ~3.3V
I tried first with some "makeshift" level shifting through resistors but I guess it lowers speed too much => A proper level-shifter (5V => 3.3V) might work out well
When referencing the PIN "xx" I have been just using the PIN-Number "Dxx" written on the ESP32-Dev-Board
You may connect an antenna of ~17.3cm to improve range
#include <RCSwitch.h>
RCSwitch mySwitch = RCSwitch();
#define RXD2 27
void setup() {
Serial.begin(115200);
Serial.print("Ready to receive.");
mySwitch.enableReceive(RXD2);
}
void loop() {
if (mySwitch.available()) {
Serial.print("Received ");
Serial.print( mySwitch.getReceivedValue() );
Serial.print(" / ");
Serial.print( mySwitch.getReceivedBitlength() );
Serial.print("bit ");
Serial.print("Protocol: ");
Serial.print( mySwitch.getReceivedProtocol() );
Serial.print(" / ");
Serial.println( mySwitch.getReceivedDelay() );
mySwitch.resetAvailable();
}
}
In your RC and Outlet can be configured by DIP-Switches you might not need to connect the receiver overall - you can directly insert the DIP-Switches levels in the RCSwitch-Library
Connecting the sender (is fine with just 3.3V)
You can use the ESP32-3.3 to supply power
You may want to double check the PIN-Labels - I got confused because the DATA-Label was off and first interpreted as GND | DATA | VCC instead of GND | VCC | DATA
You may connect an antenna of ~17.3cm to improve range
#include <Arduino.h>
#include <WiFi.h>
#include <RCSwitch.h>
#define TXD2 25
RCSwitch mySwitch = RCSwitch();
void setup() {
Serial.begin(115200);
// Transmitter is connected to Arduino Pin #10
mySwitch.enableTransmit(TXD2);
// Optional set protocol (default is 1, will work for most outlets)
// mySwitch.setProtocol(2);
// Optional set pulse length.
mySwitch.setPulseLength(311);
// Optional set number of transmission repetitions.
// mySwitch.setRepeatTransmit(15);
}
void loop() {
/* See Example: TypeA_WithDIPSwitches */
mySwitch.switchOn("01010", "10000");
Serial.println("Switch On");
delay(10000);
mySwitch.switchOff("01010", "10000");
Serial.println("Switch Off");
delay(10000);
}
I have not yet used sender or receiver while WiFi being active. Though I have been reading about issues while WiFi is active and receiving / sending via 433Mhz.
The sender must have 5 V supply to go far, and it has not output pin which can damage the ESP32, and the receiver. Instead, must be connected to 3.3 V because it has an output which goes to ESP2 (3.3 V supply) and the output of the receiver must not be more than 3.3 V, so as not to damage the GPIO input of ESP32.
ESP32
The data sender(input) goes to: GPIO 5: pinMode(5, OUTPUT)
The data receiver (output), goes to GPIO 4: pinMode(4, INPUT)
Sender supply: 5 V
Receiver supply: 3.3 V (not to damage ESP32 GPIO 4)
I am new to electronics and has completed a tutorial on how to operate a 16x2 Character LCD via I2C in Arduino using liquidCrystal_I2C. Everything works fine but I have a question about the low level interaction between the I2C and the LCD. Looking at the library's source code, I notice that when writing a 4 bits nibble (LiquidCrystal_I2C::write4bits), the code writes the nibble to the I2C expander first
(LiquidCrystal_I2C::expanderWrite), and then writes again when pulsing the Enable bit. Why is the first expanderWrite necessary? Why can't write4bits just call pulseEnable (with the blacklight bit set)?
I am sure there is a reason as I checked other library like RPLCD and see a similar pattern. Can anyone enlighten me? Thank you.
From the datasheet I found the LCD requires specific timing in the communication protocol.
On the rising edge of the enable line the Register Select and Read/Write lines must have already settled for tsu1 (100ns). On the falling edge of the enable line the data must have already settled for tsu2 (60ns). By writing _data they are also writing the RS and R/W lines as they are the lower nibble of _data.
This article covers the topic very thoroughly.
//**** From LiquidCrystal_I2C.h
// flags for backlight control
#define LCD_BACKLIGHT 0x08
#define LCD_NOBACKLIGHT 0x00
#define En B00000100 // Enable bit
#define Rw B00000010 // Read/Write bit
#define Rs B00000001 // Register select bit
// ^--------Backlight bit defined above
// ^^^^---------Data bits
//**** From LiquidCrystal_I2C.cpp
void LiquidCrystal_I2C::write4bits(uint8_t value) {
expanderWrite(value);
pulseEnable(value);
}
void LiquidCrystal_I2C::expanderWrite(uint8_t _data){
Wire.beginTransmission(_addr);
Wire.write((int)(_data) | _backlightval);
Wire.endTransmission();
}
void LiquidCrystal_I2C::pulseEnable(uint8_t _data){
expanderWrite(_data | En); // En high
delayMicroseconds(1); // enable pulse must be >450ns
expanderWrite(_data & ~En); // En low
delayMicroseconds(50); // commands need > 37us to settle
}
I'm trying to create a pair of arduino/xbee beacons that will transmit some information (ultimately GPS coordinates) to each other at all times. The problem is that when they're both on, neither one seems to receive. I'm guessing it's because both might be transmitting at the same time and drowning the others message out. However, I'm not sure of a better approach since neither one is a slave or master. Here's my setup:
Hardware:
A pair of XBee-Pro 900 XSC S3B xbees.
A pair of teensy (arduino compatible with multiple UARTs)
XBee Setup
VID: 542D
DT: 8153
Serial connected to Serial 3 on Teensy
Code
long lastTxTime = 0
void setup() {
Serial.begin(9600);
Serial3.begin(9600);
}
void loop() {
long now = millis();
// Send ever 0.5 seconds
if (now - lastTxTime > 500) {
Serial.println("SEND!");
Serial3.println("WOOT!");
lastTxTime = now;
}
delay(100);
// Print anything received
while(Serial3.available()) {
char c = Serial3.read();
Serial.print(c);
}
}
When I connect both Teensy's to separate terminals, both of them output "SEND" and rarely receive a "WOOT". I put one of the XBees straight on a USB board to a terminal and it output most of the "WOOT" messages from the other.
What can I do to make this work?
What you need to look into is flow control. In the wireless standard, they use RTS (Request to Send) & CTS (Clear to Send). The RTS/CTS pins on Xbee might be what you need,
I am having hell with this and I know it is probably really simple. I am trying to read a text message from my Seeed GPRS shield. I have the shield setup as a software serial and I am displaying the information received from the GPRS to the serial monitor. I am currently sending all AT commands over serial while I work on my code. To display the data from the software serial to the serial monitor, I am using the following code.
while(GPRS.available()!=0) {
Serial.write(GPRS.read());
}
GPRS is my software serial obviously. The problem is, the text is long and I only get a few characters from it. Something like this.
+CMGR: "REC READ","1511","","13/12/09,14:34:54-24" Welcome to TM eos8
This text is a "Welcome to T-Mobile" text that is much longer. The last few characters shown are scrambled. I have done some research and have seen that I can mod the serial buffer size to 256 instead of the default 64. I want to avoid this because I am sure there is an easier way. Any ideas?
Have you tried reading into a character array, one byte at a time? See if this helps:
if (GPRS.available()) { // GPRS talking ..
while(GPRS.available()) { // As long as it is talking ..
buffer[count++]=GPRS.read();
// read char into array
if(count == 64) break; // Enough said!
}
Serial.write(buffer,count); // Display in Terminal
clearBufferArray();
count = 0;
}
You need to declare the variables 'buffer' and 'count' appropriately and define the function 'clearBufferArray()'
Let me know if this helps.
Looks like this is simply the result of the lack of flow control in all Arduino serial connections. If you cannot pace your GPRS() input byte sequence to a rate that guarantees the input FIFO can't overflow, then your Serial.write() will block when the output FIFO fills. At that point you will be dropping new GPRS input bytes on the floor until Serial output frees up more space.
Since the captured output is apparently clean up to about 64 bytes, this suggests
a) a 64 byte buffer,
b) a GPRS data rate much higher than the Serial one, and
c) that the garbage data is actually the occasional valid byte from later in the sequence.
You might confirm this by testing the return code from Serial.write. If you get back zero, that byte is getting lost.
If you were using 9600 for Serial and 57600 for GPRS, I would expect somewhat more than 64 bytes to come through before the output gets mangled, but if the GPRS rate is more than 64x the Serial rate, the entire output FIFO could fill up within a single output byte transmission time.
Capturing to an intermediate buffer should resolve your issue, as long as it is large enough for the whole message. Similarly, extending the size of either the source (in conjunction with testing the Serial.write) or destination (without any additional code) FIFOs to the maximum datagram size should work.
I've had the same problem trying to read messages and get 64 characters. I overcame it by adding a "delay(10)" in the loop calling the function that does the read from the GPRS. Seems to be enough to overcome the race scenario. - Using Arduino Mega.
void loop() {
ReadmyGPRS();
delay(10); //A race condition exists to get the data.
}
void ReadmyGPRS(){
if (Serial1.available()){ // if data is comming from GPRS serial port
count = 0; // reset counter
while(Serial1.available()) // reading data into char array
{
buffer[count++]=Serial1.read(); // writing data into array
if(count == 160)break;
}
Serial.write(buffer,count);
}
}
I am writing a small test program that attempts to perform a serial.write() followed by a serial.read() within an ISR. The code will eventually be used to prompt an external GSM shield to send an SMS on a regular basis.
ISR(TIMER2_OVF_vect) {
Serial.println("AT+CMGS=\"0123456789\""); // Tell Sim900 To prepare sms to number 01...
while(Serial.read()!='>'); // Wait for Sim900 to respond
Serial.print("A text message"); // the SMS body
Serial.write(0x1A); //Confirm send instruction
Serial.write(0x0D);
Serial.write(0x0A);
}
}
What I have found after a lot of testing is that Serial.read() within an ISR is not capable of reading a live serial prompt, instead it will only read any input that was buffered before the ISR was triggered.
Is there any way around this?
The only solution I have found is to place this code instead within the main loop(). But I want to send the SMS using a timer interrupt.
Thank you
You need to place the code in the loop() but using an IF:
float toBeSent = interval;
loop() {
if (millis() > toBeSent) {
Send();
toBeSent = milli() + interval;
}
}
interval is your sending interval in milliseconds.
I had a similar problem a while ago which I managed to resolve by using the Arduino SoftwareSerial library instead of the hardware based Serial.read.
There are some overheads associated with using SoftwareSerial, and you can only read one port at a time, so I leave it up to those with a better understanding of the Arduino platform to tell you if this is a good idea, but one of the benefits of this library is that you can use it within an ISR.
To use the SoftwareSerial library, add the following two lines of code at the top of your sketch remembering to replqce the rx_pin and tx_pin with the corresponding pin values you want to use:
#include <SoftwareSerial.h>
SoftwareSerial mySerial(rx_pin, tx_pin);
Then replace the key word Serial throughout your sketch with mySerial (or whatever name you have chosen to give your SoftwareSerial instance).
An important thing to keep in mind when using SoftwareSerial is that you can only use certain pins on the Arduino so read the documentation first.
If you wanted to live dangerously you could enable interrupts inside the ISR and use a flag to prevent reentry.
int flag=0;
ISR(TIMER2_OVF_vect) {
flag = 1
if (flag){return;}
sei();
Serial.println("AT+CMGS=\"0123456789\""); // Tell Sim900 To prepare sms to number 01...
while(Serial.read()!='>'); // Wait for Sim900 to respond
Serial.print("A text message"); // the SMS body
Serial.write(0x1A); //Confirm send instruction
Serial.write(0x0D);
Serial.write(0x0A);
}
flag = 0;
}