LinuxCNC on OrangePi

Linux CNC

If you want to use a LinuxCNC for your machine, you either take old PC with parallel port from scrap, or buy an expensive MESA card, but there's a better and cheap way, you can use the OrangePi directly and do all the stepping magic on the internal RISC coprocessor.


The LinuxCNC is an opensource controller for CNC machines (various kinds are supported - mills, lathes, robot arms, etc.). It can run on an ordinary PC with Linux (preferably Debian) installed.

When it comes to interfacing to outer world, there are many options. The most simple and straightforward is using a parallel port, which is nowadays obsolete and hard to find, also achieving a high frequency and precise signal generation for stepper motors is quite hard on the non realtime OS. The second option involves buying an expensive MESA cards or other specialized hardware which can do the stepping for you. Of course, the parallel port or specialized cards can only provide you with the basic IO signals, you have to connect them to the stepper drivers, spindle control, endstops inputs, etc. but this is quite generic for all CNC control systems (unless the drivers and stuff is already integrated to the control board).

Recently a guy from Russia ported all the step generators and other LinuxCNC stuff to the RISC coprocessor inside Allwinner H3/H5/H6 based single board computers, like OrangePi PC. Basically, the LinuxCNC runs under Linux on the main CPU inside the Allwinner SOC and all the heavy lifting is done on a small RTOS CPU embedded inside the SOC. The code running on the RTOS is completely separated from the Linux, there's no OS involved, therefore it could be optimized for a realtime performance needed for precise signal generation. It's similar to using a MESA card, but without any external hardware.

The project is named allwincnc. It went through a rapid development and most of the stuff already works. It's currently (fall of 2021) archived as the global semiconductor outage affected availability of the Allwinner based SBCs and the author doesn't have enough HW to test on.

Btw, if you have only Raspberry Pi around, you can check the project Remora which is using a 3D printer control board Smoothieboard connected over SPI to do the heavylifting for the RPi.

Custom CNC shield

When it comes to the wiring your OrangePi to the outer world, you will need some additional hardware. The I/Os of the OrangePi are 3,3 V (some, like UART, are 5 V tolerant) and can provide a very small current, so you need some level shifters and transistor/relay outputs to make it usable. You can definitely buy several cheap chinese modules with level shifters or board of relays, but the result won't look that professional and reliable.

Therefore I've decided to design a custom shield for my OrangePi PC. The shield provides a basic separation between the OrangePi and external devices (using the 74HC4050 level shifter, no optocouplers used) with up to 15 V inputs. The low power outputs (stepper signals) can be switched between 3,3 and 5 V by a solder bridge, high power outputs support up to 24 V. All IOs are connected to LEDs for easier debugging.

All the design files can be found on my GitHub.


The shield provides following interfaces:

  • 5 stepper controllers (direction and step signals) supported
  • 3 encoders input
  • Spindle control - both PWM and 0-10 V outputs
  • 6 limit switches (endstops, home position,...)
  • Probe sensor input
  • Stop button input
  • 2 power outputs (cooling, ESTOP,...)

The OrangePi can be powered through the shield itself (dedicated 5 V input connector on the shield), or in reversed manner, the OrangePi can power the shield without need for any external power (with exception of high power outputs and 0-10V for spindle control).

The board itself doesn't contain any stepper drivers, this is intentional as it can be connected to any steppers, from small Nema17 like on your 3D printer, to much more powerful ones. This way, you can use any stepper driver you want and simply wire the step/direction signals to it. On the MPCNC I've decided to use the cheap chinese TB6600.


The installation is not that straightforward. A patched Linux kernel is needed for a proper communication between Linux and the RISC coprocessor, therefore not every Debian version out there will work for you. Look for kernel versions older than 5.10.34. I'm using Armbian_21.02.3 with linux version 5.10.21 on my Orange Pi PC.

