STM8-Dev-Board

STM8-Dev-Board-STM8S003F3P6

View on GitHub

Migrating existing code from C++ to C syntax

The original Arduino environment uses C++ syntax while sduino can only use plain C syntax. Luckily, not many C++ features are used and in most cases a conversion is not very hard. In most cases a conversion from C++ to C it is just a matter of exchanging a dot for an underscore:

A C++ method name class.method() becomes a C function name class_method().

This is possible since most libraries are written to be used as a singleton anyway, so the fixed name prefix is not a problem. Only very few libraries need a slightly more complex approach to be able to deal with multiple instances.

There are two bigger problems left:

Polymorph functions

The concept of polymorphism does not exist for plain C. As a workaround ‘mangled’ function names are used for the different parameter type combinations supported by the original polymorph methods.

Typical name extensions are: _u for unsigned values, _i for signed integer values, _cfor characters, _s for strings, _n for data buffer/length combinations.

For more non-regular polymorphism the name extension is often related to the different use cases or to the names of the given parameters. Refer to the respective library header file for details.

Some examples of typical name changes:

C++ name C name
Serial.print(int) Serial_print_i
Serial.print(unsigned) Serial_print_u
Serial.print(float) Serial_print_f
Serial.print(char) Serial_print_c
Serial.print(char *) Serial_print_s
Serial.print(char *buf, int len) Serial_print_n
Serial.print(unsigned n, int base) Serial_print_ub
random(long) random
random(long howsmall, long howbig) random_minmax

The _Generic selection

The C11 standard introduced the _Generic selection function. This function allows for automatic selection of different function variants at compile time depending on the type of the function arguments mimicing polymorph C++ functions.

Recent versions of SDCC already support this function (command line argument --std-sdcc99), but it’s usefulness is still limited to some special cases.

#define Serial_print(X) _Generic((X), \
    char*: Serial_print_s, \
    signed long: Serial_print_i, \
    signed int: Serial_print_i, \
    signed char: Serial_print_c, \
    unsigned long: Serial_print_u, \
    unsigned int: Serial_print_u, \
    unsigned char: Serial_print_u \
    )(X)

This would unify some, but not all print function variants:

C++ name C name using _Generic
Serial.print(int) Serial_print
Serial.print(unsigned) Serial_print
Serial.print(char) Serial_print
Serial.print(char *) Serial_print_s
Serial.print(char *buf, int len) Serial_print_n
Serial.print(unsigned n, int base) Serial_print_ub

Unfortunately cpp does not match string constants and char* resulting in a very non-regular usage pattern:

char *string="Hello";

Serial_print(string);	// works
Serial_print("Hello");	// doesn't work
Serial_print_s("Hello");// works

To avoid too much confusion it might be better to not use _Generic at all.

Another problem using the _Generic selector is configurable instance names. The preprocessor does not allow for variable macro names. That means _Generic would work with fixed name like Serial, but it wouldn’t work for SoftwareSerial with no standard instance name.

This is a good introduction into the use of _Generic.

Inheritance from Print class

Most character output modules inherit methods from the Print class by providing a virtual write method. A similar result can be achived by providing a function pointer to the write function to be used to the print functions.

This additional parameter is hidden from the user by providing more convinient defines in every library that need to ‘inherit’ functions from Print. This way lcd.print("Hello World!") becomes lcd_print_s("Hello World!") and Serial.print("Hello World!") becomes Serial_print_s("Hello World!"). Both call the same code from Print, but with different function pointers to their own putchar/write function.

Libraries with multiple instances

If multiple instances need to be supported, an approach similar to a C file descriptor is used. The “constructor” function allocates and initializes a data structure for one particular instance and return a pointer to this structure. Typically, this is done in the setup()-function and this pointer is stored in a global variable to be used as a device descriptor.

So far the Stepper library is the only example:

#include <Stepper.h>
Stepper myStepper;		// variable to store the "device descriptor"
void setup(void) {
	myStepper = Stepper_2phase(100,11,12);	// init a 2 phase stepper
}
void loop() {
	Stepper_step(myStepper, 20);		// do 20 steps forward
}

Differences from the original Arduino environment

Additional output pin modes

Pin mode Pin properties
OUTPUT output, push-pull, slow mode (default)
OUTPUT_OD output, open drain, fast mode
OUTPUT_FAST output, push-pull, fast mode
OUTPUT_OD_FAST output, open drain, fast mode

Timer

millis() uses timer4. The prescaler and end value is calculated at compile time for a cycle time as close to 1ms as possible. Default values @16Mhz: prescaler=64, counter cycle=250 (end value=249), resulting in exactly 1ms intervals.

timer1: PWM for PC3, PC4, on alternate mapping PC6, PC7, could be used for ADC
timer2: PWM for PA3, PD3, PD4 or PC5 (mutual exclusive)
timer4: millis()

Leaving out unused parts

Some functions of the core Arduino system can be left out on compilation to save code space. This is done by compiler flags that can be defined in the Makefile:

BOARD_TAG = stm8sblue
CFLAGS = -DNO_SERIAL -DNO_ANALOG_IN -DNO_ANALOG_OUT

include ../../../sduino.mk

These flags are supported:

Flag Bytes saved Functions lost
NO_SERIAL 765 all serial communication
NO_ANALOG_OUT 406 analogWrite()
NO_ANALOG_IN 56 analogRead()

Other modifications

makeWord(unsigned char, unsigned char) is an inline function now.

Additional compile-time flags

Some internal details can be influenced by setting compile-time defines using the CFLAGS=-Dflagname line in the Makefile.

SUPPORT_ALTERNATE_MAPPINGS: Allow the use of alternateFunctions()

ENABLE_SWIM: Do not disable the remote debugging function on the SWIM pin. This means that this pin can not be used for normal I/O functions.

USE_SPL: Use SPL functions for I/O access instead of direct register accesses. Useful only for debugging and porting to other CPU variants. Do not use for regular development.

General notes on the Arduino port

ADC

the prescaler is initialised for an ADC clock in the range of 1..2 MHz. The minimum prescaler value is 2, so for a clock speed of less than 2 MHz the required minimum ADC clock frequency can not be reached anymore.

Mapping of logical pin numbers to register addresses

The lookup-table approach for assigning port and bit adresses to the logical pin numbers is not effient on the STM8. The hole system could be changed to a more regular scheme and replace the tables by hardcoded adress calculations.

Inefficient compilation

digitalWrite compiles very ineffiently. It might be worth some hand optimization.

Accessing the alternate pin functions

Added alternateFunction() to allow switching of some pins to their alternate functions. This allows for three more PWM pins, but maybe it adds to much complexity for the Arduino API. Not sure if it should stay. Has to be enabled by defining SUPPORT_ALTERNATE_MAPPINGS.

Useful CPU features that are not supported by the Arduino API

Input-Capture-Mode: Available for all four channels, at least for timer1. Would be great for precise time measurements. Maybe build a library?

Encoder interface mode: Hardware support for reading quadrature encoder and keeping track of the current (motor) position using a hardware timer counter. Perfectly suited to all kinds of position feedback.