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...")
 
(DBC File)
 
(87 intermediate revisions by 4 users not shown)
Line 1: Line 1:
= GPIO =
+
<span id="BackToTop"></span>
== Objective ==
+
<div class="noprint" style="background-color:#FAFAFA; position:fixed; bottom:2%; left:0.25%; padding:0; margin:0;">
Interface your LPC17xx to a switch and an LED.
+
[[#top|Back to the Top]]
 +
</div>
  
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 :
+
= Hello World =
*  No hardware damage if faulty software is written.
 
*  Circuit should prevent excess amount of current to avoid processor damage.
 
  
== Required Background ==
+
=== Assignment ===
* You should know how to bit-mask variables
+
*Get the development environment from sourceforge.
*  You should know how to wire-wrap
+
: '''[https://sourceforge.net/projects/armdevpkg/files/: SourceForge SJSU Development Package]'''  
*  You should know fundamentals of electricity and the famous '''V = IR''' equation.
 
  
== GPIO ==
+
*Compile a sample FreeRTOS sample project
{| 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.
+
*Load it onto the processor
  
|[[File:tutorial_gpio_design.png|center|frame|GPIO Design]]
+
*Add a terminal command to your project, have it do something creative (Like print temperature on the screen)
|}
 
  
== Coding ==
+
Reference this article on how to add a terminal command:
=== Hardware Registers ===
 
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 :
 
{| class="wikitable"
 
|+LPC17xx Port0 Registers
 
|-
 
|'''LPC_GPIO0->FIODIR'''
 
|Direction of the port pins, 1 = output
 
|-
 
|'''LPC_GPIO0->FIOPIN'''
 
|
 
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/>
+
http://www.socialledge.com/sjsu/index.php?title=SJ_One_Board
=== Switch ===
 
{| class="wikitable"
 
|+ 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
+
SUBMIT YOUR CODE online (copy/paste to Text Entry box)Or simply provide me a link if you use Gitlab or other online repository for your code submission.
* 32 physical pins of PORT0We use AND logic to test if JUST the
 
* 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 ===
+
Only submit the relevant code, or just the code you added.
{| 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
 
* and resetting to 0 will turn OFF LED.
 
*/
 
LPC_GPIO0->FIOPIN |= (1 << 3);
 
  
/* Faster, better way to set bit 3 (no OR logic needed) */
 
LPC_GPIO0->FIOSET = (1 << 3);
 
  
/* Likewise, reset to 0 */
+
= Project Setup =
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 ==
+
= Final Wiki Schedule =
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 =
+
= CAN Communication Demo =
== 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'''ransmitter.  There 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'''econd.  If a transmitter is configured with 9600bps, then the receiver must be listening on the other end at the same speed.
+
=== Assignment ===
  
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 :
+
Demonstrate that each ECU is able to communicate over the CAN Bus
[[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.
+
*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
  
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.'''
+
In person demonstration should be given for this assignment and there is nothing to turn in otherwise.
*  [https://www.youtube.com/watch?v=RU_NUPprx2Y UART Driver Video]
 
  
<BR/>
+
= Producer / Consumer Tasks =
  
== UART Pins ==
 
Before you write a UART software driver, you need to understand the physical constraints and identify the UART pins on your processor.  A 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.
 
  
[[File:tutorial_uart_pinsel.png|center|Find RXD2 and TXD2 of UART2]]
+
=== Assignment ===
<BR/>
 
[[File:tutorial_gpio_mux.png|center|Example MUX that we need to configure for a PIN selection]]
 
  
<BR/>
+
*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 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.
 +
*Create a task that is waiting on the orientation enumeration to be sent by the previous task.
 +
*:  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.
  
== Clock System & Timing ==
+
= Project Prototypes =
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 processor.  The 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.
 
  
9600bps means that one bit takes 1/9600 = 104uS (micro-seconds) per bit.  The 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.
+
=== Assignment ===
  
[[File:uart_tutorial_clock.jpg|center|frame|Example clock system of LPC17xx]]
+
Project prototypes are expected to demonstrate the following:
  
== Hardware Design ==
+
*Reliable CAN communication between different ECUs
There is not much hardware design other than to locate UART-2 pins on your processor board and connecting these wires to the second board.  Each pin on a microcontroller may be designed to provide specific feature.  So the first thing to do is identify which physical pins can provide UART-2 signals.
+
*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
  
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.
 
  
  
== Software Driver ==
+
= Individual Contribution =
[[File:uart_tutorial_overview.jpg|center|frame|UART chapter summary]]
 
  
{|
 
|
 
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/>
+
You will be grading your group members based on how much or how little they contributed to the projectYou must be ethical, and honest, and not go based on your "emotions".
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.
 
  
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.
+
Example:
  
<syntaxhighlight lang="c">
+
Name Letter Grade Notes
void uart0_init(void)
+
Last1, First1 A This student was involved in all aspects of the project design and was present at all group meetings.
{
+
Last2, First2 C- This student contributed to writing the report but was absent for many group meetings.
    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
 
    LPC_PINCON->PINSEL0 |= (0x5 << 4);  // Set values for UART0 Rx/Tx
 
 
 
    LPC_UART0->LCR = (1 << 7); // Enable DLAB
 
    /* 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
 
}
 
</syntaxhighlight>
 
| [[File:uart_tutorial_dlab.jpg|center|frame|DLAB bit to access registers]]
 
  [[File:uart_tutorial_formula.jpg|center|frame|Baud rate formula]]
 
|}
 
 
 
<syntaxhighlight lang="c">
 
char uart0_putchar(char out)
 
{
 
    LPC_UART0->THR = out;
 
    while(! (LPC_UART0->LSR & (1 << 6)));
 
    return 1;
 
}
 
</syntaxhighlight>
 
 
 
<BR/>
 
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.
 
<BR/>
 
 
 
== Advanced Design ==
 
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 data.  You 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 ==
 
*  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 =
 
== 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>MOSI --> </code>Master Out Slave In  (driven by master)
 
*  <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.
 
 
 
The SCK signal can reach speed of 24Mhz and beyond, however, SD cards are usually limited to 24Mhz according to the specifications.  Furthermore, 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.
 
 
 
<BR/>
 
 
 
== Hardware ==
 
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.
 
 
 
<BR/>
 
== 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.
 
 
 
{|
 
|<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
 
    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
 
    LPC_SSP1->CR1 = (1 << 1);  // Enable SSP as Master
 
    LPC_SSP1->CPSR = 8;        // SCK speed = CPU / 8
 
}
 
 
 
char spi1_ExchangeByte(char out)
 
{
 
    LPC_SSP1->DR = out;
 
    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/>
 
=== 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/>
 
== 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.
 

Latest revision as of 00:10, 18 March 2020

Hello World

Assignment

  • Get the development environment from sourceforge.
SourceForge SJSU Development Package
  • Compile a sample FreeRTOS sample project
  • Load it onto the processor
  • Add a terminal command to your project, have it do something creative (Like print temperature on the screen)

Reference this article on how to add a terminal command:

http://www.socialledge.com/sjsu/index.php?title=SJ_One_Board

SUBMIT YOUR CODE online (copy/paste to Text Entry box). Or simply provide me a link if you use Gitlab or other online repository for your code submission.

Only submit the relevant code, or just the code you added.


Project Setup

Final Wiki Schedule

CAN Communication Demo

Assignment

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

Assignment

  • 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 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.
  • Create a task that is waiting on the orientation enumeration to be sent by the previous task.
    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

Assignment

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


Individual Contribution

You will be grading your group members based on how much or how little they contributed to the project. You must be ethical, and honest, and not go based on your "emotions".

Example:

Name Letter Grade Notes Last1, First1 A This student was involved in all aspects of the project design and was present at all group meetings. Last2, First2 C- This student contributed to writing the report but was absent for many group meetings.