Advanced Microcomputer Design

CmpE240

Syllabus

Course Description

Hardware implementation of a microcomputer architecture using modern microprocessors and related integrated circuits: clock subsystem, bus drivers, map decoders, R/W memory, ROM, serial and parallel I/O, DMA, interrupts.

Course Catalog Description

Microcomputer architecture design describing the system bus, memory subsystems and peripherals. Unidirectional and bidirectional system bus; SRAM, SDRAM and FLASH memories and their bus interfaces; DMA, interrupt controller, transmitter/receiver, timers, display adapter, A/D and D/A converters and other system peripherals and their interfaces with system bus.

Planned classes

Class #1: Introduction

Introduction to the SJ2 board, and development environment

  • Discuss class expectations
  • Introduction to SRAM and Flash memory
  • Mail out the boards
  • Setup slack for class collaboration
Class #2: Board Software Introduction
Class #3: Hands-on experiments
  • lpc40xx.h memory map review and LPC User manual review
  • Bit masking, LPC provided memory map
  • LED and switch interface
  • SJ2 unit-test framework
Class #4: DMA
  • Introduction to the peripheral
  • Volatile keyword
  • Lab assignment: Setup memory-to-memory transfers
Class #5: Midterm
  • First 60 minutes: Review session and Timer Peripheral walkthrough
  • Review session, followed by the exam
Class #6: Timer
  • Setup HW timer that rolls over each second
  • Lab assignment: Build an API to create a precise delay of nanoseconds
Class #7: DMA to GPIO
  • Lab assignment: Setup timer to trigger for the DMA
  • Transfer a block of memory to the GPIO memory
Class #8: UART driven by GPIO
  • UART communication bus
  • Lab assignment: Use the timer API to delay by nanoseconds
Class #9: Memory
  • SRAM layout
  • Flash layout
  • Startup and linker script
  • EEPROM
  • Lab assignment: Write a "peripheral driver"
Class #10: Future facing knowledge: FreeRTOS
  • Basics of an RTOS
  • Stack pointer
  • Lab assignment: Create multiple tasks
  • Final exam review
Last class
  • Final examination
  • Goodbyes

Lab Assignments

Lab Assignments

Preparation for Labs

C programming basics

  • Functions
  • Structures
  • Pointers

 


Part 0: Compile the sample project

In this part, the objective is simply to compile your project, and to make sure you can load the compiled image onto your board.

  • Erase the contents of your existing main.c and use the code given below
  • Use the Google Chrome based serial terminal to confirm the output
// file: main.c
#include <stdio.h>

// Separate include files for Clang to sort separately
#include "delay.h"

void main(void) {
  unsigned counter = 0;
  
  while (1) {
    printf("Running: %u\n", counter); 
    ++counter;

    delay__ms(1000U);
  }
}

 


Part 1: Get familiar with GPIO Blinky API

The objectives of this part is:

  • Learn how to use existing API to blink LEDs
  • Get familiar with how to develop clean C APIs
  • Explore the code of the existing APIs to assess how it works
#include <stdio.h>

#include "board_io.h"
#include "delay.h"
#include "gpio.h"

/* This is just a sample
 * Cross-reference the schematic, and browse through the header files included above
 * to go above and beyond, and in general play with existing APIs
 */
int main(void) {
  gpio_s led0 = board_io__get_led0();

  while (1) {
    gpio__toggle(led0);
    delay__ms(500U);
  }

  return 0;
}

 


Conclusion

This exercise was mostly to get familiar with the board, and be excited about how your software can control hardware. In the following weeks, you will be developing your own code by directly manipulating the microcontroller's memory to achieve your objectives.

Lab Assignments

GPIO - LED and Switch Interface

Objective

The objective of the assignment is to access microcontroller's port-pins to manipulate LEDs that are connected to a few on-board LEDs of the SJ2 board.

Please reference this article for good hints related to this assignment.

 


Part 0:

In this part, we will setup basic skeleton of the code before we access the memory map to manipulate an LED.

// file: main.c
#include <stdio.h>

// Separate include files for Clang to sort separately
#include "delay.h"

void main(void) {
  unsigned counter = 0;
  
  while (1) {
    printf("Running: %u\n", counter); 
    ++counter;

    delay__ms(1000U);
  }
}

 


Part 1:

In this part, we will actually reference the LPC user manual and manipulate one of the on-board LEDs.

You can refer Chapter 8 - Table 94 of the user manual.

// file: main.c
#include <stdio.h>

// Separate include files for Clang to sort separately
// Add header files required
#include "delay.h"

