I2C: Network support

add-device-sharing
Kyle Johannes 2022-10-18 00:12:16 +00:00
parent 13ec2bca98
commit 9817887523
12 changed files with 328 additions and 3 deletions

1
.gitignore vendored
View File

@ -9,3 +9,4 @@ third-party/concurrentqueue/benchmarks
third-party/concurrentqueue/tests
*.bak
.vs
.cache

View File

@ -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)

View File

@ -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 <iostream>
@ -122,6 +124,20 @@ bool Decoder::decode(std::shared_ptr<Message>& result, const std::shared_ptr<Pac
iso.network = packet->network;
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: {

View File

@ -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<uint8_t>& 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<I2CMessage>(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;

View File

@ -0,0 +1,83 @@
#include "icsneo/communication/packet/i2cpacket.h"
namespace icsneo
{
std::shared_ptr<Message> HardwareI2CPacket::DecodeToMessage(const std::vector<uint8_t>& bytestream)
{
auto msg = std::make_shared<I2CMessage>();
const I2CHeader* packet = reinterpret_cast<const I2CHeader*>(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<Network::CoreMini>(packet->networkID));
msg->address = (packet->CoreMiniBitsI2C.ID & 0x3FFu);
msg->deviceMode = static_cast<I2CMessage::DeviceMode>(packet->CoreMiniBitsI2C.CT);
msg->direction = static_cast<I2CMessage::Direction>(packet->CoreMiniBitsI2C.DIR);
msg->isExtendedID = static_cast<bool>(packet->CoreMiniBitsI2C.EID & 0x01u);
msg->isTXMsg = static_cast<bool>(packet->CoreMiniBitsI2C.TXMsg & 0x01u);
msg->txTimeout = static_cast<bool>(packet->CoreMiniBitsI2C.TXTimeout & 0x01u);
msg->txNack = static_cast<bool>(packet->CoreMiniBitsI2C.TXNack & 0x01u);
msg->txAborted = static_cast<bool>(packet->CoreMiniBitsI2C.TXAborted & 0x01u);
msg->txLostArb = static_cast<bool>(packet->CoreMiniBitsI2C.TXLostArb & 0x01u);
msg->txError = static_cast<bool>(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<uint8_t>& 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<uint8_t>(numControlBytes & 0xFFu));
bytestream.push_back(static_cast<uint8_t>((numControlBytes) >> 8) & 0xFFu);
bytestream.push_back(static_cast<uint8_t>(numDataBytes & 0xFFu));
bytestream.push_back(static_cast<uint8_t>((numDataBytes) >> 8) & 0xFFu);
bytestream.push_back(static_cast<uint8_t>((message.stats) >> 8) & 0xFFu);
bytestream.push_back(static_cast<uint8_t>(message.stats & 0xFFu));
if(message.isExtendedID)
{
bytestream.push_back(static_cast<uint8_t>(message.address & 0xFFu));
bytestream.push_back(static_cast<uint8_t>(((message.address) >> 8) & 0x03u) | 0x04u);
} else {
bytestream.push_back(static_cast<uint8_t>(message.address & 0xFFu));
bytestream.push_back(static_cast<uint8_t>(0x00u));
}
if(I2CMessage::Direction::Read == message.direction)
{ bytestream.back() |= static_cast<uint8_t>(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;
}
}

View File

@ -89,6 +89,7 @@ public:
AtomicOperationCompletedNonatomically = 0x2035,
WiVIStackRefreshFailed = 0x2036,
WiVIUploadStackOverflow = 0x2037,
I2CMessageExceedsMaxLength = 0x2038,
// Transport Events
FailedToRead = 0x3000,

View File

@ -0,0 +1,48 @@
#ifndef __I2CMESSAGE_H_
#define __I2CMESSAGE_H_
#ifdef __cplusplus
#include "icsneo/communication/message/message.h"
#include <vector>
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<uint16_t>(0x0000u);
uint16_t address;
Direction direction;
DeviceMode deviceMode;
//Must contain the target register address to read or write
std::vector<uint8_t> 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<uint8_t> dataBytes;
};
}
#endif // __cplusplus
#endif

View File

@ -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 <cstdint>
#include <memory>
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<Message> DecodeToMessage(const std::vector<uint8_t>& bytestream);
static bool EncodeFromMessage(const I2CMessage& message, std::vector<uint8_t>& bytestream, const device_eventhandler_t& report);
};
}
#endif //_cplusplus
#endif

View File

@ -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;
}

View File

@ -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());
}

View File

@ -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 {

View File

@ -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 <vector>
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<Encoder> packetEncoder;
std::optional<Packetizer> packetizer;
std::optional<Decoder> 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<uint8_t> testBytes =
{0xaa, 0x0c, 0x11, 0x00, 0x58, 0x00, 0x01, 0x00,
0x01, 0x00, 0x00, 0x01, 0x68, 0x10, 0x12, 0x00};
std::vector<uint8_t> 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<uint8_t> bytestream;
auto message = std::make_shared<icsneo::I2CMessage>();
message->network = icsneo::Network::NetID::I2C;
message->controlBytes.push_back(static_cast<uint8_t>(0x12u)); //Product ID register address
message->dataBytes.push_back(static_cast<uint8_t>(0x00u));
message->address = 0x68u; //7 bit addressing, BASE_ADDR
message->stats = static_cast<uint16_t>(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<icsneo::Message> decodeMsg;
std::shared_ptr<icsneo::I2CMessage> message = std::make_shared<icsneo::I2CMessage>();
message->network = icsneo::Network::NetID::I2C;
message->controlBytes.push_back(static_cast<uint8_t>(0x12u)); //Product ID register address
message->dataBytes.push_back(static_cast<uint8_t>(0x80u));
message->address = 0x68u; //7 bit addressing, BASE_ADDR
message->stats = static_cast<uint16_t>(0x0002u);
message->direction = I2CMessage::Direction::Read;
message->deviceMode = I2CMessage::DeviceMode::Controller;
message->isTXMsg = true;
message->timestamp = static_cast<uint64_t>(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<icsneo::I2CMessage>(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);
}