LED Display Matrix Overview

Final Construction

The goal of this project was to learn how to build a LED Matrix for use in hobby electrical designs, and to understand the I2C protocol.  This process incorporated the schematic design process, understanding the mathematics and complications of cathode and anode interconnections, and optimizing the construction.  Much was learned about how to use the I2C protocol for use in Arduino IDE and in C language libraries, despite the inability to implement such in programming due to difficulties with the Arudino IDE bootloader. 

If we had been successful in programming the I2C protocol, LED matrix drivers could have been incorporated as I2C devices. This would have made programming much simpler for the matrix once the necessary libraries and handlers were created. Additionally, with a working I2C functionality, additional matrix drivers and LED arrays could be incorporated in the future to add modularity to the LED Display Matrix, or to expand upon the project for larger animations.

It was also identified that an interrupt for cycling through animations using the UART serial terminal interface would be a means to improve user interaction.  Interrupts were researched as well for push-button usage, and while complicated to implement could be just as successful for external interaction with the matrix animations.

LED Display Matrix – Code

Code Initialization Block and First Frame

The code for this project was created entirely in Assembly. The Xplained Mini ATmega328P served as a matrix driver for a 9×9 grid, 18 pinout of two cathode, common anode Red-Green LEDs. The LEDs were designed to have two addresses in mind, one for each combination of cathode and anode. With 81 LEDs, this created 162 addresses. To activate a single LED, the cathode bit was written to sink and the anode was written to source current using the PINB, C and D pins as outputs. The Xplained Mini ATmega328P requires the DDRn and PORTn registers to be assigned before the pinouts PINB, PINC, and PIND can be used.  The DDRn register designates PINn as an input or output. Writing to PORTn determines what PINn outputs—even if it is designated as an input. When both a DDRn and PORTn bit are 0, the output becomes a high-impedance tristate value. This effectively cuts off any connection to the LED inputs, leaving all 18 inputs driven high to VCC, and no LEDs turned on. Activating DDRn to set PINn to an output, and writing PORTn to 0 turns a PINn to sink, activating cathodes on the line. When PORTn is 1 while an output, PINn becomes a source activating any anodes on the line.

In configuring the LED frames, the mapping of the LED pinouts was crucial. Due to programming in Assembly, knowing which bits of the DDRn and PORTn registers was required for every frame to be displayed on the final matrix. An excel mapping of LED pinouts for desired frame animations was created. This map allowed for groupings of LED addresses that could be turned on at the same time without activating other LEDs connected in series.  Combining LEDs for activation reduced the code from a theoretical 894 lines of code to 272 (not including repetitive lines for subroutine calls or testing code).

In total, 4 unique frames were programmed in their own subroutines.  A 5th subroutine was created to act as a loop between the others, displaying the 4 frames as an animation cycle to an observer of the LED Display Matrix.

LED Display Matrix – Schematic

LED Display Matrix Schematic

This is the LED Display Matrix schematic. The matrix was composed of 81 two cathode, common anode Red-Green LEDs.  Wiring was designed by pairing LED anodes and cathodes together in a schema for each LED to have is own unique address.  Each pinout from the Xplained Mini serves to either sink or source current to a cathode or anode in the matrix while each line is pulled high to VCC. This design allows for a total of 18 pins to be used to sink (ground) or source (power) to activate single LEDs without turning on others, thus enabling numerous unique images to be programmed to the matrix.  

The wiring of this matrix created 162 unique addresses for LEDs, an address being composed of one anode and one cathode. These were activated by directly writing to the data direction registers (DDRn) and data registers (PORTn) of the Xplained Mini ATmega328P pinout. The logic for activating the DDRn and PORTn allowed for each pin to act as a sink, source, or high impedance input. This was utilized while programming to set these registers to values that would turn on multiple LEDs as desired for an image.

Audio Visualizer Overview

The base concept of an audio visualizer is: an output reacts to a change in an input. This base interaction was what originally inspired the idea. In this case, a device, the Sparkfun sound detector board, is measuring the change in the environmental noise level through the principle of capacitance. Team 8 was interested in understanding the fundamentals of how microphones functioned and how easily it could drive an output such as a strip of LEDs.

