The idea for this project came from hacknmod.com. This device has a 5×8 LED matrix which displays a face. The face will react to sound, opening and closing the mouth, making it look like the face is talking or singing along with the sound. Every 250ms, we take a sample from the microphone, decide which face to use according to voltage, and then display the face column by column. We had to redesign the schematic and the code to make use of our Microchip microcontroller, since the controller that was used on hacknmod.com was an Atmel chip.
Device Demo |
The main components of this device are the LED matrix, which is a Liteon #LTP14158AG 5×8 dot matrix, a microphone from Horn Industrial Co LTD, and the PIC16F818 microcontroller from Microchip. The device gets its power from a USB connector. The pot regulates the device sensitivity to sound. The filter built on the 2N3904 transistor is used to reduce noise coming from the power line. In many computers the USB voltage is not filtered out good enough to be used in audio applications.
The original value of the capacitor in the feedback loop of LM386, which was 0.1μF, was increased to 4.7μF to increase the mic's sensitivity. With the current resistor value (120 ohm), the current through each LED segment is about 25mA (milliamperes), which is maximum for the PIC. Since up to 4 dots can be up in a column, the maximum current from pin, say 13, of the LED matrix (about 100mA) is more than a microcontroller can handle. Furthermore, using common column resistors in the original design has 2 drawbacks: the resistor value was too large for dots to be noticeable in the day light, and the brightness of columns was dependent on how many LEDs are on in that column. This is why MOSFET keys are used, which was not a part of the original schematic. With these additions, the LEDs are very bright and their brightness does not depend on the number of LEDs on in a column. The resistor at the PIC's pin 3 is needed because port RA4 has an open drain output.
Surface-mount technology (SMD) packages are used for most of the components. The LED is attached to the board by using home-made connectors made of a 14-pic IC socket by cutting it off into two parts. Blue lines on the board image are wire jumpers and the tiny black connector on the back of the board is used for PIC programming. This allows us to reload new code onto the chip whenever needed. The board was composed by using Eagle software. Note that there are some other wire jumpers in the PCB#3 image below. They were added to fix the initial design flows. The final PCB layout is free from these glitches.
Schematic | Board | PCB#1 | PCB#2 | PCB#3 | |||||
When reading the voltage from our microphone, we found that the value of the voltage fluctuates rather quickly. In order to detect voltage peaks, we had to use a hardware trick. This is done by using a diode, a capacitor, and a resister between the output of the audio amplifier and the input pin of the chip. The amplifier charges the capacitor and the diode prevents it from discharging when the volume decreases. This means that while we take a voltage reading, the capacitor's voltage will be equal to the peak voltage of the reading, and will remain steady throughout the reading. The resistor is needed for discharging the capacitor slowly over time. Without the resistor, it would take several minutes for the capacitor to discharge, and once face #6 is displayed for the first time, it would stay for too long.
The idea for the software is as follows: we take a voltage sample (adc) from the microphone, compute |102 - adc| (102 being the value when there is no sound), and then decide which face to use. The mid value (102) was computed in the following way: The ADC uses a 5V internal reference and LM386 is powered from 4V, so in the steady state, its output is 1/2 of the power voltage, i.e. about 2V. We only use 8 higher-order bits of ADC, which results in a maximum ADC value of 255 for 5V, so the 2V value is then (2/5)·255 = 102. After the first implementation, we found that just taking one sample wasn't good enough because even if the sound stays constant, the voltage received from the microphone jumps around quickly and thus, isn't very accurate. To fix this, we tried averaging 8 samples, and then deciding which face to use. This also didn't work because of how quickly the voltage varies. With no other option, we had to use the hardware fix discussed above. We also reverted to taking only one sample at a time. The samples are taken by an interrupt service routine that is called at regular intervals provided by TMR1 overflows. The PIC runs at 4 MHz.
bsf ADCON0, GO ; start A/D conversion btfsc ADCON0, GO ; is it over? goto $-1 ; NO - wait movf ADRESH, w ; get ADC value sublw midLevel ; w = mid_value - w btfss STATUS, C sublw 0 ; w = |mid_value - w| movwf signalAmp movlw 12 ; signal level step between faces clrf faceNo ; faceNo = 0 subwf signalAmp, f btfss STATUS, C ; signal < 1st threshold? goto ISR_end ; YES incf faceNo, f ; faceNo = 1 subwf signalAmp, f btfss STATUS, C ; signal < 2nd threshold? goto ISR_end ; YES incf faceNo, f ; faceNo = 2 subwf signalAmp, f btfss STATUS, C ; signal < 3rd threshold? goto ISR_end ; YES incf faceNo, f ; faceNo = 3 subwf signalAmp, f btfss STATUS, C ; signal < 4th threshold? goto ISR_end ; YES incf faceNo, f ; faceNo = 4 subwf signalAmp, f btfss STATUS, C ; signal < 5th threshold? goto ISR_end ; YES incf faceNo, f ; faceNo = 5 |
After picking the face, we need to output it to the LED matrix. This is done by turning the LEDs on, column by column. To do this, we first need to turn off the current column, get the binary code for this column of the face that we want to output, load the column, and then turn the column back on. We then have to use a short delay to make sure that the LEDs get turned on before moving to the next column. Each column is displayed for 3 msec (milliseconds) resulting in 3·5=15msec screen refresh period, which is about 66Hz.
loop clrf colCnt ; start with column 1 bcf PORTA, 0 ; turn off display call getColumn ; get column code movwf PORTB ; preload column bsf PORTA, 4 ; turn on column 1 call colDelay incf colCnt, f ; update column number bcf PORTA, 4 ; turn off display call getColumn ; get column code movwf PORTB ; preload column bsf PORTA, 7 ; turn on column 2 call colDelay incf colCnt, f ; update column number bcf PORTA, 7 ; turn off display call getColumn ; get column code movwf PORTB ; preload column bsf PORTA, 3 ; turn on column 3 call colDelay incf colCnt, f ; update column number bcf PORTA, 3 ; turn off display call getColumn ; get column code movwf PORTB ; preload column bsf PORTA, 2 ; turn on column 4 call colDelay incf colCnt, f ; update column number bcf PORTA, 2 ; turn off display call getColumn ; get column code movwf PORTB ; preload column bsf PORTA, 0 ; turn on column 5 call colDelay goto loop |
When retrieving the binary code for a column we use two variables, faceNo and colCnt. In the beginning of the getColumn method, we multiply faceNo by 5 because there are 5 columns per face. Then to the resulting number, we add colCnt. The result is then added to the program counter to get to the correct spot in the table. For example, if we want to get the binary code for the 3rd column in face 2 (faceNo = 1), we take (1·5)+3 = 8. Then we add 8 to the program counter to jump down 8 lines of code, and return b'10000001'. The reason we don't see the face patterns in the table below is because of the way that the LED matrix is connected to the PIC. As you see we use all of PORTB to fill the column when lighting each single LED in the matrix. For example if you want to light the top two LEDs you would send the bits to PORTB like so b'00000110' This sends bits to RB1 & RB2, lighting the corresponding LEDs of 14 & 9 on the Schematic.
getColumn movf faceNo, w ; w = faceNo addwf faceNo, w ; w = faceNo*2 addwf faceNo, w ; w = faceNo*3 addwf faceNo, w ; w = faceNo*4 addwf faceNo, w ; w = faceNo*5 addwf colCnt, w ; w = offset in face table addwf PCL, f retlw b'00001000' ; face 1 retlw b'00000101' retlw b'00000001' retlw b'00000101' retlw b'00001000' retlw b'00000000' ; face 2 retlw b'10000101' retlw b'10000001' retlw b'10000101' retlw b'00000000' retlw b'10000000' ; face 3 retlw b'00010101' retlw b'00010001' retlw b'00010101' retlw b'10000000' retlw b'10010000' ; face 4 retlw b'00100101' retlw b'00100001' retlw b'00100101' retlw b'10010000' retlw b'10110000' ; face 5 retlw b'01000101' retlw b'01000001' retlw b'01000101' retlw b'10110000' retlw b'10110100' ; face 6 retlw b'01000101' retlw b'01000001' retlw b'01000101' retlw b'10110100' |
All source code is designed to be compiled in the MPLAB IDE.
Last modified:Mon, Jan 23, 2023.