103 lines
3.4 KiB
C
103 lines
3.4 KiB
C
/*
|
|
* mcp3008.c — 8-channel SPI ADC.
|
|
*
|
|
* Pins: CS, SCK, MOSI (DIN), MISO (DOUT), CH0..CH7 (analog inputs).
|
|
*
|
|
* Protocol: master sends a 3-byte command to start a conversion:
|
|
* byte 0: 0x01 (start bit)
|
|
* byte 1: top nibble = SGL/DIFF (1 bit) + channel (3 bits), shifted left
|
|
* byte 2: don't care
|
|
* The chip responds with:
|
|
* byte 0: ignored
|
|
* byte 1: low 2 bits = upper bits of 10-bit result
|
|
* byte 2: lower 8 bits of result
|
|
*
|
|
* Each channel reads its analog voltage via vx_pin_read_analog().
|
|
*/
|
|
|
|
#include "velxio-chip.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
static const char* CH_NAMES[8] = {"CH0","CH1","CH2","CH3","CH4","CH5","CH6","CH7"};
|
|
|
|
typedef struct {
|
|
vx_pin CS, SCK, MOSI, MISO;
|
|
vx_pin CH[8];
|
|
vx_spi spi;
|
|
uint8_t buf[3]; /* SPI exchange buffer */
|
|
} chip_state_t;
|
|
|
|
static void on_cs_change(void* ud, vx_pin pin, int value) {
|
|
chip_state_t* s = (chip_state_t*)ud;
|
|
if (value == VX_LOW) {
|
|
/* Pre-fill buffer: byte 0 ignored, bytes 1..2 will be filled with the
|
|
* conversion result after the chip sees the command. For now, send 0xff
|
|
* (NACK markers) and the on_done callback handles real responses. */
|
|
s->buf[0] = 0xff;
|
|
s->buf[1] = 0xff;
|
|
s->buf[2] = 0xff;
|
|
vx_spi_start(s->spi, s->buf, 3);
|
|
} else {
|
|
vx_spi_stop(s->spi);
|
|
}
|
|
}
|
|
|
|
static void on_spi_done(void* ud, uint8_t* buffer, uint32_t count) {
|
|
chip_state_t* s = (chip_state_t*)ud;
|
|
if (count < 3) return;
|
|
|
|
/* buffer now holds master's 3 bytes after the exchange completed.
|
|
* Parse: byte[0] = start bit, byte[1] = SGL_DIFF+channel<<4 in upper nibble. */
|
|
uint8_t start = buffer[0] & 0x01;
|
|
uint8_t header = buffer[1];
|
|
uint8_t channel = (header >> 4) & 0x07;
|
|
if (!start) return;
|
|
|
|
/* Read the analog voltage and convert to 10-bit ADC value (0..1023, ref=5V). */
|
|
double voltage = vx_pin_read_analog(s->CH[channel]);
|
|
if (voltage < 0) voltage = 0;
|
|
if (voltage > 5.0) voltage = 5.0;
|
|
uint16_t result = (uint16_t)((voltage / 5.0) * 1023.0 + 0.5);
|
|
|
|
/* Set up the response for the next exchange — typical MCP3008 use sends
|
|
* 3 master bytes to receive the full 10-bit value, but our buffer-based
|
|
* model already overwrites in-place. To send the response back in the same
|
|
* transaction we'd need to pre-fill before vx_spi_start. Since the master
|
|
* ALREADY sent its 3 bytes, we record the result and the chip's miso pin
|
|
* should be driven; for the test API, we use a follow-up start. */
|
|
s->buf[0] = 0x00;
|
|
s->buf[1] = (uint8_t)((result >> 8) & 0x03); /* upper 2 bits */
|
|
s->buf[2] = (uint8_t)(result & 0xff); /* lower 8 bits */
|
|
|
|
/* Begin the response exchange (same buffer, master will clock it out). */
|
|
vx_spi_start(s->spi, s->buf, 3);
|
|
}
|
|
|
|
void chip_setup(void) {
|
|
chip_state_t* s = (chip_state_t*)calloc(1, sizeof(chip_state_t));
|
|
s->CS = vx_pin_register("CS", VX_INPUT_PULLUP);
|
|
s->SCK = vx_pin_register("SCK", VX_INPUT);
|
|
s->MOSI = vx_pin_register("MOSI", VX_INPUT);
|
|
s->MISO = vx_pin_register("MISO", VX_OUTPUT_HIGH);
|
|
|
|
for (int i = 0; i < 8; i++) {
|
|
s->CH[i] = vx_pin_register(CH_NAMES[i], VX_ANALOG);
|
|
}
|
|
|
|
vx_spi_config cfg = {
|
|
.sck = s->SCK,
|
|
.mosi = s->MOSI,
|
|
.miso = s->MISO,
|
|
.cs = s->CS,
|
|
.mode = 0,
|
|
.on_done = on_spi_done,
|
|
.user_data = s,
|
|
};
|
|
s->spi = vx_spi_attach(&cfg);
|
|
|
|
vx_pin_watch(s->CS, VX_EDGE_BOTH, on_cs_change, s);
|
|
|
|
vx_log("MCP3008 ADC ready");
|
|
}
|