No items found.

Designing a traffic light system using an arduino microcontroller

Designing a traffic light system using an arduino microcontrollerDesigning a traffic light system using an arduino microcontroller

What is an Arduino microcontroller and what is it used for

An Arduino is a microcontroller that can be used to control electronic devices such as lights, motors, and other devices. They can also be used to create interactive projects such as games and robots. Arduinos are some of the most popular microcontrollers used by hobbyists and makers because they are easy to use and relatively inexpensive.

Code generation for microcontrollers in Collimator

Collimator is a powerful simulation platform that allows users to fully test a system before deploying to real world hardware. Models in Collimator can be quite complex, involving multiple hardware components that run their own code. By using Submodels in Collimator to represent each physical processor, C code can be generated and exported in order to deploy the model on physical hardware such as an Arduino, or any other compatible microcontroller. This guide will demonstrate how to generate and deploy code to an Arduino Uno and connect the Collimator model to actual hardware inputs and outputs.

Arduino traffic light with pedestrian button Tutorial

In this project, we'll be using an Arduino Uno to simulate a traffic light on a busy street with a pedestrian crossing. 3 LEDs will represent the red, yellow, and green lights, and a button will represent the pedestrian crossing button. The traffic light stays green until a pedestrian arrives and pushes the button. The traffic light then changes to yellow, then red while the pedestrian crosses, and back to green again until the next crossing.

Overview of the traffic light model

Model overview

The Collimator model consists of a submodel representing the Arduino Uno, and a group which simulates a button press. Because Collimator generates code for submodels, the Collimator model must be arranged such that each hardware device that will use code generation is represented as a separate submodel. On the left, the Button group will generate a button press at 15 seconds so we can test the system within Collimator. On the right, the TrafficLight submodel will take an input for the pedestrian crossing button, and provide 3 outputs for the green, yellow, and red LEDs. The outputs are not connected to anything but will still be shown in the visualizer.

The TrafficLight submodel uses a counting loop, and some basic logic to determine the states of each LED. 

Traffic light submodel

On the left, a UnitDelay with a constant Adder that increases the loop by 1 with each time step. By itself, this loop would count the number of seconds since the start of the simulation.

A UnitDelay block to keep track of the time

If the button is pushed, the IfThenElse block before the UnitDelay will reset the loop to a value of 0. Now the value in this loop will represent the number of seconds since the button was last pushed.

The ifThenElse block resets the loop when the button is pressed.

After the button is pushed the light should turn yellow, so the value stored in the loop is now compared to YellowTime, which is 2 seconds. If it’s been less than 2 seconds, Y is on.

Light turns yellow after button push

Another similar test is used to determine if R is on.

Checking if the red light is on

YellowTime is subtracted from the counting loop value first because RedTime is the number of seconds red should be on, not the number of seconds since the last button push.

Determining RedTime from the time yellow has been on.

One more check is needed since the result of the subtractor might be negative, which would indicate that Y should be on an not R. With the highlighted parts here, R can only turn on if Y is off.

Ensuring R is only on when Y is off

Finally, the simplest test to see if G should be on is if neither Y or R are on.

Assigning green by the state of red and yellow

Here is the visualization tab after running the simulation. The light starts Yellow for 2 seconds, then turns Red for 4 seconds, then turns Green and stays Green until the button is pressed. Then the cycle repeats.

Exporting code from Collimator

With the TrafficLight submodel selected, click the button at the bottom of the properties side menu. 

Generating code in Collimator

You should see this popup at the top of the screen.

Code generation confirmation

A zip file will download.

Code zipfile

A closer look at the auto generated code

// content for startup() and main() entry points starts here.
// user needs to copy paste this content into their code.
// this code goes in the declaration part of your main() compilation unit.
#include "TrafficLight.h"
TrafficLight_vars_t TrafficLight_vars;
TrafficLight_vars_ptr_t TrafficLight_vars_ptr = &TrafficLight_vars;
// this function must be called once at the startup of your controller.
TrafficLight_initialize(dt_ms,TrafficLight_vars_ptr);
// this function is called any time you desire collimator generated code to update the outputs in collimator_io struct.
TrafficLight_main(dt_ms,TrafficLight_vars_ptr);