The Sparkfun sound detector works off of two main theories, the first being the theory of parallel plate capacitance. This theory essentially states the closer the two plates are the lower the capacitance. For a standard parallel plate capacitor, the voltage is inversely proportional to the capacitance as modeled by the equation Q = C*V. Q is the amount of charge that moves across the two plates, while c is the capacitance in farads and V is the potential difference. In the case of the microphone, the voltage was set at 3.3V. The voltage that comes out of this tiny parallel plate capacitor is very small. This is where the second principle of op-amps comes in. The op-amp circuit shown above is used to amplify the voltage from the microphone to the rest of the circuit. In our case team 8 used analog port 0 to read the value from the envelope output pin and analog port 2 to read the values from the audio pin.

Audio Output

The figure above shows the two outputs used by team 8: envelope and audio. These outputs controlled the number of LEDs to be lit up. This interpolated relationship is how the LED visualizer works. The team would take the envelope signal map to a value from 0 to 9 then iterate a for loop to illuminate that mapped number of LEDs. The LED would utilize an RGB implementation to understand which color it was going to display.

Audio Visualizer Schematics

Team 8 Audio Visualizer Schematic

Team 8 worked through the final period of the class to create an Arduino based audio visualizer. The goal was to make a device that accepted sound via the spark fun sound detector microphone and signaled the Arduino Uno to change the LED depending on the volume of the music.  It started with testing out our code just on the LEDs and board with building a wooden structure to hold the hardware after.  This resulted in an upside-down T shaped structure that has a multicolor display that pulses. The circuit shown above required a 330-ohm resistor and a 100 microfarad capacitor. The capacitor was used to smooth out the power running into the led strip as fluctuations could cause adverse effects in the lighting, and the resistor was used as a ballast, or current limiting, resistor.

The general design of the box was inspired by a soundbar type device that could be placed in any general living room. As shown from the AutoCAD sketch above the main housing unit was designed to be a 5 x 5-inch box. This allowed for the Arduino and a small breadboard to be housed within this member. This also allowed for cutouts to be created to push two arm members through. This cutout was to act a strengthing arm as well as holding the arm in place. once these arms and main box units were laser cut 4 strips of 9 LEDs were cut and glued to the members. The microphone was then attached and glued to the hole cut out in the front of the box. Electrical tape was used to insulate the microphone from outside vibrations.

Team 8 Audio Visualizer Code

Team 8 Utilized the help of an external library as well as created functions in their own. The external library we utilized was known as FAST LED, and allowed us to easily push colors out to the LED strip quickly. Team 8 focused on building functions that mapped an input from a microphone then addressed and lit up the WS2182B LED strip.

This diagram features a general overview of the code team 8 used to create this project. After the baud rate was set the code would evaluate where a counter variable D was. This counter variable was used in place of a frequency dependence due to a lack of our current microphone to reliably produce a distinct clear difference in frequency.

The following code is an example of one of the functions Team 8 wrote and placed into a custom library. We used an Led matrix to assign individual values to each pixel of the WS2182B LED Strip. As can be shown from this function, the input from the Sparkfun soundboard is read by the Arduino through analog port 0. That value is then mapped onto a value from 0 to 9, because our led matrix had 4 rows and 9 columns per side. Once that mapping was complete the program would assign values to the LEDs through a for loop, starting with the first row and column of each matrix. Finally, once that process was complete the LEDs would be cleared and the remapped values would be calculated again. This is one example of the 5 functions team 8 derived over the course of this final project.

Traffic Control System – Code

The entire traffic system is composed of three subsystems. The first subsystem controls the traffic lights. This system has a default state and an alternative state. The default state of the intersection is for the east -west road to have green lights, while the north- south are red. The alternative state is for the north -south lights to be green and the east -west to be red. There are two ways to swap from the default state to the alternative state. The first way is for a car to pull up to the south side of t he intersection and trigger the IR sensor. After a few seconds of delay, the state swap is initiated. The east -west lights go from green to yellow for one second and then change to red for the duration of the alternative state. After another delay, the north-south lights go from red to green. They stay green for 10 seconds before the reverse swap occurs. The reverse swap switches the alternative state back to the default state.

The second way to change the default state of the intersection is handled by the second subsystem. The second subsystem handles the crosswalk, and has a direct influence over the operation of the first subsystem. The second subsystems first function is triggered by any of the 4 blue buttons. If a blue button is pressed, this tells the controller that a pedestrian wants to cross the east -west road using either of the two crosswalks. After a short delay, the intersection switches to what is called the “alternative plus” state. This state is the same as the alternative state with addition of counting down the 7- segment displays. In this state, drivers coming from the south would have to yield to pedestrians crossing the street before turning. The second function this subsystem handles is the crosswalk for the north- south road. This function doesn’t interfere with the default operation of the intersection. It simply verifies that the north -south lights are red, then starts the countdown on the corresponding 7- segment displays.

