【问题标题】:C or C++ preprocessor selecting code blocksC 或 C++ 预处理器选择代码块
【发布时间】:2020-12-29 22:27:09
【问题描述】:

我正在尝试为嵌入式系统编写生产测试软件,我可以在其中编写单个测试脚本(理想情况下只是 C++ 代码),其中部分在主机计算机上执行,部分在 DUT(设备下测试)。通过串口进行通信。

这里的一个重要目标是减少嵌入式端的代码大小,同时不降低测试输出的可读性。所以我的 0 级目标,你可以说是一个热身练习,是能够写出这样的东西:

//TestScript.cpp
START_TESTS() 
...
 const unsigned pot1res = testPotentiometer(pot1);
 TEST_PRINT("Potentiometer 1 test result %u", pot1res);
 const unsigned pot2res = testPotentiometer(pot2);
 TEST_PRINT("Potentiometer 2 test result %u", pot2res);
...
END_TESTS()

它将通过嵌入式端的预处理器技巧和选择性编译来编译

 const unsigned pot1res = testPotentiometer(pot1);
 write_uart(123); //Unique id, perhaps from __COUNTER__
 write_uart(pot1res);
 const unsigned pot2res = testPotentiometer(pot2);
 write_uart(124); //Unique id, perhaps from __COUNTER__
 write_uart(pot2res);

在主机上

std::array gTestStrings = {
        ... other strings ....
    TestString{123, "Potentiometer 1 test result %u", unsigned_tag},
    TestString{124, "Potentiometer 2 test result %u", unsigned_tag},
         ... more strings ....
};

当然,后者的目的是主机软件简单地侦听 UART 的唯一 ID,然后从 gTestStrings 查找所需的参数,接收它们,并将消息打印到其测试日志中。请注意,字符串已从嵌入端完全消失。

这里的嵌入端当然很简单,只要把TEST_PRINT 宏定义清楚就行了,支持可变参数等应该不会太难。但是,不清楚如何定义主机端,因为宏之间的代码必须完全消失。我很确定我可以使用一些模板等正确获取unsigned_tags 等。

标准 C++17 值得赞赏,但如果需要,允许使用 GCC/Clang 细节,预处理器显然会在这方面发挥重要作用。当然,宏的语法也可以在必要时进行调整。

【问题讨论】:

  • 我不明白,你会如何删除const unsigned pot2res = testPotentiometer(pot2); 行? unsigned_tag 是什么?
  • unsigned_tag (这里可能是一个错误的名称选择)是一个标识类型的值。 IE。例如,有时测试结果可能是一个浮点数,然后我们就会有 float_tag。

标签: c++ c-preprocessor conditional-compilation


【解决方案1】:

另一个选项可能是 X 宏:

pot_tests.h

X(123, pot1, "Potentiometer 1 test result %u")
X(124, pot2, "Potentiometer 2 test result %u")

embedded.c

START_TESTS()
 ...
#define X(id, pot, text)                                 \
    do {                                                 \
        const unsigned potres = testPotentiometer(pot);  \
        write_uart(id);                                  \
        write_uart(potres);                              \
        TEST_PRINT(text, potres);                        \
    } while(0);
#include "pot_tests.h"
#undef X
 ...
END_TESTS()

host.cpp

std::array gTestStrings = {
    ... other strings ....
    #define X(id, pot, text)  TestString{id, text, unsigned_tag},
    #include "pot_tests.h"
    #undef X
    ... more strings ....
};

【讨论】:

    【解决方案2】:
    #ifdef __linux__ // or whatever you have there
    #define START_TESTS()     std::array gTestStrings = {
    #define END_TESTS()       };
    #define TEST(expr, str)   TestString{__LINE__, str, unsigned_tag},
    #else
    #define START_TESTS()     {
    #define END_TESTS()       }
    #define TEST(expr, ...)   do{ \
        const auto _val = (expr); \
        write_uart(__LINE__); \
        write_uart(_val); \
    } while(0)
    #endif
    
    int main() {
       START_TESTS()
          TEST(testPotentiometer(pot1), "Potentiometer 1 test result %u");
          TEST(testPotentiometer(pot2), "Potentiometer 2 test result %u");
       END_TESTS()
    }
    

    我不是特别喜欢这种设计 - 对我来说似乎不够灵活。但是这样的代码可以作为你编写更好东西的模板。我在 TEST 宏中添加了表达式 - 这样它就可以在主机端删除,并在设备端使用。

    【讨论】:

      【解决方案3】:

      在“使用模板对不同类型做同样的事情”的基础上,您可以简单地定义一个模板化的类或方法,根据是否在嵌入上下文或宿主上下文。


      伪代码示例:

      #include <sstream>
      #include <string>
      
      class Mock
      {
      protected:
          // TODO : output values array / map - by - testID
      
      public:
          unsigned readPot(int const testID)
          {
              // TODO : return value from the values array/map
          }
      
      };
      
      class ActualDevice
      {
      public:
          unsigned readPot(int const testID)
          {
              // TODO : write/read the device
          }
      
      };
      
      //DEVICE_IMPL must implement a method with this signature:
      //unsigned readPot(int const testID)
      template<typename DEVICE_IMPL>
      void runTests()
      {
          DEVICE_IMPL device;
          std::string testOutput;
          
          //TODO : substitute with for-loop to run all tests
          int testID = 1;
          {
              unsigned output = device.readPot(testID);
              std::stringstream accum;
              accum << "Test " << testID << " output = " << (int)output << std::endl;
              testOutput = accum.str();
              // TODO : do something with testOutput
          }
      }
      
      void testHost ()
      {
          runTests<Mock>();
      }
      
      
      void testDevice()
      {
          runTests<ActualDevice>();
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-08-29
        相关资源
        最近更新 更多