Monday, June 14, 2010

Thermometer using a thermocouple, instrumentation amplifer, and microcontroller

Cobbled up an instrumentation amplifier with a gain of 200 using three op amps (3/4 of an MCP609), six 5% 20K carbon resistors manually matched to less than 1% tolerance using a DMM and a gain resistor of 200ohms. Added a voltage divider buffered with two op amps (MCP6232) for two level shift voltages at the input of the IA and its voltage reference pin. Then plugged in a 30yr old Omron E52 CA1D type K thermocouple and had its obscenely noisy outputs decoupled with 1nF and 100nF ceramic caps. Filtered the gained signal with a 1st order active low pass filter (100Kohm 5% carbon, 1uF tantalum, 1/4 of the MCP609) with a cut off frequency of 1.6Hz. Sent the signal and reference-pin level shift voltage to the onboard ADC of a PIC16F690, employed a 4.096-Volt, 1%-tolerance voltage reference (MCP1541) and a 2-degree-tolerance silicon temperture sensor (MCP9700) to measure cold junction temperature. Juggled all the resulting 10-bit numbers around using floating piont and integer math and finally dumped the values to an Displaytech 162F LCD screen--so that pathetic 10Hz, decimal-based creatures like me can see the results. Well, after much tweaking and error correction of the microcontroller firmware, the breadboarded circuit actually measured steaming bubbling boiling water (at sea level) to be 99 to 100 Celsius. Not bad I say. Indeed the 1-degree accuracy amazes me given how much error and noise could've thrown the accuracy off by several degrees. Given the (sub)millivolt signal and the many hardware components involved in the signal conditioning and its conversion from analog to digital, there are numerous sources of errors which can significantly affect the accuracy of the measurement. Here are some error sources which come to mind. Some are probably not that significant at the 1 degree accuracy I've specified as my target:
  • thermocouple cold junction temperature and temperature of IC that measures the cold junction temperature 
  • IC temperature sensor tolerance  
  • op amp offset voltage
  • offset voltage drift
  • mismatched resistors in the instrumentation amplifier
  • resistor value drift
  • resistor noise
  • gain resistor value and stability
  • op amp noise
  • common mode rejection 
  • ADC reference voltage tolerance/drift
  • ADC quantization errors
  •  noise from digital circuits affecting analog circuit
  •  EMI, RF, and 50/60Hz power line hum and its harmonics

Circuit Details

A type K thermocouple (TC) is used to sense temperature. Its + and - output are both level shifted to Vref/2, where Vref = 4.096V. An instrumentation amplifier made from three of the quad op amp MCP609 amplifies the TC voltage by 200 and removes the level shift voltage. Reference pin of the IA is connected to +0.4V in order that TC may be able to sense temperatures less than the cold junction (CJ) temperature. The Microchip PIC 16F690's 10-bit ADC digitizes gained TC voltage and the level shift voltage applied to IA ref pin. The latter is then subtracted from the former, thus leaving only the amplified TC voltage. This value now corresponds to a temperature relative to the CJ temperature. A lookup table (values are stored in a 24-element, 1-dimensional array) is used to determine the temperature of TC to within 20 degrees Celsius. Interpolation is then used to determine temperature to within 1 degree. CJ temperature is sensed by a Microchip MCP9700 temperature sensor. The ADC digitizes its output and firmware converts it to degrees Celsius. Actual thermocouple temperature is obtained by adding CJ and TC. Temperature range that can be measured is approximately between -40 to 420 Celsius. I really don't have much need for a thermometer measuring anything colder than a freezer, so the level shifting voltage at the IA reference pin need not be greater than 0.5V. On the other hand I do want the circuit to be able to measure high temperatures, >400 Celsius--thus the need to keep the level shifting voltage as low as possible. Decreasing the gain is an option but I can't get myself to part with the nice round figure of 200.

