Difference between revisions of "Self-driving Car"

From Embedded Systems Learning Academy
Jump to: navigation, search
(Infrastructure code for CAN message subscriptions)
(Schedule)
 
(32 intermediate revisions by 4 users not shown)
Line 8: Line 8:
  
 
== Gitlab ==
 
== 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.
+
Create a "master" Gitlab project that contains sub-folders of each project OR a Gitlab project that contains different "master" branches for each controller.  Please provide me the access (Gitlab 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:
 
The folder structure should be:
TeamX_CmpE_Fall2014
+
TeamX_CmpE_Fall2015
 
*:  Sensor
 
*:  Sensor
 
*:  IO
 
*:  IO
 
*:  <other controller projects>
 
*:  <other controller projects>
 
== Schedule ==
 
{| class="wikitable"
 
|+ 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
 
* [http://www.coderemarks.com Code Review]
 
|-
 
| 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 ==
 
== Parts ==
Line 81: Line 21:
  
 
== 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 96: Line 36:
 
*:  Tilt (angle of the car)
 
*:  Tilt (angle of the car)
 
|-
 
|-
| '''Motor Controller'''
+
| '''Motor and I/O Controller'''
 
'''(2 members)'''
 
'''(2 members)'''
 
|
 
|
Line 102: Line 42:
 
*:    Provide a means to steer, and drive the car
 
*:    Provide a means to steer, and drive the car
 
*  Provide feedback of the speed using a wheel encoder or speed sensor
 
*  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
 
*  Provide an LCD screen to report car status
 
*:  Errors and communication status
 
*:  Errors and communication status
Line 112: Line 48:
 
*  Button hard-coded to set a specific destination
 
*  Button hard-coded to set a specific destination
 
*  Provide means to turn on/off the headlights (etc).
 
*  Provide means to turn on/off the headlights (etc).
 +
|-
 
|-
 
|-
 
| '''Communication Bridge + Android'''
 
| '''Communication Bridge + Android'''
'''(3 members)'''
+
'''(2 members)'''
 
|
 
|
 
*  Provide means to communicate and display status on an Android/iPhone device
 
*  Provide means to communicate and display status on an Android/iPhone device
Line 121: Line 58:
 
|-
 
|-
 
| '''Geographical Controller'''
 
| '''Geographical Controller'''
'''(3 members)'''
+
'''(2 members)'''
 
|
 
|
 
*  Interface to a 5Hz or faster GPS
 
*  Interface to a 5Hz or faster GPS
*  Interface to a backup GPS in case primary one fail (use a different manufacturer)
 
 
*  Interface to a compass
 
*  Interface to a compass
 
*  Allow a GPS coordinate to be "set"
 
*  Allow a GPS coordinate to be "set"
Line 131: Line 67:
 
*  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)'''
+
'''(2 members)'''
 
|
 
|
 
*  This is the primary unit that communicates with every controller to drive the car
 
*  This is the primary unit that communicates with every controller to drive the car
Line 143: Line 79:
  
 
== Communication ==
 
== 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.  The most important thing to realize is that CAN bus is 1:1 communication rather than 1:many.  Although most of the time, all the controllers should only communicate with the '''Master Controller''', sometimes there may be a necessity for any one controller to communicate with a specific controller, therefore we need to provide a means to facilitate this.  Given below is an example communication interface for one controller.
+
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, and the controller or message with the highest priority shall pick a lower CAN message ID.
 
 
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 ===
 
=== 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.
+
We can split the 11-bit CAN message ID into 2 portions, one that dictates the priority of the message (message type), and the other that dictates the priority of the controller.  For example, the airbag sensor ECU should use lowest controller ID and lowest message type.
  
'''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: '''<code>0x50.00.000 - 0x50.FF.FFF</code>'''.  In particular, your CAN hardware filter needs a single EXTENDED GROUP filter.
+
The split of message ID can also help filter out unwanted messages from arriving into your microcontroller as the CAN peripheral of the microcontroller will filter out the unwanted dataFor example, you can choose to accept all messages from the sensor controller, while making sure you receive no messages from the motor controller.  In particular, your CAN hardware filter needs a single EXTENDED GROUP filter.
  
 
{| class="wikitable"
 
{| class="wikitable"
|+ CAN Communication Protocol (29-bit CAN ID)
+
|+ CAN Communication Protocol (11-bit CAN ID)
 
|-
 
|-
 
| Reserved bit
 
| Reserved bit
| Destination Controller
 
 
| Source Controller
 
| Source Controller
| Message number
+
| Message type
|-
 
| <code>1-bit : B28</code>
 
| <code>8-bit : B27:B20</code>
 
| <code>8-bit : B19:B13</code>
 
| <code>12-bit: B12:B00</code>
 
|}
 
 
 
{| class="wikitable"
 
|+ Message Numbers
 
|-
 
| Reserved
 
| <code>0x000 - 0x0FF</code>
 
|-
 
| Common commands
 
| <code>0x100 - 0x1FF</code>
 
|-
 
| Common responses
 
| <code>0x200 - 0x2FF</code>
 
|-
 
| Controller specific commands
 
| <code>0x300 - 0x3FF</code>
 
|-
 
| Subscription messages
 
| <code>0x400 - 0x4FF</code>
 
|-
 
| Subscribed messages
 
| <code>0x500 - 0x5FF</code>
 
|}
 
 
 
=== Example Controller Communication Table ===
 
 
 
 
 
{| class="wikitable"
 
|+ 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
+
| <code>1-bit : B10</code>
|  
+
| <code>6-bit : B9:B4</code>
byte [0-3] : Current time
+
| <code>4-bit : B3:B0</code>
byte [4: CPU usage %
 
 
|}
 
|}
  
{| class="wikitable"
+
=== [[DBC Format]] ===
|+ Subscription Rate Info
+
DBC format is a well known format to describe the format of a CAN message.  This is essentially the schema of the data that is communicated over the CAN bus.  Please view the linked [[DBC Format]] article for details before reading further.
|-
 
| Byte 0
 
| Effect
 
|-
 
| 0
 
| Off
 
|-
 
| 1
 
| 1Hz
 
|-
 
| 5
 
| 5Hz
 
|-
 
| 10
 
| 10Hz
 
|-
 
| 20
 
| 20Hz
 
|-
 
| 255
 
| "On data change", capped to 20Hz
 
|}
 
  
{| class="wikitable"
+
== How will communication work? ==
|+ Geographical Controller Communication Table
+
After startup, begin to send your data messages at the desired periodic rates using the periodic scheduler 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. You can have global variables for the CAN data messages, and tasks should update the data within these messages.
|-
 
| Message Number
 
| Purpose
 
| Data layout
 
|-
 
| 0x301
 
| Set GPS destination
 
|
 
byte [0-3] : (float) Longitude
 
  byte [4-7] : (float) Latitude
 
|-
 
| 0x401
 
| Subscribe to GPS data
 
| See '''Subscription Rate''' above
 
|-
 
| 0x402
 
| Subscribe to compass data
 
| See '''Subscription Rate''' above
 
|-
 
| 0x501
 
| Subscribed data of 0x401
 
|
 
byte [0-3] : (float) Longitude
 
byte [4-7] : (float) Latitude
 
|-
 
| 0x502
 
| Subscribed data of 0x402
 
|
 
  byte [0-1] : (uint16) Current compass degree
 
byte [2-3] : (uint16) Destination compass degree
 
|}
 
 
 
{| class="wikitable"
 
|+ Sensor Controller Communication Table
 
|-
 
| Message Number
 
| Purpose
 
| Data layout
 
|-
 
| 0x401
 
| Subscribe to distance sensor data
 
| See '''Subscription Rate''' above
 
|-
 
| 0x501
 
| Subscribed data of 0x401
 
|
 
byte [0] : Front sensor value in inches
 
byte [1] : Left sensor value in inches
 
byte [2] : Right sensor value in inches
 
etc.
 
|}
 
  
 
== 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 304: Line 111:
  
 
*  Each controller shall display its version information at startup, for example:
 
*  Each controller shall display its version information at startup, for example:
*:  "Version 1.2"
+
*:  printf("Vesion: %s %s\n", __DATE__, __TIME__);
*:  "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
 
*  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)'''
*:  Each LED should be labeled about what it means(maybe with a label machine?)
+
*:  Each LED should be labeled about what it means (maybe with a label maker?)
  
 
=== 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.
+
In 1Hz periodic callback, check if the Bus if OFF, and simply reset it
If the semaphore is ever given, reset your CAN bus, and re-subscribe to the desired messages.
+
Do not reset it in the CAN ISR callback from bus-off since a bad controller can continuously cause errors without the delay of 1Hz
  
 
=== 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 333: Line 137:
 
*  Where is the kill switch?
 
*  Where is the kill switch?
 
*  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?
 
*  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 347: Line 148:
  
 
== Sample Code ==
 
== Sample Code ==
 
 
<BR/>
 
<BR/>
=== Subscribe and handle subscribed data ===
+
=== Part 1: Basic structure and CAN initialization ===
<syntaxhighlight lang="C">
+
<syntaxhighlight lang="cpp">
 
+
/************** periodic_callbacks.cpp ************/
 
+
#include "can.h"
/**
 
* Have an enumeration of controller IDs
 
*/
 
typedef enum {
 
    cid_geographical_controller = 50,
 
    cid_master_controller = 60,
 
} cid_t;
 
  
/**
+
bool period_init(void)
* Each controller shall then set its own ID
 
*/
 
const cid_t our_controller_id = cid_master_controller;
 
 
 
/**
 
* Create a "union" whose struct overlaps with the uint32_t
 
*/
 
typedef union {
 
    struct {
 
        uint32_t msg_num : 12; ///< Message number
 
        uint32_t src : 8;      ///< Source ID
 
        uint32_t dst : 8;      ///< Destination ID
 
        uint32_t : 1;          ///< Unused (reserved 29th bit)
 
    };
 
    /// This "raw" overlaps with <DST> <SRC> <ID>
 
    uint32_t raw;
 
 
 
} __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
 
*/
 
extern "C" uint32_t make_id(uint8_t dst, uint16_t msg_num)
 
 
{
 
{
     controller_id_t cid;
+
     /* Initialize CAN Bus and set the acceptance filter(s) */
    cid.raw = 0;
+
     CAN_init(can1, 100, 10, 10, NULL, NULL);
    cid.msg_num  = msg_num;
 
    cid.src = our_controller_id;
 
     cid.dst = dst;
 
    return cid.raw;
 
}
 
  
int main(void)
+
     /* TODO Initialize acceptance filters */
{
+
     CAN_reset_bus(can1);
    /**
+
      
    * We use magic numbers here, but each message ID (such as 0x401) should be
+
     return true;
    * part of an enumeration that is shared between different controllers.
 
    */
 
    can_msg_t msg = { 0 };
 
    msg.msg_id = make_id(cid_geographical_controller, 0x401);
 
 
 
    /**
 
    * Send the message to the geographical controller to subscribe to
 
    * GPS data to be sent at 5Hz:
 
    */
 
    msg.frame_fields.data_len = 1;
 
    msg.frame_fields.is_29bit = 1;
 
    msg.data.bytes[0] = 5;
 
    CAN_tx(can1, &msg, portMAX_DELAY);
 
 
 
     /**
 
    * Now, we should be able to retrieve our data from the geographical controller at 5Hz.
 
    * Since we may have subscribed to many messages, you will need to see pCid->src to find out
 
    * where the message came from, and then also check the pCid->id to detect what message it is,
 
    * and then parse the data bytes into our variable types.
 
    */
 
     if (CAN_rx(can1, &msg, portMAX_DELAY)) {
 
        controller_id_t *pCid = &(msg.msg_id);
 
 
 
        /**
 
        * Check if we got the response from the GPS controller
 
        * If you subscribe to a lot of messages, you might want to put this
 
        * in a large switch statement that calls functions.
 
        */
 
        if (pCid->src == cid_geographical_controller)
 
        {
 
            if (pCid->msg_num== 0x501) /* 0x501 is a magic num, should be an enum */
 
            {
 
                float longitude = * (float*) &(msg.data.bytes[0]);
 
                float latitude  = * (float*) &(msg.data.bytes[4]);
 
            }
 
        }
 
     }
 
 
 
     return 0;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 
 
<BR/>
 
<BR/>
  
=== CAN Rx Task ===
+
=== Part 2: Periodic Parsing 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.  Ideally, you want an API that adds a subscription message, and only this API caller should somehow receive the subscribed message when available.
+
Only one FreeRTOS periodic function 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.
  
<syntaxhighlight lang="C">
+
See the [[DBC Format]] article for more sample code related to handling the received messages over the CAN bus.
/* Either a "plain vanilla" FreeRTOS task, or run() method of scheduler_task */
 
void main_task(void *p)
 
{
 
    can_msg_t msg;
 
    while(1) {
 
        if (CAN_rx(can1, &msg, portMAX_DELAY)) {
 
            /* This is psuedocode, so add your real logic here */
 
            if (!dst_is_not_us) {
 
                log_error("HW CAN Filter must be incorrect!");
 
            }
 
            else if (command_message) {  /* 0x100 - 0x1FF */
 
                handle_common_cmd(msg);  /* 0x200 - 0x2FF */
 
            }
 
            else if (our_command) {      /* 0x300 - 0x3FF */
 
                handle_our_cmd(msg);
 
            }
 
            else if (new_subscribe) {    /* 0x400 - 0x4FF */
 
                subscription_add(msg);
 
            }
 
            else if (subscribed_msg) {  /* 0x500 - 0x5FF */
 
                handle_subscribed_msg(msg);
 
            }
 
            else if (cmd_response_msg) { /* 0x200 - 0x2FF */
 
                handle_cmd_rsp_msg(msg);
 
            }
 
            else {
 
                log_error("I don't expect this message!");
 
            }
 
        }
 
    }
 
}
 
void subscription_task(void *p)
 
{
 
    /* Subscription message loop at 20Hz
 
    * This should send all messages ranging from 1-20Hz
 
    */
 
    while (1) {
 
        subscription_send();
 
        vTaskDelay(OS_MS(50));
 
    }
 
}
 
</syntaxhighlight>
 
 
 
<BR/>
 
=== Infrastructure code for CAN message subscriptions ===
 
  
 
<syntaxhighlight lang="C">
 
<syntaxhighlight lang="C">
/*** subscription_task.hpp ***/
+
void period_100Hz(void)
#ifndef SUBSCRIPTION_TASK_HPP_
 
#define SUBSCRIPTION_TASK_HPP_
 
 
 
#include <stdint.h>
 
 
 
#include "can.h"
 
#include "vector.hpp"
 
#include "scheduler_task.hpp"
 
 
 
 
 
 
 
/**
 
* Enumeration of message rate and the index.
 
* We need the message rate in ms to set the task's run() method call frequency, but
 
* we also need an index to be able to add subscription messages to the correct list.
 
*/
 
typedef enum {
 
    msgRate1Hz  = (1000 << 4) | 0,
 
    msgRate5Hz  = ( 200 << 4) | 1,
 
    msgRate10Hz = ( 100 << 4) | 2,
 
    msgRate20Hz = (  50 << 4) | 3,
 
 
 
    /// Marks the last entry; do not use!
 
    msgRateLast = (0 << 4) | 5,
 
} msgRate_t;
 
 
 
/** @{ Converts msgRate_t to frequency in milliseconds and its index */
 
static inline uint8_t msgRateToIndex(msgRate_t mr)  { return (mr & 0x0F); }
 
static inline uint16_t msgRateToFreqMs(msgRate_t mr) { return (mr >> 4);  }
 
/** @} */
 
 
 
/**
 
* Structure of a subscribed 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
 
} subscribedMsg_t;
 
 
 
/**
 
* The task that sends out the subscription messages of a list at the defined frequency.
 
*/
 
class canSubscribedMsgTask : public scheduler_task
 
{
 
    public:
 
        canSubscribedMsgTask(msgRate_t rate,                ///< The message rate of this task
 
                            VECTOR<subscribedMsg_t> *ptr,  ///< The subscription message list to send
 
                            uint8_t priority              ///< The priority of this task
 
                            );
 
 
 
        /// FreeRTOS task method
 
        bool run(void *p);
 
 
 
        /// Add a subscription message to be sent at the given rate
 
        static bool addSubscribedMsg(msgRate_t rate,        ///< The message rate
 
                                    uint8_t dst,          ///< Destination address
 
                                    uint16_t msgNum,      ///< Message number
 
                                    can_msg_t *pCanMsg    ///< Actual CAN message pointer
 
                                    );
 
 
 
    private:
 
        canSubscribedMsgTask();                ///< Private default constructor, do not use.
 
        const msgRate_t mMsgRate;              ///< This tasks' message rate
 
        VECTOR<subscribedMsg_t> *mpSubsMsgList; ///< The pointer to the subscription list
 
 
 
        /// The list of subscription message list
 
        static VECTOR<subscribedMsg_t> *mpSubsList[msgRateLast];
 
};
 
 
 
 
 
 
 
#endif /* SUBSCRIPTION_TASK_HPP_ */
 
 
 
</syntaxhighlight>
 
 
 
<BR/><BR/>
 
<syntaxhighlight lang="C">
 
/*** subscription_task.cpp ***/
 
#include <string.h>
 
 
 
#include "subscription_task.hpp"
 
#include "file_logger.h"
 
 
 
 
 
 
 
/// Shared list of vector pointers between all of the instances of canSubsribedMsgTask (s)
 
VECTOR<subscribedMsg_t> *canSubscribedMsgTask::mpSubsList[msgRateLast] = { 0 };
 
 
 
/// Extern methods (defined at another *.c file)
 
extern "C" uint32_t make_id(uint8_t dst, uint16_t msg_num);
 
 
 
 
 
 
 
canSubscribedMsgTask::
 
canSubscribedMsgTask(msgRate_t rate,                ///< The message rate of this task
 
                    VECTOR<subscribedMsg_t> *ptr,  ///< The subscription message list to send
 
                    uint8_t priority              ///< The priority of this task
 
                    ) :
 
    scheduler_task("sendMsg", 3 * 512, priority),  ///< Default constructor calls
 
    mMsgRate(msgRate1Hz),                          ///< Store our message rate
 
    mpSubsMsgList(ptr)                              ///< Store our subscription list pointer
 
{
 
    /* Set the task rate to call the run() method */
 
    setRunDuration(msgRateToFreqMs(mMsgRate));
 
 
 
    /* Add our subscribed message list pointer to the list such that
 
    * addSubscribedMsg() method can add subscribed messages to our list.
 
    */
 
    mpSubsList[msgRateToIndex(mMsgRate)] = mpSubsMsgList;
 
}
 
 
 
bool canSubscribedMsgTask::run(void *p)
 
 
{
 
{
 
     can_msg_t msg;
 
     can_msg_t msg;
    subscribedMsg_t subsMsg;
+
         // Process all messages that arrived in the last 10ms
    const can_t canbusNum = can1;  /* CAN Bus to use */
+
         while (CAN_rx(can1, &msg, 0))  
    const uint32_t timeoutMs = 50; /* Some reasonable time */
+
         {
 
+
            /* TODO: Call auto generated code from DBC parser to parse the message */
    for (unsigned int i = 0; i < mpSubsMsgList->size(); i++)
 
    {
 
        /* Get the item pointer from the list */
 
         subsMsg = (*mpSubsMsgList) [i];
 
 
 
        /* Copy the CAN message from the CAN message pointer */
 
        memset(&msg, 0, sizeof(msg));
 
        msg = *(subsMsg.canMsgPtr);
 
 
 
        /* Form the message ID that we need to use */
 
        msg.msg_id = make_id(subsMsg.dstAddr, subsMsg.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(canbusNum, &msg, timeoutMs)) {
 
            LOG_ERROR("Error sending message from %uHz task within %u ms",
 
                      msgRateToFreqMs(mMsgRate), timeoutMs);
 
         }
 
    }
 
 
 
    return true;
 
}
 
 
 
bool canSubscribedMsgTask::addSubscribedMsg(msgRate_t rate, uint8_t dst, uint16_t msgNum, can_msg_t *pCanMsg)
 
{
 
    bool ok = false;
 
 
 
    /* Populate the fields of the subscribed message */
 
    subscribedMsg_t subsMsg;
 
    subsMsg.dstAddr = dst;
 
    subsMsg.msgNum = msgNum;
 
    subsMsg.canMsgPtr = pCanMsg;
 
 
 
    /* Look up the vector for this msgRate_t(rate), and check if it has capacity */
 
    VECTOR<subscribedMsg_t> *vectorPtr =  mpSubsList[msgRateToIndex(rate)];
 
 
 
    if (! (ok = (NULL != vectorPtr))) {
 
        LOG_ERROR("Vector pointer for %uHz task was NULL, was the task created?",
 
                  msgRateToFreqMs(rate));
 
    }
 
    else {
 
        /* Add the subscription message to our list or if no capacity, log an error*/
 
        if ((ok = (vectorPtr->size() < vectorPtr->capacity()))) {
 
            (*vectorPtr) += subsMsg;
 
        }
 
        else {
 
            LOG_ERROR("List capacity for %uHz task has exceeded maximum subscriptions",
 
                      msgRateToFreqMs(rate));
 
 
         }
 
         }
    }
 
 
    return ok;
 
 
}
 
}
 
 
</syntaxhighlight>
 
</syntaxhighlight>

Latest revision as of 14:40, 15 July 2016

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 OR a Gitlab project that contains different "master" branches for each controller. Please provide me the access (Gitlab 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_Fall2015
    Sensor
    IO
    <other controller projects>

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 and I/O 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
  • 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

(2 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

(2 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

(2 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, and the controller or message with the highest priority shall pick a lower CAN message ID.

Recommended CAN Message ID format

We can split the 11-bit CAN message ID into 2 portions, one that dictates the priority of the message (message type), and the other that dictates the priority of the controller. For example, the airbag sensor ECU should use lowest controller ID and lowest message type.

The split of message ID can also help filter out unwanted messages from arriving into your microcontroller as the CAN peripheral of the microcontroller will filter out the unwanted data. For example, you can choose to accept all messages from the sensor controller, while making sure you receive no messages from the motor controller. In particular, your CAN hardware filter needs a single EXTENDED GROUP filter.

CAN Communication Protocol (11-bit CAN ID)
Reserved bit Source Controller Message type
1-bit : B10 6-bit : B9:B4 4-bit : B3:B0

DBC Format

DBC format is a well known format to describe the format of a CAN message. This is essentially the schema of the data that is communicated over the CAN bus. Please view the linked DBC Format article for details before reading further.

How will communication work?

After startup, begin to send your data messages at the desired periodic rates using the periodic scheduler 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. You can have global variables for the CAN data messages, and tasks should update the data within these messages.

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:
    printf("Vesion: %s %s\n", __DATE__, __TIME__);
  • 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 maker?)

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:

  • In 1Hz periodic callback, check if the Bus if OFF, and simply reset it
  • Do not reset it in the CAN ISR callback from bus-off since a bad controller can continuously cause errors without the delay of 1Hz

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?
  • 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

/************** periodic_callbacks.cpp ************/
#include "can.h"

bool period_init(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);
    
    return true;
}


Part 2: Periodic Parsing Task

Only one FreeRTOS periodic function 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.

See the DBC Format article for more sample code related to handling the received messages over the CAN bus.

void period_100Hz(void)
{
    can_msg_t msg;
        // Process all messages that arrived in the last 10ms
        while (CAN_rx(can1, &msg, 0)) 
        {
            /* TODO: Call auto generated code from DBC parser to parse the message */
        }
}