Difference between revisions of "S19: CANT Bus"

From Embedded Systems Learning Academy
Jump to: navigation, search
(Driver Module)
(Grading Criteria)
Line 1: Line 1:
=== Grading Criteria ===
<font color="green">
*  How well is Software & Hardware Design described?
*  How well can this report be used to reproduce this project?
*  Code Quality
*  Overall Report Quality:
**  Software Block Diagrams
*  Hardware Block Diagrams
**:  Schematic Quality
**  Quality of technical challenges and solutions adopted.
== [C]ompile [A]nother [N]on-[T]rivial Bus ==
== [C]ompile [A]nother [N]on-[T]rivial Bus ==

Latest revision as of 16:37, 29 May 2019

[C]ompile [A]nother [N]on-[T]rivial Bus


Design and implement an autonomous car to navigate to a destined location chosen by the user using a phone application. The user will set gps coordinates via an Android application while the current location is processed. The RC car should be able to use these coordinates to navigate itself to the destined location avoiding obstacles and providing useful data on the way. Several objectives need to be completed in order for the RC car to function properly: integrate proximity sensors, gps, compass, lcd screen, motor driver board, esp32 communication, hall sensor, and overall integration in order for the RC car to be able to determine where it is, the speed it's going, and the environment around it to successfully navigate to the final destination.


The purpose of this project was to convert a RC car into a self driving vehicle that can navigate itself to a destination sent via an Android mobile app. To achieve this, the project was divided into 7 modules: Avoidance, Motor, Master Driver, Localize, Comms, and Android Application. The Avoidance module contains 8 IR sensors that allow the car to see the world around it. This module finds objects and measures the distance between it and the object. Motor controls a brushed motor which drives the car at various speeds and a servo to turn the car. In addition, Motor also contains a hall effect sensor that keeps track of the cars speed. Localize tells the car where it is in relation to where it needs to go. It contains a GPS and Compass to provide current coordinates and heading. Comms houses the wireless device that talks to the the mobile app as well as an LCD display to show various car data such as speed and destination. The mobile app is where the user can enter GPS coordinates as a destination point, or alter the current destination with a way-point. Lastly, the Driver receives the data from all modules and decides how the car shall react. In order to complete these modules basic knowledge of I2C, UART, SPI, and CAN bus protocols, gpio control, PWM control, phone application development, and pathing algorithms was required. While each peripheral in the modules used their own form of communication, each of the modules was connected using the CAN bus protocol.

Team Members & Responsibilities

  • Kevin Chan
    • IR sensors (Proximity), Compass (Localize), GPS (Localize), Distance Vector (Localize), Code Review
  • Khrysta Finch
    • Chassis, Driver (Driver), LCD display (Comms), Schematic, Telemetry (Comms)
  • Andrew Javier
    • Hall Sensor (Motor), Wireless communications via esp32(Comms), GPS (Localize), Chassis
  • Aaron Lee
    • GPS (Localize), Pathing Algorithm (Comms)
  • Jonathan Rojas
    • Motor (Motor), Servo (Motor), Motor driver board (Motor), hall sensor (Motor), Cable Management, Chassis
  • Vijay Vanapalli
    • GPS (Localize), Wireless communications via esp32(Comms), Mobile app (Comms)
  • Nelson Wong
    • Team Leader, Driver (Driver), Telemetry (Comms), PCB/Schematic, Chassis, Mobile App (Comms), Power, Code Review

Source Code

Gitlab Project Link


  • Android Application
    • Vijay Vanapalli
  • Testing Team
    • Kevin Chan
    • Nelson Wong


This table can also be viewed via Google Sheets! See the link below:

