MSXV New Testing Framework [WIP]
This is for design testing only. Not for hardware tests.
3 Types of testing
hardware tests (only on hardware)
design tests (only on simulation)
smoke tests (both?)
Due to the inclusion of FreeRTOS, tasks and functions must be designed in a way for ease of testability. Testing is super important especially in a real vehicle and it’s much more important that a vehicle is debug-able and understandable instead of optimal.
Newest VSCode has great use of the debugger:
Should use with JSON
{ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "(gdb) Launch", "type": "cppdbg", "request": "launch", "program": "/home/vagrant/shared/fwxv/build/x86/bin/projects/leds", "args": [], "stopAtEntry": false, "cwd": "/home/vagrant/shared/fwxv", "environment": [], "externalConsole": false, "MIMode": "gdb", "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true }, { "description": "Set Disassembly Flavor to Intel", "text": "-gdb-set disassembly-flavor intel", "ignoreFailures": true } ] } ] }
Inter Task communication
So there are different way tasks can communicate with each other, thus different ways for the tests to snoop data. The different ways in FreeRTOS are as follows
Global Variables
Queues
Messages
Stream Buffers
Function overloading
Global Variables
Global variables are defined on the top of the .c
file and are usually static
. This means that the variable is confined within the file its defined in. Since this is for design testing, we will predominately use these to snoop the internal data.
Queues
FreeRTOS queues is a FIFO that can hold a specific number of elements of a fixed size. This is usually used within a project. Tests should not access queues directly as that can create more issues.
Messages
Message buffers are variable length messages that can be sent from an ISR to a task or from task to task. Currently not really using it, but if we did, it would be complicated to test. Thus will not be accessing message buffers directly in tests.
Stream Buffers
Stream buffers are similar to messages except that it sends a stream of bytes one at a time. Since this is dynamically allocated we will not use this, but again should not be used directly in tests.
Function Overloading
Function overloading is a great way to change a function behaviour. Using this, its possible to change inputs and change firmware behaviour in the middle of a run. We can use function overloading to get the data that should be returned to a function, or change the output accordingly. This is what was predominately used in MSXIV and we could use it again, however, not strictly necessary.
Summary:
So in order to separate project functionality vs project testing, it’s important to keep them as far as part as possible. We want to keep projects as “black box” as possible in order to validate inputs and outputs. So as long as functions are black box, it’s possible and useful to do function overloading. However, something like overloading an entire task is not useful since we’re not testing anything useful.
Thus, it’s important to minimize the effect that tests have on the actual project to make sure that what is being tests is project functionality only.
Design of Testing Architecture (Steering)
So the design of the testing architecture will be similar to the CAN architecture. See CAN Autogeneration for more details.
Essentially since this is design testing and simulation only, don’t need to worry about the memory usage. So we will defined output and input global variables to evaluate “black box” testing.
I will be using the “steering” project from firmware_xiv
for how describing the new architecture.
Steering main():
So steering starts off with initializing all its module, then start processing the event.
Steering has 3 main functionalities:
Processing digital inputs
Processing analog inputs
Processing CAN data
Processing Digital Inputs
This is in steering_digital_input.c
and essentially it sets GPIO
interrupts to raise an event if an interrupt occurs.
Processing analog inputs
This is in steering_control_stalk.c
and essentially it calls an ADC
read periodically. Every ADC
read, it’ll run the callback and check the value parameters. It will raise an event if the ADC
read value satisfies the conditions.
Processing CAN data
This is in steering_can.c
and just sends CAN messages based on the event that’s read.
Steering Summary:
Thus there are 3 main functionality of steering
which will roughly translate to their own TASKS.
Project Design
The project design for steering
should be as follows
initialize all GPIOs, SPI, etc
initialize all TASKS
start TASKS in cycle
cycle = 0
while (true)
CAN receive Task -> Non-concurrent
GPIO Tasks -> Concurrent
ADC Tasks -> Concurrent
CAN Send Task -> Non-concurrent
cycle++
#ifdef TESTING
post(cycle_finish)
mutex(test_mux)
#endif
Projects should start with initialization of all modules and then start running each tasks. Each tasks should be run in what’s called a cycle.
So the CAN receiving tasks starts first. This is because other tasks depend on the CAN messages coming in.
Next the GPIO and ADC tasks can run. These don’t depend on each other, thus can be parallel
Finally CAN send tasks runs last. This depends on GPIO and ADC to finish before running.
In the case of testing, there’s 2 additional functions to be run. A post
operation indicating the end of a cycle, and a mutex
operation that will block the tasks from running.
The test that is run will look something like this
void test_setup()
{
setups all the modules
starts the tasks
}
void run_cycle(int c)
{
for (int i = 0; i < c; i++)
{
mutex(cycle_finish);
post(test_mux);
}
}
void test_steering()
{
set steering inputs
run_cycle(1);
trigger GPIOs
run_cycle(1);
change ADC values
run_cycle(5);
....
}
And this should be the framework. While the test_steering
input is running, all other tasks should be blocked. Once test_steering
runs run_cycle
then test_steering
is block and all other tasks should run.