Once you boot into fresh Armbian, follow these steps:

  • Disable kernel and BSP updates - Freeze system option under armbian-config command
  • Enable desktop environment under armbian-config System tab
  • Update rest of the system - apt update && apt upgrade
  • Clone the installer - git clone
  • Install the allwincnc - cd installer && ./
  • Reboot to load new kernel
  • Now you can Launch LinuxCNC and have fun.

    If you encounter issue with missing gtksourceview module when launching linuxcnc, do:

    • wget
    • dpkg -i python-gtksourceview2_2.10.1-3_armhf.deb - this will fail on dependencies
    • apt install -f install the dependencies
    • dpkg -i python-gtksourceview2_2.10.1-3_armhf.deb - install the missing library

    To launch the LinuxCNC upon boot automatically:

    • start the LinuxCNC, select configuration you want to use, check "Create shortcut on desktop" and confirm
    • Copy the resulting .desktop file from desktop to ~/.config/autostart


The LinuxCNC configuration is split into multiple files. The hardware specific config is in 'something.hal', it contains info how to control the steppers, where are the endstops connected, etc. The machine specific config with axes details, feed rates, etc. is stored in 'config.ini'. Finally the tools are defined in 'tool.tbl'.

There's a graphical configuration tool called pncconf that was designed to simplify configuration of MESA based control boards. It cannot be used directly for the allwincnc project, but it's a good start to create a sample config you can modify later. However, in this text, I'll start from scratch.


Let's start with the HAL configuration. The HAL config defines connection between the LinuxCNC engine and the real hardware driving all the steppers and stuff. The hardware is represented by a loadable drivers, you load the driver for your hardware, kinematics and other stuff and define signal connections. Multiple HAL files can be defined (e.g. one for your stepper controller, one for the control panel inputs,...). Some basic info about the HAL can be found in the official docs.

This configuration is highly user-specific, I'll be describing hal for my CNC shield and MPCNC with dual steppers on X and Y axes, but the principles are generic enough to tweak it for your setup accordingly. My config file is here.

We'll be using following modules:

  • arisc - allwincnc project driver
  • Motion - provides signals for motor position,...
  • IO control - basic machine IO controls - cooling, etc.
  • HALUI - User controls - jogging,...
loadrt [EMCMOT]EMCMOT base_period_nsec=[EMCMOT]BASE_PERIOD servo_period_nsec=[EMCMOT]SERVO_PERIOD num_joints=[KINS]JOINTS
loadrt arisc pwm="p,p,p,p,p,f" in=PA2,PC1,PA19,PA7,PG8,PC3,PA10,PA20 out=PA3,PG7,PG6 encoders=3

First, we are loading kinematics and motion module as defined in the .ini file. The [KINS]KINEMATICS is a macro that gets replaces by content of the KINEMATICS variable from the [KINS] section. The first two lines are pretty standard and can be found in almost all config files out there. The last line is special. It loads the arisc module (part of the allwincnc project), this module allows communication with the firmware running on the RISC coprocessor. As per docs, we define the pwm channels used (for steppers and spindle), inputs and outputs and amount of encoders the board will use.

addf      servo-thread
addf         servo-thread
addf          servo-thread
addf motion-command-handler  servo-thread
addf motion-controller       servo-thread
addf arisc.gpio.write        servo-thread
addf arisc.pwm.write         servo-thread

Now we need to define which functions will execute in the processing thread. The order of execution is preserved, therefore each time the thread is started, it will read all the inputs and feedback data, process the motion calculations and write the results.

# X axis setup
setp arisc.pwm.0.pos-scale  [JOINT_0]SCALE
setp arisc.pwm.0.dc-cmd      0.5 # 50%
setp arisc.pwm.0.dc-max-t    5000
setp arisc.pwm.0.dir-hold      50000
setp arisc.pwm.0.dir-setup    50000
setp arisc.pwm.0.pwm-port   0 #PA
setp arisc.pwm.0.pwm-pin     0 #PA0
setp arisc.pwm.0.dir-port       0 #PA
setp arisc.pwm.0.dir-pin        6 #PA6
net x-pos-cmd  joint.0.motor-pos-cmd  arisc.pwm.0.pos-cmd
net x-pos-fb      joint.0.motor-pos-fb      arisc.pwm.0.pos-fb
net x-vel-cmd   joint.0.vel-cmd              arisc.pwm.0.vel-cmd
net x-en           joint.0.amp-enable-out  arisc.pwm.0.enable