Task Contributors Date
WBS# Task Title Dependencies Jonathan Kevin Andrew Khrysta Nelson Aaron Vijay Start End Status
1 Determine theory of operations x x x x x x x 3/19 4/1
1.1 Compass x x 3/19 4/1 Done
1.2 GPS x x 3/19 4/1 Done
1.3 Proximity x x 3/19 4/1 Done
1.4 LIDAR x x 3/19 4/1 Done
1.5 Wireless x x x 3/19 4/1 Done
1.6 LCD x 3/19 4/1 Done
1.7 Hall Effect Sensors x 3/19 4/1 Done
1.8 Motor x x 3/19 4/1 Done
1.9 Steering x x 3/19 4/1 Done
1.10 Battery health x 3/19 4/1 Cancelled
1.11 Mobile app x x x 3/28 4/1 Done
1.12 Wireless module x x x 3/28 4/1 Done
1.13 Driver x x 3/19 4/6 Done
2 Chassis and mounting x x 5/1 5/7
2.1 Acrylic to chassis x 4/1 4/4 Done
2.2 PCB to acrylic 3.5 x x 4/25 4/28 Done
2.3 SJ One to acrylic x 4/25 5/7 Done
2.4 Power modules to acrylic 3.2 x x 4/25 5/7 Done
2.5 Proximity to acrylic x 4/25 5/7 Done
2.6 Magnet collet to motor shaft x 4/18 4/23 Done
2.7 Hall effect sensor to chassis x 4/18 4/23 Done
3 PCB and assembly x x x x x x x 3/5 4/25 Done
3.1 Finalize BOM x x x x x x x 3/5 4/1 Done
3.2 Determine module footprints x x x x 3/28 4/1 Done
3.3 Finalize schematic x x 4/1 4/9 Done
3.4 Review schematic x x x 4/1 4/10 Done
3.5 Finalize layout x 4/1 4/9 Done
3.6 Review layout x x x 4/1 4/10 Done
3.7 Order PCB x 4/10 4/24 Done
3.8 PCB assembly x 4/23 4/25 Done
3.9 Assembly of modules x x x x x x 4/23 4/25 Done
4 ECU Logic x x x x x x x 3/5 5/23 Done
4.1 MOTOR x x x x 3/5 5/23 Done
4.1.1 Tachometer logic x x 4/3 4/10 Done
4.1.2 Motor logic x x x 4/2 4/18 Done
4.1.3 Steering logic x x x 4/2 4/18 Done
4.2 COMMS x x x x 4/13 5/21 Done
4.2.1 Wireless logic x x 4/13 5/21 Done
4.2.2 LCD logic x 4/28 5/10 Done
4.3 LOCALIZE x x 5/22 5/22 Done
4.3.1 GPS logic x x 4/22 5/22 Done
4.3.2 Compass logic x x x 4/28 5/22 Done
4.3.3 GPS/Compass synthesis x 5/1 5/22 Done
4.4 AVOIDANCE x x 4/3 5/22 Done
4.4.1 LIDAR logic x x 4/3 5/22 Done
4.4.2 Proximity logic x x 4/3 5/22 Done
4.4.3 LIDAR/proximity synthesis x x 5/2 5/22 Done
4.5 DRIVER x x 4/1 5/22 Done
4.5.1 Battery health logic x 4/15 4/17 Done
4.5.2 Driver logic x x 4/1 5/22 Done
5 Wireless module x x x 4/5 5/23 Done
5.1 Development environment x 4/5 4/7 Done
5.3 Wifi connectivity x 4/5 4/7 Done
5.4 Handle XHR endpoints x 4/5 5/23 Done
5.5 Relay via UART x 4/5 4/29 Done
6 Mobile app x x x 4/1 5/22 Done
6.2 Send XHR requests x 4/1 4/29 Done
6.3 Google Maps Overlay x 4/1 4/1 Done
6.4 Show car icon x 4/15 5/22 Done
6.5 Show waypoints x 5/10 5/19 Done
6.6 Service button press as XHRs x 4/15 4/29 Done
7 Documentation x 3/5 5/23 Done
7.1 Summary of individual contribution x x x x x x x 3/5 5/22 Done
7.2 Project wiki schedule x x 3/5 5/23 Done
7.3 BOM x x x x x x x 3/5 4/5 Done
7.4 CAN comms: the dbc x x x x x x x 3/5 5/22 Done
7.5 MOTOR x 3/5 5/23 Done
7.5.1 Theory of operation x x x x 3/5 5/23 Done
7.5.2 Hardware design x x x 3/5 3/19 Done
7.5.3 Software design x 3/5 5/23 Done
7.5.4 Challenges x 5/22 5/23 Done
7.6 COMMS x x x x 4/28 5/23 Done
7.6.1 Theory of operation x x x x 4/28 5/1 Done
7.6.2 Hardware design x x 5/1 5/9 Done
7.6.3 Software design x x 5/1 5/23 Done
7.6.4 Challenges x x 5/1 5/23 Done
7.7 LOCALIZE x x 5/1 5/23 Done
7.7.1 Theory of operation x x 5/1 5/14 Done
7.7.2 Hardware design x x 5/2 5/14 Done
7.7.3 Software design x x 5/2 5/23 Done
7.7.4 Challenges x 5/1 5/23 Done
7.8 AVOIDANCE x x 5/1 5/23 Done
7.8.1 Theory of operation x x 5/1 5/8 Done
7.8.2 Hardware design x 5/5 5/23 Done
7.8.3 Software design x x 5/5 5/23 Done
7.8.4 Challenges x x 5/1 5/23 Done
7.9 DRIVER x x 5/1 5/23 Done
7.9.1 Theory of operation x x 5/1 5/23 Done
7.9.2 Hardware design x x 5/5 5/23 Done
7.9.3 Software design x x 5/5 5/23 Done
7.9.4 Challenges x 5/1 5/23 Done
7.10 Mobile app x x x 4/18 5/23 Done
7.10.1 Theory of operation x x x 4/18 5/20 Done
7.10.2 Software design x 4/20 5/23 Done
7.10.3 Challenges x 4/18 5/23 Done
7.11 Overall PCB x x x 3/18 4/25 Done
7.11.1 Hardware design x x x 3/18 4/25 Done
7.11.2 Challenges x 4/7 5/23 Done
7.12 Videography x x 5/23 5/23 In Progress
7.13 Conclusion x x x x x x x 5/23 5/23 Done
8 Supervision/Advisory x x x x x x x 4/1 5/23 Done
8.1 Maintain knowledge of system architecture x 4/1 5/23 Done
8.2 Assert code review x 4/1 5/23 Done
8.3 Define system test approach x 4/1 5/23 Done
8.4 Enforce coding style and practice x 4/1 5/23 Done
8.5 Tracking project deliverables x x x x x x x 4/1 5/23 Done
9 Final Demonstration x x x x x x x 5/22 5/23 Done

Parts List & Cost

Item# Part Desciption Vendor Qty Cost
1 RC Car Cobra [1] 1 $180.00
2 CAN Transceivers SN65HVD230 Microchip [2] 5 $10
3 LSM303 Compass Adafruit [3] 1 $15
4 ublox Neo-6m GPS Ublox [4] 1 $16
5 Proximity Sensors - VL53L0X ST [5] 8 $120
6 Hall Effect Sensors Adafruit [6] 3 $24
7 LiPo Batteries (3S 11.1V 5500 mAh) FLOUREON [7] 2 $34
8 Printed Circuit Board Bay Area Circuits 1 $60
9 ESP32 Wireless Module Espressif [8] 1 $5
10 Power Management Integrated Circuit (PMIC) Amazon [9] 1 $12
11 2.2" TFT LCD display Adafruit [10] 1 $25

Printed Circuit Board

We used the online tool Upverter to create our schematic and design the final PCB board. Both schematic and board can be found on Upverter using the link below.

PCB and Schematic

CAN Communication

CAN BUS Messages

