[GPIO I/O] Example ADC MCP3208 & SPI


#13

Ok I'm hoping this is a software problem. I get accurate readings on 16 pots using 2 MCP3208 if, and here's the catch...

I always get accurate readings from MCP3208 #1, the one connected to axoloti PA4
I only get accurate readings from MCP3208 #2, connected to PA3 if the pot on the same pin number on the other MCP3208 is turned to the maximum, otherwise I get some sort of average. If I turn the pot down, I get no reading at all..
I'm guessing somewhere in my script I multiply the values,
- but I reinitialised the vars,
- I increased the sleeptime between polling (thought it was some overflow)
- I renamed the z var in the second loop

It took me quite a while to figure out why everything was dancing and then not, I thought it was hardware, so I resoldered a lot of things... apparently for nothing, or not? a bit puzzled.

below is my version of the script

/*
MCP3208 script2 code
by paul
adapted by kaos, this script is not working, there is problem...

Connect the MCP3208 CS pin to the NSS(PA4) or any other digital out pin of axoloti.
In this example, the MCP3208 CS pin is connected to axoloti B0(GPIOB,0) pin.

If you use more then one spi device, it's importent to disable every other spi device by switching their cs pin. 
*/

uint8_t *txbuf;
uint8_t *rxbuf;
const SPIConfig spicfg_a = {NULL, GPIOA, 4, 3<<3  | SPI_CR1_LSBFIRST }; //MCP3208 pin PA4
const SPIConfig spicfg_b = {NULL, GPIOA, 3, 3<<3  | SPI_CR1_LSBFIRST }; //MCP3208 pin PA3




void setup(void){
	static uint8_t _txbuf[8] __attribute__ ((section (".sram2")));
	static uint8_t _rxbuf[8] __attribute__ ((section (".sram2")));
	txbuf = _txbuf;
	rxbuf = _rxbuf;

	palSetPadMode(GPIOA,4,PAL_MODE_OUTPUT_PUSHPULL);	// MCP3208
	palSetPadMode(GPIOA,3,PAL_MODE_OUTPUT_PUSHPULL);	// MCP3208
	//changes
	palWritePad(GPIOA,4,1);								// pull high to disable first MCP3208
	palWritePad(GPIOA,3,1);								// pull high to disable second MCP3208
}

