Skip to content

Commit dcf88ee

Browse files
authored
Write general CAN interface classes for more easily using CAN (#9)
* Initial test implementation with can interface architecture * Initial test implementation with can interface architecture (added missing files) * Made GetData() return reference * Initial CANSignal and CANTXMessage implementation * Initial, non-functional implementation of CANRX callback * Tick the test_tx_message * Message send reorder, timer reset fix * Reorder send * Added unit tests, fixed bugs * Removed test GetMask() function * Moved ProcessMessage definition into cpp file * message_t is now a private variable instead of being constructed in SendMessage and ReceiveMessage * Automatically register RX messages * Fixed CANRXMessage inheriting from ICANRXMessage * Fixed CANRXMessage ID needing to be uint16_t * Actually send message in tick * can.events() * Teensy transmit working * Send and receive working for Teensy 4.0/4.1 * Added static_assert to ensure correct number of signals sent to CANRXMessage and CANTXMessage constructors * Removed todo * ESP transmit and receive working * Removed unneeded includes * Removed unused functions and main.cpp * Removed pass by reference that was breaking Teensy * Moved ESP32CAN * Moved ESP32CAN to lib_deps * Resolved some PR comments * Renamed function for consistency * Removed unnecessary includes * Added ESP32CAN library into src to remove need for additional lib_dep
1 parent 73f1ac2 commit dcf88ee

26 files changed

Lines changed: 1097 additions & 1024 deletions

.clang-format

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
BasedOnStyle: Google
3+
AccessModifierOffset: "-4"
4+
AlignAfterOpenBracket: Align
5+
BinPackArguments: "false"
6+
BinPackParameters: "false"
7+
BreakBeforeBinaryOperators: NonAssignment
8+
BreakBeforeBraces: Allman
9+
ColumnLimit: "120"
10+
IndentWidth: "4"
11+
Language: Cpp

.vscode/settings.json

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"files.associations": {
3+
"array": "cpp",
4+
"string": "cpp",
5+
"*.tpp": "cpp",
6+
"*.tcc": "cpp",
7+
"cctype": "cpp",
8+
"chrono": "cpp",
9+
"clocale": "cpp",
10+
"cmath": "cpp",
11+
"cstdarg": "cpp",
12+
"cstdint": "cpp",
13+
"cstdio": "cpp",
14+
"cstdlib": "cpp",
15+
"ctime": "cpp",
16+
"cwchar": "cpp",
17+
"cwctype": "cpp",
18+
"deque": "cpp",
19+
"unordered_map": "cpp",
20+
"vector": "cpp",
21+
"exception": "cpp",
22+
"algorithm": "cpp",
23+
"functional": "cpp",
24+
"ratio": "cpp",
25+
"system_error": "cpp",
26+
"tuple": "cpp",
27+
"type_traits": "cpp",
28+
"fstream": "cpp",
29+
"initializer_list": "cpp",
30+
"iosfwd": "cpp",
31+
"istream": "cpp",
32+
"limits": "cpp",
33+
"new": "cpp",
34+
"ostream": "cpp",
35+
"numeric": "cpp",
36+
"sstream": "cpp",
37+
"stdexcept": "cpp",
38+
"streambuf": "cpp",
39+
"cinttypes": "cpp",
40+
"utility": "cpp",
41+
"typeinfo": "cpp"
42+
}
43+
}

include/app_can.h

Lines changed: 0 additions & 45 deletions
This file was deleted.