AIA1, AIA2, and AIA3 are configured as a classic three-op amp instrumentation amplifier. R1 = R2 = R3 = R4 = R5 = R6 = 20K. Gain is set by RG and is given by the equation (2R1/RG + 1). ALPF (1/4 of the MCP609) is used as an active low pass filter. Given the 100K 5% carbon resistor and 1uF tantalum capacitor cutoff frequency is 1.6 Hz with a 20dB rolloff. Op amp has a gain of 1. R1 to R6 are 5% carbon resistors which have been measured using a DMM to be within 1% of each other. Gain resistor is a 5% carbon measured at 201 ohms.

VREF (MCP1541) output is 4.096V and is used by ADC as voltage reference. It is also used for the level shift voltages since maximum gained TC output cannot exceed voltage reference voltage. Level shifting voltage VLS1 for TC must be equal to Vref/2, because output of AIA1 = E1 + (E1-E2)*R1/RG and output of AIA2 = E2 - (E1-E2)*R1/RG. The plus and minus (E1-E2)*R1/RG output is symmetrical for both op amps. VLS2 is injected into Vin+ of the difference amplifier to level shift the gained and common-mode-subtracted TC voltage. This is necessary in order to measure temperature when TC is colder than cold junction temperature. Without this level shift any TC temperature below CJ would be result in zero reading because of the single supply. VLS2 is sampled by ADC whenever reading the thermocouple temperature so that it can be deducted from it. Cold junction temperature is read by TS (MCP9700) and added to the computed thermocouple temperature. Ideally TS is placed in contact with the isothermal block to measure cold junction temperature of thermocouple leads.

Cf1a, Cf1b, Cf2a, and Cf2b are ceramic capacitors and are absolutely essential. Without them noise gets injected and IA outputs garbage. 0.1uF capacitor filters out 60Hz noise of metal casings of 60VAC appliances to which thermocouple can come into contact with. 1nF filters out high frequency noise that gets conducted by thermocouple wire acting as an antenna. Capacitors should be placed close to the IA input pins.

Separate VDD and ground connections should be made for analog and digital circuits to reduce digital noise from being injected into analog circuit. A "star" connection should be employed. All op amps are decoupled with 0.1uF caps. (See Analog Devices AD623 Instrumentation Amplifier datasheet Rev.C p.13.)

Some Equations

1. Determining cold junction temperature as measured by a Microchip MCP9700 temperature sensor

MCP9700 output at zero Celsius = 500mV

Let
t = temperature in Celsius (negative when below zero)
V = MCP9700 output voltage = 500mV + 10mV per degree Celsius
CJ = 10-bit ADC reading of MCP9700 output, with the ADC using a 4.096 voltage reference

V = 0.5 + 0.01t
CJ = (0.5 + 0.01t)*1024/4.096
CJ = (0.5 + 0.01t)*250

Therefore
t = CJ/2.5 - 50


2. Determining thermocouple temperature by use of a lookup table and interpolation

Let
TCm = thermocouple voltage at temperature m, where m = -60, -40, -20, 0, 20, ..., 400 Celsius
TCn = thermocouple voltage at temperature n, where n = m + 20
TCr = thermocouple voltage at temperature r -- the temperature of the thermocouple junction relative to the cold junction
s = number of degrees Celsius = r - m

Given that temperature r lies between m and m+20
TCr = TCm + s(TCn - TCm)/20

Given TCr and a table of values for thermocouple at various temperatures we need to look for s in order to determine r. Solving for s:
s = (TCr - TCm)*20/(TCn - TCm)

Temperature at which thermcouple junction is relative to the cold junction is therefore:
r = m + s

Actual temperature of the thermocouple joint is r + t, where t = cold junction temperature

Actual thermocouple output is gained by instrumentation amplifier and then digitized by 10-bit ADC referenced to 4.096 volts. Therefore thermocouple reading that is output by the ADC is = TCr*G*1024/4.096 = TCr*G/4, where G = gain of the IA. Because of this the table of thermocouple values for TCm and TCn are multiplied by G/4 before being used in the above equations.

Sunday, June 6, 2010

Does it really show?



Was just fooling around with image processing software. Take any photo or pic, blur it till it's but an unrecognizable goop of colors, tweak its brightness, contrast, color composition, etc and you have what ends up in the background of this artwork.

Both backgrounds use the same original drawing/painting. They've just been manipulated quite differently.

Microchip logo and name filched from one of their pdf docs