void loop(void){
	// txtbuf[0] = txtbuf[1] = txtbuf[2] = 0b00000000;
   	txbuf[2] = 0b00000000;
 	
    	for(int pin=0; pin<8; pin++){
 
	        txbuf[0] = pin < 4 ? 0b01100000 : 0b11100000;
	 
	        if (pin % 4 == 0) { // pin == 0 || pin == 4
	            txbuf[1] = 0b00000000;
	        } else if (pin % 4 == 1) { // pin == 1 || pin == 5
	            txbuf[1] = 0b00000010;
	        } else if (pin % 4 == 2) { // pin == 2 || pin == 6
	            txbuf[1] = 0b00000001;
	        } else {
	            txbuf[1] = 0b00000011;
	        }
			

		palWritePad(GPIOA,4,0);		// enable MCP3208 #1
		spiSelect(&SPID1);			// START SPI
		spiSend(&SPID1,3,txbuf);	// send SPI data txbuf[]
		spiReceive(&SPID1,3,rxbuf);	// receive SPI data from MCP3208
		spiUnselect(&SPID1);		// STOP SPI
		palWritePad(GPIOA,4,1);		// disable MCP3208 #1
				
		int z = (rxbuf[1] << 8| rxbuf[0]) << 16;

		if (pin == 0){
			PExParameterChange(&parent->PExch[PARAM_INDEX_c0_value],z,0xFFFD);
		}
		else if (pin == 1){
			PExParameterChange(&parent->PExch[PARAM_INDEX_c1_value],z,0xFFFD); 
		}
		else if (pin == 2){
			PExParameterChange(&parent->PExch[PARAM_INDEX_c2_value],z,0xFFFD);
		}
		else if (pin == 3){
			PExParameterChange(&parent->PExch[PARAM_INDEX_c3_value],z,0xFFFD);
		}
		else if (pin == 4){
			PExParameterChange(&parent->PExch[PARAM_INDEX_c4_value],z,0xFFFD);
		}
		else if (pin == 5){
			PExParameterChange(&parent->PExch[PARAM_INDEX_c5_value],z,0xFFFD);
		}
		else if (pin == 6){
			PExParameterChange(&parent->PExch[PARAM_INDEX_c6_value],z,0xFFFD);
		}
		else if (pin == 7){
			PExParameterChange(&parent->PExch[PARAM_INDEX_c7_value],z,0xFFFD);
		}
	
	}
	chThdSleepMilliseconds(10);
	txbuf[2] = 0b00000000;

	for(int pin=0; pin<8; pin++){
 
	        txbuf[0] = pin < 4 ? 0b01100000 : 0b11100000;
	 
	        if (pin % 4 == 0) { // pin == 0 || pin == 4
	            txbuf[1] = 0b00000000;
	        } else if (pin % 4 == 1) { // pin == 1 || pin == 5
	            txbuf[1] = 0b00000010;
	        } else if (pin % 4 == 2) { // pin == 2 || pin == 6
	            txbuf[1] = 0b00000001;
	        } else {
	            txbuf[1] = 0b00000011;
	        }

		palWritePad(GPIOA,3,0);		// enable MCP3208  #2
		spiSelect(&SPID1);			// START SPI
		spiSend(&SPID1,3,txbuf);	// send SPI data txbuf[]
		spiReceive(&SPID1,3,rxbuf);	// receive SPI data from MCP3208
		spiUnselect(&SPID1);		// STOP SPI
		palWritePad(GPIOA,3,1);		// disable MCP3208 #2
				
		int zz = (rxbuf[1] << 8| rxbuf[0]) << 16;

		if (pin == 0){
			PExParameterChange(&parent->PExch[PARAM_INDEX_d0_value],zz,0xFFFD);
		}
		else if (pin == 1){
			PExParameterChange(&parent->PExch[PARAM_INDEX_d1_value],zz,0xFFFD); 
		}
		else if (pin == 2){
			PExParameterChange(&parent->PExch[PARAM_INDEX_d2_value],zz,0xFFFD);
		}
		else if (pin == 3){
			PExParameterChange(&parent->PExch[PARAM_INDEX_d3_value],zz,0xFFFD);
		}
		else if (pin == 4){
			PExParameterChange(&parent->PExch[PARAM_INDEX_d4_value],zz,0xFFFD);
		}
		else if (pin == 5){
			PExParameterChange(&parent->PExch[PARAM_INDEX_d5_value],zz,0xFFFD);
		}
		else if (pin == 6){
			PExParameterChange(&parent->PExch[PARAM_INDEX_d6_value],zz,0xFFFD);
		}
		else if (pin == 7){
			PExParameterChange(&parent->PExch[PARAM_INDEX_d7_value],zz,0xFFFD);
		}	
	}
	chThdSleepMilliseconds(10);
}

Kaos


#14

Sounds like #1 is replying too when you try to address only #2, and you get a "bitwise and" of the two pots as a result...
Looks like you're not using spicfg_a/spicfg_b configs, and you should...

Let me also suggest to try sending and receiving all channel data in a single spiExchange() call, way more efficient...


#15

I am, in the script it says

I guess I have to reset the rxbuf somewhere? It just gets copied over

I'll google the spiExchange, makes sense to me to make it more efficient, not done adding stuff here :smile:

K


#16

err, no, that's only the declaration.
spiStart() takes it as an argument.
No need to reset rxbuf.


#17

right, so I call it in the setup routine as something like this:

palWritePad(GPIOA,4,0);		// enable MCP3208 #1

> spiStart(&spicfg_a);
spiSelect(&SPID1); // START SPI
spiSend(&SPID1,3,txbuf); // send SPI data txbuf[]
spiReceive(&SPID1,3,rxbuf); // receive SPI data from MCP3208
spiUnselect(&SPID1); // STOP SPI
palWritePad(GPIOA,4,1); // disable MCP3208 #1

but that fails. looking in the SPI Chibios Docs i need to call it as

void spiStart	(	
   SPIDriver * 	spip,
const SPIConfig * 	config 
)

but I have no clue on the SPIdriver, no pointers defined in the example code...

K


#18

scroll up a bit here


#19

OK it works!

> spiStart(&SPID1, &spicfg_b);

16 pots using only 5 pins...optimising will be for some other time!
onto the next challenge :smile:

Thanks!

