Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FWT-146 Start Drinking On (SDO) company time #112

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
dfad85a
SDO Sample
DiegoLHendrix Nov 1, 2024
35424c8
This SDO no worky
DiegoLHendrix Nov 5, 2024
dd1dd90
Merge branch 'main' of https://github.com/RIT-EVT/EVT-core into featu…
DiegoLHendrix Nov 9, 2024
c00d790
idk anymore
DiegoLHendrix Nov 9, 2024
fbeb922
Merge branch 'main' of https://github.com/RIT-EVT/EVT-core into featu…
DiegoLHendrix Nov 12, 2024
4451a8b
The voices are getting louder
DiegoLHendrix Nov 18, 2024
ae64dc7
I Start Drinking On mondays at 8:15 am
DiegoLHendrix Nov 22, 2024
028f030
Start Drinking On comapny time
DiegoLHendrix Nov 23, 2024
9169c95
Good heavens I have arrived!
DiegoLHendrix Dec 3, 2024
4c750e4
Added comments
DiegoLHendrix Dec 3, 2024
580dbfa
CANopen is my sleep paralysis demon
DiegoLHendrix Dec 6, 2024
fff86b4
Merge branch 'main' of https://github.com/RIT-EVT/EVT-core into featu…
DiegoLHendrix Dec 6, 2024
ffc8495
please work cmake
DiegoLHendrix Dec 6, 2024
f85675a
Merge branch 'main' into feature/diegolhendrix/sdo-sample
DiegoLHendrix Dec 8, 2024
6319675
Merge branch 'main' of https://github.com/RIT-EVT/EVT-core into featu…
DiegoLHendrix Dec 14, 2024
9301881
Beetlejuice Beetlejuice Beetlejuice
DiegoLHendrix Dec 16, 2024
8a9b880
I am the Oppenheimer of CAN
DiegoLHendrix Dec 17, 2024
53d5b88
Merge branch 'feature/diegolhendrix/sdo-sample' of https://github.com…
DiegoLHendrix Dec 17, 2024
ff17043
Applied Formatting Changes During GitHub Build
Dec 17, 2024
359df66
Get me out of PR review hell
DiegoLHendrix Dec 21, 2024
0563097
Applied Formatting Changes During GitHub Build
Dec 21, 2024
7ee46da
I am Tyler Durden
DiegoLHendrix Jan 3, 2025
92d8cc7
Applied Formatting Changes During GitHub Build
Jan 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions include/core/io/CANOpenMacros.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,12 @@
}, \
{ \
/* SDO Server Request COBID */ \
.Key = CO_KEY(0x1200, 0x01, CO_OBJ__N__R_), \
.Key = CO_KEY(0x1200, 0x01, CO_OBJ_DN__R_), \
.Type = CO_TUNSIGNED32, \
.Data = (CO_DATA) CO_COBID_SDO_REQUEST(), \
}, \
{ /* SDO Server Response COBID */ \
.Key = CO_KEY(0x1200, 0x02, CO_OBJ__N__R_), \
.Key = CO_KEY(0x1200, 0x02, CO_OBJ_DN__R_), \
.Type = CO_TUNSIGNED32, \
.Data = (CO_DATA) CO_COBID_SDO_RESPONSE(), \
}
Expand Down
38 changes: 38 additions & 0 deletions include/core/io/CANopen.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
#include <core/dev/Timer.hpp>
#include <core/io/CAN.hpp>
#include <core/io/types/CANMessage.hpp>
#include <core/utils/log.hpp>
#include <core/utils/types/FixedQueue.hpp>

// Allows for resizable CANOpen queue if needed
#ifndef CANOPEN_QUEUE_SIZE
#define CANOPEN_QUEUE_SIZE 150
#endif
namespace log = core::log;
Comment on lines +16 to +23
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to add logging, do it in the cpp file, not the hpp file

Suggested change
#include <core/utils/log.hpp>
#include <core/utils/types/FixedQueue.hpp>
// Allows for resizable CANOpen queue if needed
#ifndef CANOPEN_QUEUE_SIZE
#define CANOPEN_QUEUE_SIZE 150
#endif
namespace log = core::log;
#include <core/utils/types/FixedQueue.hpp>
// Allows for resizable CANOpen queue if needed
#ifndef CANOPEN_QUEUE_SIZE
#define CANOPEN_QUEUE_SIZE 150
#endif


