From b68517318593d980a176509ba807cf2965061384 Mon Sep 17 00:00:00 2001 From: Pavel Kirienko Date: Sat, 25 Jan 2014 20:06:43 +0400 Subject: [PATCH] Very basic stuff: CAN driver iface, linked list, system clock interface, tests --- libuavcan/CMakeLists.txt | 49 +++++++++ libuavcan/include/uavcan/can_driver.hpp | 123 ++++++++++++++++++++++ libuavcan/include/uavcan/linked_list.hpp | 107 +++++++++++++++++++ libuavcan/include/uavcan/system_clock.hpp | 44 ++++++++ libuavcan/test/linked_list.cpp | 105 ++++++++++++++++++ libuavcan/test/test_main.cpp | 11 ++ 6 files changed, 439 insertions(+) create mode 100644 libuavcan/CMakeLists.txt create mode 100644 libuavcan/include/uavcan/can_driver.hpp create mode 100644 libuavcan/include/uavcan/linked_list.hpp create mode 100644 libuavcan/include/uavcan/system_clock.hpp create mode 100644 libuavcan/test/linked_list.cpp create mode 100644 libuavcan/test/test_main.cpp diff --git a/libuavcan/CMakeLists.txt b/libuavcan/CMakeLists.txt new file mode 100644 index 0000000000..d40f34abee --- /dev/null +++ b/libuavcan/CMakeLists.txt @@ -0,0 +1,49 @@ +cmake_minimum_required(VERSION 2.6) + +if("${CMAKE_BUILD_TYPE}" STREQUAL "") + set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Default build type is RelWithDebInfo" FORCE) +endif() + +project(libuavcan) + +# +# -DUAVCAN_DEBUG=1 enables the tracing feature that writes debug info into stdout. +# Normally this feature should be used only for library development. +# +set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O1 -g") +set(CMAKE_CXX_FLAGS_RELEASE "-O1 -DNDEBUG") +set(CMAKE_CXX_FLAGS_DEBUG "-g3 -DUAVCAN_DEBUG=1") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++03 -Wall -Wextra -Werror -pedantic") + +include_directories(include) + +# +# libuavcan +# +# TODO + +# +# Test +# +find_package(GTest QUIET) +if (GTEST_FOUND) + find_package(Threads REQUIRED) + include_directories(${GTEST_INCLUDE_DIRS}) + + file(GLOB_RECURSE TEST_CXX_FILES RELATIVE ${CMAKE_SOURCE_DIR} "test/*.cpp") + add_executable(libuavcan_test ${TEST_CXX_FILES}) + #add_dependencies(libuavcan_test libuavcan) + + set_target_properties(libuavcan_test PROPERTIES + COMPILE_FLAGS "-Wno-unused-parameter -Wno-unused-function" + ) + + target_link_libraries(libuavcan_test ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) + #target_link_libraries(libuavcan_test ${CMAKE_BINARY_DIR}/libuavcan.so) + + add_custom_command(TARGET libuavcan_test POST_BUILD + COMMAND "./libuavcan_test" + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +else (GTEST_FOUND) + message(">> Google test is not found, you will not be able to run libuavcan_test") +endif (GTEST_FOUND) diff --git a/libuavcan/include/uavcan/can_driver.hpp b/libuavcan/include/uavcan/can_driver.hpp new file mode 100644 index 0000000000..97db0a14bb --- /dev/null +++ b/libuavcan/include/uavcan/can_driver.hpp @@ -0,0 +1,123 @@ +/* + * CAN bus driver interface. + * Copyright (C) 2013 Pavel Kirienko + */ + +#pragma once + +#include +#include +#include + +namespace uavcan +{ +/** + * Raw CAN frame, as passed to/from the CAN driver. + */ +struct CanFrame +{ + static const uint32_t MASK_STDID = 0x000007FF; + static const uint32_t MASK_EXTID = 0x1FFFFFFF; + static const uint32_t FLAG_EFF = 1 << 31; ///< Extended frame format + static const uint32_t FLAG_RTR = 1 << 30; ///< Remote transmission request + + uint32_t id; ///< CAN ID with flags (above) + uint8_t data[8]; + uint8_t dlc; ///< Data Length Code + + Frame() + : id(0) + , dlc(0) + { } + + Frame(uint32_t id, const uint8_t* data, uint8_t dlc) + : id(id) + , dlc(dlc) + { + assert(data && dlc <= 8); + std::memmove(this->data, data, dlc); + } + + bool isExtended() const { return id & FLAG_EFF; } + bool isRemoteTransmissionRequest() const { return id & FLAG_RTR; } +}; + +/** + * CAN hardware filter config struct. @ref ICanDriver::filter(). + */ +struct CanFilterConfig +{ + uint32_t id; + uint32_t mask; +}; + +/** + * Single non-blocking CAN interface. + */ +class ICanIface +{ +public: + virtual ~ICanIface() { } + + /** + * Non-blocking transmission. + * If the frame wasn't transmitted upon TX timeout expiration, the driver should discard it. + * @return 1 = one frame transmitted, 0 = TX buffer full, negative for error. + */ + virtual int send(const Frame& frame, uint64_t tx_timeout_usec) = 0; + + /** + * Non-blocking reception. + * out_utc_timestamp_usec must be provided by the driver, ideally by the hardware CAN controller; 0 if unknown. + * @return 1 = one frame received, 0 = RX buffer empty, negative for error. + */ + virtual int receive(Frame& out_frame, uint64_t& out_utc_timestamp_usec) = 0; + + /** + * Configure the hardware CAN filters. @ref CanFilterConfig. + * @return 0 = success, negative for error. + */ + virtual int filter(const CanFilterConfig* filter_configs, int num_configs) = 0; + + /** + * Number of available hardware filters. + */ + virtual int getNumFilters() const = 0; + + /** + * Continuously incrementing counter of detected hardware errors. + */ + virtual uint64_t getNumErrors() const = 0; +}; + +/** + * Generic CAN driver. + */ +class ICanDriver +{ +public: + virtual ~ICanDriver() { } + + /** + * Returns the interface by index, or null pointer if the index is out of range. + */ + virtual ICanIface* getIface(int iface_index) = 0; + + /** + * Total number of available CAN interfaces. + */ + virtual int getNumIfaces() const = 0; + + /** + * Block until the blocking timeout expires, or one of the specified interfaces becomes available for read or write. + * Iface masks will be modified by the driver to indicate which exactly interfaces are available for IO. + * Bit position in the masks defines interface index. + * @param [in,out] inout_write_iface_mask Mask indicating which interfaces are needed/available to write. + * @param [in,out] inout_read_iface_mask Same as above for reading. + * @param [in] timeout_usec Zero means non-blocking operation. + * @return Positive number of ready interfaces or negative error code. + */ + virtual int select(int& inout_write_iface_mask, int& inout_read_iface_mask, uint64_t timeout_usec) = 0; +}; + +} diff --git a/libuavcan/include/uavcan/linked_list.hpp b/libuavcan/include/uavcan/linked_list.hpp new file mode 100644 index 0000000000..6390067229 --- /dev/null +++ b/libuavcan/include/uavcan/linked_list.hpp @@ -0,0 +1,107 @@ +/* + * Singly-linked list. + * Copyright (C) 2013 Pavel Kirienko + */ + +#pragma once + +#include +#include + +namespace uavcan +{ +/** + * Classes that are supposed to be linked-listed should derive this. + */ +template +class LinkedListNode +{ + T* next_; + +protected: + LinkedListNode() + : next_(NULL) + { } + +public: + T* getNextListNode() const { return next_; } + + void setNextListNode(T* node) + { + next_ = node; + } +}; + +/** + * Linked list root. + */ +template +class LinkedListRoot +{ + T* root_; + +public: + LinkedListRoot() + : root_(NULL) + { } + + T* get() const { return root_; } + + unsigned int length() const + { + T* node = root_; + unsigned int cnt = 0; + while (node) + { + cnt++; + node = node->getNextListNode(); + } + return cnt; + } + + void insert(T* node) + { + remove(node); // Making sure there will be no loops + node->setNextListNode(root_); + root_ = node; + } + + bool remove(const T* node) + { + if (root_ == NULL || node == NULL) + return false; + + if (root_ == node) + { + root_ = root_->getNextListNode(); + return true; + } + else + { + T* p = root_; + while (p->getNextListNode()) + { + if (p->getNextListNode() == node) + { + p->setNextListNode(p->getNextListNode()->getNextListNode()); + return true; + } + p = p->getNextListNode(); + } + return false; + } + } + + template + void map(Fun& fun) + { + T* node = root_; + while (node) + { + fun(node); + node = node->getNextListNode(); + } + } +}; + +} diff --git a/libuavcan/include/uavcan/system_clock.hpp b/libuavcan/include/uavcan/system_clock.hpp new file mode 100644 index 0000000000..1775ded228 --- /dev/null +++ b/libuavcan/include/uavcan/system_clock.hpp @@ -0,0 +1,44 @@ +/* + * System clock driver interface. + * Copyright (C) 2013 Pavel Kirienko + */ + +#pragma once + +#include + +namespace uavcan +{ +/** + * System clock interface - monotonic and UTC. + * Note that this library uses microseconds for all time related values (timestamps, deadlines, delays), + * if not explicitly specified otherwise. + * All time values are represented as 64-bit integers. + */ +class ISystemClock +{ + /** + * Monototic system clock in microseconds. + * This shall never jump during UTC timestamp adjustments; the base time is irrelevant. + * On POSIX systems use clock_gettime() with CLOCK_MONOTONIC. + */ + virtual uint64_t getMonotonicMicroseconds() const = 0; + + /** + * UTC clock in microseconds. + * This can jump when the UTC timestamp is being adjusted. + * Return 0 if the UTC time is not available yet (e.g. the device just started up with no battery clock). + * On POSIX systems use gettimeofday(). + */ + virtual uint64_t getUtcMicroseconds() const = 0; + + /** + * Set the UTC system clock. + * @param [in] timestamp New UTC timestamp. Avoid when possible. + * @param [in] offset Current UTC time error. More precise than just timestamp, use it if possible. + * For POSIX refer to adjtime(), settimeofday(). + */ + virtual void adjustUtcMicroseconds(uint64_t new_timestamp_usec, int64_t offset_usec) = 0; +}; + +} diff --git a/libuavcan/test/linked_list.cpp b/libuavcan/test/linked_list.cpp new file mode 100644 index 0000000000..d802f95ec5 --- /dev/null +++ b/libuavcan/test/linked_list.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2013 Pavel Kirienko + */ + +#include +#include + +struct ListItem : uavcan::LinkedListNode +{ + int value; + ListItem() : value(0) { } +}; + +TEST(LinkedList, Basic) +{ + uavcan::LinkedListRoot root; + + /* + * Insert/remove + */ + EXPECT_EQ(0, root.length()); + + ListItem item1; + root.insert(&item1); + root.insert(&item1); // Insert twice - second will be ignored + EXPECT_EQ(1, root.length()); + + EXPECT_TRUE(root.remove(&item1)); + EXPECT_FALSE(root.remove(&item1)); + EXPECT_EQ(0, root.length()); + + ListItem items[3]; + root.insert(&item1); + root.insert(items + 0); + root.insert(items + 1); + root.insert(items + 2); + EXPECT_EQ(4, root.length()); + + /* + * Order persistence + */ + items[0].value = 10; + items[1].value = 11; + items[2].value = 12; + const int expected_values[] = {12, 11, 10, 0}; + ListItem* node = root.get(); + for (int i = 0; i < 4; i++) + { + EXPECT_EQ(expected_values[i], node->value); + node = node->getNextListNode(); + } + + EXPECT_TRUE(root.remove(items + 0)); + EXPECT_TRUE(root.remove(items + 2)); + EXPECT_FALSE(root.remove(items + 2)); + EXPECT_EQ(2, root.length()); + + const int expected_values2[] = {11, 0}; + node = root.get(); + for (int i = 0; i < 2; i++) + { + EXPECT_EQ(expected_values2[i], node->value); + node = node->getNextListNode(); + } + + root.insert(items + 2); + EXPECT_EQ(3, root.length()); + EXPECT_EQ(12, root.get()->value); + + /* + * Emptying + */ + EXPECT_TRUE(root.remove(&item1)); + EXPECT_FALSE(root.remove(items + 0)); + EXPECT_TRUE(root.remove(items + 1)); + EXPECT_TRUE(root.remove(items + 2)); + EXPECT_EQ(0, root.length()); +} + +struct Summator +{ + long sum; + Summator() : sum(0) { } + void operator()(const ListItem* item) + { + sum += item->value; + } +}; + +TEST(LinkedList, Predicate) +{ + uavcan::LinkedListRoot root; + ListItem items[5]; + for (int i = 0 ; i < 5; i++) + { + EXPECT_FALSE(root.remove(items + i)); // Just to make sure that there's no such item + root.insert(items + i); + items[i].value = i; + } + EXPECT_EQ(5, root.length()); + + Summator sum; + root.map(sum); + EXPECT_EQ(10, sum.sum); +} diff --git a/libuavcan/test/test_main.cpp b/libuavcan/test/test_main.cpp new file mode 100644 index 0000000000..fc18353571 --- /dev/null +++ b/libuavcan/test/test_main.cpp @@ -0,0 +1,11 @@ +/* + * Copyright (C) 2013 Pavel Kirienko + */ + +#include + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}