Difference between revisions of "Self-driving Car"

From Embedded Systems Learning Academy
Jump to: navigation, search
(Sample Code)
Line 53: Line 53:
 
|  
 
|  
 
* Prototype project demonstration
 
* Prototype project demonstration
* [http://www.coderemarks.com Code Review]
 
 
|-
 
|-
 
| November (Week7)
 
| November (Week7)
Line 81: Line 80:
  
 
== Controllers ==
 
== Controllers ==
Given below are the controllers, their duties, and the number of people involved.  It is quite possible that one team gets done with their part, but that doesn't mean your job is done.  If you are done, help others.  If you are done, and the primary objective is met (the car can self-drive), then add more features.  There are many things you can do, and the 16-week semester definitely won't provide an opportunity to sit and relax.  '''Get up and learn!'''.
+
Given below are the controllers, their duties, and the number of people involved.  It is possible that one team gets done with their part, but that doesn't mean your job is done.  If you are done, help others.  If you are done, and the primary objective is met (the car can self-drive), then add more features.  There are many things you can do, and the 16-week semester definitely won't provide an opportunity to sit and relax.  '''Get up and learn!'''.
  
 
{| class="wikitable"
 
{| class="wikitable"
Line 130: Line 129:
 
*  This unit needs to compute the "heading degree" to reach the destination
 
*  This unit needs to compute the "heading degree" to reach the destination
 
|-
 
|-
| '''Master Controller'''
+
| '''Central Controller'''
 
'''(3 members)'''
 
'''(3 members)'''
 
|
 
|
Line 180: Line 179:
 
| <code>0x300 - 0x3FF</code>
 
| <code>0x300 - 0x3FF</code>
 
|-
 
|-
| Add subscription message(s)
+
| Reserved
 
| <code>0x400 - 0x4FF</code>
 
| <code>0x400 - 0x4FF</code>
 
|-
 
|-
| Subscribed messages
+
| Data messages
 
| <code>0x500 - 0x5FF</code>
 
| <code>0x500 - 0x5FF</code>
 
|}
 
|}
Line 215: Line 214:
 
  byte [0-3] : Current time
 
  byte [0-3] : Current time
 
  byte [4]  : CPU usage %
 
  byte [4]  : CPU usage %
|}
 
 
{| class="wikitable"
 
|+ Subscription Rate Info
 
|-
 
| Byte 0
 
| Effect
 
|-
 
| 0
 
| Off
 
|-
 
| 1
 
| 1Hz
 
|-
 
| 5
 
| 5Hz
 
|-
 
| 10
 
| 10Hz
 
|-
 
| 20
 
| 20Hz
 
|-
 
| 50
 
| 50Hz
 
|-
 
| Any other value
 
| Invalid date rate
 
 
|}
 
|}
  
Line 258: Line 229:
 
  byte [4-7] : (float) Latitude
 
  byte [4-7] : (float) Latitude
 
|-
 
|-
| 0x401
 
| Subscribe to GPS data
 
| See '''Subscription Rate''' above
 
|-
 
| 0x402
 
| Subscribe to compass data
 
| See '''Subscription Rate''' above
 
|-
 
 
| 0x501
 
| 0x501
| Subscribed data of 0x401
+
| GPS Data Message
 
|
 
|
 
  byte [0-3] : (float) Longitude
 
  byte [0-3] : (float) Longitude
Line 273: Line 236:
 
|-  
 
|-  
 
| 0x502
 
| 0x502
| Subscribed data of 0x402
+
| Compass Data Message
 
|
 
|
 
  byte [0-1] : (uint16) Current compass degree
 
  byte [0-1] : (uint16) Current compass degree
Line 285: Line 248:
 
| Purpose
 
| Purpose
 
| Data layout
 
| Data layout
|-
 
| 0x401
 
| Subscribe to distance sensor data
 
| See '''Subscription Rate''' above
 
 
|-  
 
|-  
 
| 0x501
 
| 0x501
| Subscribed data of 0x401
+
| Sensor Data Message
 
|
 
|
 
  byte [0] : Front sensor value in inches
 
  byte [0] : Front sensor value in inches
Line 298: Line 257:
 
  etc.
 
  etc.
 
|}
 
|}
 +
 +
== How will communication work? ==
 +
After startup, begin to send your data messages at the desired periodic rates using the periodic API as listed below in the example.  Whichever controller wants to listen to your periodic message shall intercept your message and use it for its needs.
  
 
== Features ==
 
== Features ==
The first feature to develop is the self-drive capability and everything else comes later.  While some people in the team may be focusing on delivering this primary feature, other members can focus on other things such as automatic headlights, variable speed settings through Android interface etc.
+
'''The first feature to develop is the self-drive capability and everything else comes later'''.  While some people in the team may be focusing on delivering this primary feature, other members can focus on other things such as automatic headlights, variable speed settings through Android interface etc. '''If your product team fails, then just like it would happen in the industry, you will get laid off, and I will see you again in the course ;('''
  
 
=== Quick and Easy Features ===
 
=== Quick and Easy Features ===
Line 312: Line 274:
 
*  Each controller shall use the 2-digit LED display to display meaningful info
 
*  Each controller shall use the 2-digit LED display to display meaningful info
 
*:  Maybe Geo Controller can display # of feet to destination
 
