May 272017

So… a bit of head scratching this afternoon. I had working PWM on the LEDs… which was fantastic.

With the trade-off of LEDs being ⅛ brightness maximum, which is fine since the LED strings that I’ve been given for this project would make Manfred Mann proud…

I was a little stumped with my audio PWM. I could not get a peep out of it, not via the amplifier or anything else. Tracing back, I was getting nothing out of the PWM pin. Real strange. Looked up the data sheets, couldn’t see what I was doing wrong.

My code for initialising Timer1 looked like this:

	/* Timer 1 configuration for PWM */
	OCR1B = 128;			/* Initial PWM value: audio */
	OCR1D = 32;			/* Initial PWM value: light */
	OCR1C = 255;			/* Maximum PWM value */
	TCCR1A = (2 << COM1B0)		/* Clear OC1B on match,
					   nOC1B not used */
		| (1 << PWM1B);		/* Enable PWM channel B */
	TCCR1B = (1 << CS10);		/* No prescaling, max speed */
	TCCR1C = (3 << COM1D0)		/* Set OC1D on match,
					   nOC1D not used */
		| (1 << PWM1D);		/* Enable PWM channel D */
	TCCR1D = (0 << WGM10); /* Fast PWM mode */

It looked fine… even checked the header files to see there wasn’t some cock up in the headers. Never had an issue with avr-libc, but you never know, and of course, no problem there.

In exasperation, I swapped setting up TCCR1A and TCCR1C. Bingo, I had PWM both channels. Why? Turns out there are some bits in TCCR1C that shadow TCCR1A, and I was inadvertently setting those back to 0 when I set up TCCR1C. So my code now:

	/* Timer 1 configuration for PWM */
	OCR1C = 255;			/* Maximum PWM value */
	TC1H = 0;			/* Reset counter high bits */
	TCNT1 = 0;			/* Reset counter low bits */
	OCR1A = 0;			/* Initial PWM value: spare */
	OCR1B = 127;			/* Initial PWM value: audio */
	OCR1D = 127;			/* Initial PWM value: light */
	TCCR1A = (1 << PWM1B);		/*
					 * Clear TCCR1A except for PWM1B,
					 * we'll configure OC1B
					 * via the shadow bits in TCCR1C
	TCCR1B = (1 << CS10);		/* No prescaling, max speed */
	TCCR1C = (2 << COM1B0S)		/* Clear OC1B on match,
					   nOC1B not used */
		| (3 << COM1D0)		/* Set OC1D on match,
					   nOC1D not used */
		| (1 << PWM1D);		/* Enable PWM channel D */
	TCCR1D = (0 << WGM10);		/* Fast PWM mode */
	TCCR1E = 0;			/* Not used: PWM6 mode */

We still need to turn PWM1B on via TCCR1A… but we set the output control mode via TCCR1C to avoid clobbering our settings.

Having fixed that, I now have a second problem, in my dead-bugging of the amp, I got my left and right arse-about, which would explain why my amp is making no noise. That’ll be a tomorrow job methinks, at 22:30 UTC+10:00 it is too dark to work on PCBs. I’ll just keep myself amused looking at the waveform on the oscilloscope.

May 272017

So these are some completely untested notes on how I might achieve auto-detection of peripherals and I²C devices.

The idea here is that a peripheral can be plugged into any of the 8 ports on the device. It won’t matter, because there’ll be a standard boot-up procedure, consisting of:

  1. Deriving an I²C bus address (possibly by sampling ADC LSB bits to generate a random address) and verifying that there are no clashes by attempting to poll the derived address a few times.
  2. Performing a read of the bus roll-call I²C address to enumerate the IDs of all automatically-addressed I²C devices on the bus. During this step, the GPIOs are monitored: the slave should pulse the GPIO it is connected to as it “calls out” its ID.
  3. Measuring ADC readings at the GPIO pins and observing power-up state (common GPIO pin in high-impedance state)
  4. Pulling the common GPIO pin (MISO) high and seeing which GPIO pins go high.
  5. Pulling the common GPIO pin (MISO) low and seeing which GPIO pins go low.

The peripherals may be connected via a number of ways:

The boot-up procedure is used to determine what is attached, by performing some simple actions and observing the GPIO pin, which thanks to the ‘4066s, also doubles as an ADC.

As for I²C addressing… I do not want to have to program a unique address into each device. The concept here is instead that each device generates its own address, waits for a clear moment on the bus, then tries polling its proposed address to see if it gets an ACK. In the case of a time-out, it waits a random length of time and tries again when the bus is clear, and after a few times, can safely assume it has the address.

This gives rise to a problem though, knowing what the addresses of the neighbours are.

The idea here is to use a roll-call address. This is common to all units implementing the auto-addressing protocol. When a device hears the roll-call address, it tries to send its ID in reply. When another device with a numerically lower ID sends at the same time, the device sees the collision and tries again on the next byte. As each calls out its ID, it pulses the interrupt pin low (if it uses one).

The result from the master’s perspective is it sees a stream of IDs sorted from numerically lowest to highest, terminated by a stream of 0xff bytes. Direct peers are able to then detect who their peers are.

Timing of all of this will be the challenge. We want to ensure everyone has a unique ID by he time the first roll-call is performed so we can move on to detecting the dumb devices (LEDs and buttons). This network feature will be a stretch-goal of the project though, so plenty of time to think about that.