Difference between revisions of "S22: Silver Arrow"

From Embedded Systems Learning Academy
Jump to: navigation, search
(Sensor interface to sjtwo LPC board and signal conditioning)
(Compass)
Line 638: Line 638:
 
[[File:Compass_(1).jpg|600px|left]] [[File:compasscalculations.JPG|300px|centre]]
 
[[File:Compass_(1).jpg|600px|left]] [[File:compasscalculations.JPG|300px|centre]]
  
 +
<h6> Compass calculations </h6>
 
<pre>
 
<pre>
 
static void get_current_compass_heading(void) {
 
static void get_current_compass_heading(void) {

Revision as of 02:47, 28 May 2022

Silver Arrow RC

Contents

SILVER ARROW

SACar side view.jpg SACar top view.jpg



Abstract

Silver Arrow RC is a self-navigating, electric, battery-powered RC car. The goal is to use GPS navigation to get to the location specified in the Android app. To sense its surroundings and avoid obstructions in its route, the car combines input from several sensors and make decisions to navigate itself  to the destination location

Introduction

The project was divided into 5 modules:

  • Bridge and Sensor Controller
  • Motor Controller
  • Geographical Controller
  • Driver and LCD controller
  • Android Application


Team Members & Responsibilities

SATeam picture.jpg

Rishabh Gupta

  • Driver Node
  • Compass & GPS Calibaration
  • Waypoint algorithm

Vilas Dhuri

  • Mobile Application
  • Car Mounting and hardware assembly
  • Wiki page manage

Vivek Tapkir

  • Sensor controller
  • Communication Bridge Controller

Saharash Shivahre

  • Geo controller
  • LCD Integration

Naveena Sura

  • Geo controller
  • Line Buffer, Geo Logic and Waypoint algorithm
  • Git repo manager

Daya Modekar

  • Motor controller
  • Hardware Integration and design

Pushkar Deodhar

  • Code Reviewer
  • Unit Testing
  • Hardware/circuit/PCB designing


Schedule

Week# Start Date End Date Task Status
1 03/16/2022 03/22/2022
  • Read previous projects, gather information, and discuss among the group members.
  • Distribute modules to each team member.
  • Completed
  • Completed
2
03/23/2022
03/29/2022
  • Purchased RC car and batteries.
  • Research and finalize which ultrasonic sensor the project will use
  • Purchased Bluetooth connector
  • Research math needed to determine the distance between navigation points. Decide on distance algorithm
  • Create a branch for motor controller driver. Create draft template API for motor controller
  • Using previous projects, determine what works needs to be completed for main board. Bring findings to weekly meeting
  • Completed
  • Completed
  • Completed
  • Completed
  • Completed
  • Completed
  • Completed
3
03/30/2022
03/04/2022
  • Ordered and received necessary parts for the car
  • Getting acquainted with the basics of the android studio application
  • Researched basic mobile application development
  • Completed basic driver logic code, Motor controller, and sensor node logic
  • Completed
  • Completed
  • Completed
4
04/05/2022
04/11/2022
  • Updated DBC file as per requirement
  • Completed ultrasonic sensor level testing
  • Completed Bluetooth configuration for mobile app connection, and established communication with SJ2 board
  • Developed a basic mobile application showing maps and current location
  • Began coding compass data
  • Ongoing testing of RPM sensors, motor logic, and sensors for different node
  • Completed
  • Completed
  • Completed
  • Completed
  • Completed
  • Completed
5
04/12/2022
04/18/2022
  • Sensor data tuning and crosstalk avoidance by loading multiple samples into the buffer
  • Bridge controller data sending and receiving over Bluetooth has been established
  • Implement an application with a startup Bluetooth connection page and test Bluetooth data transmission
  • Implement Maps page with OnClick marker with coordinates
  • Ensure CAN bus nodes are communicating correctly by verifying PCAN data.
  • Unit Test Direction Distance Calculation Module. Manual calculation of data should match module output
  • Tune driver and obstacle avoidance algorithm based on data from sensor nodes with unit-testing
  • Begin laying out hardware requirements and discuss hardware integration
  • Start researching on Wheel encoder according to the requirement
  • Completed
  • Completed
  • Completed
  • Completed
  • Completed
  • Completed
  • Completed
  • Completed
  • Completed
6
04/19/2022
04/25/2022
  • Begin to analyze real-world tests from the previous week's implementation and perform fixes for issues faced
  • Final integration and of all modules (sent data from GPS&Compass to->Driver to->Motors & Wheels)
  • Start working on the stabilizing hardware and also purchase the required components
  • Integration testing with obstacle avoidance
  • Start the work on the WayPoint Algorithm
  • Send Destination coordinates over BT to the Driver node
  • Complete Prototype 1
  • Complete
  • Complete
  • Complete
  • Complete
  • Complete
  • Complete
7
04/26/2022
05/2/2022
  • Stabilize hardware and Canbus nodes are communicating correctly by verifying PCON data
  • Added CAN debug messages and MIA LEDs
  • LCD integration for better and fast debugging of car data
  • Start PID testing and tuning of the motor car
  • Work on compass calibration and integration of data with other can modules
  • Work on Mobile application car data display page
  • More integrated testing and analyzing sensor response time and data while the car is moving
  • Complete Prototype 2
  • Complete
  • Complete
  • Complete
  • Complete
  • Complete
  • Complete
  • Complete
  • Complete
8
05/3/2022
05/09/2022
  • Finishing touches on Mobile application
  • Update Wiki Report to reflect all changes and include final testing video
  • Make final changes and commits in the obstacle avoidance and sensor node logic
  • Test and improve the RC car performance based on the changes
  • Complete Prototype 3
  • Complete
  • Complete
  • Complete
  • Complete
  • Complete
9
05/10/2022
05/16/2022
  • Finalize phase of the application and commit the changes
  • Test outdoor drive with corner conditions
  • Test the Way-point algorithm and make tunings accordingly
  • Make fine-tunings to Different nodes
  • Complete
  • Complete
  • Complete
  • Complete
10
05/17/2022
05/23/2022
  • Final testing
  • Update Wiki page
  • Make commits to GIT
  • Complete
  • Complete
  • Complete
11
05/25/2022
05/25/2022
  • Final Car Demo
  • All members update their respective Wiki sections
  • Complete
  • Complete


Parts List & Cost

Item# Part Desciption Vendor Qty Cost
1 The Traxxas Rustler XL-5® Stadium Truck Traxxas [1] 1 $250.00
2 Waveshare SN65HVD230 CAN Transceivers Amazon [2] 6 $59.34
3 Adafruit Ultimate GPS Breakout Adafruit [3] 1 $29.95
4 LCD 20*4 RPIGEAR [4] 1 $30
5 Adafruit GPS Antenna Adafruit [5] 1 $19.95
6 DC 5v voltage regulator XL6009 Amazon [6] 1 $11.99
7 Traxxas 6520 RPM Sensor Traxxas [7] 1 $13.76
8 Traxxas Trigger Magnet Traxxas [8] 1 $3
9 Maxbotix LV-MaxSonar-EZ1 Ultrasonic Range Finder Amazon [9] 4 $120 ( Provided by Dr. Ozemek)
10 Traxxas LiPo Batteries(2) with Dual iD charger(1) Traxxas [10] 1 $269.99 ( Provided by Dr. Ozemek)
11 DSD TECH HC-05 Bluetooth Serial Pass-through Module Amazon[11] 1 $9.99
12 HiLetgo CP2102 USB 2.0 to TTL Module Serial Converter Amazon [12] 1 $6.29
13 Connecting wires, LEDs, switches Anchor electronics [13] 1 $97


Printed Circuit Board

<Picture and information, including links to your PCB>



CAN Communication

<Talk about your message IDs or communication strategy, such as periodic transmission, MIA management etc.>

CAN protocol was used to establish the communication between the four controllers. The Message IDs for the messages transmitted by the four controllers were selected such that the data that has to be sampled more often than others had the highest priority in arbitration. We also used Debug CAN messages to output any necessary information needed for Debugging, like the GPS fix lock, Destination bearing etc.,

Different controllers are configured to transmit their messages at different periodicities based on the data sampling rate needed for smooth movement of the car.

Hardware Design

We used the CAN in Full CAN mode at 100Kbps baud rate, and enabled hardware filtering to receive only the messages needed. Four CAN transceivers were used to connect the Controllers to the CAN bus. Terminal resistors of 120 Ohms are used at the ends of the CAN bus to reduce signal reflection.

DC Motor and ESC

DBC File

https://gitlab.com/naveena.sura/silver-arrow/-/blob/MOTORCONTROLLER/dbc/project.dbc



Sensor and Bridge ECU : Sensors

GitLab Link:Sensor and Bridge controller

Hardware Design

The sensor part of the bridge and sensor ECU board is responsible for receiving the sensor readings, converting them to Inches, and then sending them to the driver controller board over CAN Bus.

Component selection

Sensor selection was a critical part of the sensor controller because time constraints and range data accuracy were the key challenges. Two ultrasonic transducers sensor type was our first choice, but since it needs to have time measurement between the transmitted trigger signal and received echo which was complex software logic, it got ruled out. Maxbotix sensors have easy-to-use output formats and low power consumption of 2 mA for 3v power. The easier output format was to fetch distance readings in terms of change in analog voltage form at the scale of ~6.4 mV/inch (with a 3.3v supply).

Maxbotix LV-MaxSonar
Sensor variants selection to avoid crosstalk and data precision

After mounting sensors all together with their alignment, there is the probability of adjacent sensors creating crosstalk in their sensor readings. To avoid this scenario, we have followed a different Maxbotix LV-MaxSonar sensor variant selection approach. One for front and rear sensors, MB1030 series with narrow beam pattern coverage in their transmission vicinity. And another sensor selection for the Left and right sensors, MB1000 has a wider beam pattern to cover maximum obstacles around corner edges.

Beam characteristics of Maxbotix LV-MaxSonar sensors

Sensor interface to sjtwo LPC board and signal conditioning

The power given to the Vcc pins of each Maxbotix sensor was +3.3v since it has less current consumption as compared to the +5v interface. Since we were using the Analog output of Maxbotix sensors, onboard ADC (Analog to digital converter) pins were used for the sensor interface. LPC 408x has a 12-bit successive approximation analog to a digital converter. These ADC have been configured to read sensor readings on a scale of 4096 counts. To use sjtwo pins as ADC with better signal accuracy, there was the additional change required in default ADC drivers which require disabling pull-up and pull-down resistors from the IOCON register of each ADC initialization. Maxbotix sensors need to get triggered by logic high pulse on their Rx Pin, this has been implemented by setting a dedicated trigger pin as GPIO output.


ADC initialization configurations

By doing IOCON configurations specific to ADC channel, selection of analog mode and disabling pull-up, pull-down mode, sensor inputs were configured for 3 ADC channels and 1 DAC Channel which was configured as ADC using IOCON Selection.

Software Design

<List the code modules that are being called periodically.>

Technical Challenges

< List of problems and their detailed resolutions>



Sensor and Bridge ECU : Communication Bridge

GitLab Link:Sensor and Bridge controller

Hardware Design

Software Design

<List the code modules that are being called periodically.>

Technical Challenges

< List of problems and their detailed resolutions>



Motor ECU

Hardware Design

Motor Controller Node includes the operational control of the DC motor, Servo motor, Electronic speed control (ESC), and the wheel encoder(RPM Sensor). The job of the motor controller is to control the steering of front wheels at appropriate angles and to spin the rear wheels at speeds commanded by the driver node in order to traverse the RC car to the destination location.The DC motor, servo motor, and ESC(Traxxas ESC XL-05) were provided with the Traxxas RC car. The wheel encoder and the trigger magnet were purchased separately from Traxxas's website.

Motor Node Pinout
SJ2 Board Pin Description
5V Input power
3.3V CAN transceiver power
PWM2 P2.1 DC Motor Speed Control
PWM5 P2.4 Servo Motor Angle Control
CAP0 P2.6 RPM Sensor\Wheel Encoder
CAN1 TX CAN Transceiver Tx
CAN1 RX CAN Transceiver Rx
GND Grounding

DC Motor and ESC

The DC motor is controlled by the ESC using PWM signals which were provided by the motor controller board for forward, neutral, and reverse movements.The DC motor and ESC were provided with RC car.The ESC is powered ON using a 7.4 LiPo battery.The ESC converts this 7.4V to 6V and provides input to DC Motor.

DC Motor and ESC
ESC wires Description Wire Color
Vout Output Power (6V) RED
GND Ground BLACK
PWM PWM input from SJ2-Board (P2.1) WHITE

The car can be operated at 100Hz in the following 3 modes :
Sport Mode (100% Forward, 100% Brakes, 100% Reverse)
Racing Mode (100% Forward, 100% Brakes, No Reverse)
Training Mode (50% Forward, 100% Brakes, 50% Reverse)


Servo Motor

The servo motor responds to PWM pulses. It has three pins namely Vcc, PWM Input Signal, and GND. The servo is powered using 6V from the car battery. Based on the PWM signal supplied from the SJTwo board the front wheels are turned.

Servo Motor
Servo Wires Description Wire Color
Vin Input Voltage (6V) RED
GND Ground BLACK
PWM PWM input from SJ2-Board (P2.4) WHITE

The PWM frequency for our Traxxas Servo motor also needed to be 100Hz. An idle (wheel's pointing forward) duty cycle is 15%. The full duty cycle range is [10%, 20%], where [10%, 15%) is the steer left range, and (15%, 20%] is the steer right.

Wheel Encoder

For speed sensing we purchased a Traxxas RPM sensor as it mounted nicely in the gearbox. The RPM sensor works by mounting a magnet to the spur gear and a hall effect sensor fixed to the gearbox. To get the revolutions per second we used Timer2 as an input capture.

RPM Sensor
Trigger Magnet
RPM Sensor Wires Description Wire Color
Vin Input Voltage (6V) RED
GND Ground BLACK
Sensor Output Input Capture to SJ2-Board (P2.6) WHITE

Software Design

The software for the motor node was divided into multiple files and made modular to improve readability and understanding of the complex logic involved.The main code modules are for:

  • Servo Motor
  • DC Motor
  • RPM Sensor
  • Motor State Machine
  • PID

Technical Challenges

  • First and foremost, the Traxxas motor ESC and other Traxxas components are Traxxas Hobby Parts, and they are not designed for development. As a result, no technical specification documentation or program/development guidelines are accessible. The motor ESC must be tested by supplying PWM duty cycles in different sequences at different duty cycle percentages. We utilized the remote control to recreate certain circumstances in which the ESC acted strangely.
  • If a Lipo battery is used, the ESC setup switches to the Lipo battery state. When the NiMH battery is replaced, the ESC begins flashing the led in a green-red pattern, and the ESC power button stops working. In this case, the handbook contains a calibration procedure that can rescue your boat and restore regular ESC operation.
  • Another challenge with the motor node program development is that you cannot rely on unit testing. Whoever works on the motor node, should make sure that the sequence of the duty cycles being fed to the ESC produces the expected results on the motor as well. The forward-reverse transitioning and the speed controlling both can be a bit tricky. Also, the hard brake logic can take a while to get working properly.




Geographical Controller

https://gitlab.com/naveena.sura/silver-arrow/-/tree/GEO_COMPASS/projects/lpc40xx_freertos/l5_application

Hardware Design

The geographical controller handles all compass and GPS data processing. The controller communicates with the Adafruit Ultimate GPS Breakout via UART, which provides accurate GPS data formatted in GPGGA. An antenna has been used to receive the GPS coordinates correctly. The controller uses the I2C protocol to communicate with the Adafruit Magnetometer in order to determine our heading and where the car should point in order to get closer to its destination. The magnetometer provided us with data with a maximum deviation of 5 degrees in any direction. The magnetometer started providing the accurate values only after calibration.

GEO CONTROLLER.png
Table 5. Geographical Node Pinout
SJTwo Board GPS/Compass Module Description
P4.28 (TX3) RX Adafruit GPS Breakout
P4.29 (RX3) TX Adafruit GPS Breakout
P0.10 (SDA) SDA Adafruit Magnetometer
P0.10 (SCL) SCL Adafruit Magnetometer
P2.8 CAN transceiver (Tx) CAN transmit
P2.7 CAN transceiver (Rx) CAN receive
Vcc 3.3V Vcc Vcc
GND GND Ground

Software Design

The GEO controller consists of three main modules namely GPS processing, compass Processing, and Waypoints Algorithm. These three main module are then used by the GEO logic to determine where the car should be moving. The Geo Controller receives the desired Destination location coordinates from the Bridge Controller on the CAN bus and calculates the destination bearing and the distance to the destination using the haversine formula, which are sent to the DRIVER node over the CAN bus. The current compass heading is also sent to the DRIVER controller which helps it make a decision to steer the car.

This controller is also responsible for directing the car on the shortest path to the destination location using waypoints algorithm(following the checkpoints). Here, when a desired location is entered, and a set of waypoints are selected, the Controller selects the next best waypoint using the shortest path algorithm and calculates the destination bearing accordingly. This next best waypoint selection continues until it reaches the desired destination location. These waypoints act as checkpoints to reach the destination in a predestined path instead of a straight path.

The GPS module that we used to fetch the current location coordinates transmits data to the GEO controller using UART3. This data is sent to the line buffer to buffer the data and the Geo logic module fetches this data from the line buffer and processes the GPGGA strings to calculate the current polar latitude and longitude coordinates.

The GPS module also provides a FIX signal to indicate whether the GPS module has fixed on a satellite or not. This information from the FIX signal is used to glow an LED to indicate that the GPS signal is fixed. The same information is also sent over CAN bus and displayed on the LCD display for debugging.

Compass

In our design we have used magnetometer to get the current heading angle of the car. Because of the necessary calibrations that came with the code, the compass module was one of the toughest aspects of the GEO controller. The compass is configured to communicate with the SJ2 board over I2C.   The accelerometer was not employed in the early phases of research, but it was necessary to account for tilt while the automobile was driving at high speeds. To get the actual and stable data compass required precise calibration and different mathematical computations.

Our method involved first getting raw x, y and z values of the magnetometer from the compass. These values are non calibrated. Hence we then needed modify these value through the added calibration matrixes in the code. How to calculate these values can be find the "Technical Challenges Faced" column. The modified values are then used to calculate the compass heading using the formula as mentioned in the code snippet below

Compass (1).jpg
Compasscalculations.JPG
Compass calculations
static void get_current_compass_heading(void) {

  float magnitude =
      sqrtf(magnetometer_processed.x * magnetometer_processed.x + magnetometer_processed.y * magnetometer_processed.y +
            magnetometer_processed.z * magnetometer_processed.z);
  float Mxz = magnetometer_processed.z / magnitude;
  float Mxy = magnetometer_processed.y / magnitude;
  float Mxx = magnetometer_processed.x / magnitude;
  current_compass_heading = (atan2(Mxy, Mxx)) * 180 / PI;
  if (current_compass_heading < 0) {
    current_compass_heading = 360 + current_compass_heading;
  }


static void transformation_mag(float uncalibrated_values[3]) {

  float calibrated_mag_values[3];
  float matrix[3][3] = {{1.207, 0.01, 0.171}, {0.053, 1.152, 0.031}, {-0.014, -0.003, 1.264}};
  float bias[3] = {-62.569, 88.671, 85.599};
  for (int i = 0; i < 3; ++i)
    uncalibrated_values[i] = uncalibrated_values[i] - bias[i];
  float result[3] = {0, 0, 0};
  for (int i = 0; i < 3; ++i)
    for (int j = 0; j < 3; ++j)
      result[i] += matrix[i][j] * uncalibrated_values[j];
  for (int i = 0; i < 3; ++i)
    calibrated_mag_values[i] = result[i];

  magnetometer_processed.x = calibrated_mag_values[0];
  magnetometer_processed.y = calibrated_mag_values[1];
  magnetometer_processed.z = calibrated_mag_values[2];
}

static void get_raw_compass_data(void) {
  uint8_t magnetometer_data[6] = {0U};
  uint8_t items_to_read = 6;
  float mag[3];
  i2c__read_slave_data(current_i2c, magnetometer_read, compass_magnetomer_OUT_X_H_M, magnetometer_data, items_to_read);

  mag[0] = (int16_t)(magnetometer_data[1] | (int16_t)(magnetometer_data[0] << 8));
  mag[2] = (int16_t)(magnetometer_data[3] | (int16_t)(magnetometer_data[2] << 8));
  mag[1] = (int16_t)(magnetometer_data[5] | (int16_t)(magnetometer_data[4] << 8));
  // printf("mag 0 %f, mag 1 %f, mag 2 %f\n", mag[0], mag[1], mag[2]);
  transformation_mag(mag);
}

Waypoint Algorithm

In real world, the car just cannot move anywhere over the roads and need to maintain a particular path and find the best possible and smallest route to its final destination. Hence, we have used waypoint algorithm in our implementation. These waypoints are known coordinates on a given location and our algorithm rather than just going in any one direction, follow the path through these waypoints. These waypoints are kind of checkpoints between the origin and the final destination and our car will follow these points to reach to the final location. How does it work? Here we have chosen 12 points over a known location as shown in the image below. These waypoints form a grid of points (all black and orange dots). Once our car gets the final coordinates, our waypoint algorithm runs on the GEO NODE and gives the DRIVER NODE directions for best waypoint. This waypoint must satisfy the below conditions:

  • It is the closest waypoint to the current location of the car.
  • Waypoint to destination distance is less than the current coordinate to the final coordinate distance.

So when the algorithm finds the destination coordinates, then bearing is calculated with that particular coordinate, so that car can be further instructions to move towards that particular point. As shown in the figure below, our algorithm keeps finding new coordinates with time (orange ones) and at last go to the final destination coordinates. The algorithm is mentioned below in the form of flowchart and code:

Waypoint.jpg
Waypointflowchart (1).jpg
Waypoint algorithm code
gps_coordinates_t find_next_point(gps_coordinates_t origin, gps_coordinates_t destination) {
  const float origin_to_destination_distance = calculate_distance_rc_car_to_destination_in_meters(origin, destination);
  float waypoint_distance_to_destination = 0;
  float origin_distance_to_waypoint = 0;
  float closest_way_point_ditsance = 1E+37;
  uint8_t waypoint_array_location = 0;

  for (uint8_t i = 0; i < max_points; i++) {
    origin_distance_to_waypoint =
        calculate_distance_rc_car_to_destination_in_meters(origin, locations_we_can_travel[i]);
    waypoint_distance_to_destination =
        calculate_distance_rc_car_to_destination_in_meters(locations_we_can_travel[i], destination);
    if (origin_distance_to_waypoint < 0.005)
      continue;
    if ((origin_distance_to_waypoint <= closest_way_point_distance) &&
        (waypoint_distance_to_destination < origin_to_destination_distance)) {
      waypoint_array_location = i;
      closest_way_point_distance = origin_distance_to_waypoint;
    } else {
      // do nothing
    }
  }
  if (origin_to_destination_distance <= closest_way_point_disance) {
    set_bearing_for_waypoint(origin, destination);
    return destination;
  } else {
    set_bearing_for_waypoint(origin, locations_we_can_travel[waypoint_array_location]);
    return locations_we_can_travel[waypoint_array_location];
  }
}

Technical Challenges

< List of problems and their detailed resolutions>



Master Module

The master module or the DRIVER NODE is the central node of our system which controls the navigation of the car. It takes he sensor values from the SENSOR NODE and geographical data from the GEO NODE and takes the navigation decision as per algorithm and sends the motor commands to the MOTOR NODE.

Hardware Design

The driver node is the main node responsible for moving the car to the destination as it receives appropriate messages from the bridge-sensor node and geo node and processes the signals before sending speed and steering values to the motor node. The LCD has also been interfaced to the DRIVER node and communicates with the controller via UART. The LCD displays messages, which helped us in debugging messages on the CAN bus while the car was moving.

DRIVER LCD 1.png
Table 5. Driver Node Pinout
SJTwo Board LCD/CAN Description
P4.28 (TX3) RX LCD
P4.29 (RX3) TX LCD
P0.1 CAN transceiver (Tx) CAN transmit
P0.0 CAN transceiver (Rx) CAN receive
Vcc 3.3V Vcc Vcc
GND GND Ground

Software Design

The Driver begins navigation only when there is some distance to be covered. Once a distance is set, our car has two algorithms to steer and control the speed of the car.

Object Detection Logic

This logic navigate the car according to the objects detected by the ultrasonic sensors mounted over the SENSOR NODE. As the object is detected, driver takes appropriate decisions and navigate the car without hitting the detected object. The algorithm is briefly described in the flow chart below.

ObjectDetection.jpg
Navigation according to object detection algorithm:
static void change_angle_of_car(bool is_obstactle_on_right) {
  if (is_obstactle_on_right == false) {
    motor_data.SERVO_STEER_ANGLE_sig = (motor_data.SERVO_STEER_ANGLE_sig >= -40)
                                           ? motor_data.SERVO_STEER_ANGLE_sig - offset_to_angle
                                           : -max_angle_threshold;
  } else {
    motor_data.SERVO_STEER_ANGLE_sig = (motor_data.SERVO_STEER_ANGLE_sig <= 40)
                                           ? motor_data.SERVO_STEER_ANGLE_sig + offset_to_angle
                                           : max_angle_threshold;
  }
  if (received_heading.DISTANCE <= 3) {
  motor_data.DC_MOTOR_DRIVE_SPEED_sig = 0;
  gpio__construct_as_output(0, 15);
  gpio__set(reverse_buzzer);
  }
  else {
  motor_data.DC_MOTOR_DRIVE_SPEED_sig = car_speed_if_obstacle;
  gpio__reset(reverse_buzzer);
  }
  gpio__construct_as_output(2, 0);
  gpio__construct_as_output(2, 2);
  gpio__construct_as_output(0, 15);
  gpio__reset(reverse_buzzer);
  gpio__set(reverse_light_1);
  gpio__set(reverse_light_2);
}

static void reverse_car_and_turn(void) {
  motor_data.SERVO_STEER_ANGLE_sig =
      (motor_data.SERVO_STEER_ANGLE_sig <= 35) ? motor_data.SERVO_STEER_ANGLE_sig + 10 : max_angle_threshold;
    motor_data.DC_MOTOR_DRIVE_SPEED_sig = reverse_speed;
  gpio__construct_as_output(2, 0);
  gpio__construct_as_output(2, 2);
  gpio__construct_as_output(0, 15);
  gpio__reset(reverse_buzzer);
  gpio__set(reverse_light_1);
  gpio__set(reverse_light_2);
}

dbc_MOTOR_SPEED_AND_ANGLE_MSG_s driver_motor_commands(void) {
  bool is_object_on_right = false;
  if (check_for_obstacle()) {
    if (us_sensor_data.SENSOR_left <= distance_from_obstacle && us_sensor_data.SENSOR__middle <= distance_from_obstacle &&
        us_sensor_data.SENSOR_right <= distance_from_obstacle) {
      reverse_car_and_turn();
    } else if ((us_sensor_data.SENSOR__left <= distance_from_obstacle &&
      us_sensor_data.SENSOR__middle <= distance_from_obstacle) ||
      us_sensor_data.SENSOR__left <= distance_from_obstacle) {
      is_object_on_right = false;
      change_angle_of_car(is_object_on_right);
    } else if ((us_sensor_data.SENSOR__right <= distance_from_obstacle &&
      us_sensor_data.SENSOR__middle <= distance_from_obstacle) ||
      us_sensor_data.SENSOR__right <= distance_from_obstacle) {
      is_object_on_right = true;
      change_angle_of_car(is_object_on_right);
    } else if (us_sensor_data.SENSOR__rear <= distance_from_obstacle_rear) {
      is_object_on_right = (us_sensor_data.SENSOR__right < us_sensor_data.SENSOR__left) ? true : false;
      change_angle_of_car(is_object_on_right);
      gpio__construct_as_output(0, 15);
      gpio__set(reverse_buzzer);
    } else if (us_sensor_data.SENSOR__middle <= distance_from_obstacle) {
      is_object_on_right = (us_sensor_data.SENSOR__right < us_sensor_data.SENSOR__left) ? true : false;
      change_angle_of_car(is_object_on_right);
    } else {
      printf("ERROR condition\n");
    }
  } else {
      follow_gps_direction();
  }
  set_lcd_motor_status(motor_data);
  return motor_data;
}

Follow Destination Logic

If there is no object detected, then driver takes decision to navigate towards the destination. Master module gets the compass heading, destination heading and the distance between the current location and the destination location, from the GEO NODE. Once the values are received, the DRIVER NODE take decision as per the flow chart below.

Driver gps (2).jpg


Navigation according to GPS location algorithm
static void motor_move_command(direction_to_move_t direction_to_turn, float total_turn_angle,
                               float distance_magnitude) {
  while (total_turn_angle > 45)
    total_turn_angle = total_turn_angle / 4;
  if (direction_to_turn == left) {
    if (total_turn_angle >= 40) {
      motor_data.SERVO_STEER_ANGLE_sig = 40;
    } else {
      motor_data.SERVO_STEER_ANGLE_sig = ((int)total_turn_angle / 5)*5;
    }
  } else {
    if (total_turn_angle >= 40) {
      motor_data.SERVO_STEER_ANGLE_sig = -40;
    } else if (total_turn_angle >= 35) {
      motor_data.SERVO_STEER_ANGLE_sig = ((int)(total_turn_angle / 5))*(-5);
    }
  }

  if (motor_data.SERVO_STEER_ANGLE_sig != 0) {
    gpio__construct_as_output(2, 0);
    gpio__construct_as_output(2, 2);
    gpio__set(reverse_light_1);
    gpio__set(reverse_light_2);
  } else {
    gpio__construct_as_output(2, 0);
    gpio__construct_as_output(2, 2);
    gpio__reset(reverse_light_1);
    gpio__reset(reverse_light_2);
  }
  if (distance_magnitude >= 40) {
    motor_data.DC_MOTOR_DRIVE_SPEED_sig = MAX_SPEED;
     gpio__construct_as_output(0, 15);
    gpio__reset(reverse_buzzer);
  } else if (distance_magnitude >= 10) {
    motor_data.DC_MOTOR_DRIVE_SPEED_sig = MED_SPEED;
     gpio__construct_as_output(0, 15);
    gpio__reset(reverse_buzzer);
  } else if (distance_magnitude > 3) {
    motor_data.DC_MOTOR_DRIVE_SPEED_sig = MIN_SPEED;
    gpio__construct_as_output(0, 15);
    gpio__set(reverse_buzzer);
  } else {
    motor_data.DC_MOTOR_DRIVE_SPEED_sig = 0;
    gpio__construct_as_output(0, 15);
    gpio__set(reverse_buzzer);
  }
}

LCD Module

The purpose to include an LCD display on the RC car was to be able to display signals over the can bus in order to monitor and debug the behavior of the RC car while it is in motion. The messages displayed on the LCD are mentioned below:

  • Ultrasonic sensor values (left, right, center and back)
  • Compass heading
  • Distance to the destination
  • Motor speed
  • Steering angle
Code to write to the 4 rows of LCD display using UART:
void lcd_row_write(uint8_t row, uint8_t *new_data) {
  uint8_t x, y;
  uint8_t data_string[CONST_LCD_WIDTH + 1];

  // Check row number
  if ((row < 1) || (row > CONST_LCD_NUM_ROWS)) {
    printf("Row %i is out of bounds", row);
    return 0;
  }
  // Check length of string
  y = strlen(new_data);
  if (y > CONST_LCD_WIDTH) {
    printf("Width %i is out of bounds", y);
    return 0;
  }
  // Initialize to empty
  memset(data_string, CONST_LCD_EMPTY, sizeof(data_string));
  // copy the new data
  memcpy(data_string, new_data, y);
  // Write to LCD
  x = 0;
  for (y = 0; y < CONST_LCD_WIDTH; y++) {
    x |= lcd_uart_write_reg(CONST_ADDR_LCD_DATA_START + y, data_string[y]);
  }
  // Issue update command
  x |= lcd_uart_write_reg(CONST_ADDR_LCD_ACTION, (row - 1) | CONST_LCD_ACTION_GO_BIT);
  // Wait for LCD update
  delay__ms(2);
  // return x;
}

Technical Challenges

Mobile Application

https://gitlab.com/naveena.sura/silver-arrow/-/merge_requests/8

App bt list.jpg New Maps screen satellitle.jpeg Car data page.jpg

Hardware Design

Google Pixel 6 mobile was used to run the application and HC-05 Bluetooth Module was used to establish communication between the application and Bridge Controller

Software Design

Androidappflow.jpg

The software's main front-end code was written in Kotlin. We used Bluetooth communication for transferring and receiving data over the application. Before we open the application, we first need to pair the HC-05 Bluetooth module with the mobile. This is done by going to the mobile’s Bluetooth Setting and pairing the module. The application has 3 “activities”. Activity 1: Bluetooth Connection Screen When the app first opens the Bluetooth socket that is created is checked if it is NULL, which it should be with a start-up. If this occurs the Bluetooth connection screen will be brought up, and the user will need to tap a device to attempt a connection. If the connection fails, the screen will again be brought up. Once the connection is successfully established, we move on to the next “Activity 2: Maps Activity”

Activity 2: Maps Activity: The Google Map SDK is so large and has so many capabilities, we created an account on Google Cloud Console, enabled the API, and then utilized the API key in the Android App's Manifest file. In this activity, we have set up 3 “OnClickListners” viz. My Location, North Align, Send Destination Coordinates. If the user clicks on to “My Location” icon and if the Mobile’s location is “On”, then the map will transport itself to the current location of the mobile. The user can zoom in and out like any other GoogleMaps application and click anywhere on the map to drop a pin. If the user again taps onto the pin, then it will show the coordinates of the selected dropped pin. When the user clicks on “Send Destination Coordinates” the coordinates of the dropped pin are sent over Bluetooth to the Bridge Controller, and the app goes to the final activity “Activity 3: CarDataDisplaActivity”. Advice for future students: Enable "Satellite View" in your Maps Activity so that it easier for you to add your destination location.

Activity 3: CarDataDisplayActivity: This is a very simple activity. All this activity does that it shows the various data that are sent over the CAN bus on the car and displays it live on the app. The application receives the data over the HC-05 Bluetooth module in a long comma (,) separated string format and then segregates the data into the respective fields. Few of the data that are displayed on the application include all the sensor values, current car speed, distance remaining towards the destination, etc. Lastly, this activity has two buttons to start and stop the car.

Technical Challenges

One of the major technical challenges was getting acquainted with the Android Studio Environment. One mistake that we made was that instead of implementing with a relatively well-known JavaScript language, we used Kotlin language which was difficult to get the hang of. Luckily, the android developer’s website provides a lot of useful information for newbie application developers for getting started with the basic building blocks.

Secondly, with security and privacy being an essential paradigm for any app development, setting up and granting permissions for the app to use location and Bluetooth was tricky. Googling stuff and diving deep into the developer’s website helps us overcome these issues. When using Google Maps, you’ll need to create an account and use a key to connect to the app. In Android Studio, that key is located in the "googlemapsapi" file and is ignored by default in git.

In order to display different types of car data, such as sensor data, car speed, current, and destination heading, the bridge controller is appending all the necessary data, that it received over the CAN bus, and sends it in a string format and comma (,) separated. This was decoded in the form of an array of range values and then displayed on the mobile app screen. The range for each value and the message format and structure early was pre-decided. For e.g., sensor values will only range from 0 – 99, whereas the distance from the destination will range from 0-999. If you decide to go by our method, we highly suggest that you carefully decide on the message format and structure. Our implementation is as follows:

Decoding the String message of car data to display on the app

 val startOfHashIndex: Int = recDataString.indexOf("#")
 if (startOfHashIndex != -1) {
   recDataString = recDataString.substring(startOfHashIndex, recDataString.length)
   val endOfLineIndex: Int =  recDataString.indexOf("~") // determine the end-of-line    
   if (endOfLineIndex > 0) 
     {     // make sure there data before ~
           var dataInPrint: String =  recDataString.substring(0, endOfLineIndex) // extract string
           txtString!!.setText("Data Received = $dataInPrint")
           val dataLength = dataInPrint.length //get length of data received
           txtStringLength!!.setText("String Length = $dataLength")
           if (recDataString[0] === '#') //if it starts with # we know it is what we are looking for
             {
               val sensor0: String = recDataString.substring(1,3) //get sensor value from string between indices 1-5
               val sensor1: String = recDataString.substring(5, 7) //same again...
               val sensor2: String = recDataString.substring(9, 11)
               val sensor3: String = recDataString.substring(13, 15)
               val distance_remain: String = recDataString.substring(17, 20)
               val compass_direction: String = recDataString.substring(22, 25)
               val compass_direction2: String = recDataString.substring(27, 30)
               car_speed!!.setText("Current car Speed: " + sensor0 + " km/hr") //update the textviews with sensor values
               sensorView1!!.setText("Right Sensor: " + sensor1 + " inches")
               sensorView2!!.setText("Left Sensor: " + sensor2 + " inches")
               sensorView3!!.setText("Middle Sensor: " + sensor3 + " inches")
               rem_dist!!.setText("Distance from Destination: " + distance_remain + " meters")
               heading_direction!!.setText("Current Heading: " + compass_direction + " '")
               heading_direction2!!.setText("Destination Heading: " + compass_direction2 + " '")
             }
      }                   
                           





Unit Testing

Unit testing has been performed for all the nodes. We used “CMock” to test the code coverage and proper execution and flow of our program. Each line of code we design is testable, and the most efficient way to test it is through unit-tests. A unit test is a method of testing a unit, which is the smallest amount of code in a system that can be logically separated. That is a function, a subroutine, a method, or a property in most programming languages. CMock is a framework for generating mocks based on a header API. All you have to do to use CMock is add a mock header file to the test suite file. The figure below illustrates the main functions tested for all the four nodes.

Unit testing report.png

Conclusion

The RC car project was a great success! We were able to meet all of the conditions that were placed on us. The car could travel itself from one waypoint to the next while keeping a constant speed and avoiding obstructions. This project encompasses a wide range of disciplines, including mechanical engineering, embedded engineering, android development, and project management, all of which we had no prior expertise with. We gained a better grasp of embedded systems and strong software design principles, such as test-driven development, while also acquiring new information while working on this project. Interfacing with ultrasonic sensors and performing signal processing on the data, interacting with a GPS module, determining heading from a magnetometer and incorporating tilt compensation using an accelerometer, writing firmware for a Bluetooth module and communicating with an Android application that we created, interacting with electric motors using PWM and creating a PID to control speed, and not to mention our deep dive into the CAN protocol and not only learning the intricacies of the CAN protocol and not only learning the intricacies Many of these jobs necessitated extensive research to gain a thorough understanding of subjects before we could begin. With each iteration, we learned that it is important for a project team to set certain achievable milestones. Conquering these milestones will help one to get a better idea of where one stands with respect to the final deadline date. A few takeaways from this project include:

  • How to write good software that is testable.
  • Unit-testing saves a lot of time.
  • To avoid constant overwriting, use Git to manage various versions of code and merge features to target branches, rather than uploading individual files.
  • Project Management and working together in a group. Coming to a consensus when the views and opinions of group members don't match.
  • How easy it is to build a basic mobile application

Project Video

Silver Arrow Youtube link

Project Source Code

Silver Arrow GitLab

Advice for Future Students

  • Start Early. Go through previous years' wiki pages and set achievable milestones. Starting early provides your team with a number of advantages over other teams. You'll run into problems sooner, which means you'll have more time to fix them. Also, if your team gets started early, you may always check to see if the modules they purchased are malfunctioning.
  • Invest in good parts and always buy spare parts. "Good Parts" may not necessarily be "expensive". Wen went through different project teams and used their advice on what parts to order and not. Don't underestimate the amount of mechanical design and work that goes into getting things up and running.
  • Figure out the power circuit as soon as possible. Even though it is possible to power the different modules and their subsequent peripherals from the car's ESC, it is advised NOT to do that. Be extra careful while making connections as this project has a lot. Use color coding for wire and avoid "spaghetti wire". You'll need enough current to power all of the controllers and components.
  • Integrating LCD on the car chassis helps a lot as it is better to decode the car (even when it's moving) than connecting the DB9 connector and looking over the BUSMASTER.

Acknowledgement

References