The DBC file that was used to generate our CAN messages can be found below. Each message has a priority ID assigned to it that is used by the CAN protocol to see which device to listen to first in cases of bus arbitration; the lower the ID number the higher priority it has. In our case, the message with the highest priority at 10 is KILL_MOTOR. Since we are using LiPO (Lithium Polymer) batteries, the desire to disconnect all power for the battery depleted was high on the list. Should the car be driving at that time, the KILL_MOTOR_cmd shuts off the motor immediately, allowing us to catch up to the car and swap out batteries. The KILL_MOTOR_REMOTE message follows the KILL_MOTOR message in priority. This is simply a redundancy we built in to have the message delivered to the mobile app and notifying us that the battery is low.

The priority decreases from there with HEARTBEAT from the Driver being the next highest. Since the Driver doesn't send many messages and it's there to collect data from all the other modules, it needs to let the others know it's still available. MOTOR_CMD follows as we wanted the car to move as quickly as possible, which leads to AVOIDANCE_LIDAR as the next highest. Knowing when we were in close proximity to an object is key to making sure we don't run into it. Note that while this message has "LIDAR" as part of the name, this is legacy nomenclature from when we initially had LIDAR for the car. They are now IR sensors.

GPS, Compass, and Speed were assigned the lowest priorities. While knowing where we are in relation to where we are going is important, it was less demanding that knowing if we are about to hit something or if the battery was depleted. Overall, this choice in priority has served well in providing a functional autonomous car.

Hardware Design

Each of the modules are connected to a CAN transceiver via transmit and receive lines. The transceivers themselves are connected together through their CAN high and low pins (CANH/CANL) with each of those lines terminating with a 120 ohm resistor.

Figure 1: CAN bus architecture design



 SG_ KILL_MOTOR_cmd : 0|8@1+ (1,0) [0|0] "" MOTOR,COMMS
 SG_ KILL_MOTOR_cmd : 0|8@1+ (1,0) [0|0] "" DRIVER 
 SG_ MOTOR_CMD_steer : 0|3@1+ (1,-1) [-1|1] "degrees" MOTOR
 SG_ MOTOR_CMD_drive : 8|4@1+ (1,-2) [-2|5] "" MOTOR
 SG_ DRIVER_STATUS_enum : 0|3@1+ (1,0) [0|0] "" COMMS
 SG_ LIDAR_f_right : 0|8@1+ (1,0) [0|0] "cm" DRIVER
 SG_ LIDAR_f_m_right : 8|8@1+ (1,0) [0|0] "cm" DRIVER
 SG_ LIDAR_f_middle : 16|8@1+ (1,0) [0|0] "cm" DRIVER
 SG_ LIDAR_f_m_l : 24|8@1+ (1,0) [0|0] "cm" DRIVER
 SG_ LIDAR_f_left : 32|8@1+ (1,0) [0|0] "cm" DRIVER
 SG_ LIDAR_b_right : 40|8@1+ (1,0) [0|0] "cm" DRIVER
 SG_ LIDAR_b_middle : 48|8@1+ (1,0) [0|0] "cm" DRIVER
 SG_ LIDAR_b_left : 56|8@1+ (1,0) [0|0] "cm" DRIVER

 SG_ DESIRED_HEADING : 0|32@1+ (0.1,0) [0|360.0] "degrees" DRIVER,COMMS
 SG_ DESIRED_DISTANCE : 32|32@1+ (0.00001,0) [0|0] "km" DRIVER,COMMS

 SG_ GPS_STATUS : 0|8@1+ (1,0) [0|0] "" DRIVER,COMMS
 SG_ GPS_TX_LATITUDE : 8|24@1+ (0.000001,37.000000) [37.000000|38.000000] "degrees" DRIVER,COMMS
 SG_ GPS_TX_LONGITUDE : 32|32@1- (0.000001,-122.000000) [-122.000000|-121.000000] "degrees" DRIVER,COMMS

 SG_ IMU_STATUS : 0|8@1+ (1,0) [0|0] "" DRIVER,COMMS
 SG_ IMU_COMPASS : 8|12@1+ (0.1,0) [0|360.0] "degrees" DRIVER,COMMS
 SG_ SPEED_kph : 0|16@1- (0.001,0) [-5|10] "kph" COMMS,DRIVER

 SG_ IMU_COMPASS_X : 0|10@1- (1,0) [-200|200] "" DBG
 SG_ IMU_COMPASS_Y : 10|10@1- (1,0) [-200|200] "" DBG

 SG_ MOTOR_STATUS_data : 0|8@1+ (1,0) [0|0] "" COMMS,DRIVER
 SG_ SET_WAYPOINT_LAT : 0|24@1+ (0.000001,37.331610) [37.331610|37.340132] "degrees" LOCALIZE,DRIVER
 SG_ SET_WAYPOINT_LONG : 24|24@1- (0.000001,-121.886025) [-121.886025|-121.876412] "degrees" LOCALIZE,DRIVER

 SG_ SET_STATUS_enum : 0|2@1+ (1,0) [0|0] "" DRIVER

 SG_ VEL_ARR : 0|32@1+ (1,0) [0|0] "" AVOIDANCE

 SG_ AVOIDANCE_COUNT : 0|32@1+ (1,0) [0|0] "" DRIVER

 SG_ MOTOR_SPEED_DBG : 0|32@1+ (0.0001,0) [0|0] "ms" DRIVER

 SG_ TACHOMETER_DBG : 0|16@1- (0.001,0) [-5|10] "kph" COMMS,DRIVER