Me? A PIC fanboy?

Tuesday, June 1, 2010

Macros and Defines for the PIC Baseline and Mid-Range Microcontrollers

[I had this article up on my website about a year or so ago. I've changed ISP and that website is now defunct. I'm posting it here since it may be useful to PIC newbies. I personally still use these macros and defines. Can't do assembly without them.

Because blogspot truncates text beyond a certain width, I suggest you copy the include file and paste it in MPLAB or a text editor so you can see all the comments to the right of the code.
]


The following include file contains a good number of macros that I've created to make writing code a little easier for the PIC. Also makes the code shorter--albeit only apparently--and easier to read and understand. As in my case, you'll probably find the relational macros (e.g., <, >=, <>0, etc.) the most useful and most used. I'd tear my remaining hair out if I didn't have these macros.

You won't fail to notice that I have four defines each for the btfss and btfsc instructions. I really have a difficult time with these two. The reason is because they force me to think in terms of skipping the next instruction when the test condition is true. I don't know about you but I find it way easier and natural to think of performing the next instruction should the bit test be true. To address this quirk of mine (and in retaliation against what seems to be the defacto assembly way), I've redefined btfsc and btfss as "do next" instructions. "dnx" translates into "do next."

While on the subject of mnemonics, the ones I use for the other macros are as follows:

bit0 = bit is equal to zero
bit1 = bit is equal to one
lt = less than
gt = greater than
eq = equals
not = not
l = literal
lit = literal
f = file register
w = the w register
dest = destination

If you don't like the mnemonics above then by all means change them to something that suits you. Feel free to make up your own macro labels/names.

One very important caveat: Do always keep in mind that these are macros and will be expanded inline (i.e., the macro label will be replaced by the lines of assembly instructions). As such make sure you don't write something like:

dnxgteq    reg1, reg2             ; check if reg1 >= reg2
addlf      0xA, reg3              ; do this if it is
; other instructions follow here

This code will fail when reg1 < reg2. The second macro has two lines of assembly and thus when reg1 < reg2 the second line of the macro will be executed along with whatever instructions come after it. The moral is: Never have any (multi-instruction) macros right after a dnx macro.

Now here's the include file that I #include in all my PIC Baseline and Mid-Range asm files.

; ==================================================================================
;
;      Defines and Macros  
;
;      Include this file in all PIC Midrange assembly files
;      Edwardson Tan
;      April 2007
;
; ==================================================================================

      ;------------------------------------------------
      ; do next instruction if bit = 1 (skip next instruction if bit = 0)
      #define      donextif1    btfsc
      #define      dnxbit1      btfsc
      #define      dnxtrue      btfsc
      #define      dnxhigh      btfsc
      ;------------------------------------------------

      
      ;------------------------------------------------
      ; do next instruction if bit = 0 (skip next instruction if bit = 1)
      #define      donextif0    btfss
      #define      dnxbit0      btfss
      #define      dnxfalse     btfss
      #define      dnxlow       btfss
      ;------------------------------------------------


      ;------------------------------------------------
      ; copies the contents of one register to another
      ; same as macro copyregister
movff macro      reg1,reg2
      movfw      reg1
      movwf      reg2
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; copies a literal value to a register
      ; same as macro copyliteral
movlf macro      literal, register
      movlw      literal
      movwf      register
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; "sets" a register (opposite of "reset")
      ; places a literal value of 1 in the register
setf  macro      register
      movlw      .1
      movwf      register
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; gets the high bits <12:8> of program counter PC and pastes them into PCLATH
      ; this routine is necessary just prior to doing a table read (retlw) 
      ; with a computed goto, i.e., with a ADDWF PCL,f instruction 
getpchigh macro  routine_label         ; routine_label is the name/label of the subroutine      
      movlw      high routine_label    ; "high" is an arithmetic operation that retrieves the high bits of routine_label
      movwf      PCLATH
      endm            
      ;------------------------------------------------
      

      ;------------------------------------------------
      ; mimics the pagesel assembler directive
