LogoChip: Audio Playback Demo

Over the weekend and last night, I’ve put together a demo of using sensors and triggers with the LogoChip. The demo uses one of the built-in A/D converters to monitor a CdS photocell, and then trigger audio playback on a Radio Shack 276-1323 recording module.

The code watches for the light level to dim and then triggers one of two recordings, the idea being that someone is probably standing over the box looking into it. It’s actually a bit complex, because I keep a running average of the recent brightness, to make it easier to tell whether a sudden drop is a significant event or just noise variation on the sensor. LogoChip Logo doesn’t have terribly good support for arrays, and it was fairly interesting writing ring-buffer code.

Adapting the Playback Modules

[INSERT PHOTO OF CIRCUIT IN BOX]

The little PCBs on the sides are the Radio Shack audio playback modules. Each has an offboard record button and an onboard playback button. I desoldered the 9V battery clip that came with the units and soldered on leads to take power from my PCB, to cut down the number of batteries I had to install. I just now realized I had mistakenly plugged the power into the regulated 5V instead of the unregulated 9V, so that may explain why the playback was relatively quiet. It also speaks well for a wide supply voltage range on the modules.

The playback buttons are NO with pullup resistors, and ground their line when closed. Because they’re rubber pushbuttons with graphite contacts the press directly onto the PCBs, there were no leads to solder my remote trigger wires onto, so I wrapped them around the lower leads (toward the middle of the PCB) of the pullup resistors, R3.

[INSERT CLOSEUP PICTURE OF MODULE PCB]

Then I plugged the remote triggers into ports C6 and C7 on the LogoChip. Since the playback buttons on the modules are active low, I had to reverse my software logic and set the ports high on initialization, then pull them low to activate.

;   Configure the audio trigger I/O port.
to init-squawk-port
        ;   Set left and right triggers off (active low, so set high)
        setbit squawk-left-bit squawk-port
        setbit squawk-right-bit squawk-port

        ;   Set left and right triggers as outputs
        clearbit squawk-left-bit squawk-ddr
        clearbit squawk-right-bit squawk-ddr
end

;   Trigger the external playback modules
;   Parameter: which module to trigger
to squawk :squawk-bit
        ;   Trigger it (active low, so set low)
        clearbit :squawk-bit squawk-port

        ;   Wait a smidge to be sure it registers
        wait 1

        ;   Clear the trigger (active low, so set high)
        setbit :squawk-bit squawk-port
end

The Software

[INSERT DETAILED DESCRIPTION OF RUNNING AVERAGE FUNCTION]

I learned several important lessons from this programming exercise:

  • The A/D converter needs longer than .1 seconds between readings–at that repeat rate, I was getting garbage every time. .2 seconds appears to have been working reliably.
  • The LogoChip Language Reference gives the positions of global variables m and n incorrectly, which led to a buffer overflow in my code. Global n is actually position 0 and m is 1, not 1 and 2 as stated.
  • LogoChip Logo does bizarre things to arithmetic expressions. I have no idea how it was evaluating

    setsum-bright sum-bright - global (array-base + array-index) + bright

    because I couldn’t figure out any sequence of evaluation that led to the results it was giving me. I solved it with parentheses:

    setsum-bright (sum-bright - global (array-base + array-index)) + bright

    but it’d be nice to understand what was happening.

The Code

Here are the programs:

  • squawk.txt – Test code to play back sound samples
  • light-level.txt – Test code to monitor and report light levels
  • light-demo.txt – Program to monitor light levels and trigger audio playback

Here’s the code:

;   Keith Neufeld
;   27-Feb-2006
;
;   LogoChip program to demo measuring ambient light level
;   with CdS photocell wired from LogoChip port A1 to +5V
;   and 1K resistor wired from A1 to GND.
;
;   When light level suddenly becomes dimmer, trigger audio playback
;   using Radio Shack 276-1323 20-second recording modules
;   with play buttons wired to LogoChip ports C6 and C7.
;   Play buttons are active low.

;   Port assignments for the photocell and audio units
constants [
        [cds-port porta] [cds-ddr porta-ddr] [cds-bit 1]
        [squawk-port portc] [squawk-ddr portc-ddr]
                [squawk-left-bit 6] [squawk-right-bit 7]
]

;   Array for averaging brightness
global [array0 array1 array2 array3 array4 array5 array6 array7 array-index]
constants [
        [array-base 2]
        [array-len 8]
]

;   Current brightness, sum of recent brightness, average recent brightness
global [bright sum-bright avg-bright]
;   Calculated brightness values that indicate dimming
global [threshold-shady threshold-dark]

;   At powerup, run the main program.
to powerup
        startup
end

;   At startup, clear the array storage space, initialize some
;   global variables, and configure the I/O ports.
to startup
        init-array
        init-vars
        init-squawk-port
        init-cds-port

        prs "starting
        watch-brightness