CM_ BU_ DRIVER "The driver controller driving the car";
CM_ BU_ MOTOR "The motor controller of the car";
CM_ BU_ LOCALIZE "The localization controller of the car";
CM_ BU_ AVOIDANCE "The collision avoidance controller of the car";
CM_ BU_ COMMS "The wireless comms and telemetry controller of the car";
CM_ BO_ 100 "Sync message used to synchronize the controllers";
CM_ BO_ 501 "0: stop, 1: ready, 2: navigate, 3: skip/next"
BA_DEF_ "BusType" STRING ;
BA_DEF_ BO_ "GenMsgCycleTime" INT 0 0;
BA_DEF_ SG_ "FieldType" STRING ;
BA_DEF_DEF_ "BusType" "CAN";
BA_DEF_DEF_ "FieldType" "";
BA_DEF_DEF_ "GenMsgCycleTime" 0;
BA_ "GenMsgCycleTime" BO_ 500 100;
BA_ "GenMsgCycleTime" BO_ 100 1000;
BA_ "GenMsgCycleTime" BO_ 101 100;
BA_ "GenMsgCycleTime" BO_ 400 100;
BA_ "GenMsgCycleTime" BO_ 200 100;
BA_ "FieldType" SG_ 102 DRIVER_STATUS_enum "DRIVER_STATUS_enum";
BA_ "FieldType" SG_ 103 SET_STATUS_enum "SET_STATUS_enum"; 
BA_ "FieldType" SG_ 500 DBC_TEST1_enum "DBC_TEST1_enum";
VAL_ 102 DRIVER_STATUS_enum 7 "STOP" 6 "FWD_R" 5 "REV_R" 4 "REV_M" 3 "REV_L" 2 "FWD_L" 1 "FWD_M" 0 "IDLE" ;
VAL_ 103 SET_STATUS_enum 1 "GO" 0 "STOP" ;
VAL_ 500 DBC_TEST1_enum 2 "DBC_TEST1_enum_val_two" 1 "DBC_TEST1_enum_val_one" ;

Proximity ECU

The Proximity ECU was also known as the Avoidance module and can be found on GitLab here. It's sole purpose is to monitor the environment around the car and take measurements of objects that break the threshold.

Hardware Design

Figure 2: VL530X

Avoidance contains eight VL53L0X infrared sensors for obstacle detection that utilizes the I2C interface. The sensors measure a distance in centimeters with a maximum range of 2 meters with 1 millimeter resolution. The VL53L0X sensor used in the car was purchased pre-attached to a breakout board where the voltage was stepped down from 3.3V to 2.8V. The XSHUT pin on the sensor acts as an enable where they were wired to GPIO pins on the SJOne board. For the car, there were a total of 8 sensors used: 5 in the front, 3 in the back. The sensors come preassigned to an address of 0x52 but can be reassigned to a different address. The table for sensor address assignment and GPIO are seen below.

Sensor Position GPIO pin Slave Address
Front Right P2_2 0x54
Front Middle Right P0_30 0x61
Front Middle P2_1 0x56
Front Middle Left P0_29 0x63
Front Left P2_0 0x58
Back Right P2_5 0x5A
Back Middle P2_4 0x5C
Back Left P2_3 0x5E
Default none 0x52

Power for the sensors came from the car power supply regulated for 3.3V instead of from the SJOne board. The problem and solution of assignment of the I2C address can be seen below in the software design. The block diagram for this module can be found below. Note that for brevity, not all 8 proximity sensors are shown. Each sensor is connected in the same manner each with its own dedicated GPIO pin.

Figure 3: Proximity IR sensors

Software Design

For the software design, a C++ library was ported over from Pololu along with configuration of the library to work with the SJOne Board. A C wrapper was written to allow it to be unit tested and included in our periodic_callbacks.c file. There were difficulties with having a singleton object for 8 sensors that created conflict. This was resolved by having a table of addresses, that the singleton object would select and reassign its address with to sweep through the sensor addresses. The configuration of the reassignment of addresses also took careful consideration.

To reassign the address, the modification to the VL53L0X library from the Pololu library can be seen below.

void VL53L0X::setAddress(uint8_t new_addr, uint8_t i2c_write)
      writeReg(I2C_SLAVE_DEVICE_ADDRESS, (new_addr >> 1) & 0x7F);
  address = new_addr >> 1;

In this segment, we gave the option to change the software address instead of changing both software and hardware addresses. This gives us the ability to make a configuration change without changing the slave address register. The i2c_write signal is only enabled high on initialization and all device reads happen when i2c_write is low. This function is called before every switch to a new device. Since we adapted the VL53L0X library with the i2c read from the SJOne Board, we follow the convention of changing this software address before calling their modified method that retrieves and processes the sensor data.

The initialization function for these sensors followed the flowchart below of how to reassign the addresses of each sensor even when they all start with the same address. The process would first enable the first sensor with XSHUT using the GPIO pin, the select the device based on the default address. The function would then assign it the addressed based on the table above. After initializing was completed for one sensor, the sensor would stay enabled, but the singleton object would be reset back to 0x52 to set the next sensor.

Figure 4: Proximity IR Init Flowchart

To collect and store data from each sensor, a data structure was created to be placed in a CAN message. A function was created where it would again sweep the address space of the address table, and store the data to its respective location in the data structure. The VL53L0X library outputs the sensor data in millimeters, but due to the size of the CAN message and having 8 different sensors - a conversion was required. We selected our units to be centimeters so our CAN message would contain 8 different sensors with a maximum of 255 centimeters. We preferred not to split the data into two CAN messages, because it was vital that the data being sent to DRIVER was accurate and complete across all the sensors. This occurs every 20 Hz, where a CAN message is broadcasted on the bus for DRIVER to use.

  • 100Hz periodic
    • proxy_get_data(); - Call all 8 IR sensors, and retrieve data into structure
    • pack_avoidance_data(data); - Pack avoidance struct into CAN message for DRIVER
    • CAN_tx(can1, &can_msg_dbg, 0); - Send packaged avoidance struct on CAN bus
  • 1Hz periodic
    • Reset CAN Bus if Bus OFF

Technical Challenges

The only issues with the proximity sensors came from using a library provided by Pololu that used a singleton object. To address this issue, a table of addresses was created where the singleton object would sweep the table of addresses to retrieve data from each sensor. The singleton object would reassign its own address from the table to access each sensor individually. The process described in the software design was also used for retrieving data from each sensor.

Motor ECU

Figure 5: Architecture of Motor Controller

The Motor ECU also known as the Motor module was used to control the speed and direction of the RC car as well as determining the speed of the car.

