shabaz designed a Data Acquisition Board for Pi Pico . In the previous post, I used it in continuous sample mode. I took 1 second worth of samples, in 860 samples per second mode.
In this post, I average those samples; using C++ STL constructs. I'm using all the code from the 1st post, to collect 860 samples in a buffer.
Actions to do on that buffer:
- swap the bytes. The ADS1115 delivers them in the wrong order for Pico math.
- sum the data in the array, and divide it by array size
In the original program, the buffer is a plain C array, not an STL container. I don't change that. I use STL iterators that can work on such a continuous buffer.
Swapping bytes with a Lambda function
A Lambda function is a small anonymous set of instructions, that (in this case) will apply on all values of a dataset. The Lambda in my code swaps bytes in an uint16_t:
{ u = u >> 8 | u << 8; }
Here is how STL can perform this on all elements of my 860 samples buffer:
std::for_each(std::begin(buf), std::end(buf), [](uint16_t& u){ u = u >> 8 | u << 8; }); // swap bytes
The std::begin() and std::end() iterators work on a common buffer. std::for_each() will iterate from begin to end, and call the Lambda on each element in that range. Result is that the buffer is modified in place, with bytes swapped for each uint16_t value in that buffer.
Calculate the average with a numeric algorithm
In this case, the sum of the buffer is taken. Then it's divided by the sum of elements in the buffer. Again, I'm using STL iterators to traverse the buffer. In this case, the buffer itself does not change state.
std::accumulate sums up all elements in the given range:
double sum = std::accumulate(std::begin(buf), std::end(buf), 0.0);
Then the average is taken. the std::size() STL function works on a plain C buffer.
uint16_t avg = sum / std::size(buf);
This could be done in a single call. The result of std::accumulate() is a variable of type auto. Both swapping and accumulation could be written like this:
// for averaging #include <iterator> #include <numeric> uint16_t buf[SAMPLES]; std::for_each(std::begin(buf), std::end(buf), [](uint16_t& u){ u = u >> 8 | u << 8; }); // swap bytes uint16_t avg = std::accumulate(std::begin(buf), std::end(buf), 0.0) / std::size(buf); printf("AVG = %.3f V\n", to_volts(BOARD0, 0, avg));
Full source of the main function. There are more functions that can be STL'd (the to_volts() conversion, printing the results, ...). For this post, I wanted to focus on the two calls related to averaging.
// ************ main function ******************* int main(void) { stdio_init_all(); board_init(); build_data_rate(BOARD0, DR_860); build_gain(BOARD0, AIN1, GAIN_2_048); // +- 2.048V #define SAMPLES 860 // #define SAMPLES 10 uint16_t buf[SAMPLES]; build_cont_conversion(0); adc_set_mux(BOARD0,0,false); adc_enable_rdy(0); uint8_t* buf8_ptr = (uint8_t*)buf; *buf8_ptr = ADS1115_REG_CONVERSION; i2c_write_blocking(i2c_port, adc_addr[BOARD0], buf8_ptr, 1, false); uint8_t* buf8_end_ptr = ((uint8_t*)buf) + (sizeof buf); rdy = false; while (buf8_ptr < buf8_end_ptr) { if (rdy) { rdy = false; i2c_read_blocking(i2c_port, adc_addr[BOARD0], buf8_ptr, 2, false); buf8_ptr += 2; } } uint16_t* buf16_ptr = buf; uint16_t* buf16_end_ptr = buf + (sizeof buf) / 2; // buffer bytes will now swapped, // and that's needed for the next actions // use stl iterator and lambda function std::for_each(std::begin(buf), std::end(buf), [](uint16_t& u){ u = u >> 8 | u << 8; }); // swap bytes // prereq: bytes are already swapped double sum = std::accumulate(std::begin(buf), std::end(buf), 0.0); uint16_t avg = sum / std::size(buf); printf("AVG = %.3f V\n", to_volts(BOARD0, 0, avg)); while (buf16_ptr < buf16_end_ptr) { // *buf16_ptr = *buf16_ptr >> 8 | *buf16_ptr << 8; // swap bytes already done with stl lambda printf("AIN1 = %.3f V\n", to_volts(BOARD0, 0, *buf16_ptr)); buf16_ptr++; } while (FOREVER) {} }
Check the previous post for the source of the functions called in this example.
The use of the iterators, Lambda function and other STL functions did not add noticeable resource use.
I also tried to use streaming to cout. It works, but that's a Flash hog. The internet has multiple reports on the big size of the IOStream library for an embedded device, and my tests confirm that.
Enjoy.