I'm going to assume we're talking about signals that are less than a few kHz. In the single ended case, I would twist a ground wire with the signal. In the differential, I would twist the + and - alone with no ground, just like you said.
How you condition the signal is where some questions need to be asked. If:
- the application environment is noisy
- the boards are more than a few feet apart
- the sensor is something like a strain gauge or pressure cell
I would run the signal immediately into an instrumentation amplifier to half fill the span of the ADC, and send the signal over a differential pair, with a line driver IC. On the other end a difference amplifier can unbalance the signal and make it single ended for conversion, an inch from the ADC. The conversion back to single ended will then fill the span of the ADC.
If:
- the application environment is not noisy
- the boards are very close to each other
- the sensor is something like a strain gauge or pressure cell
I would still run the signal into an instrumentation amplifier, and fill the span of the ADC, but send the signal directly over to the ADC, single ended. If there isn't a lot of noise and the boards are close, there's no need for a high common mode rejection system.
If the signal was already greater in amplitude than a few hundred millivolts, there may not even be a need for the instrumentation amplifier. It was there to keep very small signals out of the noise floor.
The nice thing about AVR is code ports very easily from processor to processor. I have a set of functions on file for setting up the peripherals.
Here's my ADC initialization function:
void init_adc(void)
{
sei();
ADMUX = 0b11100000;
ADCSRA = 0b10001100;
ADCSRA = ADCSRA | (1<< ADSC);
}
The sei() function serves as a global interrupt enable, and only needs to be called once if multiple interrupts are being used.
The ADMUX register has the ADC set up with the internal 2.56V reference, the conversion result left adjusted so the most significant bits are in ADCH, and converting ADC0, single ended.
The ADSRA register has the ADC enabled, in interrupt mode, and sets the prescaler to \$\frac{f_{osc}}{16}\$. This register also houses the conversion complete interrupt flag.
ADCSRA = ADCSRA | (1<< ADSC);
This line starts the conversion. It's important to note that the ADC is not free running. A new conversion must be started after each conversion.
My bare bones interrupt service routine code:
ISR(ADC_vect)
{
? = ADCH;
/*possible incrementing ADMUX for additional channels*/
ADCSRA = ADCSRA | (1<< ADSC);
}
The most significant part of the result is in ADCH. ADCL contains the least significant bits. Read from ADCH as necessary. If you have multiple channels you're converting single ended, this is a good place to handle the multiplexing. For example:
if (n == 3)
{
ADMUX = 0b00100000;
n = 0;
}
else
{
ADMUX++;
n++;
}
I strongly recommend reading the datasheet to understand the operation of the ADC before you start writing code. Atmel's datasheets are pretty good, and offer good explanations and some sample code.
Best Answer
Yeah I agree with @pjc50, you should use a microcontroller. They usually have built in ADCs. For example the Arduino uses a ATMega328 which has a 6 channel 10-bit ADC built in with a full scale range of 5V. However, the Arduino ADC won't be able to keep up with 10 kHz at the default clock pre-scale factor of 128, so you'll need to increase the sampling rate of your ADC by lowering the pre-scale factor, as described here.
After you get the digital codes, you can simply compute the voltages with your code. If you need to output actual U1 and U2 voltages, then you can use an external DAC like one of these.
For your enjoyment, here's a tutorial on how to write code to operate an AVR ADC and here is a a study on the ATMega ADC clock rate vs. performance.