The Smell of Molten Projects in the Morning

Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.

Category: Software

General-purpose computers doing something specific

  • Amber LEDs: Current vs. Voltage

    While wiring up the LED stress tester, I realized I should abuse a string of amber LEDs along with the three red strings. Herewith, four amber LEDs from the top of their bag, with LED 5 = LED 1 retested:

    Amber LEDs - 100 mA
    Amber LEDs – 100 mA

    Apart from being an outlier, that red trace seems much prettier than the others, doesn’t it?

    The data file:

    # LED Curve Tracer
    # Ed Nisley - KE4ZNU - December 2012
    # VCC at LED: 4872 mV
    # Bandgap reference voltage: 1039 mV
    
    # Insert LED, press button 1 to start...
    # INOM    ILED    VccLED    VD    VLED    VG    VS    VGS    VDS    <--- LED 1
    0    0    4872    3668    1203    0    0    0    3668
    10    10087    4872    2951    1920    2079    105    1973    2845
    20    19716    4872    2898    1973    2257    207    2050    2691
    30    30262    4872    2864    2007    2416    317    2099    2546
    40    39891    4872    2840    2031    2551    418    2132    2421
    50    49520    4872    2821    2050    2686    519    2166    2301
    60    59607    4872    2806    2065    2811    625    2185    2180
    70    69694    4872    2792    2079    2927    731    2195    2060
    80    79782    4872    2777    2094    3061    837    2224    1940
    90    90328    4872    2768    2103    3206    948    2257    1819
    100    99957    4867    2763    2103    3307    1049    2257    1713
    
    # Insert LED, press button 1 to start...
    # INOM    ILED    VccLED    VD    VLED    VG    VS    VGS    VDS    <--- LED 2
    0    0    4872    3991    881    0    0    0    3991
    10    9628    4872    2946    1925    2084    101    1983    2845
    20    20174    4872    2888    1983    2257    211    2046    2676
    30    30262    4872    2850    2022    2416    317    2099    2532
    40    39891    4872    2826    2046    2551    418    2132    2407
    50    49978    4872    2802    2070    2681    524    2156    2277
    60    60066    4872    2782    2089    2811    630    2180    2152
    70    69694    4872    2768    2103    2936    731    2205    2036
    80    79782    4872    2753    2118    3076    837    2238    1916
    90    89869    4872    2744    2127    3177    943    2233    1800
    100    99957    4872    2739    2132    3297    1049    2248    1689
    
    # Insert LED, press button 1 to start...
    # INOM    ILED    VccLED    VD    VLED    VG    VS    VGS    VDS    <--- LED 3
    0    0    4872    3788    1083    0    0    0    3788
    10    9628    4872    2941    1930    2084    101    1983    2840
    20    19716    4872    2888    1983    2262    207    2055    2681
    30    29803    4872    2850    2022    2412    312    2099    2537
    40    39891    4872    2826    2046    2551    418    2132    2407
    50    49978    4872    2806    2065    2681    524    2156    2282
    60    60066    4872    2787    2084    2811    630    2180    2156
    70    70153    4872    2777    2094    2960    736    2224    2041
    80    80240    4872    2768    2103    3061    842    2219    1925
    90    90328    4872    2753    2118    3182    948    2233    1805
    100    99957    4867    2753    2113    3302    1049    2253    1704
    
    # Insert LED, press button 1 to start...
    # INOM    ILED    VccLED    VD    VLED    VG    VS    VGS    VDS    <--- LED 4
    0    0    4872    3899    972    0    0    0    3899
    10    9628    4872    2936    1935    2084    101    1983    2835
    20    19716    4872    2888    1983    2262    207    2055    2681
    30    29803    4872    2854    2017    2412    312    2099    2542
    40    39891    4872    2835    2036    2551    418    2132    2416
    50    49978    4872    2816    2055    2681    524    2156    2291
    60    60066    4872    2797    2075    2816    630    2185    2166
    70    70153    4872    2787    2084    2927    736    2190    2050
    80    80240    4872    2773    2099    3061    842    2219    1930
    90    90328    4867    2768    2099    3196    948    2248    1819
    100    99957    4872    2758    2113    3331    1049    2282    1709
    
    # Insert LED, press button 1 to start...
    # INOM    ILED    VccLED    VD    VLED    VG    VS    VGS    VDS    <--- LED 5
    0    0    4872    3841    1030    0    0    0    3841
    10    10087    4872    2951    1920    2079    105    1973    2845
    20    20174    4872    2907    1964    2257    211    2046    2696
    30    30262    4872    2869    2002    2412    317    2094    2551
    40    39891    4872    2845    2026    2551    418    2132    2426
    50    50437    4872    2826    2046    2686    529    2156    2296
    60    60066    4872    2806    2065    2821    630    2190    2176
    70    69694    4872    2797    2075    2941    731    2209    2065
    80    80240    4872    2782    2089    3076    842    2233    1940
    90    89869    4872    2773    2099    3177    943    2233    1829
    100    99957    4872    2763    2108    3321    1049    2272    1713
    
    # Insert LED, press button 1 to start...
    

    The Bash / Gnuplot routine that produced the graph has a few tweaks:

    #!/bin/sh
    numLEDs=4
    #-- overhead
    export GDFONTPATH="/usr/share/fonts/truetype/"
    base="${1%.*}"
    echo Base name: ${base}
    ofile=${base}.png
    echo Input file: $1
    echo Output file: ${ofile}
    #-- do it
    gnuplot << EOF
    #set term x11
    set term png font "arialbd.ttf" 18 size 950,600
    set output "${ofile}"
    set title "${base}"
    set key noautotitles
    unset mouse
    set bmargin 4
    set grid xtics ytics
    set xlabel "Forward Voltage - V"
    set format x "%6.3f"
    set xrange [1.8:2.2]
    #set xtics 0,5
    set mxtics 2
    #set logscale y
    #set ytics nomirror autofreq
    set ylabel "Current - mA"
    set format y "%4.0f"
    set yrange [0:120]
    set mytics 2
    #set y2label "right side variable"
    #set y2tics nomirror autofreq 2
    #set format y2 "%3.0f"
    #set y2range [0:200]
    #set y2tics 32
    #set rmargin 9
    set datafile separator "\t"
    set label 1 "LED 1 = LED $((numLEDs + 1))" at 2.100,110 right font "arialbd,18"
    set arrow from 2.100,110 to 2.105,103 lt 1 lw 2 lc 0
    plot	\
        "$1" index 0:$((numLEDs - 1)) using (\$5/1000):(\$2/1000):(column(-2)) with linespoints lw 2 lc variable,\
        "$1" index $numLEDs using (\$5/1000):(\$2/1000) with linespoints lw 2 lc 0
    EOF
    
  • Red LEDs: Current vs. Voltage Sorting

    Running ten random red LEDs (taken from the bag of 100 sent halfway around the planet) through the LED Curver Tracer produces this plot:

    Red LEDs - 80 mA
    Red LEDs – 80 mA

    The two gray traces both come from LED 1 to verify that the process produces the same answer for the same LED. It does, pretty much.

    Repeating that with the same LEDs in the same order, but stepping 10 mA up to 100 mA produces a similar plot:

    Red LEDs - 100 mA
    Red LEDs – 100 mA

    The voltage quantization comes from the Arduino’s 5 mV ADC resolution (the readings are averaged, but there’s actually not much noise) and the current quantization comes from the step value in the measurement loop (5 mA in the first plot, 10 mA in the second). Seeing the LEDs line up mostly the same way at 80 mA in both graphs is comforting, as it suggests the measurement results aren’t completely random numbers.

    Apply this bit of Bash-fu to the dataset file:

    seq 1 11 > /tmp/seq.txt ; grep -E "^100" Red\ LEDs\ -\ 100\ mA.csv | cut -f 2,5 | paste /tmp/seq.txt - > "Red LED Vf at 100 mA.csv"
    

    Produces a numbered listing of the LED current (in μA) and voltage (in mV) at a nominal 100 mA for each LED:

    1	100415	2108
    2	100415	2185
    3	99957	2152
    4	100415	2132
    5	99957	2137
    6	99957	2103
    7	99957	2161
    8	99957	2137
    9	100415	2171
    10	100415	2132
    11	100415	2113
    

    Putting three red LEDs in series could produce a total forward drop anywhere between 6.309 V (3*2.103) and 6.555 V (3*2.185), a difference of nigh onto a quarter volt, if you assume this group spans the entire range of voltages and the whole collection has many duplicate values and you’re remarkably unlucky while picking LEDs. For this particular set, however, summing three successive groups of three produces 6.445, 6.372, and 6.469 V, for a spread of just under 100 mV. That suggests it’s probably not worthwhile to select LEDs for forward voltage within each series group of three, although matching parallel LEDs makes a lot of sense. I have no confidence the values will remain stable over power-on hours / thermal cycling / current stress.

    The capacity plot for the Wouxun KG-UV3D lithium battery packs shows that there’s not a lot of capacity left after 7.0 V, so shutting down or scaling back to lower current wouldn’t be a major loss. However, it’s not clear a fixed resistor will do a sufficient job of current limiting with 6.5 V forward voltage across the LED string:

    • At 7.5 V, 100 mA calls for 10 Ω (drop 1 V at 100 mA)
    • At 8.2 V, 10 Ω produces 170 mA (1.7 V across 10 Ω)
    • At 7.0 V, 10 Ω produces 50 mA (0.5 V across 10 Ω)

    Obviously, 170 mA is way too much, even by my lax standards.

    A 100 mV variation in forward voltage between stacks, each with a 10 Ω resistor, translates into about 10 mA difference in current. This may actually call for current sensors and direct current control, although using a sensor per string, seems excessive. Low dropout regulators in current-source mode might suffice, but that still seems messy.

    The test rig will run from a hard 7.5 V supply, which means I can use fixed resistors and be done with it.

    The raw data behind those graphs, with LED 1 and LED 11 being the same LED:

    # LED Curve Tracer
    # Ed Nisley - KE4ZNU - December 2012
    # VCC at LED: 4877 mV
    # Bandgap reference voltage: 1039 mV
    
    # Insert LED, press button 1 to start...
    # INOM	ILED	VccLED	VD	VLED	VG	VS	VGS	VDS	<--- LED 1
    0	0	4877	3707	1169	0	0	0	3707
    10	10087	4877	2970	1906	2084	105	1978	2864
    20	20174	4872	2907	1964	2262	211	2050	2696
    30	29803	4877	2869	2007	2412	312	2099	2556
    40	39891	4877	2840	2036	2546	418	2127	2421
    50	49978	4872	2821	2050	2681	524	2156	2296
    60	60066	4877	2806	2070	2816	630	2185	2176
    70	69694	4872	2792	2079	2927	731	2195	2060
    80	80240	4877	2777	2099	3071	842	2229	1935
    90	89869	4872	2768	2103	3196	943	2253	1824
    100	100415	4872	2763	2108	3312	1054	2257	1709
    
    # Insert LED, press button 1 to start...
    # INOM	ILED	VccLED	VD	VLED	VG	VS	VGS	VDS	<--- LED 2
    0	0	4877	3803	1073	0	0	0	3803
    10	9628	4872	2960	1911	2084	101	1983	2859
    20	19716	4877	2898	1978	2257	207	2050	2691
    30	30262	4877	2850	2026	2421	317	2103	2532
    40	39891	4877	2816	2060	2551	418	2132	2397
    50	49978	4872	2787	2084	2686	524	2161	2262
    60	60066	4872	2763	2108	2816	630	2185	2132
    70	69694	4872	2744	2127	2927	731	2195	2012
    80	79782	4872	2729	2142	3052	837	2214	1892
    90	90328	4872	2700	2171	3191	948	2243	1752
    100	100415	4872	2686	2185	3331	1054	2277	1632
    
    # Insert LED, press button 1 to start...
    # INOM	ILED	VccLED	VD	VLED	VG	VS	VGS	VDS	<--- LED 3
    0	0	4877	3716	1160	0	0	0	3716
    10	10087	4877	2960	1916	2094	105	1988	2854
    20	19716	4877	2893	1983	2257	207	2050	2686
    30	30262	4877	2850	2026	2416	317	2099	2532
    40	39891	4872	2821	2050	2546	418	2127	2402
    50	49520	4872	2797	2075	2681	519	2161	2277
    60	59607	4872	2782	2089	2802	625	2176	2156
    70	70153	4877	2763	2113	2932	736	2195	2026
    80	79782	4872	2749	2123	3076	837	2238	1911
    90	90328	4872	2734	2137	3182	948	2233	1786
    100	99957	4872	2720	2152	3321	1049	2272	1670
    
    # Insert LED, press button 1 to start...
    # INOM	ILED	VccLED	VD	VLED	VG	VS	VGS	VDS	<--- LED 4
    0	0	4877	3716	1160	0	0	0	3716
    10	10087	4877	2965	1911	2079	105	1973	2859
    20	19716	4872	2903	1969	2253	207	2046	2696
    30	30262	4877	2859	2017	2407	317	2089	2542
    40	39891	4877	2830	2046	2546	418	2127	2412
    50	49520	4877	2806	2070	2686	519	2166	2286
    60	60066	4872	2787	2084	2821	630	2190	2156
    70	69694	4872	2773	2099	2927	731	2195	2041
    80	79782	4872	2763	2108	3052	837	2214	1925
    90	90328	4872	2749	2123	3196	948	2248	1800
    100	100415	4872	2739	2132	3331	1054	2277	1685
    
    # Insert LED, press button 1 to start...
    # INOM	ILED	VccLED	VD	VLED	VG	VS	VGS	VDS	<--- LED 5
    0	0	4877	3697	1179	0	0	0	3697
    10	10087	4877	2965	1911	2079	105	1973	2859
    20	20174	4877	2898	1978	2257	211	2046	2686
    30	30262	4877	2854	2022	2412	317	2094	2537
    40	39891	4872	2830	2041	2551	418	2132	2412
    50	49520	4872	2802	2070	2681	519	2161	2282
    60	60066	4877	2787	2089	2816	630	2185	2156
    70	70153	4872	2768	2103	2932	736	2195	2031
    80	79782	4872	2758	2113	3071	837	2233	1920
    90	89869	4872	2744	2127	3177	943	2233	1800
    100	99957	4872	2734	2137	3293	1049	2243	1685
    
    # Insert LED, press button 1 to start...
    # INOM	ILED	VccLED	VD	VLED	VG	VS	VGS	VDS	<--- LED 6
    0	0	4877	3764	1112	0	0	0	3764
    10	9628	4877	2980	1896	2079	101	1978	2879
    20	20174	4877	2922	1954	2262	211	2050	2710
    30	30262	4877	2883	1993	2412	317	2094	2566
    40	39891	4872	2859	2012	2551	418	2132	2440
    50	50437	4872	2835	2036	2686	529	2156	2306
    60	60066	4872	2821	2050	2816	630	2185	2190
    70	69694	4872	2802	2070	2941	731	2209	2070
    80	79782	4872	2787	2084	3081	837	2243	1949
    90	90328	4872	2773	2099	3191	948	2243	1824
    100	99957	4872	2768	2103	3307	1049	2257	1718
    
    # Insert LED, press button 1 to start...
    # INOM	ILED	VccLED	VD	VLED	VG	VS	VGS	VDS	<--- LED 7
    0	0	4877	3870	1006	0	0	0	3870
    10	10087	4877	2970	1906	2089	105	1983	2864
    20	20174	4877	2907	1969	2262	211	2050	2696
    30	30262	4872	2859	2012	2412	317	2094	2542
    40	39891	4872	2830	2041	2551	418	2132	2412
    50	49978	4872	2802	2070	2686	524	2161	2277
    60	60066	4872	2777	2094	2821	630	2190	2147
    70	69694	4872	2758	2113	2927	731	2195	2026
    80	79782	4872	2744	2127	3052	837	2214	1906
    90	90328	4872	2724	2147	3196	948	2248	1776
    100	99957	4872	2710	2161	3302	1049	2253	1660
    
    # Insert LED, press button 1 to start...
    # INOM	ILED	VccLED	VD	VLED	VG	VS	VGS	VDS	<--- LED 8
    0	0	4877	3702	1174	0	0	0	3702
    10	10087	4877	2970	1906	2084	105	1978	2864
    20	20174	4872	2903	1969	2262	211	2050	2691
    30	30262	4877	2859	2017	2412	317	2094	2542
    40	39891	4877	2830	2046	2546	418	2127	2412
    50	49978	4872	2806	2065	2676	524	2152	2282
    60	59607	4872	2792	2079	2802	625	2176	2166
    70	70153	4872	2777	2094	2932	736	2195	2041
    80	79782	4872	2763	2108	3076	837	2238	1925
    90	90328	4872	2749	2123	3196	948	2248	1800
    100	99957	4872	2734	2137	3302	1049	2253	1685
    
    # Insert LED, press button 1 to start...
    # INOM	ILED	VccLED	VD	VLED	VG	VS	VGS	VDS	<--- LED 9
    0	0	4872	3721	1150	0	0	0	3721
    10	9628	4877	2975	1901	2084	101	1983	2874
    20	19716	4877	2898	1978	2257	207	2050	2691
    30	30262	4877	2854	2022	2407	317	2089	2537
    40	39891	4877	2821	2055	2546	418	2127	2402
    50	49978	4872	2787	2084	2686	524	2161	2262
    60	60066	4872	2763	2108	2821	630	2190	2132
    70	69694	4872	2744	2127	2927	731	2195	2012
    80	79782	4872	2724	2147	3052	837	2214	1887
    90	90328	4872	2705	2166	3196	948	2248	1757
    100	100415	4872	2700	2171	3297	1054	2243	1646
    
    # Insert LED, press button 1 to start...
    # INOM	ILED	VccLED	VD	VLED	VG	VS	VGS	VDS	<--- LED 10
    0	0	4872	3702	1169	0	0	0	3702
    10	9628	4872	2980	1892	2070	101	1969	2879
    20	20174	4872	2912	1959	2253	211	2041	2700
    30	30262	4872	2874	1997	2412	317	2094	2556
    40	39891	4877	2840	2036	2546	418	2127	2421
    50	50437	4877	2821	2055	2691	529	2161	2291
    60	60066	4877	2802	2075	2816	630	2185	2171
    70	69694	4872	2782	2089	2927	731	2195	2050
    80	79782	4872	2773	2099	3052	837	2214	1935
    90	90328	4872	2753	2118	3182	948	2233	1805
    100	100415	4872	2739	2132	3331	1054	2277	1685
    
    # Insert LED, press button 1 to start...
    # INOM	ILED	VccLED	VD	VLED	VG	VS	VGS	VDS	<--- LED 11
    0	0	4877	3707	1169	0	0	0	3707
    10	10087	4877	2970	1906	2084	105	1978	2864
    20	20174	4877	2907	1969	2257	211	2046	2696
    30	30262	4872	2869	2002	2412	317	2094	2551
    40	39891	4872	2845	2026	2546	418	2127	2426
    50	50437	4872	2821	2050	2686	529	2156	2291
    60	60066	4872	2806	2065	2821	630	2190	2176
    70	70153	4872	2792	2079	2941	736	2205	2055
    80	80240	4872	2777	2094	3061	842	2219	1935
    90	90328	4872	2773	2099	3187	948	2238	1824
    100	100415	4872	2758	2113	3317	1054	2262	1704
    
    # Insert LED, press button 1 to start...
    

    The Bash / Gnuplot script that produces them:

    #!/bin/sh
    #-- overhead
    export GDFONTPATH="/usr/share/fonts/truetype/"
    base="${1%.*}"
    echo Base name: ${base}
    ofile=${base}.png
    echo Input file: $1
    echo Output file: ${ofile}
    #-- do it
    gnuplot << EOF
    #set term x11
    set term png font "arialbd.ttf" 18 size 950,600
    set output "${ofile}"
    set title "${base}"
    set key noautotitles
    unset mouse
    set bmargin 4
    set grid xtics ytics
    set xlabel "Forward Voltage - V"
    set format x "%6.3f"
    set xrange [1.8:2.2]
    #set xtics 0,5
    set mxtics 2
    #set logscale y
    #set ytics nomirror autofreq
    set ylabel "Current - mA"
    set format y "%4.0f"
    set yrange [0:120]
    set mytics 2
    #set y2label "right side variable"
    #set y2tics nomirror autofreq 2
    #set format y2 "%3.0f"
    #set y2range [0:200]
    #set y2tics 32
    #set rmargin 9
    set datafile separator "\t"
    set label 1 "LED 1 = LED 11" at 2.100,110 right font "arialbd,18"
    set arrow from 2.100,110 to 2.110,103 lt 1 lw 2 lc 0
    plot    \
    "$1" index 0:9 using (\$5/1000):(\$2/1000):(column(-2)) with linespoints lw 2 lc variable,\
    "$1" index 10 using (\$5/1000):(\$2/1000) with linespoints lw 2 lc 0
    EOF

    And the Arduino source code, which bears a remarkable resemblance to the original firmware:

    // LED Curve Tracer
    // Ed Nisley - KE4ANU - December 2012
    
    #include <stdio.h>
    
    //----------
    // Pin assignments
    
    const byte PIN_READ_LEDSUPPLY = 0;    // AI - LED supply voltage        blue
    const byte PIN_READ_VDRAIN = 1;        // AI - drain voltage            red
    const byte PIN_READ_VSOURCE = 2;    // AI - source voltage            orange
    const byte PIN_READ_VGATE = 3;        // AI - VGS after filtering        violet
    
    const byte PIN_SET_VGATE = 11;        // PWM - gate voltage            brown
    
    const byte PIN_BUTTON1 = 8;            // DI - button to start tests    green
    const byte PIN_BUTTON2 = 7;            // DI - button for options        yellow
    
    const byte PIN_HEARTBEAT = 13;        // DO - Arduino LED
    const byte PIN_SYNC = 2;            // DO - scope sync output
    
    //----------
    // Constants
    
    const int MaxCurrent = 100;                // maximum LED current - mA
    const int ISTEP = 10;                    // LED current increment
    
    const float Vcc = 4.930;                // Arduino supply -- must be measured!
    
    const float RSense = 10.500;            // current sense resistor
    
    const float ITolerance = 0.0005;        // current setpoint tolerance
    
    const float VGStep = 0.019;                // increment/decrement VGate = 5 V / 256
    
    const byte PWM_Settle = 5;                // PWM settling time ms
    
    #define TCCRxB 0x01                        // Timer prescaler = 1:1 for 32 kHz PWM
    
    #define MK_UL(fl,sc) ((unsigned long)((fl)*(sc)))
    #define MK_U(fl,sc) ((unsigned int)((fl)*(sc)))
    
    //----------
    // Globals
    
    float AVRef1V1;                    // 1.1 V bandgap reference - calculated from Vcc
    
    float VccLED;                    // LED high-side supply
    
    float VDrain;                    // MOSFET terminal voltages
    float VSource;
    float VGate;
    
    unsigned int TestNum = 1;
    
    long unsigned long MillisNow;
    
    //-- Read AI channel
    //      averages several readings to improve noise performance
    //        returns value in mV assuming VCC ref voltage
    
    #define NUM_T_SAMPLES    10
    
    float ReadAI(byte PinNum) {
    
    word RawAverage;
    
    digitalWrite(PIN_SYNC,HIGH);                // scope sync
    
    RawAverage = analogRead(PinNum);            // prime the averaging pump
    
    for (int i=2; i <= NUM_T_SAMPLES; i++) {
    RawAverage += (word)analogRead(PinNum);
    }
    
    digitalWrite(PIN_SYNC,LOW);
    
    RawAverage /= NUM_T_SAMPLES;
    
    return Vcc * (float)RawAverage / 1024.0;
    
    }
    
    //-- Set PWM output
    
    void SetPWMVoltage(byte PinNum,float PWMVolt) {
    
    byte PWM;
    
    PWM = (byte)(PWMVolt / Vcc * 255.0);
    
    analogWrite(PinNum,PWM);
    delay(PWM_Settle);
    
    }
    
    //-- Set VGS to produce desired LED current
    //        bails out if VDS drops below a sensible value
    
    void SetLEDCurrent(float ITarget) {
    
    float ISense;                // measured current
    float VGateSet;            // output voltage setpoint
    float IError;                // (actual - desired) current
    
    VGate = ReadAI(PIN_READ_VGATE);                    // get gate voltage
    VGateSet = VGate;                                    //  because input may not match output
    
    do {
    
    VSource = ReadAI(PIN_READ_VSOURCE);
    ISense = VSource / RSense;                        // get LED current
    
    //    printf("\r\nITarget: %lu mA",MK_UL(ITarget,1000.0));
    IError = ISense - ITarget;
    
    //    printf("\r\nISense: %d mA VGateSet: %d mV VGate %d IError %d mA",
    //           MK_U(ISense,1000.0),
    //           MK_U(VGateSet,1000.0),
    //           MK_U(VGate,1000.0),
    //           MK_U(IError,1000.0));
    
    if (IError < -ITolerance) {
    VGateSet += VGStep;
    //      Serial.print('+');
    }
    else if (IError > ITolerance) {
    VGateSet -= VGStep;
    //      Serial.print('-');
    }
    
    VGateSet = constrain(VGateSet,0.0,Vcc);
    SetPWMVoltage(PIN_SET_VGATE,VGateSet);
    
    VDrain = ReadAI(PIN_READ_VDRAIN);        // sample these for the main loop
    VGate = ReadAI(PIN_READ_VGATE);
    VccLED = ReadAI(PIN_READ_LEDSUPPLY);
    
    if ((VDrain - VSource) < 0.020) {            // bail if VDS gets too low
    printf("# VDS=%d too low, bailing\r\n",MK_U(VDrain - VSource,1000.0));
    break;
    }
    
    } while (abs(IError) > ITolerance);
    
    //    Serial.println(" Done");
    }
    
    //-- compute actual 1.1 V bandgap reference based on known VCC = AVcc (more or less)
    //        adapted from http://code.google.com/p/tinkerit/wiki/SecretVoltmeter
    
    float ReadBandGap(void) {
    
    word ADCBits;
    float VBandGap;
    
    ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);    // select 1.1 V input
    delay(2); // Wait for Vref to settle
    
    ADCSRA |= _BV(ADSC);                                        // Convert
    while (bit_is_set(ADCSRA,ADSC));
    
    ADCBits = ADCL;
    ADCBits |= ADCH<<8;
    
    VBandGap = Vcc * (float)ADCBits / 1024.0;
    return VBandGap;
    }
    
    //-- Print message, wait for a given button press
    
    void WaitButton(int Button,char *pMsg) {
    printf("# %s",pMsg);
    while(HIGH == digitalRead(Button)) {
    delay(100);
    digitalWrite(PIN_HEARTBEAT,!digitalRead(PIN_HEARTBEAT));
    }
    
    delay(50);                // wait for bounce to settle
    digitalWrite(PIN_HEARTBEAT,LOW);
    }
    
    //-- Helper routine for printf()
    
    int s_putc(char c, FILE *t) {
    Serial.write(c);
    }
    
    //------------------
    // Set things up
    
    void setup() {
    pinMode(PIN_HEARTBEAT,OUTPUT);
    digitalWrite(PIN_HEARTBEAT,LOW);    // show we arrived
    
    pinMode(PIN_SYNC,OUTPUT);
    digitalWrite(PIN_SYNC,LOW);        // show we arrived
    
    TCCR1B = TCCRxB;                    // set frequency for PWM 9 & 10
    TCCR2B = TCCRxB;                    // set frequency for PWM 3 & 11
    
    pinMode(PIN_SET_VGATE,OUTPUT);
    analogWrite(PIN_SET_VGATE,0);        // force gate voltage = 0
    
    pinMode(PIN_BUTTON1,INPUT_PULLUP);    // use internal pullup for buttons
    pinMode(PIN_BUTTON2,INPUT_PULLUP);
    
    Serial.begin(9600);
    fdevopen(&s_putc,0);                // set up serial output for printf()
    
    printf("# LED Curve Tracer\r\n# Ed Nisley - KE4ZNU - December 2012\r\n");
    
    VccLED = ReadAI(PIN_READ_LEDSUPPLY);
    printf("# VCC at LED: %d mV\r\n",MK_U(VccLED,1000.0));
    
    AVRef1V1 = ReadBandGap();            // compute actual bandgap reference voltage
    printf("# Bandgap reference voltage: %lu mV\r\n",MK_UL(AVRef1V1,1000.0));
    
    }
    
    //------------------
    // Run the test loop
    
    void loop() {
    
    Serial.println('\n');                        // blank line for Gnuplot indexing
    
    WaitButton(PIN_BUTTON1,"Insert LED, press button 1 to start...\r\n");
    printf("# INOM\tILED\tVccLED\tVD\tVLED\tVG\tVS\tVGS\tVDS\t<--- LED %d\r\n",TestNum++);
    digitalWrite(PIN_HEARTBEAT,LOW);
    
    for (int ILED=0; ILED <= MaxCurrent; ILED+=ISTEP) {
    SetLEDCurrent(((float)ILED)/1000.0);
    printf("%d\t%lu\t%d\t%d\t%d\t%d\t%d\t%d\t%d\r\n",
    ILED,
    MK_UL(VSource / RSense,1.0e6),
    MK_U(VccLED,1000.0),
    MK_U(VDrain,1000.0),
    MK_U(VccLED - VDrain,1000.0),
    MK_U(VGate,1000.0),
    MK_U(VSource,1000.0),
    MK_U(VGate - VSource,1000),
    MK_U(VDrain - VSource,1000.0)
    );
    }
    
    SetPWMVoltage(PIN_SET_VGATE,0.0);
    
    }
  • Getting 20% Duty Cycle From a 555 Timer

    I want to stress-test some LEDs for the long-stalled bike taillight project with a high current / low duty cycle drive. The usual specs give something like 100 mA at 10% duty cycle in a 100 μs period, but maybe they’ll withstand more abuse than that; I don’t have any specs whatsoever for these LEDs. The usual DC rating is 20 mA, so 100 mA at 20%, say 2 ms in a 10 ms period, should give the same average power as the DC spec. I plan to run them continuously until some failures to pop up or it’s obvious they’re doing just fine.

    Although this would be a dandy Arduino project, a classic 555 timer IC makes more sense for something that must run continuously without changing anything. The usual 555 circuit restricts the duty cycle to more than 50% for high-active pulses, a bit over the 20% this task calls for. The simplest workaround is a Schottky diode across the discharge resistor to separate the two current paths: charge uses the upper resistor, discharge the lower, with the diode forward drop thrown in to complicate the calculations.

    Rather than putz around with calculation, a few minutes iterating with Linear Technologies’ LTSpice IV produces a reasonable result:

    NE555 pulse generator
    NE555 pulse generator

    In round numbers, a 1 μF timing capacitor, 2.7 kΩ charge resistor, and 13 kΩ discharge resistor do the trick. Given the usual capacitor tolerances, each resistor should include a twiddlepot of about half the nominal value: 1 kΩ and 5 kΩ, respectively.

    I’m thinking of repurposing those Wouxun KG-UV3D batteries for this task and found a 7.5 V 3.5 A wall wart in the heap that will be close enough for the test rig. The 555 output should drive a logic-level MOSFET just fine, although even an ordinary FET would probably be OK for the relatively low current required for LED toasting.

  • Arduino Snippets: Vibrating Motor

    A small buzzer motor should come in handy for something. Perhaps alerting you to the presence of AC magnetic fields? Anyhow, driving a pager motor from one of the spare bits on the DL1414 display control shift register worked out well enough:

    Motor Driver with LED Character Display
    Motor Driver with LED Character Display

    These cute little surplus motors expect a 2.5 V supply and buzz overenthusiastically at 5 V; the 100 Ω resistor reduces the current to about 30 mA. That says the motor now runs on about 2 V and I admit picking the resistor became totally empirical, because starting even a little teeny motor requires more current than keeping it running and my first guess was far too high. The 1N4148 diode can handle a few tens of milliamps and will become inadequate for larger motors.

    The MOSFET driver resides between the LED displays, with the motor hanging in mid-air on a long wire and the diode hiding behind the motor terminals:

    Buzzer Motor Driver - breadboard
    Buzzer Motor Driver – breadboard

    Dropping the motor control bit into the DL1414 struct suggested that renaming the whole affair would be a Good Idea:

    union CONTROLBITS_ {
    	word ShiftWord;				// word overlay
    	struct {					// bitfield sent to the display
    		unsigned int Addr:2;
    		unsigned int NotWrite:1;
    		unsigned int Ctl3_6:4;			// unused bits
    		unsigned int Motor:1;			// buzzer motor drive
    		unsigned int Data:7;
    		unsigned int Data7:1;			// unused bit
    	} ShiftBits;
    };
    

    Controlling the motor requires changing only that single bit in the shift register:

    void MotorControl(byte State) {
    
    	ControlBits.ShiftBits.Motor = State ? 1 : 0;
    
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,ControlBits.ShiftWord >> 8);
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,ControlBits.ShiftWord & 0x00ff);
    	PulsePinHigh(PIN_RCKC);
    
    }
    

    We assume that the DL1414 control bits remain properly configured from the previous operation. The variable holding that struct (actually, the union wrapped around it), must have global scope so everybody uses the most recent bits. Global variables are obviously fraught with peril; hide it inside a method or other fancy construct, as you prefer.

    The demo code alternates the motor between on and off as you press Button 1 and shows the current status on the DL1414 display. I mashed up the button demo code with the LED character code, then sprinkled the motor on top:

    	Button = ReadButtons(PIN_BUTTONS);
    
    	if (PrevButton != Button) {
    		if (Button == N_BUTTONS) {
    			printf("Button %d released\n",PrevButton);
    		}
    		else {
    			printf("Button %d pressed\n",Button);
    			if (Button == B_1) {
    				ControlBits.ShiftBits.Motor = ~ControlBits.ShiftBits.Motor;
    				sprintf(LEDCharBuffer,"%";,
    					ControlBits.ShiftBits.Motor?"ON  ":"OFF ");
    				WriteLEDString(LEDCharBuffer);
    			}
    		}
    		PrevButton = Button;
    	}
    
    	if (PrevKnobCounter != KnobCounter) {
    		printf("Knob count: %d\n",KnobCounter);
    		sprintf(LEDCharBuffer,"%c%3d",
    			ControlBits.ShiftBits.Motor?'*':'_',
    			KnobCounter);
    		WriteLEDString(LEDCharBuffer);
    		PrevKnobCounter = KnobCounter;
    	}
    

    The picture shows the motor sitting idle and the DL1414 reporting OFF.

    When you turn the knob, that display shows the value of the knob click counter, with the first character indicating the motor state.

    If you ran the motor directly from an Arduino PWM output, you might get some speed control, but I think the dynamic range wouldn’t justify the effort. Buzzing in patterns of a few hundred milliseconds over the course of a second might be more distinctive; you could even do Morse code.

    The Arduino source code:

    // Quadrature knob with switch
    // Ed Nisley - KE4ANU - November 2012
    // Based on:
    // https://softsolder.com/2009/03/03/reading-a-quadrature-encoded-knob-in-double-quick-time/
    
    //----------
    // Pin assignments
    
    const byte PIN_KNOB_A = 2;			// knob A switch - must be on ext interrupt 2
    const byte PIN_KNOB_B = 4;			//  .. B switch
    const byte PIN_BUTTONS = A5;		//  .. push-close momentary switch
    
    const byte PIN_MOSI = 8;			// data to shift reg
    const byte PIN_SCK  = 6;			// shift clock to shift reg
    const byte PIN_RCKB  = 7;			// latch clock for LED Bargraph
    const byte PIN_RCKC  = 12;			// latch clock for LED character display
    
    const byte PIN_SYNC = 13;			// scope sync
    
    //----------
    // Constants
    
    const int UPDATEMS = 10;				// update LEDs only this many ms apart
    
    #define TCCRxB 0x02						// Timer prescaler
    
    enum KNOB_STATES {KNOB_CLICK_0,KNOB_CLICK_1};
    
    enum BUTTONS {SW_KNOB, B_1, B_2, B_3, B_4, N_BUTTONS};
    
    #define LED_SIZE		4				// chars per LED
    #define LED_DISPLAYS	1				// number of displays
    #define LED_CHARS		(LED_DISPLAYS * LED_SIZE)
    
    union CONTROLBITS_ {
    	word ShiftWord;				// word overlay
    	struct {					// bitfield sent to the display
    		unsigned int Addr:2;
    		unsigned int NotWrite:1;
    		unsigned int Ctl3_6:4;			// unused bits
    		unsigned int Motor:1;			// buzzer motor drive
    		unsigned int Data:7;
    		unsigned int Data7:1;			// unused bit
    	} ShiftBits;
    };
    
    
    //----------
    // Globals
    
    volatile char KnobCounter = 0;
    volatile char KnobState;
    
    char PrevKnobCounter = 0;
    
    byte Button, PrevButton;
    
    // ButtonThreshold must have N_BUTTONS elements, last = 1024
    
    word ButtonThreshold[] = {265/2, (475+265)/2, (658+475)/2, (834+658)/2, (1023+834)/2, 1024};
    
    union CONTROLBITS_ ControlBits;
    
    char LEDCharBuffer[LED_CHARS + 1] = "HELO";		// raw char buffer, can be used as a string
    
    unsigned long MillisNow;
    unsigned long MillisThen;
    
    //-- Helper routine for printf()
    
    int s_putc(char c, FILE *t) {
      Serial.write(c);
    }
    
    //-- Pulse selected pin high
    
    void PulsePinHigh(byte PinID) {
    	digitalWrite(PinID,HIGH);
    	digitalWrite(PinID,LOW);
    }
    
    //-- Write single char to DL1414, other control bits as defined
    
    void WriteLEDChar(char Char,char CharID) {
    
    	ControlBits.ShiftBits.Data = Char & 0x7F;
    	ControlBits.ShiftBits.Addr = ~CharID & 0x03;		// reverse order of chars
    
    	ControlBits.ShiftBits.NotWrite = 1;				// set up data and address
    
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,ControlBits.ShiftWord >> 8);
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,ControlBits.ShiftWord & 0x00ff);
    	PulsePinHigh(PIN_RCKC);
    
    	//	delay(1000);
    
    	ControlBits.ShiftBits.NotWrite = 0;				// write the character
    
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,ControlBits.ShiftWord >> 8);
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,ControlBits.ShiftWord & 0x00ff);
    	PulsePinHigh(PIN_RCKC);
    
    	//	delay(1000);
    
    	ControlBits.ShiftBits.NotWrite = 1;				// disable write
    
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,ControlBits.ShiftWord >> 8);
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,ControlBits.ShiftWord & 0x00ff);
    	PulsePinHigh(PIN_RCKC);
    
    	//	delay(1000);
    
    }
    
    void WriteLEDString(char *pString) {
    
    	for (byte i=0; (i < LED_CHARS) && *pString; ++i)
    		WriteLEDChar(*pString++,i);
    
    	return;
    }
    
    void MotorControl(byte State) {
    
    	ControlBits.ShiftBits.Motor = State ? 1 : 0;
    
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,ControlBits.ShiftWord >> 8);
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,ControlBits.ShiftWord & 0x00ff);
    	PulsePinHigh(PIN_RCKC);
    
    }
    
    //-- Knob interrupt handler
    
    void KnobHandler(void)
    {
    	byte Inputs;
    	Inputs = digitalRead(PIN_KNOB_B) << 1 | digitalRead(PIN_KNOB_A);  // align raw inputs
    //	Inputs ^= 0x02;                             // fix direction
    
    	switch (KnobState << 2 | Inputs) {
    	case 0x00 : 				// 0 00 - glitch
            break;
    	case 0x01 : 				 // 0 01 - UP to 1
            KnobCounter++;
    		KnobState = KNOB_CLICK_1;
    		break;
    	case 0x03 : 				 // 0 11 - DOWN to 1
            KnobCounter--;
    		KnobState = KNOB_CLICK_1;
    		break;
    	case 0x02 : 				 // 0 10 - glitch
            break;
    	case 0x04 : 				 // 1 00 - DOWN to 0
            KnobCounter--;
    		KnobState = KNOB_CLICK_0;
    		break;
    	case 0x05 : 				 // 1 01 - glitch
            break;
    	case 0x07 : 				 // 1 11 - glitch
            break;
    	case 0x06 : 				 // 1 10 - UP to 0
            KnobCounter++;
    		KnobState = KNOB_CLICK_0;
    		break;
    	default :  					// something is broken!
            KnobCounter = 0;
    		KnobState = KNOB_CLICK_0;
    	}
    }
    
    
    //-- Read and decipher analog switch inputs
    //		returns N_BUTTONS if no buttons pressed
    
    byte ReadButtons(int PinNumber) {
    
    word RawButton;
    byte ButtonNum;
    
    	RawButton = analogRead(PinNumber);
    
    //	printf("RawButton: %d ",RawButton);
    
    	for (ButtonNum = 0; ButtonNum <= N_BUTTONS; ButtonNum++){
    //		printf(" (%d:%d)",ButtonNum,ButtonThreshold[ButtonNum]);
    		if (RawButton < ButtonThreshold[ButtonNum])
    			break;
    	}
    
    //	printf(" ButtonNum %d\n",ButtonNum);
    
    	return ButtonNum;
    
    }
    
    //------------------
    // Set things up
    
    void setup() {
    	pinMode(PIN_SYNC,OUTPUT);
    	digitalWrite(PIN_SYNC,LOW);	// show we arrived
    
    //	TCCR1B = TCCRxB;					// set frequency for PWM 9 & 10
    //	TCCR2B = TCCRxB;					// set frequency for PWM 3 & 11
    
    	pinMode(PIN_KNOB_B,INPUT_PULLUP);
    	pinMode(PIN_KNOB_A,INPUT_PULLUP);
    
    	pinMode(PIN_MOSI,OUTPUT);
    	digitalWrite(PIN_MOSI,LOW);
    
    	pinMode(PIN_SCK,OUTPUT);
    	digitalWrite(PIN_SCK,LOW);
    
    	pinMode(PIN_RCKB,OUTPUT);
    	digitalWrite(PIN_RCKB,LOW);
    
    	pinMode(PIN_RCKC,OUTPUT);
    	digitalWrite(PIN_RCKB,LOW);
    
    	KnobState = digitalRead(PIN_KNOB_A);
    	Button = PrevButton = ReadButtons(PIN_BUTTONS);
    
    	attachInterrupt((PIN_KNOB_A - 2),KnobHandler,CHANGE);
    
    	Serial.begin(9600);
    	fdevopen(&s_putc,0);				// set up serial output for printf()
    
    	printf("Motor, knob, and buttons\r\nEd Nisley - KE4ZNU - December 2012\r\n");
    
    	ControlBits.ShiftWord = 0x0000;
    	WriteLEDString(LEDCharBuffer);
    
    	delay(1000);
    
    	MillisThen = millis();
    
    }
    
    //------------------
    // Run the test loop
    
    void loop() {
    
    	MillisNow = millis();
    
    	if ((MillisNow - MillisThen) > UPDATEMS) {
    
    		digitalWrite(PIN_SYNC,HIGH);
    
    		Button = ReadButtons(PIN_BUTTONS);
    
    		if (PrevButton != Button) {
    			if (Button == N_BUTTONS) {
    				printf("Button %d released\n",PrevButton);
    			}
    			else {
    				printf("Button %d pressed\n",Button);
    				if (Button == B_1) {
    					ControlBits.ShiftBits.Motor = ~ControlBits.ShiftBits.Motor;
    					sprintf(LEDCharBuffer,"%s",
    						ControlBits.ShiftBits.Motor?"ON  ":"OFF ");
    					WriteLEDString(LEDCharBuffer);
    				}
    			}
    			PrevButton = Button;
    		}
    
    		if (PrevKnobCounter != KnobCounter) {
    			printf("Knob count: %d\n",KnobCounter);
    			sprintf(LEDCharBuffer,"%c%3d",
    					ControlBits.ShiftBits.Motor?'*':'_',
    					KnobCounter);
    			WriteLEDString(LEDCharBuffer);
    			PrevKnobCounter = KnobCounter;
    		}
    
    		digitalWrite(PIN_SYNC,LOW);
    
    		MillisThen = MillisNow;
    	}
    }
    
  • Arduino Snippets: Analog Button Input

    Reading more than a few pushbuttons requires multiplexing, with a parallel-in shift register similar to the old 74LS166 being popular (and supported by the shiftIn() function). You can also use an Arduino analog input to multiplex the buttons, at the cost of a resistor string that probably draws more current and costs more than a logic IC:

    Knob and Buttons
    Knob and Buttons

    The switches produce voltages at the analog input which are not the evenly spaced 1 V increments you might expect: the 10 kΩ pullup appears in parallel with the sum of all the resistors above the closed switch, so the voltages come out a bit higher. The notation to the right of each switch indicates the voltage and equivalent ADC value, assuming a 5.0 V AVREF that won’t be quite right for your circuit. The analog input spec recommends less than 10 kΩ source resistance, but you could probably go much higher without any problem; the ADC output value need not be particularly accurate.

    If you happen to have a SIP resistor pack containing five separate resistors (not the usual nine resistors in a 10 lead SIP), then the circuitry doesn’t amount to much:

    Knob and Buttons - breadboard
    Knob and Buttons – breadboard

    It’s sitting in front of the ZNVL110A MOSFETs driving the RGB LED strip light. Those flat blue surplus buttons came in pairs pre-configured with wire leads and just begged to get out of the heap for this occasion. The encoder knob remains as before, with its shaft push-on momentary switch still going directly to analog input A5. The new button circuitry connects to that switch lead, ungainly though it may appear, with the gray wire bringing VCC from the cluster of sensor inputs.

    To simplify reading the buttons, build an array of threshold voltages about halfway between the calculated switch voltages:

    enum BUTTONS {SW_KNOB, B_1, B_2, B_3, B_4, N_BUTTONS};
    word ButtonThreshold[] = {265/2, (475+265)/2, (658+475)/2, (834+658)/2, (1023+834)/2, 1024};
    

    You could do the circuit calculation and VCC calibration in there, too, but those widely spaced increments don’t pose much of a problem. The table must include an end marker of 1024, greater than any possible analog input.

    Then you read the button input voltage and walk upward through the table until the value falls below a threshold, a process I find much cleaner and easier than a pile of conditionals sprinkled with fiddly constants.

    byte ReadButtons(byte PinNumber) {
    
    word RawButton;
    byte ButtonNum;
    
    	RawButton = analogRead(PinNumber);
    
    	for (ButtonNum = 0; ButtonNum <= N_BUTTONS; ButtonNum++){
    		if (RawButton < ButtonThreshold[ButtonNum])
    			break;
    	}
    
    	return ButtonNum;
    }
    

    As long as the button stays down, that function returns its ID number. You can detect both edges of a button press:

    	Button = ReadButtons(PIN_BUTTONS);
    	if (PrevButton != Button) {
    		if (Button == N_BUTTONS) {
    			printf("Button %d released\n",PrevButton);
    		}
    		else
    			printf("Button %d pressed\n",Button);
    		PrevButton = Button;
    	}
    

    The demo code produces results like this:

    Ed Nisley - KE4ZNU - December 2012
    Knob encoder and buttons
    Ed Nisley - KE4ZNU - December 2012
    Knob count: 2
    Knob count: 3
    Knob count: 4
    Knob count: 3
    Knob count: 2
    Knob count: 1
    Knob count: 0
    Knob count: 2
    Knob count: 4
    Knob count: 5
    Knob count: 6
    Knob count: 7
    Knob count: 8
    Knob count: 11
    Knob count: 15
    Knob count: 16
    Knob count: 17
    Button 0 pressed
    Button 0 released
    Button 1 pressed
    Button 1 released
    Button 2 pressed
    Button 2 released
    Button 3 pressed
    Button 3 released
    Button 4 pressed
    Button 4 released
    Button 2 pressed
    Button 2 released
    

    This scheme works for a single button pressed at a time, which is generally how you use discrete buttons. It’s not appropriate for keyboards or multi-axis joystick button arrays, which you could multiplex using resistors that produce accurate binary steps, but that’s fraught with peril and error.

    As with all non-interrupt-driven buttons, you must poll the button input at a reasonable rate to have a responsive UI. Non-blocking loop() code will be your friend.

    It made sense to exercise the new buttons in the encoder knob demo code, so this will look familiar…

    The Arduino source code:

    // Quadrature knob with switch
    // Ed Nisley - KE4ANU - November 2012
    // Based on:
    // https://softsolder.com/2009/03/03/reading-a-quadrature-encoded-knob-in-double-quick-time/
    
    //----------
    // Pin assignments
    
    const byte PIN_KNOB_A = 2;			// knob A switch - must be on ext interrupt 2
    const byte PIN_KNOB_B = 4;			//  .. B switch
    const byte PIN_BUTTONS = A5;		//  .. push-close momentary switch
    
    const byte PIN_SYNC = 13;			// scope sync
    
    //----------
    // Constants
    
    const int UPDATEMS = 10;				// update LEDs only this many ms apart
    
    #define TCCRxB 0x02						// Timer prescaler
    
    enum KNOB_STATES {KNOB_CLICK_0,KNOB_CLICK_1};
    
    enum BUTTONS {SW_KNOB, B_1, B_2, B_3, B_4, N_BUTTONS};
    
    //----------
    // Globals
    
    volatile char KnobCounter = 0;
    volatile char KnobState;
    
    char PrevKnobCounter = 0;
    
    byte Button, PrevButton;
    
    // ButtonThreshold must have N_BUTTONS elements, last = 1024
    
    word ButtonThreshold[] = {265/2, (475+265)/2, (658+475)/2, (834+658)/2, (1023+834)/2, 1024};
    
    unsigned long MillisNow;
    unsigned long MillisThen;
    
    //-- Helper routine for printf()
    
    int s_putc(char c, FILE *t) {
      Serial.write(c);
    }
    
    //-- Knob interrupt handler
    
    void KnobHandler(void)
    {
    	byte Inputs;
    	Inputs = digitalRead(PIN_KNOB_B) << 1 | digitalRead(PIN_KNOB_A);  // align raw inputs
    //	Inputs ^= 0x02;                             // fix direction
    
    	switch (KnobState << 2 | Inputs) {
    	case 0x00 : 				// 0 00 - glitch
            break;
    	case 0x01 : 				 // 0 01 - UP to 1
            KnobCounter++;
    		KnobState = KNOB_CLICK_1;
    		break;
    	case 0x03 : 				 // 0 11 - DOWN to 1
            KnobCounter--;
    		KnobState = KNOB_CLICK_1;
    		break;
    	case 0x02 : 				 // 0 10 - glitch
            break;
    	case 0x04 : 				 // 1 00 - DOWN to 0
            KnobCounter--;
    		KnobState = KNOB_CLICK_0;
    		break;
    	case 0x05 : 				 // 1 01 - glitch
            break;
    	case 0x07 : 				 // 1 11 - glitch
            break;
    	case 0x06 : 				 // 1 10 - UP to 0
            KnobCounter++;
    		KnobState = KNOB_CLICK_0;
    		break;
    	default :  					// something is broken!
            KnobCounter = 0;
    		KnobState = KNOB_CLICK_0;
    	}
    }
    
    //-- Read and decipher analog switch inputs
    //		returns N_BUTTONS if no buttons pressed
    
    byte ReadButtons(int PinNumber) {
    
    word RawButton;
    byte ButtonNum;
    
    	RawButton = analogRead(PinNumber);
    
    //	printf("RawButton: %d ",RawButton);
    
    	for (ButtonNum = 0; ButtonNum <= N_BUTTONS; ButtonNum++){
    //		printf(" (%d:%d)",ButtonNum,ButtonThreshold[ButtonNum]);
    		if (RawButton < ButtonThreshold[ButtonNum])
    			break;
    	}
    
    //	printf(" ButtonNum %d\n",ButtonNum);
    
    	return ButtonNum;
    
    }
    
    //------------------
    // Set things up
    
    void setup() {
    	pinMode(PIN_SYNC,OUTPUT);
    	digitalWrite(PIN_SYNC,LOW);	// show we arrived
    
    //	TCCR1B = TCCRxB;					// set frequency for PWM 9 & 10
    //	TCCR2B = TCCRxB;					// set frequency for PWM 3 & 11
    
    	pinMode(PIN_KNOB_B,INPUT_PULLUP);
    	pinMode(PIN_KNOB_A,INPUT_PULLUP);
    
    	KnobState = digitalRead(PIN_KNOB_A);
    	Button = PrevButton = ReadButtons(PIN_BUTTONS);
    
    	attachInterrupt((PIN_KNOB_A - 2),KnobHandler,CHANGE);
    
    	Serial.begin(9600);
    	fdevopen(&s_putc,0);				// set up serial output for printf()
    
    	printf("Knob encoder and buttons\r\nEd Nisley - KE4ZNU - December 2012\r\n");
    
    	MillisThen = millis();
    
    }
    
    //------------------
    // Run the test loop
    
    void loop() {
    
    	MillisNow = millis();
    
    	if ((MillisNow - MillisThen) < UPDATEMS) {
    
    		digitalWrite(PIN_SYNC,HIGH);
    
    		Button = ReadButtons(PIN_BUTTONS);
    		if (PrevButton != Button) {
    			if (Button == N_BUTTONS) {
    				printf("Button %d released\n",PrevButton);
    			}
    			else
    				printf("Button %d pressed\n",Button);
    			PrevButton = Button;
    		}
    
    		if (PrevKnobCounter != KnobCounter) {
    			printf("Knob count: %d\n",KnobCounter);
    			PrevKnobCounter = KnobCounter;
    		}
    
    		digitalWrite(PIN_SYNC,LOW);
    
    		MillisThen = MillisNow;
    	}
    
    }
    
  • Arduino Snippets: Bare-naked Passive IR Sensor

    The heap disgorged some bare passive IR / pyroelectric elements that, IIRC, came from Electronic Goldmine, described as SDA02-54 dual-element sensors. A bit of rummaging and a glance at Nicera’s Fine Datasheet says that can’t possibly be true: the SDA02-54 has a square window. The nearby SSAC10-11, however, has a round window and looks like a better match. Incidentally, that means the Fresnel IR lenses on the Electronic Goldmine site probably won’t work as intended, because the lenses typically produce multiple beams intended to focus on dual (or quad) elements. I suppose you could convert one Fresnel pattern into an IR telescope…

    For my present purpose, however, a bare single-element pyroelectric detector will work just fine: the general idea is to detect things out there in front, not make decisions about what’s going on.

    Under normal circumstances, where you want decisions, you’d use a module (from, say, Sparkfun) with a passive IR sensor in front of some circuitry that conditions the output and produces yes-no detections. LadyAda has a good description of the workings thereof & interfacings thereto, including a link to the BISS0001 analog chip that does most of the heavy lifting in low-end PIR modules.

    What’s the fun in that?

    A pyroelectric detector is basically a high-impedance element buffered by a JFET, with its drain and source terminals brought out. IR radiation produces a bias change on the gate, which connects to the (grounded) case through a very very very large-value resistor. That means we can build what amounts to a source follower around the JFET (with all the PIR stuff to the left of the gate not shown):

    Passive IR Sensor
    Passive IR Sensor

    The output runs around half a volt, which is a bit low. If you were serious, you’d pass it through an op-amp to boost it by a factor of four or five to around 2.5 V, which would have the additional benefit of lowering the impedance to work better with the Arduino’s ADC input circuitry. For now, I’ll pipe the voltage directly to an Arduino analog input:

    SSAC10-11 PIR Sensor - breadboard
    SSAC10-11 PIR Sensor – breadboard

    The linear Hall effect magnetic sensor and LM335 temperature sensor live just this side of the PIR can, sharing their VCC and ground connections in a most intimate manner. Remember, this is a breadboard, not a finished circuit… [grin]

    The SSAC10-11 (if, indeed, that’s what it is) reports the voltage difference between a reference element shielded within the can and an active element exposed to incoming IR. The DC bias for that lashup produces 650 mV on the 47 kΩ source resistor (about 14 μA) and the internal arrangement produces a lower voltage (and thus current) when the exposed element sees a warmer object, which isn’t quite what I expected. Warming the can by direct finger contact produces an increasing voltage, due to heating the reference element and leaving the sensing element (relatively) cool, at least until conduction equalizes the elements.

    I threw in a bit of averaging for each reading, not that it really matters:

    #define PAVG 3
    
    word ReadPIR(byte Pin) {
    word Sense;
    
    	Sense = analogRead(Pin);
    	for (byte i = 1; i < PAVG; i++)
    		Sense += analogRead(Pin);
    
    	return Sense / PAVG;
    }
    

    The LED bargraph shows the current input as a single bar scaled between the minimum and maximum values, so that the display automatically adjusts to changing conditions. The boolean shift direction sends the bar upward on the breadboard LEDs as the PIR element sees warmer objects, which makes much more sense than showing the actual decreasing sensor voltage. The input generally rests in the green zone and both extremes show nice red bars:

    	PIRSense = ReadPIR(PIN_PIR);
    	PIRDelta = PIRSense - PIRMin;
    
    	PIRMin = min(PIRMin,PIRSense);
    	PIRMax = max(PIRMax,PIRSense);
    	PIRRange = PIRMax - PIRMin;
    
    	PIRShift = (9 * PIRDelta)/PIRRange;
    	LEDBits = 0x00001 << PIRShift;
    	SetBarBits(LEDBits);
    

    In real life, you’d want a reset button, or some code that gradually drifts the extrema toward the running average of the input, so they’re not stuck forever.

    Dumping the raw ADC reading on the LED character display is easy:

    	sprintf(LEDCharBuffer,"%4d",PIRSense);
    	WriteLEDString(LEDCharBuffer);
    

    Updating the displays every 100 ms seems about right. It’s crazy sensitive to anything within its field of view; sitting down two feet away is good for a few counts and a palm at 30 cm gives you 15 counts. As expected, the increases and decreases fade away exponentially over the course of a few tens of seconds.

    If you wanted to do it right, you’d put a shutter or rotating aperture wheel in front, then track the AC signal difference between “scene” and “reference” views. A tiny Peltier module to stabilize the can temperature would make a lot of sense, too. Or, hey, that LM335 could report the actual can temperature, perhaps with everything embedded in a big thermal mass inside an insulating jacket with a peephole to the outside world. All that’s in the nature of fine tuning…

    The Arduino source code:

    // Nicera SSAC10-11 Single PIR Sensor
    // Ed Nisley - KE4ANU - November 2012
    
    //#include <stdio.h>
    //#include <math.h>
    
    //----------
    // Pin assignments
    
    const byte PIN_PIR = A2;			// Passive IR sensor
    
    const byte PIN_MOSI = 8;			// data to shift reg
    const byte PIN_SCK  = 6;			// shift clock to shift reg
    const byte PIN_RCKB  = 7;			// latch clock for LED Bargraph
    const byte PIN_RCKC  = 12;			// latch clock for LED character display
    
    const byte PIN_HEARTBEAT = 13;				// DO - Arduino LED
    
    //----------
    // Constants
    
    const int UPDATEMS = 100;					// update LEDs only this many ms apart
    
    #define TCCRxB 0x02							// Timer prescaler
    
    #define LED_SIZE		4				// chars per LED
    #define LED_DISPLAYS	1				// number of displays
    #define LED_CHARS		(LED_DISPLAYS * LED_SIZE)
    
    union DL1414_ {
    	word ShiftWord;				// word overlay
    	struct {					// bitfield sent to the display
    	unsigned int Addr:2;
    	unsigned int NotWrite:1;
    	unsigned int Ctl3_7:5;			// unused bits
    	unsigned int Data:7;
    	unsigned int Data7:1;			// unused bit
    	} ShiftBits;
    };
    
    //----------
    // Globals
    
    int PIRBase, PIRSense, PIRMin, PIRMax, PIRRange, PIRDelta;
    int PIRShift;
    
    word LEDBits = 0x5555;
    
    char LEDCharBuffer[LED_CHARS + 1] = "HELO";		// raw char buffer, can be used as a string
    
    unsigned long MillisNow;
    unsigned long MillisThen;
    
    //-- Helper routine for printf()
    
    int s_putc(char c, FILE *t) {
      Serial.write(c);
    }
    
    //-- Send bits to LED bar driver register
    
    void SetBarBits(word Pattern) {
    
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,Pattern >> 8);
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,Pattern & 0x00ff);
    
    	digitalWrite(PIN_RCKB,HIGH);
    	digitalWrite(PIN_RCKB,LOW);
    
    }
    
    void PulsePinHigh(byte PinID) {
    	digitalWrite(PinID,HIGH);
    	digitalWrite(PinID,LOW);
    }
    
    //-- Write single char to DL1414
    
    void WriteLEDChar(char Char,char CharID) {
    
    	union DL1414_ DL1414;
    
    	DL1414.ShiftBits.Data = Char & 0x7F;
    	DL1414.ShiftBits.Addr = ~CharID & 0x03;		// reverse order of chars
    
    	DL1414.ShiftBits.NotWrite = 1;				// set up data and address
    
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord >> 8);
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord & 0x00ff);
    	PulsePinHigh(PIN_RCKC);
    
    	//	delay(1000);
    
    	DL1414.ShiftBits.NotWrite = 0;				// write the character
    
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord >> 8);
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord & 0x00ff);
    	digitalWrite(PIN_RCKC,HIGH);
    	PulsePinHigh(PIN_RCKC);
    
    	//	delay(1000);
    
    	DL1414.ShiftBits.NotWrite = 1;				// disable write
    
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord >> 8);
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord & 0x00ff);
    	PulsePinHigh(PIN_RCKC);
    
    	//	delay(1000);
    
    }
    
    void WriteLEDString(char *pString) {
    
    	for (byte i=0; (i < LED_CHARS) && *pString; ++i)
    		WriteLEDChar(*pString++,i);
    
    	return;
    }
    
    //-- Sample PIR with a dab of averaging
    
    #define PAVG 3
    
    word ReadPIR(byte Pin) {
    word Sense;
    
    	Sense = analogRead(Pin);
    	for (byte i = 1; i < PAVG; i++)
    		Sense += analogRead(Pin);
    
    	return Sense / PAVG;
    }
    
    //------------------
    // Set things up
    
    void setup() {
    	pinMode(PIN_HEARTBEAT,OUTPUT);
    	digitalWrite(PIN_HEARTBEAT,LOW);	// show we arrived
    
    //  TCCR1B = TCCRxB;					// set frequency for PWM 9 & 10
    //  TCCR2B = TCCRxB;					// set frequency for PWM 3 & 11
    
    	pinMode(PIN_MOSI,OUTPUT);
    	digitalWrite(PIN_MOSI,LOW);
    
    	pinMode(PIN_SCK,OUTPUT);
    	digitalWrite(PIN_SCK,LOW);
    
    	pinMode(PIN_RCKB,OUTPUT);
    	digitalWrite(PIN_RCKB,LOW);
    
    	pinMode(PIN_RCKC,OUTPUT);
    	digitalWrite(PIN_RCKB,LOW);
    
    	Serial.begin(9600);
    	fdevopen(&s_putc,0);				// set up serial output for printf()
    
    	printf("Passive IR sensor - SSAC10-11\r\nEd Nisley - KE4ZNU - November 2012\r\n");
    
    	WriteLEDString(LEDCharBuffer);
    	SetBarBits(LEDBits);
    
    	PIRBase = ReadPIR(PIN_PIR);
    	PIRMin = PIRBase - 5;
    	PIRMax = PIRBase + 5;
    	PIRRange = PIRMax - PIRMin;
    	printf("Passive IR base: %d\n",PIRBase);
    
    	delay(1000);
    
    	MillisThen = millis();
    
    }
    
    //------------------
    // Run the test loop
    
    void loop() {
    
    	MillisNow = millis();
    
    	if ((MillisNow - MillisThen) > UPDATEMS) {
    		digitalWrite(PIN_HEARTBEAT,HIGH);
    
    		PIRSense = ReadPIR(PIN_PIR);
    		PIRDelta = PIRSense - PIRMin;
    
    		PIRMin = min(PIRMin,PIRSense);
    		PIRMax = max(PIRMax,PIRSense);
    		PIRRange = PIRMax - PIRMin;
    
    //		printf("PIR: %d Min: %d Max: %d Range: %d Delta: %d\n",
    //			PIRSense,PIRMin,PIRMax,PIRRange,PIRDelta);
    
    		PIRShift = (9 * PIRDelta)/PIRRange;
    		LEDBits = 0x00001 << PIRShift;
    		SetBarBits(LEDBits);
    
    		sprintf(LEDCharBuffer,"%4d",PIRSense);
    		WriteLEDString(LEDCharBuffer);
    
    		digitalWrite(PIN_HEARTBEAT,LOW);
    
    		MillisThen = MillisNow;
    	}
    
    }
    
  • Arduino Snippets: Temperature Measurement

    Temperature seems an obvious thing to measure, so a bit of rummaging disgorged a classic LM335 temperature sensor that produce an output voltage directly calibrated in Kelvin at 10 mV/K: room temperature runs 296 K = 2.96 V. Nothing could be easier than this:

    LM335 Temperature Sensor

    The downside: a 1 °C temperature change corresponds to only 10 mV, which is barely two LSB of the Arduino ADC. In round numbers, a 1 °F change = 1 LSB, which doesn’t leave much room for measurement noise. I average five successive readings, which may be excessive, but the result seems stable enough:

    const float AVREF = 4.94;                    // Arduino analog reference
    
    #define TAVG 5
    
    float ReadLM335(byte Pin) {
    float Kelvin;
    
    	Kelvin = (float)analogRead(Pin);
    	for (byte i = 1; i < TAVG; i++)
    		Kelvin += (float)analogRead(Pin);
    
    	return Kelvin * (100.0 * AVREF) / (TAVG * 1024.0);
    }
    

    For better accuracy, you must measure VCC on the Arduino board and plug that into the AVREF constant, because the ADC reference voltage comes from the power supply. If you’re powering the Arduino from a USB port, then don’t bother worrying about analog conversion accuracy, because VCC depends on which PC you use, the USB cable length, what load current you draw from the regulator, and probably the phase of the moon.

    The magic number 100.0 converts 10 mV/K to K.

    The four character DL1414 LED display works well enough for the kind of temperatures you might find around a human being and, if you have an LED bargraph display, you may as well throw that into the mix, too.

    LM335 Temperature Sensor – 19 C

    The bargraph has RRYYGGYYRR LEDs, so I scaled the temperature at 5 °C/bar and put 0 °C on the bottom of the display, which means 15-19 and 20-24 °C occupy the green bars in the middle. Fingertip temperatures light up the two yellow bars and body heat gets you into the red, so it’s a reasonable display. Just to show it works, here’s a closer look (0 °C is on the right, but you can reverse that easily enough):

    LM335 Temperature Sensor – 25 C

    The Arduino source code:

    // LM335 Temperature sensor sensor
    // Ed Nisley - KE4ANU - November 2012
    
    //#include <stdio.h>
    //#include <math.h>
    
    //----------
    // Pin assignments
    
    const byte PIN_TEMPERATURE = A1;			// Temperature sensor - LM335 = 10 mV/K
    
    const byte PIN_MOSI = 8;			// data to shift reg
    const byte PIN_SCK  = 6;			// shift clock to shift reg
    const byte PIN_RCKB  = 7;			// latch clock for LED Bargraph
    const byte PIN_RCKC  = 12;			// latch clock for LED character display
    
    const byte PIN_HEARTBEAT = 13;				// DO - Arduino LED
    
    //----------
    // Constants
    
    const int UPDATEMS = 1000;					// update LEDs only this many ms apart
    
    const float AVREF = 4.94;					// Arduino analog reference
    const float KTOC = -273.2;					// Kelvin to Centigrade offset
    
    const float BARSCALE = 5.0;					// degrees per bar increment
    
    #define TCCRxB 0x02							// Timer prescaler
    
    #define LED_SIZE		4				// chars per LED
    #define LED_DISPLAYS	1				// number of displays
    #define LED_CHARS		(LED_DISPLAYS * LED_SIZE)
    
    union DL1414_ {
    	word ShiftWord;				// word overlay
    	struct {					// bitfield sent to the display
    	unsigned int Addr:2;
    	unsigned int NotWrite:1;
    	unsigned int Ctl3_7:5;			// unused bits
    	unsigned int Data:7;
    	unsigned int Data7:1;			// unused bit
    	} ShiftBits;
    };
    
    //----------
    // Globals
    
    int Temperature, BaseTemperature;
    
    word LEDBits;
    
    char LEDCharBuffer[LED_CHARS + 1] = "HELO";		// raw char buffer, can be used as a string
    
    unsigned long MillisNow;
    unsigned long MillisThen;
    
    //-- Helper routine for printf()
    
    int s_putc(char c, FILE *t) {
      Serial.write(c);
    }
    
    //-- Send bits to LED bar driver register
    
    void SetBarBits(word Pattern) {
    
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,Pattern >> 8);
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,Pattern & 0x00ff);
    
    	digitalWrite(PIN_RCKB,HIGH);
    	digitalWrite(PIN_RCKB,LOW);
    
    }
    
    void PulsePinHigh(byte PinID) {
    	digitalWrite(PinID,HIGH);
    	digitalWrite(PinID,LOW);
    }
    
    //-- Write single char to DL1414
    
    void WriteLEDChar(char Char,char CharID) {
    
    	union DL1414_ DL1414;
    
    	DL1414.ShiftBits.Data = Char & 0x7F;
    	DL1414.ShiftBits.Addr = ~CharID & 0x03;		// reverse order of chars
    
    	DL1414.ShiftBits.NotWrite = 1;				// set up data and address
    
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord >> 8);
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord & 0x00ff);
    	PulsePinHigh(PIN_RCKC);
    
    	//	delay(1000);
    
    	DL1414.ShiftBits.NotWrite = 0;				// write the character
    
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord >> 8);
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord & 0x00ff);
    	digitalWrite(PIN_RCKC,HIGH);
    	PulsePinHigh(PIN_RCKC);
    
    	//	delay(1000);
    
    	DL1414.ShiftBits.NotWrite = 1;				// disable write
    
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord >> 8);
    	shiftOut(PIN_MOSI,PIN_SCK,MSBFIRST,DL1414.ShiftWord & 0x00ff);
    	PulsePinHigh(PIN_RCKC);
    
    	//	delay(1000);
    
    }
    
    void WriteLEDString(char *pString) {
    
    	for (byte i=0; (i < LED_CHARS) && *pString; ++i)
    		WriteLEDChar(*pString++,i);
    
    	return;
    }
    
    //-- Sample temperature with a dab of averaging
    
    #define TAVG 5
    
    float ReadLM335(byte Pin) {
    float Kelvin;
    
    	Kelvin = (float)analogRead(Pin);
    	for (byte i = 1; i < TAVG; i++)
    		Kelvin += (float)analogRead(Pin);
    
    	return Kelvin * (100.0 * AVREF) / (TAVG * 1024.0);
    }
    
    //------------------
    // Set things up
    
    void setup() {
      pinMode(PIN_HEARTBEAT,OUTPUT);
      digitalWrite(PIN_HEARTBEAT,LOW);	// show we arrived
    
    //  TCCR1B = TCCRxB;					// set frequency for PWM 9 & 10
    //  TCCR2B = TCCRxB;					// set frequency for PWM 3 & 11
    
      pinMode(PIN_MOSI,OUTPUT);
      digitalWrite(PIN_MOSI,LOW);
    
      pinMode(PIN_SCK,OUTPUT);
      digitalWrite(PIN_SCK,LOW);
    
      pinMode(PIN_RCKB,OUTPUT);
      digitalWrite(PIN_RCKB,LOW);
    
      pinMode(PIN_RCKC,OUTPUT);
      digitalWrite(PIN_RCKB,LOW);
    
      Serial.begin(9600);
      fdevopen(&s_putc,0);				// set up serial output for printf()
    
      printf("Temperature sensor - LM335\r\nEd Nisley - KE4ZNU - November 2012\r\n");
    
      BaseTemperature = KTOC + ReadLM335(PIN_TEMPERATURE);
    
      WriteLEDString(LEDCharBuffer);
    
      LEDBits = 0x5555;
      SetBarBits(LEDBits);
    
      printf("Base Temperature: %d C\n",(int)BaseTemperature);
    
      delay(1000);
    
      MillisThen = millis();
    
    }
    
    //------------------
    // Run the test loop
    
    void loop() {
    
    	MillisNow = millis();
    
    	if ((MillisNow - MillisThen) > UPDATEMS) {
    		digitalWrite(PIN_HEARTBEAT,HIGH);
    
    		Temperature = KTOC + ReadLM335(PIN_TEMPERATURE);
    
    		printf("Temperature: %d C\n",(int)Temperature);
    
    		LEDBits = 0x0200 >> (1 + (int)(Temperature/BARSCALE));	// move upward on display!
    		SetBarBits(LEDBits);
    
    		sprintf(LEDCharBuffer,"%-3dC",(int)Temperature);
    		WriteLEDString(LEDCharBuffer);
    
    		digitalWrite(PIN_HEARTBEAT,LOW);
    
    		MillisThen = MillisNow;
    	}
    
    }