/*
MCP3208 script2 code
by paul
adapted by kaos, this script is not optimized, but it works!
Connect the MCP3208 CS pin to the NSS(PA4) or any other digital out pin of axoloti.
In this example, the MCP3208 CS pin is connected to axoloti B0(GPIOB,0) pin.

If you use more then one spi device, it's importent to disable every other spi device by switching their cs pin. 
*/

uint8_t *txbuf;
uint8_t *rxbuf;
const SPIConfig spicfg_a = {NULL, GPIOA, 4, 3<<3  | SPI_CR1_LSBFIRST }; //MCP3208 pin PA4
const SPIConfig spicfg_b = {NULL, GPIOA, 3, 3<<3  | SPI_CR1_LSBFIRST }; //MCP3208 pin PA3




void setup(void){
	static uint8_t _txbuf[8] __attribute__ ((section (".sram2")));
	static uint8_t _rxbuf[8] __attribute__ ((section (".sram2")));
	txbuf = _txbuf;
	rxbuf = _rxbuf;

	palSetPadMode(GPIOA,4,PAL_MODE_OUTPUT_PUSHPULL);	// MCP3208
	palSetPadMode(GPIOA,3,PAL_MODE_OUTPUT_PUSHPULL);	// MCP3208
	//changes
	palWritePad(GPIOA,4,1);								// pull high to disable first MCP3208
	palWritePad(GPIOA,3,1);								// pull high to disable second MCP3208
}

void loop(void){
	// txtbuf[0] = txtbuf[1] = txtbuf[2] = 0b00000000;
   	txbuf[2] = 0b00000000;
 	
    	for(int pin=0; pin<8; pin++){
 
	        txbuf[0] = pin < 4 ? 0b01100000 : 0b11100000;
	 
	        if (pin % 4 == 0) { // pin == 0 || pin == 4
	            txbuf[1] = 0b00000000;
	        } else if (pin % 4 == 1) { // pin == 1 || pin == 5
	            txbuf[1] = 0b00000010;
	        } else if (pin % 4 == 2) { // pin == 2 || pin == 6
	            txbuf[1] = 0b00000001;
	        } else {
	            txbuf[1] = 0b00000011;
	        }
			

		palWritePad(GPIOA,4,0);		// enable MCP3208 #1
		spiStart(&SPID1, &spicfg_a);
		spiSelect(&SPID1);			// START SPI
		spiSend(&SPID1,3,txbuf);	// send SPI data txbuf[]
		spiReceive(&SPID1,3,rxbuf);	// receive SPI data from MCP3208
		spiUnselect(&SPID1);		// STOP SPI
		palWritePad(GPIOA,4,1);		// disable MCP3208 #1
				
		int z = (rxbuf[1] << 8| rxbuf[0]) << 16;

		if (pin == 0){
			PExParameterChange(&parent->PExch[PARAM_INDEX_c0_value],z,0xFFFD);
		}
		else if (pin == 1){
			PExParameterChange(&parent->PExch[PARAM_INDEX_c1_value],z,0xFFFD); 
		}
		else if (pin == 2){
			PExParameterChange(&parent->PExch[PARAM_INDEX_c2_value],z,0xFFFD);
		}
		else if (pin == 3){
			PExParameterChange(&parent->PExch[PARAM_INDEX_c3_value],z,0xFFFD);
		}
		else if (pin == 4){
			PExParameterChange(&parent->PExch[PARAM_INDEX_c4_value],z,0xFFFD);
		}
		else if (pin == 5){
			PExParameterChange(&parent->PExch[PARAM_INDEX_c5_value],z,0xFFFD);
		}
		else if (pin == 6){
			PExParameterChange(&parent->PExch[PARAM_INDEX_c6_value],z,0xFFFD);
		}
		else if (pin == 7){
			PExParameterChange(&parent->PExch[PARAM_INDEX_c7_value],z,0xFFFD);
		}
	
	}
	chThdSleepMilliseconds(10);
	txbuf[2] = 0b00000000;

	for(int pin=0; pin<8; pin++){
 
	        txbuf[0] = pin < 4 ? 0b01100000 : 0b11100000;
	 
	        if (pin % 4 == 0) { // pin == 0 || pin == 4
	            txbuf[1] = 0b00000000;
	        } else if (pin % 4 == 1) { // pin == 1 || pin == 5
	            txbuf[1] = 0b00000010;
	        } else if (pin % 4 == 2) { // pin == 2 || pin == 6
	            txbuf[1] = 0b00000001;
	        } else {
	            txbuf[1] = 0b00000011;
	        }

		palWritePad(GPIOA,3,0);		// enable MCP3208  #2
		spiStart(&SPID1, &spicfg_b);
		spiSelect(&SPID1);			// START SPI
		spiSend(&SPID1,3,txbuf);	// send SPI data txbuf[]
		spiReceive(&SPID1,3,rxbuf);	// receive SPI data from MCP3208
		spiUnselect(&SPID1);		// STOP SPI
		palWritePad(GPIOA,3,1);		// disable MCP3208 #2
				
		int zz = (rxbuf[1] << 8| rxbuf[0]) << 16;

		if (pin == 0){
			PExParameterChange(&parent->PExch[PARAM_INDEX_d0_value],zz,0xFFFD);
		}
		else if (pin == 1){
			PExParameterChange(&parent->PExch[PARAM_INDEX_d1_value],zz,0xFFFD); 
		}
		else if (pin == 2){
			PExParameterChange(&parent->PExch[PARAM_INDEX_d2_value],zz,0xFFFD);
		}
		else if (pin == 3){
			PExParameterChange(&parent->PExch[PARAM_INDEX_d3_value],zz,0xFFFD);
		}
		else if (pin == 4){
			PExParameterChange(&parent->PExch[PARAM_INDEX_d4_value],zz,0xFFFD);
		}
		else if (pin == 5){
			PExParameterChange(&parent->PExch[PARAM_INDEX_d5_value],zz,0xFFFD);
		}
		else if (pin == 6){
			PExParameterChange(&parent->PExch[PARAM_INDEX_d6_value],zz,0xFFFD);
		}
		else if (pin == 7){
			PExParameterChange(&parent->PExch[PARAM_INDEX_d7_value],zz,0xFFFD);
		}	
	}
	chThdSleepMilliseconds(10);
}

