Showing posts with label ADC. Show all posts
Showing posts with label ADC. Show all posts

Sunday, 6 September 2015

PIC fan boy ...

... that's me


So, here's a nice quick and useful project (for a change!). At work, hanging round the EE types, trying to pick up tips or pick up ideas or just pick brains, I see that they use these fume extractors so that they don't breathe in the nasty stuff that gets produced when the flux burns off as you're soldering...

You can buy something cheap and cheerful if you like, but - I thought - much better to make your own, with a few tweaks.

So, rather than having the fan switched on all the time, how about automating it so that it would switch on and off when necessary, without me having to touch anything? 

Sounds good. 

Even better would be if I could place it just adjacent to the PCB I happened to be be working on, and then as I approach the board with my soldering iron, the fan could switch on and ramp up speed to suck (or blow) away the fumes until I moved my hands away again. Cool eh?

Well I thought so too.


4-pins on my fan


So, I had an old (well old'ish) PC case fan with the requisite wires necessary for controlling the speed. The four pins are used as described in this PDF, which I've summarised below.

The 12V and GND are self explanatory, and just supply the fan with power. The green sense pin is optionally used if you want to read back the actual speed of the fan, and I'm not concerned about that. The important one here is the blue "control" pin, which is actually a Pulse Width Modulated (PWM) input that you can drive to control the speed of the fan. Internally, the fan pulls this up to 5V (according to the spec) or 12V (according to my measurements, which I'm glad I double-checked!) and you can pulse it by pulling it low. So, by default, if it's unconnected, it's effectively full on. 

PWM, as you can read in this SparkFun tutorial is a simple waveform that has a variable proportion of on-time to off-time at some fixed frequency. In the case of the 4-pin PWM fan, the standard specifies 25kHz (give or take a bit) as the frequency.

So, a picture being worth a thousand words, courtesy of SparkFun's CC license, this is what it means:

As you can see, the duty cycle can be used to convey information to the device you're controlling. The length (i.e. time) of each period is defined by the frequency, so for 25kHz, that's a period of 1/25000 or 40 microseconds. So a duty cycle of 75% means that the line is high for 30 microseconds then low for 10 microseconds; 50% means it's high for 20 microseconds then low for 20 microseconds; 25% means it's high for 10 microseconds then low for 30, and so on.

4-pin fans normally respond to a subset of the full 100% range of duty cycles and will commonly spin at some predefined minimum RPM below say 20%. This is to ensure that there is always some airflow in case of problems. The datasheet for the particular fan you choose should show the details. For example, for the fan I'm using, the chart looks like so:

You can see that with 12V, you can't make the fan spin at anything less than about 400RPM.

Ok, how to produce this variable PWM signal? Well, a PIC of course!


To bit-bang or not ...


It would be possible, and pretty simple, to use a timer, an interrupt routine and a bit of code to pull a pin low according to whatever duty cycle you choose. But, with some PIC devices, it's even easier than that because the PWM behaviour is encapsulated in a dedicated hardware module on the chip and you can configure it with a few register settings and then you're free to do whatever else you like (or need) in the code.

For example the tiny 8-pin PIC12F1822 has more than enough to do what we need. The particular feature to look for is the "ECCP (Enhanced/Capture Compare PWM) Module" - it may come in slightly different terms on other devices but PWM is often associated with CCP (capture and compare) peripherals as it's easy to provide both functions from similar hardware.

The PIC12F1822 also has a 4-channel 10-bit ADC, which we'll need for the proximity sensor. More of that later. For now, we need to figure out what to put in the registers that control the PWM output.

The PIC uses one of its 8-bit timers, TMR2, in the PWM mode, so the registers that control the frequency (i.e. period) and duty cycle are PR2, T2CON, CCPR1L and CCP1CON. The datasheet also gives the crucial equations that we can use to determine how to set the right values.

The annoying thing is that the equations aren't written in the way we need to use them. For example, equation 24-1; we want to know what value to set to PR2. We already know the period we want (40 microseconds) and our clock frequency, Fosc, (in this case I've configured the PIC to use its internal 8MHz oscillator), so we just plug in what we know to the equation and rearrange it to get PR2.