Hardware Design

Figure 6: Architecture of Motor Controller
Figure 7: Architecture of Motor Controller
Figure 8: Architecture of Motor Controller
Figure 9: Architecture of Motor Controller

Motor contains a MW645 HiTech servo motor, brushless motor, motor driver board, and a hall sensor switch. Servo motor used to control the steering of the RC car working at a frequency of 50Hz with dutycyles (8%-left, 6%-middle, and 4%-right) for maneuvering obstacles utilizing PWM to set the frequency. The brushless motor was used to control the speed of the RC car working at a frequency of 100Hz with dutycyles (60%backwards, 70%-stop, and 75-85%-forward), a motor driver board Adafruit 16-Channel 12-bit PWM/Servo Driver - I2C interface - PCA9685 was used to control the motor via PWM as well, but at a different frequency. Lastly a hall switch was used to detect the speed of the RC car via GPIO pins set up as interrupts. Power for the sensors and servo came from the car power supply regulated for 5V instead of from the SJOne board. The problem and solution of assignment can be seen in the technical challenges section of this module.

Software Design

For the software design a C wrapper was written to allow the servo, motor, and hall sensor to be utilized in the periodic call backs with unit-testing capabilities. The C wrapper included PWM for the servo control, interrupt for the hall sensor, and i2c for motor control, leds and button switches for manual control and calibration. Once all the functions are written: set_speed, set_steering, i2c_writte, i2c_read, i2c_ init, switch_button, display_led, they can be used to test and control the RC car functionalities as well as determining the speed. The initialization function for this included initializing the PWM, i2c, interrupt, and CAN bus. Once everything is initialize only two main functions were use one to receive commands from driver and one to send the status of the RC car. The overall flow the program can be seen in the following figure where the receive can message checks for specific ID in order to kill the motor, set the speed and steering values, or driver heartbeat in order to determine if the driver is active. Once the driver sends a command the motor module will set the values and will send the speed values in m/s right after as well as check for ramp. Next the second main function, will send a status value of 1 for debugging purposes using BUS Master.

Figure 10: Architecture of Motor Controller


Periodic Init

  • Initializes CAN, servo PWM (50Hz), motor driver board PWM (100Hz), interrupt for hall sensor, and speed calibration

1Hz Task

  • Used to check the state of CAN and reset.

10Hz Task

  • Receive CAN message for Motor Control
  • Send Motor Status
  • Send Motor Speed

Technical Challenges

  • Motor and Servo Frequencies
  • Hall Sensor Speed Determination

Motor and Servo Frequencies

  • Single frequency for PWM on SJone board

Using PWM on the SJOne board only allows for one PWM frequency on all the PWM pins. For Motor, we required two different frequencies (motor (100Hz) and servo (50Hz)). We solved this by using a motor driver board using i2c to create a second PWM frequency.

  • Fluctuating PWM Frequency

The PWM for MOTOR varied by a few Hz depending on the environment causing the duty cycle to control the motor to change. We set the frequency to be 100Hz, but varied between 98.7Hz. This caused the dutycycle to change from 70% to 80% to initiate the motor.

  • Single frequency for PWM on SJone board

We solved this by using a motor driver board using i2c to create a second PWM frequency.

  • Fluctuating PWM Frequency

In order to be able to deal with any situation and environment the RC car will be in we created a function to manually calibrate the first speed by increasing the dutycycle by 1% using push buttons and automatically incrementing the higher speeds with it.

Hall Sensor Speed Determination

  • Creating a secure mount with enough magnets

Having trouble to detect more than one interrupt from the hall sensor no matter what speed, periodic callback speed (10Hz, 20Hz, 100Hz), and magnets.

  • Creating a secure mount with enough magnets

Created a custom mount with a magnet ring with alternating magnets with precise spacing to give the interrupt at 10Hz task and hall sensor enough time to reset.

Localize ECU

The Localize ECU (also called GEO controller) can be found on GitLab here. It's sole purpose is to provide compass, GPS, and directional data to DRIVER and COMMs

Figure 11: Geographic Controller

Hardware Design


The LSM303 was selected as the compass for the final product. The LSM303 is a digital compass that contains an acceleration sensor and temperature sensor onboard. It uses the I2C communication protocol and can be power via a 5V or 3.3V power source. For this sensor, the SJOne board directly powered the compass. The clock rate for the slave clock was set to 100 KHz. The compass requires calibration which will be described in the both the software and technical challenges.

Figure 12: LSM303 Block Diagram


Figure 13: Neo-6M GPS Module

The Neo-6M GPS module was the device used to supply the SJOne board with positioning information. It communicates using the UART protocol and sets information at a baud rate of 9600. When powered, the module automatically sends various NMEA sentences at a rate of 1 Hz. Its only connection to the SJOne board is through the Rx pin through the Neo-6M Tx pin. The module itself has two main components: the Neo-6M module and the receiver. For optimal results, the receiver and module were mounted separately and above all other components with the white side facing up and the metallic side of the receiver facing down. While results were not perfect, it provided the most accurate and consistent results.

Software Design


For the LSM303, the compass required an initialization function to set all the registers. The slave address for writing to the LSM303 is 0x3D and to read from the registers is 0x3C. The data for the compass can be read from 6 8-bit registers, where X Y and Z axis are divided into high and low registers. The data is represented in two’s-complement where the conversion needed to be done after merging the two bytes together. As per the datasheet, the X axis is registers 3 and 4, where register 3 is the high byte and 4. The same goes for the Z axis which is registers 5 and 6, and Y is registers 7 and 8. After the raw coordinates are obtained, the calibration goes into effect.

LSM303 Slave Address Operation
0x3D Write
0x3C Read