#20

How many MCP3208 devices is possible to use with axoloti or what is the max number of pots is posible to connect to axoloti using mcp chips? Do they intorduce aditional cpu load?


#21

My not the best to answer this BUT I think the max is 8 per 3208 , so that gives 128
Load I'd assume with increase linearly per 3208, as each is streated separately

Note: if not tried this, as I don't have any 3208s, this is based on the code and circuits I've seen here


#22

Yes, 8 MCP3208 devices is the maximum that can be addressed.
The main concern is not CPU load, but the update rate. If a single conversion is scheduled every millisecond, at 128 channels that gets you 8 samples per second. Scheduling conversions at 3kHz (control rate), gets you 24 samples per second. Scheduling faster gets tricky, as it will start to interfere with control rate.
The MCP3208 protocol requires to setup a data transaction for each conversion, it would be much nicer if it could read out its 8 channels in a single data transaction. Perhaps some other converter exists that can do this, but don't know a widely available cheap part number that can do this, but I have not searched extensively.


The Xylobox Wavetable Synthesizer (hardware + patches)
Which potentiometers should I buy?
#23

can I just understand this correctly?
so basically, because they share the same spi port, you can only address one 3208 (and one pin of it) on each cycle, and you have to issue the send and receive on different cycles.

also quick wiring question...
on the above diagram the first 3208 has a cap between pins 2 and 3, is this also required for the second (and subsequent) 3208s?

(I'll note for my usage I'm not planning on that many pots ... but thought I might have some analogue inputs that I want a high sample rates, e.g. ribbon but a fews pots that could be lower if necessary)


#24

After setup, for every transaction, you need these calls:

spiSelect(&SPID1); // START SPI
spiSend(&SPID1,3,txbuf); // send SPI data txbuf[]
spiReceive(&SPID1,3,rxbuf); // receive SPI data from MCP3208
spiUnselect(&SPID1);

I think spiSend() and spiReceive() can be merged in one call of spiExchange. But the chip select input must see a high pulse between transactions. The data sheet shows DIN is "don't care" after the first transaction.

The spiSend(), spiReceive() and spiExchange() function calls setup the transaction (using DMA), suspends the current thread so other threads can do useful work in the meantime, and when the transaction is finished, it resumes the original thread.