void blinky_leds(void){

	// 1. Refer to the datasheet and configure the direction and pin using the memory address
	uint32_t *port1_pin_register = (uint32_t *)(MEMORY_ADDRESS);
    ...
	
	// Set directions to the pins
	...
	
	while (1) {
		delay__ms(100);
		
		METHOD1: Use bitmasking techniques to set and reset the pins
		
		METHOD2: Use the set and clear registers 
    }
}

int main(void)
{
	blinky_leds();
	return 0;
}


 


Part 2:

In this part, we will use the LPC memory map to manipulate the on-board LEDs. This will reduce us from cross referencing the LPC user manual.

 Use LPC40xx MCU Memory Map as reference

// file: main.c
#include <stdio.h>

// Separate include files for Clang to sort separately
// Add header files required
#include "board_io.h"
#include "delay.h"
#include "gpio.h"
#include "lpc40xx.h"

void blinky_leds(void) {

	// Step 1: Choose pin as GPIO
	LPC_IOCON->(port_pin_number) &= ~7;
    
	// Step 2: Enable the direction pin 
	
	while (1) {
		delay__ms(100);
		
		// Use set and clear registers to set and clear pins accordingly
	}
}
	

Part 3:

Create a comprehensive GPIO driver and manipulate the on-board LED's using the on-board switches. You could use PART1: GPIO Driver as reference.


Extra Credit:

Do something creative with your implemented GPIO driver.  You could have the LED blink three times when the switch is pressed.


 

Go above and beyond

Want to have even more fun and experience multi-tasking functionality of your board's software package? Then follow this link:

Lab Assignments

DMA - Memory to Memory transfer

Objective
  • Copy data from one memory block to another memory block using DMA controller
  • Use implemented driver to compare the performance between DMA copy and CPU copy

 


Part 0: Read the DMA chapter

The first step is to familiarize yourself with the DMA peripheral. Read the LPC user manual multiple times before you start the assignment.

 


Part 1: Basic DMA driver

In this portion of the lab, you will implement GPDMA driver by choosing one of the DMA channels (0-7, preferably 7) for performing memory-to-memory copy between 2 arrays. You will be modifying DMA registers available in lpc40xx.h

#include <stdbool.h>
  
#include "lpc40xx.h"
#include "lpc_peripherals.h"

typedef enum {
  DMA__CHANNEL_0 = 0,
  DMA__CHANNEL_1,
  DMA__CHANNEL_2,
  DMA__CHANNEL_3,
  DMA__CHANNEL_4,
  DMA__CHANNEL_5,
  DMA__CHANNEL_6,
  DMA__CHANNEL_7,
} dma__channel_e;

LPC_GPDMACH_TypeDef *dma_channels[] = {LPC_GPDMACH0, LPC_GPDMACH1, LPC_GPDMACH2, LPC_GPDMACH3,
                                       LPC_GPDMACH4, LPC_GPDMACH5, LPC_GPDMACH6, LPC_GPDMACH7};


void dma__initialize(void) {
  // 1. Power on GPDMA peripheral; @see "lpc_peripherals.h"
  lpc_peripheral__turn_on_power_to(...); // Fill in the arguments
}

void dma__copy(dma__channel_e channel, const void *dest, void *source, uint32_t size_in_bytes);
  // 2. Enable GPDMA - Set the Enable bit in Config register
  
  // 3. Clear any pending interrupts on the channel to be used by writing to the IntTCClear
  // and IntErrClear register. The previous channel operation might have left interrupt active

  // 4. Write the source address into the CSrcAddr register.
  dma_channels[channel]->CSrcAddr = (uint32_t)source;
  
  // 5. Write the destination address into the DestAddr register.
  
  // 6. Write the control information into the Control register in one instruction
  // transfer size [11:0]
  // source burst size [13:12]
  // dest burst size [15:14]
  // source transfer width [20:18]
  // destination transfer width [23:21]
  // source address increment [26]
  // destination address increment [27]
  // TCI enable

  // Enable channel by setting enable bit for the channels CConfig register

  // IMPORTANT:
  // Poll for DMA completion here
}

bool check_memory_match(const void *src_addr, const void *dest_addr, size_t size) {
  // Write code to check the data of source and destination arrays
  // Hint: You may use memcmp()
}

int main(void) {
  const uint32_t array_len = _____; //specify a length (< 2^12)

  // Be aware that you only have 64K of RAM where stack memory begins
  // You can make these 'static uint32_t' or make them global to not use stack memory
  uint32_t src_array[array_len];
  uint32_t dest_array[array_len];

  // Initialize the source array items to some random numbers

  // Choose a free DMA channel with the priority needed.
  // DMA channel 0 has the highest priority and
  // DMA channel 7 the lowest priority
  dma__initialize();
  dma__copy(...);        // fill in the arguments
    
  const bool memory_matches = check_memory_match(...); // fill in the arguments

  while (true) {
  }

  return 1; // main() shall never return
}

 