An example code sample for how we retrieved data can be seen below, where ret_data is a struct for the LSM303 data.

    ret_data.xMag = twos_complement(buffer[0], buffer[1]);
    ret_data.yMag = twos_complement(buffer[4], buffer[5]);
    ret_data.zMag = twos_complement(buffer[2], buffer[3]);

    ret_data.xMag -=26;
    ret_data.yMag +=244;

    if(ret_data.yMag > 2)
        ret_data.yMag -= 135;
    if(ret_data.xMag < -42)
        ret_data.xMag += 133;

Figure 14: Compass Heading Calculation Flow

To calibrate the compass, the X and Y points were plotted while negating the Z axis. The Z axis was negated because the calibration of the compass requires that the compass be perfectly level. A sample of points were collected, and the plot can be seen below.

Figure 15: LSM303 raw data, no shifting, no calibration

To calibrate the compass, offsets were added such that the circle is centered at 0,0. The calibration was then added, and it looks like the plot below.

Figure 16: LSM303 X shifted down 50, Y shifted down 50

An additional feature that needed to be added was closing the gaps created from having the compass close to the car. Shifting was needed to close the circle when there were gaps in the calibration caused from the electronics of the car.

Figure 17: LSM303 data on car with gaps caused by electronics

After the points have been calibrated, an angle was formed from the point in relation to 0,0. The points go counter clockwise it goes in the order of North - 0, East - 90, South - 180, West - 270. The X and Y points were outputted to the CAN bus as a debug message and the compass direction was used for pathing.

Figure 18: Compass direction based on points


There are three main stages to collecting data from the Neo-6M GPS module. The first is to store the raw data into a string from the UART receive buffer. The second step is the verify the validity of the message and the string and store the different parts of the message into a struct if the message is valid. The final step is to verify the coordinates stored in the struct.

Message collection from the Neo-6M is handled through the UART library on the SJOne board. Two of the functions were used:

  • is_Queue_Not_Empty() which returns the value of getRxQueueSize()
  • gets() which stores the messages into a string and is ended through a timeout, reaching the final character of the string, or a new line character

Due to messages arriving once per second, these two functions were necessary. The is_Queue_Not_Empty() function only allows reading if a message is ready on the queue. This will prevent a message from being cut off due to timeout. Since every message is appended with a new line character, gets() will store the message into the buffer without storing parts of a new message.

For the second part of the GPS data collection, the Tiny GPS++ library was used. This provided us resources to parse and store the data from the various NMEA libraries. Furthermore, the library contains a TinyGPSCustom class which allows us to focus only on the $GPRMC messages. If a message was stored into a buffer, then this second part of GPS data collection occurred. The first step is to run the Tiny GPS++ function encode(char c). This function passes in a char and stores it in a substring. Each substring is created after receiving a $ and is eventually checked once the * character was received. If the checksum passes, then the commit() function is called which stores all of the data in the string into a struct. The following shows an example of the $GPRMC message.


The fields of GPRMC are message time, current time in UTC, validity of GPS, latitude in degree-minute-second, north/south, longitude in degree-minute-sentence, east/west, speed over ground in knots, track angle, current date, magnetic variation, and checksum. Of these fields, the GPS validity, latitude, longitude, and checksum were used. When latitude and longitude are stored in the struct, they are converted into a decimal form from degree-minute-second.

The final part of the GPS data collection is to validate the latitude and longitude values obtained from the Tiny GPS++ library. Due to nuances with the string, it is possible to obtain values that are inconsistent with the current location. It is also possible to get a latitude and longitude of 0 if the validity of GPS field of the $GPRMC message had a V for void. The check was performed by the valid_coordinates(float latitude, float longitude) function and would only return true if the latitude was between 37 and 38 and if the longitude was between -122 and -121. Should these conditions meet, the coordinates were updated and the value is sent to the CAN bus. If they aren't meet, the old values remain the same until a validity check changes them.

Figure 19: GPS Data Collection Flow
  • 1Hz periodic
    • Reset CAN Bus if Bus OFF
    • getGPSData(); - Extract GPS data and place in GPS struct
    • dbc_encode_DESIRED_VECTOR(); - Encode distance vector to CAN message
    • dbc_encode_LOCALIZE_GPS(); - Encode GPS data to CAN message for Comms and Driver
    • pack_compass_dbg(); - Pack compass data into CAN message
    • CAN_tx(); - Send CAN Message
  • 10Hz periodic
    • call_compass(); - Extract Compass Heading and Magnetic Flux values of X and Y
    • find_direction(); - Generate a heading and distance vector based on GPS and compass data
  • 100 Hz periodic
    • CAN_rx(); - Receive CAN message 500 if available for waypoint data

Technical Challenges

Compass Issues

  • Issues with Calibration of LSM303
    • The problem with the LSM303 and all digital compasses, is there is a lot of interference with magnetic fields in buildings that requires calibration for different environments. In addition, compasses do not come pre-calibrated as the strength of the North pole varies depending on location. To resolve this issue, we calibrated the compass in various locations. Our final calibration that we used and kept was the furthest away from any potential magnetic fields. Our selected location was on the top floor of the engineering building that was away from any lab rooms or elevators.

Unreliable GPS lock

  • Obtaining Coordinates
    • When attempting to obtain coordinates, the GPS module took several minutes in order to connect to a satellite and return coordinates. This process took more time if the GPS module was in a building or in a non-open area.
    • Information from the GPS module was delivered at a rate of 1 Hz. When this information arrived, there were several possibilities such as $GPGGA, $GPGSV, and $GPRMC.
      • Coordinates were only obtained when $GPRMC messages were sent and it yielded an Active (A) coordinate.
      • There was no way to manipulate the GPS module to only send $GPRMC messages.
  • Parsing Data
    • All messages are appended with a new line "\r\n" at the end allowing it to be perfect candidates for the UART gets function. To help with consistency, the gets function was only called when there were characters in the queue to prevent the read from being timed out.
    • Occasionally, some messages become cut off and merged with the next message. This skewed readings as commas were used to separate different sections of the data, including instances where $GPRMC was sent.
    • To save resources, coordinates were only calculated when $GPRMC messages were sent with an (A) status message. This data is presented in a degree, minute, second format as opposed to the decimal format used by the SJOne and the mobile application. As a fail safe, converted coordinates were only saved and sent if the value yielded a latitude between 37 and 38 degrees and a longitude between -122 and -121 degrees.

