Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /**
- * \mainpage MCP3204/08 value reader program
- *
- * This program reads values from channels of the MCP3204/08 ADC and prints the
- * sum of the channels' values. The channel numbers are taken as command-line
- * arguments. * The device via which to access the ADC may be set via the `-d`
- * option (`/dev/spidev0.0` by default). The program may be instructed to fetch
- * multiple samples using the `-s` option.
- *
- * According to the datasheet, the MCP3204/08 is accessed via an interface
- * resembling SPI. Instructing the ADC to sample a channel and reading the
- * value back is done in a simple transaction. In fact, the SPI clock drives the
- * spproximation. The MCP3208/04 is an incredible simple, feature-less and
- * obsolete 12bit ADC. I have no idea why people would buy this piece of crap.
- *
- * We are stuck with this chip, because a certain someone ordered it. If you get
- * the change to get a new one, with a higher dynamic and an internal gain,
- * throw away the MCP.
- */
- // system headers
- #include <linux/spi/spidev.h>
- #include <linux/types.h>
- #include <sys/ioctl.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- // library headers
- #include <errno.h>
- #include <stdint.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- /**
- * Prepare the ADC and the SPI interface
- *
- * This function sets up the SPI interface and the ADC.
- *
- * @returns 0 on success, -1 on error
- */
- static int setup_adc(
- int fd ///< fd of the ADC
- ) {
- uint8_t mode = 0;
- return ioctl(fd, SPI_IOC_WR_MODE, &mode);
- }
- /**
- * Read a channel's value from the ADC
- *
- * Read the value of a specific channel from the ADC. Multiple samples may be
- * read. In this case, the function will return the average of all samples. This
- * lets the caller choose the number of samples freely without adjusting any
- * calculation carried out afterwards.
- *
- * Obviously, the ADC has to be set up before calling this function.
- *
- * @returns the channel's value, or -1 if an error occured
- */
- static int read_channel(
- int fd, ///< fd of the ADC
- unsigned int channel, ///< channel to read
- unsigned int samples ///< number of samples
- ) {
- // A transaction looks like the following:
- //
- // TX 1|M|C2-C0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|
- // RX 0|0|0|0|0|0| V11-V0 | V1-V11 |0|0|
- //
- // The transaction is started by writing a single `1`, followed by the mode
- // (`1` for single mode, e.g. not differential) and the channel number (MBS
- // first). The response is delivered a single clock later. It consist of the
- // freshly sampled value, MSB first, followed by the same value in reversed
- // bit order. Note that the LSB is shared.
- // We prepare a single TX buffer instructing the ADC to sample the channel.
- // The buffer is reused in all transactions.
- uint8_t tx[4] = {0xc0 | channel << 3, 0};
- // We fetch multiple samples in one batch. Therefore, we need multiple RX
- // buffers. Each single buffer is a sequence of 4 bytes.
- uint8_t rx[4 * samples];
- // We prepare a transaction for each sample
- struct spi_ioc_transfer tr[samples];
- memset(tr, 0, sizeof(tr[0]) * samples);
- for (unsigned int i = 0; i < samples; ++i) {
- tr[i].tx_buf = (__u64) tx;
- tr[i].rx_buf = (__u64) &rx[i*4];
- tr[i].len = 4;
- tr[i].delay_usecs = 1;
- // we need >10kHz in order to prevent the S&H from floating away
- tr[i].speed_hz = 20000;
- tr[i].bits_per_word = 8;
- tr[i].cs_change = 1;
- }
- // Perform the sampling
- if (ioctl(fd, SPI_IOC_MESSAGE(samples), tr) < 0)
- return -1;
- // Calculate the AVG as advertised
- unsigned int sum = 0;
- for (unsigned int i = 0; i < samples; ++i) {
- // The RX buffer is filled byte-wise. For bit-fiddling we need to
- // convert it to a known endianess. We chose to interpret the buffer as
- // low-endian. This way, we can extract the value by simply shifting
- // it to the right.
- uint32_t buf = ((uint32_t) rx[4*i+0]) << 24 |
- ((uint32_t) rx[4*i+1]) << 16 |
- ((uint32_t) rx[4*i+2]) << 8 |
- ((uint32_t) rx[4*i+3]);
- sum += buf >> 13;
- }
- return sum / samples;
- }
- /**
- * Main routine, obviously
- *
- * Go figure
- */
- int main(int argc, char* argv[]) {
- // Defaults
- const char* device = "/dev/spidev0.0";
- unsigned int samples = 1;
- // Read options
- int opt;
- while ((opt = getopt(argc, argv, "d:n:")) != -1) {
- switch (opt) {
- case 'd':
- device = optarg;
- break;
- case 'n':
- samples = atoi(optarg);
- break;
- default:
- fprintf(stderr, "Usage: %s, [-d device] [-n sample_count] channels...\n", argv[0]);
- exit(EXIT_FAILURE);
- }
- }
- // Prepare the device
- int fd = open(device, O_RDWR);
- if (fd < 0) {
- fprintf(stderr, "Could not open device: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- }
- if (setup_adc(fd) < 0) {
- fprintf(stderr, "Could not setup ADC: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- }
- // Read the values and calculate the sum
- unsigned int sum = 0;
- unsigned int count = 0;
- while (optind < argc) {
- unsigned int channel = atoi(argv[optind]);
- // The MCP3208 only has 8 channels.
- if (channel > 8) {
- fprintf(stderr, "Invalid channel: %s\n", argv[optind]);
- exit(EXIT_FAILURE);
- }
- int value = read_channel(fd, channel, samples);
- if (value < 0) {
- fprintf(stderr, "Error reading channel %d: %s\n", channel, strerror(errno));
- exit(EXIT_FAILURE);
- }
- sum += value;
- ++optind;
- ++count;
- }
- // Success! Print the result.
- printf("%d\n", sum);
- exit(EXIT_SUCCESS);
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement