Saturday, December 31, 2011

MCP79400 RTCC breakout board

Last design for the year! 32.768kHz crystal and capacitors will be onboard as well as the Schottky diode and current limiting resistor for the back-up coin battery (which will be off-board). PCB measures 0.8 x 1.0 inch. All components except for the 0.1" male headers are on the copper side (am using a single-sided presensitized board) because the MCP79400 real time clock calendar chip is available only in surface mount packages. I don't have any chip resistors and capacitors so the discretes are all through-holes. The two headers are spaced 0.7-inch apart so they'll still plug nicely into a breadboard.

I could have this board fabricated tomorrow. Thing is, I have yet to purchase my very first MCP79400! Might do so in mid-January.

Ignore that yellow airwire at pin 6. I think it's a bug in DesignSpark PCB 3.0. Even after routing, some airwires remain. Even after executing a design rule check. And even after closing DesignSpark and opening it again. Go figure.

You'll probably notice the SMD pads are much longer than recommended by chip manufacturers. When the chip is mounted on the board there should be around 2mm of exposed pad. The rationale is that the excess pad area permits the soldering iron tip to simultaneously make contact with the pad and the leads. Without the excess we'd have to position the iron tip on top of the pins to solder them. This translates to higher thermal resistance and longer heating time--not good for the chip.



-----

January 1, 2012 Addendum

Made a couple of revisions. Added a 1uF filter cap just to make sure there's sufficient power on board. Also added a 10K pull resistor to the MFP pin as specified in the MCP79400 datasheet. The I2C's SDA and SCL lines need pull-ups but since their value depends on the I2C frequency I've left them out.


Friday, December 30, 2011

Voltmeter woes

Just got duped. The digital panel-mounting voltmeter I bought which supposedly has a range of 0 to 20VDC stops measuring--with fairly good accuracy--at a little over 15V. Feed any voltage higher than that and meter's accuracy starts dropping off fast. By the time the measured voltage is 20V the meter reads less than 17V.




The exasperating part about it is I found this out only after I had redesigned a circuit so as to factor its limited range of 20V. If you haven't guessed it yet, the cheapo meter is made in China. Yes, it came in that blue and white box with Chinese characters. Doesn't come with any instruction leaflet. And there are only Chinese text on all sides of the carton. Frustrating. At least the silkscreen on the board isn't in Chinese! As you can probably make out, the pins for the connectors are marked (left to right) 5VIN, GND, GND, VIN+. The first two pins are for power and the latter two are for the measured voltage input. A check using a DMM shows the two middle pins (GND) are in fact connected to one another on the PCB. The multiturn potentiometer (blue rectangular component at the top) is used to calibrate the meter. Calibration is fine--if you don't need the entire specified range of 20V. Else, it's a joke.

Tuesday, December 13, 2011

Devil's in the details

And I just got burned. I inadvertently designed a short circuit in a pcb!

I create circuit board artwork using DesignSpark PCB and when printing it out on transparency film I always have the board outline printed out as well. The outline allows for precise alignment of the mask (the film) and the presensitized board prior to exposure. In this particular design of a simple 5-volt power supply I had a wide strip of copper for both the +5V and ground. And I extended those tracks all the way to the edge of the board (see the left side of the artwork image below).


Yep, disaster in the waiting. I printed the artwork, exposed the presensitized board, etched it, drilled the holes, soldered the components, and all the while I was absolutely oblivious to the short I had created. I powered up the board while probing the output with a multimeter. Reading? 23mV. I cut off power immediately. Tried it again. Still the same zero voltage. I was so clueless of the booboo that I even desoldered one of the filter caps suspecting it might be defective. Only while poring through board with a magnifying lens did I finally spot the fine 5-mil trace at the very edge of the board bridging the power rails. Facepalm moment indeed. Actually if I had moved the mask by some half millimeter that outline would've been off the physical board and the short wouldn't have appeared.

It's worth noting that even a 5-mil track is faithfully reproduced--implying there's insufficient undercutting during etching to remove the track. This is good of course--if we want such fine copper traces. Luckily it's easy to cut and gouge out the offending copper track. After making sure the bridge had been completely obliterated I soldered the cap back on and the circuit worked as designed. No electronic component was harmed in the making of this sophomoric design mistake.

Hopefully the stress, anxiety, and panic I was subjected to has burned the lesson in my head--since I'll still continue the practice of printing the board outline, I must remember to leave some 20 mils around the perimeter of the board copper-free. I better burn that minimum gap requirement into DesignSpark's design rules. And more importantly, I better start paying attention to the error messages which the design rule check routine spits out--because I don't usually take a look at the track-to-track, pad-to-track, shape-to-track, shape-to-pad, etc. spacing errors. I have a knack for breaking rules. Unfortunately, this time around I got bitten.

Ambient light sensor on the roof

Installed an ambient light level sensor on the roof. Voltage from the simple voltage divider made up of an LDR and a fixed 5% resistor (750Kohm in this case) is digitized by the analog-to-digital converter of the PIC12F1822 (16 readings are taken successively--takes around 1ms--and averaged) and is sent out through the its serial port at 19.2kbps every 50ms. Communications is simplex--this transmitter merely sends while receivers in remote locations can only receive data. Serial data from the MCU is fed to the SN75176B transceiver which then converts the data stream to an RS-485 compliant signal which is then sent down a pair of unshielded twisted pair cable, for use by any circuit requiring the data. Checksum is sent with every data packet for data integrity verification.

A pair of wires in the UTP cable provides filtered (but unregulated) power to the board. Because the input voltage is somewhere around 8 to 8.5V and may drop below 8 if loads elsewhere (various circuits use the same power supply) increase, I used a 1N5817 Schottky diode instead of a 1N400x for polarity reversal protection. That halves the forward voltage drop across the diode to just 0.3V and thus increases the headroom for the 78L05, ensuring it's able to maintain voltage regulation. There are separate power and ground tracks/traces for the analog and digital sections to minimize digital noise from contaminating the sensor section. The analog section has a 1uF tantalum filter cap and the MCP6232 op amp has the mandatory 0.1uF decoupling cap. You might notice the 1/4-watt resistor adjacent to the LDR. Reason is the dang supplier doesn't have the complete range of values for 1/8-watt resistors.