Comms ECU & LCD

The Comms module source code includes the code for the ESP32 WiFi device and LCD display. Below is the block diagram for this module's connections.

Figure 20: Comms module diagram with LCD

Hardware Design

LCD Display

The LCD display used is a 2.4" TFT display made by Adafruit. This display uses either the 8-bit or SPI protocol to transfer data. For this application, the SPI protocol was used in combination with an additional Data/Command line to distinguish between command packets (pulled low) or data packets (pulled high). The display is then connected via a controller that determines what pixels to activate and color. The controller itself uses the I2C protocol to communicate with the Comms board.

Software Design

ESP32 WiFi Module

On the side of the SJOne board, the UART library is primarily used to communicate with the ESP32. For incoming messages, the SJOne relies on two primary functions: getRxQueueSize() and gets(). The former checks to see if there are any incoming messages from the ESP32 while the latter stores the message into a string. To mitigate the possibility of a message getting truncated due to being read late, the board will only read if anything is present in queue. Once the message is read, it is parsed. There are three possible messages from the ESP32: start, stop, and coordinate data. The start is denoted by an 'A' character while the stop is denoted by a single 'Z' character. For coordinates, it is formatted like the message below


The type of message is determined through reading the first character of the string. If the first character is a '3', then the SJOne reads it as coordinates and uses the '|' to separate latitude and longitude. Both latitude and longitudes are stored in substrings until the end when they are converted to float using atof().

To simplify the use of web sockets, latitude, longitude, the status of a waypoint being reached, and a question mark are all sent. A sample of the message sent to the ESP is shown below.


The first field is longitude and then followed by 'w' for west. The next field is latitude followed by an 'n'. The third field is an O or N. This represents if a waypoint is reached with O meaning waypoint not reached and N representing a waypoint is reached. The status is determined if the distance is below 5 meters. All of this information is retrieved from the CAN bus and is appended together using the sprintf() function.

LCD Display

The main elements we wanted to be able to see on the car were speed and destination. Destination is displayed in Lat/Long decimal degree coordinates and speed is in kilometers per hour. In addition, we also show the distance between our car and the destination and our current heading from due north. Due to the complexity of the display, each pixel requires about 7 bytes to activate, the possibility of overrunning the scheduler was high. As such, a controller was inserted to perform the actual data transfer to write pixels. The Comms module simply tells the controller what to update, via I2C, from the 1Hz periodic function. To make writing pixels a little easier, the Adafruit GFX Library was used to create fonts, lines, and the triangles used to mimic a compass.

Figure 21: LCD display

There are five basic functions used to update the display, four are used to set values to a data struct and the fifth sends the data over I2C:

  • set_display_waypoint() - packages the latitude and longitude destination struct values as a C-string
  • set_display_speed() - packages the car's current speed struct value in a C-string.
  • set_display_distance() - packages the distance remaining between the car and the destination in a C-string
  • set_display_arrow() - selects which heading arrow the display should draw: N, S, E, W, NW, NE, SW, or SE
  • updateDisplay() - sends the full struct that has been prepared by the above functions over I2C

Technical Challenges

LCD Issues

Originally the plan was to use a 2.2" TFT display by QVGA. This display had been used in previous projects before, however, in this project there was nothing but problems. The SPI communication from the SJOne board was not being received or sent by the display. When testing on a known board and the Adafruit library, the SPI comms would respond, but nothing would actually display on the screen. Assuming this particular display had been compromised, additional displays were purchased with the same results. After much Googling and more research, it turned out that this particular display is known for failures and inconsistencies. The Adafruit display was the original the QVGA was based on. Once that was purchased and tested, there were no further issues, both SPI communications and the display worked fine.

Driver Module

Driver module source code can be found on GitLab here.

Hardware Design

With respect to hardware, the DRIVER ECU design has very little going on. Other than the CAN transceiver, the only anticipated components were level-shifting operational amplifiers that would have served the purpose of providing voltage readings. Although designed, we decided to forego implementation for a simpler solution: battery indicator buzzers that will alert us if any cell of the Lithium Polymer batteries we use approach the fatal 3.0V voltage.

Software Design

The main purpose of the DRIVER ECU is to fuse the information AVOIDANCE and LOCALIZE, in order to inform MOTOR of the necessary steering and motor commands to assert on the steering servo motor and the brushed DC electric drive motor. The following diagrams informs you on at a high level on how the information is gathered from each of those ECUs, then fed to DRIVER.

CANT Bus Logic - Localize.png

CANT Bus Logic - Avoidance.png

CANT Bus Logic - Driver.png

Given the premise established in these diagrams, we wish to 1. translate the avoidance data into a set of permissible (or, in other words, admissible) velocities 2. use the heading and distance information to inform us on how to prioritize the velocities. As in, if the desired heading is straight ahead, we will prefer the forward velocity over all others. 3. assert the velocity - this is the motor_cmd value we send to MOTOR

From this premise, we describe the key routine calls within our periodic function:

  • 100Hz periodic
    • check_CAN_messages() - as the name suggests, this function checks the registers to see if any CAN messages were received and pulls the needed data from them.
  • 10Hz periodic
    • determine_admissible_velocities() - based on the proximity sensor information from AVOID, determine whether traversal is feasible. If not, nullify the velocity for this 10Hz time slice.
    • determine_system_state() - determine the mode and directionality for this time slice:
      • this can be IDLE, which informs the system that there are no waypoints being serviced
      • STOP, which informs that the car is currently stopped but is servicing a waypoint,
    • and the directionalities: FORWARD_MID, FORWARD_LEFT, FORWARD_RIGHT for forward directions, and
    • choose_velocity_by_preference_order() - based on the state determined above, we can refer to a preset list of preferences for our directionality. From this, we select the best available velocity for this timeslice.
    • set_velocity() - send the selected velocity (or non-velocity) to MOTOR
  • 1Hz periodic
    • set_manual() - checks if manual mode has been selected, this is mostly used for testing and resetting to a known state.
    • send_reset() - checks the status of CAN and if it needs to be, resets the bus
    • set_heartbeat() - sends out a blank message to keep all the other modules in check

