From b01abccbc0cde42f96b4740746e94ed7709790f0 Mon Sep 17 00:00:00 2001 From: jsphuebner Date: Thu, 27 Jul 2023 15:46:10 +0200 Subject: [PATCH] Revised test framework and added VCU tests --- include/vehiclecontrol.h | 2 + src/vehiclecontrol.cpp | 19 +-- test/Makefile | 7 +- test/test.h | 24 +++- test/test_fp.cpp | 28 ++-- test/test_fu.cpp | 38 +++-- test/test_list.h | 54 ------- test/test_main.cpp | 25 ++-- test/test_throttle.cpp | 18 +-- test/test_vcu.cpp | 300 +++++++++++++++++++++++++++++++++++++++ 10 files changed, 389 insertions(+), 126 deletions(-) delete mode 100644 test/test_list.h create mode 100644 test/test_vcu.cpp diff --git a/include/vehiclecontrol.h b/include/vehiclecontrol.h index 0a6bbbc..e5929cf 100644 --- a/include/vehiclecontrol.h +++ b/include/vehiclecontrol.h @@ -49,6 +49,8 @@ class VehicleControl static uint16_t bmwAdcValues[4]; static FunctionPointerCallback callback; static uint32_t lastCanRxTime; + static uint8_t canErrors; + static uint8_t seqCounter; static void GetTemps(float& tmphs, float &tmpm); static float GetUserThrottleCommand(); diff --git a/src/vehiclecontrol.cpp b/src/vehiclecontrol.cpp index 6d6cff0..f493ea7 100644 --- a/src/vehiclecontrol.cpp +++ b/src/vehiclecontrol.cpp @@ -50,9 +50,13 @@ float VehicleControl::tempmFiltered = 0; int VehicleControl::udcFiltered = 0; uint16_t VehicleControl::bmwAdcNextChan = 0; uint16_t VehicleControl::bmwAdcValues[4]; +uint8_t VehicleControl::canErrors; +uint8_t VehicleControl::seqCounter; void VehicleControl::SetCan(CanHardware* canHw) { + seqCounter = 0; //Mainly useful for unit tests + canErrors = 0; can = canHw; can->AddReceiveCallback(&callback); CanClear(); @@ -66,7 +70,6 @@ void VehicleControl::CanClear() bool VehicleControl::CanReceive(uint32_t canId, uint32_t data[2]) { const int maxErrors = 5; - static uint8_t errors = 0, lastCounter = 0; if (canId != (uint32_t)Param::GetInt(Param::controlid)) return false; @@ -90,24 +93,24 @@ bool VehicleControl::CanReceive(uint32_t canId, uint32_t data[2]) if (calcCrc != crc) { ErrorMessage::Post(ERR_CANCRC); - if (errors < maxErrors) errors++; + if (canErrors < maxErrors) canErrors++; } else if (ctr1 != ctr2 || //The two counters within the message don't match up - ctr1 == lastCounter) //The counters match but haven't moved since the last message + ctr1 == seqCounter) //The counters match but haven't moved since the last message { ErrorMessage::Post(ERR_CANCOUNTER); - if (errors < maxErrors) errors++; + if (canErrors < maxErrors) canErrors++; } - else if (errors > 0 && errors < maxErrors) + else if (canErrors > 0 && canErrors < maxErrors) { //As long as we haven't reached maxErrors, good frames decrease the error counter - errors--; + canErrors--; } - lastCounter = ctr1; + seqCounter = ctr1; //once we've reached maxerrors we cannot recover, inverter needs restarting. - if (errors < maxErrors) + if (canErrors < maxErrors) { //This lets consuming functions check the age of the last valid frame lastCanRxTime = rtc_get_counter_val(); diff --git a/test/Makefile b/test/Makefile index 1062912..4dcfb44 100644 --- a/test/Makefile +++ b/test/Makefile @@ -2,11 +2,12 @@ CC = gcc CPP = g++ LD = g++ CP = cp -CFLAGS = -std=c99 -g -I../include -I../libopeninv/include -CPPFLAGS = -g -I../include -I../libopeninv/include +CFLAGS = -std=c99 -ggdb -DSTM32F1 -I../include -I../libopeninv/include -I../libopencm3/include +CPPFLAGS = -ggdb -DSTM32F1 -DCONTROL_FOC=1 -DCONTROL_SINE=0 -DCONTROL=DCONTROL_FOC -I../include -I../libopeninv/include -I../libopencm3/include LDFLAGS = -g BINARY = test_sine -OBJS = test_main.o fu.o test_fu.o test_fp.o my_fp.o my_string.o test_throttle.o throttle.o sine_core.o +OBJS = test_main.o fu.o test_fu.o test_fp.o test_vcu.o my_fp.o my_string.o params.o vehiclecontrol.o \ + test_throttle.o throttle.o sine_core.o temp_meas.o VPATH = ../src ../libopeninv/src all: $(BINARY) diff --git a/test/test.h b/test/test.h index 71d2e8a..5794842 100644 --- a/test/test.h +++ b/test/test.h @@ -1,23 +1,35 @@ #ifndef TEST_H_INCLUDED #define TEST_H_INCLUDED #include +#include -class IUnitTest +typedef void (*VoidFunction)(); + +class UnitTest { - public: - virtual ~IUnitTest() {} - virtual void RunTest() = 0; + public: + UnitTest(const std::list*); + virtual void TestSetup() {} + virtual void TestCaseSetup() {} + const std::list GetCases() { return *_cases; } + void SetTestCaseList(const std::list* cases) { _cases = cases; } + + private: + const std::list* _cases; }; extern int _failedAssertions; + +#define REGISTER_TEST(t, ...) static UnitTest* test = new t (new std::list { __VA_ARGS__ }); + #define STRING(s) #s #define ASSERT(c) \ if (c) \ - cout << "Test " << __FILE__ << "::" << __func__ << " passed." << endl; \ + std::cout << "Test " << __FILE__ << "::" << __func__ << " passed." << std::endl; \ else \ { \ - cout << "Assertion failed: " << STRING(c) << " in " __FILE__ " : " << __LINE__ << endl; \ + std::cout << "Assertion failed: " << STRING(c) << " in " __FILE__ " : " << __LINE__ << std::endl; \ _failedAssertions++; \ } diff --git a/test/test_fp.cpp b/test/test_fp.cpp index 612a486..30c553d 100644 --- a/test/test_fp.cpp +++ b/test/test_fp.cpp @@ -22,10 +22,14 @@ #include "my_fp.h" #include "my_math.h" #include "sine_core.h" -#include "test_list.h" +#include "test.h" #include "string.h" -using namespace std; +class FPTest: public UnitTest +{ + public: + FPTest(const std::list* cases): UnitTest(cases) {} +}; static void TestMacros() { @@ -46,7 +50,7 @@ static void TestItoa() static void TestAtoi() { ASSERT(fp_atoi("-2.5", 5) == FP_FROMFLT(-2.5)); - ASSERT(fp_atoi("2.155", 5) == FP_FROMFLT(2.15)); + ASSERT(fp_atoi("2.155", 5) == FP_FROMFLT(2.16)); } static void TestMedian3() @@ -68,19 +72,7 @@ static void TestAtan2() ASSERT(SineCore::Atan2(2048, 3547) == 10922); //60° } -static void TestLn() -{ - //ASSERT(fp_ln(1) == 0); - ASSERT(fp_ln(5389) == FP_FROMFLT(8.5777)); - ASSERT(fp_ln(8290) == FP_FROMFLT(9.0)); -} +//This line registers the test +REGISTER_TEST(FPTest, TestMacros, TestItoa, TestAtoi, TestMedian3, TestAtan2); + -void FPTest::RunTest() -{ - TestMacros(); - TestItoa(); - TestAtoi(); - TestMedian3(); - TestAtan2(); - TestLn(); -} diff --git a/test/test_fu.cpp b/test/test_fu.cpp index b8a1777..161a405 100644 --- a/test/test_fu.cpp +++ b/test/test_fu.cpp @@ -21,39 +21,42 @@ #include "fu.h" #include "my_fp.h" -#include "test_list.h" +#include "test.h" -using namespace std; +class FUTest: public UnitTest +{ + public: + FUTest(const std::list* cases): UnitTest(cases) {} +}; -static void Setup(u32fp fweak, int boost, u32fp fmin) +static void Setup(float fweak, int boost) { + MotorVoltage::SetMaxAmp(10000); MotorVoltage::SetBoost(boost); MotorVoltage::SetWeakeningFrq(fweak); - //MotorVoltage::SetMinFrq(fmin); - MotorVoltage::SetMaxAmp(10000); } static void TestBoost1() { - Setup(FP_FROMINT(10), 1000, FP_FROMFLT(0)); - ASSERT(MotorVoltage::GetAmp(0)==1000); + Setup(10, 1000); + ASSERT(MotorVoltage::GetAmp(FP_FROMINT(1))==1900); } static void TestBoost2() { - Setup(FP_FROMINT(10), 1000, FP_FROMFLT(1)); - ASSERT(MotorVoltage::GetAmp(0.5)==0); + Setup(10, 1000); + ASSERT(MotorVoltage::GetAmp(FP_FROMFLT(0.1))==0); } static void TestFU1() { - Setup(FP_FROMINT(10), 0, FP_FROMFLT(1)); + Setup(10, 0); ASSERT(MotorVoltage::GetAmp(FP_FROMFLT(5))==(5.0/10.0f * 10000)); } static void TestFU2() { - Setup(FP_FROMINT(10), 1000, FP_FROMFLT(1)); + Setup(10, 1000); ASSERT(MotorVoltage::GetAmp(FP_FROMFLT(5))==(1000 + 5.0/10.0f * (10000-1000))); ASSERT(MotorVoltage::GetAmp(FP_FROMFLT(9.5))==(1000 + 9.5/10.0f * (10000-1000))); ASSERT(MotorVoltage::GetAmp(FP_FROMFLT(10))==10000); @@ -62,16 +65,11 @@ static void TestFU2() static void TestFUPerc() { - Setup(FP_FROMINT(10), 1000, FP_FROMFLT(1)); + Setup(10, 1000); ASSERT(MotorVoltage::GetAmpPerc(FP_FROMFLT(5), FP_FROMFLT(50))==((1000 + 5.0/10.0f * (10000-1000))/2)); ASSERT(MotorVoltage::GetAmpPerc(FP_FROMFLT(22), FP_FROMFLT(50))==10000); } -void FUTest::RunTest() -{ - TestBoost1(); - TestBoost2(); - TestFU1(); - TestFU2(); - TestFUPerc(); -} +//This line registers the test +REGISTER_TEST(FUTest, TestBoost1, TestBoost2, TestFU1, TestFU2, TestFUPerc); + diff --git a/test/test_list.h b/test/test_list.h deleted file mode 100644 index 4113a2e..0000000 --- a/test/test_list.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * This file is part of the tumanako_vc project. - * - * Copyright (C) 2010 Johannes Huebner - * Copyright (C) 2010 Edward Cheeseman - * Copyright (C) 2009 Uwe Hermann - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#ifndef TEST_LIST_H_INCLUDED -#define TEST_LIST_H_INCLUDED - -#include "test.h" - -class FPTest: public IUnitTest -{ - public: - virtual void RunTest(); -}; - -class FUTest: public IUnitTest -{ - public: - virtual void RunTest(); -}; - -class ThrottleTest: public IUnitTest -{ - public: - virtual void RunTest(); -}; - -#ifdef EXPORT_TESTLIST -IUnitTest* testList[] = -{ - new FPTest(), - new FUTest(), - new ThrottleTest(), - NULL -}; -#endif - -#endif // TEST_LIST_H_INCLUDED diff --git a/test/test_main.cpp b/test/test_main.cpp index 02226b1..fba83bd 100644 --- a/test/test_main.cpp +++ b/test/test_main.cpp @@ -19,25 +19,28 @@ * along with this program. If not, see . */ #include +#include #include "test.h" -#define EXPORT_TESTLIST -#include "test_list.h" using namespace std; int _failedAssertions = 0; +int testIdx = 0; +list testList; int main() { - int dummy; - IUnitTest** currentTest = testList; - cout << "Starting unit Tests" << endl; - while (*currentTest) + for (UnitTest* currentTest: testList) { - (*currentTest)->RunTest(); - currentTest++; + bool allTestsRun = false; + + for (VoidFunction testCase: currentTest->GetCases()) + { + currentTest->TestCaseSetup(); + testCase(); + } } if (_failedAssertions > 0) @@ -50,3 +53,9 @@ int main() return 0; } + +UnitTest::UnitTest(const std::list* cases) +: _cases(cases) +{ + testList.push_back(this); +} diff --git a/test/test_throttle.cpp b/test/test_throttle.cpp index 17d7661..6726920 100644 --- a/test/test_throttle.cpp +++ b/test/test_throttle.cpp @@ -21,10 +21,14 @@ #include "my_fp.h" #include "my_math.h" -#include "test_list.h" +#include "test.h" #include "throttle.h" -using namespace std; +class ThrottleTest: public UnitTest +{ + public: + ThrottleTest(const std::list* cases): UnitTest(cases) {} +}; static void TestSetup() { @@ -128,10 +132,6 @@ static void TestDualThrottle() Throttle::potmax[1] = 4000; } #endif -void ThrottleTest::RunTest() -{ - TestSetup(); - TestBrkPedal(); - TestRegen(); - //TestDualThrottle(); -} + +//This line registers the test +REGISTER_TEST(ThrottleTest, TestSetup); diff --git a/test/test_vcu.cpp b/test/test_vcu.cpp new file mode 100644 index 0000000..9c854ac --- /dev/null +++ b/test/test_vcu.cpp @@ -0,0 +1,300 @@ +/* + * This file is part of the stm32-... project. + * + * Copyright (C) 2021 Johannes Huebner + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "canhardware.h" +#include "params.h" +#include "errormessage.h" +#include "inc_encoder.h" +#include "digio.h" +#include "hwdefs.h" +#include "pwmgeneration.h" +#include "vehiclecontrol.h" +#include "test.h" + +static uint32_t crc32_word(uint32_t Crc, uint32_t Data); + +static uint32_t crc = 0; +static uint32_t rtc = 0; +static uint32_t speed = 0; +static ERROR_MESSAGE_NUM errorMessage; +static CanCallback* vcuCan = 0; +static uint32_t vcuCanId; + +class VCUTest: public UnitTest +{ + public: + VCUTest(const std::list* cases): UnitTest(cases) {} + virtual void TestCaseSetup(); +}; + +class CanStub: public CanHardware +{ + void SetBaudrate(enum baudrates baudrate) {} + void Send(uint32_t canId, uint32_t data[2], uint8_t len) {} + virtual void ConfigureFilters() {} +}; + +static void FillInCanData(uint32_t* data, uint32_t pot, uint32_t pot2, uint32_t canio, uint32_t cruisespeed, uint32_t regenPreset, uint32_t seq) +{ + uint32_t crc = 0xFFFFFFFF; + data[0] = pot| (pot2 << 12) | (canio << 24) | (seq << 30); + data[1] = cruisespeed | (seq << 14) | (regenPreset << 16); + crc = crc32_word(crc, data[0]); + crc = crc32_word(crc, data[1]); + data[1] |= crc << 24; +} + +static void CanTest1() +{ + ASSERT(vcuCan != 0); + ASSERT(vcuCanId == Param::GetInt(Param::controlid)); +} + +static void CanTest2() +{ + uint32_t data[2]; + + FillInCanData(data, 100, 200, CAN_IO_FWD, 1000, 50, 1); + + vcuCan->HandleRx(vcuCanId, data); + VehicleControl::GetDigInputs(); + VehicleControl::ProcessThrottle(); + + ASSERT(Param::GetBool(Param::din_forward)); + ASSERT(!Param::GetBool(Param::din_reverse)); + ASSERT(Param::GetInt(Param::pot) == 0); //pot=0 because throtmode is not CAN + ASSERT(Param::GetInt(Param::pot2) == 0); //pot2=0 because throtmode is not CAN + ASSERT(Param::GetInt(Param::regenpreset) == 100); //Overwritten by "ADC" value +} + +static void CanTest3() +{ + uint32_t data[2]; + + FillInCanData(data, 100, 200, CAN_IO_START, 1000, 50, 1); + Param::SetInt(Param::potmode, POTMODE_DUALCHANNEL | POTMODE_CAN); + + vcuCan->HandleRx(vcuCanId, data); + VehicleControl::GetDigInputs(); + VehicleControl::ProcessThrottle(); + + ASSERT(Param::GetBool(Param::din_start)); + ASSERT(!Param::GetBool(Param::din_forward)); + ASSERT(Param::GetInt(Param::pot) == 100); //pot=0 because throtmode is not CAN + ASSERT(Param::GetInt(Param::pot2) == 200); //pot2=0 because throtmode is not CAN + ASSERT(Param::GetInt(Param::regenpreset) == 50); +} + +static void TestCanSeqError1() +{ + uint32_t data[2]; + + Param::SetInt(Param::potmode, POTMODE_DUALCHANNEL | POTMODE_CAN); + FillInCanData(data, 100, 200, CAN_IO_START, 1000, 60, 1); + vcuCan->HandleRx(vcuCanId, data); + //call again with same sequence counter -> triggers an error message + FillInCanData(data, 100, 200, CAN_IO_START, 1000, 60, 1); + vcuCan->HandleRx(vcuCanId, data); + + ASSERT(errorMessage == ERR_CANCOUNTER); + ASSERT(Param::GetInt(Param::regenpreset) == 60); //Only after 5 errors will this be reset to 0 +} + +static void TestCanSeqError2() +{ + uint32_t data[2]; + + Param::SetInt(Param::potmode, POTMODE_DUALCHANNEL | POTMODE_CAN); + + for (int i = 0; i < 6; i++) + { + //Call 6 times with same sequence counter -> will trigger unrecoverable error + FillInCanData(data, 100, 200, CAN_IO_START, 1000, 60, 1); + vcuCan->HandleRx(vcuCanId, data); + } + + //Now simulate 500ms or 50 rtc ticks passed + rtc = 51; + VehicleControl::ProcessThrottle(); + VehicleControl::GetDigInputs(); + + ASSERT(errorMessage == ERR_CANTIMEOUT); + ASSERT(Param::GetInt(Param::regenpreset) == 0); + ASSERT(!Param::GetBool(Param::din_start)); + ASSERT(Param::GetInt(Param::potnom) == 0); +} + +void VCUTest::TestCaseSetup() +{ + VehicleControl::SetCan(new CanStub()); + Param::LoadDefaults(); +} + +REGISTER_TEST(VCUTest, CanTest1, CanTest2, CanTest3, TestCanSeqError1, TestCanSeqError2); + +/* Stub functions */ +extern "C" void crc_reset() +{ + crc = 0xFFFFFFFF; +} + +extern "C" uint32_t crc_calculate_block(uint32_t* data, uint32_t len) +{ + while (len--) + crc = crc32_word(crc, *(data++)); + return crc; +} + +extern "C" uint32_t rtc_get_counter_val() +{ + return rtc; +} + +extern "C" void timer_set_oc_value(uint32_t, uint16_t, uint16_t) +{ + +} + +extern "C" void spi_setup() +{ + +} + +extern "C" uint16_t gpio_get(uint32_t port, uint16_t pin) +{ + return 0; +} + +extern "C" void gpio_set(uint32_t port, uint16_t pin) +{ +} + +extern "C" void gpio_clear(uint32_t port, uint16_t pin) +{ +} + +extern "C" uint16_t spi_xfer(uint32_t, uint16_t) +{ + return 0; +} + +bool PwmGeneration::Tripped() +{ + return true; +} + +DigIo DigIo::err_out; +DigIo DigIo::brk_out; +DigIo DigIo::dcsw_out; +DigIo DigIo::prec_out; +DigIo DigIo::vtg_out; +DigIo DigIo::temp0_out; +DigIo DigIo::cruise_in; +DigIo DigIo::start_in; +DigIo DigIo::brake_in; +DigIo DigIo::mprot_in; +DigIo DigIo::fwd_in; +DigIo DigIo::rev_in; +DigIo DigIo::emcystop_in; +DigIo DigIo::bms_in; +DigIo DigIo::ocur_in; +DigIo DigIo::desat_in; +AnaIn AnaIn::uaux(0); +AnaIn AnaIn::udc(1); +AnaIn AnaIn::tmphs(2); +AnaIn AnaIn::tmpm(3); +AnaIn AnaIn::throttle1(4); +AnaIn AnaIn::throttle2(5); + +void DigIo::Configure(uint32_t, uint16_t, PinMode::PinMode) +{ + +} + +uint16_t AnaIn::Get() +{ + return 0; +} + +void ErrorMessage::Post(ERROR_MESSAGE_NUM e) +{ + errorMessage = e; +} + +uint32_t Encoder::GetSpeed() +{ + return speed; +} + +u32fp Encoder::GetRotorFrequency() +{ + return speed; +} + +void Encoder::ResetDistance() +{ + +} + +int32_t Encoder::GetDistance() +{ + return 0; +} + +int Encoder::GetRotorDirection() +{ + return 1; +} + +void Param::Change(Param::PARAM_NUM p) +{ + +} + +CanHardware::CanHardware() {} + +bool CanHardware::AddReceiveCallback(CanCallback* cb) +{ + vcuCan = cb; + return true; +} + +bool CanHardware::RegisterUserMessage(uint32_t canId) +{ + vcuCanId = canId; + return true; +} + +const char* errorListString = ""; +HWREV hwRev = HW_REV3; +uint16_t AnaIn::values[NUM_SAMPLES*ANA_IN_COUNT]; + +static uint32_t crc32_word(uint32_t Crc, uint32_t Data) +{ + int i; + + Crc = Crc ^ Data; + + for(i=0; i<32; i++) + if (Crc & 0x80000000) + Crc = (Crc << 1) ^ 0x04C11DB7; // Polynomial used in STM32 + else + Crc = (Crc << 1); + + return(Crc); +}