end

;   Loop forever, taking samples and watching for dimming.
global [already-shady already-dark]
to watch-brightness
        loop [
                ;   Calculate what fractions of previous brightness
                ;   constitute being dim
                calc-thresholds

                ;   Get current brightness
                read-bright

                ;   The following logic for different brightness conditions
                ;   is carefully arranged to make a successful test exclude
                ;   success on subsequent tests.  I would have preferred to
                ;   write this using
                ;
                ;   if  [ block 1 ]
                ;   elseif  [ block 2 ]
                ;   elseif  [ block 3 ]
                ;
                ;   but LogoChip Logo doesn't support that, and I got cranky
                ;   nesting the conditional blocks three deep using the syntax
                ;   that _is_ supported.
                ;
                ;   Caveat maintainer.

                ;   If we're getting brighter,
                if bright > avg-bright [
                        ;   Start paying attention to getting dimmer again
                        setalready-dark 0
                        setalready-shady 0
                ]

                ;   If we're significantly darker than before,
                if bright < threshold-dark [
                        ;   And we haven't already done something about it,
                        if not already-dark [
                                ;   Then cause an event
                                dim-a-lot-event

                                ;   And remember that we're already dark
                                ;   and don't need to trigger another event.
                                setalready-dark 1
                                setalready-shady 1
                        ]
                ]

                ;   If we're a little darker than before,
                if bright < threshold-shady [
                        ;   And we haven't already done something about it,
                        if not already-shady [
                                ;   Then cause an event
                                dim-a-little-event

                                ;   And remember that we're already shady
                                ;   and don't need to trigger another event.
                                setalready-shady 1
                        ]
                ]
        ]
end

;   Event routine for when slight dimming is detected
to dim-a-little-event
        prs "|dimming a little|

        ;   Play the message from the right speaker.
        squawk squawk-right-bit

        ;   Give it a couple of seconds to finish.
        wait 20
end

;   Event routine for when significant dimming is detected
to dim-a-lot-event
        prs "|dimming a lot|

        ;   Play the message from the left speaker.
        squawk squawk-left-bit

        ;   Give it a couple of seconds to finish.
        wait 20
end

;   Clear the array.
to init-array
        setarray-index 0
        repeat array-len [
                setglobal (array-base + array-index) 0
                setarray-index (array-index + 1) % array-len
        ]
end

;   Initialize global variables.
to init-vars
        setsum-bright 0

        setalready-shady 0
        setalready-dark 0
end

;   Configure the audio trigger I/O port.
to init-squawk-port
        ;   Set left and right triggers off (active low, so set high)
        setbit squawk-left-bit squawk-port
        setbit squawk-right-bit squawk-port

        ;   Set left and right triggers as outputs
        clearbit squawk-left-bit squawk-ddr
        clearbit squawk-right-bit squawk-ddr
end

;   Configure the photocell I/O port.
to init-cds-port
        ;   Set photocell port as input
        setbit cds-bit cds-ddr

        ;   Give time for the operator to get their hand out of the way
        ;   and find out how bright the room can be
        repeat 10 [
                ;   Read the current brightness and store rolling average
                read-bright
                ;prs "average
                ;print avg-bright
        ]
        ;prs "|final average|
        ;print avg-bright
end

;   Trigger the external playback modules
;   Parameter: which module to trigger
to squawk :squawk-bit
        ;   Trigger it (active low, so set low)
        clearbit :squawk-bit squawk-port

        ;   Wait a smidge to be sure it registers
        wait 1

        ;   Clear the trigger (active low, so set high)
        setbit :squawk-bit squawk-port
end

;   Read the brightness and work it into a rolling average.
;   Uses a ring buffer to compute the rolling average.  If you know what
;   that means, there's not much I can teach you about LogoChip Logo.
to read-bright
        ;   Read current brightness
        setbright read-ad cds-bit
        ;prs "|current brightness|
        ;print bright

        ;   Roll off oldest value from rolling average and add current
        ;   Parentheses around first two operands are necessary--
        ;   returns random result without
        setsum-bright (sum-bright - global (array-base + array-index)) + bright

        ;   Save current brightness and advance history pointer
        setglobal (array-base + array-index) bright
        setarray-index (array-index + 1) % array-len

        ;   Calculate recent average brightness, rounding the fraction
        setavg-bright (sum-bright + (array-len / 2)) / array-len

        ;   A/D converter seems to require .2 sec between readings
        wait 2
end

;   Calculate thresholds of dimming based on recent average brightness.
;   Multiply then divide because fractions become 0 in integer arithmetic.
to calc-thresholds
        setthreshold-shady (9 * avg-bright) / 10
        ;prs "|shady threshold|
        ;print threshold-shady
        setthreshold-dark (5 * avg-bright) / 10
end

Leave a Reply