Image below shows the board in situ with the translucent plastic cover yet to be snapped on. Screws on the board act as standoffs to keep the soldered side from contact with the bottom which might accumulate moisture (hence the drilled holes on the blue plastic to act as drain). The gray 4-pair UTP cable can be seen on the lower left.

Sunday, December 4, 2011

Grew up on Casio


Photo of Casio fx-3000 and fx-115MS scientific calculator. An uncle bought me the 3000 back in 1977. Was already gaga over sci calcs back then. It still works but I haven't used it in decades. Though not noticeable in the pic with the 115 (because we're looking head-on through yellow plastic filter) the LCD is already showing its age and deteriorating. Actually it's fared better than my Casio fx-502P whose LCD went kaput years ago. The 3000's power switch had been giving me problems and had to take out its board and clean both the switch and board contacts.

I now use the 6-year old 115 exclusively. It probably has twice the number of functions. The engineering units (nano, micro, kilo, etc) and decimal/binary/hex functions make life easier. And being able to go back and edit the formula/equation and plug in different values for the variables is just indispensable.

As for speed, the two are truly generations apart. A "u" appears as the leftmost character in the 3000 to alert the user that the microcontroller is still running the numbers through its ALU (arithmetic logic unit). Even something as simple as 1+1 takes a quarter of a second. It's that slow. 69! takes 3.3sec. The 115 on the other hand spits out the factorial result in the blink of an eye, literally. Given that both calcs cannot handle or display any number greater than or equal to 10^100, 69 is the highest possible number for factorials for both.

The equation shown on the display of the 115 is (2π*1k*1µ)-1. This of course is the formula for the cutoff frequency of a simple low pass filter with a 1kohm resistor and 1µF cap. Being able to enter "M" for mega, "k" for kilo, "n" for nano, "p" for pico, etc.comes in very handy. They save me the trouble of keying in values in scientific notation.


Back side of the calculator. The sticker is original. Could be the serial number of the unit. You might be able to make out the printing at the bottom. Part of it says "Made in Japan." All Casios were back then. The 115MS on the other hand is churned out in China. Thumbs down.


Without the front panel you can clearly see the poor state of the LCD.


The microcontroller is a NEC D895G. Googled but can't find any datasheet for it.


This is the side of the board that greets you when you take off the back cover.

Rail-to-rail op amps

Just got bitten. Or rather, just found out after over two years I'd made a booboo. I had used a Microchip MCP618 as a unity gain buffer for an ANLC with the sensor unit located a remote location. I was upgrading the load-control part of the circuit yesterday and while monitoring the output of the op amp at between sunset and dusk I was shocked to see the voltage jump from from VDD to around 4.80V. (Thereafter it decreased in a smooth continuous gradient). That sent me into a near-panic. Questions raced through my head. Was the long length of cable--some 20 meters--from sensor to control circuit responsible for this behavior? Do I need to add a high value load resistor? Is there some soldering/board defect at the sensor?

Eventually I zeroed in on the fact that the 618 is not a full rail-to-rail op amp. Rechecking the datasheet and refreshing my memory, indeed while its output can, its input does not swing rail-to-rail. So to test whether the 618 is cause of the problem I bench-tested it using a 100K pot as input to the unity gain buffer. Sure enough as I ever so gradually turned the pot down from maximum, the buffer output jumps from 5.18 (VDD) to around 4.90V.I tried this several times just to make sure its inherent. Well, it doesn't miss a beat. The same discontinuous output occurs every time.

I then popped the 618 off the breadboard and dropped in a pin-for-pin compatible MCP6273. This op amp has true rail-to-rail input and output. As I was hoping, as the pot is wound down the output voltage does not exhibit the sudden drop but instead smoothly and continuously decreases from VDD.

Whew! I'm just glad it isn't some much more major error or defect.

Moral of the story: Make sure the op amp has rail-to-rail input and output if the application requires the entire range of values.

Tuesday, November 29, 2011

Relay board

Over the years I've moved away from relays and adopted triacs for switching high voltage AC loads. Their quiet operation and compact size (heat sink excluded) are a plus. In the last few years, however, I've reverted to the electromechanical device. The prodigal son has returned (nahhh). Relays have their drawbacks including mechanical failure, contact point burning/pitting, and coil burnout, but they provide superb electrical isolation between control and power side of the circuit and negligible voltage drop across the contacts.