*:  Maybe Geo Controller can display # of feet to destination
*:  Master controller can display number of CAN messages received per second.
+
*:  Central controller can display number of CAN messages received per second.
 
*  Each controller shall use the 4-LED lights for some indication
 
*  Each controller shall use the 4-LED lights for some indication
 
*:  LED0 should be lit if an error happens (common to everyone)
 
*:  LED0 should be lit if an error happens (common to everyone)
Line 318: Line 280:
  
 
=== Robustness ===
 
=== Robustness ===
Your project is one project as a whole.  So if it doesn't work, do not blame it on "hey, their controller crashed".  If a controller crashes it will restart, and the subscribed messages will vanish.  If subscribed messages vanish, other controllers should re-subscribe.  So your code should be robust, and self-recover from any crashed event or any brief power disruptions.
+
Your project is one project as a whole.  So if it doesn't work, do not blame it on "hey, their controller crashed".  If a controller crashes it will restart, and you will have live with missing data messages.  So your code should be robust, and self-recover from any crashed event or any brief power disruptions.
  
Likewise, if you send a message, and it fails (in case the other controller is down), your CAN bus may go to abnormal state and turn off. In this condition, all of your messages will fail, and the entire communication needs to be re-done including all the subscriptions.  I recommend the following:
+
Likewise, if you send a message, and it fails (in case the other controller is down), your CAN bus may go to abnormal state and turn off. In this condition, all of your messages will fail, and you will have to handle this.  I recommend the following:
 
*  Attach a BUS off callback function that gives "can_bus_crashed" semaphore.
 
*  Attach a BUS off callback function that gives "can_bus_crashed" semaphore.
*  If the semaphore is ever given, reset your CAN bus, and re-subscribe to the desired messages.
+
*  If the semaphore is ever given, reset your CAN bus after a 3 second timeout.
  
 
=== Startup Tests ===
 
=== Startup Tests ===
Since we rely on multiple controllers, it is critical to be able to test each controller '''quickly, reliably, and easily'''.  So here is a startup test the master controller must process upon each boot:
+
Since we rely on multiple controllers, it is critical to be able to test each controller '''quickly, reliably, and easily'''.  So here is a startup test the Central  Controller must process upon each boot:
Master controller sends a message requesting boot information from each controller
+
Central controller sends a message requesting boot information from each controller
Master controller checks responses after about 100 ms:
+
Central controller checks responses after about 100 ms:
 
*:  Each controller must have responded
 
*:  Each controller must have responded
 
*:  Each controller's boot code (normal, abnormal) should be validated.
 
*:  Each controller's boot code (normal, abnormal) should be validated.
Line 336: Line 298:
 
*  Can you remotely shut down the car in 3 seconds?
 
*  Can you remotely shut down the car in 3 seconds?
 
*  Where is the kill switch?
 
*  Where is the kill switch?
*  If controller A sends subscription message twice, are you sure you won't double subscribe?
 
 
*  If your controller goes down, will it fully recover to "last known configuration"?
 
*  If your controller goes down, will it fully recover to "last known configuration"?
*  If you stop receiving subscribed messages, what will you do?
 
 
*  If critical sensor data stops coming, how will you stop the car?
 
*  If critical sensor data stops coming, how will you stop the car?
 
*  How can you quickly discover one or more controllers reaching an error state?
 
*  How can you quickly discover one or more controllers reaching an error state?
Line 349: Line 309:
  
 
== Sample Code ==
 
== Sample Code ==
 
 
<BR/>
 
<BR/>
 
=== Part 1: Basic structure and CAN initialization ===
 
=== Part 1: Basic structure and CAN initialization ===
Line 356: Line 315:
 
#ifndef CAN_MSG_ID_H_
 
#ifndef CAN_MSG_ID_H_
 
#define CAN_MSG_ID_H_
 
#define CAN_MSG_ID_H_
 
  
  
Line 362: Line 320:
 
  * Have an enumeration of controller IDs
 
  * Have an enumeration of controller IDs
 
  */
 
  */
typedef enum {
+
typedef enum :uint8_t {
 
     cid_geographical_controller = 1,
 
     cid_geographical_controller = 1,
     cid_master_controller = 2,
+
     cid_central_controller = 2,
 +
    cid_broadcast = 0xff
 
} cid_t;
 
} cid_t;
  
 
/// TODO Each controller shall set its own ID
 
/// TODO Each controller shall set its own ID
#define OUR_CONTROLLER_ID      (cid_master_controller)
+
#define OUR_CONTROLLER_ID      (cid_central_controller)
  
 
/**
 
/**
Line 414: Line 373:
 
     /* Initialize CAN Bus and set the acceptance filter(s) */
 
     /* Initialize CAN Bus and set the acceptance filter(s) */
 
     CAN_init(can1, 100, 10, 10, NULL, NULL);
 
     CAN_init(can1, 100, 10, 10, NULL, NULL);
 +
    /* TODO Initialize acceptance filters */
 
     CAN_reset_bus(can1);
 
     CAN_reset_bus(can1);
  
     /* This creates all of the subscription tasks */
+
     /* This creates all of the periodic message tasks */
 
     can_msg_task_init();
 
     can_msg_task_init();
  
Line 427: Line 387:
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 
 
<BR/>
 