The third subsystem operates independently from the first two. It controls the flashing lights over the train track and the servo motors that bring the arms down. To mimic an oncoming train, a red push button is located to the south east of the train track. Once this button is pushed, a 3 second delay is initiated and followed by the servo motor arms moving across each side of the road to block oncoming traffic and pedestrians from crossing the track while the train is passing.

These systems are controlled by two ATmega328Ps. The main board controls the railroad and traffic light functions while a second board controls the seven segment displays that are the crosswalk count downs. The two board system was chosen because the ATmega328P did not have enough ports to control all of these functions. For a schematic for this code, go to https://www.ece.louisville.edu/courses/ece412/?p=2351.

Main Code Segment

#define F_CPU 8000000UL
#include <util/delay.h>
#include <avr/io.h>
#include <avr/interrupt.h>

#define G1 PD0 //setting lights up with ports
#define Y1 PD1
#define R1 PD2
#define G2 PD3
#define Y2 PD4
#define R2 PD5

#define Servo PB1

#define B1 PC5
#define B2 PC4
#define B3 PC3

int main(void)
{
DDRB=0xff;
DDRD=0xff;
DDRC=0x00;
PORTB=0x00;
PORTC=0x00;
PORTD=0x00;
DDRC|=0<<DDC0; //setting PC0 to be an input 0x01
PORTC|=(1<<PORTC0); //setting PC0 to have pull-up resistor as long as 
//pin is setup as input
DDRC|=0<<DDC1;//setting PC1 to be an input 0x02
PORTC|=(1<<PORTC1);//setting PC1 to have pull-up resistor as long as 
//pin is setup as input
DDRC|=0<<DDC2;//setting PC2 to be an input 0x04
PORTC|=(1<<PORTC2);//setting PC2 to have pull-up resistor as long as 
//pin is setup as input
DDRC|=0<<DDC3;//setting PC3 to be an input 0x08

PORTC|=(1<<PORTC3);//setting PC3 to have pull-up resistor as long as 
//pin is setup as input
DDRC|=0<<DDC4;//setting PC4 to be an input 0x10
PORTC|=(1<<PORTC4);//setting PC3 to have pull-up resistor as long as 
//pin is setup as input
DDRC|=0<<DDC5;//setting PC5 to be an input 0x20
PORTC|=(1<<PORTC5);//setting PC3 to have pull-up resistor as long as 
//pin is setup as input
DDRD|=0xFF;
TCCR1A|=1<<WGM11|1<<COM1A1|1<<COM1A0;//setup compare match
TCCR1B|=1<<WGM13|1<<WGM12|1<<CS11|1<<CS10;//Setting Clock Pre-scaler 
//to /64
ICR1=2499;//value calculated for frequency to be 50hz for the 
//Servo motors
while(1)
if(!(PINC&(1<<B1)))//Setting up buttons if pushed crosswalks 
{
_delay_ms(6000);
PORTD=0x00;
PORTD|=(1<<G1);
PORTD|=(1<<R2);
_delay_ms(5000);
PORTB=0x00;
}
else if(!(PINC&(1<<B2)))// NO ! PC4 Power is low, ! before 
{
//parenthesisis if high, both work
_delay_ms(8000);
PORTD=0x00;
PORTD|=(1<<Y1);
PORTD|=(1<<R2);
_delay_ms(3000);
PORTD=0x00;
PORTD|=(1<<R1);
PORTD|=(1<<R2);
_delay_ms(3000);
PORTD=0x00;
PORTD|=(1<<G2);
PORTD|=(1<<R1);
_delay_ms(23000);
PORTD=0x00;
}
else if(!(PINC&0x01)==0x01)//if PC0 Power is low, Beam broken
{
_delay_ms(8000);
PORTD=0x00;
PORTD|=(1<<Y1);
PORTD|=(1<<R2);
_delay_ms(5000);
PORTD=0x00;
PORTD|=(1<<R1);
PORTD|=(1<<R2);
_delay_ms(3000);
PORTD=0x00;
PORTD|=(1<<R1);
PORTD|=(1<<G2);
_delay_ms(8000);
PORTD=0x00;
PORTD|=(1<<Y2);
PORTD|=(1<<R1);
_delay_ms(6000);
PORTD=0x00;
PORTD|=(1<<R1);
PORTD|=(1<<R2);
_delay_ms(3000);
PORTD=0x00;
PORTD|=(1<<G1);
PORTD|=(1<<R2);
_delay_ms(5000);
}
else if(!(PINC&(1<<B3)))//Servo Motor
{
_delay_ms(4000);
OCR1A=ICR1-365;//up to neutral position(-365)
_delay_ms(1000);//delay
OCR1A=ICR1-545;//down right position (-155), (-550) for other way
_delay_ms(10000);
OCR1A=ICR1-365;//up to neutral position(-365)
_delay_ms(500);//delay
}
else
{
PORTD=0x00;
PORTD|=(1<<G1);
PORTD|=(1<<R2);
}
}