So, PR2 = (Period/4*Tosc)-1, which with our period of 40 microseconds and Tosc of 1/8MHz, gives us PR2 = 79. Simple eh? It would be much simpler if the datasheet just gave that formula in the first place because I expect most people would start with what they know to calculate what they want [sigh].

Anyway, now we know PR2, we only need to calculate how to set the 10-bits that are spread over all eight bits of CCPR1L and two bits of CCP1CON. Confused? Yes - why not just have a bigger register? Well, because it's an 8-bit PIC I guess, so it makes sense. 

In any case, we only need to mess with equation 24-3 now because we want to specify the duty cycle ratio (that's our 25%, 50%, whatever) to get the corresponding value for CCPR1L:CCP1CON<5:4>.

Looking at equation 24-3, it's obvious that if we set it to zero, it will give zero percent duty cycle. What about 100%? Well, like we did with equation 24-1, we just rearrange it and plug in what we know. As we already calculated PR2 to be 79, we know that the denominator, 4(PR2 + 1) must be equal to 4*(79+1) = 320. Then with our desired duty cycle of 100% (i.e. a ratio of 1), we can see that setting CCPR1L:CCP1CON<5:4> to 320 will give us 100%. 

Perfect. 

We can scale other percentages accordingly, so a percentage between zero and 100 will be scaled to a value between zero and 320, and write a simple function that takes a percentage and sets the correct values in the registers:

So, you can see from the above equations where the magic number of 320 comes from in that code, and the two lines in the middle are just splitting the 10-bit value into two registers. 

The final line is used to turn off the fan completely if the duty cycle is zero - remember that the fan has a minimum speed, even if we set the duty cycle to zero, so I've configured another PIC pin to switch a MOSFET that cuts the power to the fan when I really want it off. You'll see that in the schematic.


Speaking of schematics ...

It's probably time to show you what I've done.




Perhaps not a lot of explanation needed, but just in case....

The main power is 12V, so there's a jumper (JP1) drawn there so I can connect to my PSU. The PIC only needs 5V, as does the proximity sensor, so I have 5V voltage regulator with associated smoothing capacitor providing the power to the logic.

Pin 5 (RA2) is the one configured for PWM output, which pulls the fan's control pin to ground via a 2N7000 MOSFET. The crucial thing to note here is that all of the talk about PWM above, with the various percentage duty cycles, assumed that this relates to the percentage of time the output is high. Whereas, the fan's control signal is pulled up internally, so it's by default high, and we want to pull it down to slow the speed. Luckily, this is trivial to do with the PIC because we can simply flip a bit to choose whether the PWM output is active-high or active-low. Look at the specification of CCP1M in the CCP1CON register in the datasheet. Very nice.

As I mentioned in the code snippet about, pin 6 (RA1) is used as a low-side switch to allow me to switch the GND connection to the fan on or off. Effectively switching the fan off whenever RA1 is not high.

The final part of the puzzle is the proximity sensor, which is fed in to one of the PIC's ADC channels, so I can read the approximate distance from my hand/soldering iron during operation. This connects through JP2, and has a 10uF capacitor to smooth out any bumps as it draws quite a bit of current. I'd better tell you about that next.


Getting closer ...

So, the proximity sensor I chose is the Sharp GP2Y0A41SK0F, which outputs an analog voltage of between 0.3V and 3.1V over a range of 30cm to 4cm respectively. 




In testing, I recorded outputs of zero to 3.2V on my oscilloscope, so that's the way I've coded it.


Code .... just a little bit ...

Which is true. There's hardly any code in this project, and I'm only using a tiny fraction of what the PIC can do. It seems a waste, but there you go .... you can buy a lot of features for very little cost these days.

So, the code.

Leaving aside the configuration bits and other housekeeping gubbins (you can download the whole source code below as usual), the main function starts out configuring the internal oscillator and setting all pins as digital except the ADC channel we're using (that's AN3 on RA4).