include/can_interface.h

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
#pragma once
2+
3+
#include <stdint.h>
4+
5+
#include <array>
6+
#include <chrono>
7+
#include <vector>
8+
9+
class CANMessage
10+
{
11+
public:
12+
CANMessage(uint16_t id, uint8_t len, std::array<uint8_t, 8> data) : id_{id}, len_{len}, data_{data} {}
13+
14+
uint16_t id_;
15+
uint8_t len_;
16+
std::array<uint8_t, 8> data_;
17+
};
18+
19+
class ICANSignal
20+
{
21+
public:
22+
virtual void EncodeSignal(uint64_t *buffer) = 0;
23+
virtual void DecodeSignal(uint64_t *buffer) = 0;
24+
};
25+
26+
// Generates a mask of which bits in the message correspond to a specific signal
27+
constexpr uint64_t generate_mask(uint8_t position, uint8_t length)
28+
{
29+
return 0xFFFFFFFFFFFFFFFFull << (64 - length) >> (64 - (length + position));
30+
}
31+
static constexpr int kCANTemplateFloatDenominator{10000};
32+
constexpr int CANTemplateConvertFloat(float value) { return value * kCANTemplateFloatDenominator; }
33+
constexpr float CANTemplateGetFloat(int value) { return static_cast<float>(value) / kCANTemplateFloatDenominator; }
34+
/**
35+
* @brief A class for decoding and encoding CAN signals. Note: only works with little endian signals on a little endian
36+
* architecture, and you must manually ensure consistency with the DBC
37+
*
38+
* @tparam SignalType The type of variable in the application to be encoded/decoded
39+
* @tparam position The position of the first bit of the signal in the message
40+
* @tparam length The length of the signal in the message
41+
* @tparam factor The factor to multiply the raw signal by (gotten using CANTemplateConvertFloat(float value))
42+
* @tparam offset The offset added to the raw signal (gotten using CANTemplateConvertFloat(float value))
43+
* @tparam signed_raw Whether or not the signal is signed
44+
* @tparam mask This is calculated for you by default
45+
* @tparam unity_factor This is calculated for you by default
46+
*/
47+
template <typename SignalType,
48+
uint8_t position,
49+
uint8_t length,
50+
int factor,
51+
int offset,
52+
bool signed_raw = false,
53+
uint64_t mask = generate_mask(position, length),
54+
bool unity_factor = factor == CANTemplateConvertFloat(1)
55+
&& offset == 0> // unity_factor is used for increased precision on unity-factor 64-bit
56+
// signals by getting rid of floating point error
57+
class CANSignal : public ICANSignal
58+
{
59+
public:
60+
CANSignal() {}
61+
62+
void EncodeSignal(uint64_t *buffer) override
63+
{
64+
if (unity_factor)
65+
{
66+
*buffer |= (static_cast<uint64_t>(signal_) << position) & mask;
67+
}
68+
else
69+
{
70+
*buffer |= (static_cast<uint64_t>(((signal_ / CANTemplateGetFloat(factor)) + CANTemplateGetFloat(offset)))
71+
<< position)
72+
& mask;
73+
}
74+
}
75+
76+
void DecodeSignal(uint64_t *buffer) override
77+
{
78+
if (unity_factor)
79+
{
80+
signal_ = static_cast<SignalType>((*buffer & mask) >> position);
81+
}
82+
else
83+
{
84+
signal_ = static_cast<SignalType>((((*buffer & mask) >> position) * CANTemplateGetFloat(factor))
85+
+ CANTemplateGetFloat(offset));
86+
}
87+
}
88+
89+
SignalType &value_ref() { return signal_; }
90+
91+
void operator=(const SignalType &signal) { signal_ = signal; }
92+
93+
operator SignalType() const { return signal_; }
94+
95+
private:
96+
SignalType signal_;
97+
};
98+
99+
class ICANTXMessage
100+
{
101+
public:
102+
virtual void Tick(std::chrono::milliseconds elapsed_time) = 0;
103+
virtual uint16_t GetID() = 0;
104+
virtual void EncodeSignals() = 0;
105+
};
106+
107+
class ICANRXMessage
108+
{
109+
public:
110+
virtual uint16_t GetID() = 0;
111+
virtual void DecodeSignals(CANMessage message) = 0;
112+
};
113+
114+
class ICAN
115+
{
116+
public:
117+
enum class BaudRate
118+
{
119+
kBaud1M = 1000000,
120+
kBaud500K = 500000,
121+
kBaud250K = 250000,
122+
kBaud125k = 125000
123+
};
124+
125+
virtual void Initialize(BaudRate baud);
126+
127+
virtual bool SendMessage(CANMessage &msg) = 0;
128+
129+
virtual void RegisterRXMessage(ICANRXMessage &msg) = 0;
130+
131+
virtual void Tick() = 0;
132+
};
133+
134+
/**
135+
* @brief A class for storing signals in a message that sends every period
136+
*/
137+
template <size_t num_signals>
138+
class CANTXMessage : public ICANTXMessage
139+
{
140+
public:
141+
template <typename... Ts>
142+
CANTXMessage(ICAN &can_interface, uint16_t id, uint8_t length, std::chrono::milliseconds period, Ts &...signals)
143+
: can_interface_{can_interface},
144+
message_{id, length, std::array<uint8_t, 8>()},
145+
period_{period},
146+
signals_{&signals...}
147+
{
148+
static_assert(sizeof...(signals) == num_signals, "Wrong number of signals passed into CANTXMessage.");
149+
}
150+
151+
void Tick(std::chrono::milliseconds elapsed_time)
152+
{
153+
timer_ -= elapsed_time;
154+
if (timer_ <= std::chrono::milliseconds(0))
155+
{
156+
timer_ = period_;
157+
EncodeSignals();
158+
can_interface_.SendMessage(message_);
159+
}
160+
}
161+
162+
uint16_t GetID() { return message_.id_; }
163+
164+
private:
165+
ICAN &can_interface_;
166+
CANMessage message_;
167+
std::chrono::milliseconds period_;
168+
std::array<ICANSignal *, num_signals> signals_;
169+
170+
std::chrono::milliseconds timer_{period_};
171+
172+
void EncodeSignals()
173+
{
174+
uint64_t temp_raw{0};
175+
for (uint8_t i = 0; i < num_signals; i++)
176+
{
177+
signals_[i]->EncodeSignal(&temp_raw);
178+
}
179+
*reinterpret_cast<uint64_t *>(message_.data_.data()) = temp_raw;
180+
}
181+
};
182+
183+
/**
184+
* @brief A class for storing signals that get updated every time a matching message is received
185+
*/
186+
template <size_t num_signals>
187+
class CANRXMessage : public ICANRXMessage
188+
{
189+
public:
190+
template <typename... Ts>
191+
CANRXMessage(ICAN &can_interface, uint16_t id, Ts &...signals)
192+
: can_interface_{can_interface}, id_{id}, signals_{&signals...}
193+
{
194+
static_assert(sizeof...(signals) == num_signals, "Wrong number of signals passed into CANRXMessage.");
195+
can_interface_.RegisterRXMessage(*this);
196+
}
197+
198+
uint16_t GetID() { return id_; }
199+
200+
void DecodeSignals(CANMessage message)
201+
{
202+
uint64_t temp_raw = *reinterpret_cast<uint64_t *>(message.data_.data());
203+
for (uint8_t i = 0; i < num_signals; i++)
204+
{
205+
signals_[i]->DecodeSignal(&temp_raw);
206+
}
207+
}
208+
209+
private:
210+
ICAN &can_interface_;
211+
uint16_t id_;
212+
std::array<ICANSignal *, num_signals> signals_;
213+
214+
uint64_t raw_message;
215+
};

