When I fixed my stupid error earlier, I got stdout/printf working. I then poked a lot of simple examples and started to understand how to write simple contiki apps using protothreads. It’s really pretty simple, as long as you remember that it’s all done with switch/case magic, so you can’t use stack variables the way you might expect. This is ok. This makes you think more about where you data lives and what you’re passing around. With stdout working though, I started to go through what else was needed.
Contiki offers ~4 different apis for doing things at some point in time. ctimers, etimers, stimers and rtimers. (And also with the clock_* api, which are for when you reallllllly want to do some busy waiting. Don’t do that!) This wiki page was very helpful in understanding the differences, and even having some “porting guide” information. This is an area I feel contiki is very weak in, the almost complete lack (to my eyes) of a porting guide. There’s quite a few different ports in the tree, but they’re often implemented in quite radically different ways, and some of them appear to be unmaintained.
But, the good thing about all these timers, is that’s all core. Your port only needs to implement clock.c, and it’s basically all taken care of! Neat! Except rtimers. rtimers are the realtime timers, used for turning radios on and off at the right times to synchronize. When I get to the radio (soon) I’ll be looking at other implementations and seeing what’s best.
As it stands though, ctimers, etimers, timers and the clock routines are all working in my port, and I’d played with some simple play applications to test out how they worked. I then tried to get the shell working. So, this is an app. So you need to put APPS+=serial-shell into your makefile. (Remember, this is the makefile for your final application) Then, because the AUTOSTART_PROCESSES() macro can only be used once in an application, the apps you can include can’t use that macro. You need to start them yourself. Here’s what my “demo” playground main application process looked like:
PROCESS_THREAD(foo_process, ev, data)
{
PROCESS_BEGIN();
printf("Hello foo world\n");
leds_blink(); // oh yeah, the leds api works for the stm32l discovery board too ;)
serial_shell_init(); // starts the serial shell process
// these are all commands the shell can run.
shell_ps_init();
shell_blink_init();
shell_powertrace_init();
// these are experiments with my own applications
static struct blipper_info bl1 = { CLOCK_SECOND * 2, 'a' };
static struct blipper_info bl2 = { CLOCK_SECOND * 5, 'b' };
process_start(&blipper_process, (void*)&bl1);
process_start(&blipper2_process, (void*)&bl2);
PROCESS_END();
} |
PROCESS_THREAD(foo_process, ev, data)
{
PROCESS_BEGIN();
printf("Hello foo world\n");
leds_blink(); // oh yeah, the leds api works for the stm32l discovery board too ;)
serial_shell_init(); // starts the serial shell process
// these are all commands the shell can run.
shell_ps_init();
shell_blink_init();
shell_powertrace_init();
// these are experiments with my own applications
static struct blipper_info bl1 = { CLOCK_SECOND * 2, 'a' };
static struct blipper_info bl2 = { CLOCK_SECOND * 5, 'b' };
process_start(&blipper_process, (void*)&bl1);
process_start(&blipper2_process, (void*)&bl2);
PROCESS_END();
}
Note that serial-shell is actually just a wrapper around the shell app.
Note that you need to call a shell_XXXXX routine for each command you need to add. Note well that that is not documented anywhere. You actually have to look in [contiki-root]/apps/shell
for all the shell-xxx.c
files that call shell_register_command()
Or, have a look at [contiki-root]/examples/example-shell/example-shell.c
Ok, so you got a shell application built and flashed. But… it doesn’t work! I ran into two problems here. One of them was very well documented, I just didn’t read it. I was used to having to implement newlib syscalls like _read()
and _write()
, but you actually need to not implement _read()
, and make sure you just follow the directions!.
Unfortunately, in my case, this wasn’t enough. I could run my application with the native target, but not on my stm32. Again, reading the documentation, contiki processes serial input line by line, looking for a line feed (hex 0xa) to mark the end of line, and completely ignoring carriage return characters (hex 0xd) With gdb I could see that on the native platform, entering a command and pressing “enter” sent only a LF character, and it worked. With picocom and a USB-serial adapter to my board, I was seeing only a CR character, which was ignored. Wikipedia has a lot to say about this, and some of the helpful people on ##stm32 pointed out that this was because my serial device was in “cooked” mode, and I could put it back to raw mode to send the “right” characters. Turns out lots of terminal software has to deal with this, and miniterm.py (Something I just happened to have installed anyway) by default does the “right thing” (or the other thing) There were some suggestions that contiki should probably be more flexible in it’s input, and deal with both either, or, and both, but not require a specific one. That’s a future debate, but not one I’m battling now.
With a different serial terminal program, all of a sudden I had a console, and a shell. Here’s a full log of my app running, and me typing, “ps” and “help”
--- Miniterm on /dev/ttyUSB2: 115200,8,N,1 ---
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
Platform init complete
Hello foo world
Hello blipper id: a!
Started timer
Hello blipper2 id: b!
Started timer
Should get printed after any event, even if it wasn't ours....
callback about to be set...
Should get printed after any event, even if it wasn't ours....
0.0: Contiki>
Should get printed after any event, even if it wasn't ours....
Processes:
ps
periodic blipper2 process
periodic blipper process
Shell server
Shell
Contiki serial shell
Ctimer process
Event timer
Serial driver
Should get printed after any event, even if it wasn't ours....
0.0: Contiki>
a hit timer expiry tick: 0 at clock time: 2
Should get printed after any event, even if it wasn't ours....
In the callback!
Should get printed after any event, even if it wasn't ours....
Available commands:
?: shows this help
blink [num]: blink LEDs ([num] times)
exit: exit shell
help: shows this help
kill : stop a specific command
killall: stop all running commands
null: discard input
powertrace [interval]: turn powertracing on or off, with reporting interval
ps: list all running processes
quit: exit shell
Should get printed after any event, even if it wasn't ours....
0.0: Contiki>
a hit timer expiry tick: 1 at clock time: 4
Should get printed after any event, even if it wasn't ours....
b hit timer expiry tick: 0 at clock time: 5
b Should only print after our event....
--- exit ---
Note that there is no local echo of commands! (Again, this can be turned on by the terminal program, but often it’s handled by the far side’s shell implementation)
But, all’s well that ends well. At this point I have what’s a pretty complete port of the core of contiki to the STM32L discovery board, and all the code that is common for any stm32 (and mostly, any chip using libopencm3) is in the cpu section of contiki, rather than the platform code.
Now, it’s just time to start the radio drivers, and work out the best way of implementing the rtimers!
My Platform port: https://github.com/karlp/contiki-outoftree This is all you need to target the STM32L discovery, it includes my changes to Contiki, as well as libopencm3 as git submodules.
Contiki port: https://github.com/karlp/contiki-locm3/tree/locm3 You’ll need this if you want to make another platform port based on libopencm3