This is configuration of a single stepper driver, some timing is defined first, followed by the definition of the IO pins (these are configured by the pwm, no need to set them in the out section of the loadrt arisc command). The setp command sets a value to given signal (parameter of the arisc pwm module in this case). Finally the net for control and feedbacks are defined by the net command. Each net has an unique name and has to contain one OUT signal and multiple IN signals can be defined. In this case, the net x-pos-cmd connects joint.0.motor-pos-cmd output from the motion module to the arisc.pwm.0.pos-cmd stepper controller. The number of the joint depends on the .ini configuration, I'm using XXYYZ coordinates where 0 is X axis, 1 is X2, 2 is Y, etc. When using a simple XYZ machine with only three steppers, the joints are 0 for X, 1 for Y, 2 for Z.

setp arisc.pwm.5.freq-cmd    1000.0 # Hz
setp arisc.pwm.5.dc-scale     24000.0 # max RPM
setp arisc.pwm.5.pwm-port   0 # PA
setp arisc.pwm.5.pwm-pin    0
setp arisc.pwm.5.dir-port      0 # PA
setp arisc.pwm.5.dir-pin       3
setp arisc.pwm.5.dir-invert   1
net s-rpm  spindle.0.speed-out  arisc.pwm.5.dc-cmd
net s-en   spindle.0.on         arisc.pwm.5.enable

The spindle control configuration is quite straightforward, in this case, we are using a 0-10 V output converted from by PWM signal by simple low-pass filter, therefore the output voltage is proportional to duty cycle. The configuration above converts the required spindle speed from LinuxCNC (0-100 %) to equivalent duty cycle for the PWM generator on PA3.

setp arisc.encoder.0.pos-scale  [AXIS_0]SCALE
setp arisc.encoder.0.A-port     0   # 0=PA
setp arisc.encoder.0.A-pin      8
setp arisc.encoder.0.B-port     0   # 0=PA
setp arisc.encoder.0.B-pin      9
# net x-enc-en  arisc.encoder.0.enable  joint.0.amp-enable-out
# net x-pos-fb  arisc.encoder.0.pos     joint.0.motor-pos-fb

This block configures the encoder input. Encoders can be used for closed-loop systems, just uncomment the last two lines. Or you can use it for jogging control, etc.

net home-z joint.4.home-sw-in joint.4.neg-lim-sw-in arisc.gpio.PA19-in
net stepper-enable joint.0.amp-enable-out  arisc.gpio.PA3-out

Finally some direct pins configuration, here we create net home-z that connects PA19 input pin to home and negative limit signals for joint 4 (z axis). On the next line, we connect out stepper enable (from joint 0) signal to PA3 output pin.


The .ini file contains the machine specific configuration like kinematics, feed rates, accelerations, units, etc. The documentation has a reasonable quality, but it's still easiest to start with the configuration generated by pncconf tool and modify it afterwards. My unfinished config file is here.

DISPLAY = gscreen

First interesting section is the [DISPLAY], here we can define how the control UI will look like. The default one is axis, it's quite nice, but designed for large screens with mouse and keyboard inputs. I'll be using a small 7 inch touch screen together with the OrangePi, so I need something different. After experimenting with the available options, I ended up with the Gscreen that fits the small screen nicely after switching to full screen and reducing DPI (System->Settings->Appearance->Fonts - dpi to 56, font size to 11).


KINEMATICS = trivkins coordinates=XXYYZ kinstype=BOTH

Now we need to define a kinematics for the machine. As I'm doing configuration for the MPCNC which is a simple XYZ CNC with doubled steppers on X and Y axes, I'll be using the trivkins kinematics. As you can see, I'll need 5 joins, one for each stepper motor. the coordinates=XXYYZZ are telling the trivkins that there are doubled steppers on X and Y axes. The kinstype=BOTH allows moving joints in coordinated and independent manners (for auto squaring,...).

HALUI = halui
HALFILE = shield.hal

Here we load the halui component (for connecting a jogger and other stuff later) and load our hal file with defines of the shield.

MIN_LIMIT = -9999.0
MAX_LIMIT = 9999.0

HOME = 0.0
MIN_LIMIT = -9999.0
MAX_LIMIT = 9999.0
SCALE = 1000.0


Lastly we need to defined the axes and joints. The axes were separated from joints in LinuxCNC 2.8, so now for the axes we only define the limits and velocities and rest is defined per joint. As we have two joints (two steppers) on axis X, we need to define two joints (with same content).


Previous Post