So here's my relay module v1.0 complete with polarity reversal protection diode, flyback diode, and LED power-on indicator. The relay in this case is a SPDT with a contact rating of 10A @240VAC/24VDC and a coil of 12VDC (measured resistance = 420 ohms). The board is designed such that the LED and its current limiting resistor need not be installed for the circuit to work--the additional current draw may be undesirable or an indicator lamp may simply be superfluous. Even the input diode in series with the positive rail isn't absolutely essential (if polarity reversal isn't an issue) and a jumper wire can be soldered in its stead. Omitting the flyback diode, on the other hand, is an experiment for foolhardy to try out.

DC coil power input is via the 2-pin connector with red going to V+ as per convention, while relay contacts are accessed via the 3-way terminal block. I'm obsessed with isolation so I've placed them on opposite ends of the board.

The paper phenolic board is 1 x 3 inches, scored and snapped off from a 4x3-inch "mother" board which contains three other clones. Yeah, I'm mass producing this.... Uhhh sort of.

Saturday, November 26, 2011

Random number generator

Needed a way to make a microcontroller turn a load on at random. A little research and I found an algorithm that goes by the highfalutin name linear feedback shift register. Basically the bits in the register are shifted one bit either to the left or right and the result is then XORed with a constant. The content of the register changes in a pseudo random way. It isn't at all random because when we perform the shift and XOR the register goes through each possible value once and only once (each of 255 numbers for an 8-bit register) until we cycle back to the original seed value (initial value of the register). Thereafter, the exact same sequence gets repeated.

It's important to make sure the seed stored in the register is nonzero, else it will remain zero.

PIClist has a number of code snippets for implementing the LFSR in Microchip PICs. The following is for generating a 16-bit random number written in PIC18 assembly:

  BCF     STATUS,C
  RRCF    LFSRVALUEH,F
  RRCF    LFSRVALUEL,F
  BTFSS   STATUS,C
  RETURN
  MOVLW   0xA1
  XORWF   LFSRVALUEH
  XORWF   LFSRVALUEL
  RETURN

Converting the above into mikroC PIC16 assembly with the 16-bit random number stored in variable N, we have:

asm{bcf     STATUS,C
    rrf     _N_L0+1,F
    rrf     _N_L0+0,F
    btfss   STATUS,C
    goto    $+4
    movlw   0xA1
    xorwf   _N_L0+1,F
    xorwf   _N_L0+0,F}

And converting the same into mikroC we have:

int8 RanNum()
{
  static unsigned int RANDOM = 1;

  RANDOM >>= 1;
  if (STATUS.C)
    RANDOM ^= 0xA1A1;
  return RANDOM;
}

The above code translates into just 8 lines of mikroC assembly using the enhanced midrange instruction set.

For the circuit I'm using it in, I need a number from 0 to 99 to emulate a percentage value. So what I did was to simply take the last two digits (decimal) by dividing the 16-bit number by 100 and taking the remainder. I used a 16-bit generator instead of an 8-bit because the application requires that repetition of the sequence does not occur too quickly. I also wanted the seed number to be changed every time the circuit starts from a power-off condition so that the same sequence doesn't repeat every time the circuit powers up. To implement this the firmware loads RANDOM with the value stored in an EEPROM address. A crucial check is made to make sure RANDOM is not zero. If it is then it's incremented by one. RANDOM is then incremented and stored back in the same EEPROM location. Thus, every time the circuit boots up, RANDOM starts with a different seed.

#define  addr_seed     0x1    // plug in your choice of eeprom address for the seed number
static unsigned int RANDOM;

void IniSeed()
{
  // seed number for random number generator is obtained from an eeprom location
  // the value of seed number is incremented and stored back in eeprom
  // thus, a different seed number is used every time the MCU powers up.
  RANDOM = EEPROM_Read(addr_seed);
  if (!RANDOM)                           // seed number must not be zero!
    RANDOM++;
  EEPROM_Write(addr_seed, RANDOM+1);
}

int8 RanNum()
{
  RANDOM >>= 1;
  if (STATUS.C)
    RANDOM ^= 0xA1A1;
  return RANDOM % 100;     // a number from 0 to 99 is returned 
} 

Note that EEPROM_Read() and EEPROM_Write() are mikroC built-in functions. Admittedly the seed number in the above routine is only 8 bits long, but for my particular application this is sufficient. If you need a 16-bit seed then simply read the contents of two EEPROM addresses into RANDOM, increment RANDOM, and store the low and high bytes of RANDOM in the appropriate EEPROM addresses:

void IniSeed()
{
  RANDOM = EEPROM_Read(addr_seed)*256 + EEPROM_Read(addr_seed + 1);
  if (!RANDOM)                           // seed number must not be zero!
    RANDOM++;
  EEPROM_Write(addr_seed, (RANDOM + 1)/256);
  EEPROM_Write(addr_seed+1, RANDOM + 1);
}

Wednesday, November 23, 2011

Variable intermittent wiper control - printed circuit board

Finally got around to making a PCB for the wiper control. Used DesignSpark PCB to draw the schematic and PCB layout. The board is 3 x 3.5 inches and was exposed to fluorescent light for 70 seconds. Vertical distance of the two 11-watt Toshiba compact fluorescent lamps from the board was 3 inches. On the long sides, I placed cardboard on which aluminum foil had been taped to reflect light back into the exposure area and even out the lighting. (Up to now I have no idea whether the board is sensitive only to UV or UV + visible light, nor do I know to what degree aluminum reflects/absorbs UV.) Details of the procedure and materials I use can be found in Making PCBs using presensitized boards.

I introduced one modification to the June 2011 circuit. The wire length from the board to the push button (a tact switch epoxied to the end of the stem switch that controls the headlights and turn signal lights) is almost a meter in length, so instead of directly connecting it to one of the MCU pins, I am now using a KB817 optoisolator. I also added a red LED to the circuit. It's connected to one of the MCU pins via a current limiting 330-ohm resistor. Right now it serves no function. I just included it just in case and since the MCU pin is unused.

The Sonalert buzzer isn't in the pics because it's connected via the terminal blocks.






As always the soldering leaves much to be desired. I particularly had trouble soldering the huge pins of the Telemecanique RXM2 DPDT relay to the board. The trick, which I learned only after botching it, is to lay on lots of flux paste on the pins (and copper pads) and melt a huge blob of solder to the puny 30W soldering iron (30-year old, non temperature controlled, and directly plugged into the 220VAC outlet). The thermal mass of the gob of solder and large contact area coupled with the cleaning action of the flux greatly increases the chances of successfully and cleanly soldering these gargantuan pins.

Friday, October 21, 2011

Cascading (snowfall) LED lights

I first saw these kinds of lights some two years ago and back then didn't know what they were called so I dubbed them raindrop lights. Check out the following vids for a couple of variations of cascading lights: http://youtu.be/FK-wlvhhtXk and http://youtu.be/qR4DLUNAbAo. The first vid has a close-up of the unit, showing--to my surprise--DIP ICs and 1/4 Watt resistors. The LEDs these units use are of the type which can be seen from practically any direction.

Just as a challenge I wanted to emulate the effect using ordinary white LEDs and a microcontroller. After some trial and error I got the firmware working. I'm sure it could be improved upon and different variations could be implemented. For instance as in the second vid above, the cascade could be made without PWM, leaving fading for the very last LED.

So here's a vid of the breadboarded circuit. The firmware that drives it follows. If anyone will be so kind enough to donate a real video cam the pic quality can be greatly enhanced  :-)



/*

Cascading (Snowfall or Meteor) LED Lights
October 16 2011

processor = PIC16F1827
compiler = mikroC v5.0.0

(configuration settings -- copied from mikroC's "Edit Project" window)
CONFIG1   :$8007 : 0x0F84
CONFIG2   :$8008 : 0x1613

*/


// if using different PORT pins just change the assignments below
// the firmware does not need to be edited
#define  led1                LATA.f1             // led1 is the first LED to be lit (topmost LED of the cascade)
#define  led2                LATA.f0
#define  led3                LATA.f7
#define  led4                LATA.f6
#define  led5                LATB.f7
#define  led6                LATB.f6
#define  led7                LATB.f5
#define  led8                LATB.f4
#define  led9                LATB.f3
#define  led10               LATB.f2
#define  led11               LATB.f1
#define  led12               LATB.f0
#define  led13               LATA.f4
#define  led14               LATA.f3
#define  led15               LATA.f2             // led15 is the last LED to be lit (bottom LED of the cascade)
#define  lastled             led15               // this is used in the Twinkle() function

// following used for twinkle effect
#define  fadeintime          2                   // number of PWM periods for each pulse width during fade in from min to max brightness
#define  fadeouttime         7                   // number of PWM periods for each pulse width during fade out from max to min brightness
#define  twinkle_delay       1                   // deciseconds between the end of the cascade and beginning of twinkle effect
                                                 // during this time led15 is off

#define  cascade_delay       20                  // deciseconds between cascades. during this time all LEDs are off
                                                 // time begins after Twinkle()

#define  t0_ini              256-128             // TMR0 initialize value every time it overflows
#define  t1h_ini             256-98              // TMR1H initialize value every time it overflows
#define  cascadetime         10                  // number of cycles (PWM periods) per stage
#define  maxstage            22                  // maximum number of stages
                                                 // a stage is when a set of LEDs are lit
                                                 // each successive stage moves the cascade by one LED

#define  int1                bit
#define  int8                unsigned char
#define  int16               unsigned int
#define  int32               unsigned long

#define  on                  1
#define  off                 0

// ==================================================================================================================

void IniReg()
{
  OSCCON = 0b1110000;        // 8MHz
  TRISA = 0;
  TRISB = 0;
  ANSELA = 0;
  ANSELB = 0;
  LATA = 0;
  LATB = 0;
  T1CON = 0b110000;          // prescale = 1:8, timer1 off
                             // given 8MHz, TMR1H = 98 counts, prescale = 1:8, timer1 tick = ~100ms
} // void IniReg()


// During the start of each PWM period the LEDs that will be "PWMed" for that stage are turned on.
void IniLEDs(int8 STAGE)
{
  switch (STAGE)
  {
    case 1:
      led1 = on;
      break;

    case 2:
      led1 = on;
      led2 = on;
      break;

    case 3:
      led1 = on;
      led2 = on;
      led3 = on;
      break;

    case 4:
      led1 = on;
      led2 = on;
      led3 = on;
      led4 = on;
      break;

    case 5:
      led1 = on;
      led2 = on;
      led3 = on;
      led4 = on;
      led5 = on;
      break;

    case 6:
      led1 = on;
      led2 = on;
      led3 = on;
      led4 = on;
      led5 = on;
      led6 = on;
      break;

    case 7:
      led1 = on;
      led2 = on;
      led3 = on;
      led4 = on;
      led5 = on;
      led6 = on;
      led7 = on;
      break;

    case 8:
      led1 = on;
      led2 = on;
      led3 = on;
      led4 = on;
      led5 = on;
      led6 = on;
      led7 = on;
      led8 = on;
      break;

    case 9:
      led2 = on;
      led3 = on;
      led4 = on;
      led5 = on;
      led6 = on;
      led7 = on;
      led8 = on;
      led9 = on;
      break;

    case 10:
      led3 = on;
      led4 = on;
      led5 = on;
      led6 = on;
      led7 = on;
      led8 = on;
      led9 = on;
      led10 = on;
      break;

    case 11:
      led4 = on;
      led5 = on;
      led6 = on;
      led7 = on;
      led8 = on;
      led9 = on;
      led10 = on;
      led11 = on;
      break;

    case 12:
      led5 = on;
      led6 = on;
      led7 = on;
      led8 = on;
      led9 = on;
      led10 = on;
      led11 = on;
      led12 = on;
      break;

    case 13:
      led6 = on;
      led7 = on;
      led8 = on;
      led9 = on;
      led10 = on;
      led11 = on;
      led12 = on;
      led13 = on;
      break;

    case 14:
      led7 = on;
      led8 = on;
      led9 = on;
      led10 = on;
      led11 = on;
      led12 = on;
      led13 = on;
      led14 = on;
      break;

    case 15:
      led8 = on;
      led9 = on;
      led10 = on;
      led11 = on;
      led12 = on;
      led13 = on;
      led14 = on;
      led15 = on;
      break;

    case 16:
      led9 = on;
      led10 = on;
      led11 = on;
      led12 = on;
      led13 = on;
      led14 = on;
      led15 = on;
      break;

    case 17:
      led10 = on;
      led11 = on;
      led12 = on;
      led13 = on;
      led14 = on;
      led15 = on;
      break;

    case 18:
      led11 = on;
      led12 = on;
      led13 = on;
      led14 = on;
      led15 = on;
      break;

    case 19:
      led12 = on;
      led13 = on;
      led14 = on;
      led15 = on;
      break;

    case 20:
      led13 = on;
      led14 = on;
      led15 = on;
      break;

    case 21:
      led14 = on;
      led15 = on;
      break;

    case 22:
      led15 = on;
      break;

    default:
      break;
  } // switch (STAGE)
} //void IniLEDs()


// Within the PWM period, LEDs are turned off when their particular pulse width has been reached.
void SwitchLEDs(int8 STAGE, int8 PWEXP)
{
  switch (PWEXP)
  {
    case 0:
      switch (STAGE)
      {
        case 8:
          led1 = off;
          break;

        case 9:
          led2 = off;
          break;

        case 10:
          led3 = off;
          break;

        case 11:
          led4 = off;
          break;

        case 12:
          led5 = off;
          break;

        case 13:
          led6 = off;
          break;

        case 14:
          led7 = off;
          break;

        case 15:
          led8 = off;
          break;

        case 16:
          led9 = off;
          break;

        case 17:
          led10 = off;
          break;

        case 18:
          led11 = off;
          break;

        case 19:
          led12 = off;
          break;

        case 20:
          led13 = off;
          break;

        case 21:
          led14 = off;
          break;

        case 22:
          led15 = off;
          break;

        default:
          break;
      }
      break;

    case 1:
      switch (STAGE)
      {
        case 7:
          led1 = off;
          break;

        case 8:
          led2 = off;
          break;

        case 9:
          led3 = off;
          break;

        case 10:
          led4 = off;
          break;

        case 11:
          led5 = off;
          break;

        case 12:
          led6 = off;
          break;

        case 13:
          led7 = off;
          break;

        case 14:
          led8 = off;
          break;

        case 15:
          led9 = off;
          break;

        case 16:
          led10 = off;
          break;

        case 17:
          led11 = off;
          break;

        case 18:
          led12 = off;
          break;

        case 19:
          led13 = off;
          break;

        case 20:
          led14 = off;
          break;

        case 21:
          led15 = off;
          break;

        default:
          break;
      }
      break;

    case 2:
      switch (STAGE)
      {
        case 6:
          led1 = off;
          break;

        case 7:
          led2 = off;
          break;

        case 8:
          led3 = off;
          break;

        case 9:
          led4 = off;
          break;

        case 10:
          led5 = off;
          break;

        case 11:
          led6 = off;
          break;

        case 12:
          led7 = off;
          break;

        case 13:
          led8 = off;
          break;

        case 14:
          led9 = off;
          break;

        case 15:
          led10 = off;
          break;

        case 16:
          led11 = off;
          break;

        case 17:
          led12 = off;
          break;

        case 18:
          led13 = off;
          break;

        case 19:
          led14 = off;
          break;

        case 20:
          led15 = off;
          break;

        default:
          break;
      }
      break;

    case 3:
      switch (STAGE)
      {
        case 5:
          led1 = off;
          break;

        case 6:
          led2 = off;
          break;

        case 7:
          led3 = off;
          break;

        case 8:
          led4 = off;
          break;

        case 9:
          led5 = off;
          break;

        case 10:
          led6 = off;
          break;

        case 11:
          led7 = off;
          break;

        case 12:
          led8 = off;
          break;

        case 13:
          led9 = off;
          break;

        case 14:
          led10 = off;
          break;

        case 15:
          led11 = off;
          break;

        case 16:
          led12 = off;
          break;

        case 17:
          led13 = off;
          break;

        case 18:
          led14 = off;
          break;

        case 19:
          led15 = off;
          break;

        default:
          break;
      }
      break;

    case 4:
      switch (STAGE)
      {
        case 4:
          led1 = off;
          break;

        case 5:
          led2 = off;
          break;

        case 6:
          led3 = off;
          break;

        case 7:
          led4 = off;
          break;

        case 8:
          led5 = off;
          break;

        case 9:
          led6 = off;
          break;

        case 10:
          led7 = off;
          break;

        case 11:
          led8 = off;
          break;

        case 12:
          led9 = off;
          break;

        case 13:
          led10 = off;
          break;

        case 14:
          led11 = off;
          break;

        case 15:
          led12 = off;
          break;

        case 16:
          led13 = off;
          break;

        case 17:
          led14 = off;
          break;

        case 18:
          led15 = off;
          break;

        default:
          break;
      }
      break;

    case 5:
      switch (STAGE)
      {
        case 3:
          led1 = off;
          break;

        case 4:
          led2 = off;
          break;

        case 5:
          led3 = off;
          break;

        case 6:
          led4 = off;
          break;

        case 7:
          led5 = off;
          break;

        case 8:
          led6 = off;
          break;

        case 9:
          led7 = off;
          break;

        case 10:
          led8 = off;
          break;

        case 11:
          led9 = off;
          break;

        case 12:
          led10 = off;
          break;

        case 13:
          led11 = off;
          break;

        case 14:
          led12 = off;
          break;

        case 15:
          led13 = off;
          break;

        case 16:
          led14 = off;
          break;

        case 17:
          led15 = off;
          break;

        default:
          break;
      }
      break;

    case 6:
      switch (STAGE)
      {
        case 2:
          led1 = off;
          break;

        case 3:
          led2 = off;
          break;

        case 4:
          led3 = off;
          break;

        case 5:
          led4 = off;
          break;

        case 6:
          led5 = off;
          break;

        case 7:
          led6 = off;
          break;

        case 8:
          led7 = off;
          break;

        case 9:
          led8 = off;
          break;

        case 10:
          led9 = off;
          break;

        case 11:
          led10 = off;
          break;

        case 12:
          led11 = off;
          break;

        case 13:
          led12 = off;
          break;

        case 14:
          led13 = off;
          break;

        case 15:
          led14 = off;
          break;

        case 16:
          led15 = off;
          break;

        default:
          break;
      }
      break;

    // for PWEXP = 7, no led need be switched off since it's duty cycle will be gradually decreased anyway in the next STAGE
    // therefore there's no need for a separate case statement. 
    // The default option below will automatically "catch" the case when PWEXP = 7

    default:
      break;
  } // switch (PULSEWIDTH)
} // void SwitchLEDs()


/*
The particular LEDs to be lit and their pulse width depends on the current STAGE. 
At the start of each PWM period all LEDs for the particular stage are turned on.
Then one by one, beginning with the LED that has the least pulse width, they're turned off. 
After the second TMR0 overflow, the prescale value of timer0 is changed every TMR0 overflow as shown in the table below. 
Every time TMR0 overflows it is re-initialized with t0_ini (128decimal).
The total time elapsed is the pulse width of the LEDs. 
The change in PW between the LEDs is exponential. 
The lead LED has a duty cycle of 100%.

PWN period = 8.192ms 
Fosc = 8Mhz
t0 tick = (TMR0 count)*(prescale)/(Fosc/4)

t0_ini  TMR0 count   prescale   t0 tick (us)  total time elapsed (us) 
128        128           1           64               64 
128        128           1           64              128 
128        128           2          128              256 
128        128           4          256              512 
128        128           8          512             1024 
128        128          16         1024             2048 
128        128          32         2048             4096 
128        128          64         4096             8192 
*/
void Cascade()
{
  // OPTREG[] lookup table contains values to be successively loaded into OPTION register during one PWM period
  // timer0 prescale values are {1,1,2,4,8,16,32,64}
  // to obtain timer0 prescale value = 1, prescaler is NOT assigned to timer0
  // timer0 initial value is kept constant at 128decimal
  const int8 OPTREG[] = {0b1000, 0b1000, 0b0, 0b1, 0b10, 0b11, 0b100, 0b101};

  int8 STAGE;                  // at which stage the cascade is currently
  int8 PWEXP;                  // exponent (with base = 2) of the pulse width
  int8 TOTALTIME;              // records number of PWM periods

  PWEXP = 0;
  for (STAGE = 1; STAGE <= maxstage; STAGE++)
  {
    TOTALTIME = 0;
    while (TOTALTIME <= cascadetime)
    {
      if (!PWEXP)                                // initialize (turn on) LEDs only when it's the start of a PWM period
        IniLEDs(STAGE);
      OPTION_REG = OPTREG[PWEXP];
      TMR0 = t0_ini;
      INTCON.T0IF = 0;
      while (!INTCON.T0IF) ;
      SwitchLEDs(STAGE, PWEXP);                   // turn off the LED
      if (++PWEXP > 7)
      {
        PWEXP = 0;
        TOTALTIME++;
      }
    } // while (TOTALTIME <= cascadetime)
  } // for (STAGE=1; STAGE<= maxstage; STAGE++)
} // void Cascade()


// PWM routine for the twinkle effect
void Dim(int8 EXP, int8 FADETIME)
{
  // pulse width (output high = led on) in terms of the value of TMR0, given a fixed timer0 period of 8.192ms
  const int8 PW[] = {1, 2, 4, 8, 16, 32, 64, 128, 255};

  int8 TOTALTIME = 0;
  int8 PULSEWIDTH;

  TMR0 = 0;
  INTCON.T0IF = 0;
  PULSEWIDTH = PW[EXP];

  while (TOTALTIME++ <= FADETIME)
  {
    lastled = on;
    while (TMR0 < PULSEWIDTH);
    lastled = off;
    while (!INTCON.T0IF);
    INTCON.T0IF = 0;
  }
}

// twinkle effect where fade in time and fade out time are or can be different
void Twinkle()
{
  int8 i;
  
  OPTION_REG = 0b10000101;             // timer0 prescale = 1:64
  for (i = 0; i <= 8; i++)
    Dim(i, fadeintime);
  for (i = 7; i > 0; i--)
    Dim(i, fadeouttime);
  lastled = off;
} // void Twinkle()


// delay in deciseconds
// as an alternative timer0 can be used instead of timer1 if, say, using an MCU with only one timer
void DelayDsec(int8 dsec)
{
  int8 i;

  TMR1L = 0;
  T1CON.TMR1ON = 1;
  for (i = 0; i < dsec; i++)
  {
    TMR1H = t1h_ini;
    PIR1.TMR1IF = 0;
    while (!PIR1.TMR1IF);
  }
  T1CON.TMR1ON = 0;
} // void DeadTime()


void main()
{
  IniReg();
  while(1)
  {
    Cascade();
    DelayDsec(twinkle_delay);
    Twinkle();
    DelayDsec(cascade_delay);
  }
} // void main()



----

November 23 2011 Addendum

Here's the schematic for the cascading lights circuit. As you can see it's just one MCU and LEDs on all the pins except for pin#4 (RA5) which is an input-only pin. Resistors can be as low as, oh about, 180 ohms for a power supply of 5 volts. You have to watch out for the maximum rating of each pin (25mA if I remember correctly) and the maximum for each PORT.


----

Schematic when using a PIC16F84A

Thursday, October 13, 2011

Video of LED fade in/out

For details of the following vid see yesterday's LED fading using exponentially changing PWM duty cycle. However, because the dimming wasn't as smooth as I wanted it to be, I changed a couple of items.

1. I used 32 pulse width values instead of 16. And so the look-up table for this demo is:
const char PW[] = {0, 0, 1, 1, 1, 2, 2, 3, 4, 5, 6, 7, 9, 10, 12, 15, 18, 22, 26, 31, 37, 44, 53, 63, 75, 90, 107, 127, 151, 180, 214, 255};

2. To preserve the 655ms total time from zero to maximum brightness I halved the timer2 postscale to 1:5.

3. Because of the increase in PW values, the constant in one of the conditional statements had to be changed from 15 to 31:
if (direction && ++i > 31).

LED fading using exponentially changing PWM duty cycle

Not wishing to make the title any longer than it is, I left out the following qualification: Although the duty cycle (pulse width for output high) varies exponentially versus time, the time interval between changes in duty cycle is fixed.

One could I suppose also achieve fading with a linearly changing duty cycle and exponentially changing time interval. Sounds more complicated though.

The problem with fading an LED using PWM is that human perception of brightness is logarithmic, just like our hearing. So changing the PWM duty cycle in a linear fashion (e.g. incrementing or decrementing the duty cycle by 1% every unit time interval) does not result in a perception of linear change in brightness.

The solution is to change the duty cycle exponentially. The following is one formula that provides a way of obtaining a series of duty cycle values with a minimum of 1 and a maximum of 2n, where n is a positive integer. The fact that the numeric base = 2 means the results are perfectly suited to computing values for 8, 16, or 32-bit registers of microcontrollers.

duty cycle = 2x/r

where
x = 1, 2, 3, ..., m-1, m
m = integral number of duty cycle values required.
r is chosen such that 2m/r = maximum duty cycle value

One way to understand x/r is to think of a number of rulers all of the same length--say, 12 inches. One has graduations only in inches. Another has divisions only in half inches (of course two half inches still make an inch). Yet another is marked off in quarter inches. One has one-eighth inch graduations. And another is divided into sixteenths of an inch. x corresponds to the number of graduations/divisions. Hence our 12-inch ruler with quarter-inch divisions has a total of 12 inches x 4 divisions/inch = 48 divisions. And the ruler with one-sixteenth inch graduations has 12 x 16 = 192 divisions. On the other hand, 1/r corresponds to the unit used by our ruler or the distance between graduations. So for inch-graduated ruler, 1/r = 1/1. For the one-eighth-inch-graduated ruler, 1/r = 1/8.

Let's say we are constrained to using an 8-bit register (eg. CCPR1L of the PIC12F615) for our duty cycle values. This means the maximum pulse width value we can specify is 0xFF or 255 decimal. Assume we want to make an LED fade in from zero to full brightness in a relatively short time--less than half a second. Having the LED ramp up 16 values should be sufficient for a visually smooth, seamless fade in. Going back to our ruler, if we employ the ruler with one-inch divisions, we'd have eight values and those values, as per our formula, would be 21/1, 22/1, ... 28/1. But since we'd like 16 different pulse widths, we have to use the half-inch-graduated ruler which has 16 divisions from 1/2 inch to 8 inches. To obtain the actual duty cycle values, we use the formula giving us the following series: 21/2, 22/2, 23/2, ... 215/2, 216/2, or 1.41, 2.00, 2.83, .... 181.02, 256.00. Rounding those numbers off we have the following integral series: 1, 2, 3, 4, 6, 8, 11, 16, 23, 32, 45, 64, 91, 128, 181, 255. Obviously, given our 8-bit constraint, the maximum duty cycle value has to be 255 not 256.

If we wanted 32 pulse width values--perhaps because we are fading the LED over a much longer time interval and 16 values don't provide a smooth enough fade--we'd choose the ruler with quarter-inch divisions. Using the equation we have: 21/4, 22/4, 23/4, 24/4, 25/4, ... 231/4, 232/4. Here's a graph of this example.


Bear in mind that the ruler analogy is only an aid to understanding. Once you get the hang of the equation, you can throw away these imaginary rulers. And contrary to what the ruler examples imply, we're not limited to 2n number of pulse widths (eg. 8, 16, 32, 64). Still taking our maximum duty cycle = 0xFF, we can have, for instance, 24 values: 21/3, 22/3, 23/3, 24/3, 25/3, ... 223/3, 224/3. Or, say, 40 values: 21/5, 22/5, ... 213/5, 214/5, 215/5, ... 239/5, 240/5. Or 56 values: 21/7, 22/7, ... 255/7, 256/7. Notice that for all these examples m/r is still = 8 and so maximum duty cycle value in all these series is 0xFF.

One may, if desired, also use the following slightly modified formula

duty cycle = 2x/r - 1

Using this equation forces the series to start with zero.

Given m = 16, r = 2, and thus a maximum duty cycle value of 216/2 = 0xFF, the series we end up with is: 0, 1, 2, 3, 5, 7, 10, 15, 22, 31, 44, 63, 90, 127, 180, 255. Note how the maximum is neatly the maximum possible for our 8-bit register.

Below is a sample program where an LED alternately fades in and out. The firmware employs a look-up table to change the duty cycle. PWM frequency is 244.14 Hz. This is the minimum PWM frequency obtainable in the PIC12F615 when using its internal oscillator (Fosc = 4MHz). That PWM frequency translates to a period of 4.096ms. Because 4.096ms x 16 duty cycle values is only = 65.5ms, I set timer2 postscaler to 1:10 which has the effect of holding each duty cycle value for 10 periods. With that the amount of time for the LED to go from minimum to maximum duty cycle = 4.096ms x 16 x 10 = 655ms which is slow enough for humans to actually see and appreciate. The downside is that keeping the DC value for 10 periods is noticeable at the lower DC values. The stepwise change is obvious. To remedy this, we can always increase the number of DC values and decrease the timer2 postscale value.

For the PIC12F615,

fPWM = Fosc / [(PR2+1)*4*(T2PS)]

fPWM = PWM frequency
Fosc = frequency of the oscillator
PR2 = PR2 register value
T2PS = timer2 prescale value

PWM period = 1 / fPWM

Pulse width = (CCPR1L:CCP1CON<5:4>)*(T2PS)/Fosc

CCPR1L:CCP1CON<5:4> is a 10-bit number, with the 8-bit MSB in CCPR1L and the 2-bit LSB in CCP1CON<5:4>

In the firmware below CCP1CON<5:4> is = 0. When using the pulse width equation remember to left shift CCPR1L by two places. In other words multiply CCPR1L by 4.

LED is connected to pin P1A/GP2.

/*
LED fading using exponentially changing PWM duty cycle with fixed time interval between changes in duty cycle

October 12 2011

processor = PIC12F615
compiler = mikroC v5.0.0

*/

// computed duty cycle values using the equation INT{[2^(x/r)] + 0.5} - 1
// x = 1, 2, ..., 16
// r = 2
// values are stored in a look-up table in flash rather than RAM
const char PW[] = {0, 1, 2, 3, 5, 7, 10, 15, 22, 31, 44, 63, 90, 127, 180, 255};  

void main()
{
  unsigned char i = 0;
  bit direction;
  
  ANSEL = 0;                 // all pins digital

  // initialize PWM registers
  // see PIC12F615 datasheet DS41302C p.96 for PWM set up procedure
  PR2 = 255;
  CCP1CON = 0b1100;          // enable PWM mode, single output (P1A only)
  CCPR1L = 0;
  PIR1.TMR2IF = 0;           // clear timer2 interrupt flag
  T2CON = 0b1001011;         // timer2 prescale = 1:16, postscale = 1:10
  T2CON.TMR2ON = 1;          // turn on timer2
  TRISIO = 0b0;

  direction = 1;
  while(1)
  {
    if (PIR1.TMR2IF)          // timer2 interrupt flag is set when postscale value is reached 
    {
      if (direction && ++i > 15)
      {
        direction = 0;
        i = 14;
      }
      else if (!direction && --i == 0)
        direction = 1;
      CCPR1L = PW[i];
      PIR1.TMR2IF = 0;
    }
  } // while(1)
} // void main()



----

References:

http://www.instructables.com/community/linear-PWM-LED-fade-with-arduino/
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=printview&t=45080&start=0
http://en.wikipedia.org/wiki/F-number
http://www.mikroe.com/app/webroot/forum/viewtopic.php?f=88&t=22277

Tuesday, September 27, 2011

Checking out the Pierce oscillator

The Pierce oscillator is a very simple circuit and as with the ring oscillator also uses inverters (actually just one inverter will do). The principle of operation, however, is very different. While the ring oscillator exploits the incremental propagation delay through the series of inverters, the Pierce oscillator puts the inverter/gate into the linear region of operation and employs it as an analog amplifier.

To find out the details of the gate-based Pierce oscillator I recommend reading the references listed below. What follows is just a summary of the tips and formulas culled from the said references, pointers and tidbits which I found useful in getting a crystal-based oscillator up and running. I also did a number of breadboarded experiments and the results are shown below.

The following schematic shows the basic Pierce oscillator based on a CMOS inverter as its amplifier.


INV is of course the inverter (buffered or unbuffered). Oscillator signal is taken from the inverter output (Vout). To improve (decrease) its rise/fall time, another inverter or, better yet, a (fast!) Schmitt trigger can be used to spruce up the signal. The signal at the inverter input is a sinusoid and can also be used (some Microchip MCU datasheets show the clock signal being derived from the input of the inverter while other Microchip datasheets show it being taken from the inverter output).

Rf is a feedback resistor that puts the gate in linear (as opposed to digital) mode operation. The following table provides a rule of thumb value for Rf given crystal frequency:


Among other things, Rs limits the amount of crystal drive--increasing Rs decreases drive. A ballpark figure or first cut value for Rs can be derived by computing for and equating Rs to the reactance of Cb:
Xcb = 1/(2πfCb), where f = crystal frequency
According to Microchip:
Rs is typically 40 K ohms or less, but is almost never more than 100 K ohms. If the value for Rs is too high, then the high impedance input side of the amplifier may be more susceptible to noise, very much the same way a pull-up resistor on an input pin is normally kept below about 50 K ohms to prevent noise from having enough strength to override the input.
Freescale (Motorola) meanwhile claims that for low frequencies such as 32.768kHz watch crystals, Rs can go as high as 330Kohms. Because of a permissible maximum drive of just 1µW, a minimum Rs value for tuning fork crystals such as the 32.768kHz crystal is 10kohm.

Ca and Cb along with Rs and XTAL provide a 180 degree phase shift (INV provides the other 180 degrees for a total of 360). Moreover, the Rs and Cb network partly "acts as a low pass filter that discourages the crystal from running at a third or fifth harmonic, or other higher frequency" (Lancaster and Berlin). Increasing Ca and Cb decreases the gain. Ca and Cb are usually equal but Cb can be made larger than Ca. The voltage at the input of INV is (partly) determined by Cb / Ca, so that increasing Cb relative to Ca increases the voltage at INV input. As a rule the values of Ca and Cb should satisfy the following condition:
Cload = (Ca)(Cb)/(Ca + Cb) + stray capacitance
where Cload is the load capacitance of the crystal as per manufacturer's specifications. Typical load capacitances are 12 pF, 15 pF, 18 pF, 20 pF, 22 pF and 32 pF.

Finally, an advice worth keeping in mind: "Oscillator design is an imperfect art at best. Combinations of theoretical and experimental design techniques should be used." And so we move on to the experimental side of things.

I performed a few tests on a breadboard using 4.000 MHz and 32.768 kHz crystals. The values for the resistors and caps are the ones I arrived at after some trial and error. The values below seem to work better. Yes, that's a very subjective assessment and shall leave it at that.


I. Motorola MC14049UBCP hex inverting buffer

A. XTAL = 32.768 kHz
Rf = 10Mohm
Rs = 100kohm
Ca = 33pF
Cb = 50pF

Oscilloscope setup
Channel 1 (yellow) hooked up to INV2 output
Channel 2 (cyan) probe hooked up to Cb


CH1: INV2 output
CH2: INV1 output


CH1: Cb
CH2: INV1 output



I increased the capacitances as follows
Ca = 18pF
Cb = 100pF
and I was pleasantly surprised to see that the waveforms became much more stable--it practically ceased "wiggling" (the frequency was stable but the duty cycle wasn't and so the falling edge would be shifting rapidly to and fro horizontally--I was triggering on the rising edge so that wasn't moving at all)

CH1: INV2 output
CH2: Cb


Notice how the increasing the Cb to Ca ratio has decreased the signal's amplitude at Cb.


B. XTAL = 4.000 MHz
Rf = 5.1Mohm
Rs = 2kohm
Ca = Cb = 18pF

CH1: INV2 output
CH2: Cb


The CMOS 4000 series is relatively slow. In the reading above rise time is almost 37ns.

CH1: INV2 output
CH2: INV1 output


CH1: Cb
CH2: INV1 output



II. National Semiconductor MM74HC02N quad 2-input NOR gate. Unfortunately, I don't currently have any CMOS HC or AC series inverters.  According to the datasheet each NOR gate is buffered as follows:


Using a buffered gate in a Pierce oscillator consumes less power and has a gain in the order of thousands compared to an unbuffered gate which has a gain of hundreds. The drawback of a buffered gate is that it is more sensitive to the values of the passive components and tends to be less stable.



A. XTAL = 32.768 kHz
Rf = 10Mohm
Rs = 51kohm
Ca = 33pF
Cb = 100pF

CH1: NOR2 output
CH2: Cb


I believe the over- and undershoots (at the rising and falling edges) is confusing the scope. In the screenshot above it's 40.98kHz, but in real time it's all over the place, sometimes reaching as high as 80kHz. So I turned on the cursors. As you can see it says 32.89kHz. Resolution is such that moving the cursor just one pixel down results in a reading of 32.68kHz.

To obtain a slightly better reading I sent the INV2 output to a Goldstar FG-2002C function generator / frequency counter. The LED display has a resolution to two decimal places and said the signal was 32.77kHz. I guess the output is pretty close to the crystal's fundamental frequency. The frequency counter apparently loads the INV1 output because connecting it directly produces garbage readings (bounces around from 40 to 65kHz). A minimum of 15 ohms of resistance or a few picofarads (the smallest I have right now is 18pF) of capacitance in series is enough to satisfy INV1. The reading is the same as probing INV2 output.

CH1: NOR2 output
CH2: NOR1 output


CH1: NOR2 output
CH2: VDD


CH1: NOR1 output
CH2: Cb


Changing the time base and capturing those overshoots/undershoots:

CH1: NOR1 output
CH2: Cb
Trigger: rising edge, 1.0V


CH1: NOR1 output
CH2: Cb
Trigger: falling edge, 4.0V



B. XTAL = 4.000 MHz
Rf = 5.1Mohm
Rs = 10kohm
Ca = Cb = 33pF

CH1: NOR2 output
CH2: Cb


CH1: NOR2 output
CH2: NOR1 output


CH1: Cb
CH2: NOR1 output


It turns out that the scope probe might be (largely) responsible for the ringing in the output. When not probing either INV1 or INV2, VDD has much less ripple and the ripple in Cb signal practically disappears:


CH1: Cb
CH2: ground


CH1: NOR2 output
CH2: VDD


CH1: Cb
CH2: VDD



References: