Class Project and Useful Articles

Project Introduction and Guidelines

 Controllers

Various different controllers are used and each should have limited and exclusive responsibilities. Use this article as a reference to derive your project schedule and individual team member tasks.

 

sj2-243_v2.png

 

Example of how GPS coordinates are sent from Mobile phone:

  1. Mobile phone sends data as ASCII text to the Bridge
  2. Bridge controller receives information over UART
  3. Bridge controller sends data using DBC/CAN to the Geological
  4. No other controller should need to know the destination coordinates because the Geological controller will guide the Driver controller with its compass heading, and tell it to stop when necessary

 


1: Bridge and Sensor Controller

Requirements

  • Shall be interfaced to all of the distance sensors
    • Note that your board only has 3 ADC channels if you choose to use this interface
    • Note that ultrasonic sensors can collide in their sound waves and interfere with each other. It may be better to use different frequency based sensors (maybe different vendor or models) to minimize the interference.
  • Shall output all sensor information with minimum refresh rate of 20Hz

  • Shall provide the interface (such as bluetooth serial) to a mobile application running on a phone
    • Typical interface to a Bluetooth or Wifi is UART
    • Bluetooth requires pairing, and is usually stable after that
    • Wifi would require your wifi to act as an access point for your mobile phone to
    • Both interfaces should work, and you can pick one based on past semesters' reports
    • Recommendation is that Mobile phone sends a line of command terminated by a newline, and then the Bridge controller can parse the information (such as using scanf)
  • Required but low priority:
    • Provide battery voltage reading to Mobile Application and possibly to DRIVER to output on LCD

 


2: Motor Controller

Requirements

  • Shall be interfaced with speed sensors or wheel encoders to provide speed information
    • This should be transmitted on the CAN bus as either kph or mph; usually the units will be small and you could use DBC scale of 0.001 or 0.01
  • Shall be interfaced to motor controllers (or servo controller) to control steering and speed
    • Recommend hobby grade RC car (such as Traxxas), and not a $20 RC car from Amazon
  • Provide self-test capability button
    • Self-Test pressed, so Driver controller should be commanded to a 5 seconds wheel test mode
    • This should include forward, backward motion, and steering test
  • Shall process the speed command, and compensate for the grade of the ground (uphill or downhill)
    • For example, the DRIVER may command 0.5 kph, and the motor controller should process the wheel encoder and use a simple PID control loop to match the command regardless of the grade or battery power

 


3: Geological Controller

Requirements

  • Shall be interfaced to a magnetometer
  • Shall be interfaced to a GPS
  • Shall provide raw GPS and compass readings
  • Shall have the ability to "set destination" and then provide heading degree towards destination
    • Driver controller should not need to get involved in GPS data. It should simply receive a compass destination heading, and actual compass heading. When there is no obstacle, it should simply try to take its current heading towards the destination heading (which is just a compass degree)
  • Shall have the waypoints algorithm

 


4: Driver and LCD Controller

Requirements

  • Shall be interfaced to an LCD display to output meaningful and diagnostic information
    • Sensor values, commanded motor values etc.
    • Recommendation is to use a simple UART or I2C based LCD
    • Something like this
  • Shall receive all relevant sensor messages and process them for obstacle avoidance
  • Shall receive compass actual degree, and heading degree (towards destination)
  • Shall have the obstacle avoidance algorithm
    • In absence of any obstacles, it shall use compass to follow the GPS destination
  • Shall send output drive commands for the Motor Controller

 


5: Mobile Application

Requirements

  • Minimize the buttons (and hence the code)
    • If there is no Bluetooth connection, automatically display list of bluetooth devices
    • Once connected, automatically show the Google Maps page
  • Shall have Google Maps or similar for a user to pick the destination
  • Shall transmit the destination to the Bridge Controller as necessary
  • Optional requirement to display car data, such as speed, compass, and anything else useful to you during debugging. Definitely some extra credit opportunity here based on how well you accomplish your Mobile Application

 


Project Hints

 

Git strategy to share common DBC file

A common issue for everyone is how to have separate projects in your Git repo.

  1. One option is to create different folder, one for each project
    • Maybe have a top level DBC file, and manually copy to all other folders upon update
    • Maybe infrastructure code can find DBC file at root level directory itself? This might be a simple modification in the Python based Scons script
  2. Another option is to use different Git branches
    • Single folder for your project, such as lpc40xx_freertos but different "virtual" master branches
    • git checkout master_driver
    • git checkout master_sensor
    • The "master" branch is where the DBC is at, so when people want to get the latest, what they do is:
      • git checkout master_driver
      • git checkout master
      • git pull origin master
      • git checkout master_driver
      • git rebase master
      • git push origin head