namespace core::io {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because these are just free functions, we should consider wrapping them in another namespace layer, so it's easier to tell where they're coming from. For example, core::io::canopen

@mjh9585 thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would agree with that. It will be a breaking change but can be rolled into a 2.0 release as one of the major changes.

Copy link
Contributor

@mjh9585 mjh9585 Feb 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now you can leave this.


Expand Down Expand Up @@ -94,6 +96,42 @@ void initializeCANopenNode(CO_NODE* canNode, CANDevice* canDevice, CO_IF_DRV* ca
*/
void processCANopenNode(CO_NODE* canNode);

/**
* This function sets up and starts an SDO download (write) request to transfer data
* to the specified object dictionary entry on the target CANopen node.
*
* @param node[in] Reference to the CANopen node object
* @param data[in] Pointer to the data buffer that holds the data to send
* @param size[in] Size of the data to transfer in bytes
* @param entry[in] Object dictionary entry (index + subindex) to write to
* @param AppCallback[in] Callback function when the transfer completes
* @return CO_ERR[out] Returns the result of the transfer operation
*/
CO_ERR SDOTransfer(CO_NODE& node, uint8_t* data, uint8_t size, uint32_t entry,
void (*AppCallback)(CO_CSDO* csdo, uint16_t index, uint8_t size, uint32_t entry));

/**
* This function starts an SDO upload (read) request to fetch data from the specified
* object dictionary entry on the target CANopen node
*
* @param node[in] Reference to the CANopen node object
* @param data[in] Pointer to the buffer where received data will be stored
* @param size[in] Size of the buffer provided to receive data
* @param entry[in] Object dictionary entry (index + subindex) to read from
* @param AppCallback[in] Callback function when the receive completes
* @return CO_ERR[out] Returns the result of the receive operation
*/
CO_ERR SDOReceive(CO_NODE& node, uint8_t* data, uint8_t size, uint32_t entry,
void (*AppCallback)(CO_CSDO* csdo, uint16_t index, uint8_t size, uint32_t entry));

/**
* This function assigns the user-provided callback function and AppContext to be
* used when an SDO operation completes
*
* @param AppCallback[in] Pointer to the callback function to register
* @param AppContext[in] Context to be passed to the callback
*/
void registerCallBack(void (*AppCallback)(CO_CSDO* csdo, uint16_t index, uint8_t sub, uint32_t code), void* AppContext);
} // namespace core::io

#endif
1 change: 1 addition & 0 deletions samples/canopen/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
add_subdirectory(canopen_rpdo)
add_subdirectory(canopen_sample)
add_subdirectory(canopen_sdo)
add_subdirectory(canopen_tpdo)
15 changes: 10 additions & 5 deletions samples/canopen/canopen_rpdo/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <core/io/UART.hpp>
#include <core/io/types/CANMessage.hpp>
#include <core/manager.hpp>
#include <core/utils/log.hpp>
#include <core/utils/time.hpp>
#include <core/utils/types/FixedQueue.hpp>

Expand All @@ -19,6 +20,7 @@
namespace io = core::io;
namespace dev = core::dev;
namespace time = core::time;
namespace log = core::log;

///////////////////////////////////////////////////////////////////////////////
// EVT-core CAN callback and CAN setup. This will include logic to set
Expand All @@ -36,20 +38,21 @@ namespace time = core::time;
* @param message[in] The passed in CAN message that was read.
*/

io::UART& uart = io::getUART<io::Pin::UART_TX, io::Pin::UART_RX>(9600);

// create a can interrupt handler
void canInterrupt(io::CANMessage& message, void* priv) {
auto* queue = (core::types::FixedQueue<CANOPEN_QUEUE_SIZE, io::CANMessage>*) priv;

// print out raw received data
uart.printf("Got RAW message from %X of length %d with data: ", message.getId(), message.getDataLength());
log::LOGGER.log(log::Logger::LogLevel::INFO,
"Got RAW message from %X of length %d with data: ",
message.getId(),
message.getDataLength());
uint8_t* data = message.getPayload();
for (int i = 0; i < message.getDataLength(); i++) {
uart.printf("%X ", *data);
log::LOGGER.log(log::Logger::LogLevel::INFO, "%X ", *data);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of logging each byte, build a string of the data and print 1 log message.

data++;
}
uart.printf("\r\n");
log::LOGGER.log(log::Logger::LogLevel::INFO, "\r\n");

if (queue != nullptr)
queue->append(message);
Expand All @@ -59,6 +62,8 @@ int main() {
// Initialize system
core::platform::init();

io::UART& uart = io::getUART<io::Pin::UART_TX, io::Pin::UART_RX>(9600);

// Initialize the timer
dev::Timer& timer = dev::getTimer<dev::MCUTimer::Timer2>(100);

Expand Down
4 changes: 4 additions & 0 deletions samples/canopen/canopen_sdo/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
include(../../../cmake/evt-core_build.cmake)

set( SAMPLE_SOURCES main.cpp SDOCanNode.cpp)
make_exe(canopen_sdo "${SAMPLE_SOURCES}")
95 changes: 95 additions & 0 deletions samples/canopen/canopen_sdo/SDOCanNode.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#include "SDOCanNode.hpp"
#include <core/io/CANopen.hpp>
#include <cstdio>

SDOCanNode::SDOCanNode() {
sampleDataA = 0;
sampleDataB = 0;
transferBuffArray[0] = 0;
transferBuffArray[1] = 0;
}

void SDOCanNode::SdoReceiveCallback(CO_CSDO* csdo, uint16_t index, uint8_t sub, uint32_t code) {
char messageString[50];
if (code == 0) {
/* read data is available in 'readValue' */
snprintf(&messageString[0], 25, "Value received %x, %x\r\n", csdo->Tfer.Buf[0], csdo->Tfer.Buf[1]);
} else {
/* a timeout or abort is detected during SDO transfer */
snprintf(&messageString[0], 25, "SDO receive callback don goofed 0x%x\r\n", code);
}

log::LOGGER.log(log::Logger::LogLevel::DEBUG, "SDO Receive Operation: \r\n\t%s\r\n", messageString);
}

void SDOCanNode::SdoTransferCallback(CO_CSDO* csdo, uint16_t index, uint8_t sub, uint32_t code) {
char messageString[50];
if (code == 0) {
/* read data is available in 'readValue' */
snprintf(&messageString[0], 25, "Value transferred %x, %x\r\n", csdo->Tfer.Buf[0], csdo->Tfer.Buf[1]);
} else {
/* a timeout or abort is detected during SDO transfer */
snprintf(&messageString[0], 25, "SDO transfer callback don goofed 0x%x\r\n", code);
}

log::LOGGER.log(log::Logger::LogLevel::DEBUG, "SDO Transfer Operation: \r\n\t%s\r\n", messageString);
}

void SDOCanNode::transferData(CO_NODE& node) {
/* Increment the first element of transferBuffArray by 1. */
transferBuffArray[0]++;
/* Set the second element of transferBuffArray to twice the new value of the first element. */
transferBuffArray[1] = transferBuffArray[0] * 2;

/*
* Initiates an SDO transfer for the specified node using the provided
* transfer buffer array. Targets the object dictionary entry at index 0x2100,
* sub-index 0x02. Registers and executes the SDOTransferCallback function upon completion.
*/
CO_ERR err = core::io::SDOTransfer(node, transferBuffArray, 2, CO_DEV(0x2100, 0x02), SdoTransferCallback);

/* Check if the SDO transfer was successfully started. */
if (err == CO_ERR_NONE) {
/* Transfer is started successfully */
log::LOGGER.log(log::Logger::LogLevel::INFO, "SDOTransfer Sent Request");

/* Note: don't use the 'readValue' until transfer is finished! */
} else {
/* Unable to start the SDO transfer */
log::LOGGER.log(log::Logger::LogLevel::ERROR, "SDOTransfer Request Error");
}
}

void SDOCanNode::receiveData(CO_NODE& node) {
static uint8_t receiveBuffArray[1];

/*
* Initiates an SDO receive operation for the specified node, reading data into
* the provided receive buffer array. Targets the object dictionary entry at
* index 0x2100, sub-index 0x01. Registers and executes the SDOReceiveCallback function upon completion.
*/
CO_ERR err = core::io::SDOReceive(node, receiveBuffArray, 1, CO_DEV(0x2100, 0x01), SdoReceiveCallback);

/* Check if the SDO receive operation was successfully started. */
if (err == CO_ERR_NONE) {
/* Transfer is started successfully */
log::LOGGER.log(log::Logger::LogLevel::INFO, "SDOReceive Sent Request");

/* Note: don't use the 'readValue' until transfer is finished! */
} else {
/* Unable to start the SDO transfer */
log::LOGGER.log(log::Logger::LogLevel::ERROR, "SDOReceive Request Error");
}
}

CO_OBJ_T* SDOCanNode::getObjectDictionary() {
return &objectDictionary[0];
}

uint8_t SDOCanNode::getNumElements() {
return OBJECT_DICTIONARY_SIZE;
}

uint8_t SDOCanNode::getNodeID() {
return NODE_ID;
}
147 changes: 147 additions & 0 deletions samples/canopen/canopen_sdo/SDOCanNode.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#include <core/utils/log.hpp>
#include <cstdint>

#include <co_core.h>
#include <core/io/CANDevice.hpp>
#include <core/io/CANOpenMacros.hpp>

/**
* Representation of the CAN node. Handles constructing the object
* dictionary and other baseline settings. The idea is that each "board"
* will have a specific object dictionary associated with it. The object
* dictionary itself will also need to have information on "data of interest".
* For example, a temperature management system may to expose water pump
* flow rate in the object dictionary.
*/

namespace log = core::log;

class SDOCanNode : public CANDevice {
public:
SDOCanNode();

/**
* The application-specific callback function for finalizing an SDO receive operation.
* @param csdo[in] is the client-SDO object.
* @param index[in] is the object dictionary index.
* @param sub[in] is the object dictionary subindex.
* @param code[in] indicates the completion status of the operation (0 for success, error code otherwise).
*/
static void SdoReceiveCallback(CO_CSDO* csdo, uint16_t index, uint8_t sub, uint32_t code);

/**
* The application-specific callback function for finalizing an SDO transfer operation.
* @param csdo[in] is the client-SDO object.
* @param index[in] is the object dictionary index.
* @param sub[in] is the object dictionary subindex.
* @param code[in] indicates the completion status of the operation (0 for success, error code otherwise).
*/
static void SdoTransferCallback(CO_CSDO* csdo, uint16_t index, uint8_t sub, uint32_t code);

/**
* Update Object Dictionary entry
*
* @param node[in] The canopen node to write to
*/
void transferData(CO_NODE& node);

/**
* Read Object Dictionary entry
*
* @param node[in] The canopen node to read from
*/
void receiveData(CO_NODE& node);

/**
* Get a pointer to the start of the object dictionary
*
* @return Pointer to the start of the object dictionary
*/
CO_OBJ_T* getObjectDictionary() override;

/**
* Get the number of elements in the object dictionary.
*
* @return The number of elements in the object dictionary
*/
uint8_t getNumElements() override;

/**
* Get the device's node ID
*
* @return The node ID of the can device.
*/
uint8_t getNodeID() override;

/**
* Get the device's node ID
*
* @return The node ID of the can device.
*/
static constexpr uint8_t NODE_ID = 2;

private:
/**
* This sample data will be exposed over CAN through the object
* dictionary. The address of the variable will be included in the
* object dictionary and can be updated via SDO via a CANopen client.
* This device will then broadcast the value via a triggered PDO.
*/
uint8_t sampleDataA;
uint16_t sampleDataB;

uint8_t transferBuffArray[2]{};

/**
* Have to know the size of the object dictionary for initialization
* process.
*/
static constexpr uint8_t OBJECT_DICTIONARY_SIZE = 24;

/**
* The object dictionary itself. Will be populated by this object during
* construction.
*
* The plus one is for the special "end of dictionary" marker.
*/
CO_OBJ_T objectDictionary[OBJECT_DICTIONARY_SIZE + 1] = {
MANDATORY_IDENTIFICATION_ENTRIES_1000_1014,
HEARTBEAT_PRODUCER_1017(2000),
IDENTITY_OBJECT_1018,
SDO_CONFIGURATION_1200,

{
/* Communication Object SDO Server */
.Key = CO_KEY(0x1280, 0x00, CO_OBJ_D___R_),
.Type = CO_TUNSIGNED32,
.Data = (CO_DATA) 0x03,
},
{
/* SDO Server Request COBID */
.Key = CO_KEY(0x1280, 0x01, CO_OBJ_D___R_),
.Type = CO_TUNSIGNED32,
.Data = (CO_DATA) CO_COBID_SDO_REQUEST(),
},
{
/* SDO Server Response COBID */
.Key = CO_KEY(0x1280, 0x02, CO_OBJ_D___R_),
.Type = CO_TUNSIGNED32,
.Data = (CO_DATA) CO_COBID_SDO_RESPONSE(),
},
{
/* Node ID of Server */
.Key = CO_KEY(0x1280, 0x03, CO_OBJ_D___R_),
.Type = CO_TUNSIGNED8,
.Data = (CO_DATA) 1,
Comment on lines +113 to +135
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great to have macros for these

},

// User defined data, this will be where we put elements that can be
// accessed via SDO and depending on configuration PDO
DATA_LINK_START_KEY_21XX(0, 0x02),
DATA_LINK_21XX(0x00, 0x01, CO_TUNSIGNED8, &sampleDataA),
DATA_LINK_21XX(0x00, 0x02, CO_TUNSIGNED16, &sampleDataB),

// End of dictionary marker
CO_OBJ_DICT_ENDMARK,
};
};
Loading