Next, we configure the PWM module. This is where we load PR2 with our calculated value of 79, and set the correct bits in CCP1M to use PWM in active-low mode. The remainder of the code just configures TMR2 with a 1:1 pre-scaler and then kicks everything off.
Next, we configure RA1 as an output, so we can switch the fan on or off via the MOSFET, and configure the ADC on channel 3, so we can read the proximity sensor.
Now, the main loop does as much or as little as you'd expect. It reads the ADC and converts the reading into a duty cycle percentage to be applied to the PWM output, then reads the ADC again and sets a new PWM duty cycle, then ... etc.

Note that the code forces anything less than 15% to zero, so we switch off the fan at the point where it will no longer respond to lower duty cycles (remember that chart from the fan's spec?). Also, even though we have a 10-bit ADC available, I disregard the lower two bits because it simplifies the code and we don't need that precision anyway.


Laying out the board

Now, this is a nice small and tidy circuit, so I decided to stop using the mysterious and slightly intimidating auto-routing features that Eagle provides and made this the first board that I routed manually.

I've used thicker traces, and actually it looks pretty good to me and the auto-router didn't do anything other than complicate matters. So I think I'll be routing manually from now on.

Here's the layout:

And here's some pictures of the assembled board.






Seeing is believing ...

As always, we have to prove that the damn thing does what it's meant to do, even though I haven't yet put it in a nice enclosure (and probably won't because I've had my fun now!).

So, I've hooked up the power supply, the fan and the proximity sensor like so...



... and I've recorded a short video of the gizmo in action ..... enjoy!




Source code

If you'd like the full source code for this project, you can download it here.

Saturday, 27 June 2015

Don't be so wet! ...

.... or so dry!

It's been six months since my last post, a long time but I've been busy.

Continuing my exploration of all things electronic, I've been happily making stuff, making mistakes, making a fool out of myself by asking stupid questions of anyone and everyone, but also learning a lot. I've compounded all the practical work with a couple of free online courses too - both from Coursera, I've stumbled successfully through Linear Circuits and also Introduction to Electronics - not too advanced, but useful nonetheless and certainly as much as I could manage with a full time job!

So, what's with the "wet and dry" theme?


How to explore your own hobby while keeping your "significant other" happy!


My wife is a keen gardener. That's keen with a capital K, and gardener with a capital G. So we have an active and ever evolving garden, plus more houseplants than you can shake a stick at.

So, for my next project, I cunningly suggested that I might make her something that would allow her to monitor the state of her houseplants from the comfort of the sofa - that is to say, a wireless plant moisture monitoring system! No, I didn't mention that you can buy such things for very little money and with much more hope of success, but I got the green light to start building things, which was the main result I wanted ... hehe.


Wireless wetness

If you Google or eBay for nRF24l01p, you'll find loads and loads of people who will sell you these little units very cheaply:

What it is, is a 2.4GHz wireless transceiver that you can talk to with Arduino, Pi, PIC, whatever and generally get simple wireless capability into your projects very easily. So I bought a handful.

They run off 3.3V so rather than using my normal preference of 5V PICs, I chose to use one of the PIC16LF range of low power microcontrollers, in this case the PIC16LF1554, which has much more available than I needed for this project (I just need SPI and ADC really) but it did allow me to test and prototype with a UART talking to my PC via a TTL to USB adapter.

The grand plan

So the grand plan was to have a number of moisture monitoring sensors, each one stuck into a favoured houseplant pot, and each periodically measuring the soil moisture level and pinging off a packet of data to a central monitoring station, which would collect and collate the various wetness reports and allow them to be presented in a suitable manner.

I planned to use simple resistive moisture sensors because a) they are cheap, and b) they are cheap. So a few quid on eBay and a long wait for China Post and I harvested a dozen of these:



To read the moisture level, you simply wire this as part of a voltage divider that you sample through the microcontroller's ADC (analog to digital) pin to get a resistance measurement that you blindly assume is both accurate and varies in a linear relationship with the moisture level. Neither of these are true, but who cares - that's not the point! I did pay homage to some advice though that warned against always sampling with the same polarity - risk of gradual electroplating of one prong from the other - so I always take two ADC readings, one with polarity reversed, to even up the battle a little.

