📜 ⬆️ ⬇️

We make a modular multichannel ADC

In various projects, it is often necessary to monitor a variety of parameters that are represented by analog values. Of course, the microcontroller is often enough, but sometimes the processing algorithm is too complicated for it and requires the use of a full-fledged computer. In addition, it is much easier to organize the preservation of logs and beautiful visualization of data. In this case, either a ready-made industrial solution is taken (which, of course, is expensive, but is often redundant), or something is self-made. In the most banal case, this could be an Arduino board with an infinite loop from analogRead and serial.write. If there is a lot of input data (more than analog inputs), then several boards will be needed, to figure out how to interrogate them correctly from a computer, etc. In many cases, the solution I developed will be suitable (perhaps I was not the first to come up with just such an implementation, I was not particularly interested this question), which will save time for debugging and make the relative simple and clear system architecture.



To understand whether this solution is suitable for you, I propose to get acquainted with its characteristics:

Maximum number of channels: 44;
Sample Rate: 1000 Hertz;
Resolution: 8 bits.
')
The characteristics are quite mediocre, but for many tasks they may be suitable. This is not an oscilloscope, but a sensor polling system. In addition, using her example, you can get acquainted with the use of USART not quite as intended.

The system consists of separate ADC modules based on ATMEGA8 microcontroller (you can use another AVR family with ADC and USART hardware module, if you change the firmware a little). There can be one or several modules, each provides 6 or 8 ADCs, depending on the microcontroller case (the output version has 6 ADCs, and for surface mounting 8), only the total number of channels should not exceed 44. The main feature is that regardless of the number of modules requires only one USART from the side of the computer (this can be a USB adapter or a hardware COM port). This is achieved by the fact that the USARTs of all microcontrollers are connected in series (RX one to the TX of the other), and the RX and TX pins of the outermost ones in the chain are already connected to the computer.

Here it should be noted that the bit width of my ADC is not quite 8 bits - only 255 gradations are possible instead of 256. The value 0xFF is reserved for a particular purpose. If the microcontroller receives it, then it starts issuing each time the value from the next channel of its ADC, and when they end, retransmit 0xFF further along the chain. If the USART input has a value other than 0xFF, then the microcircuit simply sends the byte further. Thus, by transferring one arbitrary value and 44 0xFF, you can get values ​​from all channels of all ADCs (if the ADC is smaller, then the extra channels will be equal to 0xFF). An arbitrary value is needed so that all modules reset the pointer to the current ADC channel, which must be transmitted when receiving 0xFF. In reality, it is more convenient to transmit 45 0xFF, in order to reliably determine the end of the reception (if you received 0xFF, then the channels have ended).

The schematic diagram contains not a lot of details:


The program for AVR looks extremely simple and takes slightly less than 300 bytes of memory:

#include <avr/io.h> #include <avr/interrupt.h> #include <avr/wdt.h> #include <util/delay.h> // Firmware options #define USART_BAUDRATE 460800 #define LED_PIN 1 #define ADC_COUNT 6 #define STARTUP_DELAY 1000 // Calculated UBRR value #define UBRR (F_CPU / (16 * (uint32_t)USART_BAUDRATE) - 1) // Global variables uint8_t adc[ADC_COUNT]; // Buffer uint8_t cur_in_adc; // Input byte index uint8_t cur_out_adc; // Output byte index // USART interrupt handler ISR(USART_RXC_vect) { // Read data from USART uint8_t buffer = UDR; if (buffer == 0xFF) { if (cur_out_adc < ADC_COUNT) { // Return data byte from buffer UDR = adc[cur_out_adc]; cur_out_adc++; // Activate led PORTB |= _BV(LED_PIN); } else { // Chain 0xFF UDR = 0xFF; // Deactivate led PORTB &= ~_BV(LED_PIN); } } else { // Chain data byte UDR = buffer; // Reset byte counter cur_out_adc = 0; // Deactivate led PORTB &= ~_BV(LED_PIN); } } // Main function void main() { // Setup watchdog timer wdt_enable(WDTO_15MS); // Setup pin for led DDRB |= _BV(LED_PIN); // Blink led PORTB |= _BV(LED_PIN); for (uint8_t i = 0; i < STARTUP_DELAY / 5; i++) { _delay_ms(5); wdt_reset(); } PORTB &= ~_BV(LED_PIN); // Setup ADC ADMUX = _BV(REFS1) | _BV(REFS0) | _BV(ADLAR); ADCSRA = _BV(ADEN) | _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0); // Setup USART UBRRL = UBRR & 0xFF; UBRRH = UBRR >> 8; UCSRA = 0; UCSRB = _BV(RXCIE) | _BV(RXEN) | _BV(TXEN); UCSRC = _BV(URSEL) | _BV(UCSZ1) | _BV(UCSZ0); // Enable interrupts sei(); // Main loop while (1) { // Reset watchdog timer wdt_reset(); // Select ADC channel ADMUX = _BV(REFS1) | _BV(REFS0) | _BV(ADLAR) | cur_in_adc; // Start conversion and wait until it performed ADCSRA |= _BV(ADIF) | _BV(ADSC); while (ADCSRA & _BV(ADSC)); // Put value from ADC to buffer uint8_t value = ADCH; adc[cur_in_adc] = (value != 0xFF) ? value : 0xFE; // Switch to next channel cur_in_adc++; if (cur_in_adc >= ADC_COUNT) { cur_in_adc = 0; } } } 


In the repository on GitHub, you can find the KiCad schematic and printed circuit board files, as well as a sample program for a computer that reads data from this system and outputs them in CSV format.

Conveniently, the serial port is used almost at the limit of its capabilities, so it doesn’t need to take care of data synchronization (I just send 1000 commands to read the ADC every second) - if we transfer 46 kilobytes of data every second at 460800 bits per second, then we can to be completely sure that blocks of 46 bytes of data (one measurement) will arrive every millisecond (although buffering by the OS kernel and USB adapter will, of course, delay, but measurements will always be made with the desired frequency).

Printed circuit board was designed in KiCad:



All boards are connected in a chain, the last RX and TX boards are connected by a jumper.
The quality of the ADC can be estimated from this image of a saw at 10 Hz:



For comparison, the image from the DS203 oscilloscope (it also acts as a generator):



Unfortunately, I do not have a better signal source, but my system should be suitable for low-frequency signals.

It should be noted that not every USART-USB converter provides a speed of 460800 bps when the channel is fully loaded. The converter based on CP2102 made me look for a long time error in my own code, until I tried the FT232. A loss of about 0.17% of data is also observed (measures are taken in the computer program so that data synchronization is not lost). Most likely this is caused by a bad USART line, or a flaw in the program. In general, for 90% of applications it should not be critical, but you probably should not bet on nuclear power plants.

And finally, I will say that the cost of one module is about 50-60 rubles, if you order all parts from China, so the solution should be quite attractive.

Source: https://habr.com/ru/post/239447/


All Articles