Hello, dear friend, you can consult us at any time if you have any questions, add WeChat: daixieit

EE3580 – Digital Systems

Laboratory Sheets

Laboratory 1 - Serial Terminal with Arduino

In this laboratory we use the I/O and serial interface of the Atmega32 microcontroller (MCU) on the

Arduino UNO.

Our goal is to send command through the serial line to enable/disable digital pins and to receive external inputs. The serial line will be used as a console to access external actuators (output lines) and receive input from external sensors (input lines).

In our setup, two output pins are connected to two LEDs and two inputs are received from two push- buttons as shown in Figure 1. Clearly, in real applications we will used more complex input/output devices than LED and push-buttons, but from the point of view of the microcontroller these digital inputs/outputs will be no different than simple devices we use.

This laboratory consists of four tasks of increasing difficulty. Task 4 code should be included in the Appendix of the final report.

Figure 1 Logical connections between MCU and components


1.           Arduino UNO (part of the kit)

2.            Breadboard

3.            1 resistors 1k

4.            1 resistors 10k

5.           2 LEDs

6.            1 Pushbuttons

7.            Multimeter

Figure 2 - Arduino connection for lab 1

Task1: Connecting and Testing

The LEDs are connected between pin PB4 and PB5 (respectively pin 12 and 13 on the Arduino header) and ground through a 1 kΩ resistor. The resistors in series to LEDs limit the current flowing through an LED. The normally open (NO) pushbuttons connected to PB3 (pin 11 on the Arduino header) to Vcc  uses a 10 kΩ pull-up resistor. The resistors on the push-buttons prevent the input line in high-impedance (“Z”) from floating (these resistors could be omitted using the internal pull-up resistors). NB: The pull- up resistor can be enabled using the Arduino function pinMode(pin, INPUT_PULLUP), which makes the pin an input with pull-up resistor.

Our first goal is to connect properly the buttons and LEDs and verify the connections. The picture in the Appendix will tell where to find the microcontroller pins on the Arduino headers. The resistors, LEDs and push-buttons will be lay down on a breadboard and connected to the Arduino headers through point-to-point wires (Figure 2).

To verify that everything is connected correctly we use simple sketches. A simple sketch should be used each time a new component is tested. In these sketches, we need to remember that PINB is an input register and PORTB is an output register, and that we are changing only one bit in these registers (resp. PB3, PB4 or PB5). Note that we accessed the register PORTB directly. However the same function can be done using the Arduino function pinMode() and digitalWrite() that do the same function.

1.   To verify that the LED on PB5 is connected correctly (similarly for PB6), we can write a short sketch as the one below and see if the LED blinks every second. Note that toggling ON and OFF the LED is done using a XOR and the mask 1<