TrafficLight.H

// TrafficLight.h starts here
#include <math.h>
#include <stdint.h>
#include <stdbool.h>


typedef struct TrafficLight_state_t {
    double TrafficLight_UnitDelay;
} TrafficLight_state_t;


typedef TrafficLight_state_t * TrafficLight_state_ptr_t;


typedef struct TrafficLight_feedthrough_state_t {
    
} TrafficLight_feedthrough_state_t;


typedef TrafficLight_feedthrough_state_t * TrafficLight_feedthrough_state_ptr_t;


typedef struct TrafficLight_vars_t {
    bool TrafficLight_mid_0;
    bool TrafficLight_low_0;
    bool TrafficLight_button_0;
    double TrafficLight_Zero_0;
    double TrafficLight_YellowTime_0;
    bool TrafficLight_Y_0;
    double TrafficLight_UnitDelay_0;
    double TrafficLight_RedTime_0;
    bool TrafficLight_R_0;
    double TrafficLight_One_0;
    bool TrafficLight_Not_0_0;
    bool TrafficLight_LogicalOperator_0_0;
    double TrafficLight_IfThenElse_0;
    bool TrafficLight_G_0;
    bool TrafficLight_And_0_0;
    double TrafficLight_Adder_0_0;
    double TrafficLight_Adder_0;
} TrafficLight_vars_t;


typedef TrafficLight_vars_t * TrafficLight_vars_ptr_t;


extern void TrafficLight_initialize(double,TrafficLight_vars_ptr_t);
extern void TrafficLight_main(double,TrafficLight_vars_ptr_t);


TrafficLight.C

// TrafficLight.cpp starts here
#include "TrafficLight.h"


// state vector is local since writing to this can cause unexpected behavior.
TrafficLight_state_t TrafficLight_state;
TrafficLight_state_ptr_t TrafficLight_state_ptr = &TrafficLight_state;
TrafficLight_feedthrough_state_t TrafficLight_feedthrough_state;
TrafficLight_feedthrough_state_ptr_t TrafficLight_feedthrough_state_ptr = &TrafficLight_feedthrough_state;
double time = 0.0;


static inline void
TrafficLight_initialize(double dt_ms,
                        TrafficLight_vars_ptr_t TrafficLight_vars_ptr)
{
    TrafficLight_state_ptr->TrafficLight_UnitDelay = 0.000000000000000000e+00;
    TrafficLight_vars_ptr->TrafficLight_button_0 = 0;
}