<BR/>
  
Line 442: Line 401:
 
             /* TODO This is psuedocode, so add your real logic here */
 
             /* TODO This is psuedocode, so add your real logic here */
 
             if (!dst_is_not_us) {
 
             if (!dst_is_not_us) {
                 LOG_ERROR("HW CAN Filter must be incorrect!");
+
                 LOG_ERROR("CAN acceptance filter must be incorrect (0x%08X)!", msg.msg_id);
 
             }
 
             }
 
             else if (command_message) {  /* 0x100 - 0x1FF */
 
             else if (command_message) {  /* 0x100 - 0x1FF */
Line 450: Line 409:
 
                 handle_our_cmd(msg);
 
                 handle_our_cmd(msg);
 
             }
 
             }
             else if (new_subscribe) {   /* 0x400 - 0x4FF */
+
             else if (data_message) {     /* 0x500 - 0x5FF */
                subscription_add(msg);
+
                 handle_data_msg(msg);
            }
 
            else if (subscribed_msg) {  /* 0x500 - 0x5FF */
 
                 handle_subscribed_msg(msg);
 
 
             }
 
             }
 
             else if (cmd_response_msg) { /* 0x200 - 0x2FF */
 
             else if (cmd_response_msg) { /* 0x200 - 0x2FF */
Line 468: Line 424:
  
 
<BR/>
 
<BR/>
=== Part 3: Infrastructure code for CAN message subscriptions ===
+
=== Part 3: Infrastructure code for sending CAN messages periodically ===
  
 
==== can_msg_task.hpp ====
 
==== can_msg_task.hpp ====
 
<syntaxhighlight lang="cpp">
 
<syntaxhighlight lang="cpp">
#ifndef SUBSCRIPTION_TASK_HPP_
+
#ifndef CAN_TASK_HPP_
#define SUBSCRIPTION_TASK_HPP_
+
#define CAN_TASK_HPP_
  
 
#include <stdint.h>
 
#include <stdint.h>
Line 481: Line 437:
  
 
/**
 
/**
  * The task that sends out the subscription messages of a list at the defined frequency.
+
  * The task that sends out the periodic messages of a list at the defined frequency.
 
  */
 
  */
 
class canMsgTask : public scheduler_task
 
class canMsgTask : public scheduler_task
Line 498: Line 454:
 
         bool run(void *p);
 
         bool run(void *p);
  
         /// Add a subscription message to be sent by this task
+
         /// Add a periodic message to be sent by this task
 
         /// You probably cannot access it directly, so use can_msg_task_mgr.hpp's API
 
         /// You probably cannot access it directly, so use can_msg_task_mgr.hpp's API
         bool addSubscribedMsg(uint8_t dst,          ///< Destination address
+
         bool addPeriodicMsg(uint8_t dst,          ///< Destination address
                              uint16_t msgNum,      ///< Message number
+
                            uint16_t msgNum,      ///< Message number
                              can_msg_t *pCanMsg    ///< Actual CAN message pointer
+
                            can_msg_t *pCanMsg    ///< Actual CAN message pointer
                            );
+
                          );
  
 
     private:
 
     private:
 
         /**
 
         /**
         * Structure of a subscribed message
+
         * Structure of a periodic message
 
         */
 
         */
 
         typedef struct {
 
         typedef struct {
Line 513: Line 469:
 
             uint16_t msgNum;        ///< The message number for the node
 
             uint16_t msgNum;        ///< The message number for the node
 
             can_msg_t *canMsgPtr;  ///< Pointer to the actual CAN message pointer
 
             can_msg_t *canMsgPtr;  ///< Pointer to the actual CAN message pointer
         } subscribedMsg_t;
+
         } periodicMsg_t;
  
 
         canMsgTask();              ///< Private default constructor, do not use.
 
         canMsgTask();              ///< Private default constructor, do not use.
         const float mTaskRateHz;             ///< The run duration of the task
+
         const float mTaskRateHz;               ///< The run duration of the task
         const can_t mCanBusNum;               ///< The CAN Bus number
+
         const can_t mCanBusNum;               ///< The CAN Bus number
         VECTOR<subscribedMsg_t> mSubsMsgList; ///< The message subscription list
+
         VECTOR<periodicMsg_t> mPerdiocMsgList; ///< The periodic message list
 
};
 
};
#endif /* SUBSCRIPTION_TASK_HPP_ */
+
#endif /* CAN_TASK_HPP_ */
 
</syntaxhighlight><BR/>
 
</syntaxhighlight><BR/>
  
Line 533: Line 489:
 
     mTaskRateHz(rateHz),        ///< Task rate in Hz
 
     mTaskRateHz(rateHz),        ///< Task rate in Hz
 
     mCanBusNum(canBusNum),      ///< CAN Bus to use
 
     mCanBusNum(canBusNum),      ///< CAN Bus to use
     mSubsMsgList(capacity)     ///< Construct the list
+
     mPerdiocMsgList(capacity)   ///< Construct the list
 
