Difference between revisions of "Self-driving Car"
(→Part 3: Infrastructure code for CAN message subscriptions) |
Proj user3 (talk | contribs) (→Part 3: Infrastructure code for CAN message subscriptions) |
||
Line 648: | Line 648: | ||
* vector doesn't contain the same message number and destination addr | * vector doesn't contain the same message number and destination addr | ||
*/ | */ | ||
− | (*vectorPtr) | + | (*vectorPtr).push_back(subsMsg); |
} | } | ||
else { | else { |
Revision as of 18:45, 6 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.
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
Week | Milestone |
October (Week1) |
|
October (Week2) |
|
October (Week3) |
|
October (Week4) |
|
October (Week5) |
|
November (Week6) |
|
November (Week7) |
|
November (Week8) |
|
November (Week9) |
|
December(Week10) |
|
December(Week11) | Project Demonstration |
Parts
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!.
Sensor Controller
(2 members) |
|
Motor Controller
(2 members) |
|
I/O Unit
(2 members) |
|
Communication Bridge + Android
(3 members) |
|
Geographical Controller
(3 members) |
|
Master Controller
(3 members) |
|
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
Reserved bit | Destination Controller | Source Controller | Message number |
1-bit : B28
|
8-bit : B27:B20
|
8-bit : B19:B13
|
12-bit: B12:B00
|
Reserved | 0x000 - 0x0FF
|
Common commands | 0x100 - 0x1FF
|
Common responses | 0x200 - 0x2FF
|
Controller specific commands | 0x300 - 0x3FF
|
Add subscription message(s) | 0x400 - 0x4FF
|
Subscribed messages | 0x500 - 0x5FF
|
Example Controller 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 % |
Byte 0 | Effect |
0 | Off |
1 | 1Hz |
5 | 5Hz |
10 | 10Hz |
20 | 20Hz |
50 | 50Hz |
Any other value | Invalid date rate |
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 |
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
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.
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
- Master 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 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.
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:
- 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.
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:
- Master controller sends a message requesting boot information from each controller
- Master 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 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 you stop receiving subscribed messages, what will you do?
- 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
/**
* Have an enumeration of controller IDs
*/
typedef enum {
cid_geographical_controller = 50,
cid_master_controller = 60,
} cid_t;
/**
* 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;
cid.raw = 0;
cid.msg_num = msg_num;
cid.src = our_controller_id;
cid.dst = dst;
return cid.raw;
}
int main(void)
{
/* Initialize CAN Bus and set the acceptance filter(s) */
CAN_init(can1, 100, 10, 10, NULL, NULL);
CAN_reset_bus(can1);
/* Create the subscription lists.
* Set an initial capacity, and we disallow the capacity to grow such that the tasks can
* access the vector without having to lock/unlock the interrupts or use a semaphore
*/
const int maxMsgsPerList = 50;
VECTOR<subscribedMsg_t> *list1Hz = new VECTOR<subscribedMsg_t> (maxMsgsPerList);
VECTOR<subscribedMsg_t> *list20Hz = new VECTOR<subscribedMsg_t> (maxMsgsPerList);
/* Create the subscription handler tasks, and give them the rate, and our list pointers */
scheduler_add_task(new canSubscribedMsgTask(msgRate1Hz, list1Hz, 1));
scheduler_add_task(new canSubscribedMsgTask(msgRate20Hz, list20Hz, 2));
/* 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. Ideally, you want an API that adds a subscription message, and only this API caller should somehow receive the subscribed message when available.
/* 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)) {
/* 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("Unexpected Message ID: 0x%08X", msg.msg_id);
}
}
}
}
Part 3: Infrastructure code for CAN message subscriptions
/* Granted that the canSubscribedMsgTask tasks are added, a function foo can add subscription like this */
void foo()
{
/* Static function, so need to use :: */
can_msg_t canMsg = { 0 };
canSubscribedMsgTask::addSubscribedMsg(msgRate1Hz, dst, msgNum, &canMsg);
}
/*** subscription_task.hpp ***/
#ifndef SUBSCRIPTION_TASK_HPP_
#define SUBSCRIPTION_TASK_HPP_
#include <stdint.h>
#include "can.h"
#include "vector.hpp"
#include "scheduler_task.hpp"
/**
* Encoded 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,
msgRate50Hz = ( 20 << 4) | 4,
/// 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_ */
/*** 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;
subscribedMsg_t subsMsg;
const can_t canbusNum = can1; /* CAN Bus to use */
const uint32_t timeoutMs = 50; /* Some reasonable time */
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()))) {
/* TODO: Check the vector to avoid double subscription, so make sure the
* vector doesn't contain the same message number and destination addr
*/
(*vectorPtr).push_back(subsMsg);
}
else {
LOG_ERROR("List capacity for %uHz task has exceeded maximum subscriptions",
msgRateToFreqMs(rate));
}
}
return ok;
}