STM32/ARM Cortex-M3 HOWTO: Development under Ubuntu (Debian)

Automatic testing.

Well until now everything has been done manually. Write some code, compile it and put it on the target to see if it is working.

However would it not be nice to start to create reusable functions that is proved to work, that you then can put to use at any given time?

So what do we need to start doing this type of work with what we have today?

A good place would probably be to write a simple main.c file, that calls a function to test and then looks at the return value to find out if that was ok or not.

Filename: main.c

void test_failed(int number) {
    while(1);
}

void test_success(int number) {
    while(1);
}

int main(void) {
    int status = start_test();

    if( status > 0 ) {
        test_failed(status);
    }
    test_success(0);

    while(1);
}

And then write a function to test.

Filename: test01.c

#include <stdio.h>
#include <stdlib.h>

/**
 * Start test ...
 *
 * @return 0 is success, 1.n is failure at subpart n
 */
int start_test()
{
    int test = 1;

    int a = 0;
    int b = 0;
    int c = 0;

    a = 10;
    b = 10;
    c = a * b;

    if (c != 100) {
        return test;
    }

    return 0;
}

This test checks if we can multiply two int:s with each other, so it is really a basic test. But it is good to know that this basic building block is working.

Then if we build and flash and debug the code we can end up in 3 different places.

So why not add those three places as breakpoints in gdb, and start him directly? So let's create a file that gdb can read, and call it commands.gdb.

Filename: commands.gdb

break test_success
break test_failed
break Default_Handler
target remote localhost:3333
cont 
frame

To start gdb with this file would then look something like this.

arm-none-eabi-gdb --command=commands.gdb main.elf

If we look at what will happen is more or less, that we first add some break points. Then connect to the gdb server and then cont (continue) until something happens. Then we execute frame and get some stack information.

But since we then have to quit gdb manually, and the solution is to add the magic word --batch. Batch will force gdb into batch mode that more or less mean that he will execute what he can and then quit.

arm-none-eabi-gdb --batch --command=commands.gdb main.elf

And the printout more or less looks like this, and here we can see that it was a big success since we ended up in test_success.

