diff --git a/.gitignore b/.gitignore index f2185fb..7930b34 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ CMakeSettings.json third-party/concurrentqueue/benchmarks third-party/concurrentqueue/tests *.bak -.vs \ No newline at end of file +.vs +.cache diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ad118a..94d439c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -182,6 +182,7 @@ set(SRC_FILES communication/packet/ethphyregpacket.cpp communication/packet/logicaldiskinfopacket.cpp communication/packet/wivicommandpacket.cpp + communication/packet/i2cpacket.cpp communication/packet/scriptstatuspacket.cpp communication/decoder.cpp communication/encoder.cpp @@ -389,6 +390,7 @@ if(LIBICSNEO_BUILD_TESTS) test/diskdriverwritetest.cpp test/eventmanagertest.cpp test/ethernetpacketizertest.cpp + test/i2cencoderdecodertest.cpp ) target_link_libraries(libicsneo-tests gtest gtest_main) diff --git a/communication/decoder.cpp b/communication/decoder.cpp index ba2bfef..cff8a58 100644 --- a/communication/decoder.cpp +++ b/communication/decoder.cpp @@ -10,6 +10,7 @@ #include "icsneo/communication/message/scriptstatusmessage.h" #include "icsneo/communication/message/a2bmessage.h" #include "icsneo/communication/message/flexray/control/flexraycontrolmessage.h" +#include "icsneo/communication/message/i2cmessage.h" #include "icsneo/communication/command.h" #include "icsneo/device/device.h" #include "icsneo/communication/packet/canpacket.h" @@ -21,6 +22,7 @@ #include "icsneo/communication/packet/ethphyregpacket.h" #include "icsneo/communication/packet/logicaldiskinfopacket.h" #include "icsneo/communication/packet/wivicommandpacket.h" +#include "icsneo/communication/packet/i2cpacket.h" #include "icsneo/communication/packet/scriptstatuspacket.h" #include @@ -122,6 +124,20 @@ bool Decoder::decode(std::shared_ptr& result, const std::shared_ptrnetwork; return true; } + case Network::Type::I2C: { + if(packet->data.size() < sizeof(HardwareI2CPacket)) { + report(APIEvent::Type::PacketDecodingError, APIEvent::Severity::Error); + return false; + } + + result = HardwareI2CPacket::DecodeToMessage(packet->data); + if(!result) { + report(APIEvent::Type::PacketDecodingError, APIEvent::Severity::Error); + return false; //malformed packet indicated by a nullptr return + } + + return true; + } case Network::Type::Internal: { switch(packet->network.getNetID()) { case Network::NetID::Reset_Status: { diff --git a/communication/encoder.cpp b/communication/encoder.cpp index 986e71c..b7da23c 100644 --- a/communication/encoder.cpp +++ b/communication/encoder.cpp @@ -6,6 +6,8 @@ #include "icsneo/communication/packet/canpacket.h" #include "icsneo/communication/packet/ethphyregpacket.h" #include "icsneo/communication/message/ethphymessage.h" +#include "icsneo/communication/packet/i2cpacket.h" +#include "icsneo/communication/message/i2cmessage.h" using namespace icsneo; @@ -68,6 +70,18 @@ bool Encoder::encode(const Packetizer& packetizer, std::vector& result, // packets to the device. This function just encodes them back to back into `result` return HardwareISO9141Packet::EncodeFromMessage(*isomsg, result, report, packetizer); } // End of Network::Type::ISO9141 + case Network::Type::I2C: { + auto i2cmsg = std::dynamic_pointer_cast(message); + if(!i2cmsg) { + report(APIEvent::Type::MessageFormattingError, APIEvent::Severity::Error); + return false; + } + buffer = &result; + if(!HardwareI2CPacket::EncodeFromMessage(*i2cmsg, result, report)) { + return false; + } + break; + } default: report(APIEvent::Type::UnexpectedNetworkType, APIEvent::Severity::Error); return false; diff --git a/communication/packet/i2cpacket.cpp b/communication/packet/i2cpacket.cpp new file mode 100644 index 0000000..4d0d262 --- /dev/null +++ b/communication/packet/i2cpacket.cpp @@ -0,0 +1,83 @@ +#include "icsneo/communication/packet/i2cpacket.h" + +namespace icsneo +{ + std::shared_ptr HardwareI2CPacket::DecodeToMessage(const std::vector& bytestream) + { + auto msg = std::make_shared(); + const I2CHeader* packet = reinterpret_cast(bytestream.data()); + const size_t numPayloadBytes = packet->length; + const size_t numControlBytes = packet->CoreMiniBitsI2C.CBLen; + const size_t numDataBytes = numPayloadBytes - numControlBytes; + if( (numPayloadBytes == 0) || (numDataBytes > I2CMaxLength) || + (sizeof(I2CHeader) != (bytestream.size() - numPayloadBytes)) ) + { return nullptr; } + + msg->network = Network::GetNetIDFromCoreMiniNetwork(static_cast(packet->networkID)); + msg->address = (packet->CoreMiniBitsI2C.ID & 0x3FFu); + msg->deviceMode = static_cast(packet->CoreMiniBitsI2C.CT); + msg->direction = static_cast(packet->CoreMiniBitsI2C.DIR); + + msg->isExtendedID = static_cast(packet->CoreMiniBitsI2C.EID & 0x01u); + msg->isTXMsg = static_cast(packet->CoreMiniBitsI2C.TXMsg & 0x01u); + msg->txTimeout = static_cast(packet->CoreMiniBitsI2C.TXTimeout & 0x01u); + msg->txNack = static_cast(packet->CoreMiniBitsI2C.TXNack & 0x01u); + msg->txAborted = static_cast(packet->CoreMiniBitsI2C.TXAborted & 0x01u); + msg->txLostArb = static_cast(packet->CoreMiniBitsI2C.TXLostArb & 0x01u); + msg->txError = static_cast(packet->CoreMiniBitsI2C.TXError & 0x01u); + //We don't care about 0xTRB0Dx in this case... + //copy 0xTRB0STAT even though we likely won't use it either + msg->stats = packet->stats; + msg->timestamp = (packet->timestamp & (0x7FFFFFFFFFFFFFFFull)); + + //The device will combine the 'control' bytes and data bytes into one payload + //The control bytes will always come before the data + auto cbStart = bytestream.begin() + sizeof(I2CHeader); + auto dataStart = cbStart + numControlBytes; + std::copy(cbStart, dataStart, std::back_inserter(msg->controlBytes)); + std::copy(dataStart, bytestream.end(), std::back_inserter(msg->dataBytes)); + + return msg; + } + + bool HardwareI2CPacket::EncodeFromMessage(const I2CMessage& message, std::vector& bytestream, const device_eventhandler_t& report) + { + const size_t numControlBytes = message.controlBytes.size(); + const size_t numDataBytes = message.dataBytes.size(); + if(I2CMaxLength < numDataBytes) + { + report(APIEvent::Type::I2CMessageExceedsMaxLength, APIEvent::Severity::Error); + return false; + } + if(message.controlBytes.empty() || message.dataBytes.empty()) + { + //You'll need to provide a target R/W register in controlBytes + //alternatively, you're expecting to read without providing a dataBytes payload + report(APIEvent::Type::RequiredParameterNull, APIEvent::Severity::Error); + return false; + } + + bytestream.push_back(static_cast(numControlBytes & 0xFFu)); + bytestream.push_back(static_cast((numControlBytes) >> 8) & 0xFFu); + bytestream.push_back(static_cast(numDataBytes & 0xFFu)); + bytestream.push_back(static_cast((numDataBytes) >> 8) & 0xFFu); + bytestream.push_back(static_cast((message.stats) >> 8) & 0xFFu); + bytestream.push_back(static_cast(message.stats & 0xFFu)); + + if(message.isExtendedID) + { + bytestream.push_back(static_cast(message.address & 0xFFu)); + bytestream.push_back(static_cast(((message.address) >> 8) & 0x03u) | 0x04u); + } else { + bytestream.push_back(static_cast(message.address & 0xFFu)); + bytestream.push_back(static_cast(0x00u)); + } + + if(I2CMessage::Direction::Read == message.direction) + { bytestream.back() |= static_cast(0x10u); } + + std::copy(message.controlBytes.begin(), message.controlBytes.end(), std::back_inserter(bytestream)); + std::copy(message.dataBytes.begin(), message.dataBytes.end(), std::back_inserter(bytestream)); + return true; + } +} \ No newline at end of file diff --git a/include/icsneo/api/event.h b/include/icsneo/api/event.h index 054e89f..f7ad670 100644 --- a/include/icsneo/api/event.h +++ b/include/icsneo/api/event.h @@ -89,6 +89,7 @@ public: AtomicOperationCompletedNonatomically = 0x2035, WiVIStackRefreshFailed = 0x2036, WiVIUploadStackOverflow = 0x2037, + I2CMessageExceedsMaxLength = 0x2038, // Transport Events FailedToRead = 0x3000, diff --git a/include/icsneo/communication/message/i2cmessage.h b/include/icsneo/communication/message/i2cmessage.h new file mode 100644 index 0000000..86c3058 --- /dev/null +++ b/include/icsneo/communication/message/i2cmessage.h @@ -0,0 +1,48 @@ +#ifndef __I2CMESSAGE_H_ +#define __I2CMESSAGE_H_ + +#ifdef __cplusplus + +#include "icsneo/communication/message/message.h" +#include + +namespace icsneo { + +class I2CMessage : public Frame { +public: + enum class DeviceMode : uint8_t { + Target = 0, + Controller = 1 + }; + + enum class Direction : uint8_t { + Write = 0, + Read = 1 + }; + + bool isExtendedID = false; + bool isTXMsg = false; + bool txError = false; + bool txLostArb = false; + bool txAborted = false; + bool txNack = false; + bool txTimeout = false; + uint16_t stats = static_cast(0x0000u); + uint16_t address; + Direction direction; + DeviceMode deviceMode; + + //Must contain the target register address to read or write + std::vector controlBytes; + + //The device expects a dataBytes payload even if you're reading + //In the case of a read these bytes aren't interesting, but they have to be there + //Add bytes to write, or the same number of junk bytes you expect the device send back + std::vector dataBytes; +}; + +} + +#endif // __cplusplus + +#endif diff --git a/include/icsneo/communication/packet/i2cpacket.h b/include/icsneo/communication/packet/i2cpacket.h new file mode 100644 index 0000000..cd79945 --- /dev/null +++ b/include/icsneo/communication/packet/i2cpacket.h @@ -0,0 +1,61 @@ +#ifndef __I2CPACKET_H__ +#define __I2CPACKET_H__ + +#ifdef __cplusplus + +#include "icsneo/communication/message/i2cmessage.h" +#include "icsneo/api/eventmanager.h" +#include "icsneo/communication/packetizer.h" +#include "icsneo/communication/network.h" +#include +#include + +namespace icsneo { + +static constexpr size_t I2CMaxLength = 1024u; + +#pragma pack(push, 2) + +struct I2CHeader { + struct { + // C1xI2C + uint16_t ID : 10;// i2c address, 7-bit or 10-bit + uint16_t EID : 1;// using extended addressing, i.e. 10-bit + uint16_t CT : 1;// Controller/Target + uint16_t DIR : 1;// read/write + uint16_t RESERVED_0 : 3; + + // C2xI2C + uint16_t TXMsg: 1; + uint16_t CBLen: 11; + uint16_t RESERVED_1: 4; + + // C3xI2C + uint16_t TXTimeout : 1; + uint16_t TXNack : 1; + uint16_t TXAborted : 1; + uint16_t TXLostArb : 1; + uint16_t TXError : 1; + uint16_t RESERVED_2: 11; + } CoreMiniBitsI2C; + + uint8_t coreMiniMessageData[8]; + uint16_t stats; + + uint64_t timestamp; + + uint16_t networkID; + uint16_t length; +}; + +#pragma pack(pop) + +struct HardwareI2CPacket { + static std::shared_ptr DecodeToMessage(const std::vector& bytestream); + static bool EncodeFromMessage(const I2CMessage& message, std::vector& bytestream, const device_eventhandler_t& report); +}; +} + +#endif //_cplusplus + +#endif \ No newline at end of file diff --git a/include/icsneo/device/tree/rada2b/rada2b.h b/include/icsneo/device/tree/rada2b/rada2b.h index a6409e5..1777c23 100644 --- a/include/icsneo/device/tree/rada2b/rada2b.h +++ b/include/icsneo/device/tree/rada2b/rada2b.h @@ -28,7 +28,10 @@ public: Network::NetID::LIN, Network::NetID::A2B1, - Network::NetID::A2B2 + Network::NetID::A2B2, + + Network::NetID::I2C, + Network::NetID::I2C2 }; return supportedNetworks; } diff --git a/include/icsneo/device/tree/radgigastar/radgigastar.h b/include/icsneo/device/tree/radgigastar/radgigastar.h index d126f2b..6f6f747 100644 --- a/include/icsneo/device/tree/radgigastar/radgigastar.h +++ b/include/icsneo/device/tree/radgigastar/radgigastar.h @@ -59,7 +59,12 @@ protected: Network::NetID::LIN, - Network::NetID::FlexRay + Network::NetID::FlexRay, + + Network::NetID::I2C, + Network::NetID::I2C2, + Network::NetID::I2C3, + Network::NetID::I2C4, }; rxNetworks.insert(rxNetworks.end(), supportedRxNetworks.begin(), supportedRxNetworks.end()); } diff --git a/include/icsneo/icsneocpp.h b/include/icsneo/icsneocpp.h index 1f33c26..7961a0f 100644 --- a/include/icsneo/icsneocpp.h +++ b/include/icsneo/icsneocpp.h @@ -16,6 +16,7 @@ #include "icsneo/communication/message/iso9141message.h" #include "icsneo/communication/message/canerrorcountmessage.h" #include "icsneo/communication/message/ethphymessage.h" +#include "icsneo/communication/message/i2cmessage.h" #include "icsneo/communication/message/a2bmessage.h" namespace icsneo { diff --git a/test/i2cencoderdecodertest.cpp b/test/i2cencoderdecodertest.cpp new file mode 100644 index 0000000..857c593 --- /dev/null +++ b/test/i2cencoderdecodertest.cpp @@ -0,0 +1,90 @@ +#include "icsneo/icsneocpp.h" +#include "icsneo/communication/encoder.h" +#include "icsneo/communication/packet/i2cpacket.h" +#include "icsneo/communication/message/i2cmessage.h" +#include "icsneo/communication/packetizer.h" +#include "icsneo/api/eventmanager.h" +#include "gtest/gtest.h" +#include + +using namespace icsneo; + +class I2CEncoderDecoderTest : public ::testing::Test { +protected: + void SetUp() override { + report = [](APIEvent::Type, APIEvent::Severity) { + // Unless caught by the test, the packetizer should not throw errors + EXPECT_TRUE(false); + }; + packetizer.emplace([this](APIEvent::Type t, APIEvent::Severity s) { + report(t, s); + }); + packetEncoder.emplace([this](APIEvent::Type t, APIEvent::Severity s) { + report(t, s); + }); + packetDecoder.emplace([this](APIEvent::Type t, APIEvent::Severity s) { + report(t, s); + }); + } + device_eventhandler_t report; + std::optional packetEncoder; + std::optional packetizer; + std::optional packetDecoder; + //Read request to the device + //Control length 1, control bytes 0x12 (I2C register to read from) + //data length 1: blank bytes padded in that the device will fill in the reply + std::vector testBytes = + {0xaa, 0x0c, 0x11, 0x00, 0x58, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x00, 0x01, 0x68, 0x10, 0x12, 0x00}; + + std::vector recvBytes = + {0xaa, 0x0c, 0x24,0x00, 0x58, 0x00, 0x68, 0x18, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x97, 0x29, + 0xe6, 0xfb, 0xc1, 0xfc, 0xb0, 0x80, 0x35, 0x00, + 0x02, 0x00, 0x12, 0x80}; +}; + +TEST_F(I2CEncoderDecoderTest, PacketEncoderTest) { + std::vector bytestream; + auto message = std::make_shared(); + message->network = icsneo::Network::NetID::I2C; + message->controlBytes.push_back(static_cast(0x12u)); //Product ID register address + message->dataBytes.push_back(static_cast(0x00u)); + message->address = 0x68u; //7 bit addressing, BASE_ADDR + message->stats = static_cast(0x0001u); + message->direction = I2CMessage::Direction::Read; + message->isTXMsg = true; + packetEncoder->encode(*packetizer, bytestream, message); + EXPECT_EQ(bytestream, testBytes); +} + +TEST_F(I2CEncoderDecoderTest, PacketDecoderTest) { + std::shared_ptr decodeMsg; + std::shared_ptr message = std::make_shared(); + + message->network = icsneo::Network::NetID::I2C; + message->controlBytes.push_back(static_cast(0x12u)); //Product ID register address + message->dataBytes.push_back(static_cast(0x80u)); + message->address = 0x68u; //7 bit addressing, BASE_ADDR + message->stats = static_cast(0x0002u); + message->direction = I2CMessage::Direction::Read; + message->deviceMode = I2CMessage::DeviceMode::Controller; + message->isTXMsg = true; + message->timestamp = static_cast(0xB0FCC1FBE62997); + + EXPECT_TRUE(packetizer->input(recvBytes)); + auto packets = packetizer->output(); + if(packets.empty()) { EXPECT_TRUE(false); } + EXPECT_TRUE(packetDecoder->decode(decodeMsg, packets.back())); + auto testMessage = std::dynamic_pointer_cast(decodeMsg); + EXPECT_EQ(message->network, testMessage->network); + EXPECT_EQ(message->controlBytes, testMessage->controlBytes); + EXPECT_EQ(message->dataBytes, testMessage->dataBytes); + EXPECT_EQ(message->address, testMessage->address); + EXPECT_EQ(message->stats, testMessage->stats); + EXPECT_EQ(message->direction, testMessage->direction); + EXPECT_EQ(message->deviceMode, testMessage->deviceMode); + EXPECT_EQ(message->isTXMsg, testMessage->isTXMsg); + EXPECT_EQ(message->timestamp, testMessage->timestamp); +}