pageselect macro label                 ; label is the name/label of the subroutine       
      movlw      high label            ; "high" is an arithmetic operation that retrieves the high bits of routine_label
      movwf      PCLATH
      endm            
      ;------------------------------------------------



; ==============================================================================================
;      macros that compare two registers
;      >, <, >=, <=, =, <>
; ==============================================================================================

      ;------------------------------------------------
      ; do next instruction if N1 > N2, skip if not
      ; N1 and N2 are registers
      ; in arguments list of macro call, specify N1 first, comma, then N2
dnxgt macro      N1,N2
      movfw      N2
      subwf      N1,w                  ; N1 minus N2
      donextif1  STATUS,Z              ; check if N1 = N2
      goto       $+3      
      donextif1  STATUS, C             ; note that if C = 0 then subtraction resulted in a negative number
      endm                             ; if N1 >= N2 then C = 1, C = 0 only when N1 < N2 
      ;------------------------------------------------
      

      ;------------------------------------------------
      ; do next instruction if N1 < N2, skip if not
      ; N1 and N2 are registers
      ; in arguments list of macro call, specify N1 first, comma, then N2
dnxlt macro      N1,N2
      movfw      N2
      subwf      N1,w                  ; N1 minus N2
      donextif0  STATUS, C             ; note that if C = 0 then subtraction resulted in a negative number
      endm                             ; if N1 >= N2 then C = 1, C = 0 only when N1 < N2  

      ;------------------------------------------------

      ;------------------------------------------------
      ; do next instruction if N1 >= N2, skip if not
      ; N1 and N2 are registers
      ; in arguments list of macro call, specify N1 first, comma, then N2
dnxgteq macro    N1,N2
      movfw      N2
      subwf      N1,w                  ; N1 minus N2
      donextif1  STATUS, C             ; note that if C = 0 then subtraction resulted in a negative number
      endm                             ; if N1 >= N2 then C = 1, C = 0 only when N1 < N2  
      ;------------------------------------------------

      ;------------------------------------------------
      ; do next instruction if N1 <= N2, skip if not
      ; N1 and N2 are registers
      ; in arguments list of macro call, specify N1 first, comma, then N2
dnxlteq macro    N1,N2
      movfw      N2
      subwf      N1,w                  ; N1 minus N2
      donextif0  STATUS,C              ; note that if C = 0 then subtraction resulted in a negative number
      goto       $+3                   ; if N1 >= N2 then C = 1, C = 0 only when N1 < N2 
      donextif0  STATUS,Z
      goto       $+2
      endm
      ;------------------------------------------------

      
      ;------------------------------------------------
      ; do next instruction if N1 = N2, skip if not
      ; N1 and N2 are registers
      ; in arguments list of macro call, specify N1 first, comma, then N2
dnxeq macro      N1,N2
      movfw      N2
      subwf      N1,w                  ; N1 minus N2
      donextif1  STATUS,Z              ; check if N1 = N2
      endm
      ;------------------------------------------------
      

      ;------------------------------------------------
      ; do next instruction if N1 <> N2, skip if not
      ; N1 and N2 are registers
      ; in arguments list of macro call, specify N1 first, comma, then N2
dnxnoteq macro   N1,N2
      movfw      N2
      subwf      N1,w                  ; N1 minus N2
      donextif0  STATUS,Z              ; check if N1 = N2
      endm
      ;------------------------------------------------


; ==============================================================================================
;      macros that compare a register and a literal
;      >, <, >=, <=, =, <>

; ==============================================================================================

      ;------------------------------------------------
      ; do next instruction if N > k, skip if not
      ; N = register, k = literal
      ; in arguments list of macro call, specify register first, comma, then literal or literal label
dnxgtlit macro   N,k
      movlw      k
      subwf      N,w                   ; N minus k
      donextif1  STATUS,Z              ; check if N = k
      goto       $+3      
      donextif1  STATUS, C             ; note that if C = 0 then subtraction resulted in a negative number
      endm                             ; if N >= k then C = 1, C = 0 only when N < k 
      ;------------------------------------------------
      

      ;------------------------------------------------
      ; do next instruction if N < k, skip if not
      ; N = register, k = literal
      ; in arguments list of macro call, specify register first, comma, then literal or literal label
