interrupts

Solar Cluster: Lessons learned

  1. Don’t rely on the internal pull-up in the MCU for the nRESET pin. It might work for no-connect scenarios, but it’s not going to win a war against a Olimex programmer dongle that’s decided to lean on the pin a bit too hard.
  2. The linker needs to know what MCU it is too.

I basically found I had the ATTiny24A resetting repeatedly when the programmer was connected. Set a LED to stay on, it’d blink. The cause was the programmer was interacting with the MCU via the reset pin when connected. The solution was a resistor between 5V and reset, I stuck a 47k across the relevant pins of the ICSP header. (Annoyingly, they’re at opposite corners!)

The other blooper was having me scratching my head every time I tried to define an interrupt, the MCU would just sit there. Even if the ISR had nothing in it, and sei was never called, it’d still sit there dumb.

My Makefile at this point looked like this:

CFLAGS = -Os -g -mmcu=attiny24a -Wall -Werror
CPPFLAGS = -DF_CPU=1000000UL
CROSS_COMPILE ?= avr-
CC = $(CROSS_COMPILE)gcc
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
SIZE = $(CROSS_COMPILE)size
PROG_ARGS ?=-c stk500v2 -P /dev/ttyACM0
PROG_DEV ?= t24

.PHONY: clean all

all: powerctl.ihex

clean:
	-rm *.ihex *.elf *.o

%.ihex: %.elf
	$(OBJCOPY) -j .text -j .data -O ihex $^ $@

%.elf:
	$(CC) -o $@ $^
	$(SIZE) -d $@
	$(OBJDUMP) -xdS $@

powerctl.elf: powerctl.o
test.elf: test.o

%.pgm: %.ihex
	avrdude $(PROG_ARGS) -p $(PROG_DEV) -U flash:w:$^:i

The call to objdump was to try and figure out what was going wrong. Note that I don’t pass -mmcu to the final link. The linker needs to know what MCU it is just as much as the compiler does. That was my error. Having done this, I now have working interrupts.

Solar Cluster: Working around build errors in crossdev

I’ve been doing further work on my charge controller. Last weekend, I managed to fix the issues that were causing the MOSFETs to not work, and the “faulty” MOSFET turned out to be fine: the fault was a glitch with my home-fabricated PCB. (No, commercial PCB makers, this is not an invitation for you to advertise as you have done on other projects! The job is done.)

In the midst of doing this, I was also having problems getting the toolchain to generate code correctly, the code would fail to run if I had an interrupt service routine defined, even if the interrupts were not enabled it still failed to do anything. Something in the interrupt vector table was off.

So back to crossdev to see if I can build a newer toolchain that works. crossdev -t avr would get as far as building the full C/C++ compiler, then crap out with a missing ldscript. (I don’t have the log handy to show you the exact error, but it takes place in a ./configure script, so you see “C compiler cannot generate executables” or some such like that, and will see the ldscript error in the offending config.log.)

The kludge?

# cd /usr/avr/lib
# ln -s ../../lib64/binutils/avr/${BINUTILS_VERSION}/ldscripts/

Substitute ${BINUTILS_VERSION} with the active version. Voila, having done that kludge, I now have this:

$ avr-gcc --version
avr-gcc (Gentoo 5.3.0 p1.1, pie-0.6.5) 5.3.0
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Not sure if it works or not, but I’m documenting the above as a note to myself next time I hit this error.

Interrupt controllers from logic gates

Well, in the last post I started to consider the thoughts of building my own computer from a spare 386 CPU I had liberated from an old motherboard.

One of the issues I face is implementing the bus protocol that the 386 uses, and decoding of interrupts.  The 386 expects an 8-bit interrupt request number that corresponds to the interrupting device.  I’m used to microcontrollers where you use a single GPIO line, but in this case, the interrupts are multiplexed.

For basic needs, you could do it with a demux IC.  That will work for a small number of interrupt lines.  Suppose I wanted more though?  How feasible is it to support many interrupt lines without tying up lots of GPIO lines?

CANBus has an interesting way of handling arbitration.  The “zeros” are dominant, and thus overrule “ones”.  The CAN transceiver is a full-duplex device, so as the station is transmitting, it listens to the state of the bus.  When some nodes want to talk (they are, of course, oblivious to each-others’ intentions), they start sending a start-bit (a zero) which synchronises all nodes, then begin sending an address.

While each node is sending the same “bit value”, the receiving nodes see that value.  As each node tries sending a 1 while the others are sending 0’s, it sees the disparity, and concludes that it has lost arbitration.  Eventually, you’re left with a single node that then proceeds to send its CANBus frame.

Now, we don’t need the complexity of CANBus to do what we’re after.  We can keep synchronisation by simple virtue that we can distribute a common clock (the one the CPU runs at).  Dominant and recessive bits can be implemented with transistors pulling down on a pull-up resistor, or a diode-OR: this will give us a system where ‘1’s are dominant.  Good enough.

So I figured up Logisim to have a fiddle, came up with this:

Interrupt controller using logic gates

Interrupt controller using logic gates

interrupt.circ is the actual LogiSim circuit if you wanted to have a fiddle; decompress it.  Please excuse the mess regarding the schematic.

On the left is the host-side of the interrupt controller.  This would ultimately interface with the 386.  On the right, are two “devices”, one on IRQ channel 0x01, the other on 0x05.  The controller handles two types of interrupts: “DMA interrupts”, where the device just wants to tell the DMA controller to put data into memory, or “IRQ”s, where we want to interrupt the CPU.

The devices are provided with the following control signals from the interrupt controller:

Signal Controlled by Description
DMA Devices Informs the IRQ controller if we’re interrupting for DMA purposes (high) or if we need to tell the CPU something (low).
IRQ Devices Informs the IRQ controller we want its attention
ISYNC Controller Informs the devices that they have the controller’s attention and to start transmitting address bits.
IRQBIT[2…0] Controller Instructs the devices what bit of their IRQ address to send (0 = MSB, 7 = LSB).
IDA Devices The inverted address bit value corresponding to the bit pointed to by IRQBIT.
IACK Devices Asserted by the device that wins arbitration.

Due to the dominant/recessive nature of the bits, the highest numbered device wins over lesser devices. IRQ requests also dominate over DMA requests.

In the schematic, the devices each have two D-flip-flops that are not driven by any control signals.  These are my “switches” for toggling the state of the device as a user.  The ones feeding into the XOR gate control the DMA signal, the others control the IRQ line.

Down the bottom, I’ve wired up a counter to count how long between the ISYNC signal going high and the controller determining a result.  This controller manages to determine which device requested its attention within 10 cycles.  If clocked at the same 20MHz rate as the CPU core, this would be good enough for getting a decoded IRQ channel number to the data lines of the 386 CPU by the end of its second IRQ acknowledge cycle, and can handle up to 256 devices.

A logical next step would be to look at writing this in Verilog and trying it out on an FPGA.  Thanks to the excellent work of Clifford Wolf in producing the IceStorm project, it is now possible to do this with completely open tools.  So, I’ve got a Lattice iCE40HX-8K FPGA board coming.  This should make a pretty mean SDRAM controller, interrupt controller and address decoder all in one chip, and should be a great introduction into configuring FPGAs.