Preparing the hardware

The Afro ESC supports different ways of communication (PWM, I2C, UART), but PWM is most common for these and those pins are nicely exposed with a JR-Style Servo cable.

The Edisons PWM block did just have the pins exposed as some holes, so I got cables with the counter part (based on the form of the actual contacts, the ESCs have female connectors, so I needed male ones, but based on the overal form of these it would be the other way around, and both ways are sometimes used...).
I soldered those cables to the board and connected a jumper for it to use the system voltage to power the PWM, otherwise I would have had to connect an additional power source to the PWM board.

Also these ESCs have a BEC exposed via the same cable, which is unless otherwise stated a 5V source which can be used to power things. In my case it provides a current of up to 500mA, which causes this to be perfectly within the USB specifications.
The BEC is between the red and brown cable coming from the ESC, while the PWM signal will need the orange and the brown cable. Thus I did not attach the red cable to the Edison via the PWM block but instead cut a micro USB cable in half and attached the BEC of one of the ESCs to the USB cable and plugged it into the Edisons base block to power it with. I didn't not use the power block or anything because the Edison does not support full 5V, but the baseblock does some magic to make it work with USBs 5V.

Talking to the ESCs

PWM or pulse width modulation works by sending a high signal for some amount of time and the receiver measures how long the high signal was sent and knows what to do based on that time.
A high signal is anything above 1.5V or something for most of the RC stuff.
In general in the RC world the high signal (or pulse) has a length of around 1ms to indicate a minimum value (the motor does not rotate/the servo gets rotated to its lowest position) and a length of about 2ms for the highest value (motor at full speed/servo fully rotated). Looking at the sparse documentation of these ESCs I found a lowest value at 1060μs and the value for full power at 1860μs.

Mostly for safety reasons ESCs usually don't allow to start with a rotation, instead wait for an arm value which is something below the minimum value and once that was sent at least one time a speed can be provided.
I used an arm value of about 1000μs but it can also be somewhat smaller and bigger.

The signal needs to be sent constantly though. Any frequency higher than 20Hz or so works, but due to the way some ESCs work there is a habit of sending at much higher frequencies. Just keep in mind that to send a 2ms signal you can't go faster than 1s/2ms = 500Hz. I am currently sending at around 400Hz.

PCA9685

That is the PWM controller used by the Sparkfun block. It is made for dimming LEDs, but also works for other usecases...
It supports 24Hz up to 1526Hz, communicates via I2C and has a 12bit resolution of 4096 steps.
The 12 bit resolution means that when for example operating at 100Hz the resulting 10ms (1s/100) are split into 4096 evenly spaces parts and the pulse length has to be a multiple of 10ms/4096 = ~0.0024ms.
And instead of sending a time to the PWM controller, it expects a number of steps. The PWM controller then scales this with the value it knows based on what its pre scale register is set to. This same pre scale register also sets the signal frequency.

The code

Fortunately Sparkfun provides some code for the PWM block which takes care of the I2C communication and provides some functions to set stuff (but none takes a time directly...).

So far I just wrote some very minimalistic wrapper over their code initializing everything with what they provide as "servo-mode", set a higher frequency (prescaler to 14 for ~400Hz) and then send an arm signal.
Once that is done a speed as value between 0 and 1 can be set for each motor.

I thought I figured out the math for the correct timings, but it turned out that the actual number of steps was quite different, so my math was probably wrong...
I found those by trial and error (prescaler 14):
Arm - 1730
Minimum - 1960
Maximum - 3500

The code is on github.

My toolchain

The Edison runs Yocto in the latest version for it provided by Intel.
I am connecting to it via SSH over Wifi and compile directly on it using GCC.
My IDE of choice is CLion which bases it's projects on CMake and has a feature to directly transfer the files to a server which in my case is via sftp to the Edison.
I plan on improving it by adding custom build configurations running the same stuff I currently use the terminal for, but I am not sure if that will really make anything better...

Profit!