diff --git a/CMakeLists.txt b/CMakeLists.txt index 01be2265477cded3f7ad2e91fb551326c8233df0..4df2e09419a93d1a797f9f094a98c6c3e0c0301a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -159,6 +159,8 @@ sbs_target(test-ada stm32f429zi_skyward_death_stack_v3) #-----------------------------------------------------------------------------# # Tests - Drivers # #-----------------------------------------------------------------------------# +add_executable(test-can-protocol src/tests/drivers/canbus/test-can-protocol.cpp) +sbs_target(test-can-protocol stm32f429zi_stm32f4discovery) add_executable(test-canbus-loopback src/tests/drivers/canbus/test-canbus-loopback.cpp) sbs_target(test-canbus-loopback stm32f429zi_stm32f4discovery) diff --git a/src/shared/drivers/canbus/CanProtocol.h b/src/shared/drivers/canbus/CanProtocol.h new file mode 100644 index 0000000000000000000000000000000000000000..cf78a0c341fd185d846b8fc077c48e8d1497552d --- /dev/null +++ b/src/shared/drivers/canbus/CanProtocol.h @@ -0,0 +1,221 @@ +/* Copyright (c) 2022 Skyward Experimental Rocketry + * Author: Federico Mandelli + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <ActiveObject.h> +#include <utils/Debug.h> +#include <utils/collections/IRQCircularBuffer.h> + +#include "Canbus.h" + +#define NPACKET 3 // equals the number of boards in the can system + +/** + * @brief Struct that contains how the canId is composed + */ +struct CanIDMask +{ + uint32_t priority = 0x1E000000; + uint32_t type = 0x1F80000; + uint32_t source = 0x78000; + uint32_t destination = 0x7800; + uint32_t idType = 0x780; + uint32_t firstPacket = 0x40; + uint32_t leftToSend = 0x3F; +} idMask; + +namespace Boardcore +{ +namespace Canbus +{ + +/** + * @brief Generic struct that contains a logical can packet + * i.e. 1 accelerometer packet 3*4byte (acc: x,y,z)+timestamp, will be 4 + * canPacket but a single canData. + */ +struct CanData +{ + uint32_t canId = + 0; // the id of the can packet without the last 7 bits (sequence bit) + uint8_t len; + uint8_t nRec = 0; + uint64_t payload[32]; +} data[NPACKET]; + +/** + * @brief Canbus protocol, given an initialized can this class takes care of + * sending the multiple packet of CanData with the corresponding id and + * receiving single CanPacket that are then reframed as one Candata. + */ +class CanProtocol : public ActiveObject +{ +private: + miosix::FastMutex + mutex; // todo add mutex and create get data in can protocol + CanbusDriver* can; // the physical can + IRQCircularBuffer<CanData, NPACKET> + buffer; // the buffer used to send data from CanProtocol to CanHandler + +public: + /** + * @brief Construct a new CanProtocol object + * @param can CanbusDriver pointer. + */ + CanProtocol(CanbusDriver* can) { this->can = can; } + + CanData + getPacket() // return the packet, if buffer is empty return an empty packet + { + CanData temp; + mutex.lock(); + if (!buffer.isEmpty()) + { + temp = buffer.pop(); + } + mutex.unlock(); + + return temp; + } + + bool isEmpty() + { + mutex.lock(); + return buffer.isEmpty(); + mutex.unlock(); + } + + void waitEmpty() { buffer.waitUntilNotEmpty(); } + + /** + * @brief Takes a canData, it splits it into single canpacket with the + * correct sequential id + * @param toSend = containing the id e the data of the packet to send + * @warning requires toSend to be not empty + */ + void sendCan(CanData toSend) //@requires toSen to not be empty + { + CanPacket packet; + uint32_t tempLen = toSend.len - 1; + uint32_t tempId = toSend.canId; + packet.ext = true; + packet.id = + (tempId << 7) | idMask.firstPacket | (tempLen & idMask.leftToSend); + packet.length = (toSend.payload[0] + 8) / + 8; // simple formula for upper approximation + for (int k = 0; k < packet.length; k++) + { + packet.data[k] = toSend.payload[0] >> (8 * k); + } + tempLen--; + + can->send(packet); + TRACE("tosend len %d\n", toSend.len); + + for (int i = 1; i < toSend.len; i++) + { + tempId = toSend.canId; + packet.id = (tempId << 7) | !(idMask.firstPacket) | + (tempLen & idMask.leftToSend); + packet.length = (toSend.payload[i] + 8) / 8; + for (int k = 0; k < packet.length; k++) + { + packet.data[k] = toSend.payload[i] << (8 * k); + } + TRACE("packetlen %d\n, dato %d\n", packet.length, packet.data[0]); + can->send(packet); + tempLen--; + } + } + +protected: + /** + * @brief Keeps listening on hte canbus for packets, once received it checks + * if they are expected (that id is already present in data), if they are + * they are added to the list. once we receive the correct amount of packet + * we send it to can handler. + */ + void run() override // for now if a packet is missed/received in the wrong + // order the whole packet will be lost once we receive + // a new first packet without warning canhandler + { + uint32_t sourceId; + CanPacket packet; + // Infinite loop + while (true) + { + can->getRXBuffer().waitUntilNotEmpty(); + if (!can->getRXBuffer().isEmpty()) + { + + packet = can->getRXBuffer().pop().packet; + + sourceId = packet.id & idMask.source; + if (data[sourceId].canId == 0 || + (data[sourceId].canId & idMask.source) == sourceId) + { + if (packet.id & idMask.firstPacket) // it is a first + // packet of a data; + { + data[sourceId].len = + (packet.id & idMask.leftToSend) + 1; + data[sourceId].canId = + packet.id >> 7; // discard the sequence number + } + TRACE("pakcet %d, nrec %d, left %lu\n", packet.data[0], + data[sourceId].nRec, (packet.id & idMask.leftToSend)); + if ((data[sourceId].len - (data[sourceId].nRec + 1)) == + (packet.id & idMask.leftToSend)) + { + + uint64_t tempPayload = 0; + for (int f = 0; f < packet.length; f++) + { + uint64_t tempData = packet.data[f]; + tempPayload = tempPayload | (tempData << (f * 8)); + } + + data[sourceId] + .payload[data[sourceId].len - + (packet.id & idMask.leftToSend) - 1] = + tempPayload; + data[sourceId].nRec++; + } + + if (data[sourceId].nRec == data[sourceId].len) + { + mutex.lock(); + buffer.put(data[sourceId]); + // empties the struct + data[sourceId].canId = 0; + data[sourceId].nRec = 0; + data[sourceId].len = 0; + mutex.unlock(); + } + } + } + } + } +}; +} // namespace Canbus +} // namespace Boardcore diff --git a/src/tests/drivers/canbus/test-can-protocol.cpp b/src/tests/drivers/canbus/test-can-protocol.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e2126819497d35abdf939256d70a0e4bd8fb3caf --- /dev/null +++ b/src/tests/drivers/canbus/test-can-protocol.cpp @@ -0,0 +1,131 @@ + + +/* Copyright (c) 2022 Skyward Experimental Rocketry + * Author: Federico Mandelli + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include <drivers/canbus/CanProtocol.h> +#include <inttypes.h> + +#include <thread> + +#include "drivers/canbus/BusLoadEstimation.h" +#include "drivers/canbus/Canbus.h" +#include "utils/collections/CircularBuffer.h" + +constexpr uint32_t BAUD_RATE = 500 * 1000; +constexpr float SAMPLE_POINT = 87.5f / 100.0f; +constexpr uint32_t MSG_DEADLINE = 100; // ms +constexpr uint32_t MSG_LOST_DEADLINE = 400; // ms + +using std::string; +using namespace Boardcore; +using namespace Boardcore::Canbus; +using namespace miosix; + +#ifdef _ARCH_CORTEXM3_STM32 +using CanRX = Gpio<GPIOA_BASE, 11>; +using CanTX = Gpio<GPIOA_BASE, 12>; +#else +using CanRX = Gpio<GPIOA_BASE, 11>; +using CanTX = Gpio<GPIOA_BASE, 12>; +#endif + +#define SLP 5000 + +void sendData(CanProtocol* protocol, CanData toSend) +{ + while (true) + { + TRACE("send\n"); + (*protocol).sendCan(toSend); + Thread::sleep(SLP); + } +} + +int main() +{ + + { + miosix::FastInterruptDisableLock dLock; + +#ifdef _ARCH_CORTEXM3_STM32 + CanRX::mode(Mode::ALTERNATE); + CanTX::mode(Mode::ALTERNATE); +#else + CanRX::mode(Mode::ALTERNATE); + CanTX::mode(Mode::ALTERNATE); + + CanRX::alternateFunction(9); + CanTX::alternateFunction(9); +#endif + } + + CanbusDriver::CanbusConfig cfg{}; + CanbusDriver::AutoBitTiming bt; + bt.baudRate = BAUD_RATE; + bt.samplePoint = SAMPLE_POINT; + + CanbusDriver* c = new CanbusDriver(CAN1, cfg, bt); + CanProtocol protocol(c); + // Allow every message + Mask32FilterBank f2(0, 0, 0, 0, 0, 0, 0); + + c->addFilter(f2); + c->init(); + protocol.start(); + CanData toSend; + toSend.canId = 0x01; + toSend.len = 3; + toSend.payload[0] = 1; + toSend.payload[1] = 2; + toSend.payload[2] = 3; + std::thread second(sendData, &protocol, toSend); + for (;;) + { + TRACE("start \n"); + protocol.waitEmpty(); + CanData temp = protocol.getPacket(); + if (temp.canId != toSend.canId || temp.len != toSend.len || + temp.payload[0] != toSend.payload[0] || + temp.payload[1] != toSend.payload[1] || + temp.payload[2] != toSend.payload[2]) + { + TRACE("Error\n"); + TRACE("Expected id %lu, received %lu\n", toSend.canId, temp.canId); + TRACE("Expected len %d , received %d\n", toSend.len, temp.len); + TRACE( + "Expected payload 0 %llu , received " + "%llu\n", + toSend.payload[0], temp.payload[0]); + TRACE("Expected payload 1 %llu, received %llu\n", + toSend.payload[1], temp.payload[1]); + TRACE( + "Expected payload 2 %llu, received " + "%llu\n", + toSend.payload[2], temp.payload[2]); + } + else + { + TRACE("OK :)\n"); + } + } +}