Skip to content

CAN Wrapper Module Usage Guide

Kolosso edited this page Jun 16, 2024 · 33 revisions

Welcome to the CAN Wrapper Module Usage Guide. This guide will walk you through how to use the CAN Wrapper Module with examples.

Table of Contents

  1. Overview
  2. Initialisation
  3. Receiving Messages
  4. Handling Errors
  5. Additional Examples
  6. Tips 'n' Tricks
  7. Additional Reading

Overview

The CAN Wrapper Module simplifies CAN message handling on the TSAT satellite by providing an easy-to-use interface that abstracts our custom protocol. Note that the CAN Wrapper Module does not replace HAL's existing CAN interface. Rather, the CAN Wrapper Module wraps the HAL interface with one that's much cleaner, and nicer to use. It does this while automating certain tasks, such as performing timeout event checks. This makes it so that all of our subsystems can seamlessly communicate in a standardized and effective manner.

Since CAN Wrapper Module interfaces with HAL CAN, you must configure CAN in your project before using it. See CAN Wrapper Module Installation Guide

Initialisation

This module uses a flexible callback approach to handle events such as incoming messages and communication errors.

The CANWrapper_InitTypeDef structure contains many settings that tune the module to your needs.

CANWrapper_InitTypeDef wc_init = {
		.node_id = NODE_ADCS,    // your subsystem's unique ID in the CAN network.
		.notify_of_acks = false, // whether to notify you of incoming ACK's.

		.hcan = &hcan1,  // pointer to the CAN peripheral handle.
		.htim = &htim16, // pointer to the timer handle.

		.message_callback = &on_message_received, // called when a new message is polled.
		.error_callback = &on_error_occured;      // called when a communication error occurs.
};