Part 2: Compare Performance of various methods

Reuse code from Part 1. Reference the code below to measure the time taken for DMA copy, programmatic copy and standard library function memcpy()

#include <string.h>

// Declare source and destination memory for the lab
static uint8_t memory_array_source[4096];
static uint8_t memory_array_destination[4096];

// Re-initialize the memory so they do not match
static void reset_memory(void) {
  for (size_t i = 0; i < sizeof(memory_array_source); i++) {
    memory_array_source[i] = i;
    memory_array_destination[i] = i + 1;
  }
}

// Copy memory using standard library memcpy()
static uint32_t memory_copy__memcpy(void) {
  const uint32_t start_time_us = sys_time__get_uptime_us();
  {
    memcpy(...); // TODO: Fill in the parameters
  }
  const uint32_t end_time_us = sys_time__get_uptime_us();
  
  return (end_time_us - start_time_us);
}

// Copy memory using the DMA
static uint32_t memory_copy__dma(void) {
  const uint32_t start_time_us = sys_time__get_uptime_us();
  {
    dma__copy(...); // TODO: Fill in the parameters
  }
  const uint32_t end_time_us = sys_time__get_uptime_us();
  
  return (end_time_us - start_time_us);
}

static uint32_t memory_copy__loop(void) {
  const uint32_t start_time_us = sys_time__get_uptime_us();
  {
    // TODO: Use a for loop
  }
  const uint32_t end_time_us = sys_time__get_uptime_us();
  
  return (end_time_us - start_time_us);
}

int main()
{
  dma__initialize(...);

  // Test 1:
  reset_memory();
  const uint32_t dma_us = memory_copy__dma();
  if (!check_memory_match(memory_array_source, memory_array_destination, sizeof(memory_array_source)) {
    puts("ERROR: Memory copy did not work");
  }

  // TODO: Reset memory, then perform memory copy, and check if memory matches
  // Test 2:
  const uint32_t loop_us = memory_copy__loop();
  
  // Test 3:
  const uint32_t memcpy_us = memory_copy__memcpy();
 
  printf("DMA copy completed in: %lu microsec\n", dma_us);
  printf("For loop completed in: %lu microsec\n", loop_us);
  printf("memcpy completed in  : %lu microsec\n", memcpy_us);
 
  while (true) {
  }
  return 1; // main() shall never return
}

 


Requirements
  • Part 1 and Part 2 must be completed and fully functional.
    • You are encouraged to ask questions for any line of code that is not well understood (or magical).
  • Try changing source burst size and destination burst size bits to understand performance improvements
    • Note the time taken in each case and turn in the screenshots
  • Should be able to make it work for any DMA channels(0-7) or array size
    • We may ask you to change the channel or array length and then recompile and re-flash your board to and prove it works

What to turn in:

  • Turn in the screenshots of terminal output for different burst size

Extra Credit - Make program to choose DMA channels using onboard buttons and run DMA copy process

 

 


Lab Assignments

Hardware Timer

A hardware timer is a time tracking peripheral that runs independent of the foreground CPU instructions. For example, you can start to count the clock cycles using a hardware timer independent of the instructions the CPU is processing. Fundamentally, a hardware timer is a CPU peripheral, but you can consider "CPU peripheral" as an independent CPU, or co-processor.

 


Part 0: Fundamentals

Objective of this part is to fully understand the clock system, and the registers of the Timer Peripheral.

First thing to recognize is that all peripherals are configured to receive the PLL clock. As a reminder, 12MHz clock on the SJ2 (NXP chip) is multiplied to 96MHz, and this clock is then sourced to the hardware timer peripheral. What this means is that you can digitally count the number of clock cycles of the CPU which is 96Mhz, and each timer register ticking up (or incrementing) is at the rate of about 10.4ns.

The LPC User manual chapter of interest is:

The register of relevance:

  • PC
    • This register will count the input clock which is the PCLK (peripheral clock)
    • When it equals to the value of (PR + 1) then it resets back down to zero, and increment TC by 1
  • PR
    • This register provides means to divide the clock, and increment the TC register slower than the input clock of 96Mhz. So for example, if (PR == 95), then input clock of 96Mhz will increment the PC register, and when that equals to 95, the TC will increment by 1. So in this example, we will configure such that TC register will increment at 1Mhz rather than 96MHz
  • Match Registers
    • When TC matches one of the four registers, you can generate events such as resetting the timer
  • Capture Registers
    • These allow you to capture events such as an external GPIO (switch or a sensor) giving data by means of signal pulses. The capture registers can capture the time when a GPIO goes low, or it goes high, and in the end you can use this peripheral to read pulse width modulation signals