Communication: Add SPI support

pull/76/merge
Yasser Yassine 2025-09-17 17:30:12 +00:00 committed by Kyle Schwarz
parent 6d82289864
commit 88d32128c9
13 changed files with 358 additions and 0 deletions

View File

@ -204,6 +204,7 @@ set(SRC_FILES
communication/packet/flexraypacket.cpp
communication/packet/canpacket.cpp
communication/packet/a2bpacket.cpp
communication/packet/spipacket.cpp
communication/packet/ethernetpacket.cpp
communication/packet/versionpacket.cpp
communication/packet/iso9141packet.cpp

View File

@ -31,6 +31,7 @@ pybind11_add_module(icsneopy
icsneopy/communication/message/mdiomessage.cpp
icsneopy/communication/message/gptpstatusmessage.cpp
icsneopy/communication/message/ethernetstatusmessage.cpp
icsneopy/communication/message/spimessage.cpp
icsneopy/communication/message/macsecmessage.cpp
icsneopy/communication/message/scriptstatusmessage.cpp
icsneopy/communication/message/callback/messagecallback.cpp

View File

@ -0,0 +1,24 @@
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/functional.h>
#include "icsneo/communication/message/spimessage.h"
namespace icsneo {
void init_spimessage(pybind11::module_& m) {
pybind11::class_<SPIMessage, std::shared_ptr<SPIMessage>, Frame> spiMessage(m, "SPIMessage");
pybind11::enum_<SPIMessage::Direction>(spiMessage, "Direction")
.value("Write", SPIMessage::Direction::Write)
.value("Read", SPIMessage::Direction::Read);
spiMessage
.def(pybind11::init())
.def_readwrite("direction", &SPIMessage::direction)
.def_readwrite("address", &SPIMessage::address)
.def_readwrite("mms", &SPIMessage::mms)
.def_readwrite("stats", &SPIMessage::stats)
.def_readwrite("payload", &SPIMessage::payload);
}
} // namespace icsneo

View File

@ -20,6 +20,7 @@ void init_linmessage(pybind11::module_&);
void init_tc10statusmessage(pybind11::module_&);
void init_gptpstatusmessage(pybind11::module_&);
void init_mdiomessage(pybind11::module_&);
void init_spimessage(pybind11::module_&);
void init_ethernetstatusmessage(pybind11::module_&);
void init_macsecmessage(pybind11::module_&);
void init_scriptstatusmessage(pybind11::module_&);
@ -55,6 +56,7 @@ PYBIND11_MODULE(icsneopy, m) {
init_ethernetstatusmessage(m);
init_macsecmessage(m);
init_scriptstatusmessage(m);
init_spimessage(m);
init_messagefilter(m);
init_messagecallback(m);
init_diskdriver(m);

View File

@ -43,6 +43,7 @@
#include "icsneo/communication/packet/genericbinarystatuspacket.h"
#include "icsneo/communication/packet/livedatapacket.h"
#include "icsneo/communication/packet/hardwareinfopacket.h"
#include "icsneo/communication/packet/spipacket.h"
#include <iostream>
@ -184,6 +185,19 @@ bool Decoder::decode(std::shared_ptr<Message>& result, const std::shared_ptr<Pac
msg.timestamp *= timestampResolution;
return true;
}
case Network::Type::SPI: {
result = HardwareSPIPacket::DecodeToMessage(packet->data);
if(!result) {
report(APIEvent::Type::PacketDecodingError, APIEvent::Severity::Error);
return false; // A nullptr was returned, the packet was not long enough to decode
}
SPIMessage& msg = *static_cast<SPIMessage*>(result.get());
msg.network = packet->network;
msg.timestamp *= timestampResolution;
return true;
}
case Network::Type::MDIO: {
result = HardwareMDIOPacket::DecodeToMessage(packet->data);

View File

@ -13,6 +13,7 @@
#include "icsneo/communication/packet/a2bpacket.h"
#include "icsneo/communication/packet/linpacket.h"
#include "icsneo/communication/packet/mdiopacket.h"
#include "icsneo/communication/packet/spipacket.h"
using namespace icsneo;
@ -133,6 +134,17 @@ bool Encoder::encode(const Packetizer& packetizer, std::vector<uint8_t>& result,
}
break;
} // End of Network::Type::MDIO
case Network::Type::SPI: {
auto msg = std::dynamic_pointer_cast<SPIMessage>(message);
if(!msg) {
report(APIEvent::Type::MessageFormattingError, APIEvent::Severity::Error);
return false; // The message was not a properly formed LiveDataMessage
}
if(!HardwareSPIPacket::EncodeFromMessage(*msg, result, report))
return false;
result = packetizer.packetWrap(result, false);
return true;
} // End of Network::Type::SPI
default:
report(APIEvent::Type::UnexpectedNetworkType, APIEvent::Severity::Error);
return false;

View File

@ -0,0 +1,82 @@
#include "icsneo/communication/packet/spipacket.h"
#include "icsneo/communication/command.h"
#include <cstring>
#include <vector>
using namespace icsneo;
static size_t SPISubHeaderLength = 5u;
std::shared_ptr<Message> HardwareSPIPacket::DecodeToMessage(const std::vector<uint8_t>& bytestream) {
if(bytestream.size() < sizeof(HardwareSPIPacket)) {
return nullptr;
}
const HardwareSPIPacket* packet = (const HardwareSPIPacket*)bytestream.data();
size_t totalPackedLength = static_cast<size_t>(bytestream.size()) - sizeof(HardwareSPIPacket); // First 28 bytes are message header.
if(totalPackedLength < SPISubHeaderLength) {
return nullptr;
}
const uint8_t* bytes = bytestream.data() + sizeof(HardwareSPIPacket);
std::shared_ptr<SPIMessage> msg = std::make_shared<SPIMessage>();
msg->direction = static_cast<SPIMessage::Direction>(bytes[0]);
msg->address = *reinterpret_cast<const uint16_t*>(&bytes[1]);
msg->mms = bytes[3];
msg->stats = packet->stats;
msg->timestamp = packet->timestamp.TS;
size_t numWords = (totalPackedLength - SPISubHeaderLength) / 4;
msg->payload.reserve(numWords);
for(size_t offset = SPISubHeaderLength; offset < totalPackedLength; offset += 4) {
msg->payload.push_back(*reinterpret_cast<const uint32_t*>(bytes + offset));
}
return msg;
}
bool HardwareSPIPacket::EncodeFromMessage(const SPIMessage& message, std::vector<uint8_t>& bytestream, const device_eventhandler_t& /*report*/) {
// Payload length is everything excluding cmdHeader (note at the beginning there is an offset of 2)
uint16_t payloadLength = static_cast<uint16_t>(
2 +
sizeof(HardwareSPIPacket) +
SPISubHeaderLength +
message.payload.size() * sizeof(uint32_t)
);
if(payloadLength % 2) {
// Pad payload to even number
payloadLength++;
}
// +1 for AA, another +1 for firmware nuance
uint16_t fullSize = 1 + sizeof(ExtendedCommandHeader) + payloadLength + 1;
uint16_t unwrappedSize = sizeof(ExtendedCommandHeader) + payloadLength; // fullSize without AA and firmware nuance
bytestream.resize(unwrappedSize, 0);
uint32_t offset = 0;
auto* cmdHeader = reinterpret_cast<ExtendedCommandHeader*>(bytestream.data() + offset);
cmdHeader->netid = static_cast<uint8_t>(Network::NetID::Main51);
cmdHeader->fullLength = fullSize;
cmdHeader->command = static_cast<uint8_t>(Command::Extended);
cmdHeader->extendedCommand = static_cast<uint16_t>(ExtendedCommand::TransmitCoreminiMessage);
cmdHeader->payloadLength = payloadLength;
offset += sizeof(ExtendedCommandHeader) + 2; // Offset of 2 between header and packet
auto* packet = reinterpret_cast<HardwareSPIPacket*>(bytestream.data() + offset);
packet->header.frameLength = static_cast<uint16_t>(SPISubHeaderLength + message.payload.size() * sizeof(uint32_t));
packet->networkID = static_cast<uint16_t>(message.network.getNetID());
packet->length = packet->header.frameLength;
packet->timestamp.IsExtended = 1;
offset += sizeof(HardwareSPIPacket);
// Write the sub header details
bytestream[offset++] = static_cast<uint8_t>(message.direction);
bytestream[offset++] = static_cast<uint8_t>(message.address & 0xFF);
bytestream[offset++] = static_cast<uint8_t>((message.address >> 8) & 0xFF);
bytestream[offset++] = static_cast<uint8_t>(message.mms);
bytestream[offset++] = static_cast<uint8_t>(message.payload.size());
// Write the words
for(uint32_t word : message.payload) {
*reinterpret_cast<uint32_t*>(bytestream.data() + offset) = word;
offset += sizeof(uint32_t);
}
return true;
}

View File

@ -11,6 +11,7 @@ option(LIBICSNEO_BUILD_CPP_MDIO_EXAMPLE "Build the MDIO example." ON)
option(LIBICSNEO_BUILD_CPP_VSA_EXAMPLE "Build the VSA example." ON)
option(LIBICSNEO_BUILD_CPP_APP_ERROR_EXAMPLE "Build the app error example." ON)
option(LIBICSNEO_BUILD_CPP_FLEXRAY_EXAMPLE "Build the FlexRay example." ON)
option(LIBICSNEO_BUILD_CPP_SPI_EXAMPLE "Build the SPI example." ON)
if(LIBICSNEO_BUILD_C_INTERACTIVE_EXAMPLE)
add_subdirectory(c/interactive)
@ -63,3 +64,7 @@ endif()
if(LIBICSNEO_BUILD_CPP_FLEXRAY_EXAMPLE)
add_subdirectory(cpp/flexray)
endif()
if(LIBICSNEO_BUILD_CPP_SPI_EXAMPLE)
add_subdirectory(cpp/spi)
endif()

View File

@ -0,0 +1,2 @@
add_executable(libicsneocpp-spi src/spi.cpp)
target_link_libraries(libicsneocpp-spi icsneocpp)

View File

@ -0,0 +1,142 @@
/**
* libicsneo SPI message example
*
* Read a register on NetID::SPI_01 and display the result to stdout
*
* Usage: libicsneo-spi [deviceSerial] [address] [numWords]
*
* Arguments:
* deviceSerial: 6 character string for device serial
* address: 16 bit register address
* numWords: number of words to read
*/
#include <iostream>
#include <iomanip>
#include <thread>
#include <chrono>
#include <string_view>
#include <utility>
#include <tuple>
#include "icsneo/icsneocpp.h"
#include "icsneo/communication/message/spimessage.h"
static const std::string usage = "Usage: libicsneo-spi [deviceSerial] [address] [numWords]\n\n"
"Arguments:\n"
"deviceSerial: 6 character string for device serial\n"
"address: hex number representing 16 bit register number\n"
"numWords: number of words to read\n";
static std::tuple<std::string_view, uint16_t, uint16_t> parseArgs(std::vector<std::string_view>& args, bool& fail) {
if(args.size() != 4) {
std::cerr << "Invalid argument count" << std::endl;
std::cerr << usage;
fail = true;
return {};
}
std::string_view serial = args[1];
if(serial.size() != 6) {
std::cerr << "Invalid serial length" << std::endl;
std::cerr << usage;
fail = true;
return {};
}
char* endPtr;
uint16_t address = static_cast<uint16_t>(std::strtoul(args[2].data(), &endPtr, 16));
if(endPtr != (args[2].data() + args[2].size())) {
std::cerr << "Failed to convert address to hex" << std::endl;
std::cerr << usage;
fail = true;
return {};
}
uint16_t numWords = static_cast<uint16_t>(std::strtoul(args[3].data(), &endPtr, 16));
if(endPtr != (args[3].data() + args[3].size())) {
std::cerr << "Failed to convert numWords to decimal integer" << std::endl;
std::cerr << usage;
fail = true;
return {};
}
fail = false;
return {serial, address, numWords};
}
int main(int argc, const char** argv) {
std::vector<std::string_view> args(argv, argv + argc);
bool fail;
auto tup = parseArgs(args, fail);
if(fail) {
return -1;
}
auto serial = std::get<0>(tup);
auto address = std::get<1>(tup);
auto numWords = std::get<2>(tup);
std::shared_ptr<icsneo::Device> device = nullptr;
for(const auto& dev : icsneo::FindAllDevices()) {
if(dev->getSerial() == serial) {
device = dev;
break;
}
}
if(!device) {
std::cerr << "Failed to find device" << std::endl;
std::cerr << usage;
return -1;
}
if(!device->open()) {
std::cerr << "Failed to open device" << std::endl;
return -1;
}
if(!device->goOnline()) {
std::cerr << "Failed to go online" << std::endl;
return -1;
}
std::cout << "Reading register " << std::setfill('0') << std::setw(4) << std::right << std::hex << address << std::endl;
// Make message
std::shared_ptr<icsneo::SPIMessage> msg = std::make_shared<icsneo::SPIMessage>();
msg->network = icsneo::Network::NetID::SPI_01;
msg->direction = icsneo::SPIMessage::Direction::Read;
msg->address = address;
msg->payload.resize(numWords, 0); // Resize payload to desired length
// Add callback
bool receivedMessage = false;
auto handle = device->addMessageCallback(std::make_shared<icsneo::MessageCallback>([&](std::shared_ptr<icsneo::Message> msg) {
if(receivedMessage) {
return;
}
if(msg->type == icsneo::Message::Type::Frame) {
auto frame = std::dynamic_pointer_cast<icsneo::Frame>(msg);
if(!frame) {
return;
}
if(frame->network == icsneo::Network::NetID::SPI_01) {
auto spiMsg = std::dynamic_pointer_cast<icsneo::SPIMessage>(frame);
if(spiMsg->address == address) {
std::cout << "Received " << spiMsg->payload.size() << " words" << std::endl;
for(uint32_t word : spiMsg->payload) {
std::cout << std::setfill('0') << std::setw(8) << std::right << std::hex << word << ' ';
}
std::cout << std::endl;
receivedMessage = true;
}
}
}
}));
device->transmit(msg);
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
if(!receivedMessage) {
std::cout << "Did not receive response from device." << std::endl;
}
device->removeMessageCallback(handle);
return 0;
}

View File

@ -56,6 +56,7 @@ enum class ExtendedCommand : uint16_t {
GetComponentVersions = 0x001A,
Reboot = 0x001C,
SetRootFSEntryFlags = 0x0027,
TransmitCoreminiMessage = 0x0028,
GenericBinaryInfo = 0x0030,
LiveData = 0x0035,
RequestTC10Wake = 0x003D,

View File

@ -0,0 +1,30 @@
#ifndef __SPIMESSAGE_H_
#define __SPIMESSAGE_H_
#ifdef __cplusplus
#include "icsneo/communication/message/message.h"
#include <vector>
namespace icsneo {
class SPIMessage : public Frame {
public:
enum class Direction : uint8_t {
Read = 0,
Write = 1
};
Direction direction = Direction::Read;
uint16_t address = static_cast<uint16_t>(0x0000u);
uint8_t mms = static_cast<uint16_t>(0x0000u); // Memory Map Selector for ADI MAC Phy
uint16_t stats = static_cast<uint16_t>(0x0000u);
std::vector<uint32_t> payload;
};
}
#endif // __cplusplus
#endif

View File

@ -0,0 +1,42 @@
#ifndef __SPIPACKET_H__
#define __SPIPACKET_H__
#ifdef __cplusplus
#include "icsneo/api/eventmanager.h"
#include <cstdint>
#include <memory>
#include <optional>
#include "icsneo/communication/message/spimessage.h"
namespace icsneo {
typedef uint16_t icscm_bitfield;
#pragma pack(push, 2)
struct HardwareSPIPacket {
static std::shared_ptr<Message> DecodeToMessage(const std::vector<uint8_t>& bytestream);
static bool EncodeFromMessage(const SPIMessage& message, std::vector<uint8_t>& bytestream, const device_eventhandler_t& report);
struct {
uint16_t frameLength;
} header;
uint8_t offset[12];
uint16_t stats;
struct {
uint64_t TS : 60;
uint64_t : 3; // Reserved for future status bits
uint64_t IsExtended : 1;
} timestamp;
uint16_t networkID;
uint16_t length;
};
#pragma pack(pop)
}
#endif // __cplusplus
#endif