void setup() {

DDRB = (1<


void loop()


PORTB ^= (1<



2.   To verify that the pushbutton on PB3 is connected correctly, we can write the sketch below and see the output on the Serial Monitor. NB: PB3 is the fourth least significant bit (LSB), so the output should be “8” when this is not pressed and “0” when pressed.

void setup() {

Serial.begin(9600); // initialized the UART


void loop()


// Print on the serial line the content

// the PINB register opportunely masked

Serial.println(PINB & (1<



Task2: Sending a command to the Arduino

Now that the electrical connections are completed and tested, we can start writing our input/output program with console communication. We use the Serial Monitor to input the command blink” . When Arduino UNO receives this command, it blinks both LEDs for 3 times (with an ON and OFF period is 200 ms for each pulse). Otherwise, the Arduino waits for instructions.

Functions used in Task2

To send a command to the microcontroller, we use the function getstring(), which is included in the library EE3580_lab1.h (see laboratory folder). The prototype of getstring() is the following:

void getstring(char* buffer);

•    When this function is called, the program stops and waits for user input from the Serial Monitor on the Arduino IDE (menu Tools->Serial Monitor). After that the user ends the input pressing return or the button Send, this function copies the characters in the char buffer passed as argument. The function also adds the final NULL character to the buffer. Thus, the buffer must be large enough to hold all the characters of the command and the final NULL.

NB: The Serial Monitor must be configured to include a Carriage Return at the end of the input (which can be done from the dropdown box at the bottom of the monitor window).

NBB: The variable buffer” is just a pointer! The actual buffer must be allocated as a local variable to toop() or as a global variable.

To determine if the string sent to the  microcontroller is the command  blink we use the function strcmp() included in the C standard library string.h:

int strcmp(const char *str1, const char *str2);

•   This function received the NULL-terminated strings str1 and str2. It returns 0 if the strings are the same, a positive number if str2 is after str1 in alphabetic order, or a negative number if str2 is before str1 in alphabetic order.

Program to write in Task 2

Write a program to do the following tasks in loop():

1.    Use getstring() to receive a string with the command an place it in a character buffer large enough to contain the command. The user inputs the string using the Serial Monitor (NB: Carriage Return should be configured so that the program includes the NULL character at the end of the command and the microcontroller knows where the string ends). The serial line communication should be initialised in setup() with Serial.begin(9600).

2.    If the command is blink”, both LEDs are blinked ten times and the program resume from 1. Otherwise the program just ignores the command.

Task3: Programming the Console

We modify the command blink” to specify which LED to blink and how many times. The command should have the following format:


where  and  are respectively the LED colour (e.g. “red” for the LED on PB4 and “green” for the one on PB5) and the number of blinking (max 20). The command name and the arguments are separated by any number of spaces or tabulations (blanks). The blinking pattern is the same of the previous task.

Functions used in Task3

The function sscanf(), which is included in the standard library (stdio.h), can be used to parse the buffer once that the command string has been received.

int sscanf(const char *str, const char *format, ...);

•   The function is the analogous of scanf() to get user input from keyboard, which was used in past courses, except that the input comes from a string. The string is parsed according to the format string and the results are placed at the address provided by the list of arguments.

Example:  sscanf(buffer, “%s %d %d”, mystr, &led, &rep);

The command is taken from buffer and interpreted according to “%s %d %d” formatting. The results are placed in the buffer mystr and the integers led and rep.

Program to write in Task 2

The Arduino sketch in Task 3 should do the following:

1.    Use getstring() to receive a string with the command.

2.    Parse the command using sscanf(). If the command is not “ blink”, the string is discarded, and no blinking is generated. NB: only the name of the command should be compared not the whole string!

3.    If the command is “ blink”, and the two arguments are in the range of possible parameters, the command is executed.

4.    If the  and   parameters are  not valid, the command  is discarded and the program restart from step 1.

Task4: Recording the inputs

In addition to blinking the LEDs when commands are issued as in Task 3, we want to record a timestamp (the time in milliseconds since the last reset) every time the button is pressed. The system should be able to memorise the timestamp of 10 button-presses. If the user presses the button more than 10 times, only the last 10 events are remembered.

NB: The system should be able to take a timestamp even when it waits for an input! This means that the two processes should run somehow in parallel.

Finally, the program should print the list of the events on the Serial Monitor when the command print” is sent from the console.

Functions used in Task4

In order to perform parallel operations, we use a periodic internal interrupt generated by Timer2. When the interrupt is triggered, the Interrupt Service Routine (ISR) is used to check the state of the button. The function should record a timestamp when it sees a transition between high and low on PB2 (falling edge).

The function setup_timer() in EE3580_lab1.h configures Timer2 to generate an interrupt every millisecond. This function should be called at setup time.

The configuration of Timer2 is explained in the Appendix. This function should be called from setup() to start the timer and corresponding ISR.

Programs to write in Task 4:

1.    Introducing the necessary global variables, write the function ISR(TIMER2_COMPA_vect). The first action of the function is to increment a counter that can be use as measure of the current time.

long int time;



// check button state, record timestamp


2.    In the loop() function, we use a background process to manage the input from the serial line. When a print command is sent, we print all the timestamps.

Laboratory 2 – Direct Digital Synthesis

In this laboratory we use the Arduino to build a signal generator. We will use the Waveform Generator (WG) implemented by Timer0 to produce a square wave in the audio frequency band. A simple speaker (buzzer) will  be  used to  play the sound and a  potentiometer to adjust either the volume or the frequency of the square wave. In a subsequent task the Digital Direct Synthesis technique will be used to generate waves other than square. A look-up table will allow us to define the shape of the wave. This will be both plotted by the scope (or the Serial Plotter integrated in the Arduino IDE) and played from the buzzer.

For these experiments we will connect the Arduino UNO pins as illustrated in Figure 3 (or Figure 4 which shows the Arduino headers – note that a different version of the potentiometer or buzzer may be provided that does not fit as shown). ADC0 is one of the possible inputs of the A/D Converter. ADC0 is connected to the centre of a voltage divider implemented by a potentiometer.

Turning the wheel of the potentiometer we shift a sliding contact. This changes the resistor values on the two legs of the voltage divider and varies the voltage on ADC between ground and Vcc . This voltage is  converted  in  a  10-bit  digital  number  (from  0  to  1023)  by  the  MCU  and  can  be  read  using analogRead().

Figure 3 - Connections between the microcontroller and components for Task 1 and 2


1.    Arduino UNO (part of the kit)

2.    Breadboard

3.    2 resistors 1k

4.    2 capacitors 1µF

5.    1 potentiometer 50k

6.    Multimeter (only on-campus)

7.    buzzer

8.    Oscilloscopes (only on-campus)

9. Figure 4 - Connections between Arduino and components

The buzzer is connected through a 1k resistor to the output pin OC0B of the Wave Generator (Arduino pin 5). The resistor limits the current through the buzzer and provide a correct bias. Not connecting it would produce a much louder sound!

ADC0 corresponds to A0 on the Arduino header as illustrated by the pinout diagram in the Appendix.

The following four tasks have increasing difficulty. Task 4 code should be included in the Appendix of the final report.

Task 1: Connecting and Testing

We check the connections first. The centre pin of the potentiometer is the sliding contact. The two ends of the resistance provide the same resistance independently of the position of the wheel.

Once that the potentiometer is connected, a sketch like the following can be used to determine if the connections are correct:

void setup() { Serial.begin(9600); }

void loop() { Serial.println(analogRead(0)); }

Arduino UNO implements a recursive Analog/Digital Converter (ADC), to which Arduino inputs A0-A5 are multiplexed. analogRead() is configured to provide 10 bit resolution, so it generates a number between 0 and 1023, but the ADC supports from 8 to 12 bit resolution.

In order to enable Timer0 to generate a square wave we use the function timer_setup () (see below). This function is added to the sketch and called from setup(). Once that the waveform generator

(WG) is activated, the interface produces a pulse with modulation (PWM) automatically no further programming is needed to change the state of the output pin (OC0B).

void timer_setup()



// Fast PWM, prescaler 256

TCCR0A = 0x23;

TCCR0B = 0x0C;

OCR0A = 141;

OCR0B = 107;

DDRD = (1<

sei ();


The prescaler rate has been set to N=256. The frequency F and the duty cycle ρ are calculated by the formulas below and the behaviour of the counter is illustrated in Figure 5:

F =


OCR0B + 1

OCR0A + 1

In this example the values of OCR0A and OCR0B have been chosen to output a square wave at F=440 Hz with duty cycle ρ=0.75.

Figure 5 - Changed of TCNT and output bit OC0B

Using the oscilloscope test the sketch and verify that the signal is correctly generated. Connect the probe to the pin generating the on the Arduino and the probe ground to GND on the Arduino. Centre and scale correctly the signal displayed on the screen. Using the measurements cursors verify that the signal has the correct duration of the ON period and OFF periods.

Task 2: Generating the square wave

We  modify  the  function  setup_timer () adding  two  arguments  that  respectively  specify  the frequency and the duty cycle:

void setup_timer(uint32_t freq, uint8_t duty_cycle);

The frequency is provided as 32bit integer (uint32_t). The duty cycle is provided as a fixed point 8bit integer (uint8_t) with a scaling factor of 256. This means as ρ varies between 0 and 1, the duty_cycle varies between 0 and 255.

Programs to write in Task2:

1.    Write the function setup_time() that takes the frequency and duty cycle arguments. The code should  update  Timer0  registers  only  when  the  frequency  is  in  the  range  of  possible frequencies.

2.   Test the function generating a square wave at 1 kHz and ρ=0.5 and one at 10 kHz and ρ=0.25. What parameter of the PWM is changed when the thresholds OCR0A is changed?

3.    Write code in the  loop() function that  uses the values from analogRead(0) to  update the frequency. NB: analogRead() returns a 0 and 1023 which must be mapped in the range of possible frequencies.

Task 3: Building the Low-pass filter & Testing

We filter the square wave through an RC low-pass filter to extract the continuous (DC) component (as explained in the lecture). Figure 6 shows the schematic diagram used in this task (as well as the Arduino diagram in Figure 7). The low-pass filter is obtained by the series of two RC groups (with RC=1 ms). The buzzer was removed due to the very high attenuation that would produce no output if amplification is not applied.

To  maximise the attenuation of the first  harmonics, we increase the frequency of the  PWM (the prescaler  is  set  to  N=1).  However,  we  increase  the  OCR0A  threshold  as  much  as  possible  (ie OCR0A=255) to have a larger range for OCR0B, which controls the duty cycle. Thus, when N=1 and OCR0A=255 we have F=62.5 kHz.

Figure 6 Connections for Task 3 including the RC low-pass filter

Figure 7 Arduino diagram for Task 3

The diagram in Appendix 3 shows the Bode plots of this filter. The attenuation (in decibels) and the phase shift (in degrees) are displayed as a function of the frequency in log scale. The -3dB bandwidth is about 70Hz and the attenuation is more than 60dBs above 5kHz.

Build the filter on the breadboard (as shown in Figure 7). The output of the low-pass filter is connected to the ADC input A1 (and can be read using analogRead(1)).

The Arduino sketch in Task3 checks that the connections are correct, and the DC component can be extracted correctly. The output of the low-pass filter can be visualised with an oscilloscope (only during the lab on campus) or using the Serial Plotter of the Arduino IDE (for students at home). In order to use the Serial Plotter, a series of numbers (each terminated by a newline) needs to be send through the serial lines.

Program in Task3:

1.    Use the function carrier_62k() in the with the prototype below, which produces a square wave at 62.5kHz with variable duty cycle (the function is included in the library file associated with this lab):

void carrier_62k(uint8_t duty_cycle);

where duty_cycle is a fixed-point number with scaling factor 256. The duty cycle should be associated to the value read from the potentiometer.

2.    Print through the serial line the values read from the output of the low-pass filter. The output of the Serial Plotter should be similar to the one in Figure 8 when the potentiometer is turned.

Figure 8 - Serial Plotted output

Task 4: Direct Digital Synthesis (DDS)

Using Direct Digital Synthesis (DDS), we generate various periodic waves (see lecture notes).

We use OCR0B as the modulating signal for the PWM generated by Timer0. OCR0B is updated by the periodic interrupt service routine (ISR) of Timer1 called every 125 µs (ie. at 8 kHz rate). The function timer1_setup() in the library function associated to this lab configures Timer1 to trigger the periodic interrupt. Timer1 configuration is similar to Timer0 except that the timer is a  16-bit counter. The Interrupt Enable (IE) flag for Timer1 (bit OCIE1A) in TIMSK1 should be set to trigger the ISR.

The values to assign to OCR0B are stored in a 256-entry look-up table (LUT) which is indexed by the fixed-point variable index with scaling factor 256. When index is converted to an integer, it points to the next entry in the LUT. After updating OCR0B, index is updated adding stepsize to point the next entry. stepsize is calculated to scan the entire table at a given rate. When the entire table has been scanned, index moves to the top in a circular fashion.

uint8_t wave[256];

uint16_t index, stepsize;

// Look up table

// index and stepsize

Appendix 2 illustrates the details of the algorithm.

Programs to write in Task4:

1.   Write the interrupt service routine ISR(TIMER1_COMPA_vect) that updates the index and the threshold of Timer0 to generate the waveform stored in the LUT.

To  test  the  function  the  LUT  is  initialised  using  setup_lut_triangular()  in  the  library. For students in the lab, the output should be tested using the oscilloscope.

For students at home the output should be tested using the Serial Plotter by reading the values from the ADC source A1 every several milliseconds. This can be done using a time variable which is increased periodically by the ISR.

if (time % 100 == 0)


It is also recommended to increase the baudrate of the serial line to at least 115kb/s to allow faster transfer of data from the MCU to the PC. The wave frequency should not exceed 2Hz to allow data to be transferred through the serial line.

2.   Write the function setup_lut_sawtooth and setup_lut_sinusoidal() that initialises the LUT to produce a sawtooth sinusoidal wave (setup_lut_triangular() can serve as an example).

The DDS generator should be able to change the frequency of the generated wave when the potentiometer is turned. The output measured from the Serial Plotter is shown in Figure 9 for a triangular and sawtooth waveform.

Figure 9 - Triangular and Sawtooth wave output using the Serial Plotter

Laboratory 3 – Heat Detector Alarm

In this laboratory we build a Printed Circuit Board (PCB) that implements an alarm system. The alarm consists of four components mounted on a board. In this lab you can either develop an Arduino shield

with the components or a stand-alone board with a small microcontroller (Atmel Attiny85). This laboratory consists of three tasks.


1. A thermistor sensor (RNTC): The thermistor is able to detect variations of temperature in a range. We will use an NTC thermistor (negative temperature coefficient), that is a variable resistor that reduces its resistance if the temperature increases. At room temperature (25°C) the resistance is 1KW (see the thermistor specs in lab folder). The thermistor is inserted in a voltage divider connected to one of the microcontroller ADC inputs. Temperature values can be read converting the digital number from the ADC register.

2. A buzzer: A speaker is connected to one of the Waveform Generator outputs of Timer0 (OC0B). A resistance is inserted in series to speaker to limit the current flowing into it. The buzzer will produce the alarm sound that alternates two frequencies when the temperature exceeds the threshold.

3. An LED: The LED stays ON in normal conditions to indicate that the alarm is working and flashes during period of alert when the sound is produced. A series resistor (220W) can be used to limit the current through the LED.

4. A button: The button is used to reset the alarm after an alert event, to test the alarm, and to produce a log of the last 10 alert when the maximum temperature was triggered.

5. An IC socket and Attiny85 MCU (option 2 only). The socket is soldered to the PCB rather than the microcontroller. The microcontroller is programmed on a breadboard and placed on the socket once the program works correctly.

6. Decoupling capacitor (option 2 only). The decoupling capacitor is installed between Vcc  and ground to stabilise the voltage supply of the microcontroller.

DesignSpark   PCB   can   be   downloaded   from   the   RS   components   website   (https://www.rs- online.com/designspark/pcb-software).   We   recommended  to   install  the   DesignSpark   on  your laptop/PC before coming to the lab. The software runs only under Windows. If you use a Mac or other computer, you can access the program installed on campus computers from campus PCs or through a virtual desktop (https://vdi.abdn.ac.uk), under the directory Physical Science/Engineering” .

Task 1 - Develop the PCB design of the board.

This task consists in completing a design template contained either uno_shield.zip or standalone.zip depending on the option that you choose (see the explanation below).

A design project includes three files: The project main file (.prj extension), a schematic (.sch extension), and a PCB layout (extension .pcb).

The components (both symbols and layout) are contained in the file components.zip in Lab 3 directory. To use the components, download the file on the local computer, unzip it and add the unzipped folder to the list of component folders in  DS. Adding the component folder is done through the Library Manager: click the library icon on the top menu (), select the Folders tab and press the Add” button on the right-hand side of the Folders and Search Order. After browsing for the folder and confirming its addition, make sure that the Folder Enable” tick-box is enabled. The folder will be available after pressing the Apply” button on the bottom-right corner.

Once   installed,   all  the   components   needed  for  the   design  will   be   available   in  the   library ee3580_components:  ATTINY85,  buzzer,  pushbutton,  spi_conn,  and  thermistor.  The  only  other symbols you need to use for this project are GND and Vcc that can be found on the in the pre-installed in DesignSpark” library.

NB: You must use the exact symbols Vcc and GND to represent the supply voltage and the ground. Any other symbol (eg. +5V or “0”) would create a mismatch between the PCB design and the schematic.

Choose one these two options:

1. Option 1: An Arduino shield. The Arduino shield is a 5cm x 5cm board with headers that slots into the Arduino UNO header. For this, download the file uno_shield.zip from MyAberdeen.

2. Option 2: A stand-alone board: A board mounts that mounts an Attiny85 microcontroller and is programmer through SPI. Data logs are also received through the SPI interface. For this, download the file standalone.zip from MyAberdeen.

A bare-bone Arduino shield or stand-alone board are provided respectively in the uno_shield.zip and standalone.zip files in the lab folder. When unzipped, the folder contains three files: the project file (containing the list of all files included in the project), a schematics file (.sch), and a PCB file (.pcb). Geometry setting are configured in the template, you just need to focus on your design.

Important: Once all the schematic is ready, you should forward it to the PCB layout using the menu Tools->Forward Design Changes. The changes need to be forwarded every time that schematic is modified.

Useful tip: Pressing F9 (or menu View->Interaction Bar) opens the components’ panel on the right- hand side. The components for this project will be listed under ee3580_components”, the Vcc and GND references is under “DesignSpark” . You can change the library from the drop-down menu.

Drawing the Schematics (Option 1 and 2)

The schematics displays the four headers of the Arduino board for option 1 or an empty sheet for option 2. The  headers are used as input/output for your design. These allow you to connect the components to the microcontroller and receive the supply voltage (5V) from the Arduino board.

We  start  by  arranging the  components  on the  schematics  sheet.  Click the  icon that  looks  like  a transistor () on the left menu (or press F3) and select the component from the list in the dialog box and drag it on the board.

Both in the schematics and  PCB design components can be rotated by pressing  R or mirrored by pressing F. Once placed, all the parts of a component symbol can be moved independently (e.g. the name, the connector names, etc.). Hence, the components can then be wired together using the wire tool on the left menu. Once connected, the cross on the component lead disappears.

The components do not necessarily need to be linked to be connected. For example, if you connect a component to a ground, this is automatically connected to all other grounds on the board. More in general, if two wires have the same “ net” name, they are connected. You can check the net name of a wire by hovering over it with or by right-clicking it and selecting Display net name” .

Sometimes two nets that should be separated are accidently connected. In this case, the program also joins the other occurrences of the two nets. This problem is difficult to spot because the nets can be displayed separated even when they are connected. Before transforming the design into the PCB, you need to check the nets and in case manually reassign the wires to the right net.” . If a wire is on the wrong net, right-click it and Change net…” would fix the problem.