Difference between revisions of "Lab Assignments"

From Embedded Systems Learning Academy
Jump to: navigation, search
(Created page with "= GPIO = == Objective == Interface your LPC17xx to a switch and an LED. Although the interface may seem simple, you do need to consider hardware design and know some of the f...")
 
Line 1: Line 1:
= GPIO =
+
= Hello World =
== Objective ==
+
1. Get the development environment from sourceforge.
Interface your LPC17xx to a switch and an LED.
 
  
Although the interface may seem simple, you do need to consider hardware design and know some of the fundamental of electricity. There are a couple of goals for us :
+
2. Compile a sample FreeRTOS sample project
*  No hardware damage if faulty software is written.
 
*  Circuit should prevent excess amount of current to avoid processor damage.
 
  
== Required Background ==
+
3. Load it onto the processor
*  You should know how to bit-mask variables
 
*  You should know how to wire-wrap
 
*  You should know fundamentals of electricity and the famous '''V = IR''' equation.
 
  
== GPIO ==
+
4. Add a terminal command to your project, have it do something creative (Like print temperature on the screen)
{| class="wikitable"
 
|-
 
|
 
GPIO stands for "General Purpose Input Output". Each pin can at least be used as an output or input.  In an output configuration, the pin voltage is either 0v or 3.3v.  In input mode, we can read whether the voltage is 0v or 3.3v.
 
  
You can locate a GPIO that you wish to use for a switch or an LED by first starting with the schematic of the board.  The schematic will show which pins are "available" because some of the microcontroller pins may be used internally by your development board.  After you locate a free pin, such as P2.0, then you can look-up the microcontroller user manual to locate the memory that you can manipulate.
 
  
|[[File:tutorial_gpio_design.png|center|frame|GPIO Design]]
+
= LED Switch =
|}
+
Work in groups of 2:
  
== Coding ==
+
*Interface an LED (such as P2.1) to "Board A", which we will refer to as "LED Board"
=== Hardware Registers ===
+
*Interface a switch (such as P2.2) to "Board B", which we will refer to as "SW Board" (switch board)
The hardware registers map to physical pins.  If we want to attach our switch and the LED to our microcontroller's PORT0, then here are the relevant registers and their functionality :
+
*Connect the LED Board and SW Board together using "Port 2"
{| class="wikitable"
+
*Example: P2.0 of LED board connects to P2.0 on the SW Board
|+LPC17xx Port0 Registers
+
*LED board Software is simple, whenever its P2.0 (which is interfaced to the SW Board) is detected HIGH, light up its LED (on P2.1), else turn it off
|-
+
*On the SW Board, use "External Interrupt" API (eint.h) to detect when the switch gets pressed
|'''LPC_GPIO0->FIODIR'''
+
*The callback should simply turn a flag (global variable) to true
|Direction of the port pins, 1 = output
+
*The callback MUST NOT POLL, and MUST NOT DELAY, and EXIT IMMEDIATELY since it is an interrupt function
|-
+
*On the SW Board, launch a separate task, and when the flag is true, toggle the P2.0 for at least 500ms
|'''LPC_GPIO0->FIOPIN'''
+
*Bonus points: Use a binary semaphore in the interrupt, and have the task wait on the semaphore instead of polling. See the FreeRTOS video about the binary semaphore
|
+
For the demo:
Read : Sensed inputs of the port pins, 1 = HIGH
 
Write : Control voltage level of the pin, 1 = 3.3v
 
|-
 
|'''LPC_GPIO0->FIOSET'''
 
|Write only : Any bits written 1 are OR'd with FIOPIN
 
|-
 
|'''LPC_GPIO0->FIOCLR'''
 
|Write only : Any bits written 1 are AND'd with FIOPIN
 
|}
 
  
<BR/>
+
The SW Board should detect its switch press by an external interrupt and toggle its P2.0
=== Switch ===
+
The LED Board should detect its own P2.0 and light up the LED
{| class="wikitable"
+
This assignment demonstrates the use of external interrupts, and how to interface different boards together. Furthermore, you should have learned the concepts of different tasks that you can run in FreeRTOS. In general, you should never have to poll for events, and you should use interrupt functionality as much as possible.  Turn in ONE SUBMISSION per group with the following:
|+ We will interface our switch to PORT0.2, or port zero's 3rd pin (counting from 0).
 
|-
 
|
 
<syntaxhighlight lang="c">
 
/* Make direction of PORT0.2 as input */
 
LPC_GPIO0->FIODIR &= ~(1 << 2);
 
  
/* Now, simply read the 32-bit FIOPIN registers, which corresponds to
+
Only turn in the relevant source code, such as main.cpp
* 32 physical pins of PORT0We use AND logic to test if JUST the
+
Indicate at the top of the file who you worked with(Do not have the other person turn in anything)
* pin number 2 is set
 
*/
 
if (LPC_GPIO0->FIOPIN & (1 << 2)) {
 
    // Switch is logical HIGH
 
}
 
else {
 
    // Switch is logical LOW
 
}
 
</syntaxhighlight>
 
|Note that the "inline" resistor is used such that if your GPIO is mis-configured as an OUTPUT pin, hardware damage will not occur from badly written software.
 
[[File:gpio_switch_circuit.png|center|frame|Switch Circuit]]
 
|}
 
  
=== LED ===
+
= Form Groups =
{| class="wikitable"
 
|+ We will interface our LED to PORT0.3, or port zero's 4th pin (counting from 0).
 
|-
 
|
 
<syntaxhighlight lang="c">
 
/* Make direction of PORT0.3 as OUTPUT */
 
LPC_GPIO0->FIODIR |= (1 << 3);
 
  
/* Setting bit 3 to 1 of IOPIN will turn ON LED
+
Communicate with one of the class ISA to setup your groups @ Canvas. Be sure to have your group name ready and use an appropriate group name.
  * and resetting to 0 will turn OFF LED.
 
*/
 
LPC_GPIO0->FIOPIN |= (1 << 3);
 
  
/* Faster, better way to set bit 3 (no OR logic needed) */
+
Failure to do so by the deadline will result in zero points for the assignment.  Be sure to do this before leaving the class.
LPC_GPIO0->FIOSET = (1 << 3);
 
  
/* Likewise, reset to 0 */
+
Also, order CAN transceiver hardware for your entire team (1 per person)
LPC_GPIO0->FIOCLR = (1 << 3);
 
</syntaxhighlight>
 
|Given below are two configurations of an LED.  Usually, the "sink" current is higher than "source", hence the active-low configuration is used more often.
 
[[File:gpio_led_active_low.png|center|frame|Active-low LED circuit]] [[File:gpio_led_active_high.png|center|frame|Active-high LED circuit]]
 
|}
 
  
== Assignment ==
+
= Serial Communication =
At the end of this lab, you should be familiar with how a microcontroller's memory can access physical pins.  Test your knowledge by doing the following:
 
*  Interface your board's pin to an external switch
 
*  Interface your board's pin to an LED
 
*  If the switch is pressed, light up an LED
 
*  Do not use any pre-existing library such as a GPIO class
 
  
= UART =
+
Use either UART2 or UART3 to communication between two boards.  You can work in groups of 2.
== Introduction ==
 
The objective of this lesson is to understand UART, and use two boards and setup UART communication between them.
 
  
'''UART''' stands for '''U'''niversal '''A'''synchronous '''R'''eceiver '''T'''ransmitterThere is one wire for transmitting data, and one wire to receive data.   A common parameter is the baud rate known as "bps" which stands for '''b'''its '''p'''er '''s'''econdIf a transmitter is configured with 9600bps, then the receiver must be listening on the other end at the same speed.
+
*Locate the UART pins on your board. 
 +
*Use the schematic or Wikipedia board article.
 +
*Interface to another UART on someone else's board. 
 +
*If you use UART2, you cannot use the same UART2 on the 2nd board (use UART1 instead).
 +
*Prove that both of your boards and communicate with each other.  Bonus points for doing something creative, such as one board acting like a random sensor, and the second board outputting the sensor reading on the LED display.
 +
*This assignment demonstrate how to communicate to another device using UARTThe UART driver is based on queues, and there is no need to poll for data to arrive, or for data to be sent. Size your queues appropriately and they shouldn't be too large or too smallFor example, if you service the received data every 100ms, then your queues should only  need to be as big as the number of characters you expect to receive within 100ms (38400bps can provide 400 chars in 100ms).
  