How can you nest an external repository's DBC file in your project repository.

  • Git submodule
    • Your DBC can live in a completely separate repo, maybe this repository is nothing but a single DBC file
    • You can nest this external git submodule as a folder inside your lpc40xx_freertos directory
    • So if someone changes the DBC file at the dedicated dbc repo, then everyone needs to update it
      • git checkout master
      • cd dbc_directory (nested git submodule)
      • git pull origin master (of the external dbc repo, this will pull in the latest changes from there)
      • cd - (step outside of the nested git submodule)
      • git add dbc_directory (you update the githash that is pointing to the external repo's commit)
      • git commit -m "update dbc"
      • git push origin head

 



Receive CAN in only one function

Students often times tend to try to handle CAN frame reception in multiple functions. The problem is that this creates non-deterministic operation as some frames may be dropped in one place in code, and may not be handled where you really mean to handle them.

Note the following properties:

  • A while loop to empty out the CAN receive queue
    • Handling just one frame per function call will accumulate CAN frames leading to data loss
  • After creating a message header, call all decode functions
    • Only one decode function will decode at most since message header will match only once
    • This reduces your testing effort as you do not need manual switch/case statements
void can_handle_all_frames(void) {
    while (can_rx(...)) {
      msg_hdr = create_msg_header();
      
      dbc_decode_...();
      dbc_decode_...();
    }
}

 



Test I2C Sensor

If you have a sensor such as a compass which operates using I2C, then it is advised to use the built-in I2C CLI command on your SJ2 board to test out the sensor registers. Make sure you run the CLI task in your main.c and then simply type "help i2c" to explore the CLI command and trial the sensor data.

 



Transmit GPS coordinates in between controllers

Use the following DBC design:

BO_ 201 GPS_DESTINATION: 4 BRIDGE_CONTROLLER
 SG_ GPS_DESTINATION_LONGITUDE : 0|32@1+ (0.000001,0) [0|0] "" GEO_CONTROLLER

Be sure to keep in mind:

  • A GPS coordinate is usually only sent with six decimal places maximum
  • A float, is always a 32-bit float (IEEE standard)
  • A float can only store up to 6 decimal points
    • However, if your number changes from 0.123456 to 456.123456, then the precision is actually lost, and the number may actually be truncated to this: 456.123444

More things to keep in mind:

  • float is supported by HW on the ARM CM-4
  • But a "double" uses software floating point instructions

 

 

 

Unit Testing code that touches the HW registers

 This article guides you on how to unit-test code that reads or writes hardware registers of your SJ development board.

 

// Typical code
int get_ultrasonic_pulse_width(void) {
  // Send a pulse width
  LPC_GPIO1->CLR = (1 << 2);
  delay_us(10);
  LPC_GPIO1->SET = (1 << 2);
  
  const uint32_t previous = time_now();
  while (LPC_GPIO1->PIN & (1 << 3)) {
   ;
  }
  
  return time_delta(previous);
}

Before we solve the problem, let us write better code that is self expressive and does not require comments to understand its intent.

static void send_pulse_to_ultrasonic(void) {
  const uint32_t ultrasonic_pulse_pin = (1 << 2);

  LPC_GPIO1->CLR = ultrasonic_pulse_pin;
  delay_us(10);
  LPC_GPIO1->SET = ultrasonic_pulse_pin;
}

static void wait_for_ultrasonic_pulse_to_bounce_back() {
  while (LPC_GPIO1->PIN & (1 << 3)) {
   ;
  }
}

// Notice the clarity of this function compared to the previous code snippet
int get_ultrasonic_pulse_width(void) {
  send_pulse_to_ultrasonic();
  
  const uint32_t previous = time_now();
  wait_for_ultrasonic_pulse_to_bounce_back();
  return time_delta(previous);
}

And the next level:

// Separate header file to abstract the hardware, such that we can mock out this API
// file: ultrasonic_pins.h
void ultrasonic_pins__set_pulse(bool true_for_logic_high);
bool ultrasonic_pins__get_input_pin_value(void);
#include "ultrasonic_pins.h"

static void send_pulse_to_ultrasonic(void) {
  // This can now move to ultrasonic_pins.c
  // const uint32_t ultrasonic_pulse_pin = (1 << 2);

  ultrasonic_pins__set_pulse(true);
  delay_us(10);
  ultrasonic_pins__set_pulse(false);
}

static void wait_for_ultrasonic_pulse_to_bounce_back() {
  while (ultrasonic_pins__get_input_pin_value()) {
   ;
  }
}

int get_ultrasonic_pulse_width(void) {
  send_pulse_to_ultrasonic();
  
  const uint32_t previous = time_now();
  wait_for_ultrasonic_pulse_to_bounce_back();
  return time_delta(previous);
}

 

Use single periodic callback if possible

The problem with multiple callbacks is that the higher rate can interrupt a lower rate callback.

int number = 0;

void periodic_callback_10hz(uint32_t count) {
  number++;
}

// This can interrupt the 10hz in the middle of its operations
void periodic_callback_100hz(uint32_t count) {
  number++;
}

Use this instead:

int number = 0;

void periodic_callback_10hz(uint32_t count) {
  //number++;
}

// This can interrupt the 10hz in the middle of its operations
void periodic_callback_100hz(uint32_t count) {
  if (0 == (count % 10)) {
    number++;
  }

  number++;
}