Slave Code Segment

#define F_CPU 8000000UL
#include <util/delay.h>
#include <avr/io.h>
#include <avr/interrupt.h>

#define Direction PC0 //inputs
#define StartPC1//inputs
#define SSD1A PD0
#define SSD1B PD1
#define SSD1C PD2
#define SSD1D PD3
#define SSD2A PD4
#define SSD2B PD5
#define SSD2C PD6
#define SSD2D PD7
#define Blank PB0
#define B1 PC5
#define B2 PC4

int main(void)
{
DDRD=0xff;
DDRC=0x00;
DDRB=0xff;
PORTB=0x00;
PORTC=0x00;
PORTD=0x00;

DDRC|=0<<DDC0;//setting PC0 to be an input 0x01
PORTC|=(1<<PORTC0);//setting PC0 to have pull-up resistor as long as 
//pin is setup as input
DDRC|=0<<DDC1;//setting PC1 to be an input 0x02
PORTC|=(1<<PORTC1);//setting PC1 to have pull-up resistor as long as 
//pin is setup as input
DDRC|=0<<DDC2;//setting PC2 to be an input 0x01
PORTC|=(1<<PORTC2);//setting PC2 to have pull-up resistor as long as 
//pin is setup as input
DDRC|=0<<DDC4;//setting PC4 to be an input 0x01
PORTC|=(1<<PORTC4);//setting PC4 to have pull-up resistor as long as 
//pin is setup as input
DDRC|=0<<DDC5;//setting PC5 to be an input 0x01
PORTC|=(1<<PORTC5);//setting PC5 to have pull-up resistor as long as 
//pin is setup as input

while(1)
{
if(!(PINC&(1<<B1)))
{
_delay_ms(7000);
PORTB=0x00;
PORTB|=(1<<Blank);
PORTD=0x00;
PORTD|=(1<<SSD1C);
PORTD|=(0<<SSD1B);
PORTD|=(1<<SSD1A);
_delay_ms(2000);
PORTD=0x00;
PORTD|=(1<<SSD1C);
PORTD|=(0<<SSD1B);
PORTD|=(0<<SSD1A);
_delay_ms(2000);
PORTD=0x00;
PORTD|=(0<<SSD1C);
PORTD|=(1<<SSD1B);
PORTD|=(1<<SSD1A);
_delay_ms(2000);
PORTD=0x00;
PORTD|=(0<<SSD1C);
PORTD|=(1<<SSD1B);
PORTD|=(0<<SSD1A);
_delay_ms(2000);
PORTD=0x00;
PORTD|=(0<<SSD1C);
PORTD|=(0<<SSD1B);
PORTD|=(1<<SSD1A);
_delay_ms(2000);
PORTD=0x00;
PORTD|=(0<<SSD1C);
PORTD|=(0<<SSD1B);
PORTD|=(0<<SSD1A);
_delay_ms(2000);
PORTB=0x00;
PORTB|=(0<<Blank);
}
else if(!(PINC&(1<<B2)))
{
_delay_ms(7000);
PORTB=0x00;
PORTB|=(1<<Blank);
PORTD=0x00;
PORTD|=(1<<SSD2C);
PORTD|=(0<<SSD2B);
PORTD|=(1<<SSD2A);
_delay_ms(2000);
PORTD=0x00;
PORTD|=(1<<SSD2C);
PORTD|=(0<<SSD2B);
PORTD |= (0<<SSD2A);
_delay_ms(2000);
PORTD = 0x00;
PORTD |= (0<<SSD2C);
PORTD |= (1<<SSD2B);
PORTD |= (1<<SSD2A);
_delay_ms(2000);
PORTD = 0x00;
PORTD |= (0<<SSD2C);
PORTD |= (1<<SSD2B);
PORTD |= (0<<SSD2A);
_delay_ms(2000);
PORTD = 0x00;
PORTD |= (0<<SSD2C);
PORTD |= (0<<SSD2B);
PORTD |= (1<<SSD2A);
_delay_ms(2000);
PORTD = 0x00;
PORTD |= (0<<SSD2C);
PORTD |= (0<<SSD2B);
PORTD |= (0<<SSD2A);
_delay_ms(2000);
PORTB = 0x00;
PORTB |= (0<<Blank);
}
}
}