To initialise CAN Wrapper, call CANWrapper_Init with your chosen settings (sometime after MX_CAN#_Init):

CANWrapper_Init(wc_init);

Polling Events

Call CANWrapper_Poll_Events in your main loop to update CAN Wrapper.

Receiving Messages

Here is starter template for a message handling function. Add your specific subsystem's functionality as needed. Note that this code also makes use of the error tracker utility to record errors when they occur. If you wish to use the error tracker you must initialise it first (not shown here).

#include "tuk/can_wrapper.h"
#include "tuk/error_tracker.h"
#include <stdbool.h>

void on_message_received(CANMessage msg, NodeID sender, bool is_ack)
{
	ErrorBuffer error_buffer; // stores command errors.
	ErrorTracker_Push_Buffer(&error_buffer); // push to the top of the buffer stack.

	switch (msg.cmd)
	{
	case CMD_PLD_SET_WELL_LED: // example command.
	{
		// get the command arguments as defined in the command reference.
		uint8_t well_id, power;
		GET_ARG(msg, 0, well_id); // syntax: GET_ARG(msg, byte #, output var)
		GET_ARG(msg, 1, power);

		// perform instructed action.
		LEDs_Set_LED(well_id, power);
		break;
	}
	// ...
	default:
	{
		// unrecognized command.
		PUT_ERROR(ERROR_UNKNOWN_COMMAND, msg.cmd);
		break;
	}
	}

	if (ErrorBuffer_Has_Error(&error_buffer))
	{
		CANMessage error_report;
		error_report.cmd = CMD_CDH_PROCESS_ERROR;
		SET_ARG(error_report, 0, error_buffer); // syntax: SET_ARG(msg, byte #, input var)

		CANWrapper_Transmit(NODE_CDH, &error_report);
	}

	ErrorTracker_Pop_Buffer();
}

🛟 Best Practice: As usual, make sure to have sanity checks in place in all your functions, especially if a function is meant to directly handle data from the CAN bus. You cannot assume the data you are receiving is valid and correct!

Handling Errors

Here is starter template for an error handling function. Currently, the only type of error that is reported is a timeout event.

#include "tuk/can_wrapper.h"

void on_error_occured(CANWrapper_ErrorInfo error_info)
{
	switch (error_info.error)
	{
	case CAN_WRAPPER_ERROR_TIMEOUT:
	{
		// your call to CANWrapper_Transmit failed to invoke an Acknowledge message
		// in your target recipient. Or, the ACK message simply didn't reach you.
		// This was detected as a timeout event.
		// Here you can resolve the issue as appropriate.

		// You may want to run a check to see if it's still a good idea to resend
		// your message.

		// If all is well, you can re-send the message to the intended recipient like so:
		CANWrapper_Transmit(error_info.recipient, &error_info.msg);

		break;
	}
	}
}

⚠️ Note: The error handling functionality is quite bare in this version. It only notifies of timeouts, but there a plenty of other things that can go wrong. Expect improvements in the future.

Additional Examples

Create & Send a CAN Message

#include "tuk/can_wrapper.h"
#include <stdbool.h>

bool Report_PCB_Temp()
{
	uint16_t temp;
	bool success = TMP235_Read_Temp(&temp);
	if (success)
	{
		CANMessage my_msg;
		my_msg.cmd = CMD_CDH_PROCESS_PCB_TEMP;
		SET_ARG(my_msg, 0, temp);
		CANWrapper_Transmit(NODE_CDH, &my_msg);
	}

	return success;
}

🛟 Best Practice: Unless your best judgement says otherwise, favour hard-coding the recipient of a message you are about to send. This reduces the variability of behaviour in your code.

The CANMessage Data Members

The CANMessage type has three fields:

  • .cmd: the command ID
  • .body: the arguments of the command
  • .data: the entire message (including command ID & body)

The data field occupies the same section in memory as the other two. Therefore, any change to cmd or body will reflect in data, and vice-versa. Think of msg.data as a shorthand for (uint8_t*)msg. (that is literally what it is)

CANMessage msg;

msg.cmd = CMD_CDH_PROCESS_PCB_TEMP;  // set command ID.
msg.data[0] = CMD_CDH_PROCESS_MCU_TEMP; // another way to set command ID. (less legible)

msg.body[0] = 'A'; // set first byte in message body. (ie. the byte after command ID)
msg.data[1] = 'A'; // equivalent.
SET_ARG(msg, 0, 'A'); // equivalent.

// SET_ARG allows you to assign larger types to the message body very easily.
// Warning: make sure your data is no more than 7 bytes! (56 bits)
uint32_t large_number = 4294967295;
SET_ARG(msg, 0, large_number)

// you can access arguments in a similar way
uint8_t single_byte;
uint32_t four_bytes;
GET_ARG(msg, 0, single_byte); // retrieves byte 0 in the message body.
GET_ARG(msg, 0, four_bytes);  // retrieves bytes 0-3 in the message body.

It is highly encouraged to use the SET_ARG and GET_ARG macros to set and get command arguments since it not only makes your code more human-readable, but it also ensures your code is resilient against future breaking changes to the CANMessage structure in the future.

See can_message.h for the full structure definition.

Tips 'n' Tricks

  • To quickly search for commands in the code editor, type the name of a prefix (e.g. one of CMD_PLD, CMD_ACDS, CMD_PWR, or CMD_CDH) and press Ctrl + Space. You'll then be greeted with a list of matching commands. (assuming you've included tuk/can_wrapper.h)
  • If you haven't already, check out the Command Reference for TSAT-7 to read up on the expected format of each command. Ensuring the format you send is correct is important, as otherwise the data received may be incorrect or it might be cut off.

Additional Reading

The below link is old, so I don't recommend reading it, but I will leave it here in case you want to read the documentation for the old version of this interface or you want to understand the high level concepts of how CAN works. Definitely not a required read to use this module.

https://drive.google.com/file/d/1HHNWpN6vo-JKY5VvzY14uecxMsGIISU7/view?usp=share_link