Eakins Camera XY Scanning

image

Scanner Project Update and Technical Details

(work in progress)

Recently, I’ve made the design and functionality more useable, automatic scans are working as expected and its neater.

A youtube short of it scanning

https://www.youtube.com/shorts/pULRlDODE48

OpenSeaDragon example scan PCB Test.

Script Repository

Access the scripts used in this project on GitHub.

The scanner’s recent changes include the integration of a controlled XY table with a TinyG board, interfaced with the camera via USB. This setup is based on ideas shared on my GitHub and in a previous blog post.

 

Image Rotation Correction

Due to alignment challenges, post-process image alignment and rotation correction are essential. Affinity Photo 2 works pretty well though lacks customisation.

FTP Daemon Configuration for Eakins Camera to download files after scan

/etc/inetd.conf

21 stream tcp nowait root ftpd ftpd -w /mnt/sdcard

After configuring, start the inetd service to enable FTP functionalities if you’ve added a USB ethernet.

Most of this is just simple scripts, the main scan script is  a busybox sh script. I process the resulting images on windows so my scripts are TCC (Take Command) usually, but most of the apps are available on other OS’s

Scripting and Processing Workflow

The backbone of this project is a series of scripts. The primary scanning script is developed in shell script (sh), optimized for Unix-like operating systems. My data processing is executed on Windows, where I employ TCC (Take Command) scripts, known for their robust command line capabilities and batch file compatibility. The scripts are on GitHub.

Basically they calculate a G0XnYnF100 command and echo it to the /dev/ttyUSB0 device which is a TinyG, that moves the microscope to that location, then it calls a second script which does the Z focus steps with the talk command. The scripts do need some optimisation on time and checking to see if the saved image appeared, it’d be nice if it renamed the file to the Z step on each row_col

Focus Stacking Solutions

For the task of focus stacking, Helicon Focus is a handy tool for combining multiple images at different focus points into a single, detailed composite. As an alternative, I also use a customized open-source tool from PetteriAimonen’s GitHub. My version adds a Visual Studio Solution (SLN) file and integrates vcpkg for seamless dependency management, alongside a feature to batch-process images from a directory, though i think the original already does what i added …. so i might have replicated that one…

Helicon can do the focus stacking but unless your inputs require no rotation its likely going to fail the stitch, so I use Affinity Photo 2 for that it works really well for me (though it failed during the test for the writeup) , it can focus stack too, Photo 2 uses a 3rd party library a few different packages use.

Image Processing with Affinity Photo 2

In scenarios where Helicon Focus might struggle, particularly with images that need rotation adjustments, Affinity Photo 2 works . It is able to do both Focus Stacking while correcting rotational misalignments, internally it utilises  a third-party library called AutoStitch that is common across several image processing tools. http://matthewalunbrown.com/autostitch/autostitch.html

Most stitching software is expecting the camera to rotate or a 360/spherical style and not where its just moving in one plane. PTGUI/Hugin should be able to hande these too. These types of stitchers are common in the medical field for processing microscope slides etc.

A tutorial for Hugin.

https://hugin.sourceforge.io/tutorials/scans/en.shtml

For more tailored image rotation needs, I have developed a specific batch rotation tool available at https://github.com/charlie-x/rotImage . This tool is just a simple OpenCV based batch rotation tool there are other much more comprehensive solutions like ImageMagick, which has a broad rage of image manipulation functionalities, including batch rotation.

Step-by-Step Guide to Executing a Focus Stack

To initiate a focus stack, first obtain the tool from GitHub. Build it yourself or download a pre-compiled release. Note that the command syntax differs if you’re using the original version.

After gathering all images in a local directory (e.g., o:\code\eakins\scan-5), create a designated output folder (e.g., out5\).


go.bat
for /A:D %i in (O:\code\eakins\scan-5\*.*) do call run %i out5\%@name[%i].jpg

run.bat
focus-stack.exe --global-align --output=%2 --input-folder=%1

This method will compile the images into a focus-stacked collection in the out5\ directory, achieving a level of detail similarly to the banner image of this post.

If you get something like

[ERROR:15@2.413] global ocl.cpp:3765 cv::ocl::Kernel::set OpenCL: Kernel(decompose_vertical)::set(arg_index=0, flags=258): can’t create cl_mem handle for passed UMat buffer (addr=000000B1986FF140)

just try re-running it , if it fails again check there aren’t images in the folder that can’t be stacked, wrong image location, poor contrast/lighting or try not using opencl

Stitch the Image

In Affinity Photo2 from File menu “New Stack” and add those files, select “Scale, rotate and translate”

image

This will take a while

Sometimes it just fails , I think this a lack of overlap and not enough details for the feature match, the golden ratio makes a visit. Make sure the lighting is good and there is enough overlap to properly stitch. correcting rotation can also help, but try it both ways first.

image

Some fun results.

image

Helicon Focus with the same data set made this, some issues here too

imageimage

Hmmm nulspacelibs ?

Some manual tweaking gets it closer, but affinity usually does a better job its just harder to tweak later.

image

Source Images

image

OpenSeadragon

If you want to use OpenSeaDragon process the stitched image with vips

vips dzsave pcb-test3.png pcb-test3

this will create a bunch of tiles in the pcb-test3 folder and a .dzi file

<Image xmlns=”http://schemas.microsoft.com/deepzoom/2008″
   Format=”jpeg”
   Overlap=”1″
   TileSize=”254″
   >
<Size
     Height=”12747″
     Width=”20219″
   />
</Image>


Add a script.js , make sure var name and tileSources match, set the Url to the location of the files you created with vips, change the TileSize and width/height to match the .dzi

  • var pcbtest3 = {
      Image: {
        xmlns: “http://schemas.microsoft.com/deepzoom/2008″,
        Url: “pcb-test3_files/”,
        Format: “jpeg”,
        Overlap: “1”,
        TileSize: “254”,
        Size: {
            Width: “20219”,
            Height: “12747”
        }
      }
    };
  • var viewer = OpenSeadragon({
      id: “seadragon-viewer”,
      prefixUrl: “//openseadragon.github.io/openseadragon/images/”,
      tileSources: pcbtest3
    });