dnxltlit macro   N,k
      movlw      k
      subwf      N,w                   ; N minus k
      donextif0  STATUS, C             ; note that if C = 0 then subtraction resulted in a negative number
      endm                             ; if N >= k then C = 1, C = 0 only when N < k  
      ;------------------------------------------------


      ;------------------------------------------------
      ; do next instruction if N >= k, skip if not
      ; N= register, k = literal
      ; in arguments list of macro call, specify N first, comma, then k
dnxgteqlit macro N,k
      movlw      k
      subwf      N,w                   ; N minus k
      donextif1  STATUS, C             ; note that if C = 0 then subtraction resulted in a negative number
      endm                             ; if N >= k then C = 1, C = 0 only when N < k  
      ;------------------------------------------------


      ;------------------------------------------------
      ; do next instruction if N <= k, skip if not
      ; N = register, k = literal
      ; in arguments list of macro call, specify N first, comma, then k
dnxlteqlit macro N,k
      movlw      k
      subwf      N,w                   ; N minus k
      donextif0  STATUS,C              ; note that if C = 0 then subtraction resulted in a negative number
      goto       $+3                   ; if N >= k then C = 1, C = 0 only when N < k 
      donextif0  STATUS,Z
      goto       $+2
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; do next instruction if N = k, skip if not
      ; N = register, k = literal
      ; in arguments list of macro call, specify register first, comma, then literal or literal label
dnxeqlit macro   N,k
      movlw      k
      subwf      N,w                   ; N minus k
      donextif1  STATUS,Z              ; check if N = k
      endm
      ;------------------------------------------------
      

      ;------------------------------------------------
      ; do next instruction if N <> k, skip if not
      ; N = register, k = literal
      ; in arguments list of macro call, specify register first, comma, then literal or literal label
dnxnoteqlit macro N,k
      movlw      k
      subwf      N,w                   ; N1 minus N2
      donextif0  STATUS,Z              ; check if N1 = N2
      endm
      ;------------------------------------------------

      ;------------------------------------------------
      ; do next instruction if register = 0, skip if not
dnxzero macro    register
      movf       register,w
      donextif1  STATUS,Z
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; do next instruction if register <> 0, skip if not
dnxnotzero macro register
      movf       register,w
      donextif0  STATUS,Z
      endm
      ;------------------------------------------------


; ==============================================================================================
;      macros that perform math functions
; ==============================================================================================


      ;------------------------------------------------
      ; add literal k to register and put sum in register
      ; in arguments list of macro call, specify literal first, comma, then register
      ; NOTE: no provision for results > 255
addlf macro      k,register
      movlw      k
      addwf      register,f
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; add literal k to register and put sum in W register
      ; in arguments list of macro call, specify literal first, comma, then register
      ; NOTE: no provision for results > 255
addlfw macro     k,register
      movlw      k
      addwf      register,w
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; add literal k to register1 and put sum in register3
      ; this is the general macro for adding literal to a register
      ; addlf and addlfw can be emulated using this macro but with one additonal instruction 
      ; in arguments list of macro call, specify literal first, comma, reg1, comma, then reg3
      ; NOTE: no provision for results > 255
addlfdest macro  k,reg1,reg3
      movlw      k
      addwf      reg1,w
      movwf      reg3
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; add register1 to register2 and put sum in register2
      ; in arguments list of macro call, specify reg1 first, comma, then reg2
      ; NOTE: no provision for results > 255
addff macro      reg1,reg2
      movfw      reg1
      addwf      reg2,f
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; add register1 to register2 and put sum in W register
      ; in arguments list of macro call, specify reg1 first, comma, then reg2
      ; NOTE: no provision for results > 255
addffw macro     reg1,reg2
      movfw      reg1
      addwf      reg2,w
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; add register1 to register2 and put sum in register3
      ; this is the general macro for adding two registers
      ; addff and addffw can be emulated using this macro but with one additonal instruction 
      ; in arguments list of macro call, specify reg1 first, comma, reg2, comma, then reg3
      ; NOTE: no provision for results > 255