include/esp_can.h

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#pragma once
2+
3+
#include <ESP32CAN/ESP32CAN.h>
4+
5+
#include "can_interface.h"
6+
7+
class ESPCAN : public ICAN
8+
{
9+
public:
10+
/**
11+
* @brief Construct a new ESPCAN object. Note: YOU SHOULD ONLY CONSTRUCT ONE ESPCAN
12+
*
13+
* @param tx The CAN TX pin
14+
* @param rx The CAN RX pin
15+
*/
16+
ESPCAN(gpio_num_t tx = gpio_num_t::GPIO_NUM_5, gpio_num_t rx = gpio_num_t::GPIO_NUM_4);
17+
18+
void Initialize(BaudRate baud) override;
19+
20+
bool SendMessage(CANMessage &msg) override;
21+
22+
void RegisterRXMessage(ICANRXMessage &msg) override { rx_messages_.push_back(&msg); }
23+
24+
void Tick() override;
25+
26+
static void ProcessReceive(void *pvParameter)
27+
{
28+
std::array<uint8_t, 8> msg_data{};
29+
CAN_frame_t rx_frame;
30+
uint32_t msg_id;
31+
uint8_t msg_len;
32+
33+
if ((xQueueReceive(CAN_cfg.rx_queue, &rx_frame, 3 * portTICK_PERIOD_MS) == pdTRUE)
34+
&& (rx_frame.FIR.B.FF == CAN_frame_std))
35+
{
36+
msg_id = rx_frame.MsgID;
37+
msg_len = rx_frame.FIR.B.DLC;
38+
39+
for (int i = 0; i < msg_len; i++)
40+
{
41+
msg_data[i] = rx_frame.data.u8[i];
42+
}
43+
CANMessage received_message{static_cast<uint16_t>(msg_id), msg_len, msg_data};
44+
for (size_t i = 0; i < rx_messages_.size(); i++)
45+
{
46+
if (rx_messages_[i]->GetID() == received_message.id_)
47+
{
48+
rx_messages_[i]->DecodeSignals(received_message);
49+
}
50+
}
51+
}
52+
portYIELD_FROM_ISR();
53+
}
54+
55+
private:
56+
static std::vector<ICANRXMessage *> rx_messages_;
57+
};

0 commit comments

Comments
 (0)