To make things even more interesting (complicated), I also wanted a way to reconfigure the sensor units without having to reprogram their PICs, so I used a simple protocol that allows me to remotely adjust the frequency of wetness sampling for each individual sensor. It goes something like this:

  • Sensor wakes from sleep mode.
  • Sensor takes two ADC readings and creates a packet of data.
  • Sensor sends data to the master unit.
  • Sensor waits a reasonable number of milliseconds for a reply.
  • If the reply arrives and requests reconfiguration, the sensor adjusts its sleeping time.
  • Sensor goes back to sleep.

By using the PIC's watchdog timer to wake it from sleep mode, along with some suitable preset combinations of prescaler and counter, I could manage a sampling period of anything between 15 seconds and 16 hours. The former useful for testing and the latter 8-hour or 16-hour period being used in practice.

The sensor

The sensor circuit went through two revisions. Mainly because I was stupid. Ok, no "mainly", but only because I was stupid!

But then you don't learn anything without making mistakes, so stupidity is a good thing ;-)

So the first version of the sensor PCB looked like this:


The first stupid mistake is the battery on the left. I chose the wrong part in Eagle, so instead of having a battery holder, I had a fixed battery with welded solder tags that you can't remove from the board without desoldering. So obviously, you also can't switch it off. Hmmm .. too keen to get the boards ordered, and I don't mind admitting it. Lesson learnt. Probably.

The second stupid mistake was that I forgot to add the recommended 10uF capacitor across the power terminals of the RF unit. In practice it didn't seem to matter, but as I was going to redesign anyway, the second version attempted to fix these issues. The final schematic for the sensor was like so:


At the top left is the 4x2 header to receive the nRF24l01+ unit, then under that is the voltage divider circuit to the moisture sensor (JP1), and apart from power and bypass cap, that's it. So it now looks like this:


The battery can be removed or replaced as required and so I now have a nice stock of the other, fixed tag, batteries because I found I could buy 80 of them for ~£5 on Amazon. Ho hum.

Anyway, I made six sensor boards, each PIC pre-programmed with a unique identifier that gets put in the data packet when sending to the master. So the master correlates the sensor ID with its - also pre-programmed - list of houseplant names, ready for display. Hooray - nearly done.

Mastering the master

The master unit is actually the more complicated part of the system. Not because it has any inherent complexity, but really because it's difficult to fit all of the code into the 4096 words of flash memory and 256 bytes of RAM. For anyone familiar with MPLAB X, how many times have you seen your resources so completely used as this?


This is because we need the code for talking to the RF unit, the code for talking to the OLED, the code for interacting with the 23LCV1024 SRAM, various text strings, and also font mappings. Quite a lot to fit in!

Anyway, here's the schematic:


I've put a 3.3V LDO there because I'm powering it from a 5V wall-wart even though it's 3.3V throughout - it's much easier to find spare 5V supplies in the rummage boxes. So, nothing too difficult to see there - the external RAM has a fixed battery (I knew they would come in useful!) to keep its content safe during power off and I've also added a jumper in there to clear the RAM if I want to start again. In the code, it first reads the RAM looking for a magic number to see if it was previously initialised or not. If not, it writes six pages of empty data to the SRAM with the plant names coded from a 5x7 font. So the display is really just mapped to the SRAM and the PIC only needs to write the bars for the incoming data and read back a single page at a time to display.

The populated board looks like so:


Power comes in top left, the PIC is bottom left and the SRAM bottom right. The momentary push switch at top right allows one to cycle through the six houseplants to see the current moisture level and a bar chart history over the last 128 sample periods (only 128 because the OLED is 128 pixels wide).


Seeing is believing ...