Add a simple HTML file, include the scripts for OpenSeaDragon and the above script.js

  • <html>
      <head>
    <style>
          #seadragon-viewer {
            width: 100vw;   /* 100% of the viewport width */
            height: 100vh;  /* 100% of the viewport height */
          }
        </style>
      </head>
    <body style=”background-color: black;”>
            <div id=”seadragon-viewer”></div>
            //openseadragon.github.io/openseadragon/openseadragon.min.js
            http://script.js
  • </body>
    </html>

3D Depth

focus-stack can also generate depth maps and a simulated 3d output , not sure how much good it is is here. I have yet to try stitching the non focus stacked images, then stacking them, since that will make a depth map and 3d of the whole image, if we could output the translation matrix the stitcher used and then use these images over it, that might work instead. a photogrammetry tool like Reality Capture with images captured at different angles would do a better job here.

image

image

MiniWave ES15 screwdriver + KER toolkit 3d print.

Fusion 360 model

image

https://a360.co/3ZfWJPU “V18 – tested works” is the last tested printed version

image

image

image

this is V15 (Fusion 360 version), the third hole is a teensy bit off , pressure fit

image

image

Version 16+

Swap tray 1 to tray 8 (little bit of glue to over come) but you have to clip these sides a little bit to clear the magnetic locks. it is thin plastic so its easy to do.

image

Arrangement looks like this after the mod, so it removes the screwdriver+tweezers from the base KER only

image

Timelapse print

https://youtu.be/a2AgcULsrTs

image

image

The inner sketches for the holes are correct, i was using larger ones for test fits.

I moved the third hole in v17 to a 46mm from 46.3mm in v16

And just as I printed V16 which seems to work the best, Amazon delivered the PLA i wanted to use…. I guess V17 is getting a run then.

image

this is hatchbox yellow PLA, going to try microcenter in house brand inland gold pla as well, needs a little orange

image

“V18 – tested works”, is the current verified working version from the Fusion360 Link

This is the Inland Gold PLA at the bottommost  (warning inalnd uses cardboard reels)

image

The KER kit is sold as 

KER 128 in 1 Precision Screwdriver Set with Magnetic Driver Kit

I may make some small changes that should reflect in the A360.

Also its currently in slot 8, which is the extension and metal spatula, since slot 1 won’t fit. Will either make a new plate for slot 1 that lets the driver fit, or see if i can adjust so you can move slot 8 to Slot 1 instead

Bambu X1 carbon notes

not much yet as i only had the machine for a day, but keeping notes, as is the way.

Auditing these machines, since they can/are connected to a plain ip addresses in china, have unfettered local network access, no proxy settings, a closed ecosystem and could download and execute arbitrary code without permission and a *lot* of domains they can connect too

added more notes i have had in limbo since feb 2023.. time flies… 

FTP

the BambuLabs X1  Carbon currently uses FTP when the SD-CARD is installed, the username is bblc and the access code is  the password , which is stored in

C:\Users\%USERNAME%\AppData\Roaming\BambuStudio

BambuStudio.conf

It’s in the JSON file, under access_code

  • “access_code”: {
  • }

regular FTP port 21.

if you can’t find the access code, just wireshark with a tcp.port == 21 filter.

the access code appears other places too in the p1p, but i haven’t seen it on the x1c

BambuLabs say they are planning to remove the FTP connection, since they may want something either more secure or just go via their cloud (doesn’t seem to have happened )

RFID

the RFID is a mifare classic 1K and doesn’t use any of the std/extended keys, i guess dust off the proxmark time.  not susceptible to NACK

AMS RFID reader

https://www.fmsh.com/AjaxFile/DownLoadFile.aspx?FilePath=/UpLoadFile/20220804/FM17580_ps_web_2022.pdf&fileExt=file

https://github.com/ian688t6/FM175XXREADER/blob/master/mifare_card.c

3.3V signalling

image

The RFID boards are connected with a single  SPI connection with separate chip selects.

image

even after decrypt of the rfid to access the data in the rfid tag there is probably some more encryption/verification in the data stored on the card itself. (it might be pub/priv key signed , i wouldn’t put it past them they’re a very smart bunch at bambu, reached out to someone else to confirm if they’d seen any indications of it) it’d explain why it hasn’t been done yet, since there are lot of avenues to recover the keys. MITMing the SPI to emulate and add something wouldn’t work since the data is passed back as is. with current thinking of how the serial protocol works it’d be quite hard to do at that level too,and then the ams cpu would be out of sync… replacing the firmware in the ams cpu board would likely be required to add custom tags if this line of thought is valid. unless the private key is somehow recovered, if it is a private key.

note: CF 522 / CV520

AMS

24V top left of  connector , looking at rear of ams, cpu board + two rfid boards, and small power/data bus board for connection to hub and daisy chaining ( uart/rs485 3.3V signaling)

power board. 24V DC on right , bus on  left connects to the ams cpu board.

image

6 pin connector looking into the connector from rear of the AMS, pin1 top left

HUB connectors, top left is Pin1 (24V) longer pin at Pin2 is GND/VSS

image

24v,  gnd,       pair a –/+

pair a –/+, pair b –/+ pair b –/+