static inline void
TrafficLight_main(double dt_ms,
                  TrafficLight_vars_ptr_t TrafficLight_vars_ptr)
{
    double sample_time = dt_ms * 0.001;
    TrafficLight_vars_ptr->TrafficLight_YellowTime_0 = 2.000000000000000000e+00;
    TrafficLight_vars_ptr->TrafficLight_Zero_0 = 0.000000000000000000e+00;
    TrafficLight_vars_ptr->TrafficLight_RedTime_0 = 4.000000000000000000e+00;
    TrafficLight_vars_ptr->TrafficLight_One_0 = 1.000000000000000000e+00;
    TrafficLight_vars_ptr->TrafficLight_UnitDelay_0 = TrafficLight_state_ptr->TrafficLight_UnitDelay;
    double _add_accum_8;
    _add_accum_8 = 0.000000000000000000e+00;
    _add_accum_8 = _add_accum_8 - TrafficLight_vars_ptr->TrafficLight_YellowTime_0;
    _add_accum_8 = _add_accum_8 + TrafficLight_vars_ptr->TrafficLight_UnitDelay_0;
    TrafficLight_vars_ptr->TrafficLight_Adder_0_0 = _add_accum_8;
    double _add_accum_9;
    _add_accum_9 = 0.000000000000000000e+00;
    _add_accum_9 = _add_accum_9 + TrafficLight_vars_ptr->TrafficLight_One_0;
    _add_accum_9 = _add_accum_9 + TrafficLight_vars_ptr->TrafficLight_UnitDelay_0;
    TrafficLight_vars_ptr->TrafficLight_Adder_0 = _add_accum_9;
    TrafficLight_vars_ptr->TrafficLight_low_0 = TrafficLight_vars_ptr->TrafficLight_UnitDelay_0 <= TrafficLight_vars_ptr->TrafficLight_YellowTime_0;
    TrafficLight_vars_ptr->TrafficLight_Y_0 = TrafficLight_vars_ptr->TrafficLight_low_0;
    TrafficLight_vars_ptr->TrafficLight_Not_0_0 = !(TrafficLight_vars_ptr->TrafficLight_low_0);
    TrafficLight_vars_ptr->TrafficLight_mid_0 = TrafficLight_vars_ptr->TrafficLight_Adder_0_0 <= TrafficLight_vars_ptr->TrafficLight_RedTime_0;
    if (TrafficLight_vars_ptr->TrafficLight_button_0) {
        TrafficLight_vars_ptr->TrafficLight_IfThenElse_0 = TrafficLight_vars_ptr->TrafficLight_Zero_0;
    }
    else {
        TrafficLight_vars_ptr->TrafficLight_IfThenElse_0 = TrafficLight_vars_ptr->TrafficLight_Adder_0;
    }
    TrafficLight_vars_ptr->TrafficLight_And_0_0 = TrafficLight_vars_ptr->TrafficLight_Not_0_0;
    TrafficLight_vars_ptr->TrafficLight_And_0_0 = TrafficLight_vars_ptr->TrafficLight_And_0_0 && TrafficLight_vars_ptr->TrafficLight_mid_0;
    TrafficLight_state_ptr->TrafficLight_UnitDelay = TrafficLight_vars_ptr->TrafficLight_IfThenElse_0;
    TrafficLight_vars_ptr->TrafficLight_R_0 = TrafficLight_vars_ptr->TrafficLight_And_0_0;
    TrafficLight_vars_ptr->TrafficLight_LogicalOperator_0_0 = TrafficLight_vars_ptr->TrafficLight_low_0;
    TrafficLight_vars_ptr->TrafficLight_LogicalOperator_0_0 = TrafficLight_vars_ptr->TrafficLight_LogicalOperator_0_0 || TrafficLight_vars_ptr->TrafficLight_And_0_0;
    TrafficLight_vars_ptr->TrafficLight_LogicalOperator_0_0 = !(TrafficLight_vars_ptr->TrafficLight_LogicalOperator_0_0);
    TrafficLight_vars_ptr->TrafficLight_G_0 = TrafficLight_vars_ptr->TrafficLight_LogicalOperator_0_0;
    
}


1. Functions

TrafficLight_initialize(dt_ms, TrafficLight_vars_ptr) is called in the setup() function of TrafficLight.ino. It is called once on startup, and never again.

TrafficLight_main(dt_ms, TrafficLight_vars_ptr) is the main function call which happens inside the loop() function. It must be called periodically, every dt_ms milliseconds.

2. Fields

Fields are used instead of variables. They can be accessed with an arrow operator: TrafficLight_vars_ptr->TrafficLight_G_0

Between every call to the main function, the Arduino needs to set input variables for the next time step, and get output variables from the last one.

3. Timing

When setting up with TrafficLight_initialize(dt_ms,TrafficLight_vars_ptr), dt_ms should be set to the intended time between loops. When calling TrafficLight_main(dt_ms,TrafficLight_vars_ptr) in the loop, dt_ms should equal the actual time since the last loop.

The Arduino Uno hardware setup

The Arduino Uno has 14 general purpose input/output (GPIO) pins. Some of these pins have special hardware capabilities such as Pulse Width Modulation (PWM), but for this project, only 4 basic GPIO pins are needed.

digitalRead and digitalWrite functions are used to read and write digital values to pins on the Arduino. To use these functions, you must first configure the pin’s mode using pinMode().
pinMode(pin, mode) can set a particular GPIO pin to INPUT or OUTPUT, enabling digitalRead or digitalWrite

digitalRead will return either a HIGH or LOW value, depending on the voltage level present on the specified pin.