Technical Challenges

The biggest challenge we faced was the vector array algorithm as it involved a lot of math that some of us hadn't touched in many years. Eventually we were able create an algorithm what was able to take all the proximity values and plot a course of action to avoid objects while still maintaining a heading to our final destination.

In fact, we attempted several iterations of the Dynamic Window Approach[11] popularized by Sebastian Thrun, et al[12]. At each phase, we had reduced our window size, until what we were left with was one speed forward, one speed back, and one direction to either side (no fractional angles for steering commands). This left us with an unstable platform (as you can see from the left-right zigzags where you would expect smooth forward motion. Perhaps it would have been better had we 1. Started with this as a base case, and expanded from there, and 2. Ran simulations

Mobile Application and ESP32


Hardware Design

The App and the Car are connected via wifi and through the means of ESP32. The ESP32 can perform as a complete standalone system or as a slave to a host MCU, reducing communication stack overhead on the main application processor. Here, the ESP32 is being used as a host that sets up a webserver on a desired wifi connection. The wifi connection is provided by the user’s shared hotspot. The credentials of these are to be entered within the source code of the application.

Once the web server has been set up, both the ESP32 (on behalf of the SJOne board) and the App can publish messages to the web server as needed, through the means of websockets. WebSocket is a computer communications protocol providing full-duplex communication channels over a single TCP connection. The ESP32 receives a string via UART from the SJOne until a specified delimiter to differentiate between multiple messages. This string is published to the server, for the app to echo and read as necessary.

Software Design

The Mobile Application is tasked with utilizing Google Maps API, essentially acting as a UI between the user and the car. It relays the current position of the car, or allows the user to set the starting point of the car as needed - since the given point is a draggable. Similarly the destination is marked with a similar marker, which when on holding down gives the relevant descriptions of each point.

The next challenge came with being able to track the current trajectory of the car. Therefore the set objectives, was to have the set marker track the position of the car in real time, along with the refresh rate of the GPS. In this case since the GPS coordinates are being updated in the 1 Hz task, therefore a handler function is enlisted in its design that periodically checks the debug info being relayed back and forth between the Car and the App.

In order to navigate between the start and the stop point, we have to calculate the shortest possible distance between the two points. While this may be possible with just a simple distance formula, we still have to take on sample waypoints in anticipation of large obstacles or to follow a set road. This is done by creating a graph, wherein the points are labeled either according to their index or a set naming convention, and distances between these points define the cost of traversing that particular edge.

Technical Challenges

Initially, the idea was to have the ESP32 publish a webpage at a specific IP Address that the phone may access with inline buttons to further act a UI. However this was very flawed in its approach as it meant accessing web pages and not utilizing or designing UI. HTML buttons as is would open more such webpages which would become redundant and difficult to manage, especially with reusing buttons.

Websockets as a resource are limited and need to be managed efficiently so as to not lose count of the number of active websockets. As the app and the websocket server keeps running for a long time (20 minutes and over) we find a significant lag and backlog of messages being stored on the server before being published on the receivers end. The ESP8266 and the ESP32 can only hold up to 4 websockets at a time. This is important to note since we encountered a problem where we would use up all the 4 resources simultaneously to send debug messages to the app from the car, while leaving no room for error or overflow. If overflow of resources does occur then the resources are stuck in an infinite loop and are left inaccessible. The work around to this problem is to use 2 resources only, each allocated to either device viz the host ESP32 and the client App. The resources are withheld during their use but disconnected upon reading the message so that they do not populate.

When running the dijkstra algorithm on a complete graph, the app essentially takes into consideration all the specified weights, including the distance between the start and destination point which is always a straight line and thus always the shortest path. This is an undesirable result, hence one work around to this is to populate the graph less often rather than calculating all possible distances. Eventually it was easier to set all the distances between different points as the maximum possible traversable distance and then manually add legible and realistic distances as per CalcDistance(). Once this newer doctored graph is fed through Dijkstra’s algorithm we get a reasonable traversable path for the car to follow.


Over the course of this project, we learned about the importance of planning and communication while working in a large group. The difficulties of coordinating so many people was mitigated slightly by having multiple meetings during the week. Each member was encouraged to go to as many of those meetings as possible. Some were able to make it to all while others could only make it to 2 or 3. This allowed some continuity in combining all the modules together. We learned about CAN bus protocol and how powerful of a tool it can be. The importance of unit testing was stressed as well and we learned effective ways to test each line of code as we designed the modules. This project gave us hands on experience with working with others to complete a united goal of building a RC car. Although we had some difficulties with scheduling meetings due to our different schedules - we managed to coordinate in person and on our Slack channel. Our project was to design and build an autonomous RC car that would travel to a selected GPS coordinate while passing through checkpoints and avoiding obstructions. Although our RC car did not quite reach the mark that we were expecting, much was learned from this experience that we can use in our future projects and industry jobs.

Project Video


Project Source Code


Advise for Future Students

  • Form your team and start as EARLY as possible
  • Buy spare parts, things will break when you least expect them to happen
  • Don't wait to the last minute for full integration
  • Buy quality parts rather than cheaper alternatives


We would like to thank Preetpal Kang for providing us the opportunity to learn about the CAN bus protocol and how to integrate it into an embedded system. We'd also like to thank our team leader, Nelson, for keeping us on task and always being there to help and provide counsel whenever it was needed.


[1] MPU-9255 Data Sheet

[2] MPU-9255 Register Map

[3] Magnetometer Data Sheet

[4] LSM303 Data Sheet