addffdest macro  reg1,reg2,reg3
      movfw      reg1
      addwf      reg2,w
      movwf      reg3
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; subtract literal k from register and put difference in register
      ; in arguments list of macro call, specify literal first, comma, then register
      ; NOTE: no provision for negative results
sublf macro      k,register
      movlw      k
      subwf      register,f
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; subtract literal k from register and put difference in W register
      ; in arguments list of macro call, specify literal first, comma, then register
      ; NOTE: no provision for negative results
sublfw macro     k,register
      movlw      k
      subwf      register,w
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; subtract literal k from register1 and put difference in register3
      ; this is the general macro for subtracting a literal from a register
      ; sublf and subfw can be emulated using this macro but with one additonal instruction 
      ; in arguments list of macro call, specify literal first, comma, reg1, comma, then reg3
      ; NOTE: no provision for negative results
sublfdest macro  k,reg1,reg3
      movlw      k
      subwf      reg1,w
      movwf      reg3
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; subtract register1 from register2 and put difference in register2
      ; in arguments list of macro call, specify reg1 first, comma, then reg2
      ; NOTE: no provision for negative results
subff macro      reg1,reg2
      movfw      reg1
      subwf      reg2,f
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; subtract register1 from register2 and put difference in W register
      ; in arguments list of macro call, specify reg1 first, comma, then reg2
      ; NOTE: no provision for negative results
subffw macro     reg1,reg2
      movfw      reg1
      subwf      reg2,w
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; subtract register1 from register2 and put difference in register3
      ; this is the general macro for subtracting a register from another
      ; subff and subffw can be emulated using this macro but with one additonal instruction 
      ; in arguments list of macro call, specify reg1 first, comma, reg2, comma, then reg3
      ; NOTE: no provision for negative results
subffdest macro  reg1,reg2,reg3
      movfw      reg1
      subwf      reg2,w
      movwf      reg3
      endm
      ;------------------------------------------------


      ; ------------------------------------------------
      ; divide content of register by 4 and round off to nearest integer
      ; to do this just move the "decimal point" to the left two places
      ; AND the register with b'00111111'. This zeroes bits <7:6> but leaves other bits untouched
      ; if Carry bit = 1 this means remainder is >= 0.5 so add 1 to register
divby4 macro     register
      rrf        register,f
      rrf        register,f
      movlw      b'00111111'
      andwf      register,f
      dnxbit1    STATUS,C
      incf       register,f
      endm
      ;------------------------------------------------


      ;------------------------------------------------
      ; alternature divide by 4 macro
      ; this also rounds off to nearest integer
      ; this routine keeps clearing STATUS,C so that the digits that drop off on the right 
      ; don't appear on the left
;divby4 macro   register
;      bcf      STATUS,C
;      rrf      register,f
;      bcf      STATUS,C
;      rrf      register,f
;      dnxbit1  STATUS,C
;      incf     register,f
;      endm      
      ;------------------------------------------------
      

      ; ------------------------------------------------
      ; divide content of register by 8 and round off to nearest integer
      ; to do this just move the "decimal point" to the left three places
      ; AND the register with b'00011111'. This zeroes bits <7:5> but leaves other bits untouched
      ; if Carry bit = 1 this means remainder is >= 0.5 so add 1 to register
divby8 macro     register
      rrf        register,f
      rrf        register,f
      rrf        register,f
      movlw      b'00011111'
      andwf      register,f
      dnxbit1    STATUS,C
      incf       register,f
      endm      
      ;------------------------------------------------


      ;------------------------------------------------
      ; divide content of register by 16 and round off to nearest integer
      ; to do this swap nibbles, i.e., bits <7:4> and <3:0> switch places
      ; AND the register with b10001111'. This zeroes bits <6:4> but leaves other bits untouched
      ; if bit7 = 1 this means remainder of division >=0.5; hence, add 1 to register
divby16 macro    register
      swapf      register,f
      movlw      b'10001111'
      andwf      register,f
      dnxbit1    register,7
      incf       register,f
      bcf        register,7
      endm
      ;------------------------------------------------


; ==============================================================================================
;      macros that perform logic functions
; ==============================================================================================


      ;------------------------------------------------
      ; AND literal k with register and put output in register
      ; in arguments list of macro call, specify literal first, comma, then register
