Driving seven-segment display with MCP23S17 IO expander


#1

Hello everyone,
I've been playing with my axoloti and imagining some potential hardware designs for it, and I picked up a few components to use in a mono-synth box I want to build. I have a rotary encoder and 2-digit seven-segment display that I'd like to use for patch or parameter changes, and I picked up a an MCP23S17 16-bit digital IO expander (datasheet here) so as not to use up 14 pins for the display alone. So I've been trying for two days now and still can't get any output from the GPIO pins. confused

I should say that I'm a computer programmer by trade but have never really done embedded/serial/chip programming (or much of anything in C) so a lot of my confusion is just my inexperience. I put together a patch and script based on the LKM1638 example patch Johannes recommended, as well as some elements from @paul's 74HC595 shift register patch. I also took the register addresses from this tutorial.

I know that the chip select, SPI clock, and data output/input pins should be PA4, PA5, PA6, and PA7, but their arrangement seems to inconsistent between patches. I've tried several combinations, but is this documented somewhere?

Below is a photo of my breadboard and the patch is also attached. I'm sure there are some obvious mistakes throughout. I can get segments to light up if I connect the "interrupt output" pins (19 or 20) to the display, so it clearly has power, but otherwise no dice. Can someone point me in the right direction?

mcp23s17 io expander.axp (3.1 KB)


Connecting LCD to Axoloti
The Xylobox Wavetable Synthesizer (hardware + patches)
#2

I don't know the chip and I can't test your patch right now, but the data sheet says its maximum clock is 10MHz. As I remember, the default SPI settings give a clock of 20MHz, try setting it slower in the SPI config object. The SPI pinout is always the same: PA4 is the chip select, PA5 is clock, PA6 is input (from the point of view of Axoloti) and PA7 is output.

Do you have access to an oscilloscope of any sort? Seeing the signals as you poke around will make this much easier for you.


#3

Pin out is as rvsense says
PA6 is most often called "MISO" : master input, slave output.
PA7 is "MOSI", master output, slave input.
Axoloti is the master.

Where do you find contradicting info? I'd like to correct/clear up confusing info.

Are you using DMA capable memory in your code? All data buffers used in spiSend(), spiReceive or spiExchange must be declared like this:

static uint8_t _txbuf[8] __attribute__ ((section (".sram2")));
static uint8_t _rxbuf[8] __attribute__ ((section (".sram2")));

You're just hooking wire into the prototyping holes of Axoloti? That will not give a reliable connection. I suggest soldering a strip of female headers into the holes of Axoloti, that is the common way to do it in arduino world. Still, inserting an unstranded wire into a female header strip comes loose very quickly, I am not a big fan. An alternative is soldering a male pinheader strip to the bottom side of Axoloti Core, and then plugging that into your solderless breadboard.


#4

Thanks for the quick replies! I think the documentation is probably consistent, but PA6 and PA7 are variously called DIO, MOSI, and "SPI output" in people's demo patches, so I was confused as to whether they were switched depending on configuration.

Clock speed could definitely be a culprit – will try the lowest baudrates first. I do have an old cathode ray scope I could hook up, but I have no probes for it at the moment (have just been using it for A/V graphics stuff).

Yes, the bare wire is probably not helping either. It makes a solid enough connection for analog stuff (operates smoothly when turning potentiometers for example), but now that I know the pinout properly I can just solder some leads on for now. Header strip is not a bad idea – though I think I'll want a big ribbon cable connector when I actually prototype it. Do you know offhand if there's an appropriate molex-type header at 2.54mm pitch?


#5

And yes, I think the buffer is correctly declared – that part I copied directly from the LKM patch. It's a [32] byte array at the moment – perhaps that's the issue. Here's the script as of now:

  // MCP23S17 I/O EXPANDER SPI
  // by matthew cieplak

  uint8_t *txbuf;

  #define MCP_IODIRA 0x00
  #define MCP_IODIRB 0x01
  #define MCP_IOCONA 0x0A
  #define MCP_GPPUA  0x0C
  #define MCP_GPPUB  0x0D
  #define MCP_GPIOA  0x12
  #define MCP_GPIOB  0x13

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

  	mcp_send_command(MCP_IOCONA,0x28);   // I/O Control Register: BANK=0, SEQOP=1, HAEN=1 (Enable Addressing)
  	mcp_send_command(MCP_IODIRA,0x00);   // GPIOA As Output
  	mcp_send_command(MCP_IODIRB,0x00);   // GPIOB As Output
  	mcp_send_command(MCP_GPIOA,0x00);    // Reset Output on GPIOA
  	mcp_send_command(MCP_GPIOB,0x00);    // Reset Output on GPIOA
  	
  }

  void mcp_send_command(uint8_t reg, uint8_t val){
  	spiSelect(&SPID1);
  	txbuf[0] = reg;
  	txbuf[1] = val;
  	spiSend(&SPID1,2, txbuf);
  	spiUnselect(&SPID1);
  }

  void loop(void){
  	spiSelect(&SPID1);
  	//txbuf[0] = (in1>>8);     // FOR PATCH INPUT
  	//txbuf[1] = (in1);	   // FOR PATCH INPUT
  	txbuf[0] = MCP_GPIOB;
  	txbuf[1] = 0xff;
  	spiSend(&SPID1,2,txbuf);
  	spiUnselect(&SPID1);
  	chThdSleepMilliseconds(1);
  }