digitalWrite will set the specified pin to either a HIGH or LOW value, depending on the value you specify.

1. Connect Power to the Breadboard

Connecting power to the breadboard

Two jumper wires are needed to connect the Arduino’s 5v power supply to the breadboard’s power rails.

2. Connect the LEDs

Connecting the LEDs

Connect the green, yellow, and red LEDs to pins 3, 4, and 5 respectively as shown. Use a 220 ohm current limiting resistor in series with each LED. Make sure the short leg on the LED goes to ground, and the long leg to the Arduino.

3. Add a button

Adding the Button

Add a button as shown. Connect one side to 5v, and the other side to pin 2. Add a 10k ohm pull down resistor between pin 2 and ground so that whenever the button is not being pressed, any latent charge is discharged to ground.

Flashing the Arduino board with Collimator's code

Setting up Arduino's IDE

The Arduino IDE is a free, open-source development environment used to write, compile, and upload code to an Arduino or compatible microcontroller. It is available for Windows, Mac, and Linux operating systems. This section will explain how to download and install the Arduino IDE on Windows, how to set it up for use with an Arduino Uno, and how to upload the blink sketch to test that everything works.

1. Download and Install the Arduino IDE

Download the Arduino IDE from the Arduino website.

Once the download is complete, open the file and follow the prompts to install the Arduino IDE.

2. Connect your Arduino Uno

Next, connect your Arduino Uno to your computer using a USB cable. Windows should automatically install the driver if needed.

3. Open the Arduino IDE

Once the installation is complete, open the Arduino IDE.

4. Select your board
Selecting the Arduino controller

Using the drop down selector at the top of the Arduino IDE, select Arduino Uno. Make sure that there is a COM port listed. If you don’t see a COM port here, then the Arduino IDE is not able to see the Arduino Uno. If you see a COM port but it says “Unknown” instead of Arduino Uno, select the board manually as shown below.

Selecting the Arduino controller
5. Upload the blink sketch

Now that everything is set up, we can upload a sketch to test that everything is working. In the Arduino IDE, go to File > Examples > 01.Basics > Blink. This will open the blink sketch in the IDE.

Click the upload button in the Arduino IDE to compile and upload the sketch to your Arduino Uno. Once the upload is complete, you should see the LED on the Arduino Uno board blink once per second.

Importing Collimator's code

1. Create a new project in Arduino IDE

    Create a new project in the Arduino IDE by clicking File > New. Save the project and give it a name such as “TrafficLight”

Empty TrafficLight project

2. Add two new files

Adding the generated code from Collimator

Click Sketch > Show Sketch Folder to open the folder containing all the files in this sketch. Right now, there is only TrafficLight.ino. Copy into this folder the TrafficLight.c and TrafficLight.h files from the downloaded zip file containing the generated code. Rename TrafficLight.c to TrafficLight.cpp.

Go back to the Arduino IDE.

Confirming the Collimator files

There should be two new tabs at the top for the two files added.

3. Refer to text file instructions for configuring your setup() and loop() functions

Open the TrafficLight.txt file from the same zip file.

TrafficLight.txt

After copying and pasting the generated code into the TrafficLight.ino file, it should look like this.

TrafficLight.txt onto TrafficLight.ino

4. Configuring the timer

After importing the code generated by Collimator, the loop() function must be configured to run the main function periodically. Since this example is not very time sensitive, it is fine to set dt_ms to a constant 1000 so our time step is 1 second. In the declarations at the top of the ino file, define dt_ms as shown.

Timmer variable to reset the loop

Add a delay at the end of the loop.

Adding a delay at the end of the loop

5. Initialization of hardware

Before any GPIO pins can be used for button input or LED output, the pinMode must be set.

Initializing the GPIO pins

6. Accessing parameters between loops

Check the button state using digitalRead() as shown.

Checking the button state

7. Set the LEDs

Update each LED’s state using digitalWrite() as shown.

Updating each LED's state

8. Upload the sketch onto the microcontroller

Press the arrow button to compile and upload the sketch. If there are no compilation errors, the sketch should start running within a few seconds and the LEDs will light up. 

The final main code
Try it in Collimator