shabaz designed a Data Acquisition Board for Pi Pico . In this post, I'm using it in continuous sample mode. Goal: get 1 second worth of samples, in 860 samples per second mode.
There are several options to trigger the reading of the samples. You could set up a timer that fires every 1162 µs. Then let it kick off a read. There is a 10% variation though - my device clocks at 850 samples, approx 2% slower.
I've used another option that isn't dependent on the exact data rate: the ADS1115 has a RDY pin. I've configured it to pulse each time a sample is ready.

I modified the PCB, and connected the RDY pin to GP13 with a bodge wire.

The RDY pin needs a pull-up. I tested if the RP2040 internal pull-up has enough oomph: yes.

In my code, I attached an interrupt to the rising edge of GP13. Each time, it 'll initiate a read over i2c.

Highlights of the code (it's attached, with *.uf2 firmware precompiled)
volatile bool rdy = false;
void rdy_callback(uint gpio, uint32_t events) {
rdy = true;
}
// board initialisation
void
board_init(void) {
// ...
// listener for ADC RDY
gpio_init(13);
gpio_pull_up(13);
gpio_set_dir(13, false);
gpio_set_irq_enabled_with_callback(13, GPIO_IRQ_EDGE_RISE, true, &rdy_callback);
// ...
}
void adc_enable_rdy(uint8_t boardnum) {
uint8_t buf[3];
buf[0] = ADS1115_REG_LO_THRESH;
buf[1] = 0x00;
buf[2] = 0x00; //Lo_thresh MS bit must be 0
i2c_write_blocking(i2c_port, adc_addr[boardnum], buf, 3, false);
buf[0] = ADS1115_REG_HI_THRESH;
buf[1] = 0x80;
buf[2] = 0x00; //Hi_thresh MS bit must be 1
i2c_write_blocking(i2c_port, adc_addr[boardnum], buf, 3, false);
}
// ************ 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
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;
while (buf16_ptr < buf16_end_ptr) {
*buf16_ptr = *buf16_ptr >> 8 | *buf16_ptr << 8; // swap bytes
printf("AIN1 = %.3f V\n", to_volts(BOARD0, 0, *buf16_ptr));
buf16_ptr++;
}
while (FOREVER) {}
}
I didn't include the definitions and functions of shabaz ' source code example. They are available in the download.
The data collection loop is made as short as possible. I set the i2c baud rate to 1 MM. And removed the bit swapping and voltage conversion from the sample loop. I made a second loop that does these conversion activities once all data is collected.
This isn't needed for timekeeping. There's enough time for slower i2c , and to do bit swapping and voltage conversion.
What we can gain by taking them out, is to let the controller go low power while waiting for a next pulse. Not implemented, but worth investigating.

Enjoy!
Source and firmware:
adc_board_test_20231125.zip