Breakpoint 1 at 0x28: file src/main.c, line 9.
Breakpoint 2 at 0x1c: file src/main.c, line 5.
Breakpoint 3 at 0x10
main () at src/main.c:13
13  int main(void) {

Breakpoint 1, test_success (number=0) at src/main.c:9
9       while(1);
#0  test_success (number=0) at src/main.c:9
9      while(1);

So that was nice, but what if we would like to add more tests? I mean we can't just have this one silly test?

Let's write another test but with floats.

Filename: test02.c

#include <stdio.h>
#include <stdlib.h>

/**
 * Start test with foat:s
 *
 * @return 0 is success, 1.n is failure at subpart n
 */
int start_test()
{
    int test = 1;

    float x = 2;
    float y = 4;
    float z = 8;

    if( (x*y) != 8 ) { 
        return test;
    }

    test++;

    if ( (y/x) != 2 ) {
        return test;
    }

    return 0;
}

As you can see we use the same start function, start_test. So we can't compile this at the same time as the other test. But on the other hand we do not want one big program with all future test either!

Therefor we must play a little with make so we can link test01 and test02 separately against main.

Normally we would have a part in the Makefile that looks like this.

main.elf: src/stm32.ld main.o startup_stm32f10x.o test01.o
	$(LD) $(LFLAGS) -o main.elf main.o startup_stm32f10x.o test01.o

test01.o: test01/test01.c 
	$(CC) $(CFLAGS) -o test01.o test01/test01.c

But that would mean that we have a dependency from main towards test01, and if we then add test02 we still have that collision. So if we add a variable in the Makefile that tells us to link with test01.

TEST = ""

main.elf: src/stm32.ld main.o startup_stm32f10x.o $(TEST).o
	$(LD) $(LFLAGS) -o main.elf main.o startup_stm32f10x.o $(TEST).o

And then provide the variable TEST as a argument, like this.

make TEST=test01

Then $(TEST).o will be replaced by test01.o and we have exactly the same rules as before.

And if we provide something else, like test02, he will search for the a build rule to create test02.o and not test01.o. And we therefor do not have a strict dependency between those anymore.

make TEST=test02

Therefor a Makefile can look something like this.

Filename: Makefile

CC      = arm-none-eabi-gcc
LD      = arm-none-eabi-gcc 
AR      = arm-none-eabi-ar
AS      = arm-none-eabi-as
CP      = arm-none-eabi-objcopy
OD      = arm-none-eabi-objdump

MCUFLAGS = -mcpu=cortex-m3 -mthumb 

CFLAGS  =  -I./ -c -fno-common -O0 -g $(MCUFLAGS) -mfix-cortex-m3-ldrd
AFLAGS  = -ahls $(MCUFLAGS) 
LFLAGS  = -Tsrc/stm32.ld -nostartfiles $(MCUFLAGS) -mfix-cortex-m3-ldrd

CPFLAGS = -Obinary
ODFLAGS = -S

all: test


#TEST := test01
TEST = ""

test: main.elf
	@ echo "...copying"
	$(CP) $(CPFLAGS) main.elf main.bin
	$(OD) $(ODFLAGS) main.elf > main.lst

main.elf: src/stm32.ld main.o startup_stm32f10x.o $(TEST).o
	@ echo "..linking"
	$(LD) $(LFLAGS) -o main.elf main.o startup_stm32f10x.o $(TEST).o


# 
# OBJ
#
main.o: src/main.c
	@ echo ".compiling"
	$(CC) $(CFLAGS) src/main.c

startup_stm32f10x.o: src/startup_stm32f10x.s
	@ echo ".assembling"
	$(AS) $(AFLAGS) -o startup_stm32f10x.o src/startup_stm32f10x.s > startup_stm32f10x.lst


#
# OBJ from Tests
#
test01.o: test01/test01.c 
	@ echo ".compiling"
	$(CC) $(CFLAGS) -o test01.o test01/test01.c

test02.o: test02/test02.c
	@ echo ".compiling"
	$(CC) $(CFLAGS) -o test02.o $<





.PHONY: flash
flash: all
	scripts/do_flash.pl main.bin

.PHONY: clean 
clean:
	-rm -f main.o startup_stm32f10x.o test*.o 
	-rm -f main.lst main.elf main.bin startup_stm32f10x.lst 

.PHONY: clean_all
clean_all: clean
	-rm -f log.*

Now we are getting close, we can now select what test code to execute and flash (since we have this do_flash.pl script). So we should add a test runner script on top.

Filename: run_test.bsh

#!/bin/bash 


function did_it_work {
    code=$1
    #echo "did: "$code
    if [ ! $code = 0 ]
    then
        echo "Error failure: code $code "
        exit 1
    fi
}

function run_test {
    tes=$1
    log=$2

    echo "Begin test: $tes ( $log )"

    make flash TEST=$tes
    did_it_work $?

    #sleep 3

    if [ ! -f main.elf ]; then
        echo "Missing main.elf"
        exit 1
    fi

    arm-none-eabi-gdb \
        --eval-command="set logging on" \
        --batch --command=res/commands.gdb \
        main.elf
    #scripts/gdb_runner.pl main.elf $log
    did_it_work $?

    mv gdb.txt $log

    make clean
    did_it_work $?

    sleep 1

    echo "End test: $f"
    echo ""
}




CWD=`pwd`

#source ~/stm32/bin/stm32_setup.sh 
#did_it_work $?


scripts/start_server.bsh 
did_it_work $?

make clean
# No check.

#for f in $(find -name *.c | sed 's/\.\/\(test[0-9][0-9]\).*/\1/');
for f in $( ls -1 -d test* );
do
    log=$CWD/log.$f.txt

    run_test $f $log

done

echo 
echo "Results:"
echo " OK:   "`grep test_success    log.test*.txt | grep Breakpoint | wc -l`
echo " FAIL  "`grep test_failed     log.test*.txt | grep Breakpoint | wc -l`
echo " Err   "`grep Default_Handler log.test*.txt | grep Breakpoint | wc -l`
echo 

for f in $( ls -1 log.test*.txt );
do
    if [ "`grep test_success $f | grep Breakpoint | wc -l`" = "1" ]
    then 
        echo $f" - OK"
    fi

    if [ "`grep test_failed $f | grep Breakpoint | wc -l`" = "1" ]
    then 
        echo $f" - FAIL"
    fi

    if [ "`grep Default_Handler $f | grep Breakpoint | wc -l`" = "1" ]
    then 
        echo $f" - Error"
    fi

done


exit 0;


# kill `ps -A | grep openocd | awk '{print $1}'`

This script more or less check for dirs called testXX and then assumes that we have a make rule in the Makefile that matches that name. And then make, flash and start it on the target device. Just to start over with the next one.

Then at the end it closes down with a little sum up, on how the test did.

More or less.

  1. Build the test
  2. Flash with this test
  3. Start the test on the device
  4. Repeat until all test is done.

A simple but effective test suite, that could now be modified into what you need.

github

I use this test suite to verify that gcc is working, it is nice to know that the basic tool chain is doing it's job.

You can find this little project over at github where I call it FunTechCortexMX_test.

  • https://github.com/jsiei97/FunTechCortexMX_test
  • /Have fun.

    This page is licensed with Creative Commons ShareAlike 3.0