[ADC] Pick external ADC, and develop communication
Closed this issue · 9 comments
Once we have a new ADC, we will need to communicate with it over SPI or I2C (likely). How do we do this communication reliably?
For i2c something like this could be implemented in C++ https://raspberry-projects.com/pi/programming-in-c/i2c/using-the-i2c-interface
It may be worthwhile to clone ADCManager and make two versions--ExternalADCManager and BuiltInADCManager. If they are accessing two different components, they should be on separate refresh loops.
We are using the ADS1115: https://www.adafruit.com/product/1085
On the BBB, use command i2cdetect -y -r 2
to look for i2c devices on /dev/i2c-2
From Adafruit:
I2C Addressing
The ADS11x5 chips have a base 7-bit I2C address of 0x48 (1001000) and a clever addressing scheme that allows four different addresses using just one address pin (named ADR for ADdRess). To program the address, connect the address pin as follows:
0x48 (1001000) ADR -> GND
0x49 (1001001) ADR -> VDD
0x4A (1001010) ADR -> SDA
0x4B (1001011) ADR -> SCL
More debug information: kelly/node-i2c#27
The the TI site as well: http://www.ti.com/lit/ds/symlink/ads1115.pdf
Now that the low-level structure is developed on the branch pod4-i2c
, we can use the interface!
Our goal: Read all 4 of the analog ports from each of ADCs that we have connected over i2c. Since each read takes about 3 milliseconds, we are only going to read one port per refresh()
.
We are going to be doing something very similar to what is already implemented in
TMPManager.hpp
. TheTMPData
struct is very similar. The way that theidx
variable within therefresh()
loop is very similar to what we want to accomplish
First thing to do is to modify the I2Cdata
structure, such that we can store the data we read in into it. It is defined at the top of I2CManager.hpp
, and only has a dummy variable. Since we know that all of our data will be int16_t
s, (since this is a 16-bit adc), then we can simply create an array within the struct. Something like int16_t data[8]
would work (maybe with a more descriptive name). Once this is implemented, then we can try to take on the refresh()
loop.
For the refresh loop we want to read one value at a time, while still traversing all of the different ports, while keeping code size small. Look at how TMPManager.cpp
does it. Look at line 27
to see that we open a file based on a value idx
, which you can see that on line 50
we increment (and set back to 0 once we have covered all the values, that's what the modulo %
operator does). The idx
value is an index into an array which you can find defined in TMPManager.hpp
.
You can ignore most of what TMPManager.cpp
actually does, but the key part is what i listed above -- an index is used to select a variable from an array, which is used to selectively read in sensor data.
For the I2CManager.cpp
refresh()
loop, I'd approach it this way:
Identify what stays the same every loop. (hint, it is i2c_fd
)
Identify what can change after a loop (hint, it is the 0x48
vs 0x49
nodeID, and the ANC0
vs ANC1
... vs ANC3
)
So, in a way similar to how TMPManager.cpp
accomplishes it, we want to do:
int16_t value = 0;
if(!set_i2c_addr(i2c_fd, /* ACCESS NODEID BASED ON INDEX */ 0x48 )){
return false;
}
if(single_shot(i2c_fd, /* ACCESS PORT BASED ON INDEX */ ANC0, value)){
print(LogLevel::LOG_INFO, "i2c val1: %d, in micros: %lu\n", value, b-a);
}
//STORE VALUE BASED ON INDEX
//INCREMENT INDEX
What values are being used through the external ADC? Just the accelerometers?
Internal ADCs handle Accelerometers
External ADCs handle temperature