Pair B is just the state of magnetic sensor in the hub and is passed back to the AMS  as a multi node RS485 bus chain. It does not connect back to the printer ( the status is sent to the  AMS and the AMS can send likely it back to the X1 via pair A . The duty cycle of the signal is the position of the magnet basically.

image

Pair A is the primary half duplex communication line, its setup as a traditional multi node bus (or is it)

https://www.dfrobot.com/product-1492.html

https://www.lcsc.com/product-detail/Operational-Amplifier_TECH-PUBLIC-TPV358S8_C2923372.html

BUILDING THE SLICER

Building the app from github was a bit of a chore, build the deps first then the application (debug mode doesn’t seem to work with the networking plugin dll, will have to check into that a bit more, probably just an incompatibility with name mangle/that dll) the PrusaSlicer github page issues has a lot of notes on the build process.

openssl needs  a windows path / \ compatible perl to build. i just used one from vcpkg

Although the Slicer is open source (since its a fork of PrusaSlicer) the communications for the device  are in a separate library that does all the communications with the cloud service/machine. This is because this allows them to keep the network communications closed source,  the Camera works the same way.

NOTES

internal urls are :-

bambu:///machine?authkey=xxxxxxxxxx&passwd=xxxx&region=us

HARDWARE

back of the 4 way hub, i’ll add better photos.

I like the cute little VOID sticker, is it telling me it is void, it is a portal to another dimension perhaps?

Warranty void stickers are illegal in a lot of places (Definitely Germany and USA) magnusson moss act in the USA

https://www.ftc.gov/news-events/news/press-releases/2018/04/ftc-staff-warns-companies-it-illegal-condition-warranty-coverage-use-specified-parts-or-services

Rockchip RV1126

h5tq4g63efr 4Gb DDR3 SDRAM

KIOXIA  THGBMNG5D1LBAIL VD1216   2140KAE CHINA

https://www.digikey.com/en/products/detail/kioxia-america-inc/thgbmng5d1lbail/9841782

https://pdf1.alldatasheet.com/datasheet-pdf/view/1244282/TOSHIBA/THGBMNG5D1LBAIL.html

https://github.com/yunzhaoyu2050/rockchip_rv1126_rv1109_docs/blob/main/RV1126_RV1109/Rockchip_RV1126_RV1109_Quick_Start_Linux_EN.pdf

https://datasheet.lcsc.com/lcsc/2202131900_SK-HYNIX-H5TQ4G63EFR-RDC_C2803259.pdf

image

image

CPU’s used.

http://www.spintrol.com/index/index/product2/id/25.html

http://www.spintrol.com/index/index/product2/id/23.html

https://jlcpcb.com/partdetail/ZHONGKEWEI-AT8236/C2827823

https://www.alldatasheet.com/datasheet-pdf/pdf/1149566/CHIPSEA/CSU32P10.html

TI Version of the transceiver https://www.ti.com/product/SN75176B

https://www.arrow.com/en/products/tp75176e-sr/3peak

FM 1230 SC

2023-01-14 16_53_27-SharpCap (v4.0.9478, 64 bit) - USB CAMERA  - C__Users_charlie_Desktop_SharpCap C

2023-01-14 16_53_46-SharpCap (v4.0.9478, 64 bit) - USB CAMERA  - C__Users_charlie_Desktop_SharpCap C

3PEAK 75176 rs485 transceiver

https://datasheet.lcsc.com/lcsc/1811071710_3PEAK-TP75176E-SR_C94207.pdf

2023-01-14 16_55_14-SharpCap (v4.0.9478, 64 bit) - USB CAMERA  - C__Users_charlie_Desktop_SharpCap C

 2023-01-14 18_49_36-SharpCap (v4.0.9478, 64 bit) - USB CAMERA  - C__Users_charlie_Desktop_SharpCap C

F330G8BUH5468 Giga

https://www.gigadevice.com/products/microcontrollers/gd32/arm-cortex-m4/mainstream-line/gd32f303-series/

https://www.gigadevice.com/microcontroller/gd32f330g8u6/ 

2023-01-14 16_56_23-SharpCap (v4.0.9478, 64 bit) - USB CAMERA  - C__Users_charlie_Desktop_SharpCap C

the GD’s are popular because of supply chain issues, shorter lead times than the somewhat similar lines from ST Micro

CHIPSEA 32P

https://www.lcsc.com/product-detail/Microcontroller-Units-MCUs-MPUs-SOCs_CHIPSEA-CSU32P10-SOP8_C914988.html

2023-01-14 18_49_10-SharpCap (v4.0.9478, 64 bit) - USB CAMERA  - C__Users_charlie_Desktop_SharpCap C

hall effect

2023-01-14 16_53_46-SharpCap (v4.0.9478, 64 bit) - USB CAMERA  - C__Users_charlie_Desktop_SharpCap C

2023-01-14 18_50_17-SharpCap (v4.0.9478, 64 bit) - USB CAMERA  - C__Users_charlie_Desktop_SharpCap C

filament buffer ( ams hub replaces it)

image

image

image

image

filament displacement, this is spring loaded when the filament passes thru it changes the hall effect sensor state

image

image

image

image

the long magnets are setup to be horizontal , so N E S , so the sensor can determine which direction or no filament

Connectors

https://www.literature.molex.com/SQLImages/kelmscott/Molex/PDF_Images/987651-4661.pdf

Lidar

Haven’t confirmed it yet but the uploaded .3mf contains a 2d image of the first layer expectation this is probably used as the mask for the lidar/first layer inspection

image

April Tags  / ArUco

I’ll add code for the ArUco regen at some point

The codes the printer reads on the plates are 16H5’s 5mmx5mm :-

https://docs.opencv.org/4.x/d5/dae/tutorial_aruco_detection.html

detection   3:id (16x 5)-25  , hamming 2, margin   24.959 etc

1 border bit


examples

16h5a

BambuNetworkEngine.conf

Rijndael  json  with last machine, username , avatar link , userid/name and tokens.

OpenSSH

nlohman json

paho mqtt

spdlog

boost

curl

std::fmt

List of domains/ip’s

https://ipinfo.io/47.100.225.51

DevModel.bambu.com https://www.whois.com/whois/bambu.com

DevName.bambu.com

DevSignal.bambu.com

DevConnect.bambu.com

DevBind.bambu.com

bambu-lab.com https://www.whois.com/whois/bambu-lab.com

bambooolab.com https://www.whois.com/whois/bambooolab.com

portal.bambulab.com

cn.mqtt.bambulab.com

api.bambulab.com

us.mqtt.bambulab.com

pre.mqtt.bambu-lab.com

pre.us.bambu-lab.com

bambulab-demo.myshopify.com

portal-qa.bambulab.com

portal-dev.bambu-lab.com

portal-pre.bambu-lab.com

dev.mqtt.bambu-lab.com

pre_us.mqtt.bambu-lab.com

api-us-pre.bambu-lab.com

upgrade-file.bambulab.com

cdn.bambulab.com

BambuLabs X1 Carbon, P1P

EzCad .ezd parsing the pens

My notes on parsing the EZD Cad 2 format and accessing the pen information for regular and Q/MOPA types.

The key part is getBasePtr () + the offset

    1. base = (uint32_t*)(dataBuffer->data() + 0x160 );
    2. dataBuffer->data() + baseOffset + SPEED_MM_OFFSET

start of file + baseOffset + value you want to read/write

These are the offsets
this is a pointer to an offset where the pens/data so baseOffset+this
DATA_PENS_OFFSET ( 0x160 )

offset of the parameter name NAME_OFFSET ( 8UL )
this is the length of the first name of the .ezd i started with
NAME_LENGTH ( 0x10 )

USE_DEFAULT_OFFSET ( 0x0c + 8 ) USE_ON_OFF_OFFSET ( 0x0c ) LOOP_COUNT_OFFSET ( 0x14 ) SPEED_MM_OFFSET ( 0x1c ) POWER_OFFSET ( 0x28 ) FREQUENCY_OFFSET ( 0x34 )

unsigned long here Q_OFFSET ( 0x3c )

double here
Q_OFFSET_2 ( 0x3c+0xa0 ) START_TC_OFFSET ( 0x44 ) LASER_OFF_OFFSET ( 0xCC ) END_TC_OFFSET ( 0x4c ) POLYGON_TC_OFFSET ( 0x54 )

#pragma once

// so far, we no longer need this since it moves
// ezcad version 2.14.11
#define BASE_POINTER (0x27300)

// some test ezd files I found on the web
//#define BASE_POINTER (0x464)

enum {
	COL_PARAMETER = 1,
	COL_RGB = 2,
	COL_DEFAULT = 3,
	COL_ONOFF = 4,
	COL_LOOPCOUNT = 5,
	COL_SPEED_MM = 6,
	COL_POWER = 7,
	COL_FREQUENCY = 8,
	COL_Q = 9,
	COL_START_TC = 10,
	COL_LASER_OFF = 11,
	COL_END_TC = 12,
	COL_POLYGON_TC = 13,
	MAX_COLS = 14
};

#define MAX_PEN				( 256 )

#define	DATA_PENS_OFFSET		( 0x160 )	// this is a pointer to an offset where the pens/data is 
#define	NAME_OFFSET			( 8UL )		// offset of the parameter name
#define	NAME_LENGTH			( 0x10 )	// this is the length of the first name of the .ezd i started with
#define USE_DEFAULT_OFFSET		( 0x0c + 8 )
#define USE_ON_OFF_OFFSET		( 0x0c )
#define LOOP_COUNT_OFFSET		( 0x14 )
#define SPEED_MM_OFFSET			( 0x1c )
#define POWER_OFFSET			( 0x28 )
#define FREQUENCY_OFFSET		( 0x34 )
#define Q_OFFSET			( 0x3c )	// unsigned long here
#define Q_OFFSET_2			( 0x3c+0xa0 )	// double here
#define START_TC_OFFSET			( 0x44 )
#define LASER_OFF_OFFSET		( 0xCC )
#define END_TC_OFFSET			( 0x4c )
#define POLYGON_TC_OFFSET		( 0x54 )



class EZD {

public:
	uint16_t	penID;

	// start of pen data
	size_t	basePointer;

	// offset of data after the Parameter string
	uint64_t	baseOffset;

	std::string* dataBuffer = NULL;

	size_t parameter_length;

	~EZD() {
	}
		
	EZD() : baseOffset(0),basePointer(0), penID(0) ,parameter_length(0) {

	}

	EZD(size_t basePtr, uint16_t pen, std::string& data) : EZD() {

		/// whole EZD file
		dataBuffer = &data;

		// start of pen data
		basePointer = basePtr;

		// length of unicode parameter string in bytes
		parameter_length = dataBuffer->at(basePointer + 4);

		// rebases a pointer to after the unicode string .
		// 8 is the offset of the Unicode string
		baseOffset = 
			(size_t)(basePointer + NAME_OFFSET + parameter_length);
		penID = pen;
	}

	// returns the address of the last byte in the block
	size_t EndOfBlock()
	{
		// 0x27c is the size of a block with 0x10 length of the "default" string
		return basePointer + parameter_length + (0x27c - 0x10);
	}

	// these are before the unicode string so are only affected by the basePointer
	void R(uint8_t r) { dataBuffer->at(basePointer) = r; };
	void G(uint8_t g) { dataBuffer->at(basePointer + 1) = g; };
	void B(uint8_t b) { dataBuffer->at(basePointer + 2) = b; };

	uint8_t R() { return dataBuffer->at(basePointer); };
	uint8_t G() { return dataBuffer->at(basePointer + 1); };
	uint8_t B() { return dataBuffer->at(basePointer + 2); };

	uint32_t GetRGB() {
		uint32_t rgb;
		rgb = RGB(R(), G(), B());
		return rgb;

	}

	void SetRGB(uint8_t r, uint8_t g, uint8_t b) {

		R(r);
		G(g);
		B(b);
	}

	void SetRGB(COLORREF r) {
		uint32_t rgb;
		rgb = (uint32_t)r;

		R(GetRValue(rgb));
		G(GetGValue(rgb));
		B(GetBValue(rgb));
	}

	// these are before the unicode string so are only affected by the basePointer
	CString Parameter() {

		// ugly way to do a unicode string...
		std::wstring data;

		// check that we were able to actually read a string length
		ASSERT(parameter_length);
		if (parameter_length == 0) {
			return CString(_T("NA"));
		}

		// resize our string to match
		data.resize(parameter_length);

		// fetch ptr to it
		unsigned char* p = (unsigned char*)dataBuffer->data() + basePointer + 8;
		ASSERT(p);

		// check results
		if (p == 0) {
			return CString(_T("NA"));
		}

		char* d = (char*)data.data();
		ASSERT(d);

		// check results
		if (d == 0) return CString(_T("NA"));

		//copy over string data
		memcpy(
			d,
			p,
			parameter_length
		);

		return CString(data.c_str());
	}

	// these are before the unicode string so are only affected by the basePointer
	std::wstring Parameter(CString value) {

		// ugly way to do a unicode string...
		std::wstring data( value);

		// check that we were able to actually read a string length
		ASSERT(parameter_length);
		if (parameter_length == 0) {
			return std::wstring(_T(""));
		}

		// resize our string to match the length store, its a hassle to rewrite the whole file for this otherwise
		data.resize((parameter_length-1)/2);

		// fetch ptr to it
		unsigned char* p = (unsigned char*)dataBuffer->data() + basePointer + 8;
		ASSERT(p);

		// check results
		if (p == 0) {
			return std::wstring(_T(""));
		}

		char* d = (char*)data.data();
		ASSERT(d);

		// check results
		if (d == 0) return std::wstring(_T(""));

		//copy over string data
		memcpy(
			p,
			d,
			parameter_length
		);

		return data;
	}

	// these are afer the unicode string so are only affected by the basePointer and the length of the unicode string
	uint8_t UseDefault() {
		uint64_t offset = basePointer + USE_DEFAULT_OFFSET + parameter_length;
		uint8_t* b = (uint8_t*)(dataBuffer->data() + offset);
		return *b;
	}

	// only verifys its 0 or 1
	bool UseDefault(uint8_t value) {

		if ((value != 0) && (value != 1)) {
			return false;
		}

		uint64_t offset = basePointer + USE_DEFAULT_OFFSET + parameter_length;
		uint8_t* b = (uint8_t*)(dataBuffer->data() + offset);
		*b = value;

		return true;
	}

	// these are afer the unicode string so are only affected by the basePointer and the length of the unicode string
	uint8_t OnOff() {
		uint64_t offset = basePointer + USE_ON_OFF_OFFSET + parameter_length;
		uint8_t* b = (uint8_t*)(dataBuffer->data() + offset);
		return *b;
	}

	// only verifys its 0 or 1
	bool OnOff(uint8_t value) {

		if ((value != 0) && (value != 1)) {
			return false;
		}

		uint64_t offset = basePointer + USE_ON_OFF_OFFSET + parameter_length;
		uint8_t* b = (uint8_t*)(dataBuffer->data() + offset);
		*b = value;

		return true;
	}

	uint32_t getBasePtr() {
		uint32_t* base;
		base = (uint32_t*)(dataBuffer->data() + 0x160 );
		return *base;

	}

	uint32_t LoopCount() {
		uint32_t* loop;
		loop = (uint32_t*)(dataBuffer->data() + baseOffset + LOOP_COUNT_OFFSET);
		return (uint32_t)*loop;
	}
	
	bool LoopCount(uint32_t value) {

		if (value < ezdConfig.LoopCountMin || value > ezdConfig.LoopCountMax) {
			return false;
		}

		uint32_t* loop;
		loop = (uint32_t*)(dataBuffer->data() + baseOffset + LOOP_COUNT_OFFSET);
		*loop = value;

		return true;
	}

	double SpeedMM() {
		double* speed;
		speed = (double*)(dataBuffer->data() + baseOffset + SPEED_MM_OFFSET);
		return *speed;
	}

	bool SpeedMM(double val) {
		//reject OOB
		if (val < ezdConfig.SpeedMMMin || val > ezdConfig.SpeedMMMax) {
			return false;
		}

		double* speed;
		speed = (double*)(dataBuffer->data() + baseOffset + SPEED_MM_OFFSET);
		*speed = val;
		return true;
	}

	double Power() {
		double* power;
		power = (double*)(dataBuffer->data() + baseOffset + POWER_OFFSET);
		return *power;
	}

	bool Power(double val) {
		double* power;

		//reject OOB
		if (val <ezdConfig.PowerMin|| val > ezdConfig.PowerMax) {
			return false;
		}

		power = (double*)(dataBuffer->data() + baseOffset + POWER_OFFSET);
		*power = val;
		return true;
	}

	double Frequency() {
		uint32_t* frequency;
		frequency = (uint32_t*)(dataBuffer->data() + baseOffset + FREQUENCY_OFFSET);
		return (double)(*frequency) / 1000.0;
	}

	bool Frequency(double val) {

		//reject OOB
		if (val < ezdConfig.FrequencyMin || val > ezdConfig.FrequencyMax) {
			return false;
		}

		uint32_t* frequency;
		frequency = (uint32_t*)(dataBuffer->data() + baseOffset + FREQUENCY_OFFSET);
		uint32_t t = (uint32_t)(val * 1000.0);
		(*frequency) = t;

		return true;
	}

	int32_t StartTC() {
		int32_t* temp;
		temp = (int32_t*)(dataBuffer->data() + baseOffset + START_TC_OFFSET);

		return *temp;
	}

	bool StartTC(int32_t val) {
		//reject OOB
		if (val < ezdConfig.StartTCMin || val > ezdConfig.StartTCMax) {
			return false;
		}
		
		int32_t* temp;
		temp = (int32_t*)(dataBuffer->data() + baseOffset + START_TC_OFFSET);
		*temp = val;
		
		return true;
	}

	uint32_t LaserOff() {
		uint32_t* temp;
		temp = (uint32_t*)(dataBuffer->data() + baseOffset + LASER_OFF_OFFSET);

		return *temp;
	}

	bool LaserOff(uint32_t val) {
		//reject OOB
		if (val < ezdConfig.LaserOffMin || val > ezdConfig.LaserOffMax) {
			return false;
		}


		uint32_t* temp;
		temp = (uint32_t*)(dataBuffer->data() + baseOffset + LASER_OFF_OFFSET);
		*temp = val;

		return true;
	}

	uint32_t EndTC() {
		uint32_t* temp;
		temp = (uint32_t*)(dataBuffer->data() + baseOffset + END_TC_OFFSET);

		return *temp;
	}

	bool EndTC(uint32_t val) {
		//reject OOB
		if (val < ezdConfig.EndTCMin || val > ezdConfig.EndTCMax) {
			return false;
		}

		uint32_t* temp;
		temp = (uint32_t*)(dataBuffer->data() + baseOffset + END_TC_OFFSET);
		*temp = val;

		return true;

	}

	uint32_t PolygonTC() {
		uint32_t* temp;
		temp = (uint32_t*)(dataBuffer->data() + baseOffset + POLYGON_TC_OFFSET);

		return *temp;
	}

	bool PolygonTC(uint32_t val) {
		//reject OOB
		if (val < ezdConfig.PolygonTCMin|| val > ezdConfig.PolygonTCMax) {
			return false;
		}

		uint32_t* temp;
		temp = (uint32_t*)(dataBuffer->data() + baseOffset + POLYGON_TC_OFFSET);
		*temp = val;

		return true;
	}

	uint32_t Q() {
		uint32_t* temp;
		double* temp1;
		temp = (uint32_t*)(dataBuffer->data() + baseOffset + Q_OFFSET);
		temp1 = (double*)(dataBuffer->data() + baseOffset + Q_OFFSET_2);

		return *temp;
	}

	bool Q(uint32_t val) {

		//reject OOB
		if (ezdConfig.QMin < 0 || val > ezdConfig.QMax) {
			return false;
		}

		uint32_t* temp;
		double* temp1;

		temp = (uint32_t*)(dataBuffer->data() + baseOffset + Q_OFFSET);
		temp1 = (double*)(dataBuffer->data() + baseOffset + Q_OFFSET_2);

		*temp = val;
		*temp1 = double(val);

		return true;
	}
};

decode .upd files

just a very quick post on how to decode a chinese laser cutter firmware update file 644 etc

#include <iostream>
#include <fstream>
#include <vector>

uint8_t decode(uint8_t updByte)
{
     return (((uint8_t)(updByte – 1) ^ 0x88) << 7) |
         (( uint8_t)((updByte – 1) ^ 0x88) >> 7) |
         ((updByte – 1) ^ 0x88) & 0x7E;
}

int main(int argc, char* argv[])
{
     if (argc < 3) {
         return -1;
     }

    std::ifstream updFile(argv[1], std::ios::binary | std::ios::ate);
     std::streamsize size = updFile.tellg();
     updFile.seekg(0, std::ios::beg);

    std::vector<char> buffer(size);
     if (updFile.read(buffer.data(), size))
     {
         std::cout << “loaded ” << buffer.size() << std::endl;
         for (size_t i = 0; i < buffer.size(); i++ ) {
             buffer[i] = decode(buffer[i]);
         }

        auto outputFile = std::fstream(argv[2], std::ios::out | std::ios::binary);
         outputFile.write((char*)buffer.data(), buffer.size());
         outputFile.close();
     }
}

Super Mini Mini 2 – TC motor part 2

Finally done with all my trips over the seas. So had time to pull apart the SMM2 and get to the second motor and inspect it.

it came pretty dirty, and i guess i didn’t clean off that drywall as well as thought, and that stuff isn’t good so cleaned that up first

then remove all the bolts on the top, not the door ones on the side though,they’re obviously not part of the top though, the enclosure will still in place when the top is removed.

image

i am pretty sure the previous owners just hosed it down with the green stuff. But we can see the same motor OEM but this time it has sealed brushes, don’t know why they used sealed on this one and not the other!

image

reasons as why not to use this style of molex connector in a place it can get contaminated. chips inside the connector, coolant you name it, so wrapped it.

image

the umbrella door opening mech gets a lot of swarf under it too, so good place to put on a cleaning schedule

close up showing the internal part of the connector has been contaminated.image

under this wheel the chips were getting bunched up so i cleaned it out

image

this is where the cables for the motor and sensors enter the steel tube frame, the bulkhead protecting ring had fallen out and it just wouldn’t stay in place, i pressed it in here in this picture but it just fell out again. this is meant to stop the edge of the metal slowing eating away at the insulation on the wiring since it’ll move/vibrate so its worth improving it. the haas one just wouldn’t lock into place, either a sloppy fit or it wore out

image

removing the access panel on the  steel tube to get access to the connectors. neither of these were tight and the left one was a tad cross threaded and the cable was loose and moved so wasn’t really sealed , also no gasket/seal on the panel so the coolant can get inside, as you can see here. sloppy fit on the bulkhead again. not a huge deal but could be better.

image

just like the green from swamp thing, it gets everywhere.

image

rear of panel

image

motor type SPG LS series LS85E82K-A06. haas part no 32-1875

image

image

and apparently after this i didn’t take more pictures though i thought i had..

the brushes on this motor have o ring seals so they were fine inside, all we did was clean up excess coolant and chips from everywhere feasible and then put it back together, no real issues there, add a gasket to that access panel. cover up the molex connectors for the sensor with some self annealing tape etc.

two!

image

also finally managed to get an CSMD NGC from eBay for a decent amount less than the list price from Haas. i have no idea what goes on with pricing on eBay, but i regularly see these go for 2x or more the list price from Haas for used ones… ($1600) maybe there is some secret Haas charge but we did get a HFO quote for it at that price. so who knows, but apparently you can arbitrage CSMDs on eBay. More on that later (the csmd not the arbitrage )

image

haas super mini mill 2 ngc tool changer carousel motor

hello,

don’t use tool 1 during testing the carousel, use tool 2

the folks that had our SMM2 with only 300 hours before us didn’t do the best job of keeping it clean and used some wacky looking coolants.

we noticed that the tool change was a bit sloppy/noisy and looking around people just said that is just Haas, but eventually while I was in the UK it failed with a lot of serious looking warnings 9847 etc that if you looked up talked about firmware mismatches but looking into it, it was basically just saying that the motor was on, but didn’t seem to be drawing current.

oh dear.

image

so pulled apart the machine, removed the access panel to the changer , lifted out the motor and looked at the brushes. they were contaminated so cleaned it up, put it back and seemed to be better.

when i got back to the USA a week or so later we noticed the tool change was throwing a quick alarm during a tool change.

so today we pulled it apart again

this motor being disconnected, throws an 858 ATC carousel motor electrical fault

cover off

image

there are some interesting design choices in here.

removed wiring, tool #1 connector wire is marked as tool #1, t.c mark isn’t marked as t.c. mark.

image

removed the motor from the housing

image

very steely.

image

baldor motor,. spg assembly https://spgmotor.net/bs-series/#1561724885376-8af2aff5-cb26e609-82cd0c42-a4318461-c59ceb77-0fe8467c-de89

image

pepperl fuchs

NBB4-12M45-Z5-0.3MM sensors

image

yep


Get to the brushes!

remove this cover, flat blade should come out easily.

image

image

fun stuff, it is not sealed , nor is meant to be by the manufacturer even though this is a custom one for haas ( seems to be, different ratio and they run it at 160V)

20211002_161250

egads

image

brush is all gunked up

image


the new dune is looking good, but low budget sandworms

20211002_161506

i did order a couple of replacement brushes, but they’re not really right since its just the carbon side with no provision for the wire, not a big deal since the brush itself is the same length as the new one and looks fine.

cleaned up the brush, and put it back ( added some teflon tape on retainer)

lets take a look at the other brush

image

interesting, totally fine no damage,  no corrosion.

when the motor is mounted, the dirty brush is on the inside where the green rectangle is

image

most of the box this is all in is “sealed”, gasket on the front, sealed wiring… except there is a ruddy great slot and bearing as part of the tool changer mech right next to the brush, so no we know why its contaminated on that side only….  why not rotate the motor 90o so the brushes aren’t exposed to the grease and coolant, after thought?

a plate over this with an indent for the nut/bolt would have been great too, and would have kept chips and coolant etc out of this mechanism,

some photos of the offending part, you can see the grease, coolant and chips

image

the nasty brush is right next to the nut, and we cleaned up the inside of the cabinet a lot , it was a lot worse.

image

a temp fix while we decide what to do about it long term, the teflon tape will help and i wrapped the motor with some aluminum tape

image

so if you get tool change errors, or a sloppy tool change where it feels like it doesn’t engage well or the motor just sounds funky, check that motor and brush, , it might just need a good cleaning.

measured 21 ohms on the motor after cleaning. (haas specs 5 to 20 ohms)

note correct placement of wiring at the front of the motor versus behind it

image

next we are going to pull out the shuttle motor and check it, that does not look fun to remove.

also this is a fun fella.

image

image

hello onsemi/fairchild ?

cheers

Teknik Quickset V6.3.8 under Windows 10

Update: Teknik have updated the app with these fixes and released V6.4.0 they accidentally included a readme that is actually a copy of the exe again as a txt file. so I let them know and they’re fixing it too but confirmed V6.4.0 works as expected on Windows 10

I’d recently added Windows 10 to  my main since it has a lot of nice stuff, wsl2/sandboxing etc. and as much as I like windows 7 it is time for a dual boot setup.

Anyway for the projects I am currently working on, so far only the Teknik QuickSet software failed to install/run.

The failure is from an .OCX file from a third party that allows them to use Hotkeys that get processed before VBA etc sees them.

How to fix it

Step 1. Download and install quickset 6.3 from Teknik.

It’ll tell you a OCX failed to register, ignore it in the popup and it will continue the install process.

Step 2. Grab this zip archive and extract it https://www.desaware.com/support/downloads/spyworks/signedcomponents.zip

Step 3. Replace the files in the Quickset folders with the files from this zip.

Step 4. Open up an elevated CMD prompt (Run as administrator)

image

Step 5. Go to the folder where quickset DLLs are (default location below)

cd “C:\Program Files (x86)\Teknic\Quickset 6.3”
regsvr32 DWSHK80.OCX
exit

We’re done with the command prompt now.

That should be it. QuickSet will now run as expected.

image

teknic servos and servo drivers

This is a W I P and will be adding to it as time permits, also as we do discovery and get it working.

We picked up a few teknic 2311 servos and controllers from ebay, the eclipse, sst and control point trajectory planner. The plan is to convert all our existing machines that use steppers into servos. Starting the journey with a HF Mini Lathe conversion its easy to handle and if you slam it into itself during tuning (which will likely happen) then its not going to do a lot of damage to itself or anyone else, since its tiny in comparison to our other Lathe/machines. Plus it is neat to have a small CNC lathe.

Prolog

Teknic make nice gear, they sell their ClearCore controller which is a neat little dev box, as well as ClearPath servos  that are stepper replacements that take step/dir and then translate all that into the proper servo movements for a brushless motor.

Since we managed to get a good deal on the servo drives and motors we decided to venture into the world of tuning Teknic servos.

Teknic support is basically two levels :-

one:  buy 100s or 100s of units a year of the eclipse and we’ll remotely tune and help
two: buy a clearpath

Which is fair enough.

There is also no auto tune feature and no hardware monitor port on Eclipse drives, the SSTs have them but are older tech. ClearPaths have auto tune, which is a fine choice, but at first you might be how do i get the fine control i’m looking for out of step/dir that i do from –/+ 10V

From best we can tell, you download the QuickSet software for your controller, load a windows .ini style config for your drive/motor setup if there is one and start from there. If there is no config for your setup you start off with the basic parameters of the motor from the datasheet and start PID style tuning on it, first on the bench with no load. Then with the load you are planning to drive.

Since Teknic won’t help us (after all we did buy from eBay we’re on our own) and we want to have interoperability between our motion controllers (tinyg/kflop) and the eclipse/servo drives.

Sounds easy enough…..

Quickset Software

The QuickSet software has an RS232 connection to the driver that can read back the various monitor signals from the driver (some drivers have a hardware monitor line you can connect a scope too, the help docs muddle the descriptions so you spend a while looking for the non existing port, even looking for a TLC5615C 10 bit DAC convertor on the main board, so far haven’t found it. These folks really like TI.

The are two versions of QuickSet we’re using 6.3 since 5.x doesn’t handle our drive, the SST version is called SST-QuickSet.

First thing we did was head to duckduckgo and search for “how to tune teknic servos” etc , lots of ClearPath examples, nothing on the separates. What about examples of the scope traces or what you should expect to see ( beyond the normal PID style tuning or <1 1 >1 style stuff ). Nope, how about anything at all?

Nothing. Maybe someone else can find a page detailing how it works but couldn’t find a thing.

Since we had the basic unloaded motor setup finished the next step was to load it and measuring tracking errors. Poking around the software, found “Tracking (Mtr) Dir) [F3]” etc.

Noting that some of the functions have short cut keys for setup,  F3/F4/F5  Kv tune Setup (shift F1) we figured that these would be the ones to use and setup first.  each shortcut changes a few parameters and setups the scopes and stimulus generator into  the right modes.

Tracking error still seems to evade though, we think  we’re looking at it.But not sure, i’ve looked thru the apps help section and its not great. The tune mode help sections go in circles, literally.

Searching the help section finally took us to a page that says

image

Phone support whats this ? I didn’t see anything obvious in the app. I did note the  Advanced section in the options page being greyed out so filled that for later investigation.

In the initial motor setup phase there is a shortcut keypress you have to do to enable editing the basic motor parameters. it’s ctrl+shift+M 

So i could look at the Registered Hotkeys, or i could say do Phone? ctrl+Shift+P ?

image

ControlPoint basic interface

image

Standalone phone support window

image

This fun box

image

I feel like one of the Hackers in the movies now where i guessed the password in one try. But really turns out I’m just one of those hackers that tried the obvious thing first and it worked “password123” etc. This may even be documented somewhere.

But now we can set a bar  showing the Direction tracking Error

image

It enables a tonne of other settings too.

For the ControlBasic scripting there is an old windows help format file that is a hassle to read, so just convert it to a modern one (HelpNDoc does it well)

For now we’ve got the motor moving back and forth with a slight “feels like a step” motion when using step/dir that might be the style of pulses from the TinyG we used to test, or its a setup error in the drive.

Using QuickSet the motor moves great, and its crazy torquey.

We initially tuned Kv then KP/Ki etc as discussed in the help section.

inital runs

Hardware setup

Most of the connections are molex, we lucked out that the eBay seller supplied cut and uncut cables. Also orderd spares from Arrow. SST and Eclipse here

Molex

0039012180 https://www.arrow.com/en/products/0039012180/molex
0039012200 https://www.arrow.com/en/products/0039012200/molex
0039000039 https://www.arrow.com/en/products/0039000039/molex
0050579405 https://www.arrow.com/en/products/0050579405/molex
0039012060 https://www.arrow.com/en/products/0039012060/molex
0016020119 https://www.arrow.com/en/products/0016020119/molex

TE

1-480698-0 https://www.arrow.com/en/products/1-480698-0/te-connectivity
1376348-1 https://www.arrow.com/en/products/1376348-1/te-connectivity

https://www.teknic.com/files/downloads/E2-3L_manual.pdf All listed in  here on page  73

Also DB25 male needed

RS232 tx/rx to the host used a usb adapter and it worked fine.  Pins 2 and 3 on the DB-25

Drive enable , have to ground that. Watch out for the drive jumping into life if its badly configured or such Pin 4 DB-25

Supply +5v power for the logic board  Pin 20 DB-25

Pin 21 DB-25 is GND

Supply a solid 75VDC from a power supply designed for servos, they don’t play well with most SMPS, the toridials with a full wave rectifier and a few caps do well. Molex 4 Pin

Wire up motors and controllers per datasheet.

Also add step/dir for later testing. you don’t need it for the initial tune

image

Thats about it.

Notes

These are just mostly notes to ourselves. Some of the values have changed

Links

https://www.teknic.com/files/product_info/E2-3L_r1.5.pdf

https://www.teknic.com/files/product_info/N23_Industrial_Grade_Motors.pdf

https://www.teknic.com/files/downloads/Teknic_SSt_System_Manual_Rev3.8.pdf

https://www.artisantg.com/info/Teknic_SSt_1500_301_Manual_2016122792438.pdf

https://www.teknic.com/downloads/

OK Keyboard version 2(3) part 1

Another blog entry which is incomplete so I will keep adding to it as my schedule allows.

This is technically the third revision of the One Key Per GPIO Keyboard OKPGK since there is an APA102 2020 and a SK6812 version of the original. I’m still using the APA102 2020s. I added thicker traces, USB C and a few more caps to help with the very last LED in the chain, i can hopefully double the speed the LEDs are updated, though they’re fast as it is and we’ll run into the limits of the LED itself hopefully, if not already there.

This isn’t an article about the creation of they keyboard, its more of a board log/process thing.

Switches and LEDS

image

Added bluetooth with the MDBT 50 module

image

Setting it up for the PNP

image

I like to put the boards in the orientation they end up on the PNP if possible.

Building the BOM with BOM-XS.ulp

image

Prepping for export to the Neoden4 Software NEODEN-4.ulp

image

BOM-Xs.ulp for getting a parts list and order together

image

At this point its worth going back over the schematic and making sure you’re using the same device for parts that are used more than once. The 10K resistors didn’t get grouped since I used one device from the sparkfun lbr and one from microbuilder. Which happened because I imported the Bluetooth module from another one of my designs.

After using the replace feature of Eagle they are now properly grouped. Adding ATTRIBUTES from Mouser and Digikey parts helps a lot here, we normally try to use a common library where the footprints are verified and the attributes already exist. The more  data you can add here, the less work you have to do when you start to buy/build.

image

 

Prepping the Pick and Place

Magnetic bars hold it place and am using the fixed board method here.

image

Cataloging Parts

I decide to catalogue all the parts on the PNP since it has been a while since we did a run, which took a while! All the reels photographed and documented, it is so much easier to do as you’re going along. We usually keep the NSL default stack updated in the default ULP but as you change the reels you can export/import the previous stack from each different setup so there is a .USS file that is usually newer than the default stack, and we’re still working out what our default stack is.

I made a Google Sheet so we can track the changes, I plan to tie this into the export scripts so it builds up either a USS or the default stack automatically from this spreadsheet.

image

Fiducials

Next is setting up the fiducials, basically what I’m doing is locating where the fiducuals are physically on the PNP, then look at where eagle thinks they are, then using the difference between the two points (x1-x2) (y1-y2) and entering that into the Eagle Export section, which means the board is placed at the correct physical location. The machine then will run the auto fiducial finder and adjust everything automatically for any small differences during mounting or such.

I have yet to annotate this video.

Doing that gives me the XY offset for this, and entering the Xd/Yd into the X and Y boxes the outputted CSV file will contain the physical coordinates on the PNP. I’d have taken more photos of the process, but the fish pump in the PNP gets annoying to listen too (and they’re all in the video linked above)

image

Some of the components don’t fit in reels so I’ll make  a custom tray..

One of the issues we have with lack of storage space is sometimes its easier to buy new components than find them, especially if it is tiny parts. Placed an order for the CPUs and also picked up a few extra MBDT50 with the external antenna and a suitable antenna, ended up being arrow and mouser, can never seem to find an order that is from one place. It’ll also give an opportunity to check the antennas with the VNA which is always fun.

 

Another App

I threw together a quick MFC application that OAuths to the Google Sheets API and can pull the data down, then we write the USS file to the local folder for the Eagle ULP to use. I’d liked to have embedded it all into the ULP but the Oauth part is trickier there.  Alternatively publishing the sheet to the Web and then just get/json etc but I prefer this since its a two way process.

image

The idea here is you can keep a master spreadsheet with the state of the current Pick and Place setup. Data can also be edited here and pushed back to the spreadsheet. For different machines, can have different spreadsheets.

The push back is for things like Nozzle ID and X and Y of pickup, place/pickup height etc which is often fine tuned on the machine.

I haven’t pulled the images yet, not sure if I will. Rather than getting crazy on support applications I want MVP (Minimum Viable Product) the idea is to cut down spent in preparation

May also output a script that updates or adds ATTRIBUTES for the OEM Part numbers in the sch/brd file.