andlf macro      k,register
      movlw      k
      andwf      register,f
      endm
      ;------------------------------------------------




; ==============================================================================================
;      miscellaneous macros
; ==============================================================================================

      ;------------------------------------------------
      ; toggles the value of bit of register
toggle macro     register,bit
      dnxbit1    register,bit
      goto       $+3
      bsf        register,bit
      goto       $+2
      bcf        register,bit
      endm
      ;------------------------------------------------


Wednesday, May 19, 2010

So what did you learn in school?

I recently bought a 44780-based 16x2 LCD module from a shop I frequent. Before handing over the LCD they test it to show their client that it's in working order. To perform the test they have this inhouse device which I was later told is based on a Zilog MCU. The LCD module is simply plugged in, the unit switched on, and characters appear on the screen.

Just my luck. The test jig wasn't working. So the saleslady called in a technician. He brought out an analog multimeter and after probing several points on the PCB concluded that the linear voltage regulator (he pointed to a TO-92 component) was busted. He asked the saleslady how much the output of the AC adapter was. She said 9-volts, 1 ampere. He replied that was too much for the regulator which could only take 500mA. That must be what caused it to burn out.

At that point I couldn't help but chime in. I told the guy in a way as not to sound confrontative that rather than the adapter, it's the load that determines whether the current limit of the regulator is exceeded or not. He didn't listen and remained pretty convinced he was right. He asked his colleague for a regulator in a TO-220 package, because his reasoning is that a component that has a 1 amp (or 1.5A) rating will be able to handle the 1 amp current being supplied by the adapter. I certainly wasn't going to argue and give him an EE101, although in my mind I was already lecturing him on how you could hook up a 12-volt car battery that can deliver 50Amps or a 24-volt truck battery that can pump out even more current and the tiny voltage regulator chip simply wouldn't mind. At that point I just took a seat, got my phone out of my pocket and texted people. Minutes later--many minutes later--when he finally got the device working I found out from him that the regulator was just fine. Instead it was the jack into which the AC adapter is plugged into that was on the blink.

State of electronics education leaves much to be desired. I do consider knowledge of the nature of  the voltage regulator's current rating pretty basic. Confusing what it refers to is an egregious error.

Sunday, January 24, 2010

Finding the resistor values in a voltage divider

Am currently designing an instrumentation amplifier circuit that requires two different voltages for level shifting (Vcc/2 and Vcc/10). To obtain these voltages I'm using a simple voltage divider using three resistors. I tried finding the values by via trial and error--plugging the voltage divider equation into a spreadsheet and manually trying different values to get the correct voltages as well as values available for 5% resistors. Well, after some half dozen tries, I threw my hands up in the air. It's just too clumsy to do it that way. So I took pen and paper and started deriving the necessary equations. Here are the results.



Given the above circuit and the value of either A, B, or C, and given f and g, we want to find the values of the other two resistors. f and g are defined as follows:





You will notice that if we multiply Eq.1 and Eq.2 by source voltage VS on either side of the equation we get the voltage divider equations. But we don't need VS to find the resistor values.

We first solve for A in both equations.

For Eq.1





For Eq.2






Equating Eq.3 and Eq.4 we have




We then solve for either B or C. Let's do B first.







Thus,




Using Eq.3 and substituting Eq.6 for C we have







Thus,




Using Eq.3 and substituting Eq.5 for B we have







Thus,




Eq.3 and Eq.4 gave us A in terms of B and C. Using these sames equations we can also find B in terms of A and C, and C in terms of A and B. The derivation is trivial so only the results are provided below.










So there you have it--all the equations necessary to find the resistor values given any of them. In my case I still needed the spreadsheet to help me find values that are commercially available. I plugged in a couple of the equations in the spreadsheet, and then by choosing different values for B given f = 2 and g = 10, I eventually got A = 15K, C = 3K, given B = 12K. Those are values readily available for 5% resistors.




---