UART is a serial communication, so bits must travel on a single wire.  If you wish to send a '''char''' over UART, the char is enclosed within a '''start''' and a '''stop''' bit, so to send 8-bits of '''char''' data, it would require 2-bit overhead; this 10-bit of information is called a '''UART frame'''.  Let's take a look at how the character 'A' is sent over UART.  In ASCII table, the character 'A' has the value of 65, which in binary is: 0100-0001.  If you inform your UART hardware that you wish to send this data at 9600bps, here is how the frame would appear on an oscilloscope :
+
Turn in ONE SUBMISSION per group with the following:
[[File:uart_tutorial_frame.jpg|center|frame|UART Frame of 'A']]
 
  
A micrcontroller can have multiple UARTs in its hardware, and usually UART0 is interfaced to a "USB to serial" converter chip which is then connected to your computer. In this exercise, you will write a driver for UART-2 and attempt to communicate between two boards.
+
Only turn in the relevant source code, such as main.cpp for both boards
  
I encourage you to fully read this article first, and here is a video about the UART0 tutorial.  This is a FAST PACED video, so learn to pause the video and look over your LPC user manual frequently :) '''Note that I forgot to configure the PINSEL registers, which are covered by this tutorial below.'''
+
= Project Setup =
* [https://www.youtube.com/watch?v=RU_NUPprx2Y UART Driver Video]
+
*Setup your Project Report Template
 +
*Add information about the team members
 +
*Add a link to your GITLAB project and add users who can access your project:
 +
- My GITLAB username is simply, "preet"
 +
   
  
<BR/>
+
To copy the project template, follow these steps:
  
== UART Pins ==
+
Go to your class webpage:
Before you write a UART software driver, you need to understand the physical constraints and identify the UART pins on your processorA GPIO (general purpose input output) is a multi-purpose pin, and certain pins are used for certain functions.  For example, P0.0 and P0.1 on your LPC17xx processor can be used for an LED (output), a switch (input), or as UART transmitter and receive signals.  We will configure the microcontroller's internal MUX (multiplexor) to connect internal UART to external pins.
+
http://www.socialledge.com/sjsu/index.php?title=Realtime_OS_on_Embedded_Systems (Links to an external site.) (Links to an external site.)
 +
  (Links to an external site.)Go to "Project Template", click "Edit" and COPY the entire Wikipedia markup
 +
Paste this template for your project, and work on setting weekly goals in your schedule section.
  
[[File:tutorial_uart_pinsel.png|center|Find RXD2 and TXD2 of UART2]]
+
= CAN Communication =
<BR/>
 
[[File:tutorial_gpio_mux.png|center|Example MUX that we need to configure for a PIN selection]]
 
  
<BR/>
+
You can work in groups of 2:
  
== Clock System & Timing ==
+
*Build and interface the CAN transceiver with another person's CAN transceiver.
A crystal drives a processor clock, and it is usually less than 20Mhz. A processor usually uses a "PLL" or "phased-lock-loop" to generate a faster clock than the crystal. So, you could have a 4Mhz clock, and the PLL can be used to internally multiply the clock to provide 48Mhz to the processorThe same 48Mhz is then fed to processor peripherals, and sometimes you have a register that can divide this higher clock to slower peripherals that may not require a high clock rate.  Remember that lower clock speed means lower power consumption.
+
*Initialize the CAN Bus (read can.h at the drivers directory).
 +
*Use a simple periodic message (every 100ms) that is sent from BoardA to BoardB.
 +
*For example, if BoardA senses a switch pressed, send a 1-byte message with 0xAA, otherwise send 0x00 if button is not pressed
 +
*For robustness, if the CAN Bus turns off, simply turn it back on at 1Hz (every 1000ms)
 +
*On BoardB, simply light up an LED (or otherwise turn it off) based on the CAN message data
 +
*This assignment gives you an overview of practical use of the CAN Bus, and later, by utilizing the DBC file and auto-generation of code, sending and receiving data becomes very easy*While this provides the bare bone knowledge of how communication works, the future lectures will focus on the application layer while abstracting away the details of CAN messages's data encoding and decoding.
  
9600bps means that one bit takes 1/9600 = 104uS (micro-seconds) per bitThe idea is that we want to divide the peripheral clock to UART hardware by a number that yields roughly 104uS per bit.  The '''Software Driver''' section goes over how to configure your UART driver to divide the clock to yield the desired baud rate.
+
Although the real-time periodic scheduler was not discussed, it might be worth your time to simply turn it on (using #define) since it can help you send periodic messages fairly easilyThis will also help you prepare in advance for the future lectures.
  
[[File:uart_tutorial_clock.jpg|center|frame|Example clock system of LPC17xx]]
+
= Wiki Schedule =
  
== Hardware Design ==
+
Write a schedule that is a list of your team's milestonesThis is a tool for you to assign responsibilities and deadlines for your project's milestonesIn general, this should list out when the team as a whole expects a certain functionality to be delivered.
There is not much hardware design other than to locate UART-2 pins on your processor board and connecting these wires to the second boardEach pin on a microcontroller may be designed to provide specific featureSo the first thing to do is identify which physical pins can provide UART-2 signals.
 
  
After you identify the physical pins, you would connect these pins to the second board'''Remember that your TX pin should be connected to second board's RX pin''' and vice versa.  Connecting two TX pins together will damage your processor.  After you connect the Rx/Tx pairs together, you also need to connect the '''ground''' wire of two boards together.  Not connecting the ground reference together is essentially like asking the other board "How far is my hand from the ground" when the ground reference is missing.
+
The grading will depend on how well you have assigned your milestones and if the goals are measurableFor example, milestones such as "interface sensor and send sensor data over CAN messages" and "add a filter for the sensor data" are measurable goals, whereas, "interface sensor", and "send data" are too generic and un-measurable goals.
  
 +
= Periodic Scheduler =
  
== Software Driver ==
+
INDIVIDUAL HOMEWORK:
[[File:uart_tutorial_overview.jpg|center|frame|UART chapter summary]]
 
  
{|
+
Easy homework, use the periodic tasks to add two functions that they call, run your program, and then do the following:
|
 
The UART chapter on LPC17xx has a really good summary page on how to write a UART driver.  '''Read the register description of each UART register to understand how to write a driver.'''  This tutorial gives away answers but unless you spend 1-2 hours reading the UART chapter, you will forget this knowledge.  The datasheet shows many registers but remember that for a simple driver, we will not need interrupts so you can skip the sections that talk about the UART interrupts.
 
  
<BR/>
+
*Modify main.cpp to configure the periodic scheduler, and leave the terminal task present that you will use later in the assignment
Writing a uart initialization routine is simple except that some registers require a special setup to access themIn the sample code below, we power up UART0, and use the PINSEL register to configure the Rx/Tx Uart pins as the pin functionality. Finally, we just set the divider to achieve 9600bps. The exception here is that we have to read the datasheet carefully which states that the '''DLM''' and the '''DLL''' registers are only accessible if the '''DLAB''' bit is set at the '''LCR''' register.
+
*Design a Software based "filter"The simplest one could be to average a few readings but read and research more on Wikipedia (they even have Psuedocode)
 +
*Add the two new "Sensor" periodic callbacks, one at 1Hz, the other at 10Hz
 +
*In the 10Hz callback, design a Software filter to filter the Light sensor readings
 +
*In the 1Hz callback, print out the filtered sensor reading.
 +
*Do more experiments with the "Logging" capability
 +
*Log some data using LOG_INFO() or similar message from file_logger.h
 +
*Use "info 5000" command to see CPU utilization for 5 seconds.
 +
*Use "cat log.csv" to view the log file data.
  
Notice that four registers have the same address.  The UART divider registers are only accessible if DLAB bit is 1; this was done to protect accidental change of baud rate.  Furthermore, notice that the CPU is intelligent enough to know if you are accessing the RX or the TX register based on if the register is being read or written.
+
Attach your output to Canvas along with your code.
  
<syntaxhighlight lang="c">
+
= CAN TX / RX using Auto-gen code =
void uart0_init(void)
 
{
 
    LPC_SC->PCONP |= (1 << 3);      // Enable power to UART0
 
    LPC_SC->PCLKSEL0 &= ~(3 << 6);
 
    LPC_SC->PCLKSEL0 |=  (1 << 6);  // Uart clock = CPU / 1
 
  
    LPC_PINCON->PINSEL0 &= ~(0xF << 4); // Clear values
+
Work in groups of 2 by assuming one board is the DRIVER while the other board is the MOTOR or SENSOR.
    LPC_PINCON->PINSEL0 |= (0x5 << 4);  // Set values for UART0 Rx/Tx
 
  
    LPC_UART0->LCR = (1 << 7); // Enable DLAB
+
The objective of the assignment is to demonstrate CAN data handling using the auto-generated code. Follow these steps:
    /* See the formula picture to get more info.
 
    * Default values of fractional dividers simplifies the equation
 
    * Warning: You need to set DLM/DLL correctly, but if divider is small enough, it will fit inside DLL
 
    */
 
    LPC_UART0->DLM = 0;
 
    LPC_UART0->DLL = CPU_CLOCK / (16 * 9600) + 0.5);
 
  
    LPC_UART0->LCR = 3;        // 8-bit data
+
*Define a DBC message that your controller sends (TX)
}
+
*Define a DBC message that your controller receives (RX)
</syntaxhighlight>
+
*Attempt to recover the CAN Bus (from Bus OFF) at 1Hz (both Boards)
| [[File:uart_tutorial_dlab.jpg|center|frame|DLAB bit to access registers]]
+
*Send the TX message from BoardA at a fixed frequency, such as 10Hz
  [[File:uart_tutorial_formula.jpg|center|frame|Baud rate formula]]
+
*Attempt to receive the RX message (on BoardB) at the designated frequency, and light up an LED when the message enters the MIA state
|}
+
*Prove that the MIA handling is occurring as intended (simple LED test by disconnecting the board that sends you a periodic message).
 +
*Prove that valid data is being received and reacted upon (such as displaying received sensor reading on the LED display)
 +
*This is a powerful assignment that demonstrates how to send and receive data using the auto-generated code that is derived from your DBC file.  This will be the stepping stone of the overall CAN communication that will occur in your project.
  
<syntaxhighlight lang="c">
+
= Final Wiki Schedule =
char uart0_putchar(char out)
 
{
 
    LPC_UART0->THR = out;
 
    while(! (LPC_UART0->LSR & (1 << 6)));
 
    return 1;
 
}
 
</syntaxhighlight>
 
  
<BR/>
+
= DBC File =
To send a char over UART, the code looks incredibly easy; just two lines!  It is supposed to be very easy because the UART hardware is supposed to handle the UART frame, and send start bit, followed by 8-data bits, and a stop bit by simply writing the '''THR''' register.  The moment you write this register, the hardware will start shifting bits out of the wire.  The while loop is present because after you write the '''THR''' register, we want to wait until hardware is done sending the bits out of the wire otherwise writing the same register again will corrupt the on-going transfer.
+
Create your CAN communication format for each controller and link your DBC file to your Project report at your wikipedia page.
<BR/>
 
  
== Advanced Design ==
+
Make sure that the auto-generated code is created as intended and that you don't run into a DBC or the auto-generation issueIn other words, the auto-generated code should compile on all controllers.
What you've done so far is wrote a polling UART driver.  If you used 9600bps, and sent 1000 characters, your processor would basically enter a "busy-wait" loop and spend 1040ms to send 1000 bytes of dataYou can enhance this behavior by allowing your <code>uart_putchar()</code> function to enter data to a queue, and return immediately, and you can use the "THRE" or "Transmitter Holding Register Empty" interrupt indicator to remove your busy-wait loop while you wait for a character to be sent.
 
  
== Assignment ==
+
= CAN Communication Demo =
*  Locate the physical pins for a UART that is not already used by your board
 
*  Configure the PINSEL to use the pins for UART Rx/Tx
 
*  Initialize your UART at any baud rate
 
*  Write uart_putchar(char) and uart_getchar() functions
 
*  Interface your UART with someone else's board, and test the communication.
 
  
= SPI =
+
Demonstrate that each ECU is able to communicate over the CAN Bus
== SPI BUS ==
 
SPI stands for '''S'''erial '''P'''eripheral '''B'''us.  It is a high-speed, full-duplex bus that uses minimum of 3 wires to exchange data.  The popularity of this bus rose when SD cards (and its variants ie: micro-sd) officially supported this bus according to the SD specifications.  Furthermore, unlike UART in which you can only have one transmitter and a receiver, SPI bus can have one master and multiple slave devices.
 
  
=== SPI Bus Signals ===
+
*Code should utilize the auto-generated artifacts from the DBC file
{|
+
*MIA handling should occur on the messages that are expected to be received
|
+
*Upon MIA, an LED indicator should indicate that there is 1 or more message in the MIA state
* <code>MOSI --> </code>Master Out Slave In  (driven by master)
+
*In person demonstration should be given for this assignment and there is nothing to turn in otherwise.
* <code>MISO --> </code>Master In  Slave Out (driven by slave)
 
* <code>SCK&nbsp; --> </code>Clock (driven by master)
 
* <code>CS&nbsp;&nbsp;  --> </code>Chip-select signal, one per slave
 
| |[[File:spi_tutorial_conns.jpg|center|frame|SPI BUS Connections]]
 
|}
 
  
The '''CS''' signal selects one slave, and the slave takes over the MISO pin.  If a slave is not selected, then it shall leave the MISO pin in hi-z state.  If multiple slaves have their CS signal asserted, they will try to take control of the MISO pin and damage their MISO pins.  For example, if one slave drives the signal high (connect to 3.3v) and the other drives it low (connect to ground), then short-circuit will occur damaging this pin.
+
= Producer / Consumer Tasks =
  
The SCK signal can reach speed of 24Mhz and beyond, however, SD cards are usually limited to 24Mhz according to the specificationsFurthermore, any signal over 24Mhz on a PCB requires special design consideration to make sure it will not deteriorate, thus 24Mhz is the usual maximum. Furthermore, you need a CPU twice as fast as the speed you wish to run to support it. For example, to run at 24Mhz SPI, we need 48Mhz CPU or higher.  Because each wire is driven directly (rather than open-collector), higher speeds can be attained compared to 400Khz I2C bus.
+
*Create a task that polls the acceleration sensor, and determines the orientation of the board, such as "up, down, left, right"Create an enumeration such as "typdef enum { invalid, up, down, left, right, }orientation_t;
 +
*Create a task that is waiting on the orientation enumeration to be sent by the previous task.
 +
*Create a queue, and have the first task send orientation values every second to the queue.
 +
*Print something BEFORE and AFTER sending the enumeration value to the queue.
 +
*Print something immediately after the second task receives the data from the queue.
 +
*Use same priority for both tasks and note down the print-outs.
 +
*Alter the priority of second task to use higher priority, and note down the print-outs
 +
*Note down your results, and submit it at the top of your code submission.
  
<BR/>
+
= Project Prototypes =
  
== Hardware ==
+
Project prototypes are expected to demonstrate the following:
Suppose that you wanted to interface a single SPI bus to three SD cards, the following will need to be done :
 
*  Connect all MOSI, MISO, and SCK lines together
 
*  Connect individual CS lines of three SD cards to SPI master (your processor)
 
  
It is also recommended to provide a weak pull-up resistor on each of the SPI wires otherwise some devices like an SD card may not work.  50K resistor should work, however, lower resistor value can acheive higher SPI speeds.
+
*Reliable CAN communication between different ECUs
 +
*Basic obstacle avoidance (free-run mode)
 +
*Reliable sensor inputs (PCAN will be used by class mentors to assess)
 +
*Reasonable motor commands (PCAN will be used by class mentors to assess)
 +
*Extra credit if you go above and beyond and show even more progress such as a working Android app
  
<BR/>
+
= Git =
== Software Driver ==
 
Unlike UART, the SPI driver is incredibly easy.  The SPI is labeled as SSP on LPC17xx datasheet due to historic reasons, and this chapter in the datasheet shows the software setup very well.  After the SPI is initialized on the hardware pins, the next steps is to write an spi function that will exchange a byte.  Note that if the master wants to receive data, it must send a data byte out to get a data byte back.  The moment we write to the '''DR''' (data register) of the SPI peripheral, the MOSI will begin to send out the data.  At the same time, the MISO will capture the data byte back to '''the same DR register'''.  In other words, SPI bus is a forced full-duplex bus.
 
  
{|
+
Your GIT repositories will be audited to assess how much contribution you have made to your GIT project.  At minimum, there should be something checked-into the main branch to demonstrate that you know how to use GIT.
|<syntaxhighlight lang="c">
 
void spi1_Init()
 
{
 
    LPC_SC->PCONP |= (1 << 10);    // SPI1 Power Enable
 
    LPC_SC->PCLKSEL0 &= ~(3 << 20); // Clear clock Bits
 
    LPC_SC->PCLKSEL0 |=  (1 << 20); // CLK / 1
 
  
    // Select MISO, MOSI, and SCK pin-select functionality
+
= Project Prototypes 2 =
    LPC_PINCON->PINSEL0 &= ~( (3 << 14) | (3 << 16) | (3 << 18) );
 
    LPC_PINCON->PINSEL0 |= ( (2 << 14) | (2 << 16) | (2 << 18) );
 
  
    LPC_SSP1->CR0 = 7;          // 8-bit mode
+
The second version of the project prototypes should demonstrate additional functionality from the first, such as:
    LPC_SSP1->CR1 = (1 << 1);  // Enable SSP as Master
 
    LPC_SSP1->CPSR = 8;        // SCK speed = CPU / 8
 
}
 
  
char spi1_ExchangeByte(char out)
+
*Motor feedback being used to drive the car (the car should not get stuck on an up-hill ramp)
{
+
*Android/iOS app displaying useful data
    LPC_SSP1->DR = out;
+
*I/O board displaying useful data
    while(LPC_SSP1->SR & (1 << 4)); // Wait until SSP is busy
 
    return LPC_SSP1->DR;
 
}
 
</syntaxhighlight>
 
|[[File:spi_tutorial_summary.jpg|right|frame|SPI Driver from LPC17xx datasheet]]
 
|}
 
  
<BR/>
+
= Project Final Demo =
=== Multitasking Warnings ===
 
If your software runs multiple tasks, and these tasks can access SPI, care needs to be taken because if two CS signals are asserted at the same time, hardware damage will occur.  This leads to the topic of using a mutex (semaphore) under FreeRTOS and you can read the [[FreeRTOS_Tutorial | FreeRTOS tutorial]] to learn more.
 
  
<BR/>
+
= Project Wiki =
== SJ-One Board Driver Instructions ==
 
Preparation for the SPI driver:
 
* Note that when we refer to "SPI", it also means "SSP" in the LPC user manual
 
* Study the schematic, and take a note of which pins have the SSP1 or SPI#1 peripheral pin-out.  Note this down or draw this out.
 
* Study and read the SSP1 LPC user manual chapter a few times
 
* Study the schematic, and locate the CS pin for the SPI flash attached to SSP1, then write a simple GPIO driver for this to select and deslect this pin
 
* Read the SPI flash datasheet that shows the SPI transactions for read/write, signature read etc.
 
*:  Rev4 board has Adesto flash, and previous revisions have Atmel flash.
 
 
 
Writing the driver:
 
* First, initialize the pin MUX using the PINSEL register.  I believe the PINSEL register is part of the LPC_PINCON struct
 
* Read the SSP1 chapter, and follow the steps on the first page to power on the SSP peripheral, and then initialize the CR0 and the CR1 register to perform SPI master initializationaa
 
* Now, write the spi exchange byte function using the DR and the SR (status) register.
 
 
 
== Assignment ==
 
*  Write a driver for SSP#1
 
*:  This SPI is interfaced to SD card and SPI Flash memory
 
*:  You need just an <b><code>init()</code></b> routine along with <B><code>char byte_transfer(char)</code></b> function.
 
*  Identify the pin for SPI flash memory's chip-select signal (CS)
 
*  Read the SPI flash memory datasheet and read the '''signature''' of this device and display it using printf()
 
*:  This should be at <b><code>SJSU_Dev\ref_and_datasheets\datasheets</code></b>
 
*  Read the '''SPI Flash memory's status register''' and print information about '''each bit''' in a nicely formatted output.
 
*:  This isn't the SPI peripheral status register, read the SPI flash memory datasheet.
 
*  Extra credit:
 
*:  Read page zero (first 512 bytes), and print the following:
 
*::  Number of bytes per sector, number of sectors per cluster, and the total number of sectors.
 
*:  Hint: Use this: [http://www.win.tue.nl/~aeb/linux/fs/fat/fat-1.html FAT info]
 
*:: Hint#2:  You can use the API from spi_flash.h
 
 
 
=== FAT Information ===
 
Information about the FAT boot sector and MBR
 
 
 
The first sector of the flash contains a Master Boot Record (MBR) which has pointers to where the partitions are placed in the storage device. Please have a look at [https://en.wikipedia.org/wiki/Master_boot_record#Sector_layout this link] for the structure of MBR. The first 446 bytes is the bootstrap code area. After that there is a partition table with four entries and then a boot signature (0x55AA). Each entry is 16 bytes long and you can find the layout of each [https://en.wikipedia.org/wiki/Master_boot_record#PTE entry here].
 
 
 
One of the fields in the partition entry is [https://en.wikipedia.org/wiki/Partition_type#List_of_partition_IDs "partition type"]. This should tell us what type of filesystem is resident on the partition. In our case it should be FAT12 (0x01). The last 8 bytes in the partition entry will tell us the Logical Block Address (LBA) of the first absolute sector in the partition and the total number of sectors in the partition. Essentially, we want to read the sector pointed to by this LBA for the FAT boot sector. That sector will give us the required information (no of clusters, total sectors, etc.. ).
 
 
 
= I2C =
 
 
 
== Theory of Operation ==
 
I2C is prounced "eye-squared see".  It is also known as "TWI" because of the intial patent issues of this BUS.  This is a popular, low throughput (100-1000Khz), half-duplix BUS that only uses two wires regardless of how many devices are on this BUS.  Many sensors use this BUS because of its ease of adding to a system.
 
 
 
=== Open-Collector BUS ===
 
I2C is an open-collector BUS, which means that no device shall have the capability of internally connecting either SDA or SCL wires to power source.  The communication wires are instead connected to the power source through a "pull-up" resistor.  When a device wants to communicate, it simply lets go of the wire for it to go back to logical "high" or "1" or it can connect it to ground to indicate logical "0".
 
 
 
=== Pull-up resistor ===
 
Using a smaller pull-up can acheiver higher speeds, but then each device must have the capability of sinking that much more current.  For example, with a 5v BUS, and 1K pull-up, each device must be able to sink 5mA.
 
 
 
[http://www.falstad.com/circuit/#%24+1+5.0E-6+3.3115451958692312+28+5.0+50%0Ac+192+176+192+240+0+1.0E-7+4.950495049504418%0Aw+192+176+256+176+0%0Ag+192+304+192+320+0%0As+112+176+112+256+0+1+false%0Ag+112+256+112+272+0%0Aw+192+176+112+176+0%0Ar+192+176+192+80+0+10000.0%0AR+192+80+192+32+0+0+40.0+5.0+0.0+0.0+0.5%0Ap+112+176+64+176+0%0Aw+256+176+320+176+0%0Ag+320+304+320+320+0%0Ar+192+240+192+304+0+100.0%0Ac+272+176+272+240+0+1.0E-7+-0.0%0Ar+272+240+272+304+0+100.0%0Ag+272+304+272+320+0%0Ar+320+304+320+176+0+1000000.0%0Ax+3+93+159+99+0+24+I2C+Simulation%0Ax+2+116+132+120+0+14+Switch+Shows+an+I2C%0Ax+2+142+119+146+0+14+grounding+a+signal%0Ao+8+64+0+34+5.0+9.765625E-5+0+-1%0A I2C Circuit Simulation]
 
 
 
<BR/>
 
== Protocol Information ==
 
I2C was designed to be able to read and write memory on a slave device.  The protocol may be complicated, but a typical "transaction" involving read or write of a register on a slave device is simple granted a "sunny-day scenario" in which no errors occur.
 
 
 
'''The code given below illustrates I2C transaction split into functions, but this is the wrong way of writing an I2C driver.  An I2C driver should be "transaction-based" and the entire transfer should be carried out using a state machine.  The idea is to design your software to walk the I2C hardware through its state to complete an I2C transfer.'''
 
 
 
In the diagrams given below, your software should take the step given in the arrow, and the hardware will go to the next state granted that no errors occur.  To implement this in your software, you should follow the following steps :
 
#  Perform the action given by the arrow
 
#  Clear the "SI" (state change) bit for HW to take the next step
 
#  Wait for "SI" (state change) bit to set, then take the next action
 
 
 
The master will always initiate the transfer, and the device reading the data should always "ACK" the byte.  For example, the master sends the 8-bit address after the START condition and the addressed slave should ACK the 9th bit (pull the line LOW).  Likewise, when the master sends the first byte after the address, the slave should ACK that byte if it wishes to continue the transfer.
 
 
 
When the master enters the "read mode" after transmitting the read address after a repeat-start, the master begins to "ACK" each byte that the slave sends.  When the master "NACKs", it is an indication to the slave that it doesn't want to read anymore bytes from the slave.
 
 
 
<BR/>
 
=== Write Transaction ===
 
 
 
{| class="wikitable"
 
|-
 
| Code Sample
 
| State Machine
 
|-
 
|
 
 
 
A typical I2C write is to be able to write a register or memory address on a slave device.  Here are the steps:
 
#  Master sends START condition followed by device address.
 
#:  Device should then "ACK" using 9th bit.
 
#  Master sends device's "memory address" (1 or more bytes).
 
#:  Each byte should be ACK'd by slave.
 
#  Master sends the data to write (1 or more bytes).
 
#:  Each byte should be ACK'd by slave.
 
#  Master sends the STOP condition.
 
 
 
To maximize throughput and avoid having to do this for each memory location, the memory address is considered "starting address".  If we continue to write data, we will end up writing data to M, M+1, M+2 etc.
 
 
 
The ideal way of writing an I2C driver is one that is able to carry out an entire transaction given by the function below.  Note that the function only shows the different actions hardware should take to carry out the transaction, but your software should be state machine based as illustrated on the state machine diagram on the right.
 
 
 
<BR/>
 
<syntaxhighlight lang="c">
 
void i2c_write_slave_reg(void)
 
{
 
    i2c_start();
 
    i2c_write(slave_addr);
 
    i2c_write(slave_reg);
 
    i2c_write(data);
 
   
 
    /* Optionaly write more data to slave_reg+1, slave_reg+2 etc. */
 
    // i2c_write(data); /* M + 1 */
 
    // i2c_write(data); /* M + 2 */
 
 
 
    i2c_stop();
 
}
 
</syntaxhighlight>
 
|  [[File:tutorial_i2c_write.png|center|frame|I2C Write Transaction]]
 
|}
 
 
 
<BR/>
 
 
 
=== Read Transaction ===
 
 
 
{| class="wikitable"
 
|-
 
| Code Sample
 
| State Machine
 
|-
 
|
 
 
 
An I2C read is slightly more complex and involves more protocol to follow.  What we have to do is switch from "write-mode" to "read-mode" by sending a repeat start, but this time with an ODD address.  To simplify things, you can consider an I2C even address being "write-mode" and I2C odd address being "read-mode".
 
 
 
Again, the function shows what we want to accomplish.  The actual driver should use state machine logic to carry-out the entire transaction.
 
 
 
<BR/>
 
<syntaxhighlight lang="c">
 
void i2c_write_slave_reg(void)
 
{
 
    i2c_start();
 
    i2c_write(slave_addr);
 
    i2c_write(slave_reg);
 
 
 
    i2c_start();                  // Repeat start
 
    i2c_write(slave_addr | 0x01); // Odd address
 
   
 
    char data = i2c_read(0);      // NACK if reading last byte
 
 
 
    /* If we wanted to read 3 register, it would look like this:
 
    * char d1 = i2c_read(1);
 
    * char d2 = i2c_read(1);
 
    * char d3 = i2c_read(0);
 
    */
 
 
 
    i2c_stop();
 
}
 
</syntaxhighlight>
 
|  [[File:tutorial_i2c_read.png|center|frame|I2C Read Transaction]]
 
|}
 
 
 
<BR/>
 
== I2C Slave State Machine Planning ==
 
Before you jump right into the assignment, do the following:
 
*  Read and understand how an I2C master performs slave register read and write operation
 
*:  Look at existing code to see how the master operation handles the I2C state machine function
 
*:  This is important so you can understand the existing code base
 
*  Next to each of the master state, determine which slave state is entered when the master enters its state
 
*  Determine how your slave memory or registers will be read or written
 
 
 
''' It is important to understand the states, and use the datasheet to figure out what to do in the state to reach the next desired state given in the diagrams below. '''
 
 
 
=== Master Write ===
 
In the diagram below, note that when the master sends the "R#", which is the register to write, the slave state machine should save this data byte as it's INDEX location.  Upon the next data byte, the indexed data byte should be written.
 
 
 
[[File:tutorial_i2c_master_write_state.png|center|frame|I2C Master Write Transaction]]
 
 
 
=== Master Read ===
 
In the diagram below, the master will write the index location (the first data byte), and then perform a repeat start.  After that, you should start returning your indexed data bytes.
 
 
 
[[File:tutorial_i2c_master_read_state.png|center|frame|I2C Master Read Transaction]]
 
 
 
== Assignment ==
 
Extend the I2C base class to also support slave operation.  Test your I2C driver by using one board as a master, and another board as a slave.
 
#  Study <B><CODE>i2c_base.cpp</CODE></B>, particularly the following methods:
 
#*  <B><CODE>init()</CODE></B>
 
#*  <B><CODE>i2cStateMachine()</CODE></B>
 
#*:  Note that this function is called by the hardware interrupt asynchronously whenever I2C state changes.
 
#*:  The other I2C master will simply "kick off" the START state, and this function carries the hardware through its states to carry out the transaction.
 
#*  The functions you add to this base class are accessible by the I2C2 instance.
 
#  Add <B><CODE>initSlave()</CODE></B> method to the I2C to initialize the slave operation.
 
#*  Allow the user to supply a memory to be read or written by another master.
 
#  Extend the state machine for I2C slave operation.
 
#*  Study the CPU user manual first, and create a state machine diagram on paper.
 
#*  The first register supplied after the slave address should be used as an "offset" of the memory to read or write.
 
#  Demonstrate the following :
 
#*  Demonstrate that you are able to read and write the slave memory.
 
#*  For extra credit and bragging rights, create state machine diagrams, and if you can make better ones, I will use your diagrams at this wikipedia page :)
 
 
 
=== Sample Code ===
 
<syntaxhighlight lang="cpp">
 
#include "i2c2.hpp"
 
#include <stdint.h>
 
#include <stdio.h>
 
 
 
int main(void)
 
{
 
    I2C2& i2c = I2C2::getInstance(); // Get I2C driver instance
 
    const uint8_t slaveAddr = 0xC0;  // Pick any address other than the used used at i2c2.hpp
 
    uint8_t buffer[256] = { 0 };    // Our slave read/write buffer
 
 
 
    // high_level_init() will init() I2C, let's init slave
 
    i2c.initSlave(slaveAddr, &buffer, sizeof(buffer));
 
 
 
    // I2C interrupt will (should) modify our buffer.
 
    // So just monitor our buffer, and print and/or light up LEDs
 
    // ie: If buffer[0] == 0, then LED ON, else LED OFF
 
    uint8_t prev = buffer[0];
 
    while(1)
 
    {
 
        if (prev != buffer[0]) {
 
            prev = buffer[0];
 
            printf("buffer[0] changed to %#x\n", buffer[0]);
 
        }
 
    }
 
 
 
    return 0;
 
}
 
</syntaxhighlight>
 
 
 
=== Warning ===
 
Since the I2C state machine function is called from inside an interrupt, you may not be able to to use <B><CODE>printf()</CODE></B>, especially if you are running FreeRTOS.  As an alternative, use the debug printf methods from the <B><CODE>printf_lib.h</CODE></B> file.
 
 
 
= Interrupts =
 
== Introduction ==
 
This tutorial demonstrates how to use interrupts on a processor.  In general, you will understand the concept behind interrupts on any processor, but we will use the SJ-One board as an example.
 
 
 
=== What is an interrupt? ===
 
*  Hardware capability to break normal software flow to attend an urgent request
 
*  "An event that needs immediate attention"
 
 
 
== Science ==
 
The science behind interrupts lies in the hardware that allows the CPU to be interrupted.  Each peripheral in a microcontroller ''may be'' able to assert an interrupt to the CPU core, and then the CPU core would jump to the corresponding interrupt service routine ('''ISR''') to service the interrupt.
 
 
 
=== ISR Procedure ===
 
The following steps demonstrate what happens when an interrupt occurs :
 
*  CPU manipulates the PC (program counter) to jump to the ISR
 
*  '''IMPORTANT''': CPU will disable interrupts (or that priority level's interrupts until end of ISR)
 
*  Registers are saved before running the ISR (pushed onto the stack)
 
*  ISR is run
 
*  Registers are restored (popped from stack)
 
*  Interrupts are re-enabled (or that priority level's interrupt is re-enabled)
 
 
 
On some processors, the savings and restoring of registers is a manual step and the compiler would help you do it.  You can google "GCC interrupt attribute" to study this topic further.  On SJ-One board, which uses LPC17xx (ARM Cortex M3), this step is automatically taken care of by the CPU hardware.
 
 
 
=== The SW to HW Connection ===
 
Now that we understand how the CPU hardware services interrupts, we need to define how we inform the CPU WHERE our ISR function is located at.  There is something called an '''Interrupt Vector Table'''.  This table is nothing but addresses of functions that correspond to the microcontroller interrupts.  Specific interrupts use specific "slots" in this table, and we have to populate these spots with our software functions that service the interrupts.
 
[[File:tutorial_interrupts_vec_table.png|center|frame|HW Interrupt Vector Table]]
 
 
 
=== SJ-One (LPC17xx) Example ===
 
Through some magic of the compiler, and the linker script, the compiler is able to place the software interrupt vector table at a specific location that the CPU expects the interrupt vector table to be located at.  This connects the dots about how the CPU is able to determine WHERE your interrupt service routines are located at.  From there on, anytime a specific interrupt occurs, the CPU is able to fetch the address and make the JUMP.
 
[[File:tutorial_interrupts_sw_vec_table.png|center|frame|SW Interrupt Vector Table]]
 
 
 
=== GCC Magic ===
 
In the SJ-One sample project, each ISR is named, but is labeled as a "weak" function.  What this means in the land of GCC (compiler) is that unless the same function name is defined somewhere else, the weak function serves as the ISR function.  If you do define the function name somewhere else, it will override the "weak" function and the compiler will place the address of your new function into the interrupt vector table.
 
 
 
== Setup ISR on SJ-One ==
 
The first step is to be able to locate the real-name of the "weak" ISR function.  For example, you can locate a UART0 ISR function that is weak, and we will override with our new function.  The only thing to keep in mind is that due to "C++ Mangling" of function names, if your ISR is located in a *.cpp file, you will need to enclose it into '''extern "C"''' tags.  See below for examples:
 
 
 
<syntaxhighlight lang="c">
 
/***************/
 
/* my_file.cpp */
 
 
 
    extern "C"
 
    {
 
        void UART0_IRQHandler()
 
        {
 
            /* Your ISR */
 
        }
 
    }
 
 
 
/*************/
 
/* my_file.c */
 
 
 
    /* extern tag not needed for a C file */
 
    void UART0_IRQHandler()
 
    {
 
        /* Your ISR */
 
    }
 
 
 
</syntaxhighlight>
 
 
 
<BR/>
 
=== What to do inside an ISR ===
 
Do very little inside an ISR.  When you are inside an ISR, the whole system is blocked (other than higher priority interrupts).  If you spend too much time inside the ISR, then you are destroying the realtime operating system principle and everything gets clogged because of you :(
 
 
 
With that said, here is the general guideline
 
*  Spend as little time as possible. DO NOT POLL FOR ANYTHING.
 
*  If you are using FreeRTOS API, you must use '''FromISR''' functions only!
 
*  Most important: Clear the source of the interrupt
 
*:  For example, if interrupt was for rising edge of a pin, clear the "rising edge" bit such that you will not re-enter into the same interrupt function.
 
 
 
=== ISR processing inside a FreeRTOS Task ===
 
It is a popular scheme to have an ISR quickly exit, and then resume a task or thread to process the event.  For example, if we wanted to write a file upon a button press, we don't want to do that inside an ISR because it would take too long and block the system.  What we can do instead is have the ISR "give" a semaphore, and a task to block upon a semaphore.
 
 
 
What you may argue with the example below is that we do not process the ISR immediately, and therefore delay the processing.  But you can tackle this scenario by resuming a HIGHEST priority task.  Immediately, after the ISR exits, due to the ISR "yield", FreeRTOS will resume the high priority task immediately rather than servicing another task.
 
 
 
<syntaxhighlight lang="c">
 
/* Declare and create the semaphore in main() */
 
SemaphoreHandle_t gButtonPressSemaphore = NULL;
 
 
 
void my_button_press_isr(void)
 
{
 
    long yield = 0;
 
    xSemaphoreGiveFromISR(gButtonPressSemaphore, &yield);
 
    portYIELD_FROM_ISR(yield);
 
}
 
 
 
void button_press_task(void *p)
 
{
 
    while(1) {
 
        if (xSemaphoreTake(gButtonPressSemaphore, portMAX_DELAY)) {
 
            /* Process the interrupt */
 
        }
 
    }
 
}
 
 
 
void main(void)
 
{
 
    gButtonPressSemaphore = xSemaphoreCreateBinary();
 
   
 
    /* TODO: Hook up my_button_press_isr() using eint.h */
 
 
 
    /* TODO: Create button_press_task() and start FreeRTOS scheduler */
 
}
 
 
 
</syntaxhighlight>
 
 
 
<BR/>
 
 
 
== Assignment ==
 
Write the implementation of <B><CODE>eint.c</CODE></B> from scratch.  Before you get any ideas, and to make sure you are not biased, just erase this file and begin with an empty mind.
 
 
 
#  From the <B><CODE>startup.cpp</CODE></B> file, identify the name of the function for EINT3
 
#*  This interrupt function is used for Port0 and Port2 interrupts
 
#*  When either EINT3, Port0, or Port2 interrupt occurs, this function will execute
 
#  Allow the user to specify the pin number and a callback function.
 
#*  When the interrupt occurs, make the callback for the user function
 
#  Test your implementation
 
#*  Attach a couple of switches on Port2, and ensure that the callbacks are made correctly.
 
#*  If SW1 is pressed, it should go to callback1
 
#*  If SW2 is pressed, it should go to callback2
 
#  Hints:
 
#*  LPC_GPIOINT structure can configure rising or falling edges for Port0 and Port2
 
#*  You must CLEAR the source of interrupt by using either IO0IntClr or IO2IntClr otherwise your interrupt will go to an infinite loop.
 
#*  You cannot use <B><CODE>printf()</CODE></B> to print anything from inside an ISR (if FreeRTOS is running), but you can use the API from <B><CODE>printf_lib.h</CODE></B>
 
 
 
= FreeRTOS Tasks =
 
The objective of this assignment is to show you how to create a FreeRTOS task a few different ways.  The [[FreeRTOS Tutorial]] is definitely a must read before going through this lesson.
 
 
 
== FreeRTOS "Hello World" Task ==
 
A task just needs memory for its stack and an infinite loop.  To prevent "hogging" the CPU, you should use a delay such that the CPU can be allocated to other tasks.  Here is the simplest FreeRTOS task:
 
 
 
<syntaxhighlight lang="cpp">
 
void hello_world_task(void* p)
 
{
 
    while(1) {
 
        puts("Hello World!");
 
        vTaskDelay(1000);
 
    }
 
}
 
 
 
int main()
 
{
 
    xTaskCreate(hello_world_task, (signed char*)"task_name", STACK_BYTES(2048), 0, 1, 0);
 
    vTaskStartScheduler();
 
 
 
    return -1;
 
}
 
</syntaxhighlight>
 
 
 
<BR/>
 
 
 
== C++ based FreeRTOS task ==
 
As a project gets more complex, it becomes difficult to manage initialization and share queue or semaphore handles.  This was the motivation to create a C++ based FreeRTOS task.
 
 
 
=== Share "Objects" ===
 
A task can "share" its pointers, handles, or "objects" with another task by name. This way, we don't have to worry about declaring handles into a common header file, hence we do not plague the global namespace :)  See the next examples on how a task can share a handle with another task by an intuitive string name.
 
 
 
=== C++ Task ===
 
<syntaxhighlight lang="cpp">
 
 
 
/// IDs used for getSharedObject() and addSharedObject()
 
typedef enum {
 
  shared_SensorQueueId,
 
} sharedHandleId_t;
 
 
 
/// Orientation type enumeration
 
typedef enum {
 
    invalid,
 
    left,
 
    right,
 
} orientation_t;
 
 
 
class orient_compute : public scheduler_task
 
{
 
    public:
 
        orient_compute(uint8_t priority) : scheduler_task("compute", 2048, priority)
 
        {
 
            /* We save the queue handle by using addSharedObject() */
 
            QueueHandle_t my_queue = xQueueCreate(1, sizeof(orientation_t));
 
            addSharedObject(shared_SensorQueueId, my_queue);
 
        }
 
 
 
        bool run(void *p)
 
        {
 
            /* Compute orientation here, and send it to the queue once a second */
 
            orientation_t orientation = invalid;
 
            xQueueSend(getSharedObject(shared_SensorQueueId), &orientation, portMAX_DELAY);
 
 
 
            vTaskDelay(1000);
 
            return true;
 
        }
 
};
 
 
 
class orient_process : public scheduler_task
 
{
 
    public:
 
        orient_process (uint8_t priority) : scheduler_task("process", 2048, priority)
 
        {
 
            /* Nothing to init */
 
        }
 
 
 
        bool run(void *p)
 
        {
 
            /* We first get the queue handle the other task added using addSharedObject() */
 
            orientation_t orientation = invalid;
 
            QueueHandle_t qid = getSharedObject(shared_SensorQueueId);
 
 
 
            /* Sleep the task forever until an item is available in the queue */
 
            if (xQueueReceive(qid, &orientation, portMAX_DELAY))
 
            {
 
            }
 
 
 
            return true;
 
        }
 
};
 
</syntaxhighlight>
 
 
 
Note that a better design is to minimize the use of <b><code>getSharedObject()</code></b>.  So it is recommended that the creator of the handle add the shared object in its <b><code>init()</code></b>, and other tasks can store the handle in their <b><code>taskEntry()</code></b> function.
 
<BR/>
 
 
 
=== Add the task in main() ===
 
<syntaxhighlight lang="c">
 
int main()
 
{
 
    scheduler_add_task(new orient_compute(PRIORITY_LOW));
 
    scheduler_add_task(new orient_process(PRIORITY_LOW));
 
    scheduler_start();
 
    return 0;
 
}
 
</syntaxhighlight>
 
 
 
<BR/>
 
== Assignment ==
 
This assignment is based on SJ-One board, but you can alter the requirement to fit your own hardware.
 
#  Create a task (task1) that computes the orientation of the board.
 
#:  You can use the acceleration sensor to figure out the orientation of the board
 
#:  Send the orientation enumeration, such as "up", "down", "left", "right" to a queue every 1 second
 
#  Create another task (task2) that waits on the queued item
 
#:  If the orientation is left or right, light up the LEDs (otherwise turn them off)
 
#  Note down the observations by doing the following:
 
#:  Print a message before and after sending the orientation to the queue
 
#:  Print a message after the second task receives an item from the queue
 
#:  Use the same priority for both tasks, and note down the order of the print-outs
 
#:  Use higher priority for the receiving task, and note down the order of the print-outs.
 
#  Create a terminal command: "orientation on" and "orientation off"
 
#:  If orientation is commanded on, resume the task1, otherwise suspend it
 
#:  See code below on hints of how this command can get control of another task.
 
#  Answer the following questions:
 
#:  What if you use ZERO block time while sending an item to the queue, will that make any difference?
 
#:  What is the purpose of the block time during xQueueReceive() ?
 
 
 
<syntaxhighlight lang="c">
 
// At the terminal tasks taskEntry() function :
 
bool terminalTask::taskEntry()
 
{
 
    cp.addHandler(orientationCmd,  "orientation", "Two options: 'orientation on' or 'orientation off'");
 
}
 
 
 
// Somewhere else:
 
CMD_HANDLER_FUNC(orientationCmd)
 
{
 
    // Our parameter was the orientation tasks' pointer, but you may want to check for NULL pointer first.
 
    scheduler_task *compute = scheduler_task::getTaskPtrByName("compute");
 
 
 
    // You can use FreeRTOS API or the wrapper resume() or suspend() methods
 
    if (cmdParams == "on") {
 
        vTaskResume(compute->getTaskHandle());  // Can also use: compute->resume();
 
    }
 
    else {
 
        vTaskSuspend(compute->getTaskHandle()); // Can also use: compute->suspend();
 
    }
 
   
 
    return true;
 
}
 
</syntaxhighlight>
 
 
 
= FreeRTOS Application Programming =
 
In this project, we will attempt to "combine" all the FreeRTOS knowledge into a single assignment.
 
 
 
 
 
After completing the assignment, you will get a sense of how the CPU is utilized, and how to use a new FreeRTOS event group API.  All together, you should achieve a better sense of designing your tasks and using the File I/O for debugging purposes.
 
 
 
 
 
== Assignment ==
 
Please follow the steps precisely in order to complete the objectives of the assignment.  If you use the C++ FreeRTOS framework, it should make the assignment significantly easy.
 
 
 
 
 
#  Create a '''<code>producer task</code>''' that takes 1 light sensor value every 1ms.
 
#*  After collecting 100 samples (after 100ms), compute the average.
 
#*  Write average value every 100ms (avg. of 100 samples) to the '''<code>sensor queue</code>'''.
 
#*  Use medium priority for this task
 
#  Create a '''<code>consumer task</code>''' that pulls the data off the '''<code>sensor queue</code>'''
 
#*  Use infinite timeout value during queue receive API
 
#*  Open a file (sensor.txt), and append the data to an output file on the SD card.
 
#*  Save the data in this format: '''<code>printf("%i, %i\n", time, light)"</code>'''
 
#*  Note that if you write and close a file every 100ms, it may be very inefficient, so try to come up with a better method such that the file is only written once a second or so...
 
#*  Use medium priority for this task
 
#  At the end of the loop of each task, set a bit using FreeRTOS event group API.
 
#*  At the end of each loop of the tasks, set a bit using the '''<code>xEventGroupSetBits()</code>'''
 
#*  Task 1 should set bit1, Task 2 should set bit2 etc.
 
#  Create a '''<code>watchdog task</code>''' that monitors the operation of the two tasks.
 
#*  Use high priority for this task.
 
#*  Use a timeout of 1 second, and wait for all the bits to set.  If there are two tasks, wait for bit1, and bit2 etc.
 
#*  If you fail to detect the bits are set, that means that the other tasks did not reach the end of the loop.
 
#*  In the event of failed to detect the bits, append a file (stuck.txt) with the information about which task may be "stuck"
 
#*  Open the file, append the data, and close the (stuck.txt) file to flush out the data immediately.
 
#*  Extra Credit: Every sixty seconds, save the CPU usage info to a file named "cpu.txt".  See terminal command "infoHandler" for reference.  Open the file, write the file, and close it immediately so the data is immediately flushed.
 
#  Create a terminal command to "suspend" and "resume" a task by name.
 
#*  "task suspend task1" should suspend a task named "task1"
 
#*  "task resume task2" should suspend a task named "task2"
 
#  Run the system, and under normal operation, you will see a file being saved with sensor data values.
 
#*  Plot the file data in Excel to demonstrate.
 
#  Suspend the producer task.  The watchdog task should display a message and save relevant info to the SD card.
 
#  Let the system run for a while, and note down the CPU usage in your text file.
 
 
 
What you created is a "software watchdog".  This means that in an event when a loop is stuck, or a task is frozen, you can save relevant information such that you can debug at a later time.
 

Revision as of 14:45, 30 August 2016

Hello World

1. Get the development environment from sourceforge.

2. Compile a sample FreeRTOS sample project

3. Load it onto the processor

4. Add a terminal command to your project, have it do something creative (Like print temperature on the screen)


LED Switch

Work in groups of 2:

  • Interface an LED (such as P2.1) to "Board A", which we will refer to as "LED Board"
  • Interface a switch (such as P2.2) to "Board B", which we will refer to as "SW Board" (switch board)
  • Connect the LED Board and SW Board together using "Port 2"
  • Example: P2.0 of LED board connects to P2.0 on the SW Board
  • LED board Software is simple, whenever its P2.0 (which is interfaced to the SW Board) is detected HIGH, light up its LED (on P2.1), else turn it off
  • On the SW Board, use "External Interrupt" API (eint.h) to detect when the switch gets pressed
  • The callback should simply turn a flag (global variable) to true
  • The callback MUST NOT POLL, and MUST NOT DELAY, and EXIT IMMEDIATELY since it is an interrupt function
  • On the SW Board, launch a separate task, and when the flag is true, toggle the P2.0 for at least 500ms
  • Bonus points: Use a binary semaphore in the interrupt, and have the task wait on the semaphore instead of polling. See the FreeRTOS video about the binary semaphore

For the demo:

The SW Board should detect its switch press by an external interrupt and toggle its P2.0 The LED Board should detect its own P2.0 and light up the LED This assignment demonstrates the use of external interrupts, and how to interface different boards together. Furthermore, you should have learned the concepts of different tasks that you can run in FreeRTOS. In general, you should never have to poll for events, and you should use interrupt functionality as much as possible. Turn in ONE SUBMISSION per group with the following:

Only turn in the relevant source code, such as main.cpp Indicate at the top of the file who you worked with. (Do not have the other person turn in anything)

Form Groups

Communicate with one of the class ISA to setup your groups @ Canvas. Be sure to have your group name ready and use an appropriate group name.

Failure to do so by the deadline will result in zero points for the assignment. Be sure to do this before leaving the class.

Also, order CAN transceiver hardware for your entire team (1 per person)

Serial Communication

Use either UART2 or UART3 to communication between two boards. You can work in groups of 2.

  • Locate the UART pins on your board.
  • Use the schematic or Wikipedia board article.
  • Interface to another UART on someone else's board.
  • If you use UART2, you cannot use the same UART2 on the 2nd board (use UART1 instead).
  • Prove that both of your boards and communicate with each other. Bonus points for doing something creative, such as one board acting like a random sensor, and the second board outputting the sensor reading on the LED display.
  • This assignment demonstrate how to communicate to another device using UART. The UART driver is based on queues, and there is no need to poll for data to arrive, or for data to be sent. Size your queues appropriately and they shouldn't be too large or too small. For example, if you service the received data every 100ms, then your queues should only need to be as big as the number of characters you expect to receive within 100ms (38400bps can provide 400 chars in 100ms).

Turn in ONE SUBMISSION per group with the following:

Only turn in the relevant source code, such as main.cpp for both boards

Project Setup

  • Setup your Project Report Template
  • Add information about the team members
  • Add a link to your GITLAB project and add users who can access your project:

- My GITLAB username is simply, "preet"


To copy the project template, follow these steps:

Go to your class webpage: http://www.socialledge.com/sjsu/index.php?title=Realtime_OS_on_Embedded_Systems (Links to an external site.) (Links to an external site.)

(Links to an external site.)Go to "Project Template", click "Edit" and COPY the entire Wikipedia markup

Paste this template for your project, and work on setting weekly goals in your schedule section.

CAN Communication

You can work in groups of 2:

  • Build and interface the CAN transceiver with another person's CAN transceiver.
  • Initialize the CAN Bus (read can.h at the drivers directory).
  • Use a simple periodic message (every 100ms) that is sent from BoardA to BoardB.
  • For example, if BoardA senses a switch pressed, send a 1-byte message with 0xAA, otherwise send 0x00 if button is not pressed
  • For robustness, if the CAN Bus turns off, simply turn it back on at 1Hz (every 1000ms)
  • On BoardB, simply light up an LED (or otherwise turn it off) based on the CAN message data
  • This assignment gives you an overview of practical use of the CAN Bus, and later, by utilizing the DBC file and auto-generation of code, sending and receiving data becomes very easy. *While this provides the bare bone knowledge of how communication works, the future lectures will focus on the application layer while abstracting away the details of CAN messages's data encoding and decoding.

Although the real-time periodic scheduler was not discussed, it might be worth your time to simply turn it on (using #define) since it can help you send periodic messages fairly easily. This will also help you prepare in advance for the future lectures.

Wiki Schedule

Write a schedule that is a list of your team's milestones. This is a tool for you to assign responsibilities and deadlines for your project's milestones. In general, this should list out when the team as a whole expects a certain functionality to be delivered.

The grading will depend on how well you have assigned your milestones and if the goals are measurable. For example, milestones such as "interface sensor and send sensor data over CAN messages" and "add a filter for the sensor data" are measurable goals, whereas, "interface sensor", and "send data" are too generic and un-measurable goals.

Periodic Scheduler

INDIVIDUAL HOMEWORK:

Easy homework, use the periodic tasks to add two functions that they call, run your program, and then do the following:

  • Modify main.cpp to configure the periodic scheduler, and leave the terminal task present that you will use later in the assignment
  • Design a Software based "filter". The simplest one could be to average a few readings but read and research more on Wikipedia (they even have Psuedocode)
  • Add the two new "Sensor" periodic callbacks, one at 1Hz, the other at 10Hz
  • In the 10Hz callback, design a Software filter to filter the Light sensor readings
  • In the 1Hz callback, print out the filtered sensor reading.
  • Do more experiments with the "Logging" capability
  • Log some data using LOG_INFO() or similar message from file_logger.h
  • Use "info 5000" command to see CPU utilization for 5 seconds.
  • Use "cat log.csv" to view the log file data.

Attach your output to Canvas along with your code.

CAN TX / RX using Auto-gen code

Work in groups of 2 by assuming one board is the DRIVER while the other board is the MOTOR or SENSOR.

The objective of the assignment is to demonstrate CAN data handling using the auto-generated code. Follow these steps:

  • Define a DBC message that your controller sends (TX)
  • Define a DBC message that your controller receives (RX)
  • Attempt to recover the CAN Bus (from Bus OFF) at 1Hz (both Boards)
  • Send the TX message from BoardA at a fixed frequency, such as 10Hz
  • Attempt to receive the RX message (on BoardB) at the designated frequency, and light up an LED when the message enters the MIA state
  • Prove that the MIA handling is occurring as intended (simple LED test by disconnecting the board that sends you a periodic message).
  • Prove that valid data is being received and reacted upon (such as displaying received sensor reading on the LED display)
  • This is a powerful assignment that demonstrates how to send and receive data using the auto-generated code that is derived from your DBC file. This will be the stepping stone of the overall CAN communication that will occur in your project.

Final Wiki Schedule

DBC File

Create your CAN communication format for each controller and link your DBC file to your Project report at your wikipedia page.

Make sure that the auto-generated code is created as intended and that you don't run into a DBC or the auto-generation issue. In other words, the auto-generated code should compile on all controllers.

CAN Communication Demo

Demonstrate that each ECU is able to communicate over the CAN Bus

  • Code should utilize the auto-generated artifacts from the DBC file
  • MIA handling should occur on the messages that are expected to be received
  • Upon MIA, an LED indicator should indicate that there is 1 or more message in the MIA state
  • In person demonstration should be given for this assignment and there is nothing to turn in otherwise.

Producer / Consumer Tasks

  • Create a task that polls the acceleration sensor, and determines the orientation of the board, such as "up, down, left, right". Create an enumeration such as "typdef enum { invalid, up, down, left, right, }orientation_t;
  • Create a task that is waiting on the orientation enumeration to be sent by the previous task.
  • Create a queue, and have the first task send orientation values every second to the queue.
  • Print something BEFORE and AFTER sending the enumeration value to the queue.
  • Print something immediately after the second task receives the data from the queue.
  • Use same priority for both tasks and note down the print-outs.
  • Alter the priority of second task to use higher priority, and note down the print-outs
  • Note down your results, and submit it at the top of your code submission.

Project Prototypes

Project prototypes are expected to demonstrate the following:

  • Reliable CAN communication between different ECUs
  • Basic obstacle avoidance (free-run mode)
  • Reliable sensor inputs (PCAN will be used by class mentors to assess)
  • Reasonable motor commands (PCAN will be used by class mentors to assess)
  • Extra credit if you go above and beyond and show even more progress such as a working Android app

Git

Your GIT repositories will be audited to assess how much contribution you have made to your GIT project. At minimum, there should be something checked-into the main branch to demonstrate that you know how to use GIT.

Project Prototypes 2

The second version of the project prototypes should demonstrate additional functionality from the first, such as:

  • Motor feedback being used to drive the car (the car should not get stuck on an up-hill ramp)
  • Android/iOS app displaying useful data
  • I/O board displaying useful data

Project Final Demo

Project Wiki