#6

In the lkm1638 case, MOSI and MISO are wired together for bidirectional communication, and then called "Digital Input Output"...

output of the master or output of the slave, can be confusing. I like MOSI/MISO best. But SPI is really a "jungle standard"...

Oh I think you need to address the mcp23S17 too...

// MCP23S17 SPI Slave Device
#define SPI_SLAVE_ID    0x40
#define SPI_SLAVE_ADDR  0x00      // A2=0,A1=0,A0=0
#define SPI_SLAVE_WRITE 0x00
#define SPI_SLAVE_READ  0x01

      void mcp_send_command(uint8_t reg, uint8_t val){
        spiSelect(&SPID1);
        txbuf[0] = SPI_SLAVE_ID | ((SPI_SLAVE_ADDR << 1) & 0x0E)| SPI_SLAVE_WRITE;
        txbuf[1] = reg;
        txbuf[2] = val;
        spiSend(&SPID1,3, txbuf);
        spiUnselect(&SPID1);
      }

#7

Thanks for the help, guys! Addressing the chip properly, lowering the baudrate and double-checking the connections seems to work. It's still very brittle (sometimes turns off after a few minutes use) but I'll chalk that up to my horrendous connectors...

Next I need figure out how to convert float input from the script object into int array indices for the truth table (just counting change events now...) Then I'll breadboard the encoder input circuit and then I can get to prototyping.


#8

I recommend putting a 100nF capacitor across VDD and VSS of the mcp23s17, as close to that chip as possible, for decoupling. May not make a difference in stability now, but it is common good practice.

What sort of encoder and input circuit do you have in mind? Just a rotary quadrature encoder?


#9

Yep, just a regular rotary quadrature encoder. I built the input circuit and patch today, just following the recommended "input filter circuit" from the manufacturer's datasheet. (just some 10k resistors in parallel and 0.01uF caps to ground). I connected Terminal A and B to 2 digital input pads on axoloti, and the below patch decodes into an increment/decrement pair it pretty perfectly, even at high speeds.

encoder_quadrature.axp (3.1 KB)


Relative encoders (midi)
#10

You can skip the 10k resistors if you configure the gpio/in/digital mode to "pulldown", and probably can skip the 0.01uF caps too.


#11

Finally got this thing working properly. I took my display and encoder circuits and built a protoboard with them (my first project that didn't involve electrical tape!). It looks like this:

It took me a whole lot of head scratching (and a run to the store for IC sockets) to figure out that the reset pin (labeled as an output on the MCP23S17 block diagram) is actually an input that must be externally biased (to +3v/5v) to keep sync with the master SPI clock. (Thought I'd fried the IC, but turns out I'd just failed to read the datasheet...) After that incidient, it was pretty easy to convert the encoder output into 2 digits for the display, and now it works like a charm!

Now I'm off to figure out where to buy potentiometers in bulk. The patch is included below for anyone trying to work this out later:

mcp23s17_seven_segment.axp (7.8 KB)


#12

Reset is to reset the complete state of the MCP23S17, for syncing, connect the CS input to NSS (not slave select) of the stm32 SPI peripheral.


#13

Could i drive this 8*8 dot matrix too with the MCP23S17?
https://www.led-tech.de/de/Displays-und-Matrixen/Dotmatrixen/Dotmatrix-Anzeige-8x8-60x60mm-LT-1374_127_130.html
I noticed that your counter only goes to 64, so it is somehow similar..
And what if i would like to drive a 3 7segment display?


#14

Yes, I think you could drive the 8x8 dot matrix with the MCP23s17, but for complex shapes you would have to do line-by-line scan (multiplexing) within the axoloti code. So it might be better to use a matrix driver chip. Ditto for 3-digit displays, as you would need 21 output pins to manage it in an analog fashion as I have done with the MCP. Here is an example multiplexed setup with the MSP430:
http://forum.allaboutcircuits.com/blog/msp430-multiplexed-7-segment-displays.559/