So, finally (yes I know I'm rushing), I've made a short video of the master unit in action so you can see the principle. As usual, I'll link to the code downloads at the bottom ...



One thing that may be interesting is what happens to the soil moisture sensor after a few months use. This is what my first sensor looks like now, after something like five months in an indoor plant pot (over-watered, as it happens!):





Source code downloads

As promised!

Plant monitor sensor
Plant monitor master


Sunday, 26 October 2014

PIC a chip .... any chip .....

Once upon a time ...

So ... after messing around with that optical mouse a few months ago, I inadvertently rekindled my interest in a teenage hobby where, many years ago, I read every issue of Practical Electronics that came my way, bought numerous bits of Veroboard and burnt myself liberally with a cheap and nasty soldering iron, but really did nothing more than build other people's circuits and even then with just a handful of transistors etc. .... all of this digital logic was still quite new (we're talking mid 1970's here) and it was difficult to do anything really interesting without a lot more time and effort than I used to have back then .... not that I have much more now, of course ....

Anyway, since my escapade with the mouse, I've been tinkering and reading and buggering about with a couple more Arduinos, and then, after posting a begging letter on Freecycle for any unwanted electronic components, somebody very kindly gave me, among other stuff, an 8-bit PIC microcontroller - a PIC16F676 to be precise - along with an old PICKit1 evaluation board, and this got me Googling and thinking ....

This was much closer to the metal than the Arduino, and being programmable in C as well as Assembler, felt like much more of a challenge! 


So what did you do next?

What did I do? I'll tell you what I did .....

I took some of the bits I'd been playing with on the Arduino - namely a handful of 8mm addressable RGB LEDs, commonly known as NeoPixels, and I decided to migrate one of the Arduino demo programs to the PIC.

Easy enough, I thought. But not after a closer look and a second thought. Or even a third thought.

These LEDs are really quite a package. Not just any old RGB LED, capable of displaying any of the 16.8 million colours familiar from normal computer displays, but they also contain the circuitry to latch in the last-set colour, so you don't need to keep pumping it with data from the microcontroller. They also have a neat one-wire communication method and can be daisy-chained together so that a single (albeit carefully crafted) pulse of 1's and 0's can set the colour of each LED in the chain and it will stay that way until switched off or told otherwise. So each LED needs only four connections: +5V and GND (to provide the power), the one-wire DATA_IN, and the similar outgoing DATA_OUT which feeds along to the next LED in the chain.

So, a simple daisy-chain of LEDs might be wired up like this (you can actually buy these same NeoPixels already wired on a metre long strip, or a ring, or various other shapes) ...




Hard times

The downside to the handy one-wire interface, though, is the necessity to provide very tightly timed pulses. As there's only a single wire, there is no room for handshaking or any other common protocols. You must adhere to the datasheet specification, or it just won't work (although there is a bit of leeway and others have found that this can be stretched a lot further than first thought).

Most of the mid-range PIC products can be clocked at a maximum of 20MHz by using an external crystal instead of the internal oscillator. With each instruction taking four clock cycles, this gives a minimum instruction time of 200 nanoseconds. The NeoPixel datasheets, with varying degrees of detail, specify that the shortest pulse - for sending a zero rather than a one - is somewhere around 350 nanoseconds (+/- 75ns) so with a couple of assembler instructions on the PIC it's just do-able.



The following code snippet is what I came up with:
It's defined in a macro to avoid cluttering the code as this has to be repeated 8 times for each byte of the RGB data that's sent to the LED chain. I've chosen pin RC2 as the output from the PIC that hooks in to the LED one-wire input, and with the external crystal to run at maximum speed, the PIC is wired up like so:


You can see I've added an ICSP header for programming the PIC in-circuit, so I don't have to keep pulling it out of the board every time I make a change, and there's a bypass capacitor across the power inputs to suppress noise. Ignore the ADC, SDA and SCL pins for the moment, we'll come on to those soon.

I have 20 LEDs in my chain (because that's all I bought) and, as they can draw a maximum of around 60mA each when fully illuminated, they will need to have a suitable power supply,  capable of supplying at least 1.2A (mine is actually rated at 2.5A, which is comfortably larger). Referring back to the LED circuit above, you'll also see a 1000uF capacitor across the power rails and a small series resistor for noise suppression as recommended by the LED datasheet.If you try to do this yourself, read the datasheet carefully, keep the power at 5V or lower (the LEDs have been reported to explode at 6V) and definitely add that 1000uF capacitor to avoid blowing your first LED at power-on. 

You can see that I also added a handful of smaller bypass capacitors and an inline filter to keep noise to a minimum but these are possibly overkill. [UPDATE] I've now removed the inline filter and replaced it with an LT1086-5 voltage regulator and now take power for everything from this (it will supply up to 1.5A at 5V) so the schematic has been updated in the download.

Board (bored?)

On the image below, you can see the positioning of the PIC - notice I've used a different chip here, the PIC16F684 because the '676 doesn't have quite enough memory - and you can see the LEDs, which are wired in a boustrophodon pattern (Google it), all marked in yellow. You can see the LED power also marked, coming in from the middle left. The PIC and related circuitry has a separate power supply in this picture, which at that time came from my computer's USB (with grounds common). There is now a single regulated 5V supply (see update above).




So, having got this far, it's useful to make sure that the code for driving the LEDs is performing in spec. Hooking up a logic analyser to pin RC2 on the PIC, and temporarily hacking the code to make it send alternate bytes of just zeros and ones (so each byte is either 0x00 or 0xFF), you can see that the timing is pretty much ok:


A section of a set of "zero" pulses
A section of a set of "one" pulses
And indeed, by setting the LEDs to some identifiable colours, like pure Red, Green and Blue, I could see that this part of the code worked fine. Which was nice.


"Like the corners of my mind ..."

Nice, but not very interesting. The Arduino NeoPixel examples have some nice colour-chasing effects and it would be good to replicate this on the PIC.

The Arduino does this on the fly, calculating a new set of colours based on the elapsed time and the previous set of colours. Not so hard to do in C or C++, with floating point math support, but a different kettle of fish altogether using assembler on an 8-bit PIC.

So, cunning plan #1, why not use the Arduino code to generate a suitable set of data and then just hardcode that into the PIC? 

Well, here's why not...

The Arduino is relatively well endowed when it comes to memory (the Uno has 2K), but the PIC16F684 only has 128 bytes (yes, bytes), and, given that I need 60 bytes (20 LEDs x 3 bytes RGB) for just a single set of colours, it limits the options somewhat. My first PIC, the 16F676 only has 64 bytes, which makes it totally impractical, but 128 bytes is still, well, not much.

Cunning plan #2 - add some more memory! Ok, more obvious than cunning, but at least another chance to learn a bit more and get some more interesting functionality into this putative project.

Microchip also makes a range of Serial EEPROM chips, like the 24LC64, which provides 8K bytes (64K bits) of additional memory.That's enough for about 130 frames of data for my 20 LEDs, and the memory itself is very cheap (I bought 10 of these from eBay for £3.64 including postage). It talks to the PIC using I2C, so only takes up two more I/O pins, one for clock (SCL) and one for data (SDA). 

So this part of the picture goes like so:


Referring to the earlier schematic, you can see that the SCL and SDA connections hook up to pins RC1 and RC0 on the PIC.

It was easy enough to adapt the Arduino NeoPixel code and make it write 136 frames of colour data to the EEPROM instead of driving the the LEDs directly. Then all that's needed is to pop the EEPROM into the PIC circuit instead and write the code to read from it.


Mindreader

Some PICs have hardware support for I2C, but unfortunately, the 16F684 doesn't. However, Microchip have an Application Note - AN974 - that describes how to implement I2C in software to interface to a Serial EEPROM, which is just about perfect! 

All I needed to do was to adapt the example code for the EEPROM I used (this needs two bytes for the address rather than one), change the timing code to match my 20MHz clock and to run I2C at the faster rate of 400kHz, and put in the necessary infrastructure to load any of the 136 frames of data (each 60 bytes) into the PIC's memory to drive the LEDs.

Here's a very low quality snapshot of this part of the functionality in action - the colours don't get caught well on camera, so it's not (quite) as disappointing as it looks:



You can see the Serial EEPROM outlined in yellow, and it's associated bypass capacitor just partially obscured by the top left LED.


So far so good...

... but not quite good enough.

So, trying to squeeze even more functionality out of the PIC, and noting that it has an ADC (analog to digital converter), and remembering even further back to when my mate Chris and I ran a disco when we were still at school, I thought I'd try to get some sound-and-light effects going.

At first, I didn't quite know how I was going to make this happen, but I did know that it would be possible to get some audio input from a cheap and cheerful microphone into the PIC via ADC and then do "something" with it in the software. That "something" would occur to me later, but for now, the final part of the circuit you can see at the bottom left of the board is this:
On the left is an electret microphone capsule, which needs to be powered in order to work (it contains a small pre-amp), the output of which is passed into an LM358 op-amp, configured in inverting mode.

There's a 2.5V DC bias on the non-inverting op-amp input, to keep the signal from going negative (the ADC can't handle that) and the gain is set (experimentally) so that the output has a range of approximately 4V peak-to-peak.


Say what?

Ok, the audio input hooks into pin RC3 on the PIC, which is configured as an analog input (AN7). The ADC is 10-bit, which means a maximum range of 0 to 1023, so this maps my ~4Vpp to a range of around 0 to 800.

Throwing away the two least significant bits gives me an 8-bit value in the range 0 to 200, which I can use to do that "something" I mentioned earlier.

The 0-200 number I end up with is just a very rough measure of the ambient noise level at an instant in time (well not an instant, but a very short period anyway), so it's not going to behave like a true peak detector from a sample-and-hold circuit, but it's possible to simulate the same sort of response in software. In pseudo-code, it goes something like this:

  1. Read the audio level from the ADC.
  2. If the level is greater than the current peak, store it in the current peak. 
  3. Otherwise, decay the current peak by a small percentage.

This means that the peak will react quickly to increases in overall noise, but will decay more slowly if/when the noise level drops. This helps to average out the artefacts caused by sampling an alternating signal and in practice follows a sound quite well.

To avoid messing around with calculating percentages for decaying the current peak, I used more experimentation time to figure out that decaying by about 1% or 2% of the current value worked well with my overall time scheme. So, with my audio level ranging between 0 and 200, it was easy enough to hardcode this bit of logic to avoid unnecessary coding of 8-bit division:

  1. If the peak is greater than 140, decay by 3
  2. Else, if the peak is greater than 70, decay by 2
  3. Else, if the peak is non-zero, decay by 1

Seems simple, and it is, but works well and only takes a handful of lines of assembler code:


Don't worry about these short code example. If you're really interested, you can download the whole source code at the end of this post.


Nice but dim

Now, having got a number that sort of correctly follows the average sound level, I decided that the only "something" I could do with it was to use it to adjust the brightness level of the LEDs. So, the colours would stay the same as read from EEPROM, they would just be scaled down a bit dimmer when there is a low sound level.

However, this required a bit of additional coding to get things happening at the correct time. There's no point reading the EEPROM and updating the LED colours too fast, because it doesn't look good and makes you feel sick. But, we do need to react to changes in sound level quickly or the whole thing will lag behind the live sound and that also won't be good.

Therefore, I've used one of the PIC's inbuilt timer modules to flag when it's time to read a new frame of RGB data from the EEPROM. I've set this so that the colours change about 9 to 10 times a second. When I've read all 136 frames, the code just goes into reverse and reads backwards to frame zero again, and so ad infinitum.

So, what happens when the colours are not changing is that the audio level is determined as already described, then the current frame of colour data is re-read from EEPROM and then adjusted in brightness according to the audio level before being sent to the LEDs. In simplified terms, like this:
Note that it's necessary to re-read the EEPROM, even if the frame number didn't change, because we don't have enough memory to store the un-dimmed version we last read and we want to re-dim it to a new value based on the latest audio level. If we had more SRAM on the PIC we could keep a second copy of the RGB data and avoid accessing the EEPROM more frequently than the 9-10fps required. But we don't, so we can't.


The final curtain...

Right, that's enough explanation, you probably want to know what it looks like. Me too. It's just a shame that this sort of thing is very difficult to capture with video - colours are not good, focus shifts as brightness changes, etc. etc. but just so you get the feeling for it, here is version zero of my Totally Pointless Toy (TPT v0) ....



One other effect I tried was to take an idea from this project, that simulates a fire effect using an 8x8 matrix of RGB LEDs. It's easy enough to just use the Arduino to write whatever I want to the Serial EEPROM and then just plug it back into my circuit. So, 136 frames of simulated fire later, this is the result ...




Source code

Right, that's all for this post - I certainly enjoyed myself and learned a heck of a lot in the process. If you want the source code and a copy of the whole schematic, you can download it from here.


[Update]

After making some small changes, the board layout now looks as shown below, with an LT1086 5V 1.5A LDO voltage regulator supplying the whole circuit. This is a lot cleaner and just requires a single wall-wart power supply, which I robbed from an old Netgear router.