Note: I used Codecogs' LaTeX Equation Editor to produce the images for the equations above. Am a newbie to Latex and have no idea how to pad spaces to move "Eq." towards the right. With a bit of trial and error I found that placing x number of "\ ", that's forward slash and then a blank space, will force x number of blank spaces.

Sunday, January 3, 2010

Raindrop Christmas lights

During a recent visit to the malls I saw what I would describe as raindrop type Christmas lights. Instead of bulbs there were these 5-inch transparent glass or plastic tubes which housed white LEDs. A cable ran from tube to tube. The LEDs started lighting from the top and simulated a falling raindrop. One LED would light, then two, then three, ... By the time the fifth LED was on, the first was off. The sixth would light and the second go out. So there was the illusion of a segment of four LEDs racing down. This went pretty fast--some two seconds.

I didn't bring my camera and I completely forgot that my phone has a camera and so I don't have a picture to show. I googled "raindrop christmas lights" and fortunately found this.



As far as I can see from this photo this tube has around twice more LEDs than the one I saw which had around 15 to 20 of what looked like 5mm white LEDs.

Each tube has its own controller chip since the tubes weren't lighting up at the same time. More evidently, there was no big cable running from tube to tube which you'd expect if these tubes were centrally controlled--what a cabling nightmare!

What intrigues me is the smallness of the unit and what must be a limited number of parts on that long but very narrow board. I can't imagine not having some cap for filtering/bypassing so it must have high value ceramics and/or tiny electrolytics. I presume that the cable is merely for 3.3 to 5V power. And the resistors for the LED? Chip resistors? Could these LEDs have internal resistors?

As for the controller chip, I'm wondering if they're using an MCU or some other programmable IC. Could each LED be switched by a dedicated pin? Or are they charlieplexed/multiplexed?

In the lights that I saw the last LED at the bottom remains lit for about a second and a half, fading out till it's off. Beautiful effect. PWM is being used of course.

This is a most interesting product and I'd love to take one apart and see its design. It can easily be effected using an MCU. Firmware is simple. Engineering a board this compact is the bigger challenge.

Charlie to the rescue

Am working on a circuit that I want driven by an 8-pin PIC microcontroller. A 10-pin PIC would be great but such a part I can only dream of. The next available PIC has 14 pins and would be overkill for the project. The thing is I have 5 output devices to switch and only three output lines left since two other I/O lines are dedicated to analog signals while another pin is input-only (the MCLR pin) which is completely feckless for the application I'm working on. So the dilemma I'm faced with is how to switch four LEDs and one transistor with just these three pins.

Solution? Call in Charlie [see endnote]. Charlieplexing is the technique of taking advantage of the the tri-state mode of MCU I/O pins. By configuring the pins as input (high Z or high impedance), output high, or output low, a lot more LEDs can be switched than there are pins. In fact the number of LEDs that can be accommodated = n2 - n, where n = number of I/O pins. Given the three pins that I have a maximum of 32 - 3 = 6 LEDs can be switched individually:




However, I also need to get the transistor in as well. And I don't want it getting switched on along with any of the LEDs. Well, lucky me. Turns out that with that constraint I can charlieplex a maximum of four LEDs and this transistor. Here's the circuit that I ended up with (click to enlarge):


Without transistor Q, LR anode can be connected to line2 and its cathode to com and only one current limiting resistor (on com pin) is necessary. With this configuration I tried designing in two resistors, but it seems three are required. Each resistor is half the value since two resistors are conducting whenever an LED is on.

The following table shows the I/O pin configuration necessary to switch on the different LEDs and transistor. 1 = output high, 0 = output low, Z = input mode
part on | line1 | com | line2
--------+-------+-----+--------
LG | 1 | 0 | Z
LY | 0 | 1 | Z
LO | Z | 1 | 0
LR | 1 | Z | 0
Q | Z | Z | 1
To turn off all the devices just turn all pins into inputs. Yes, the gate of the MOSFET is floating when line2 is high-Z, so a pull-down resistor may be necessary.




Notes:

I actually didn't know the name of this technique when I read about it some two years ago in Microchip's 8-pin PIC Tips 'n Tricks literature. By the way, here's the latest Tips 'n Tricks. It's actually a compilation of all existing TnTs. Great resource.