There are several option to schedule the transactions:

  • Making a thread with a tight loop with spiX..() is a bit too aggressive, perhaps with the spi bitrate set to the slowest possible. I would not recommend.
  • Creating a thread with chThdSleepMilliseconds() in the loop is fine, but has a 1 millisecond timing granularity (as configured in firmware: chconfig.h). This is what I'd recommend
  • Calling spiX..() at k-rate (from the dsp thread), I would not recommend as that makes the time during the transaction unavailable for audio processing... It is possible to do SPI without the spiX..() calls, by manipulating the memory-mapped IO registers defined in stm32fxxx.h, within k-rate and poll for the result at the next k-rate interval. But such an implementation is very unportable.

The capacitor between pin 2 and 3, I think you're referring to pin 14 and 15. Pin numbering start at a "mark", often a dot, then go around counter-clock-wise. As a starting point I'd suggest a 100nF capacitor across every GND/VDD pair of pins of every chip, close to each pin pair. In this case, pin 15 is the voltage reference input, I'd give it a 100nF capacitor too on each chip. The mcp3208 datasheet has a chapter on Layout Considerations, they recommend a 1uF capacitor "placed as close as possible to the device pin". Such layout considerations are not supercritical to get it to work, but certainly useful to reach best possible performance. But I would not be surprised if it also works fine without any respect for the layout recommendations.


#25

What potentiometer values I should use with MCP3208? The same as for axoloti analog inputs?
Can I use 74HC4051 or 74HC4067 multiplexer, wil the script code wil be the same?


#26

Can I connect MOSI, MISO, SCK, NSS/CS of MCP3208 to pins PB6-PB9?
Then it will be possible to use all 15 analog inputs of axoloti and 8 additional from MCP3208, so in total 23 ins?


#27

Sorry, it is not possible to move the pin assignments for MOSI, MISO and SCK.

I have not studied those, I guess it will be the same or very similar.

yeah, use the same.


Input resistance/impedance of GPIOs in AnalogIn Mode?
#28

Thank you Johannes.

I have one more question:

In this example, the MCP3208 CS pin is connected to axoloti B0(GPIOB,0) pin.

If you use more then one spi device, it's importent to disable every other spi device by switching their cs pin.
*/

uint8_t *txbuf;
uint8_t *rxbuf;
const SPIConfig spicfg_a = {NULL, GPIOA, 4, 3<<3 | SPI_CR1_LSBFIRST }; //MCP3208 pin PA4
const SPIConfig spicfg_b = {NULL, GPIOA, 3, 3<<3 | SPI_CR1_LSBFIRST }; //MCP3208 pin PA3

Does this mean that second mcp3208 CS pin connected to PA3, but not to B0(GPIOB,0) pin? Or there is somewhere mismatch in axoloti labels?


#30

Somehow I find the answer


#31

Does that mean 8 mpc3208's can be connected directly to axoloti? Or do they need to be chained? I am building an enclosure with 48 pots, 6 tact buttons, 6 leds, a 4 digit LCD display and a rotary encoder and I am trying to find out the best strategy to approach this.


#32

Hello !
I've wired my MCP3008 and seem to get an accurate reading, but like matthewcleplak a few post earlier, I can't get a value more than 3.97 and cannot figure out why..
I'm not that good with coding so i'm having troubles figuring out if it is in the script or due to the 10-bit depth of the MCP3008, or if it's a VREF problem...
I've used a 100nf cap beetwen pin 2 and 3 and a 100uf beetween the main Vdda and GND, it seem to work as there is almost no jitter. I'm using 100k pot, that works fine when connected into the Axo's DAC.
Any ideas ? I couldn't find a simple answer in that post, even though I think matthewcleplak figured it out...

EDIT : SOMEHOW, I FOUND THE ANSWER WHILE TRYING TO FIGURE OUT THE CODE; YOU NEED TO CHANGE THIS LINE, that is in the first example script :
int z = (rxbuf[1] << 8| rxbuf[0]) << 16;
to
int z = (rxbuf[1] << 8| rxbuf[0]) << 20;

The value now goes to 63.50... that'll do it for me, although I did It kinda randomly, if somebody could explain a little bit how this work and get that last 0.50...

Hope this will be useful to someone :slight_smile:


#33

Hi all,

I cascaded 3 MCP3008's and added some series resistors (47Ohm) in the CLK/MISO and MOSI lines to get the reading stable. This seems to work, but now the readings are fine till a potmeter passes half the range. Where am I missing a point...

BTW, when lowering the potmeter below the half-point, readings get stable again...

Patch used: mcp3208 tripple channel example.axp (13.2 KB)


SPI Problems with MCP3008 and MCP23s17