Acopos 6D Electromagnetic Table
Posted: July 05, 2025
Last edited: February 07, 2026
Table of Contents
Introduction
In this blog post I would like to briefly share some work I did during two semesters at Aalborg University as part of my bachelor’s degree in robotics: planar motion control (PMC) using the Acopos 6D.
PMC here refers to Planar Motion Control, not planar motor control.
The video above is captured by my group and I as a test for moving the movers/shuttles. The goal at the time was to achieve smooth and planned movements for each mover individually using asynchronous tasks in C#. This post is going to explore this topic: basic control of movers and initialization of the table. It will serve as an introduction that goes with my repository for anyone getting a project started with the Acopos 6D table, especially the one at the AAU Smart Production lab.
Usage
The repository code can be found here. It is kept relatively simple solution-wise for a C# project because it was built and written on Linux, but can still be used on Windows and in Visual Studio. Inside of Program.cs, we include a .dll file called PMCLIB (see the .csproj file for the declaration of this library). It is a control library that translates the C# code to commands that the PMC control module underneath the table’s electromagnetic tiles can use. As far as I know it is possible to interface with the control module through dedicated software for the table or alternatively; Python, which are both things I am not familiar with, as the software was not made for Linux at the time I was working on this.
PMCLIB.dll has been publicly available since 2025, and can be found here, the only needed file is PMCLIB.dll. There are instructions on how to install it into the project on the repository’s README.
Start-up Routine
As part of using your own written code with this table, there is a routine which must be performed to take ownership of the table. To have the PMC modules behave according to the commands you send, you must first obtain ownership of the table. of the table. Creating an instance of SystemCommands and XBotCommands will allow you to interface with the system (through commands) and move movers (through commands). What I call ‘Movers’ are called ‘XBots’ in the PMCLIB library. They are instantiated in the top of program.cs:
private static SystemCommands _sysCmds = new SystemCommands();
private static XBotCommands _xbotCmds = new XBotCommands();
They are both used first in Program::PMCStartup() on line 57 which is a method that:
- Connects to the table itself (is there a line of communication available?)
- Requests and (hopefully) gains mastership (allows commands from us to be executed upon)
- Checks PMC status (in order to finish executing tasks)
- Count movers on the table top
- Initialize movers (enable hovering)
It is important to mention here that every command you send to the system and/or movers are queued to ensure operations are finished. This is a standard that is used by default on the table I worked on. The Acopos 6D is able to detect movers present on the electromagnetic tiles and will automatically designate ordered numerical IDs to movers with a grid search in columns that start in the bottom left corner (from the perspective of the video above) to the top left, then down and rightwards until it reaches the bottom right corner. When the table is ready it will activate the electromagnetic tiles and hover the movers using their polarities (the movers are metallic plates with a soft material along the edges).
Moving the Movers
The movers themselves contain no actuators, all force generation comes from the electromagnetic coils in the table tiles. The following code will move a single mover around the table top:
private static async Task Test2(int id) {
_log.Debug($"Running single mover test for mover: {id}");
for (int i = 0; i < 5; i++) {
_ = Movers[id].MoveTo(Constants.MovePointsTest[0]);
await Task.Delay(1500); // test time
_ = Movers[id].MoveTo(Constants.MovePointsTest[2]);
await Task.Delay(1500); // test time
_ = Movers[id].MoveTo(Constants.MovePointsTest[4]);
await Task.Delay(1500); // test time
_ = Movers[id].MoveTo(Constants.MovePointsTest[5]);
await Task.Delay(1500); // test time
_ = Movers[id].MoveTo(Constants.MovePointsTest[3]);
await Task.Delay(1500); // test time
_ = Movers[id].MoveTo(Constants.MovePointsTest[1]);
await Task.Delay(1500); // test time
}
//Movers[id].MoveTo(new System.Numerics.Vector2(0.060f, 0.060f));
_log.Debug($"Single mover test for mover: {id} has concluded");
}
As mentioned, the table auto assigns movers their id so usually with at least 1 mover present you would just set int id = 0. The MovePointsTest is a dictionary in Constants.cs that looks like the following:
// Viapoints for 'outer' highway - closest proximity to the table top's edge
public static Dictionary<int, Vector2> MovePointsTest = new Dictionary<int, Vector2>() {
// Key: Station Position, Value: Pos (X, Y)
{0, new Vector2(0.060f, 0.060f)},
{1, new Vector2(0.660f, 0.060f)},
{2, new Vector2(0.060f, 0.450f)},
{3, new Vector2(0.660f, 0.450f)},
{4, new Vector2(0.060f, 0.900f)},
{5, new Vector2(0.660f, 0.900f)},
};
As the code comment mentions, these coordinates will move the mover along the edge of the table top that is made up of 8 tiles vertically, and 6 tiles horizontally (again, perspective of the video). Coordinates are expressed in meters, where 0.060 m = 60 mm = one tile which means these coordinates are the distance from the anchor point (bottom left tile) but declared in meters (by default in the PMCLIB library).
To make any mover of any id move, check out Models.cs:
public async Task MoveTo(Vector2 pos,
ushort cmdLabel = 0, POSITIONMODE posMode = POSITIONMODE.ABSOLUTE, LINEARPATHTYPE pathType = LINEARPATHTYPE.DIRECT,
double finalSpdMetersPs = 0, double maxSpdMetersPs = 0.5, double maxAccelerationMetersPs2 = 10)
{
if (_cmds == null) {
_log.Warn($"Failed to execute movement for mover with id: {Id}, xbot commands reference is null!");
return;
}
_log.Debug($"Shuttle {Id} is moving!");
_cmds.LinearMotionSI(cmdLabel, Id, posMode, pathType, pos.X, pos.Y, finalSpdMetersPs, maxSpdMetersPs, maxAccelerationMetersPs2);
_log.Debug("finished moving");
await Task.Delay(1000); // Buffer time to get the mover moving.
_log.Debug("time delay of 1s passed (async task)");
}
This is the important part, that helps one understand how to make the movers move in a way that you want. Everything beyond the scope of making the table move a mover is just C# like you know it, such as:
- Using
asyncevents to run these methods independently on their own tasks - Creating UI to trigger method calls
- Creating new containers, projects, etc.
In the code above posMode is defaulted to ABSOLUTE which is a position mode that uses the previously mentioned “anchor point” (as I call it) to move from, which means any coordinates you give it is a position relative to this anchor point. The alternative to ABSOLUTE is RELATIVE which will treat the given coordinates as a distance in meters, and move that distance from where it currently is. pathType defines how it moves along the trajectory it has. DIRECT means it will move directly towards that point by the shortest path possible. This path is calculated by the PMC itself. This can be problematic if you are moving from one corner to another since that will cause the PMC to move the mover diagonally across the table. Alternatively there are path types such as x first, then y or the other way around, which will restrict it to only horizontal and vertical trajectories. The other parameters (finalSpdMetersPs, maxSpdMetersPs, maxAccelerationMetersPs2) are used for trajectory and interpolation to speed up and slow down when moving. I highly recommend tweaking these parameters and having some fun, seeing what is possible. The PMC controller handles colliding trajectories for the movers, they won’t break or fly off the table.
Closing Words
Although PMCLIB is not documented all that well, I hope this helps as a gentle introduction to getting things moving. B&R should have documentation on their website and in any case I encourage trial and error when you work on this PMC as it is a lot of fun!
Special thanks to Associate Professor Casper Schou from Robotics & Automation at Aalborg University for allowing me to test this project on the AAU Matrix Table (B&R Acopos 6D) and supervising me on earlier semester projects that served as my introductions to Planar Motor Control in general.