Team 4’s Infrared IoT Door Security Schematics

The picture above is a simplified version of the entire circuit. It starts off with the Arduino board that is wired to the infrared transmitter and receiver. Then finally connected to the Wi-Fi IC. When the door opens and the receiver/transmitter breaks then the Arduino sends a command to the IC. Once that has been received the IC sends a message to the Endpoint server of the Wi-Fi that it is connected to. On our version it was connected to Twilio to send a message that announced the door was opened.

Team 4’s Infrared IoT Door Security Code

<?php
require __DIR__ . '/vendor/autoload.php';
use Twilio\Rest\Client;

// Check if phone number send in request
if(isset($_GET['n'])) {
    
    // New Twilio client, credentials saved in environment
    $client = new Client($_ENV["TWILIO_SID"], $_ENV["TWILIO_AUTH_TOKEN"]);
    $client->messages->create(
        '+'.$_GET['n'],
        array(
            'from' => "+1xxxxxxxxxx",    //xxx.. = phone number including area code
            'body' => '-------------------- Alert, Door Open --------------------'
        )
    );
    echo "200 OK";
    http_response_code(200);
    
} else {
    
    // Error
    echo "400 Bad Request\nMissing phone number";
    http_response_code(400);
    
}

The above code was the file that we used for the endpoint server that actually received the inputs from the Wi-Fi board, and then is used to check if there is a valid phone number to message. If the phone number is missing or invalid, then it returns an error code of 400. However, if it has a valid number but has not received an input from the circuit then it continues to respond with an “ok” status code only to the http server. Then, when both a valid number is stored and the file receives the input it creates a new message and sends it to say that the door has been opened, and sends it using the ‘from’ listed number.

Traffic Control System – Schematic

7-Segment Common Anode Displays and 7447 Decoders were used for the crosswalk countdown sign for this project. 7-Segment display made up of 7 individual red LED (Light Emitting Diode). Each segment was assigned from A to G. The A segment located up top horizontal, the B segment is to the right vertical, CDEF followed in a clockwise direction, and G segment is horizontal located in the middle. All Common Anode Display of LED’s are joined together to logic “1” and each of the individual segments light up by connecting individual cathode terminal to a “0” or “LOW” signal. The image below illustrates the 74LS47 decoder connection with the common anode 7segment display.

Servo Motors were used to imitate the railroad gate in the railroad subsystem. Servo motor is a device in which it can rotate and change the motion or direction of attached devices. Servo motors can operate by sending pulse width modulation (PWM) through the control wire. Via the code, you start by setting the compare match A and then the clock prescaler values to /64. Then calculating for the frequency needed by the servo motors (there datasheets). You can find the ICR1 necessary value. Which is connected to the OCR1A/PB1 slot on the Atmega328PBxMINI board. You can then produce a PWM type signal that can be adjusted by Subtracting from that ICR1 value. By experimenting with the values. You are able to configure the angles of the servo, this is all depending on it’s possible angles.

IR Sensors are a simple digital sensor that has two components. The first component is called the Transmitter/Emitter. This transmitter device emits an IR beam. The presence of that IR beam is then read by the second component; the receiver/collector. If this receiver cannot see the IR beam, a low bit is returned from the sensor, else a high bit is returned if the beam is in tact.

The Traffic Lights are a series of green, red and yellow LEDS attached to a backboard. They require four wires, one for each LED and one for ground. Since all the LEDs share the same ground it is fairly easy to wire and test these.

The buttons are a small circuit attached to ground and the board. The board could tell when even a ground signal was detected because an internal pull up resistor was set up along with configuring the PINC ports to Inputs for the software to then read from.