{
 
{
 
     /* Nothing to do */
 
     /* Nothing to do */
Line 559: Line 515:
 
     const uint32_t timeoutMs = 50; /* Some reasonable time */
 
     const uint32_t timeoutMs = 50; /* Some reasonable time */
  
     for (unsigned int i = 0; i < mSubsMsgList.size(); i++)
+
     for (unsigned int i = 0; i < mPeriodicMsgList.size(); i++)
 
     {
 
     {
 
         /* Get the item pointer from the list */
 
         /* Get the item pointer from the list */
         const subscribedMsg_t &subsMsg = mSubsMsgList[i];
+
         const periodicMsg_t &periodicMsg = mPeriodicMsgList[i];
  
 
         /* Copy the CAN message from the CAN message pointer, this includes the
 
         /* Copy the CAN message from the CAN message pointer, this includes the
         * data length field which should've been set by the caller of addSubscribedMsg()
+
         * data length field which should've been set by the caller of addPeriodicMsg()
 
         */
 
         */
         msg = *(subsMsg.canMsgPtr);
+
         msg = *(periodicMsg.canMsgPtr);
  
 
         /* Form the message ID that we need to use */
 
         /* Form the message ID that we need to use */
         msg.msg_id = make_id(subsMsg.dstAddr, subsMsg.msgNum);
+
         msg.msg_id = make_id(periodicMsg.dstAddr, periodicMsg.msgNum);
  
 
         /* We must be able to at least queue the message without a timeout otherwise
 
         /* We must be able to at least queue the message without a timeout otherwise
Line 584: Line 540:
 
}
 
}
  
bool canMsgTask::addSubscribedMsg(uint8_t dst, uint16_t msgNum, can_msg_t *pCanMsg)
+
bool canMsgTask::addPeriodicMsg(uint8_t dst, uint16_t msgNum, can_msg_t *pCanMsg)
 
{
 
{
 
     bool ok = false;
 
     bool ok = false;
  
     /* Populate the fields of the subscribed message */
+
     /* Populate the fields of the periodic message */
     subscribedMsg_t subsMsg;
+
     periodicMsg_t periodicMsg;
     subsMsg.dstAddr = dst;
+
     periodicMsg.dstAddr = dst;
     subsMsg.msgNum = msgNum;
+
     periodicMsg.msgNum = msgNum;
     subsMsg.canMsgPtr = pCanMsg;
+
     periodicMsg.canMsgPtr = pCanMsg;
  
 
     if (! (ok = (NULL != pCanMsg))) {
 
     if (! (ok = (NULL != pCanMsg))) {
Line 598: Line 554:
 
     }
 
     }
 
     else {
 
     else {
         /* Add the subscription message to our list or if no capacity, log an error*/
+
         /* Add the periodic message to our list or if no capacity, log an error*/
         if ((ok = (mSubsMsgList.size() < mSubsMsgList.capacity()))) {
+
         if ((ok = (mPeriodicMsgList.size() < mPeriodicMsgList.capacity()))) {
             /* TODO Double check if this subsription doesn't already exist */
+
             /* TODO Double check if this message doesn't already exist */
             mSubsMsgList.push_back(subsMsg);
+
             mPeriodicMsgList.push_back(periodicMsg);
 
         }
 
         }
 
         else {
 
         else {
             LOG_ERROR("List capacity for %uHz task has exceeded maximum subscriptions of %u",
+
             LOG_ERROR("List capacity for %uHz task has exceeded maximum periodic messages of %u",
                       mTaskRateHz, mSubsMsgList.size());
+
                       mTaskRateHz, mPeriodicMsgList.size());
 
         }
 
         }
 
     }
 
     }
Line 624: Line 580:
 
typedef enum {
 
typedef enum {
 
     msgRate1Hz  = 0,
 
     msgRate1Hz  = 0,
    msgRate5Hz,
 
 
     msgRate10Hz,
 
     msgRate10Hz,
    msgRate20Hz,
 
 
     msgRate50Hz,
 
     msgRate50Hz,
  
Line 633: Line 587:
 
} msgRate_t;
 
} msgRate_t;
  
/// Initializes all of the CAN subscription message tasks
+
/// Initializes all of the CAN periodic message tasks
 
void can_msg_task_init(void);
 
void can_msg_task_init(void);
  
 
/**
 
/**
  * Adds a message subscription to be sent at the given rate.
+
  * Adds a message periodic to be sent at the given rate.
 
  * @param [in] rate    The rate of the message
 
  * @param [in] rate    The rate of the message
 
  * @param [in] dst    The node address of the destination
 
  * @param [in] dst    The node address of the destination
Line 643: Line 597:
 
  * @param [in] pCanMsg The pointer to the CAN message
 
  * @param [in] pCanMsg The pointer to the CAN message
 
  *
 
  *
  * @returns true if the subscription was added successfully
+
  * @returns true if the periodic was added successfully
 
  */
 
  */
bool add_new_subscription(msgRate_t rate, uint8_t dst, uint16_t msgNum, can_msg_t *pCanMsg);
+
bool add_new_periodic_msg(msgRate_t rate, uint8_t dst, uint16_t msgNum, can_msg_t *pCanMsg);
  
 
#endif /* CAN_MSG_TASK_MANAGER_HPP_ */
 
#endif /* CAN_MSG_TASK_MANAGER_HPP_ */
Line 663: Line 617:
 
     const uint32_t listCap = (5 * 10); /// 5 controllers, and 10 msgs per controller max
 
     const uint32_t listCap = (5 * 10); /// 5 controllers, and 10 msgs per controller max
  
     /* Create all of the tasks and add them to the scheduler
+
     /* Create all of the tasks and add them to the scheduler */
    * TODO Add more of your tasks here
 
    */
 
 
     scheduler_add_task((g_MsgTaskPtrs[msgRate1Hz]  = new canMsgTask(canbus,  1, listCap, 1)));
 
     scheduler_add_task((g_MsgTaskPtrs[msgRate1Hz]  = new canMsgTask(canbus,  1, listCap, 1)));
 
     scheduler_add_task((g_MsgTaskPtrs[msgRate10Hz] = new canMsgTask(canbus,  10, listCap, 2)));
 
     scheduler_add_task((g_MsgTaskPtrs[msgRate10Hz] = new canMsgTask(canbus,  10, listCap, 2)));
 +
    scheduler_add_task((g_MsgTaskPtrs[msgRate50Hz] = new canMsgTask(canbus,  50, listCap, 3)));
 
}
 
}
  
bool add_new_subscription(msgRate_t rate, uint8_t dst, uint16_t msgNum, can_msg_t *pCanMsg)
+
bool add_new_periodic_msg(msgRate_t rate, uint8_t dst, uint16_t msgNum, can_msg_t *pCanMsg)
 
{
 
{
 
     bool ok = false;
 
     bool ok = false;
  
 
     if (NULL != g_MsgTaskPtrs[rate]) {
 
     if (NULL != g_MsgTaskPtrs[rate]) {
         ok = (g_MsgTaskPtrs[rate])->addSubscribedMsg(dst, msgNum, pCanMsg);
+
         ok = (g_MsgTaskPtrs[rate])->addPeriodicMsg(dst, msgNum, pCanMsg);
 
     }
 
     }
  

Revision as of 01:06, 23 October 2014

This project is about a large team getting a car to self-drive to a selected destination. This involves working with an RTOS running on a low power processor and various different processor boards working together over a CAN bus.

   
Self-Drive Car Block Diagram

Gitlab

Create a "master" Gitlab project that contains sub-folders of each project. Please provide me the access (username: preet) to your master project so I can peek at all of your source code when needed. The Gitlab will track your commit history so I would also know how much work each person or team is contributing.

The folder structure should be:

  • TeamX_CmpE_Fall2014
    Sensor
    IO
    <other controller projects>

Schedule

Proposed Schedule
Week Milestone
October (Week1)
  • Create Wikipedia Project
  • Team Collaborations
  • Order large RC car (1:5 or 1:4)
October (Week2)
  • Assemble the RC car
  • Finish CAN Bus Wiring
October (Week3)
  • Verify CAN communication
  • Implement Startup Tests
October (Week4)
  • Deliver basic functionality (tested)
  • Log critical/debug data
  • Basic Android interface
October (Week5)
  • Project Integration
  • Implement release control (with change-log)
November (Week6)
  • Prototype project demonstration
November (Week7)
  • Implement code review feedback
November (Week8)
  • Finalize project features
November (Week9)
  • Testing
December(Week10)
  • More testing and trial runs
  • Optimize & Tweak
December(Week11) Project Demonstration

Parts

Controllers

Given below are the controllers, their duties, and the number of people involved. It is possible that one team gets done with their part, but that doesn't mean your job is done. If you are done, help others. If you are done, and the primary objective is met (the car can self-drive), then add more features. There are many things you can do, and the 16-week semester definitely won't provide an opportunity to sit and relax. Get up and learn!.

Controllers
Sensor Controller

(2 members)

  • Interfaced to front and rear vision.
    Consider Sonar, and/or IR sensors with long distance vision
  • Sensors must be "filtered" and must provide reliable "vision"
  • Provide additional sensor inputs:
    Provide battery voltage, and % charge remaining
    Light sensor reading
    Tilt (angle of the car)
Motor Controller

(2 members)

  • Interfaced to motor control system of the car
    Provide a means to steer, and drive the car
  • Provide feedback of the speed using a wheel encoder or speed sensor
I/O Unit

(2 members)

  • Provide an LCD screen to report car status
    Errors and communication status
    Sensor values
  • Buttons to start and stop the car
  • Button hard-coded to set a specific destination
  • Provide means to turn on/off the headlights (etc).
Communication Bridge + Android

(3 members)

  • Provide means to communicate and display status on an Android/iPhone device
  • Allow a user to see sensor values, car speed (etc)
  • Allow a user to select a destination from Google Earth
Geographical Controller

(3 members)

  • Interface to a 5Hz or faster GPS
  • Interface to a compass
  • Allow a GPS coordinate to be "set"
    Based on the set coordinate, calculate, and provide CAN data regarding
    the current heading, and the desired heading to reach the destination
  • This unit needs to compute the "heading degree" to reach the destination
Central Controller

(3 members)

  • This is the primary unit that communicates with every controller to drive the car
  • This unit shall also turn on headlights (etc), and be the "brain" of the car
  • Upon a detection of a "Start" condition, work with different controllers to drive the car
    Motor Controller and Geographical Controller to drive the car to destination
    Avoid obstacles on the way to the destination
    Add features once you finish the primary goal

Communication

Each controller shall provide a means to communicate with the other controllers. Before you read any further, it requires that you have deep knowledge of the CAN bus. CAN is a BROADCAST communication bus, but in our software we can add addressing such that we can have 1:1 communication (rather than 1:many). Each controller shall pick a controller number. The controller with the highest priority shall pick the lowest ID. Using this protocol, each controller can specifically send a message to any other controller, and likewise, upon a received message, we can tell who it came from.

Recommended CAN Message ID format

We split the 29-bit CAN message ID into 3 portions to support peer-to-peer communication. When a CAN message arrives, we will have its 29-bit ID, and out of this, we can determine who sent it, and what message number they sent.

You should configure your CAN hardware filter based on your controller ID. So if your controller is ID 0x50, you should accept all messages in this range: 0x50.00.000 - 0x50.FF.FFF. In particular, your CAN hardware filter needs a single EXTENDED GROUP filter.

You should use 0xFF as a "BROADCAST" address. So if a node sends a message with destination address 0xFF and message number is 0x101, then ALL controllers should respond to this command. So this means you need another acceptance filter with this range: 0xFF.00.000 - 0xFF.FF.FFF

CAN Communication Protocol (29-bit CAN ID)
Reserved bit Destination Controller Source Controller Message number
1-bit : B28 8-bit : B27:B20 8-bit : B19:B13 12-bit: B12:B00
Message Numbers
Reserved 0x000 - 0x0FF
Common commands 0x100 - 0x1FF
Common responses 0x200 - 0x2FF
Controller specific commands 0x300 - 0x3FF
Reserved 0x400 - 0x4FF
Data messages 0x500 - 0x5FF

Example Controller Communication Table

Common Communication Table
Message Number Purpose / Data layout
0x101 Get version and boot info (0x201 will be sent)
0x102 Get general info (0x202 will be sent)
0x103 Synchronize (set) time:
byte [0-3] : System time
0x201
byte [0-3] : Version Info
byte [4-7] : boot timestamp
0x202
byte [0-3] : Current time
byte [4]   : CPU usage %
Geographical Controller Communication Table
Message Number Purpose Data layout
0x301 Set GPS destination
byte [0-3] : (float) Longitude
byte [4-7] : (float) Latitude
0x501 GPS Data Message
byte [0-3] : (float) Longitude
byte [4-7] : (float) Latitude
0x502 Compass Data Message
byte [0-1] : (uint16) Current compass degree
byte [2-3] : (uint16) Destination compass degree
Sensor Controller Communication Table
Message Number Purpose Data layout
0x501 Sensor Data Message
byte [0] : Front sensor value in inches
byte [1] : Left sensor value in inches
byte [2] : Right sensor value in inches
etc.

How will communication work?

After startup, begin to send your data messages at the desired periodic rates using the periodic API as listed below in the example. Whichever controller wants to listen to your periodic message shall intercept your message and use it for its needs.

Features

The first feature to develop is the self-drive capability and everything else comes later. While some people in the team may be focusing on delivering this primary feature, other members can focus on other things such as automatic headlights, variable speed settings through Android interface etc. If your product team fails, then just like it would happen in the industry, you will get laid off, and I will see you again in the course ;(

Quick and Easy Features

These features are mandatory, just to help you debug faster.

  • Each controller shall display its version information at startup, for example:
    "Version 1.2"
    "Fixed rear sensor reporting zero value"
    "Version 1.1"
    "Added rear sensor value"
  • Each controller shall use the 2-digit LED display to display meaningful info
    Maybe Geo Controller can display # of feet to destination
    Central controller can display number of CAN messages received per second.
  • Each controller shall use the 4-LED lights for some indication
    LED0 should be lit if an error happens (common to everyone)
    Each LED should be labeled about what it means(maybe with a label machine?)

Robustness

Your project is one project as a whole. So if it doesn't work, do not blame it on "hey, their controller crashed". If a controller crashes it will restart, and you will have live with missing data messages. So your code should be robust, and self-recover from any crashed event or any brief power disruptions.

Likewise, if you send a message, and it fails (in case the other controller is down), your CAN bus may go to abnormal state and turn off. In this condition, all of your messages will fail, and you will have to handle this. I recommend the following:

  • Attach a BUS off callback function that gives "can_bus_crashed" semaphore.
  • If the semaphore is ever given, reset your CAN bus after a 3 second timeout.

Startup Tests

Since we rely on multiple controllers, it is critical to be able to test each controller quickly, reliably, and easily. So here is a startup test the Central Controller must process upon each boot:

  • Central controller sends a message requesting boot information from each controller
  • Central controller checks responses after about 100 ms:
    Each controller must have responded
    Each controller's boot code (normal, abnormal) should be validated.

Considerations

You should consider and design your software for all of these events:

  • Where is the kill switch?
  • Can you remotely shut down the car in 3 seconds?
  • Where is the kill switch?
  • If your controller goes down, will it fully recover to "last known configuration"?
  • If critical sensor data stops coming, how will you stop the car?
  • How can you quickly discover one or more controllers reaching an error state?
  • Log the data on the SD card as much as possible.
    If something wrong happens, you need to know what happened.
    Each controller must log its "startup" time, to debug when a controller crashes and restarts

Grade

Your grade is relative. The best team earns the best grade. Remember than three out of three features working 100% is far better than nine out of ten features working. Focus on less features, with highest quality.

Sample Code


Part 1: Basic structure and CAN initialization

/************** can_msg_id.h *************/
#ifndef CAN_MSG_ID_H_
#define CAN_MSG_ID_H_


/**
 * Have an enumeration of controller IDs
 */
typedef enum :uint8_t {
    cid_geographical_controller = 1,
    cid_central_controller = 2,
    cid_broadcast = 0xff
} cid_t;

/// TODO Each controller shall set its own ID
#define OUR_CONTROLLER_ID       (cid_central_controller)

/**
 * Create a "union" whose struct overlaps with the uint32_t of CAN message id
 */
typedef union {
    /// This "raw" overlaps with <DST> <SRC> <ID>
    uint32_t raw;

    /// Struct members overlap with "raw"
    struct {
        uint32_t msg_num : 12; ///< Message number
        uint32_t src :  8;     ///< Source ID
        uint32_t dst :  8;     ///< Destination ID
    };
} __attribute__((packed)) controller_id_t;

/**
 * Creates a message ID based on the message ID protocol
 * @param [in] dst  The destination controller ID
 * @param [in] msg_num  The message number to send to the dst controller
 *
 * @returns  The 32-bit message ID created by the input parameters
 */
static inline uint32_t make_id(uint8_t dst, uint16_t msg_num)
{
    controller_id_t cid = { 0 };
    cid.msg_num  = msg_num;
    cid.src = OUR_CONTROLLER_ID;
    cid.dst = dst;
    return cid.raw;
}

#endif /* CAN_MSG_ID_H_ */


/************** main.cpp ************/
#include "can_msg_task_mgr.hpp"
#include "can.h"

int main(void)
{
    /* Initialize CAN Bus and set the acceptance filter(s) */
    CAN_init(can1, 100, 10, 10, NULL, NULL);
    /* TODO Initialize acceptance filters */
    CAN_reset_bus(can1);

    /* This creates all of the periodic message tasks */
    can_msg_task_init();

    /* TODO Add more tasks... */

    /* Start the scheduler to run all the tasks */
    scheduler_start(); 

    return 0;
}


Part 2: CAN Rx Task

Only one FreeRTOS task should be responsible to receive CAN messages (while any task can send a CAN message). This receiving task should "route" the incoming messages to the appropriate "consumers" in your code.

/* Either a "plain vanilla" FreeRTOS task, or run() method of scheduler_task */
void rx_fanout_task(void *p)
{
    can_msg_t msg;
    while(1) {
        if (CAN_rx(can1, &msg, portMAX_DELAY)) {
            /* TODO This is psuedocode, so add your real logic here */
            if (!dst_is_not_us) {
                LOG_ERROR("CAN acceptance filter must be incorrect (0x%08X)!", msg.msg_id);
            }
            else if (command_message) {  /* 0x100 - 0x1FF */
                handle_common_cmd(msg);  /* 0x200 - 0x2FF */
            }
            else if (our_command) {      /* 0x300 - 0x3FF */
                handle_our_cmd(msg);
            }
            else if (data_message) {     /* 0x500 - 0x5FF */
                handle_data_msg(msg);
            }
            else if (cmd_response_msg) { /* 0x200 - 0x2FF */
                handle_cmd_rsp_msg(msg);
            }
            else {
                LOG_ERROR("Unexpected Message ID: 0x%08X", msg.msg_id);
            }
        }
    }
}


Part 3: Infrastructure code for sending CAN messages periodically

can_msg_task.hpp

#ifndef CAN_TASK_HPP_
#define CAN_TASK_HPP_

#include <stdint.h>
#include "can.h"
#include "vector.hpp"
#include "scheduler_task.hpp"

/**
 * The task that sends out the periodic messages of a list at the defined frequency.
 */
class canMsgTask : public scheduler_task
{
    public:
        canMsgTask(can_t canBusNum,   ///< The CAN bus to use
                   float rateHz,      ///< The message rate of this task
                   uint8_t capacity,  ///< The max capacity of the message list
                   uint8_t priority   ///< The priority of this task
                  );

        /// Init function
        bool init(void);

        /// FreeRTOS task method
        bool run(void *p);

        /// Add a periodic message to be sent by this task
        /// You probably cannot access it directly, so use can_msg_task_mgr.hpp's API
        bool addPeriodicMsg(uint8_t dst,           ///< Destination address
                            uint16_t msgNum,       ///< Message number
                            can_msg_t *pCanMsg     ///< Actual CAN message pointer
                           );

    private:
        /**
         * Structure of a periodic message
         */
        typedef struct {
            uint8_t dstAddr;        ///< The destination address of a node
            uint16_t msgNum;        ///< The message number for the node
            can_msg_t *canMsgPtr;   ///< Pointer to the actual CAN message pointer
        } periodicMsg_t;

        canMsgTask();               ///< Private default constructor, do not use.
        const float mTaskRateHz;               ///< The run duration of the task
        const can_t mCanBusNum;                ///< The CAN Bus number
        VECTOR<periodicMsg_t> mPerdiocMsgList; ///< The periodic message list
};
#endif /* CAN_TASK_HPP_ */

can_msg_task.cpp

#include "can_msg_task.hpp"
#include "can_msg_id.h"
#include "file_logger.h"

canMsgTask::canMsgTask(can_t canBusNum, float rateHz, uint8_t capacity, uint8_t priority) :
    scheduler_task("sendMsg", 3 * 512, priority),   ///< base class constructor calls
    mTaskRateHz(rateHz),        ///< Task rate in Hz
    mCanBusNum(canBusNum),      ///< CAN Bus to use
    mPerdiocMsgList(capacity)   ///< Construct the list
{
    /* Nothing to do */
}

bool canMsgTask::init(void)
{
    bool status = false;
    const uint32_t rateMs = 1000 / mTaskRateHz;

    /* The rate must be within reasonable bounds */
    const uint32_t minMs = 1;
    const uint32_t maxMs = 60 * 1000;
    if (rateMs >= minMs && rateMs <= maxMs) {
        setRunDuration(rateMs);
        status = true;
    }

    return status;
}

bool canMsgTask::run(void *p)
{
    can_msg_t msg;
    const uint32_t timeoutMs = 50; /* Some reasonable time */

    for (unsigned int i = 0; i < mPeriodicMsgList.size(); i++)
    {
        /* Get the item pointer from the list */
        const periodicMsg_t &periodicMsg = mPeriodicMsgList[i];

        /* Copy the CAN message from the CAN message pointer, this includes the
         * data length field which should've been set by the caller of addPeriodicMsg()
         */
        msg = *(periodicMsg.canMsgPtr);

        /* Form the message ID that we need to use */
        msg.msg_id = make_id(periodicMsg.dstAddr, periodicMsg.msgNum);

        /* We must be able to at least queue the message without a timeout otherwise
         * either the CAN Bus is over-utilized or our queue sizes are too small.
         */
        if (!CAN_tx(mCanBusNum, &msg, timeoutMs)) {
            LOG_ERROR("Error sending message from %uHz task within %u ms",
                      mTaskRateHz, timeoutMs);
        }
    }

    return true;
}

bool canMsgTask::addPeriodicMsg(uint8_t dst, uint16_t msgNum, can_msg_t *pCanMsg)
{
    bool ok = false;

    /* Populate the fields of the periodic message */
    periodicMsg_t periodicMsg;
    periodicMsg.dstAddr = dst;
    periodicMsg.msgNum = msgNum;
    periodicMsg.canMsgPtr = pCanMsg;

    if (! (ok = (NULL != pCanMsg))) {
        LOG_ERROR("pCanMsg was a NULL pointer (%uHz task)", mTaskRateHz);
    }
    else {
        /* Add the periodic message to our list or if no capacity, log an error*/
        if ((ok = (mPeriodicMsgList.size() < mPeriodicMsgList.capacity()))) {
            /* TODO Double check if this message doesn't already exist */
            mPeriodicMsgList.push_back(periodicMsg);
        }
        else {
            LOG_ERROR("List capacity for %uHz task has exceeded maximum periodic messages of %u",
                      mTaskRateHz, mPeriodicMsgList.size());
        }
    }

    return ok;
}

can_msg_task_mgr.hpp

#ifndef CAN_MSG_TASK_MANAGER_HPP_
#define CAN_MSG_TASK_MANAGER_HPP_

#include "can_msg_task.hpp"
#include "scheduler_task.hpp"

/// Enumeration of message rate
typedef enum {
    msgRate1Hz  = 0,
    msgRate10Hz,
    msgRate50Hz,

    /// Marks the last entry; do not use!
    msgRateLast,
} msgRate_t;

/// Initializes all of the CAN periodic message tasks
void can_msg_task_init(void);

/**
 * Adds a message periodic to be sent at the given rate.
 * @param [in] rate    The rate of the message
 * @param [in] dst     The node address of the destination
 * @param [in] msgNum  The message number of the CAN message ID
 * @param [in] pCanMsg The pointer to the CAN message
 *
 * @returns true if the periodic was added successfully
 */
bool add_new_periodic_msg(msgRate_t rate, uint8_t dst, uint16_t msgNum, can_msg_t *pCanMsg);

#endif /* CAN_MSG_TASK_MANAGER_HPP_ */

can_msg_task_mgr.cpp

#include "can_msg_task_mgr.hpp"


/// Private instances of the canMsgTask pointers we create
static canMsgTask *g_MsgTaskPtrs[msgRateLast] = { NULL };

void can_msg_task_init(void)
{
    const can_t canbus = can1;
    const uint32_t listCap = (5 * 10); /// 5 controllers, and 10 msgs per controller max

    /* Create all of the tasks and add them to the scheduler */
    scheduler_add_task((g_MsgTaskPtrs[msgRate1Hz]  = new canMsgTask(canbus,   1, listCap, 1)));
    scheduler_add_task((g_MsgTaskPtrs[msgRate10Hz] = new canMsgTask(canbus,  10, listCap, 2)));
    scheduler_add_task((g_MsgTaskPtrs[msgRate50Hz] = new canMsgTask(canbus,  50, listCap, 3)));
}

bool add_new_periodic_msg(msgRate_t rate, uint8_t dst, uint16_t msgNum, can_msg_t *pCanMsg)
{
    bool ok = false;

    if (NULL != g_MsgTaskPtrs[rate]) {
        ok = (g_MsgTaskPtrs[rate])->addPeriodicMsg(dst, msgNum, pCanMsg);
    }

    return ok;
}