fibre laser arrives… let the games begin

We picked up a 60W fibre laser after we were looking for some stuff and saw a youtube video on MOPA lasering…

Fast forward to the arrival, it got stuck in customs partly due to FDA, partly due to the time of year, we picked it up from Kelly @ Wuhan Optical which of course has its own drama right after the New Years break with the virus. Which meant even though it’d been  in customs for a while, it was very carefully disinfected with alcohol etc during unpacking.

After setup we fired up the laser and did some tests.

60W MOPA Fibre laser, it is a tasty piece of kit

 image

There is  bunch of stuff to setup in the configs and try to tune it to the coloured metal output. Like this! Amazing stuff.

Pretty neat

Stainless Steel

imageimage

Copper coated plastic or something

Dithered

image

No dither

image

Stainless steel 0.15mm thick

image

Tool Plate

image

Welding plate from Lowes, hard to see the colours

image

image

image

Block of aluminium

image

The Setup

Thus follows a bunch of images ( A ) to share with others, (  B ) for us to find later.. .which is half what this blog is.

Make sure you load the correct CAL file  for the lense you are using, if you start EzCad2 and it says “Can’t find file” or such, it is likely missing the CAL file. Which you set the location of the in the Param(F3) menu

Settings for the actual engrave/cut.

Ok back to the blog.

The Chinese software is less than great…

Here we are again, most Chinese software actually does most of what you need, it is very focused on you can do it if you do enough fiddling around, and workarounds for 90% of the stuff it’ll cover, the make it work, and stopping before the make it great practice of software dev. But given the razor thin margins and other such things , who would be shocked ? China is really good at what they do, but it is very specific.

The first thing i notice about the software is the key (F1) to display the red safe for eyes laser to show the box or contour of the thing you’re about to lase , is right next to the KEY (F2) that actually fires the laser which is not safe for eyes… so that is a terrible design choice. so watch out for that. In China they don’t even use the protective glasses (not that the supplied ones are great, get some thorlabs ones), so they’re not worried about this issue I would imagine.

Since you can use the foot pedal to start the laser running, why not just disable F2 altogether ? I’ve already accidentally fired the laser once, but I swear I hit apply and not FIRE LASERS !!!

Motor Driver 12,800 steps setup on ours

http://www.sah.co.rs/media/sah/techdocs/dm542_manual.pdf

60w Fibre Laser data sheet

https://cdn.specpick.com/images/photonics/products/RFL-P60M.pdf

The author(me) gets distracted during this write up.

Good question, Hmmm, ok why not, I’ll pause this text typing and go and look for it..(literally just happened)

Taking a look at EzCad2.exe imports I see lots of uses of

GetKeyState

That would be for checking the state of virtual keys, and VK_F2 is 0x71.. 0x70 is F1, is that it?  Virtual Key Codes

I see, 0x10(VK_SHIFT), 17(VK_CONTROL) 164 (VK_LMENU) and 165 (VK_RMENU)

It’s looking for key modifiers which is a typical use of GetKeyState, most likely i’d look for the WinProc and see the key down/up events but we’re in MFC so it’ll likely be a handler via DDX , PreTranslateMessage, Accelerators CWnd func, etc.

Poking around to see if it is obvious which is used while running the app. I run it inside the debugger but not connected to the Laser, ahh, the keys are disabled since I have no control board connected, very sensible but less than ideal for what we’re doing here.

Looks like the standard way to enable/disable controls in MFC based on a call to the LMC Driver setup.

We can see that the dialog ID for the red laser is 17023/0x427f using Spy++

image

There are a few PostMessageW’s to that ID , both id 0x111 ( WM_COMMAND) with the dialog ID and 0 so menu. Seems like a thing. Which leads us to this :-

if ( wParam == 113 )

and

if ( wParam == 112)

113 and 112 are the virtual keys for F2 and F1 respectively. They each post a message to the dialog ID’s 17023 and 1321 so if this is the right spot looking at the ID for the Mark(F2) button would equal 1321

image

0x529 = 1321, so confirmed.

There are two places it checks that VK_ and they both call  PostMessageW(hwnd, WM_COMMAND, dialogID , 0 );

Here is one location

0x04FF7DD 83 7D 0C 70                             cmp     [ebp+wParam], 0x70

0x04FF808 83 7D 0C 71                             cmp     [ebp+wParam], 0x71

Changing those should change the key, if you need to update the GUI change LANG=lang_Enu.ini

For F2

BTNMARK=Mark(F2)
CONTMARKALLPART=F2 Continuous mark all

For F1

BTNLIGHT=Red(F1)
MARKALLPART=F1 Mark all part

Also a couple of NOPs here and there will disable the key altogether! Note there are two sets of this code, this is just one.

0x04FF7DD 83 7D 0C 70                             cmp     [ebp+wParam], 0x70 ; VK_ Code
0x04FF7E1 75 25                                   jnz     short loc_0x4FF808

0x04FF7E3 6A 00                                   push    0x0              ; lParam
0x04FF7E5 68 7F 42 00 00                          push    0x427F           ; wParam
0x04FF7EA 68 11 01 00 00                          push    0x111            ; Msg
0x04FF7EF 8B 0D 44 AC 5B 00                       mov     ecx, dword_0x5BAC44
0x04FF7F5 E8 86 F2 F0 FF                          call    GetHwnd
0x04FF7FA 50                                      push    eax             ; hWnd
0x04FF7FB FF 15 5C 1A 56 00                       call    ds:PostMessageW
0x04FF801 33 C0                                   xor     eax, eax
0x04FF803 E9 E4 02 00 00                          jmp     loc_0x4FFAEC
0x04FF808

loc_0x4FF808:

0x04FF808 83 7D 0C 71                             cmp     [ebp+wParam], 0x71 ; VK Code
0x04FF80C 75 25                                   jnz     short loc_0x4FF833
0x04FF80E 6A 00                                   push    0x0              ; lParam
0x04FF810 68 29 05 00 00                          push    0x529            ; wParam
0x04FF815 68 11 01 00 00                          push    0x111            ; Msg
0x04FF81A 8B 0D 44 AC 5B 00                       mov     ecx, dword_0x5BAC44
0x04FF820 E8 5B F2 F0 FF                          call    GetHwnd
0x04FF825 50                                      push    eax            ; hWnd
0x04FF826 FF 15 5C 1A 56 00                       call    ds:PostMessageW

Saturday morning, I  ended up writing a little app to patch the EXE and change the keys, it searches for the code sequences for the VK’s which there are two of each, so I search for three, and if it only finds two sets, adjusts the location and changes the VK code then write outs a new EXE. I added the F keys and SPACE .. Don’t use a key thats already in use! Edit the lang\lang_enu.ini for english text to change the EzCad2 GUI to match the new keys.

Warning: You should never actually use this any of this information in this post, this linked app or even test it, as it may do something bad and unexpected to everything, and anything, damage you, damage the laser, the planet, etc. Its purely for informational purposes only.

https://github.com/charlie-x/fibrelasertools 

I tried this app on the actual laser, it works but there is another place where F2 is getting used so have to find that one too. I set the red mark to VK_SPACE and then use the footswitch to fire the laser, that way you dont need to tap F1 or F2, i’ll find the others.

OK we’ve gone on a journey, now we’re back to writing up the rest.

So why are we doing this

Apart from the obvious, because we can, the host software isn’t great, why not, and the other reasons

We want to make stainless steel stencils for PCB, so i try an export of the DXF directly from Eagle, the gerber tCream and various modified versions via Illustrator , using type 3 .ai files. Unfortunately the geom doesn’t close properly the stating position and ending position of everything is off so it leaves a little tab behind, drawing it in ezcad works properly its only on the dxf/gerber/ai import this happens, the geo is contiguous and closed so its something else.

Looks good, except for the one corner that isn’t cut out!

image

After fiddling around I poke around at the software, we’ve already updated EzCad  from the version supplied. I’d like to be able to drive the laser directly so investigations begin.

Various notes i’ve found about the process

DXF import failed for R12 DXF’s AC1009 (eagle spits these out) loading in AI and exporting as DFX 2004 AC1018 worked.

Straight from Eagle R12 to EzCad2

image

Load into AI and export as 2004

image

R12 as viewed in AI

image

Now in EzCad2 as a 2004 DXF

image

Gerber import from eagle works, but pads end up as circles unless you add any rounded corner to the pads of your devices.

tCream as viewed in Eagle

image

tCream gerber as viewed in EzCad

image

The rectangular pads appear as circles, but rounded pads appear correctly. The workaround I found for this is tedious but worked

image

Edit the package, then the PAD properties and set roundness to something more than 0 then update the board, and re-export the gerber.

Laser fails to cut corners of shapes

This one is partly why I started this, we had a false test that allowed us to cut rectangles clearly but I think the settings changed between the working cut and then our stencil tests, basically one small tab at the end of the laser operation would be left behind.

image

image

At first since we could cut proper rectangles and only imported geo seemed to not work,  it seem like it was an import issue. But revisiting we decided to change some of the parameters

image

Start TC is the time to delay before cutting, it gives time for the system to catch up, –200 was the recommend value. Changing this to –400 pretty much cleared up the issue, but also increasing Laser Off TC  and Polygon TC to 300 helped as well Polygon TC is the dwell time when it finishes cutting one part of the line in the polygon, Laser off is how long the laser stays on at the end of the cutting operation. Increasing these too much will cause a larger burn area on corners or end of cut so you have to tune it.

Cutting 0.127mm stainless steel our settings currently are

image

image

For stencil material it is a little thinner at .011mm and we were able to cut that at Loop 30 , Power 55%

We fiddled with the Frequency(Khz) to get a smooth cut, this changes the amount of power at the cut too and it varies on the waveform, doesn’t just increase with frequency. The lowest setting for our cutter is 20 KHz which makes seperate distinctive dots. 50 KHz was the default and it wasn’t quite as smooth an edge so 75 worked. Q we’re keeping to be as close to a non MOPA fibre as possible.

The effects of the Frequency being too low, reduced power to see the effect

image

image

Tried hatching and engraving tests as well, melted, and reformed steel. Commerical stencil cutters are likely a moving head with air assist.

image

Cross hatch with a run around the edge.

image

EzCad can be infuriating

One of the annoyances for me is the pen selection, it took me a few tries to figure out if you have anything selected on the work area, you can’t choose a new pen on the right side, so pressing ESC or de-selecting any objects in the workspace fixes that. It takes fovever to setup your default pens, which are stored in the .ezd file too.

Deep Diving

Breaking down the .EZD format

Taking a look at the binary EZD format to see if we can get some insight on how to write custom ones.

First i loaded EzCad and just saved the empty workspace, this generates a 317KBfile. All the pens are in there.

The first thing tried is switching off Default param and resaving, them comparing

Looks good, a 00 to a 01, right side is default param on.

L27320    04 00 00 00 00 00 00 00 04 00 00 00 01 00 00 00 08 00 00 00 00 00 00 00 00 40 7F 40 08 00 00 00  …………………….@@….
R27320    04 00 00 00 01 00 00 00 04 00 00 00 01 00 00 00 08 00 00 00 00 00 00 00 00 40 7F 40 08 00 00 00  …………………….@@….

image

This seems promising, Unicode string, in blocks are there 256 of them?

image

[C:\ezcad\tests]strings -u default-param-off-empty.ezd >f

Strings v2.53 – Search for ANSI and Unicode strings in binary images.
Copyright (C) 1999-2016 Mark Russinovich
Sysinternals – http://www.sysinternals.com

[C:\ezcad\tests]wc f

f:                   Words: 257        Lines: 257        Chars: 1800

hmm 257?, a quick peek at the file F tells us why, the first string is EZCADUNI which is part of the header. so yep its 256 pens as expected.

EZCADUNI
Default
Default
Default

There is a header too

image

Which is followed by a whole bunch of 00 FF FF FF, a

image

Lets see if the location of the Pens section is fixed, or if it moves around and theres a section pointer or something, i’ll add a rectangle to the file and see what changes.

That causes quite a large change in the file. But its encouraging because what happened was it overwrote some info at the start of the file, some of the ff ff ff 00 changed, the pens seemed to stay in the same place and a whole chunk of stuff was added to the end of the file.

image

Lets change one of the pen settings now. I set Loop from1 to 2

—————————————————–
L27320    04 00 00 00 00 00 00 00 04 00 00 00 02 00 00 00 08 00 00 00 00 00 00 00 00 40 7F 40 08 00 00 00  …………………….@@….
R27320    04 00 00 00 00 00 00 00 04 00 00 00 01 00 00 00 08 00 00 00 00 00 00 00 00 40 7F 40 08 00 00 00  …………………….@@….

Which is in the default section

image

I’ll add some more shapes to the file and see if the pens move.

Another large change in that first section with the FF FF FF 00 and this time a smaller amount of data, i added a circle at 0,0 and 10mm

image

Pens didn’t move though. Lets change another parameter in the pen. I changed speed from 500 to 501

—————————————————–
R27320    04 00 00 00 00 00 00 00 04 00 00 00 02 00 00 00 08 00 00 00 00 00 00 00 00 40 7F 40 08 00 00 00  …………………….@@….

L27320    04 00 00 00 00 00 00 00 04 00 00 00 02 00 00 00 08 00 00 00 00 00 00 00 00 50 7F 40 08 00 00 00  …………………….P@….

Speed 502

—————————————————–
L27320    04 00 00 00 00 00 00 00 04 00 00 00 02 00 00 00 08 00 00 00 00 00 00 00 00 50 7F 40 08 00 00 00  …………………….P@….
R27320    04 00 00 00 00 00 00 00 04 00 00 00 02 00 00 00 08 00 00 00 00 00 00 00 00 60 7F 40 08 00 00 00  …………………….`@….

Speed 1000, from 502

Changing Power from 100 to 1

—————————————————–
L27340    00 00 00 00 00 00 F0 3F 04 00 00 00 20 4E 00 00 04 00 00 00 04 00 00 00 04 00 00 00 2C 01 00 00  ……ð?…. N…………..,…
R27340    00 00 00 00 00 00 59 40 04 00 00 00 20 4E 00 00 04 00 00 00 04 00 00 00 04 00 00 00 2C 01 00 00  ……Y@…. N…………..,…

So its a mix of ints and doubles.

0x27300 R value byte
0x27301 G value byte
0x27302 G value byte
0x27304 Length of Unicode string int. 4 bytes,
0x27308 Unicode Default 440065006600610075006C0074000000

This moves depending on length of the parameter name, so these are absolute with a unicode string length of 0x10 from base to consider

0x27324 Use default parameter  uint8
0x2732c for Loop (range seems 1 to 10,000)  4 bytes int
0x27334 for Speed(MM) its stored as a double 8 bytes.
0x27340 for Power% double
0x2734c for Frequency(Khz) Int32 4 bytes, max 10,000

Checking Q but its +4 i think and uint32

0x2735c for Start TC Int32 4 bytes signed
0x273e4 for Laser Off TC(US) uint32 4 bytes, max 10,000
0x27364 for End TC(US) uint32 4 bytes, max 10,000
0x2736C for Polygon TC(US) uint32 4 bytes, max 10,000

Enough to start a simple application to edit these valus, firing up MSVC.

Few hours later !

image

I’m using default values for testing, so they don’t necessarily make sense yet. Added import and export of CSV so you can edit in text or a spreadsheet, or even share on google sheets etc. has Q since ours is a MOPA.

I found some test grids on web that seem to be an older verison of ezCad and there is an  offset difference, which i figured there would be since its such a large offset into the file.

Loading and resaving the EZD in the version of EZCad 2.14.11 i used while testing moved it to the same offset. 0x27300 for mine, 0x464 for the one i found on the web, here’s the header. Since you can load and resave it and it appears at the same offset i’m not super worried but it’d be nice to decode how it knows where it is, assuming there is some relative offset.

image

EZD writing and CSV Export working

image

It is feature complete now and i put it on my github for testing, it is very specific to the version of EzCad so i’ve only tested 2.14.11

https://github.com/charlie-x/fibrelasertools

I think i figured out how the pen offset works , offset 0x160 has a uint32_t that gets to the base of the pens – 0x10, tested a few versions of .ezd files and it worked ok, github updated again.

image

Into the Driver

EzCad2 comes with its own driver board the LMC1/4fiber etc which work over USB and also double a as dongle, which means you can’t use EzCad2 offline as it can’t save. A quick 30 second poke with a hex editor fixed that though just so I could use it on my main PC for testing and it takes FOREVER to build the arrays to test different power and frequency levels

LMC1.dll LMCMIO.dll are the main files for talking to the board, there is an MarkEz.dll available that you can use to interface with the laser, but it is really high level and  might be a paid option ?

I converted the header to English.

#ifndef MARKEZDDLL_H
#define MARKEZDDLL_H

// All functions return an integer value
#define LMC1_ERR_SUCCESS 0 // Success
#define LMC1_ERR_EZCADRUN 1 // Found that EZCAD is running
#define LMC1_ERR_NOFINDCFGFILE 2 // Cannot find EZCAD.CFG
#define LMC1_ERR_FAILEDOPEN 3 // Failed to open LMC1
#define LMC1_ERR_NODEVICE 4 // No valid lmc1 device
#define LMC1_ERR_HARDVER 5 // lmc1 version error
#define LMC1_ERR_DEVCFG 6 // Cannot find the device configuration file
#define LMC1_ERR_STOPSIGNAL 7 // Alarm signal
#define LMC1_ERR_USERSTOP 8 // User stops
#define LMC1_ERR_UNKNOW 9 // Unknown error
#define LMC1_ERR_OUTTIME 10 // Timeout
#define LMC1_ERR_NOINITIAL 11 // Not initialized
#define LMC1_ERR_READFILE 12 // Error reading file
#define LMC1_ERR_OWENWNDNULL 13 // The window is empty
#define LMC1_ERR_NOFINDFONT 14 // Cannot find the font with the specified name
#define LMC1_ERR_PENNO 15 // Wrong pen number
#define LMC1_ERR_NOTTEXT 16 // The object with the specified name is not a text object
#define LMC1_ERR_SAVEFILE 17 // Failed to save the file
#define LMC1_ERR_NOFINDENT 18 // The specified object cannot be found
#define LMC1_ERR_STATUE 19 // This operation cannot be performed in the current state

// Initialize the lmc1 control card
// input parameter: strEzCadPath EzCad software execution path
// bTestMode = TRUE means test mode bTestMode = FALSE means normal mode
// pOwenWnd represents the parent window object. If stop marking is required, the system will intercept messages from this window.

typedef int ( *LMC1_INITIAL ) ( wchar_t* strEzCadPath, //ezcad’s working directory
                                BOOL bTestMode, // Whether it is a test mode
                                 HWND hOwenWnd ); // The parent window

// Close the lmc1 control card

typedef int ( *LMC1_CLOSE ) ();

typedef int  ( *LMC1_STOPMARK ) ();

// Load the ezd file and clear all objects in the database
// Input parameters: strFileName EzCad file name
typedef int ( *LMC1_LOADEZDFILE ) ( wchar_t* strFileName );

// Mark all data in the current database
// Input parameters: bFlyMark = TRUE enables flying marking bFlyMark = FALSE enables flying marking
typedef int ( *LMC1_MARK ) ( BOOL bFlyMark );

// Mark the specified object in the current database
// input parameter: strEntName the name of the specified object to be processed
typedef int ( *LMC1_MARKENTITY ) ( wchar_t* strEntName );

// Fly mark the specified object in the current database
// input parameter: strEntName
typedef int ( *LMC1_MARKENTITYFLY ) ( wchar_t* strEntName );

// read the input port of lmc1
// input parameter: data of input port read in
typedef int ( *LMC1_READPORT ) ( WORD& data );

// Write the output port of lmc1
// input parameter: the data of the output port to write to
typedef int ( *LMC1_WRITEPORT ) ( WORD data );

// Get a preview image of all data in the current database
// input parameter: pWnd to which window the preview image is displayed
// nBMPWIDTH preview image width
// nBMPHEIGHT height of preview image
typedef  CBitmap* ( *LMC1_GETPREVBITMAP ) ( HWND hwnd, int nBMPWIDTH, int nBMPHEIGHT );


// Call the dialog for setting device parameters
typedef int ( *LMC1_SETDEVCFG ) ();

const int HATCHATTRIB_ALLCALC = 0x01;// All objects are calculated together as a whole
const int HATCHATTRIB_BIDIR   = 0x08;// Bidirectional padding
const int HATCHATTRIB_EDGE    = 0x02;// Walk once
const int HATCHATTRIB_LOOP    = 0x10;// Ring fill

// Set the current filling parameters. If you want to enable filling when adding a new object to the database, this parameter will be used
typedef int ( *LMC1_SETHATCHPARAM ) ( BOOL bEnableContour, // Enable the contour itself
                                       int bEnableHatch1, // Enable fill 1
                                       int nPenNo1, // fill pen
                                       int nHatchAttrib1, // fill attribute
                                       double dHatchEdgeDist1, // fill line margins
                                      double dHatchLineDist1, // fill line spacing
                                       double dHatchStartOffset1, // The starting offset distance of the fill line
                                      double dHatchEndOffset1, // The end offset distance of the fill line
                                       double dHatchAngle1, // fill line angle (radian value)
                                      int bEnableHatch2, // Enable padding 1
                                       int nPenNo2, // fill pen
                                       int nHatchAttrib2, // fill attribute
                                       double dHatchEdgeDist2, // fill line margins
                                      double dHatchLineDist2, // fill line spacing
                                      double dHatchStartOffset2, // The starting offset distance of the fill line
                                      double dHatchEndOffset2, // The end offset distance of the fill line
                                       double dHatchAngle2 ); // fill line angle (radian value)

// Set the current font parameters. If you want to add a new text object to the database, this font parameter will be used.
typedef int ( *LMC1_SETFONTPARAM ) ( wchar_t * strFontName, // font name
                                     double dCharHeight, // character height
                                      double dCharWidth, // character width
                                      double dCharAngle, // character inclination
                                     double dCharSpace, // character spacing
                                     double dLineSpace, // line spacing
                                     BOOL bEqualCharWidth ); // etc. Character width mode

// Get the processing parameters corresponding to the specified pen number
typedef int ( *LMC1_GETPENPARAM ) ( int nPenNo, // the pen number to be set (0-255)
                                    int & nMarkLoop, // processing times
                                    double & dMarkSpeed, // Marking times mm / s
                                     double & dPowerRatio, // power percentage (0-100%)
                                    double & dCurrent, // Current A
                                    int & nFreq, // frequency HZ
                                    int & nQPulseWidth, // Q pulse width us
                                    int & nStartTC, // start delay us
                                    int & nLaserOffTC, // Laser off delay us
                                     int & nEndTC, // end delay us
                                     int & nPolyTC, // corner delay us //
                                     double & dJumpSpeed, // jump speed mm / s
                                     int & nJumpPosTC, // Jump position delay us
                                    int & nJumpDistTC, // Jump distance delay us
                                     double & dEndComp, // end point compensation mm
                                     double & dAccDist, // Acceleration distance mm
                                    double & dPointTime, // dot delay ms
                                    BOOL & bPulsePointMode, // Pulse point mode
                                     int & nPulseNum, // Number of pulse points
                                     double & dFlySpeed );

// Set the processing parameters corresponding to the specified pen number
typedef int ( *LMC1_SETPENPARAM ) ( int nPenNo, // the pen number to be set (0-255)
                                    int nMarkLoop, // processing times
                                    double dMarkSpeed, // Marking times mm / s
                                    double dPowerRatio, // power percentage (0-100%)
                                     double dCurrent, // Current A
                                     int nFreq, // frequency HZ
                                     int nQPulseWidth, // Q pulse width us
                                     int nStartTC, // start delay us
                                    int nLaserOffTC, // Laser off delay us
                                    int nEndTC, // end delay us
                                    int nPolyTC, // corner delay us //
                                    double dJumpSpeed, // jump speed mm / s
                                     int nJumpPosTC, // Jump position delay us
                                     int nJumpDistTC, // jump distance delay us
                                     double dEndComp, // end point compensation mm
                                    double dAccDist, // Acceleration distance mm
                                     double dPointTime, // dot delay ms
                                     BOOL bPulsePointMode, // Pulse point mode
                                     int nPulseNum,
                                     double dFlySpeed ); // Number of pulse points

// Clear all data in the object library
typedef int ( *LMC1_CLEARENTLIB ) ();

// The meaning of the numbers in the alignment
//   6 —  5 — 4
//   |            |
//   |            |
//   7     8      3
//   |            |
//   |            |
//   0 —  1 — 2

// Add new text to the database
typedef int ( *LMC1_ADDTEXTTOLIB ) ( wchar_t * pStr, // string to add
                                      wchar_t * pEntName, // String object name
                                      double dPosX, // x coordinate of the bottom left corner of the string
                                      double dPosY, // the y-coordinate of the bottom left corner of the string
                                     double dPosZ, // the z coordinate of the string object
                                      int nAlign, // alignment mode 0-8
                                      double dTextRotateAngle, // Angle value (radian value) of the string rotation around the base point
                                      int nPenNo, // processing parameters used by the object
                                     BOOL bHatchText ); // Whether to fill the text object

// Add the specified file to the database
// Supported files are ezd, dxf, dst, plt, ai, bmp, jpg, tga, png, gif, tiff, etc.
typedef int ( *LMC1_ADDFILETOLIB ) ( wchar_t * pFileName, // file name
                                      wchar_t * pEntName, // String object name
                                     double dPosX, // The x coordinate of the base left corner of the file
                                      double dPosY, // y-coordinate of the base point of the bottom left corner of the file
                                      double dPosZ, // z-coordinate of the file
                                      int nAlign, // alignment mode 0-8
                                     double dRatio, // File scaling
                                     int nPenNo, // processing parameters used by the object
                                      BOOL bHatchFile ); // Whether the file object is filled. This parameter is invalid if it is an ezd file or a bitmap file.


// Add the curve to the database
typedef int ( *LMC1_ADDCURVETOLIB ) ( double ptBuf[][2], // curve vertex array
                                       int ptNum, // number of curve vertices
                                      wchar_t * pEntName, // curve object name
                                      int nPenNo, // Pen number used by the curve object
                                       int bHatch ); // whether the curve is filled


#define BARCODETYPE_39 0
#define BARCODETYPE_93 1
#define BARCODETYPE_128A 2
#define BARCODETYPE_128B 3
#define BARCODETYPE_128C 4
#define BARCODETYPE_128OPT 5
#define BARCODETYPE_EAN128A 6
#define BARCODETYPE_EAN128B 7
#define BARCODETYPE_EAN128C 8
#define BARCODETYPE_EAN13 9
#define BARCODETYPE_EAN8 10
#define BARCODETYPE_UPCA 11
#define BARCODETYPE_UPCE 12
#define BARCODETYPE_25 13
#define BARCODETYPE_INTER25 14
#define BARCODETYPE_CODABAR 15
#define BARCODETYPE_PDF417 16
#define BARCODETYPE_DATAMTX 17
#define BARCODETYPE_USERDEF 18

#define BARCODEATTRIB_REVERSE 0x0008 // Barcode reverse
#define BARCODEATTRIB_HUMANREAD 0x1000 // Display human recognition characters
#define BARCODEATTRIB_CHECKNUM 0x0004 // A check code is required
#define BARCODEATTRIB_PDF417_SHORTMODE 0x0040 // PDF417 is shortened mode
#define BARCODEATTRIB_DATAMTX_DOTMODE 0x0080 // DataMtrix is ​​in dot mode
#define BARCODEATTRIB_CIRCLEMODE 0x0100 // Customize QR code to circle mode


#define DATAMTX_SIZEMODE_SMALLEST 0
#define DATAMTX_SIZEMODE_10X10 1
#define DATAMTX_SIZEMODE_12X12 2
#define DATAMTX_SIZEMODE_14X14 3
#define DATAMTX_SIZEMODE_16X16 4
#define DATAMTX_SIZEMODE_18X18 5
#define DATAMTX_SIZEMODE_20X20 6
#define DATAMTX_SIZEMODE_22X22 7
#define DATAMTX_SIZEMODE_24X24 8
#define DATAMTX_SIZEMODE_26X26 9
#define DATAMTX_SIZEMODE_32X32 10
#define DATAMTX_SIZEMODE_36X36 11
#define DATAMTX_SIZEMODE_40X40 12
#define DATAMTX_SIZEMODE_44X44 13
#define DATAMTX_SIZEMODE_48X48 14
#define DATAMTX_SIZEMODE_52X52 15
#define DATAMTX_SIZEMODE_64X64 16
#define DATAMTX_SIZEMODE_72X72 17
#define DATAMTX_SIZEMODE_80X80 18
#define DATAMTX_SIZEMODE_88X88 19
#define DATAMTX_SIZEMODE_96X96 20
#define DATAMTX_SIZEMODE_104X104 21
#define DATAMTX_SIZEMODE_120X120 22
#define DATAMTX_SIZEMODE_132X132 23
#define DATAMTX_SIZEMODE_144X144 24
#define DATAMTX_SIZEMODE_8X18 25
#define DATAMTX_SIZEMODE_8X32 26
#define DATAMTX_SIZEMODE_12X26 27
#define DATAMTX_SIZEMODE_12X36 28
#define DATAMTX_SIZEMODE_16X36 29
#define DATAMTX_SIZEMODE_16X48 30

// Add barcode to database
typedef int ( *LMC1_ADDBARCODETOLIB ) ( wchar_t * pStr, // string
                                        wchar_t * pEntName, // String object name
                                        double dPosX, // The x coordinate of the base left corner of the character
                                        double dPosY, // Y-coordinate of the base left corner of the character
                                         double dPosZ, // character z coordinate
                                        int nAlign, // alignment mode 0-8
                                        int nPenNo,
                                        int bHatchText,
                                         int nBarcodeType, // Barcode type
                                        WORD wBarCodeAttrib, // Barcode attribute
                                        double dHeight, // the height of the entire barcode
                                         double dNarrowWidth, // the narrowest module width
                                        double dBarWidthScale[4], // bar width ratio (compared to the narrowest module width)
                                        double dSpaceWidthScale[4], // space width ratio (compared to the narrowest module width)
                                        double dMidCharSpaceScale, // character space ratio (compared to the narrowest module width)
                                        double dQuietLeftScale, // the left margin width ratio of the barcode (compared to the narrowest module width)
                                         double dQuietMidScale, // ratio of blank width in barcode (compared to the narrowest module width)
                                         double dQuietRightScale, // the right margin width ratio of the barcode (compared to the narrowest module width)
                                        double dQuietTopScale, // ratio of blank width on barcode (compared to the narrowest module width)
                                        double dQuietBottomScale, // ratio of blank width under barcode (compared to the narrowest module width)
                                         int nRow, // Number of QR code lines
                                         int nCol, // Number of QR code columns
                                        int nSizeMode, // DataMatrix size mode 0-30
                                         double dTextHeight, // Font height
                                         double dTextWidth, // Font width
                                         double dTextOffsetX, // X direction offset of human recognition characters
                                         double dTextOffsetY, // Y-direction offset of human recognition characters
                                        double dTextSpace, // Person recognition character spacing
                                         double dDiameter,
                                         wchar_t * pTextFontName ); // Text font name

// Change the text of the specified text object in the current database
// input parameter: strTextName the name of the text object whose content you want to change
// strTextNew new text content
typedef int ( *LMC1_CHANGETEXTBYNAME ) ( wchar_t * strTextName, wchar_t * strTextNew );


// Set the rotation transformation parameters
// Input parameters: dCenterX x coordinate of rotation center
// dCenterY y coordinate of rotation center
// dRotateAng rotation angle (radian value)
typedef void ( *LMC1_SETROTATEPARAM ) ( double dCenterX, double dCenterY, double dRotateAng );


//////////////////////////////////////// /////////////// //////////////////
// Extended axis function

// The extension axis moves to the specified coordinate position
// input parameter: axis extended axis 0 = axis 0 1 = axis 1
// GoalPos coordinate position
typedef int ( *LMC1_AXISMOVETO ) ( int axis, double GoalPos );

// Expansion axis correction origin
// input parameter: axis extended axis 0 = axis 0 1 = axis 1
typedef int ( *LMC1_AXISCORRECTORIGIN ) ( int axis );

// Get the current coordinates of the extended axis
// input parameter: axis extended axis 0 = axis 0 1 = axis 1
typedef double ( *LMC1_GETAXISCOOR ) ( int axis );

// The extension axis moves to the specified pulse coordinate position
// input parameter: axis extended axis 0 = axis 0 1 = axis 1
// nGoalPos pulse coordinate position
typedef int ( *LMC1_AXISMOVETOPULSE ) ( int axis, int nGoalPos );

// Get the current pulse coordinates of the extended axis
// input parameter: axis extended axis 0 = axis 0 1 = axis 1
typedef int ( *LMC1_GETAXISCOORPULSE ) ( int axis );


// Reset extended axis coordinates
// Input parameters: bEnAxis0 = enable axis 0 bEnAxis1 = enable axis 1
typedef double ( *LMC1_RESET ) ( BOOL bEnAxis0, BOOL bEnAxis1 );


// Font type attribute definition
#define FONTATB_JSF 0x0001 // JczSingle font
#define FONTATB_TTF 0x0002 // TrueType font
#define FONTATB_DMF 0x0004 // DotMatrix font
#define FONTATB_BCF 0x0008 // BarCode font

// Font record
struct lmc1_FontRecord {
    wchar_t szFontName[256]; // font name
    DWORD dwFontAttrib; // font attribute
};

// Get all font parameters supported by the current system
// Input parameters: None
// Output parameter: nFontNum font number
// Return parameter: lmc1_FontRecord * array of font records
typedef lmc1_FontRecord * ( *LMC1_GETALLFONTRECORD ) ( int & nFontNum );

// Save all objects in the current database to the specified ezd file
// Input parameters: strFileName ezd file name
typedef int ( *LMC1_SAVEENTLIBTOFILE ) ( wchar_t * strFileName );

// Get the maximum and minimum coordinates of the specified object.
typedef int ( *LMC1_GETENTSIZE ) ( wchar_t * pEntName, // String object name
                                    double & dMinx,
                                   double & dMiny,
                                   double & dMaxx,
                                   double & dMaxy,
                                    double & dZ );

// Move the relative coordinates of the specified object
typedef int ( *LMC1_MOVEENT ) ( wchar_t * pEntName, // String object name
                                 double dMovex,
                                double dMovey );


// Scale the specified object, zoom center coordinates (dCenx, dCeny)
typedef int ( *LMC1_SCALEENT ) ( wchar_t * pEntName, // String object name
                                 double dCenx,
                                 double dCeny,
                                  double dScaleX,
                                  double dScaleY );
// Mirror specified object, mirror center coordinates (dCenx, dCeny) bMirrorX = TRUE X direction mirror bMirrorY = TRUE Y direction
typedef int ( *LMC1_MIRRORENT ) ( wchar_t * pEntName, // String object name
                                   double dCenx,
                                  double dCeny,
                                  BOOL bMirrorX,
                                   BOOL bMirrorY );

// Rotate the specified object, and rotate the center coordinates (dCenx, dCeny)
typedef int ( *LMC1_ROTATEENT ) ( wchar_t * pEntName, // String object name
                                  double dCenx,
                                   double dCeny,
                                   double dAngle );
// Rotate the specified object, the coordinates of the rotation center (dCenx, dCeny) dAngle = rotation angle (positive counterclockwise, the unit is degree)
typedef int ( *LMC1_SETROTATEMOVEPARAM ) ( double dMoveX, // X displacement distance
        double dMoveY, // Y displacement distance
        double dCenterX, // X-coordinate of rotation center
        double dCenterY, // Y-coordinate of rotation center
         double dAngle ); // rotation angle
typedef int ( *LMC1_REDLIGHTMARK ) (); // Mark the red light frame

typedef int ( *LMC1_MARKLINE ) ( double x1,
                                 double y1,
                                 double x2,
                                  double y2,
                                 int pen ); //

typedef int ( *LMC1_MAKEPOINT ) ( double x,
                                   double y,
                                   double delay,
                                  int pen ); //
// Get the total number of objects
// output parameter: total number of objects
typedef int ( *LMC1_GETENTITYCOUNT ) ();

// Get the name of the object with the specified sequence number
// Input parameters: nEntityIndex The number of the specified object (circle: 0-(lmc1_GetEntityCount ()-1))
// output parameter: name of szEntName object
typedef int ( *LMC1_GETENTITYNAME ) ( int nEntityIndex, wchar_t szEntName[256] );

// Get the dog’s customer ID number
typedef WORD ( *LMC1_GETCLIENTID ) ();

typedef int ( *LMC1_GETCURCOOR ) ( double & x,
                                    double & y ); //


typedef int ( *LMC1_GOTOPOS ) ( double x, double y );
// Get the text of the specified object
typedef int ( *LMC1_GETTEXTBYNAME ) ( wchar_t * strTextName, wchar_t strText[256] );


#endif

It’s a good start but it is very tied to the version of EzCad we’re trying to see what the cost of the SDK is, it also seems like the LMC board has a lockout for the SDK mode that has to be unlocked by the EzCad folks, so a bit of a dead end.

Using the DLL is the usual LoadLibrary with GetProcAddress setup.

m_lmc1_LoadEzdFile ( ( LPTSTR ) ( “test.ezd”) );

etc…. Well it’d be nice to get yet more control.

Next lets look at the LMCMIO.dll, after extracting the exports with dumpbin /exports and and then undec to demangle the c++ names.

#ifndef _LMCMIO_H_
#define _LMCMIO_H_ (1)

// charliex
#define API __cdecl

#define tagR struct tagResult

tagR API MIO_AxisGoOrigin(unsigned char device,unsigned short,int,class CWnd *cWnd);

void API MIO_Close(int);
  void API MIO_Close(void);

tagR API MIO_Cmd(unsigned char device,unsigned short,unsigned short,unsigned short,unsigned short,unsigned short,unsigned short);
  tagR API MIO_DisableLaser(unsigned char device);
  tagR API MIO_ENABLEZ(unsigned char device,int);

int API MIO_EarseEpprom(unsigned char device);
  int API MIO_EleExecute(unsigned char device,char *,void *,unsigned long,void *,unsigned long,unsigned long &);

tagR API MIO_EnableLaser(unsigned char device);

int API MIO_EppromGetMark(unsigned char device,unsigned char * const);
  int API MIO_EppromSetMark(unsigned char device,unsigned char * const);
  int API MIO_EppromSetTimeStamp(unsigned char device);

tagR API MIO_ExecuteList(unsigned char device);
  tagR API MIO_GetAxisPos(unsigned char device,unsigned short);
  struct _GUID API MIO_GetClassGUID(void);

void * API MIO_GetDevHandle(int);

tagR API MIO_GetFlyWaitCount(unsigned char device,int,long &);
  tagR API MIO_GetListStatus(unsigned char device);
  tagR API MIO_GetLmcInfo(unsigned short * const);

int API MIO_GetLmcPartNo(unsigned char device,unsigned short &,unsigned short &);

tagR API MIO_GetMarkCount(unsigned char device,int,long &);
  tagR API MIO_GetPositionXY(unsigned char device);
  tagR API MIO_GetSerialNo(unsigned char device);
  tagR API MIO_GetState(unsigned char device);
  tagR API MIO_GetVersion(unsigned char device,unsigned short);
  tagR API MIO_GotoXY(unsigned char device,unsigned short x,unsigned short y);
  tagR API MIO_IPG_GetStMO_AP(unsigned char device);
  tagR API MIO_IPG_OpenMO(unsigned char device,int);
tagR API MIO_LaserSignalOff(unsigned char device);
  tagR API MIO_LaserSignalOn(unsigned char device);

int API MIO_ModifyEpprom(unsigned char device);

tagR API MIO_MoveAxisTo(unsigned char device,unsigned short,unsigned long,class CWnd *cwnd);
  tagR API MIO_NewCmd(unsigned char device,unsigned short *,unsigned long);

int API MIO_NxpGetDevelopNum(unsigned char device,void *,unsigned long);
  int API MIO_Open(int);
  int API MIO_OpenNewDev(void);
  int API MIO_ReadAllEpprom(unsigned char device,void *,unsigned long);
  int API MIO_ReadEpprom(unsigned char device,void *,unsigned long);
  int API MIO_ReadNxp(unsigned char device,void *,unsigned long);

tagR API MIO_ReadPort(unsigned char device);
  tagR API MIO_Reset(unsigned char device);
  tagR API MIO_ResetList(unsigned char device);
  tagR API MIO_RestartList(unsigned char device);
  tagR API MIO_SETZDATA(unsigned char device,unsigned char,unsigned char,unsigned short);
  tagR API MIO_SetAxisMotionParam(unsigned char device,unsigned short,unsigned short);
  tagR API MIO_SetAxisOriginParam(unsigned char device,unsigned short,unsigned short);
  tagR API MIO_SetControlMode(unsigned char device,unsigned short);
  tagR API MIO_SetDelayMode(unsigned char device,unsigned short);
  tagR API MIO_SetEndOfList(unsigned char device);
  tagR API MIO_SetFirstPulseKiller(unsigned char device,unsigned short);
  tagR API MIO_SetFlyRes(unsigned char device,unsigned short,unsigned short,unsigned short,unsigned short,unsigned short);
  tagR API MIO_SetFpkParam2(unsigned char device,unsigned short,unsigned short,unsigned short,unsigned short);
  tagR API MIO_SetFpkParam(unsigned char device,unsigned short,unsigned short,unsigned short,unsigned short);
  tagR API MIO_SetLaserMode(unsigned char device,unsigned short);

int API MIO_SetLmcPartNo(unsigned char device,unsigned short,unsigned short);
  tagR API MIO_SetMaxPolyDelay(unsigned char device,unsigned short);
  tagR API MIO_SetPwmHalfPeriod(unsigned char device,unsigned short);
  tagR API MIO_SetPwmPulseWidth(unsigned char device,unsigned short);
  tagR API MIO_SetSPISimmerCurrent(unsigned char device,unsigned short,int);
  tagR API MIO_SetStandby(unsigned char device,unsigned short,unsigned short,int);

int API MIO_SetSysParam(unsigned char device,unsigned char *,unsigned int,unsigned char *,unsigned int);

tagR API MIO_SetTiming(unsigned char device,unsigned short);
  tagR API MIO_StopExecute(unsigned char device);
  tagR API MIO_StopList(unsigned char device);
  tagR API MIO_TransferDataZ(unsigned char device,unsigned short *,unsigned long);
  tagR API MIO_Verify(unsigned char device);
  tagR API MIO_WriteAnalogPort1(unsigned char device,unsigned short);
  tagR API MIO_WriteAnalogPort2(unsigned char device,unsigned short);
  tagR API MIO_WriteAnalogPortX(unsigned char device,unsigned short,unsigned short);
  tagR API MIO_WriteCmdBuf(unsigned char device,struct tagLmcCmd *);
  tagR API MIO_WriteCorTable(unsigned char device,int,unsigned short (* const);[65][2]);

int API MIO_WriteEpprom(unsigned char device,void *,unsigned long);
  int API MIO_WriteNxp(unsigned char device,void *,unsigned long);

tagR API MIO_WritePort(unsigned char device,unsigned short,unsigned short);

#endif

Lots of interesting stuff there, C++ and C,  this looks like the last interface to the LMC1 board, there is also the LMC1.dll

Processing it in the same way , we see this.

public: __thiscall CLaserParamExtOutput::CLaserParamExtOutput(void) public: __thiscall CLmcDev::CLmcDev(class CLmcDev const &) public: __thiscall CLmcDev::CLmcDev(void) public: __thiscall CQComByte::CQComByte(class CQComByte const &) public: __thiscall CQComByte::CQComByte(void) public: __thiscall CLaserParamExtOutput::~CLaserParamExtOutput(void) public: __thiscall CLmcDev::~CLmcDev(void) public: virtual __thiscall CQComByte::~CQComByte(void) public: class CLaserParamExtOutput & __thiscall CLaserParamExtOutput::operator=(class CLaserParamExtOutput const &) public: class CLmcDev & __thiscall CLmcDev::operator=(class CLmcDev const &) public: class CQComByte & __thiscall CQComByte::operator=(class CQComByte const &) const CLmcDev::`vftable' const CQComByte::`vftable' public: int __thiscall CLmcDev::AddCmd(unsigned short,unsigned short,unsigned short,unsigned short,unsigned short,unsigned short) public: void __thiscall CQComByte::Answer(unsigned char *,int) public: int __thiscall CLmcDev::AppendNullCmd(void) public: int __thiscall CLmcDev::AppendNullCmdAndRun(void) public: int __thiscall CQComByte::Ask(unsigned char *,unsigned long,int) public: int __thiscall CQComByte::AskAnswerStop(unsigned char *,unsigned long,unsigned char) public: int __thiscall CQComByte::AskStr(class CString,int) public: int __thiscall CLmcDev::AxisGotoOrigin(int,int) public: int __thiscall CLmcDev::AxisMoveTo(int,double,int,int) public: int __thiscall CLmcDev::AxisMoveToDlg(int,double,int,int) public: int __thiscall CLmcDev::AxisMoveToGoal(int,int,int,int) public: double __thiscall CLmcDev::CalcJumpTime(double,unsigned short &) public: int __thiscall CLmcDev::ChangeEnt(class CEntity *) public: int __thiscall CLmcDev::CheckDevelopNumber(void) public: int __thiscall CLmcDev::CheckLaserState(int) public: void __thiscall CLaserParamExtOutput::Clear(void) public: void __thiscall CLmcDev::ClearLockInputPort(void) public: int __thiscall CLmcDev::Co2QuickPulseModeLine(class CArray<class Pt2d,class Pt2d &> &,int,double,double) public: int __thiscall CQComByte::Connect(void) protected: void __thiscall CLmcDev::D2P(int const &,double const &,unsigned short &)const public: void __thiscall CLmcDev::D2P(double const &,double const &,unsigned short &,unsigned short &)const public: int __thiscall CLmcDev::D2P_X(double const &)const public: int __thiscall CLmcDev::D2P_Y(double const &)const public: void __thiscall CQComByte::Destroy(void) public: void __thiscall CLmcDev::DestroyExcelCor(void) public: void __thiscall CQComByte::DisConnect(void) public: int __thiscall CLmcDev::EleExecute(char *,void *,unsigned long,void *,unsigned long,unsigned long &) public: void __thiscall CQComByte::EmptyAnswerBuf(void) public: void __thiscall CLmcDev::EnableLockInputPort(int) public: void __thiscall CLmcDev::ExcelCor(double &,double &) public: int __thiscall CLmcDev::ExeCurCmd(int) public: int __thiscall CLmcDev::ExecMarkCmdFile(int) public: int __thiscall CLmcDev::ExtAxisGetCurPos(int) public: int __thiscall CLmcDev::ExtAxisGotoOrigin(int) public: int __thiscall CLmcDev::ExtAxisMoveToPos(int,int,double,double,double) public: virtual int __thiscall CLmcDev::FinishMark(void) public: void __thiscall CQComByte::GetAnswer(unsigned char *,int,int &) public: int __thiscall CLmcDev::GetAxisCoorInt(int const &) public: class CString __thiscall CLmcDev::GetBoardHardInfoString(void) public: int __thiscall CLmcDev::GetCardNo(void)const public: int __thiscall CLmcDev::GetCardSN(void)const public: int __thiscall CLmcDev::GetCardState(unsigned short &) public: double __thiscall CLmcDev::GetCoor(int const &) public: int __thiscall CLmcDev::GetCurCmdNum(void) public: unsigned short __thiscall CLmcDev::GetCurOutPortData(void) public: int __thiscall CLmcDev::GetCurPosition(double &,double &) public: unsigned short __thiscall CLmcDev::GetDevState(void) protected: double __thiscall CLmcDev::GetDistMap(int const &,unsigned short const &)const public: class Pt2d __thiscall CLmcDev::GetExcelCorPt(class Pt2d) public: int __thiscall CLmcDev::GetFiberStateCode(void) public: void __thiscall CLmcDev::GetFieldOffset(double &,double &) public: int __thiscall CLmcDev::GetFlyPulseCount55(int,unsigned short &) public: int __thiscall CLmcDev::GetFlySpeed(double,double &) public: int __thiscall CLmcDev::GetFlyWaitCount(int,int &) public: int __thiscall CLmcDev::GetHardwareVer(unsigned short &,unsigned short &) public: int __thiscall CLmcDev::GetIOBoardType(void) public: int __thiscall CLmcDev::GetLaserStateErrCode(void) public: int __thiscall CLmcDev::GetLmcBoardNo(unsigned short &,unsigned short &) public: int __thiscall CLmcDev::GetLockInputPort(unsigned short &) public: int __thiscall CLmcDev::GetMarkCount(int,int &) public: int __thiscall CLmcDev::GetMarkTime(int &,int &,int &,int &) public: double __thiscall CLmcDev::GetMaxCurrent(void) public: double __thiscall CLmcDev::GetMaxPowerRatio(void) protected: unsigned short __thiscall CLmcDev::GetPluseMap(int const &,double const &)const public: double __thiscall CLmcDev::GetRealMap(double,double,double * const) public: int __thiscall CLmcDev::GetState(unsigned short &) public: unsigned short __thiscall CLmcDev::GetStopSignal(void) public: int __thiscall CLmcDev::GetUserData(unsigned short &,unsigned short &) public: int __thiscall CLmcDev::Goto(double,double) protected: int __thiscall CLmcDev::GotoOriginKernal(int,int) public: int __thiscall CLmcDev::IPGGetConfigExtend(int,unsigned short &,unsigned short &) public: int __thiscall CLmcDev::IPGGetPrepump(int &) public: int __thiscall CLmcDev::IPGGetPulseWidth(int &) public: int __thiscall CLmcDev::IPGGetPulseWidthMaxIndex(int &) public: int __thiscall CLmcDev::IPGGetPulseWidthPs(int &) public: int __thiscall CLmcDev::IPGSetConfigExtend(int,int) public: int __thiscall CLmcDev::IPGSetPrepump(int) public: int __thiscall CLmcDev::IPGSetPulseWidth(int) public: int __thiscall CLmcDev::IPGSetPulseWidthIndex(int) public: int __thiscall CLmcDev::IPGSetPulseWidthPs(int) public: void __thiscall CLmcDev::IPG_CloseMO(void) public: int __thiscall CLmcDev::IPG_GetStateMO_AP(unsigned short &) public: void __thiscall CLmcDev::IPG_OpenMO(void) public: int __thiscall CLmcDev::InitCfgExcelCor(struct LmcCfg *) public: int __thiscall CLmcDev::InitGetBoardHardInfo(void) public: int __thiscall CLmcDev::InitLmc(int,int) public: int __thiscall CLmcDev::InitZData(double *,unsigned long *,int,unsigned long,int) public: int __thiscall CLmcDev::InitZDataEx(double *,unsigned long *,int,int) public: int __thiscall CQComByte::IsAnswered(void) public: int __thiscall CLmcDev::IsAxisGoHome(int) public: int __thiscall CLmcDev::IsAxisInOrigin(int) public: int __thiscall CLmcDev::IsBoardSupportFun(int) public: int __thiscall CQComByte::IsConnect(void) public: int __thiscall CLmcDev::IsEntWillChange(class CEntity *) public: virtual int __thiscall CLmcDev::IsEsc(void) public: int __thiscall CLmcDev::IsExtAxisInOrigin(int,int) public: int __thiscall CLmcDev::IsMustDestroyDog(void) public: void __thiscall CLmcDev::IsSleepTimeCloseLaser(void) public: int __thiscall CLmcDev::IsStartExeList(void) public: int __thiscall CLmcDev::IsValidDev(void) public: int __thiscall CLmcDev::LaserLeakHandle(int) public: void __thiscall CLmcDev::LaserSignalOn(int) public: int __thiscall CLmcDev::MM2P(int const &,double const &)const public: int __thiscall CLmcDev::MarkBarcodePointMode(class CEntity *,int,double,double,class CMatrix2d *) public: int __thiscall CLmcDev::MarkEnt(class CEntity *,int,double,double,class CMatrix2d *) public: int __thiscall CLmcDev::MarkEntArray(class CEntity *,int,double,double,class CMatrix2d *) public: int __thiscall CLmcDev::MarkLineArray(class CArray<class Pt2d,class Pt2d &> &,double,double) public: int __thiscall CLmcDev::MarkWobble(class CArray<class Pt2d,class Pt2d &> &,double,double,double,double) public: int __thiscall CLmcDev::NewCmd(unsigned short *,int,unsigned short &,unsigned short &) public: struct tagResult __thiscall CLmcDev::New_MIO_AxisGoOrigin(unsigned char,unsigned short,int,class CWnd *) public: struct tagResult __thiscall CLmcDev::New_MIO_MoveAxisTo(unsigned char,unsigned short,unsigned long,class CWnd *) protected: void __thiscall CLmcDev::P2D(int const &,unsigned short const &,double &)const public: void __thiscall CLmcDev::P2D(unsigned short const &,unsigned short const &,double &,double &)const public: double __thiscall CLmcDev::P2D_X(int const &)const public: double __thiscall CLmcDev::P2D_Y(int const &)const public: double __thiscall CLmcDev::P2MM(int const &,int const &)const public: int __thiscall CLmcDev::QS_Digtal_Output(unsigned short,unsigned short) public: int __thiscall CQComByte::ReadCommBlock(unsigned char *,int) public: int __thiscall CLmcDev::ReadCorFile(class CString) public: int __thiscall CLmcDev::ReadPort(unsigned short &) public: virtual int __thiscall CLmcDev::ReadyMark(int) public: int __thiscall CLmcDev::Reset(void) public: virtual int __thiscall CLmcDev::Run(void) public: void __thiscall CLmcDev::SPI_CloseGlobal(void) public: void __thiscall CLmcDev::SPI_OpenGlobal(void) public: void __thiscall CLmcDev::SPI_SetWave(void) public: virtual int __thiscall CLmcDev::ScanBmp(class CEntity *,double,double,class CMatrix2d *) public: int __thiscall CLmcDev::ScanBmpPtBuf(struct BmpScanPt *,int,int,double,double,double) public: int __thiscall CLmcDev::SetAnalog2(unsigned short) public: int __thiscall CLmcDev::SetAxisAccTime(int) public: void __thiscall CLmcDev::SetBoardSNStr(class CString) public: void __thiscall CLmcDev::SetCardNo(int,int) public: int __thiscall CLmcDev::SetCo2FPK(int,double,double) public: int __thiscall CLmcDev::SetCurrent(double) public: void __thiscall CLmcDev::SetDevState(unsigned short,int) public: int __thiscall CLmcDev::SetDisableZ(void) public: int __thiscall CLmcDev::SetEnableZ(void) public: int __thiscall CLmcDev::SetFPK(int,int,double,double,int,int) public: int __thiscall CLmcDev::SetFlyRes(int,double,int,int,double,int,int) public: int __thiscall CLmcDev::SetFreqAnalogOutput(int) public: void __thiscall CLmcDev::SetJumpDelayTC(double,double) public: void __thiscall CLmcDev::SetMaxCurrent(double) public: void __thiscall CLmcDev::SetMaxPowerRatio(double) public: void __thiscall CLmcDev::SetOwen(class CWnd *) public: int __thiscall CLmcDev::SetPowerAnalogOutput(double) public: int __thiscall CLmcDev::SetSPISimmerCurrent(double) public: int __thiscall CLmcDev::SetZData(unsigned char,unsigned char,unsigned short) public: void __thiscall CLmcDev::ShowMarkInfo(class CString) public: int __thiscall CLmcDev::StandardMarkLine(class CArray<class Pt2d,class Pt2d &> &,int,double,double,int) public: int __thiscall CLmcDev::StopExecute(void) public: void __thiscall CLmcDev::UpdateCurAxisCoor(int) public: int __thiscall CLmcDev::VarTextElementConnectHostGetAnwser(class CEntSuperText *) public: int __thiscall CQComByte::WaitForAnswer(void) public: int __thiscall CLmcDev::WaitForFinish(void) public: int __thiscall CLmcDev::WaitForMarkFinish(void) public: int __thiscall CLmcDev::WeldLine(class CArray<class Pt2d,class Pt2d &> &,int,double,double) public: int __thiscall CLmcDev::WeldPoint(class CArray<class Pt2d,class Pt2d &> &,int,double,double) public: int __thiscall CQComByte::WriteCommBlock(unsigned char *,unsigned long) public: int __thiscall CLmcDev::WritePort(unsigned short) int __cdecl ctrlGetRaycusState(class CLmcDev *,unsigned short &,int &,int &) int __cdecl ctrlRaycusSetParam(class CLmcDev *,int,int,int) void __cdecl gf_CloseUsbMonitor(void) int __cdecl gf_DlgInputPassword(class CString &) int __cdecl gf_DlgSetCfg(int,class CLmcDev *,struct LmcCfg &,class CWnd *) class CString __cdecl gf_GetLmcCfgPath(void) class CLmcDev * __cdecl gf_GetLmcDev(void) struct tagResult __cdecl gf_GetLmcDevState(class CLmcDev *) void __cdecl gf_InitLmcCfg(struct LmcCfg &) int __cdecl gf_InitUsbMonitor(class CWnd *) int __cdecl gf_ReadLmcCfg(struct LmcCfg &,class CString) void __cdecl gf_RestartMark(void) int __cdecl gf_SaveLmcCfg(struct LmcCfg &,class CString) void __cdecl gf_SetLmcCfgPath(class CString) class CLmcDev * __cdecl gf_SetLmcDev(class CLmcDev *) void __cdecl gf_SetPauseFlag(int) int __cdecl gf_StartMarkDlg(class CString,class CString) int __cdecl gf_UsbMonitorGetNewDevice(void) public: int __thiscall CLmcDev::listChangeMarkCount(int) public: int __thiscall CLmcDev::listDelayTime(int) public: int __thiscall CLmcDev::listDelayTimeUs(int) public: int __thiscall CLmcDev::listDirectLaserSwitch(int) public: int __thiscall CLmcDev::listDirectMarkTo(double,double) public: int __thiscall CLmcDev::listEnableWeldPowerWave(int) public: int __thiscall CLmcDev::listEndOfList(void) public: int __thiscall CLmcDev::listFlyDelay(int) public: int __thiscall CLmcDev::listFlyEnable(int) public: int __thiscall CLmcDev::listFlyEncoderCount(int,int,int,int,int) public: int __thiscall CLmcDev::listIPGOpenMO(int) public: int __thiscall CLmcDev::listIPGPulseWidthIndex(int) public: int __thiscall CLmcDev::listIPGSetConfigExtend(int,int) public: int __thiscall CLmcDev::listIPGSetPrepump(int) public: int __thiscall CLmcDev::listIPGYLPMPulseWidth(int) public: int __thiscall CLmcDev::listIPGYLPMPulseWidthPs(int) public: int __thiscall CLmcDev::listJptSetParam(double,int,double) public: int __thiscall CLmcDev::listJumpSpeed(double) public: int __thiscall CLmcDev::listJumpTo(double,double,int) public: int __thiscall CLmcDev::listLaserExtOutput(int) public: int __thiscall CLmcDev::listLaserOffDelay(int) public: int __thiscall CLmcDev::listLaserOnDelay(int) public: int __thiscall CLmcDev::listLaserOnPoint(double) public: int __thiscall CLmcDev::listMarkCurrent(double) public: int __thiscall CLmcDev::listMarkEndDelay(int) public: int __thiscall CLmcDev::listMarkFreq(int) public: int __thiscall CLmcDev::listMarkPowerRatio(double) public: int __thiscall CLmcDev::listMarkPulseWidth(double) public: int __thiscall CLmcDev::listMarkSpeed(double) public: int __thiscall CLmcDev::listMarkTo(double,double,int) public: int __thiscall CLmcDev::listPolygonDelay(int) public: int __thiscall CLmcDev::listSetAnalogFPK(int,int,double,double,int,int) public: int __thiscall CLmcDev::listSetDaZ(double) public: int __thiscall CLmcDev::listSetDaZWord(unsigned short) public: int __thiscall CLmcDev::listSetMarkParam(struct MarkParam &) public: int __thiscall CLmcDev::listSetWeldPowerWave(int,unsigned short * const,unsigned short * const) public: int __thiscall CLmcDev::listWaitForInput(unsigned short,unsigned short) public: int __thiscall CLmcDev::listWritrPort(unsigned short) void __cdecl lmc_CloseDriver(void) int __cdecl lmc_GetValidDev(class CLmcDev * * const,int &) int __cdecl lmc_OpenDriver(int) public: int __thiscall CLmcDev::lsFlyWaitInput(int,int,int)

Yep, as figured this is a higher level C++ API to talk to LMCMIO.dll etc.

These are all 32 bit windows AFX/MFC dlls, you can tell via the CBitmap/CWnd etc

Some of the basic C funcs are just wrappers that call the MIO, like lmc_OpenDriver is a stub for MIO_Open, there is also a sprinkling of gf_ calls which seems mostly related to setup and configs as well as some utility functions.

DataMgr.dll is the other interesting DLL, that one is heavily tied to EzCad, checking the  LMC board and so on. more of the gf_  and AFX/MFC there interfaces to the barcode generation, Excel and huffman encoding in there too, so it does a lot of the heavy lifting for EzCad.

We’re trying to get the seller (Who has been very helpful so far) to work with us on the cutting issue for the stencils, unfortunately we have to get through all the basic troubleshooting and setup issues that most users will go thru (we did too) but i sent them an example EZD file that shows the issue, so hopefully they can cut it and test it, unfortunately New Year and Corona Virus since they’re in Wuhan….

So not a terrific amount of useful info so far, I was able to write a quick test app to init the system and see about the incompatibilities with the DLLs and different versions  of EzCad , it being a heavily C++ system there are a lot of ABI changes so anytime a vtable, or such changes it’ll break. It’s a lot easier to patch around this for pure C import DLLs.

Turning the file save on for EzCad to allow using it without being connected to the Laser, is useful too.

How to unmangle decorated names

This uses the undocumented unDNameEx function to demangle C++ names from msvc, its what the undname.exe uses, which you can just do a dumpbin /exports and feed the results into as well. First it scans the dll for the exports, then it demangles them. unDNameEx can cause memory issues if not used properly.

// demangler.cpp : This file contains the ‘main’ function. Program execution begins and ends there.

//

#include <windows.h>

#include <Dbghelp.h>

#pragma comment(lib,"dbghelp.lib")

#include <iostream>

#include <string>

#include <vector>

extern "C" PSTR __cdecl __unDNameEx(

    PSTR output_buffer,

     PCSTR mangled_name,

    DWORD cb,

    void* (__cdecl* memory_et)(DWORD),

    void(__cdecl* memory_free)(void*),

    PSTR(__cdecl* unk_GetParameter)(long i),

    DWORD un_flags

);

static const DWORD un_dwFlags = UNDNAME_COMPLETE;

static void* __cdecl _dAlloc(ULONG cb)

{

    return new (std::nothrow) char[cb];

}

static void __cdecl _dFree(void* p)

{

    if (p) {

    delete[] p;

    }

}

static PSTR __cdecl _dGetParameter(long ignore)

{

    static char none[] = "";

    return none;

}


bool GetDLLFileExports(const char *szFileName, std::vector<std::string>&pszFunctions)

{

    HANDLE hFile;

    HANDLE hFileMapping;

    LPVOID lpFileBase;

    PIMAGE_DOS_HEADER pImg_DOS_Header;

    PIMAGE_NT_HEADERS pImg_NT_Header;

    PIMAGE_EXPORT_DIRECTORY pImg_Export_Dir;

    hFile = CreateFile(szFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);

    if (hFile == INVALID_HANDLE_VALUE) {

        return false;

    }

    hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);

    if (hFileMapping == 0) {

        CloseHandle(hFile);

         return false;

    }

    lpFileBase = MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, 0);

    if (lpFileBase == 0) {

        CloseHandle(hFileMapping);

        CloseHandle(hFile);

        return false;

    }

    pImg_DOS_Header = (PIMAGE_DOS_HEADER)lpFileBase;

    pImg_NT_Header = (PIMAGE_NT_HEADERS)((LONG)pImg_DOS_Header + (LONG)pImg_DOS_Header->e_lfanew);


    if (IsBadReadPtr(pImg_NT_Header, sizeof(IMAGE_NT_HEADERS)) || pImg_NT_Header->Signature != IMAGE_NT_SIGNATURE) {

        UnmapViewOfFile(lpFileBase);

        CloseHandle(hFileMapping);

        CloseHandle(hFile);

        return false;

    }

    pImg_Export_Dir = (PIMAGE_EXPORT_DIRECTORY)pImg_NT_Header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;

    if (!pImg_Export_Dir) {

        UnmapViewOfFile(lpFileBase);

        CloseHandle(hFileMapping);

        CloseHandle(hFile);

        return false;

    }

    pImg_Export_Dir = (PIMAGE_EXPORT_DIRECTORY)ImageRvaToVa(pImg_NT_Header, pImg_DOS_Header, (DWORD)pImg_Export_Dir, 0);

    DWORD **ppdwNames = (DWORD **)pImg_Export_Dir->AddressOfNames;

    ppdwNames = (PDWORD*)ImageRvaToVa(pImg_NT_Header, pImg_DOS_Header, (DWORD)ppdwNames, 0);

    if (!ppdwNames) {

        UnmapViewOfFile(lpFileBase);

        CloseHandle(hFileMapping);

         CloseHandle(hFile);

        return false;

    }

    unsigned int nNoOfExports = pImg_Export_Dir->NumberOfNames;

    for (unsigned i = 0; i < nNoOfExports; i++) {

        char *szFunc = (PSTR)ImageRvaToVa(pImg_NT_Header, pImg_DOS_Header, (DWORD)* ppdwNames, 0);

        pszFunctions.push_back(szFunc);

        ppdwNames++;

    }

    UnmapViewOfFile(lpFileBase);

    CloseHandle(hFileMapping);

    CloseHandle(hFile);

    return true;

};


int main(int argc,char*argv[])      
{

    if (argc < 2) {

        fprintf(stderr,"need a dll to demangle as first parameter\n");

        exit(-1);

    }

    std::vector<std::string>pszFunctions;

    if (GetDLLFileExports(argv[1], pszFunctions)) {

        for (auto s : pszFunctions) {

            // undecorate them

            char* pUndecorated = __unDNameEx(

                NULL, s.data(), 0

                , _dAlloc, _dFree, _dGetParameter, un_dwFlags);

            printf("%s;\n", pUndecorated);

            delete[] pUndecorated;

        }

    }

}



Whats next..

There are a few ways I usually head into this sort of work, RE it all and rebuild, write trace and trampoline DLLS that monitor whats going on, and can inject, google for other people doing it (usually do that one last, but so far haven’t seen one! )

With this project I believe writing a DLL that can trace and intercept the data going via LMCMIO.dll is the way to go, Rohitab’s API Monitor will do a lot there, but a proxy/trampoline/interposer DLL is going to be more useful. So lets do that instead…..

Exit stage left.

CURTAIN

Act 1, Scene 2.

(this section is still in editing)

Proxy DLL

Some of the issues with making a proxy C++ DLL are around the name mangling, unlike C you can’t just alias it in the .def  file , which is also useful for pass thru.

The issue is that the linker sees the first @ and treats it as the ordinal or something else and terminates the name, so if the mangled name is ?Foo@@bar in the .DEF file , the linker will spit out ?Foo so when you go to load the dll it’ll fail. One way to fix the issue is to demangle the name and have the proxy DLL use the exact same name, which can be a hassle. So my approach was to post process the DLL to change the name, i’ll swap out @ in the name to $ or something that the DEF file will carry thru to the linker. Then we just post process the DLL to rename that $ back to the @.

Example of the original mangled function name

1    0 00001B60 ?MIO_AxisGoOrigin@@YA?AUtagResult@@EGHPAVCWnd@@@Z

demangled it looks like

struct tagResult API MIO_AxisGoOrigin(unsigned char device,unsigned short,int,class CWnd *cWnd);

so in the proxy DLL .def if you do (the aliased function is also changed but that is typical)

?MIO_AxisGoOrigin@@YA?AUtagResult@@EGHPAVCWnd@@@Z=QMIO_AxisGoOriginAAYAQAUtagResultAAEGHPAVCWndAAAZ @1

But what you’ll get is :-

?MIO_AxisGoOrigin$$YA?AUtagResult

Which of course isn’t great. compiling it as the demangled should be ok, however name mangling can change so its not always the best way to do it. So with this one, i’m going to replace the @’s with something else and then replace it in the resulting binary instead. It’d be great to find out yet another way, no amount of quoting in the DEF seems to fix it. But the SaR is usually fine.

A proxy DLL simply exports the original function names in its DLL and then internally loads the original DLL which is usually renamed. It then constructs a trampoline to jump to the original function

hL would be the original DLL, and we fetch the pointer to the original function

  p[0] = GetProcAddress(hL, “?MIO_AxisGoOrigin@@YA?AUtagResult@@EGHPAVCWnd@@@Z”);

For x86 you can just inline __asm the jump to the original function. Later on adding extra code to dump what its doing etc. For x64 it’d be an external .asm file. So far all the laser stuff is 32 bits so just covering that for now

extern “C”” {

    void QMIO_AxisGoOriginAAYAQAUtagResultAAEGHPAVCWndAAAZ() {

        __asm

          {            jmp p[0 * 4]

        }

    }

}

Whats going on here is a DLL function lives at a memory address, GetProcAddress returns that address in memory. Instead of the original DLLs address we return our functions address, then after we run our code it jumps to the old DLL address and continues the function as normal,  we can snoop, change data skip it. and so on.

DLLs can do some interesting things like we can have it bypass our DLL and jump straight to the original function, in the original DLL as well.

Build the proxy DLL

I have a modified version of https://www.codeproject.com/Articles/16541/Create-your-Proxy-DLLs-automatically for this, there is also Proxify 

This the skeleton proxy function for MIO_AxisGoOrigin in LMCIO.DLL, it saves the flags and registers, then prints a message to the internal debug system in windows, then jumps to the original function.

Starting with LMCMIO.dll

// struct tagResult __cdecl MIO_AxisGoOrigin(unsigned char,unsigned short,int,class CWnd *)

// ?MIO_AxisGoOrigin@@YA?AUtagResult@@EGHPAVCWnd@@@Z

extern "C" __declspec(naked) void __E__0__()

{

    __asm pushad

    __asm pushfd

    OutputDebugStringA("struct tagResult __cdecl MIO_AxisGoOrigin(unsigned char,unsigned short,int,class CWnd *)\n");

    __asm popfd

    __asm popad

    __asm

    {

        jmp procs[E__MIO_AXISGOORIGIN__YA_AUTAGRESULT__EGHPAVCWND___Z*4];

    }

}

This code reads the address of the original function, and stores it for use in the proxy function

procs[E__MIO_AXISGOORIGIN__YA_AUTAGRESULT__EGHPAVCWND___Z] = GetProcAddress(hL,”?MIO_AxisGoOrigin@@YA?AUtagResult@@EGHPAVCWnd@@@Z”);

if( procs[E__MIO_AXISGOORIGIN__YA_AUTAGRESULT__EGHPAVCWND___Z] == NULL ) { OutputDebugStringA("Failed to get proc address ?MIO_AxisG

It replaces the ? and @’s with _ so that it will compiler properly as they are not valid C++ function names and we’ll deal with the binary later.

So stepping back slightly and generating a DLL to show the mangling issue as it appears , making a .DEF file that uses the mangled names

EXPORTS

?MIO_AxisGoOrigin@@YA?AUtagResult@@EGHPAVCWnd@@@Z=__E__0__ @1

?MIO_Close@@YAXH@Z=__E__1__ @2

?MIO_Close@@YAXXZ=__E__2__ @3

?MIO_Cmd@@YA?AUtagResult@@EGGGGGG@Z=__E__3__ @4

?MIO_DisableLaser@@YA?AUtagResult@@E@Z=__E__4__ @5

Dumpbin /exports of the proxy.dll we can see the alias to the proxy function.

1    0 00011050 ?MIO_AxisGoOrigin = @ILT+75(___E__0__)

2    1 0001104B ?MIO_Close = @ILT+70(___E__1__)

3    2 00011046 ?MIO_Close = @ILT+65(___E__2__)

4    3 00011073 ?MIO_Cmd = @ILT+110(___E__3__)

5    4 00011055 ?MIO_DisableLaser = @ILT+80(___E__4__)

6    5 0001106E ?MIO_ENABLEZ = @ILT+105(___E__5__)

7    6 00011069 ?MIO_EarseEpprom = @ILT+100(___E__6__)

The typo in the API Earse makes me think of the Fast Show’s ERAS videos.

If we replace the LMCMIO.dll with our proxy, what happens? I’m building it on 2019 but compiling with 2013 toolset.

Renaming the original LMCMIO.dll  to LMCMIOold.dll and copying in the proxy dll to the same folder as EzCad2 and trying it, nothing happens. The app just exits with no warnings, but why?  Well it’s going to be an issue with the DLL name issue mentioned earlier, when windows tries to load functions from our proxy DLL it’ll fail, because the names are wrong, the oridinals are right but its rare anyone loads by index and not name, especially C++, how do we see this?

Windows has plenty of tools for this. We need the windows debugging tools, windbg x84 and gflags.exe. Gflags is going to allow us to turn on the internal DLL load tracing, so that we can see whats happening behind the scenes without any extra work.

Switch on SLS(show loader snaps) mode for EzCad2 and the DLL just so we can watch it later. Don’t forget to turn it off after we’re done as it generates a lot of data and will slow things down.

From the command line

gflags.exe -i EzCad2.exe +sls

gflags.exe –i LMC1.dll +sls

gflags.exe –i LMCMIO.dll +sls

C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\gflags.exe" -i EzCad2.exe +sls

Current Registry Settings for EzCad2.exe executable are: 00000002

    sls – Show Loader Snaps

DLLs also cascade load, that is the app loads a DLL, and that DLL loads another DLL. So I added the snap loader tracer to the LMC1.dll as well. WinDepends is a good place to start poking at DLL load order, as well windbg.

Next load windbgx86 and open the executable, EzCad2.exe.

There are a different modes for DLL loading sometimes its handled by windows on program start, sometimes its explicit code, or delay loading. In this case its being handled by windows so all we have to do is load it into the debugger and we’ll see the issue straight away without actually running EzCad2

Working backwards we can see LMCMIO failed to load

08bc:2130 @ 1440796701 – LdrpHandleOneOldFormatImportDescriptor – ERROR: Snapping the imports from DLL "C:\ezcad\newer\Lmc1.dll" to DLL "C:\ezcad\newer\LMCMIO.dll" failed with status 0xc0000139

08bc:2130 @ 1440796701 – LdrpLoadImportModule – RETURN: Status: 0xc0000139

08bc:2130 @ 1440796701 – LdrpHandleOneOldFormatImportDescriptor – ERROR: Loading "????" from the import table of DLL "C:\ezcad\newer\EzCad2.exe" failed with status 0xc0000139

08bc:2130 @ 1440796701 – LdrpInitializeProcess – ERROR: Walking the import tables of the executable and its static imports failed with status 0xc0000139

08bc:2130 @ 1440796701 – _LdrpInitialize – ERROR: Process initialization failed with status 0xc0000139

08bc:2130 @ 1440796701 – LdrpInitializationFailure – ERROR: Process initialization failed with status 0xc0000139

eax=00000000 ebx=7734206c ecx=0018f928 edx=0018f929 esi=c0000139 edi=7efdd000

eip=7725fcc2 esp=0018fcb0 ebp=0018fd00 iopl=0         nv up ei pl zr na pe nc

cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246

ntdll!NtTerminateProcess+0x12:

7725fcc2 83c404          add     esp,4

Going back in the windbg logs

08bc:2130 @ 1440796701 – LdrpSnapThunk – WARNING: Hint index 0x23 for procedure "?MIO_OpenNewDev@@YAHXZ" in DLL "LMCMIO.dll" is invalid

08bc:2130 @ 1440796701 – LdrpSnapThunk – ERROR: Procedure "?MIO_OpenNewDev@@YAHXZ" could not be located in DLL "LMCMIO.dll"

(8bc.2130): Unknown exception – code c0000139 (first chance)

08bc:2130 @ 1440796701 – LdrpGenericExceptionFilter – ERROR: Function LdrpSnapIAT raised exception 0xc0000139

So it tried to load ?MIO_OpenNewDev@@YAHXZ from LMCMIO.dll which is the proxy DLL (Which this is an expected failure)

Going back to the .DEF file lets look at the function

?MIO_OpenNewDev@@YAHXZ=__E__35__ @36

Thats what it ought to look like, the ordinal is correct but its been terminated at the @@ , lets check whats in the actual DLL

dumpbin /exports LMCMIO.dll

       36   23 00011177 ?MIO_OpenNewDev = @ILT+370(___E__35__)

And there it is, the proxy alias is there , which the = @ILT part. So even though the .DEF is correct, the linker has terminated at the @ because thats the token to specify the ordinal (an ordinal is a numerical index for a function, versus a name)

So now we have to go back and fix this issue. I’ve already modified my dll proxy tool to correct the names in the C++ code (basically replacing the ?/@ with _ ) but the .DEF file still generates the C++ mangled name. I have to next modify the app to also spit out the new names in the .DEF then post process the DLL to put the original C++ mangled names back.

taking a break for dinner….

Post Processing the proxy DLL

After some dinner we restart.

So the plan is to change the proxy dll maker tool to output the sanatised names to the C++ code and the .DEF file, so instead of @ and ? it will substitute them for Q and A then after the DLL is compiled a, make a second tool that parses the original DLL exports and then sanatises them in the same way , read the proxy dll and search and replace the sanatised version with the original…

After modding the dll proxy tool, and compiling it , next is throwing together a quick SAR tool. ugh the double space wordpress html nonsense… i’ll clean it up on a edit pass.

// DLLSar.cpp : This file contains the ‘main’ function. Program execution begins and ends there#include <windows.h>

#include <iostream>

#include <string>

#include <fstream>

#include <streambuf>

#include <algorithm>

#include <vector>

std::vector<std::string> defList;

std::vector<std::string> replaceList;

// list of previously used names this run

std::vector<std::string> previouslyOnSAR;

// list of strings to backlist ( no replacement )

std::vector<std::string> blackListSAR;


bool SAR(const std::string& searchString, const std::string& replaceString, std::string& replaceBuffer)

{

    bool found = false;

    size_t pos = 0;

    if (searchString.length() != replaceString.length()) {

        std::cout << "bad replace" << std::endl;

        exit(-4);

        return false;

    }


    // reset

    pos = replaceBuffer.find(searchString);

    while (pos != std::string::npos)

    {

        // replace bufer.

         std::cout << "search = " << searchString << " replace " << replaceString << std::endl;

        replaceBuffer.replace(pos, searchString.size(), replaceString);

        found = true;

        std::cout << "-";

        // find next

        pos = replaceBuffer.find(searchString, pos + replaceString.size());


    }

    return found;

}

bool exportProcAddresses(void* hModule)

{

#if defined( _WIN32 )  
    unsigned char* lpBase = reinterpret_cast<unsigned char*>(hModule);

    IMAGE_DOS_HEADER* idhDosHeader = reinterpret_cast<IMAGE_DOS_HEADER*>(lpBase);

    if (idhDosHeader->e_magic == 0x5A4D)

    {

#if defined( _M_IX86 ) 
        IMAGE_NT_HEADERS32* inhNtHeader = reinterpret_cast<IMAGE_NT_HEADERS32*>(lpBase + idhDosHeader->e_lfanew);

#elif defined( _M_AMD64 ) 
        IMAGE_NT_HEADERS64* inhNtHeader = reinterpret_cast<IMAGE_NT_HEADERS64*>(lpBase + idhDosHeader->e_lfanew);

#endif 
         if (inhNtHeader->Signature == 0x4550)

        {

             IMAGE_EXPORT_DIRECTORY* iedExportDirectory = reinterpret_cast<IMAGE_EXPORT_DIRECTORY*>(lpBase + inhNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

             std::cout << "Procesing DLL" << std::endl;

            for (register unsigned int uiIter = 0; uiIter < iedExportDirectory->NumberOfNames; ++uiIter)

            {

                char* szNames = reinterpret_cast<char*>(lpBase + reinterpret_cast<unsigned long*>(lpBase + iedExportDirectory->AddressOfNames)[uiIter]);

                //printf("%s\n", szNames);

                if (std::find(blackListSAR.begin(), blackListSAR.end(), szNames) != blackListSAR.end()) {

                    std::cout << "matched " << szNames << " skipped " << std::endl;

                    continue;

                }

                defList.push_back(szNames);

            }

            return true;

        }

    }

#endif 
    std::cerr << "DLL processing failed" << std::endl;

     return false;

}


bool parseDLL(const char* dllName)

{

    std::cout << "Parsing " << dllName << std::endl;

    HMODULE lib = LoadLibraryExA(dllName, NULL, DONT_RESOLVE_DLL_REFERENCES);

    if (!lib) {

        std::cerr << "load lib failed" << std::endl;

        return false;

    }

    // empty the list

    defList.clear();

    return exportProcAddresses(lib);

}


std::string sanatise(std::string& name)

{

    std::string output = name;

    for (uint16_t i = 0; i < name.length(); i++) {

        if (name.at(i) == ‘?’) {

            output.at(i) = ‘Q’;

        }

        if (name.at(i) == ‘@’) {

            output.at(i) = ‘A’;

        }

    }

    return output;

}


int main(int argc, char* argv[])

{

    std::string proxyBuffer;

    if (argc < 4) {

        exit(-1);

    }

    // build list of exports from original DLL

    if (parseDLL(argv[1]) == false) {

        exit(-2);

    }


    // proxied DLL

    try {

        std::ifstream inputFile(argv[2], std::ios::in | std::ios::binary | std::ios::ate);

        std::vector<char> dataVector(inputFile.tellg());

        inputFile.seekg(0, std::ios::beg);

        inputFile.read(dataVector.data(), dataVector.size());

        proxyBuffer.assign(dataVector.begin(), dataVector.end());

    }

    catch (std::ofstream::failure & readErr)

    {

        std::cerr << "\n\nFail occured when reading from file " << argv[2] << " "

            << readErr.what()

             << std::endl;

        return -3;

    }

   
    uint16_t index = 0;

    // build list of replacements.

    for (auto originalName : defList) {

        std::cout << originalName << "\n" << sanatise(originalName) << "\n";

        replaceList.push_back( sanatise(originalName));

    }

    index = 0;

    // replace them

    for (auto originalName : defList) {

        if (SAR( replaceList.at(index), originalName, proxyBuffer)) {

            std::cout << "Y";

        }

        else {

            std::cout << "n";

        }

        index++;

    }

    std::cout << "\n" << "Writing output file " << argv[3] << std::endl;

    {

        try {

            std::ofstream outputFile(argv[3], std::ios::out | std::ios::binary);

            outputFile.write(proxyBuffer.data(), proxyBuffer.length());

        }

        catch (std::ofstream::failure & writeErr) {

            std::cerr << "\nFail occured when writing to the output file " << argv[3] << " "

                 << writeErr.what()

                << std::endl;

             return -2;

        }

    }

}

After making the changes to the DLL proxy tool, the exports of the DLL now look like this, ( the ?s become Q and the @ becomes an A)

          1    0 00011050 QMIO_AxisGoOriginAAYAQAUtagResultAAEGHPAVCWndAAAZ = @ILT+75(___E__0__)

Running the SAR code using the original dll and proxied dlls as input, and an output name.

Looking at the output file with dumpbin /exports we see its changed as expected



1    0 00001B60
?MIO_AxisGoOrigin@@YA?AUtagResult@@EGHPAVCWnd@@@Z

It correctly demangles as well, so lets see if it loads.

Testing in WinDBG X86

Lets try windbgx86 again. making sure to add +sls to the proxy dll with gflags, since it is the actual file that stores those changes and we’ve liekly overwritten it.

ModLoad: 0fe80000 0fea6000   C:\ezcad\newer\LMCMIO.dll

5754:2ecc @ 1448637171 – LdrpMapViewOfSection – RETURN: Status: 0x00000000

5754:2ecc @ 1448637171 – LdrpFindOrMapDll – RETURN: Status: 0x00000000

5754:2ecc @ 1448637171 – LdrpHandleOneOldFormatImportDescriptor – INFO: DLL "C:\ezcad\newer\LMCMIO.dll" imports "KERNEL32.dll"

5754:2ecc @ 1448637171 – LdrpLoadImportModule – ENTER: DLL name: KERNEL32.dll DLL path: C:\ezcad\newer;;C:\Windows\system32;C:\Windows\system;C:\Windows;.;C:\Program Files (x86)\Windows Kits\10\Debuggers\x86;C:\msys64\usr\bin;C:\msys64;C:\msys64\usr\bin;C:\Python36\Scripts\;C:\Python36\;C:\Python37;C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.1\bin;C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.1\libnvvp;C:\Program Files\Git LFS;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\

Great, no errors on Loading so lets try running EzCad2. 

Now we see the debugger spit out the following

int __cdecl MIO_Open(int)

Which is what was expected, it is at least now loading  our trampoline function which is just a simple debug string output for now.

// int __cdecl MIO_Open(int)

// ?MIO_Open@@YAHH@Z

extern "C" __declspec(naked) void __E__34__()

{

    __asm pushad

    __asm pushfd

    OutputDebugStringA("int __cdecl MIO_Open(int)\n");

    __asm popfd

    __asm popad

    __asm

    {

        jmp procs[E_QMIO_OPENAAYAHHAZ*4];

    }

}

OK, so now we are inside our function, the proxy dll is working so far but I initially left in some debug code for testing proxy DLLs that made it keep looping the function. After rebuilding the proxy DLL I ran EzCad2 again in WinDbgx86 and :-

image

All working and the original DLL is also being called, we can see the debug trace calls to the functions being shown in the debug viewer, SLS is still on so it is much noiser output than normal. 

We can disable SLS for now. Just run gflags with –sls on the exe, and two dlls (Technically I haven’t tried it against the laser module, but trampoline/proxies generally either just work or they crash it is very rarely inbetween)

(3fe8.53a0): Break instruction exception – code 80000003 (first chance)

eax=00000000 ebx=00000000 ecx=94b00000 edx=0008e3c8 esi=fffffffe edi=00000000

eip=772e10a6 esp=0018fb08 ebp=0018fb34 iopl=0         nv up ei pl zr na pe nc

cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246

ntdll!LdrpDoDebuggerBreak+0x2c:

772e10a6 cc              int     3

0:000> g

ModLoad: 75ec0000 75f20000   C:\Windows\SysWOW64\IMM32.DLL

ModLoad: 76d90000 76e5e000   C:\Windows\syswow64\MSCTF.dll

ModLoad: 00c00000 00c38000   C:\Windows\SysWOW64\odbcint.dll

DllMain called

DllMain DLL_PROCESS_ATTACH

ModLoad: 00c50000 00c5c000   C:\ezcad\newer\LMCMIOOLD.DLL

ModLoad: 70700000 70780000   C:\Windows\SysWOW64\uxtheme.dll

ModLoad: 75fc0000 75fef000   C:\Windows\syswow64\WINTRUST.dll

ModLoad: 75510000 75632000   C:\Windows\syswow64\CRYPT32.dll

ModLoad: 75750000 7575c000   C:\Windows\syswow64\MSASN1.dll

ModLoad: 6f1a0000 6f1a9000   C:\Windows\SysWOW64\hid.dll

int __cdecl MIO_Open(int)

ModLoad: 03080000 031df000   C:\Windows\SysWOW64\ole32.dll

ModLoad: 026f0000 02703000   C:\ezcad\newer\PLUG\AngleRotate.plg

ModLoad: 02730000 02743000   C:\ezcad\newer\PLUG\AngleRotate2.plg

ModLoad: 025a0000 025aa000   C:\ezcad\newer\PLUG\ChangeText.plg

ModLoad: 02750000 02775000   C:\ezcad\newer\PLUG\GlobeMark.plg

ModLoad: 02710000 02720000   C:\ezcad\newer\PLUG\IPGSet.plg

ModLoad: 02780000 02797000   C:\ezcad\newer\PLUG\jczfont.plg

ModLoad: 027a0000 027b2000   C:\ezcad\newer\PLUG\MultiFileMark.plg

ModLoad: 027c0000 027dd000   C:\ezcad\newer\PLUG\MultiPartMark.plg

ModLoad: 02af0000 02b25000   C:\ezcad\newer\PLUG\PowerRuler.plg

ModLoad: 03080000 030a7000   C:\ezcad\newer\PLUG\ReadFace3d.plg

ModLoad: 6b6e0000 6b7a8000   C:\Windows\SysWOW64\OPENGL32.dll

ModLoad: 6d260000 6d282000   C:\Windows\SysWOW64\GLU32.dll

ModLoad: 030b0000 030d3000   C:\ezcad\newer\PLUG\RingMark.plg

ModLoad: 02b40000 02b5b000   C:\ezcad\newer\PLUG\RingTextMark.plg

ModLoad: 030e0000 03102000   C:\ezcad\newer\PLUG\RotaryMark.plg

ModLoad: 02c50000 02c6a000   C:\ezcad\newer\PLUG\RotateText.plg

ModLoad: 03110000 03135000   C:\ezcad\newer\PLUG\Splitmark2.plg

ModLoad: 03140000 03152000   C:\ezcad\newer\PLUG\SuperProject.plg

ModLoad: 70b60000 70cfe000   C:\Windows\WinSxS\x86_microsoft.windows.common-controls_6595b64144ccf1df_6.0.7601.24483_none_2b200f664577e14b\comctl32.DLL

ModLoad: 745b0000 745be000   C:\Windows\SysWOW64\DEVRTL.dll

int __cdecl MIO_Open(int)

struct tagResult __cdecl MIO_GetLmcInfo(unsigned short * const)

struct _GUID __cdecl MIO_GetClassGUID(void)

void * __cdecl MIO_GetDevHandle(int)

void * __cdecl MIO_GetDevHandle(int)

void * __cdecl MIO_GetDevHandle(int)

void * __cdecl MIO_GetDevHandle(int)

void * __cdecl MIO_GetDevHandle(int)

void * __cdecl MIO_GetDevHandle(int)

void * __cdecl MIO_GetDevHandle(int)

void * __cdecl MIO_GetDevHandle(int)

DllMain called

DllMain DLL_PROCESS_DETACH

eax=00000000 ebx=00000000 ecx=00000002 edx=00000000 esi=77342100 edi=773420c0

eip=7725fcc2 esp=0018fe70 ebp=0018fe8c iopl=0         nv up ei pl zr na pe nc

cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246

ntdll!NtTerminateProcess+0x12:

7725fcc2 83c404          add     esp,4

Much less noiser output in the debugger .It also looks like the PLG plugins are actually DLLs since they’re being ModLoaded

We can see now that it is using MIO_Open, MIO_GetLmcInfo, MIO_GetClassGUID, MIO_GetDevHandle

Now I can actually start intercepting the calls and look at the data going by.

Since I was writing the code and the write up at the same time, it is time for another break for the evening and back for the rest of the intercept tomorrow!

So far changes to the proxy making application that can handle C++ name mangling with the post process SAR Tool and a basic Proxy DLL for LMCMIO, as well as figuring out the keyboard F1 and F2 code.

Onto stage 3!

Work in progress

Making a more useful trampoline is pretty straightforward

Add a couple of helper functi0ns that print out parameters, add some global storage (which in a DLL can be a fussy affair but lets start with simple)


static  char temp_buffer[1024];


void dump_ret_param(unsigned int paramindex, unsigned int count, unsigned int offset)

{

    static  char temp_buffer[1024];

    while (count–) {

        sprintf_s(temp_buffer, sizeof(temp_buffer), "param%d=0x%x, ", paramindex, param[paramindex][offset]);

        OutputDebugStringA(temp_buffer);

        paramindex++;

    }

    OutputDebugStringA("\n");

}

void dump_return(unsigned int offset)

{

    static  char temp_buffer[1024];

    sprintf_s(temp_buffer, sizeof(temp_buffer), "returns=0x%x\n", param[0][offset]);

    OutputDebugStringA(temp_buffer);

}

then we can modify the call like this, we replace the return address back to the caller with our own, so the program flow now goes :-

caller –> trampoline –> original function –> back to end of trampoline –> back to caller

we do this so we can know not only the parameters passed in, but any data returned, in this case EAX, since this is autogenerated code it is somewhat terse

there is an array of arrays for the parameters , upto 20, and an array of return addresses since functions may be called in parallel or from other areas of the dll etc. recursive calls will need something more sophisticated.

extern "C" __declspec(naked) void __E__34__()

{               
     __asm pushad

    __asm pushfd


    // fetch out the return address

    __asm mov eax, [esp + 0x24]

    __asm mov returnAddress[E_QMIO_OPENAAYAHHAZ * 4], eax

    // fetch out the passed in parameter and store it in the param array

    __asm {    
        mov         ecx,2                                //param index

         imul        eax,ecx, (E_NUM_PROCS * 4)

        mov         edx, E_QMIO_OPENAAYAHHAZ

        mov            ebx, [esp + 0x28]

        mov         dword ptr param[eax + (edx * 4)],ebx

    }

    // overwrite the stack ptrs return address, with our return address becase we want to know what the return value is

    __asm mov eax, jump_E_QMIO_OPENAAYAHHAZ

    __asm mov [esp + 0x24],eax

    OutputDebugStringA("int __cdecl MIO_Open(int) ");

    dump_ret_param(0,4,E_QMIO_OPENAAYAHHAZ);

    __asm popfd

    __asm popad

    __asm

    {

        jmp procs[E_QMIO_OPENAAYAHHAZ*4];

    }

jump_E_QMIO_OPENAAYAHHAZ:;

    __asm pushad

    __asm pushfd

    // show the return code from eax

    __asm mov param[E_QMIO_OPENAAYAHHAZ * 4], eax

    dump_return(E_QMIO_OPENAAYAHHAZ);

    __asm popfd

    __asm popad

    /// back to original caller code

    __asm jmp returnAddress[E_QMIO_OPENAAYAHHAZ * 4]

}

Eakins Camera hackery pokery and the legend of MeasureTwice

After picking up an auto focus Eakins camera for PCB inspection and so on. I adapted my test app MeasureTwice which came from a history of wanting to measure where two holes were for a CNC operation the app grew as they do into my catchall app for inspection work.

I’ve wanted to add an XY table to it so i can capture a PCB or item larger than the view and within the workable ROI of the lense and be sharp, I finally got around to adding the X stage with a stepper on a screw and  track then added a USB control for it, ported in the control code to MT so that it can move the track back and forth.

image

Since the camera has autofocus , auto exposure etc. it’s desirable to control them from my MT app so i can fix them before starting a scan.

First things first is to pop open the camera and take a look, there’s a 3 pin connector internally that is a 3.3V Serial port called J50

image

popped out the board, 8 screws, slide out the assembly disconnect the fpc/ffc’s, Soldered in 3 wires (i cut up an old Samsung usb cable since they’re nicely made) added a zip tie as a strain relief

image

image

Serial port J50 looks like this square pad, round, round

[]- RX

O –TX

O – GND

The square pad I’d usually expect to see as ground, but there we go.

image

image

We used the power LED hole to pass the cable back through

image

and remounted the camera

image

Next step is to connect it to a USB 3.3V serial adapter

popping that open at 115Kbaud gives us a u-boot and log of the Linux boot.

U-Boot 2010.06 (Nov 22 2016 – 16:36:06)

NAND:  Check nand flash controller v610. found
Special NAND id table Version 1.36
Nand ID: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
No NAND device found!!!
0 MiB
Check spi flash controller v350… Found
Spi(cs1) ID: 0xC2 0x20 0x19 0xC2 0x20 0x19
Spi(cs1): Block:64KB Chip:32MB Name:"MX25L 256/257 35 E/F"
*** Warning – bad CRC, using default environment

In:    serial
Out:   serial
Err:   serial
USB:   scanning bus for devices… 2 USB Device(s) found
0 Storage Device(s) found
32768 KiB hi_sfc at 0:0 is now current device

## Booting kernel from Legacy Image at 82000000 …
   Image Name:   Linux-3.4.35
   Image Type:   ARM Linux Kernel Image (uncompressed)
   Data Size:    2986672 Bytes = 2.8 MiB
   Load Address: 80008000
   Entry Point:  80008000
   Loading Kernel Image … OK
OK

Starting kernel …

Uncompressing Linux… done, booting the kernel.
Booting Linux on physical CPU 0
Linux version 3.4.35 (root@linux-5w9i) (gcc version 4.8.3 20131202 (prerelease) (Hisilicon_v300) ) #2 Fri Jan 13 17:00:54 CST 2017
CPU: ARMv7 Processor [410fc075] revision 5 (ARMv7), cr=10c53c7d
CPU: PIPT / VIPT nonaliasing data cache, VIPT aliasing instruction cache
Machine: hi3516a
Memory policy: ECC disabled, Data cache writeback
Built 1 zonelists in Zone order, mobility grouping on.  Total pages: 32512
Kernel command line: mem=128M console=ttyAMA0,115200 root=/dev/mtdblock2 rootfstype=jffs2 mtdparts=hi_sfc:1M(boot),3M(kernel),26M(rootfs)
PID hash table entries: 512 (order: -1, 2048 bytes)
Dentry cache hash table entries: 16384 (order: 4, 65536 bytes)
Inode-cache hash table entries: 8192 (order: 3, 32768 bytes)
Memory: 128MB = 128MB total
Memory: 124028k/124028k available, 7044k reserved, 0K highmem
Virtual kernel memory layout:
    vector  : 0xffff0000 – 0xffff1000   (   4 kB)
    fixmap  : 0xfff00000 – 0xfffe0000   ( 896 kB)
    vmalloc : 0xc8800000 – 0xff000000   ( 872 MB)
    lowmem  : 0xc0000000 – 0xc8000000   ( 128 MB)
    modules : 0xbf000000 – 0xc0000000   (  16 MB)
      .text : 0xc0008000 – 0xc054a000   (5384 kB)
      .init : 0xc054a000 – 0xc056c434   ( 138 kB)
      .data : 0xc056e000 – 0xc059d800   ( 190 kB)
       .bss : 0xc059d824 – 0xc05bc9f8   ( 125 kB)
SLUB: Genslabs=11, HWalign=64, Order=0-3, MinObjects=0, CPUs=1, Nodes=1
NR_IRQS:128
sched_clock: 32 bits at 49MHz, resolution 20ns, wraps every 86767ms
Console: colour dummy device 80×30
Calibrating delay loop… 1196.85 BogoMIPS (lpj=5984256)
pid_max: default: 32768 minimum: 301
Mount-cache hash table entries: 512
Initializing cgroup subsys freezer
CPU: Testing write buffer coherency: ok
Setting up static identity map for 0x8041e4a8 – 0x8041e500
dummy:
NET: Registered protocol family 16
Serial: AMBA PL011 UART driver
uart:0: ttyAMA0 at MMIO 0x20080000 (irq = 40) is a PL011 rev2
console [ttyAMA0] enabled
uart:1: ttyAMA1 at MMIO 0x20090000 (irq = 41) is a PL011 rev2
bio: create slab <bio-0> at 0
SCSI subsystem initialized
hi-spi-master hi-spi-master.0: with 1 chip select slaves attached
hi-spi-master hi-spi-master.1: with 3 chip select slaves attached
usbcore: registered new interface driver usbfs
usbcore: registered new interface driver hub
usbcore: registered new device driver usb
Switching to clocksource timer0
NET: Registered protocol family 2
IP route cache hash table entries: 1024 (order: 0, 4096 bytes)
TCP established hash table entries: 4096 (order: 3, 32768 bytes)
TCP bind hash table entries: 4096 (order: 2, 16384 bytes)
TCP: Hash tables configured (established 4096 bind 4096)
TCP: reno registered
UDP hash table entries: 256 (order: 0, 4096 bytes)
UDP-Lite hash table entries: 256 (order: 0, 4096 bytes)
NET: Registered protocol family 1
RPC: Registered named UNIX socket transport module.
RPC: Registered udp transport module.
RPC: Registered tcp transport module.
RPC: Registered tcp NFSv4.1 backchannel transport module.
VFS: Disk quotas dquot_6.5.2
Dquot-cache hash table entries: 1024 (order 0, 4096 bytes)
squashfs: version 4.0 (2009/01/31) Phillip Lougher
NFS: Registering the id_resolver key type
jffs2: version 2.2. (NAND) © 2001-2006 Red Hat, Inc.
fuse init (API version 7.18)
SGI XFS with security attributes, large block/inode numbers, no debug enabled
msgmni has been set to 242
Block layer SCSI generic (bsg) driver version 0.4 loaded (major 254)
io scheduler noop registered
io scheduler deadline registered (default)
io scheduler cfq registered
brd: module loaded
Spi id table Version 1.22
Spi(cs1) ID: 0xC2 0x20 0x19 0xC2 0x20 0x19
SPI nor flash boot mode is 3 Bytes
Spi(cs1):
Block:64KB
Chip:32MB
Name:"MX25L(256/257)35(E/F)"
spi size: 32MB
chip num: 1
3 cmdlinepart partitions found on MTD device hi_sfc
3 cmdlinepart partitions found on MTD device hi_sfc
Creating 3 MTD partitions on "hi_sfc":
0x000000000000-0x000000100000 : "boot"
0x000000100000-0x000000400000 : "kernel"
0x000000400000-0x000001e00000 : "rootfs"
Found Nand Flash Controller V610.
Nand ID: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
No NAND device found
Higmac dma_sg_phy: 0x87a00000
higmac_mdio_bus: probed
PHY mdio0:01 not found
ETH0: rgmii, phy_addr=1, mii_name=mdio0
ehci_hcd: USB 2.0 ‘Enhanced’ Host Controller (EHCI) Driver
hiusb-ehci hiusb-ehci.0: HIUSB EHCI
hiusb-ehci hiusb-ehci.0: new USB bus registered, assigned bus number 1
hiusb-ehci hiusb-ehci.0: irq 53, io mem 0x100b0000
hiusb-ehci hiusb-ehci.0: USB 0.0 started, EHCI 1.00
hub 1-0:1.0: USB hub found
hub 1-0:1.0: 1 port detected
ohci_hcd: USB 1.1 ‘Open’ Host Controller (OHCI) Driver
hiusb-ohci hiusb-ohci.0: HIUSB OHCI
hiusb-ohci hiusb-ohci.0: new USB bus registered, assigned bus number 2
hiusb-ohci hiusb-ohci.0: irq 54, io mem 0x100a0000
hub 2-0:1.0: USB hub found
hub 2-0:1.0: 1 port detected
Initializing USB Mass Storage driver…
usbcore: registered new interface driver usb-storage
USB Mass Storage support registered.
mousedev: PS/2 mouse device common for all mice
i2c /dev entries driver
hisi_i2c hisi_i2c.0: Hisilicon [i2c-0] probed!
hisi_i2c hisi_i2c.1: Hisilicon [i2c-1] probed!
hisi_i2c hisi_i2c.2: Hisilicon [i2c-2] probed!
usbcore: registered new interface driver usbhid
usbhid: USB HID core driver
TCP: cubic registered
Initializing XFRM netlink socket
NET: Registered protocol family 17
NET: Registered protocol family 15
lib80211: common routines for IEEE802.11 drivers
Registering the dns_resolver key type
VFP support v0.3: implementor 41 architecture 2 part 30 variant 7 rev 5
mmc0: new high speed SDXC card at address aaaa
mmcblk0: mmc0:aaaa SE64G 59.4 GiB
mmcblk0: p1
VFS: Mounted root (jffs2 filesystem) on device 31:2.
Freeing init memory: 136K
usb 2-1: new low-speed USB device number 2 using hiusb-ohci
input: Dell Dell USB Optical Mouse as /devices/platform/hiusb-ohci.0/usb2/2-1/2-1:1.0/input/input0
generic-usb 0003:413C:3012.0001: input: USB HID v1.11 Mouse [Dell Dell USB Optical Mouse] on usb-hiusb-ohci-1/input0
[RCS]: /etc/init.d/S00devs
[RCS]: /etc/init.d/S01udev
Not recognise ACTION:change
Not recognise ACTION:change
Not recognise ACTION:change
[RCS]: /etc/init.d/S80network
[RCS]: /etc/init.d/S90hibernate
Password for ‘root’ changed
Auto login as root …
Jan  1 00:00:02 login[905]: root login on ‘ttyS000’

Welcome to HiLinux.
None of nfsroot found in cmdline.
His3516a_LoadDrivers Start…..!
~ # Hisilicon Media Memory Zone Manager
Module himedia: init ok
hi3516a_base: module license ‘Proprietary’ taints kernel.
Disabling lock debugging due to kernel taint
load sys.ko for Hi3516A…OK!
load tde.ko …OK!
load region.ko ….OK!
load vgs.ko for Hi3516A…OK!
ISP Mod init!
load viu.ko for Hi3516A…OK!
load vpss.ko ….OK!
load vou.ko ….OK!
load hifb.ko OK!
load rc.ko for Hi3516A…OK!
load venc.ko for Hi3516A…OK!
load chnl.ko for Hi3516A…OK!
load h264e.ko for Hi3516A…OK!
load h265e.ko for Hi3516A…OK!
load jpege.ko for Hi3516A…OK!
load vda.ko ….OK!
load ive.ko for Hi3516A…OK!
hi3516a_io driver init start
hi3516a_io driver init successful!
af pi level:0
af move steps:330
af move steps:320 10
insmod: can’t insert ‘/komod/extdrv/wdt.ko’: No such file or directory
*** Board tools : ver0.0.1_20121120 ***
[debug]: {source/utils/cmdshell.c:166}cmdstr:himm
0x200f0050: 0x00000000 –> 0x00000001
[END]
*** Board tools : ver0.0.1_20121120 ***
[debug]: {source/utils/cmdshell.c:166}cmdstr:himm
0x200f0054: 0x00000000 –> 0x00000001
[END]
*** Board tools : ver0.0.1_20121120 ***
[debug]: {source/utils/cmdshell.c:166}cmdstr:himm
0x200f0058: 0x00000000 –> 0x00000001
[END]
*** Board tools : ver0.0.1_20121120 ***
[debug]: {source/utils/cmdshell.c:166}cmdstr:himm
0x200f005c: 0x00000000 –> 0x00000001
[END]
*** Board tools : ver0.0.1_20121120 ***
[debug]: {source/utils/cmdshell.c:166}cmdstr:himm
0x2003002c: 0x00090007 –> 0x00090007
[END]
mipi_init
init phy power successful!
load hi_mipi driver successful!
hi3516a_io driver init start
His3516a_LoadDrivers Finish…..!
***COPYRIGHT 2016 tagye technology****
software:v2.3.1
DATE:Nov  1 2018,TIME:16:36:05
************************************
linear mode
–IMX290 1080P 60fps LINE Init OK!—-
Entering the cmos_fps_set!
vout start finish
Entering the cmos_fps_set!
open success:fd0 ===== 3
FBIOPUT_VSCREENINFO over !!!
hi_i2c_wait_txfifo_notfull->260:
transmit error, int_raw_status: 0x750!

hi_i2c_wait_txfifo_notfull->262:
tx_abrt_cause is 1.

we can see the camera sensor model, iMX290 and the hi3516a processor, the tagye tech guys seem to have a patent on microscope digital zooming so they may supply the software.

no root password needed, though in the /mm.sh script it changes the password for root at every boot

#!/bin/sh

#ifconfig eth0 192.168.1.247
#mount -t nfs -o nolock -o tcp 192.168.1.100:/nfs  /mnt

echo "root:tagye1207" | chpasswd

ulimit -c 9999
ulimit -c 9999

mount -t vfat /dev/mmcblk0p1  /mnt/sdcard

export HOME=’/root’
export LD_LIBRARY_PATH=’/usr/local/lib:/usr/lib:/qt_lib’
export LOGNAME=’root’
export OLDPWD=’/qt_lib’
export PATH=’/usr/bin:/usr/sbin:/bin:/sbin’
export PWD=’/opt’
export QT_PLUGIN_PATH=’/qt_lib/plugins’
export QT_QWS_FONTDIR=’/qt_lib/fonts’
export QWS_DISPLAY=’LinuxFb:/dev/fb0′
export TERM=’vt100′
export USER=’root’

/opt/3516a_proc &
/opt/myTest_8.17 -qws -fn DejaVuSans.ttf &

so now there is a shell, there isn’t a lot going on there are two apps running one is handling the im290 setup and auto focus, the other handles the gui for the mouse. unfortunately there are no network usb driver .ko’s so sticking with serial at the moment.

the dev board for the 3516 has a gmac on it, so that is likely what the dev is using with the commented out ifconfig


/opt/3516a_proc &
/opt/myTest_8.17 -qws -fn DejaVuSans.ttf &

i pop on a arm7l version of  strace and  can now see the myTest app talking to a local socket at /tmp/UNIX.domain and /tmp/UNIX.domain1

connect(15, {sa_family=AF_LOCAL, sun_path="/tmp/UNIX.domain1"}, 110) = 0
connect(16, {sa_family=AF_LOCAL, sun_path="/tmp/UNIX.domain"}, 110) = 0

clicking buttons shows a write to socket 16, /tmp/UNIX.domain

write(16, "\7\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"…, 100) = 100
write(16, "\7\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"…, 100) = 100

write(16, "\5\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"…, 100) = 100
write(16, "\5\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"…, 100) = 100

3516a_proc is the one that controls the camera which looks like via i2c , the other mrTest is a QT app that does the overlay and then communicates with the 3516 app  with an AF_LOCAL IPC socket.

so instead of going back and forth with a fiddly sdcard, i grabbed lrsrz source code setup an ARM7L cross compiler on a lightsail instance and built it, transferred it to the sdcard and booted up the camera, now i can just use TeraTerm to ZMODEM upload over the serial console.

/mnt/sdcard # lrz -b -Z –c -y
lrz waiting to receive.**B0100000023be50

binary, zmodem with checksum and overwrite(clobber)

it is now slightly less of a hassle to transfer files to and from the camera, building the .ko for a usb ethernet adapter I’ll tackle later

pop up the camera UI set AF mode

image

from the strace before i believe \7 is the MF command

next is a very quick test app

#include <stdio.h>
#include <stddef.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>

#define SOCK_CLOEXEC 02000000UL

int main()
{
        struct sockaddr_un tolog;
        int sock = socket(AF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC, 0);
        tolog.sun_family = AF_UNIX;
        strcpy(tolog.sun_path, "/tmp/UNIX.domain");
        connect(sock, (struct sockaddr*)&tolog, sizeof(struct sockaddr_un));
        write(sock,"\7\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",100);
        close(sock);
        return 0;
}

compile it, pass it over to the serial app vi lxz and ,kill the mYtest app, then run our test app

socket(PF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC, 0) = 3
connect(3, {sa_family=AF_LOCAL, sun_path="/tmp/UNIX.domain"}, 110) = 0
write(3, "\7\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"…, 100) = 100
close(3)                                = 0
exit_group(-1093076012)                 = ?

re-run the myTest app

/opt/myTest_8.17 -qws -fn DejaVuSans.ttf

make sure you set the exports from the /mm.sh script first or the app will complain.

image

and it has changed to manual focus mode, neat!

ok lets double check with something else

image

might as well try to change it to B&W mode, so lets strace the command

this seems like a good candidate

write(16, "\36\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"…, 100) = 100

next to modify the test app (add \36 to the start of the buffer, compile, upload,and strace it

socket(PF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC, 0) = 3
connect(3, {sa_family=AF_LOCAL, sun_path="/tmp/UNIX.domain"}, 110) = 0
write(3, "\36\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"…, 100) = 100
close(3)                                = 0
exit_group(-1091580860)                 = ?

and yup it switches to B&W mode

image

ok so it seems like we’re going the right way so far. not everything in the GUI works like this I don’t believe, but if i can get AE/MF up and down focus control, and AE off and on it’ll be a good start. if its all just one or two bytes to set modes, it’d be trivial to adapt the app to just pass in the command on the argv and poke at it.

Leaving the myTest app running there is no problem with multiple applications talking to that local IPC socket, other than if you manage to write multiple things at once that conflict and the GUI will get  out of sync if you change things programmatically that are toggles, since the GUI is unaware of what you did.


next step is to catalogue the commands, then later start looking at the main application.

and on to add more features to the always growing MeasureTwice app…

Sometimes i’ll add the strace, others just the first byte

(these are out of date now use the github link https://github.com/charlie-x/eakins-camera )

Auto Focus

write(16, "\5\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"…, 100) = 100

Click focus

\6

Manual Focus

write(16, "\7\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"…, 100) = 100

B&W on

\36

ROI Set

write(15, "\24\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"…, 100) = 100 <0.000826>

AWB

write(15, "\f\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"…, 100) = 100 <0.006507>

MWB

write(15, "\16\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0"…, 100) = 100 <0.001343>

click Capture

write(15, "\4\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"…, 100) = 100 <0.000385>

Flip

write(15, "\31\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"…, 100) = 100 <0.000899>

Mirror

write(15, "\32\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"…, 100) = 100 <0.000908>

Clicking HDR

write(15, "\23\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"…, 100) = 100 <0.001058>

Sliding MF slider in MF mode, so write \10and then a position ( i changed strace to dump in hex here)

write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xeb\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.002519>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.007700>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf4\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.007004>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.002271>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.008654>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.002378>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.003269>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.002288>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.002283>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.002493>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x21\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.006618>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x31\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.002643>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.015587>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.002530>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x4e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.002634>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x54\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.002608>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x57\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.002951>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x5b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.003195>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x64\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.007897>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x6d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.010341>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x72\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.002574>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.002904>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x83\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.027182>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x88\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.002345>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.007582>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x91\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.005741>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.002418>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.002833>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.014942>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xdf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.116899>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xea\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.002403>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.010266>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.014076>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.002584>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.024927>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x12\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.007165>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x24\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.002472>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x46\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.120134>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x4f\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.002780>
write(15, "\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x77\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.105966>

clicking 50/60Hz

Entering the cmos_fps_set!
write(15, "\x1b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"…, 100) = 100 <0.000859>

A bit of the old Heath Robinson going on but its a start,  also planning to add a Y stage.

image

If you wanted to rewrite the GUI app its just doing the above with a straight up  /dev/fb0 and QT and then overlaying the crosshairs etc.

/dev/fb0 is a hardware overlay over the cameras sensor, copying urandom onto /dev/fb0 , moving mouse around erases the fb0, but the background stays regardless

image

now that i can control the camera i can do a focus stack capture, which just means controlling the focus of the camera either by mocing it up and down or by moving the AF motor, so lets do that.

first figure out the range that the object is in focus at near and far plane. in my case 140 to 155

step from 140 to 155 and capture image each step.

serial in to the shell, add the talk app , insert an sd-card

for var in `seq 140 155`; do ./talk p $var ; ./talk C ; done

https://www.youtube.com/watch?v=tRmd8nV61Ds&feature=youtu.be

quick focus stack result with 15 steps
from

some results

camera view

after focus stack

before/after





test apps and source at

https://github.com/charlie-x/eakins-camera

Created a .ko for the r8152 USB to ethernet adapter, and it works.

/mnt/sdcard # insmod ./r8152.ko
usbcore: registered new interface driver r8152
/mnt/sdcard #
/mnt/sdcard # usb 1-1: reset high-speed USB device number 3 using hiusb-ehci
r8152 1-1:1.0: eth0: v2.12.0 (2019/04/29)
r8152 1-1:1.0: eth0: This product is covered by one or more of the following patents:
                US6,570,884, US6,115,776, and US6,327,625.

/mnt/sdcard # ifconfig
eth0      Link encap:Ethernet  HWaddr 70:88:6B:86:3F:97
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:16 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:1540 (1.5 KiB)  TX bytes:0 (0.0 B)

ifconfig eth0 192.168.1.160 netmask 255.255.255.0

/mnt/sdcard # ifconfig
eth0      Link encap:Ethernet  HWaddr 70:88:6B:86:3F:97
          inet addr:192.168.1.160  Bcast:192.168.1.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:135 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
           RX bytes:12590 (12.2 KiB)  TX bytes:0 (0.0 B)

[C:\Program Files\JPSoft\TCMD17x64]ping 192.168.1.160

Pinging 192.168.1.160 with 32 bytes of data:
Reply from 192.168.1.160: bytes=32 time=1ms TTL=64
Reply from 192.168.1.160: bytes=32 time=1ms TTL=64

image

Using VNA/J, MicroVNA Tiny and Megiq VNA

Great series on VNA Videos

This entry is partly so when I come back to this later, i’ll remember the steps! Using a MicroVNA with VNA/j and then transferring it via  touchstone(s2p) file to other software.

Run a scan in VNA/J

image

Smith Chart

image

Export S-Parameter collector

image

Choose S11 on left side, and save it to the S2P FIle

image

MeqiQ VNA software

https://www.megiq.com/resources/downloads

I had a few issues with this software on resizing to full screen, and a popup error, close the popup and resize it

Install, Load and right click in Measurement window

image

Verify looks ok

image

Click in Display tab

image

image

Click S11, Add graph

image

Choose Type and click OK

And there it is

image

Click on points o chart to match, right click over the points and select Match circuit

image

image

Select Match

image

Neoden 4 Pick and Place

Overview

This is a long post.

We replaced out Neoden TM220A with a Neoden 4, it has worked well for us but there was a couple of things I wanted to change in the software. Like the machine moves over the pulled tape with parts and sometimes the tape gets in the way. maybe some changes to the vision algorithms.

So where to begin.

The easy one; if you use Eagle and want a slightly better experience https://github.com/charlie-x/neoden4 use my ULP, if you don’t use Eagle the various formats are documented inside that ULP

The Neoden4 is a Windows XP box that is locked down a little bit, ctrl alt delete gets you to the Chinese task manager, select the  the lower middle button, then select the run menu and run CMD or explorer.

image

It has a C: Write cache protection, anything you write to the C: drive goes into a cache file that is located on the root of drive C: so if you mod the registry or such, and reboot the changes will be gone.

To get rid of it, boot XP in safe mode, then run

KYSYSProtectApp.exe

image

It’ll look better on the machine. click the lower right button down and this will remove the KYSysProtect.sys driver, using the first button will re-enable it.

[INSTALL]                     [CANCEL]

[OPEN]

[SHUTDOWN]

[UNINSTALL]

image

Then you can pop in the registry editor regedit.exe . Change shell from start.bat to explorer.exe

Reboot and you should see explorer, the D:\Neoden4\ folder contains the startup executable. Now you can add a usb to wifi or wired adapter and setup networking.

Either of these will work  and have XP drivers

https://www.amazon.com/gp/product/B00ET4KHJ2/

https://www.amazon.com/gp/product/B00762YNMG/

Notes

The run csv files are stored in D:\Neoden\proc folder

The footprints are stored inside the config folder as pkg.csv

0402,1.00,0.50,0.50,

0603,1.60,0.80,0.50,

0805,2.00,1.20,0.50,

1206,3.20,1.60,0.50,

1210,3.20,2.50,0.50,

1812,4.50,3.20,0.50,

config.ini contains all the preset positions of the feeders, etc they’re often in QT variant format but QT supports either

[Default]

bLighting=false

nRunSpeed=100

nVibFreq=100

nVibCurrent=30

fAngleCamLookUp=@Variant(\0\0\0\x87?\x99\x99\x9a)

fBrightness=@Variant(\0\0\0\x87@\0\0\0)

[Stack]

lstStack0.pos.x=410.223266601562

lstStack0.pos.y=88.2767715454102

lstStack0.nFeedId=1

lstStack0.nPeelId=1

or

[Stack]

lstStack0.pos.x=@Variant(\0\0\0\x87\x43\xcd\x1c\x94)

lstStack0.pos.y=@Variant(\0\0\0\x87\x42\xb0\x8d\xb5)

lstStack0.nFeedId=1

lstStack0.nPeelId=1

Either is valid

Setting.ini has the language and hashed serial number of your machines MAC address if this doesn’t match it’ll default to chinese

[Serial]

Language=78798069

[System]

Language=eng

If you don’t turn off ksysprotect the only changes you make that will stay after a reboot will be on the D:\ drive.

The update installed is a .cab file renamed to .neoden and the start.bat script looks for it on the USB drive and will extract it into the D:\Neoden4 folder at boot time.

@echo off

if not exist D:/NeoDen4/v4.1.3.9_1d6d860d134a44232ea1b42c0bc11e52.neoden goto run

expand D:/NeoDen4/v4.1.3.9_1d6d860d134a44232ea1b42c0bc11e52.neoden -F:* ./

del v4.1.3.9_1d6d860d134a44232ea1b42c0bc11e52.neoden

:run

start NeoDen4.exe

Exit

The shutdown button in the neoden software just shells out to the cmd processor and the shutdown command, this is pretty typical for these sorts of machines.

If you switch the XP language to English, it will likely no longer boot the needed files are missing even if you add the language pack, better to reinstall.

The ID for the Chinese/English switch is based on the first network MAC address the machine finds. If it appears in Chinese and was English, then either the config file was changed or the first MAC address did

Drivers for the cameras are on my github for 7×64 and 10 x64 , probably 8 too , Be aware since after years of people beating on Windows for the fault of bad drivers MS has stepped up the driver policy on 10 and you need to generate and resign the CAB as well the INF otherwise you’ll have to disable secure boot and/or driver enforcement to load modified drivers.

Better translations

On the whole the translations are fine, but there were some things that bugged me and used up valuable screen space. Like the Feeder column Lists Feeder 1, Feeder 2 etc, leaving less space for what is in the column that you really want to see. So just changing it in the QM file, the app uses QT 5.3.1 extract it to a C:\QM folder

The translations are stored in the Neoden.qm file which is a compiled XML/TS translation file.

QT Linguist can open QM files, you just need to change the filter in the open, but i used the command line tool instead.

C:\Qt\QT5.3.1\5.3\mingw482_32\bin\lconvert.exe Neoden4.qm -o Neoden4.ts

This will convert the binary .qm to an XML file. If you open it up you’ll see the translations like th

<message>
        <source>55å·æ–™æ ˆ</source>
       <translation>Feeder 55</translation>

</message>

<message>    <source>å☼-åo^</source>
        <translation>Outer</translation>

< /message>


The source is the Chinese, which doesn’t show up here and translation is the English version. So next changed all the relevant Feeder messages to > </ as if it were removed altogether and didn’t leave a single space the Chinese translation would appear instead

<message>     
        <source>æ-╪件</source>
        <translation>File</translation>

</message>

<message>
        <source>æ-Tæ ^</source>
        <translation> </translation>

< /message>

To convert the XML back to a .QM use the command

C:\Qt\QT5.3.1\5.3\mingw482_32\bin\lrelease.exe Neoden4.ts

The QM likely changes between versions, so best to always grab the current one.

Original

image

After, you can see the space before the numbers and you can stretch out the SMD Spec column

image

Fixing the MAC address

If you want to keep the same setup but either change the current mini PC< run it on another system or add a network card that messes up the install, you can either change the mac address so its the same as the original, or you can hard code the MAC address in QT this does mean setting up QT to compile with mingw etc.

If you haven’t grabbed QT 5.31 do so and extract it to C:\QT\QT5.3.1

The software doesn’t use any of the networking except for the MAC address

Open the file

C:\QT\QT5.3.1\qtbase\src\network\kernel\qnetworkinterface.cpp

Look for

QString QNetworkInterface::hardwareAddress() const

In the following code substitute the desired MAC address for 00:00:00:00:00:00.

/*!
        Returns the low-level hardware address for this interface. On
       Ethernet interfaces, this will be a MAC address in string
       representation, separated by colons.
       Other interface types may have other types of hardware
        addresses. Implementations should not depend on this function
        returning a valid MAC address.

*/

QString QNetworkInterface::hardwareAddress() const

{
       return QString(“00:00:00:00:00:00”);
       return d ? d->hardwareAddress : QString();

}

Now recompile QT

configure.bat -release -qt-zlib -opensource -confirm-license -platform win32-g++ -opengl desktop -prefix C:\Qt\Qt5.3.1\5.3\mingw48_32 -nomake tests -nomake examples

Then copy over the new Qt5Network.dll to the machine or your local install and test it.

Change the Logo

This is a simple one, change Logo.png in the D:\Neoden4\Res folder to a PNG of your choice that is 500×100 32 bit with an alpha background

Logo

image

Shutdown

If for some reason you don’t want the machine to shut down after you click Shut Down search and replace shutdown in the Neoden4.exe with an invalid cmd. Like xxxxxx then it won’t execute it. It is in a couple of spots. Entering the config mode incorrectly also tells the machine to shutdown, which is annoying if you do run it on a different PC

image

image

Running on a different PC

You’ll need the MAC address fix from above otherwise it’ll be in Chinese. Copy over all the files from D:\neoden4\ to the new machine , doesn’t really matter where. But you will need a serial port that is at COM2:  and the same drive letter as the USB drive is now.

If you run XP the drivers for the cameras are in the D:\Neoden4 folder, if you want x64 or newer windows we will have to make some up, which just means grabbing the right version of the Cypress FX usb drivers and adding the Cameras USB ID’s

As a side note you can use software like KernelPro to forward USB and the Serial over TCP/IP to any other machine on the network and it’ll work fine as long as you install the camera drivers and the forwarded Serial is COM2

com0com should work too, but you will need to forward the USB as well, i’ve used the Kernel Pro software a fair but and its been pretty solid and useful for sniffing, RE etc.

image

The Windows DDK also has the skeleton code for a Virtual Serial Port I’ve used that one extensively too

Another great tool for sniffing is Device Monitoring Studio, came in handy when  building the keyboard.

This of course needs a network adapter on the Neoden4 side in order to forward the Serial/USB too

The Cameras

Camera USB ID

camera1

camera1a

Second

camera2

As we can clearly see its a Cypress FX with CYUSB3.sys driver. The machine had a folder called CameraNew that had the XP32 drivers, but we may want to update those.

Just need to add this to the cyusb3.inf driver. (which i’ve linked here )

VID_52CB&PID_52CB.DeviceDesc=”Camera Neoden Tech.”

and to each relevant section (the platforms)

%VID_52CB&PID_52CB.DeviceDesc%=CyUsb3, USB\VID_52CB&PID_52CB

e.g. add it to each of these sections

;for all platforms

[Device.NT]

;for x86 platforms

[Device.NTx86]

Add the same line to each of these Device sections for the OS you want to create the driver , all of them for completeness

Cypress actually document the process here, as well as locating the drivers you might need, i used windows 7 x64 as my base

https://community.cypress.com/docs/DOC-12366

To match the device with the drivers, refer to the steps mentioned under the section “Matching Devices to the Driver ” in the attached PDF file. Adding the VID/PID is already done in the attached .inf file , so you can skip “Step A : Add the device’s Vendor ID and Product ID to the CYUSB3.INF file”.

Now you will have camera drivers for your system and can install them when you add the USB cameras.

The serial connection is hardwired on the motherboard of the mini PC which is inside the PNP, in this post I am mostly going to concentrate on the software side and to be honest if you can’t find the bits to rewire in the new PC you should probably leave it alone as it does have a few things going on. But you do just need to reroute USB (cameras) and Serial(Control) . So they’re fairly straightforward.

USB to serial is fine. since i have ran it plenty of times with a serial<>tcpip bridge

You can also use these drivers for a fresh XP install that uses the language set of your choice.

As long as you copy all the opencv dlls, and install folder on D: from the machine  that is all you need to make a fresh install on the same PC, if you change the PC you will need to do MAC fix or it might default to the Chinese language, which is fine if you speak Chinese.

I didn’t really look into how they generate the config password since its so easy to replicate a MAC , but that is what it is based on, i may take a look sometime.

Other Mods to the GUI

I wanted rid of the frameless app so it can be resized on a larger monitor, since the app uses QT it’ll use setWindowsFlags with

Qt::FramelessWindowHint

Which is 0x0000800

Looking thru the binary we can see the calls to setWindowsFlags , it is mangled so using a demangle tool we can see this is correct

QWidget::setWindowFlags(QFlags<Qt::WindowType>)


Which is right, and there is the flag being placed on the stack for the call, so just change it to 0 and  it will now run with a resizeable border

C7 04 24 00 08 00 00   mov     dword ptr [esp], 0x800

FF 15 EC 04 4F 00       call  ds:_ZN7QWidget14setWindowFlagsE6QFlagsIN2Qt10WindowTypeEE

the qt_main is where the main window is created, which itself is called from WinMain, so traverse WinMain to  the QT main routine looking for

QApplication::QApplication(int&, char**, int)



which in this case is mangled as

_ZN12QApplicationC1ERiPPci

Although I usually find QT apps a PITA to RE they do all share common flows so its easy to find the basics

We’ll know where the main window is created in that function since it’ll have

_ZN7QWidget4showEv

QWidget::show()

But we have enough info with the C7 04 24 00 08 00 00  FF 15 EC, just search and replace that with C7 04 24 00 00 00 00 FF 15 EC a global replace does change all the windows to be bordered, but you might not want to change all of them, next i want to change all the tabs to automatically expand, but i haven’t done that yet, and Neoden do actually keep doing updates.

image

image

Most of the changes they’re making seem mostly related to text fixes ,maybe since they did a Neoden USA partnership

image

Then it can run remotely on Windows 7 x64 etc with just the pnp computer being the remote host.

image

Gaining control

Lets cover getting access to the control systems if we want to change the software altogether

It is all serial to the motion controller board so it is trivial to snoop, after that there is CAN bus but it doesn’t really feel like a necessity to go that far yet, (apparentlyit now is ) maybe if people wanted to use the feeders or peelers for other projects but there are better ones. Also i wonder if its any coincidence that serial byte size is 8 bytes, which is what you send in the most common CAN frame.

Here is the boot up trace, these are annotated for the test software with a C++ wrapper for the RS232dll.dll so it can snoop, change or inject if needed

/// boot to main menu

//status msg

Write 0x45Read 0x09

Write 0x05

Read 0x14

Write 0x85

Read 0x1c

Read 0x00 0x03 0x00 0x16 0x00 0x00 0x00 0x00 0x5d

// blow/suck
    Write 0x43

Read 0x0f

Write 0xc3

Read 0x07

// blow nozzle 1

Write 0x01 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0xb2

Write 0x03
    Read 0x03

Write 0x03

Read 0x47

Write 0x43
    Read 0x0f

Write 0xc3

Read 0x07

// blow nozzle 2

Write 0x01 0x02 0x00 0x00 0x00 0x00 0x00 0x00 0x30

Write 0x03
    Read 0x03

Write 0x03

Read 0x47

Write 0x43
    Read 0x0f

Write 0xc3

Read 0x07

// blow nozzle 3

Write 0x01 0x03 0x00 0x00 0x00 0x00 0x00 0x00 0x51

Write 0x03
    Read 0x03

Write 0x03

Read 0x47

Write 0x43
    Read 0x0f

Write 0xc3

Read 0x07

// blow nozzle 4

Write 0x01 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x15

Write 0x03

Read 0x03

Write 0x03

Read 0x47

Write 0x43
    Read 0x0f

Write 0xc3

Read 0x07

// off nozzle 1

Write 0x00 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x61

Write 0x03

Read 0x03

Write 0x03

Read 0x47

Write 0x43
    Read 0x0f

Write 0xc3

Read 0x07

// off nozzle 2

Write 0x00 0x02 0x00 0x00 0x00 0x00 0x00 0x00 0xe3

Write 0x03

Read 0x03

Write 0x03

Read 0x47

Write 0x43
    Read 0x0f

Write 0xc3

Read 0x07

// off nozzle 3

Write 0x00 0x03 0x00 0x00 0x00 0x00 0x00 0x00 0x82

Write 0x03

Read 0x03

Write 0x03

Read 0x47

Write 0x43
    Read 0x0f

Write 0xc3

Read 0x07

// off nozzle 4

Write 0x00 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0xc6

Write 0x03

Read 0x03

Write 0x03

Read 0x47

//blow all off
    Write 0x43

Read 0x0f

Write 0xc3

Read 0x07

// all off?

Write 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

Write 0x03

Read 0x47

//rotate nozzles
    Write 0x41

Read 0x0d

Write 0xc1

Read 0x05

Write 0x00 0x00 0x96 0x00 0x00 0x00 0x00 0x00 0x45

Write 0x01

Read 0x01

Write 0x01

Read 0x45

// move nozzles, max retract
    Write 0x42

Read 0x0e

Write 0xc2

Read 0x06

Write 0x00 0x00 0x96 0x00 0x00 0x00 0x00 0x00 0x45

Write 0x02

Read 0x11

// rails set speed to 00
    Write 0x46

Read 0x0a

Write 0xc6

Read 0x02

Write 0x64 0x09 0x00 0x00 0x00 0x00 0x00 0x00 0x19

Write 0x06

Read 0x06

Write 0x06

Read 0x42

//rail set speed to 100%

Write 0x46

Read 0x0a

Write 0xc6

Read 0x02

Write 0x64 0x09 0x00 0xc8 0x00 0x00 0x00 0x00 0x8c

Write 0x06

Read 0x06

Write 0x06

Calculating the CRC Routines

So looking at the data, its a cmd, reply, cmd reply, cmd with extra data and an obvious CRC at the end. Going back to the binary lets see what CRC tables there are.

Yep sure enough there are two common CRCs

A full CRC16 table for poly 0x1021

dw 0, 4129, 8258, 12387, 16516, 20645, 24774, 28903, 33032

             37161, 41290, 45419, 49548, 53677, 57806, 61935, 4657

Second table is the first 16 words of the same table

dw 0, 4129, 8258, 12387, 16516, 20645, 24774, 28903, 33032, 161, 41290, 45419, 49548, 53677, 57806, 61935

Looking for references to this we see a normal CRC16, its next to com port write/write (which we can tell from the references to the rs232dll.dll )

For the smaller table its a different routine. Digging into it a bit we see it is taking the cmd we sent, the reply from the machine , masking and then calculating the crc from the smaller table.

push    ebx

sub     esp, 0x28

mov     ebx, [esp+0x2C+arg_4]

mov     eax, [esp+0x2C+arg_0]

mov     [esp+2Ch+var_28], 2

mov     [esp+2Ch+var_E], al

mov     eax, ebx

and     ebx, 0Fh

and     eax, 0FFFFFFF0h

mov     [esp+0x2C+var_D], al

lea     eax, [esp+-0x2C+var_E]

mov     [esp+2Ch+var_2C], eax

call    crc16_16

and     eax, 0x0F

cmp     al, bl

setz    al

add     esp, 0x28

pop     ebx

retn    8

So it is pulling in cmd( sent by software) and the reply from the machine, combining them into a 16 bit value ,masking it off to be the top nybble, then crcing those two bytes, then it masks the lower nybble of the calculated CRC and the reply from the machine then check to see if they match, converting it to C

bool  checkReply ( char cmd, char reply )

{
       // combine and mask
       int16_t v3 = ( cmd + ( int16_t ( reply  << 8 ) ) ) & 0xf0ff;
       // technically it calls the full 32bit crc, but we only need the lower nybble
        uint16_t crc = crc16_16 ( ( uint8_t * ) &v3, 2 );
       // mask off lower nybble
        crc &= 0x0f;
       reply &= 0x0f;
       // if they match, we’re good.
        return ( crc == reply  );

}

Lets test it out, i also put the CRC routine here

Launch C++

Feeding it a couple of cmds that the machine says are valid, and one bad command we get

CMD = 0xC3 reply = 0x07 = yes

CMD = 0x45 reply = 0x09 = yes

CMD = 0x45 reply = 0xAA = no

Great we know know that this is how the machine replies to commands.

Next is the CRC at the end of the larger blocks, this one was fixed at 2 bytes, so we know it is not it. We know there is a second CRC16 table, so looking at the references for that we find the standard CRC16 routine, and then referring to that we see the routine that then spits out the 9 bytes. The RS232 dll has a separate routine for sending out the single bytes.

Just to make it easy to prove out we can use an online tool to verify it is what I think it is.

Online CRC Calculator

Lets take a couple of known strings

0x64 0x09 0x00 0x00 0x00 0x00 0x00 0x00  CRC=0x19

0x64 0x09 0x00 0xc8 0x00 0x00 0x00 0x00  CRC=0x8C

Set CRC to HEX and 16

Result 1

image

Result 2

image

There we go, CRC16/XMODEM i know its polynomial 0x1021 and i know the seed is 0 and Neoden only uses the lower byte

So now we know what both the CRC routines are, how the basic command structure works and we can go from there

The next part is a little tedious since it means we snoop the serial, and select one command at a time, see what happens and try variations on it, record, test and repeat.. basic science . Luckily there aren’t that many.

// a few hours pass by

A quick C++ class to define a basic test harness with each part added as more information is discovered, creating a class for sub components like the Rails, feeders etc and then an embodied class for the machine.

Taking just one simple example, the buzzer

// beep if true, off if false
             // @return pass/fail
            bool Buzzer ( bool on ) {
                if ( bBoot == false ) { return false; }
                if ( getAck ( 0x47 ) == false ) {
                     return false;
                 }
                if ( getAck ( 0xc7 ) == false ) {
                      return false;
                }
                uint8_t cmd[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
                if ( on ) {
                      cmd[5] = 0x01;
                }
                //doesn’t expect a reply from the machine
                 if ( sendData ( cmd, 8 ) != 8 ) {
                    return false;
                }
                return getAck ( 0x07 );
             }

The flow is, (it varies on some commands)

check the boot worked (sending the byte sequence from before)

getAck sends 0x47 and checks for the shorter CRC’d reply , returning true or false if passed

repeats for 0xC7 , not sure why it follows this style yet, maybe its a class/subclass thing or another protocol

now it sends out the 8 byte packet with the message we’re sending, byte 5 = 1 to turn on the buzzer, 0 to turn off the buzzer

finally it checks to see if the command completed properly, by sending out the getAck(0x7)

The final getAck will often send back the cmd you sent rather than the CRC, I believe it is doing this as a pause method, in code the retry for the final command to 5 retries, the serial has a 500 ms timeout set. (it is also 115200,n,8,1)

The 8 byte send is done thru sendData which will tack on the 9th byte which is the computed CRC16/XMODEM/POLY1021 and it never expects a reply for that send

For the X Y coord’s they take mm and then are n*1000 to convert to an int16, for speed for the rails they are n*2 for the int16

Building a DLL to snoop

Even though snooping is trivial on serial, it is often useful to build a DLL that can intercept data. So the easiest one first is the RS232DLL.DLL.. . Their name not mine Smile

First step is just to take a quick peek at what the DLL exports, dumpbin (MSVC)  will do that.

dumpbin /exports rs232dll.dll

File Type: DLL
     Section contains the following exports for rs232.dll
       00000000 characteristics
        545B1F00 time date stamp Wed Nov  5 23:10:56 2014
           0.00 version
              1 ordinal base
              5 number of functions
              5 number of names
       ordinal hint RVA      name
             1    0 00001080 rs232_close
              2    1 00001050 rs232_open
             3    2 00001090 rs232_read
             4    3 000010B0 rs232_readSyn
              5    4 000010D0 rs232_write
     Summary
           5000 .data
             1000 .rdata
           1000 .reloc
            7000 .text

Great it is simple, open close, read , another read, and a write.

Now to make a proxy DLL, there are a bunch of tools to do this, I haven’t tried this one but it has both x64 and x32 which i’d added to the tool used here, the other one is here

https://github.com/SeanPesce/DLL_Wrapper_Generator

In short a proxy dll when loaded  loads the old dll which has been renamed, and then adds the functions from the old DLL as pointers, when the host calls your proxy dll , you do stuff then pass control to the original DLL

Example

extern “C” __declspec ( naked ) void __E__0__()

{
       __asm pushad
       __asm pushfd
       OutputDebugStringA ( “rs232_close\n” );
       __asm popfd
       __asm popad
       __asm {
            jmp procs[E_RS232_CLOSE*4];
       }

}

this is the proxy for the rs232_close function. All it does is print rs232_close\n to the windows debug log.

the DLLMain is where the proxy is setup

BOOL WINAPI DllMain ( HINSTANCE hInst, DWORD reason, LPVOID )
    {
       OutputDebugStringA ( “DllMain called\n” );
       if ( reason == DLL_PROCESS_ATTACH ) {
            OutputDebugStringA ( “DllMain DLL_PROCESS_ATTACH\n” );
           hLThis = hInst;
           hL = LoadLibrary ( _T ( “rs232dllOLD.DLL” ) );
           if ( !hL ) { OutputDebugStringA ( “Failed to load original library\n” ); return false; }
            procs[E_RS232_CLOSE] = GetProcAddress ( hL, “rs232_close” );
           if ( procs[E_RS232_CLOSE] == NULL ) { OutputDebugStringA ( “Failed to get proc address rs232_close\n” ); }
           procs[E_RS232_OPEN] = GetProcAddress ( hL, “rs232_open” );
           if ( procs[E_RS232_OPEN] == NULL ) { OutputDebugStringA ( “Failed to get proc address rs232_open\n” ); }
           procs[E_RS232_READ] = GetProcAddress ( hL, “rs232_read” );
           if ( procs[E_RS232_READ] == NULL ) { OutputDebugStringA ( “Failed to get proc address rs232_read\n” ); }
           procs[E_RS232_READSYN] = GetProcAddress ( hL, “rs232_readSyn” );
           if ( procs[E_RS232_READSYN] == NULL ) { OutputDebugStringA ( “Failed to get proc address rs232_readSyn\n” ); }
           procs[E_RS232_WRITE] = GetProcAddress ( hL, “rs232_write” );
           if ( procs[E_RS232_WRITE] == NULL ) { OutputDebugStringA ( “Failed to get proc address rs232_write\n” ); }
       }
       if ( reason == DLL_PROCESS_DETACH ) {
            OutputDebugStringA ( “DllMain DLL_PROCESS_DETACH\n” );
           FreeLibrary ( hL );
        }
       return 1;
    }

When the HOST attaches to our proxy DLL , it uses LoadLibrary to open the old DLL RS232dllOLDd.dll then it uses GetProcAddress on the handle for that DLL and gets the pointer to the real function, and stores it in an array for later.

Very basic,  and works great., this is for an X32 proxy with VC unless we want to emit we use an external asm file for X64, which would look like this.

; entirelyDifferentFunc

  __E__0__ proc
       PUSHAQ
       lea        rcx, _entirelyDifferentFunc
       call qword ptr    __imp_OutputDebugStringA
       POPAQ
       call        procs[0*8]
       ret

__E__0__ endp

_entirelyDifferentFunc    db    “entirelyDifferentFunc”,13,10,0

PUSHAQ/POPAQ are macros that save all the things.

Also use a.def file to map the new functions to the originals, as well define the exports/order

LIBRARY   rs232dll

EXPORTS

rs232_close @1

rs232_open @2

rs232_read @3

rs232_readSyn @4

rs232_write @5

The @number is the ordinal for the function in the DLL, its index. This tells the linker we’re exporting these functions, we can alias them here too. Depending on how the original DLL was built it might be mangled,. or use stdcall/fastcall etc etc so this can be dealt with here

aliasing looks like this

EXPORTS

rs232_close=__E__0__ @1

rs232_open=__E__1__ @2

rs232_read=__E__2__ @3

rs232_readSyn=__E__3__ @4

rs232_write=__E__4__ @5

this allows us to map the autogenerated names to the real names.

Once you’ve mapped all these out you can make a simple dump function to show the data being passed in and out

// name of function, ptr to data to dump and length of data

void dump ( const  char *name, unsigned char *ptr, unsigned int length )

{
       if ( ptr ) {
           if ( length ) {
                 //if ( ptr[0] != 0x64 )   { return; }
               if ( name ) {
                    OutputDebugStringA ( name );
               }
               _RPT0 ( _CRT_WARN, “Write = { ” );
               while ( length– ) {
                    _RPT1 ( _CRT_WARN, “0x%02x,”, *ptr++ );
               }
               _RPT0 ( _CRT_WARN, “} ;\n ” );
            }
        }

}

Here you can also filter out data you don’t care about, you can see an example commented out where i wasn’t interested in messages that started with 0x64 this can help increase signal to noise ratio

Forget Snoop, Just rewrite the DLL

Since the RS232 dll turned out to be really basic, it was just reimplemented as to have total control

__declspec ( dllexport ) int __cdecl rs232_read ( LPVOID lpBuffer, DWORD nNumberOfBytesToRead )

{
       DWORD bytesRead = 0;
       BOOL  fSuccess = ReadFile (
                             hCom,    //Handle
                            lpBuffer,  //Incoming data
                            nNumberOfBytesToRead,  
                             &bytesRead, //Bytes Read
                            0 // not overlapped
                         );
       dump ( “rs232_read”, ( unsigned char* ) lpBuffer, bytesRead );
       return bytesRead;
    }

__declspec( dllexport) tells VC that we’re exporting this function

// doesnt use serial timeouts, uses a GetTickCount

__declspec ( dllexport ) int __cdecl rs232_readSyn ( PVOID lpBuffer, DWORD nNumberOfBytesToRead, DWORD timeout )

{
       ULONGLONG endTime;
       DWORD bytesRead = 0;
       endTime = GetTickCount64() + timeout;
       do {
            bytesRead = 0;
           BOOL  fSuccess = ReadFile (
                                  hCom,
                                lpBuffer,
                                 nNumberOfBytesToRead,
                                 &bytesRead,
                                NULL // not overlapped
                            );
           if ( fSuccess & bytesRead ) {
               dump ( “rs232_readSyn”, ( unsigned char* ) lpBuffer, bytesRead );
               return 1;
            }
       } while ( GetTickCount64() < endTime );
       return 0;
    }

Mapping these functions out to be as close to the original as possible.

__declspec ( dllexport ) int __cdecl rs232_write ( LPVOID lpBuffer, DWORD nNumberOfBytesToWrite )
    {
       DWORD dwNumberOfBytesWritten = 0;
       dump ( “rs232_write”, ( unsigned char* ) lpBuffer, nNumberOfBytesToWrite );
       BOOL fSuccess
            = WriteFile (
                 hCom,
                 lpBuffer,
                 nNumberOfBytesToWrite,
                 &dwNumberOfBytesWritten,
                 NULL
             );
       return dwNumberOfBytesWritten;

}

__declspec ( dllexport ) int __cdecl rs232_close()

{
       OutputDebugStringA ( “rs232_close\n” );
       int ret = CloseHandle ( hCom );
       hCom = NULL;
       return ret;
    }

Comm States used and set internally

int  OurSetCommState ( HANDLE h, DWORD baud )

{
       DWORD v7;
       struct _DCB DCB;
       DCB.DCBlength = sizeof ( DCB );
        GetCommState ( h, &DCB );
       DCB.BaudRate = baud;
        DCB.ByteSize = 8;             //  data size, xmit and rcv
       DCB.Parity = NOPARITY;      //  parity bit
       DCB.StopBits = ONESTOPBIT;    //  stop bit
       if ( SetCommState ( h, &DCB ) ) {
            return 1;
       }
       v7 = GetLastError();
       return 0;

}

int  SetTimeouts ( HANDLE h )
    {
       DWORD error;
       struct _COMMTIMEOUTS CommTimeouts;
       GetCommTimeouts ( h, &CommTimeouts );
       CommTimeouts.ReadTotalTimeoutMultiplier = 0;
         CommTimeouts.ReadTotalTimeoutConstant = 0;
       CommTimeouts.ReadIntervalTimeout = -1;
        CommTimeouts.WriteTotalTimeoutMultiplier = 10;
       CommTimeouts.WriteTotalTimeoutConstant = 1000;
       if ( SetCommTimeouts ( h, &CommTimeouts ) ) {
            return 1;
       }
       error = GetLastError();
        _RPT1 ( _CRT_WARN, “SetTimeouts failed: = %d\n”, error );
       return 0;
    }

rs232_open function, which is called with the com2: 115200, ,n,8,1 settings , this also sets timeouts etc

// opens and sets up baud rate etc.

//a4/a5 = 0 (parity)

__declspec ( dllexport ) int __cdecl rs232_open ( LPCSTR lpFileName, int baudrate, int bits, int a4, int a5 )

{
       OutputDebugStringA ( “rs232_open ” );
        OutputDebugStringA ( lpFileName );
        OutputDebugStringA ( “\n” );
       // HANDLE hCom;
        int result = 0;
       //  Open a handle to the specified com port.
        hCom = CreateFileA ( lpFileName,
                            GENERIC_READ | GENERIC_WRITE,
                            0,      //  must be opened with exclusive-access
                            NULL,   //  default security attributes
                             OPEN_EXISTING, //  must use OPEN_EXISTING
                             0,      //  not overlapped I/O
                             NULL ); //  hTemplate must be NULL for comm devices
       if ( hCom == INVALID_HANDLE_VALUE ) {
            //  Handle the error.
           _RPT1 ( _CRT_WARN, “CreateFile failed with error %d.\n”, GetLastError() );
           return result;
       }
       SetCommMask ( hCom, 511u );
        SetupComm ( hCom, 512u, 32u );
       PurgeComm ( hCom, 12u );
       if ( OurSetCommState ( hCom, baudrate ) ) {
           if ( SetTimeouts ( hCom ) ) {
               EscapeCommFunction ( hCom, 5 );
               EscapeCommFunction ( hCom, 3 );
               PurgeComm ( hCom, 0xF );
               result = 1;
           }
       }
       return result;
    }

then there is a global for the HANDLE to the serial device/COM2 

static HANDLE hCom = 0;

And then the whole thing is wrapped in, this matches it to the original DLL which says it was built in VC6 ? linker 6.0

extern “C” {

}

rs232_readSyn has the timeout that reads the single ack back

So in this case it was just as easy to rewrite the DLL as proxying it and ignore the COM settings and fill in as needed

Adding USB cameras, for some reason

One of the first things to do with the software after making it run on a dev box was hook up a couple of USB cameras so the machine didn’t need to be up and running while fiddling with it. The Neoden cameras are Cypress FX devices which do basically just have a couple of routines to set exposure, flash, size etc the capture routine just returns a w*h uint8 grey buffer for the image, with the cameras id of 0/1(5) passed to the DLL

same procedure as before, examine the dll and see what it exports

ordinal hint RVA      name
         1    0 00001840 img_capture
           2    1 000017D0 img_init
          3    2 00001870 img_led
          4    3 000019F0 img_read
          5    4 00001A30 img_readAsy
          6    5 000018B0 img_reset
          7    6 000018F0 img_set_exp
          8    7 00001930 img_set_gain
          9    8 00001970 img_set_lt
         10    9 000019B0 img_set_wh

which more or less translates too

__declspec ( dllexport ) BOOL _cdecl img_capture ( int which_camera);
     __declspec ( dllexport ) int _cdecl img_init();
    __declspec ( dllexport ) BOOL _cdecl img_led ( int which_camera, int16_t mode );
    __declspec ( dllexport ) int _cdecl img_read ( int which_camera, unsigned char * pFrameBuffer, uint32_t BytesToRead, uint32 ms );
    __declspec ( dllexport ) int _cdecl img_readAsy (int which_camera, unsigned char * pFrameBuffer, uint32 BytesToRead, uint32 ms);
    __declspec (dllexport) int _cdecl img_reset(int which_camera);
    __declspec ( dllexport ) BOOL _cdecl img_set_exp ( int which_camera, int16_t exposure );
    __declspec ( dllexport ) BOOL _cdecl img_set_gain ( int which_camera, int16_t gain );
    __declspec ( dllexport ) BOOL _cdecl img_set_lt ( int which_camera, int16_t a2, int16_t a3 );
    __declspec ( dllexport ) BOOL _cdecl img_set_wh ( int which_camera, int16_t w, int16_t h );

so after wrapping videoinput/opencv into a new dll

static int16_t g_width, g_height;
    VideoCapture *stream1 = NULL, *stream2 = NULL;
       __declspec ( dllexport ) int _cdecl img_init()
         {
            static bool init = false;
           if ( init == false ) {
               stream1 = new VideoCapture ( 1 );
                 stream2 = new VideoCapture ( 0 );
                init = true;
            }
           return 1;
         }

the software only seemed to use readAsy

// a1 = 5 (1 if downlooking?)
       // a2 = 0x05580030
      // a3 = 0x00100000
      // a5 = 0x97 (changes)
      // dwMilliseconds = 1000
      int _cdecl img_readAsy ( int which_camera, unsigned char * pFrameBuffer, int a3, DWORD dwMilliseconds, char a5 )
      {
          if ( pFrameBuffer == NULL ) {
              return 0;
          }
         Mat cameraFrame;
         if ( which_camera == 5 ) {
               if ( stream1 == NULL ) {
                  return 0;
              }
             stream1->read ( cameraFrame );
         } else {
               if ( stream2 == NULL ) {
                  return 0;
              }
             stream2->read ( cameraFrame );
             // my camera was flipped
               flip ( cameraFrame, cameraFrame, 0 );
          }
        // convert to grey
          if ( cameraFrame.rows ) {
              cv::Mat greyMat;
              flip ( cameraFrame, cameraFrame, 1 );
              cv::cvtColor ( cameraFrame, greyMat, CV_BGR2GRAY );
              Size size ( g_width, g_height );
              resize ( greyMat, greyMat, size );
             memcpy ( pFrameBuffer, greyMat.ptr(), g_height * g_width );
           }
          return 1;
      }

// a2 = 1024, a3 =1024 a1 = 5

BOOL _cdecl img_set_wh ( int which, int16_t width, int16_t height )

{
       _RPT3 ( _CRT_WARN, “img_set_wh) %d %d %d\n”, which, width, height );
       g_width = width;
       g_height = height;
       return TRUE;
    }

So just a very quick and dirty interface to cameras that VideoInput supports, it is only really useful during desktop RE work but maybe there will be a time when i can swap out the cameras in the machine, either way it works and we can move on to the real Neoden cameras

Deeper Dive with the Neoden Camera

Picking apart the NeodenCamera.dll it is using CyApi / CyUsb3.sys to communicate with the cameras. I did see a post on eevblog that said it was CGUSB2.dll there might be an older rev of the hardware that uses those cameras, but these don’t. Similar hardware so could just be a rewrite to the new FX chip

Going over the IOCTLs

First process is to build up the Constructor/Destructor code for the CyUSB interface, since that shows me where the device context/structs are. Also decoding the IOCTLs which are built with

#define IOCTL_Device_Function CTL_CODE(DeviceType, Function, Method, Access)


It creates a 32 bit value

diagram illustrating the i/o control code layout

Online Decoder

The commonly appearing IOCTL is 0x220020 which decodes as

Device = 0x22(FILE_DEVICE_UNKNOWN) which means this device doesn’t match any of the predefined ones

Function 0x8

Access FILE_ANY_ACCESS

Method unbuffered

The others observed are

0x220008 Function 0x08 Unbuffered

0x22003C Function 0xF Buffered

0x220040  Function 0x10 Buffered

0x220010 Function 0x04 Buffered

0x220000 Function 0x0 Buffered

0x220004 Function 0x01 Buffered

Looking in cyioctl.h from the Cypress SDK the IOCTLS are defined as

#define IOCTL_ADAPT_GET_DRIVER_VERSION         CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTL_ADAPT_INDEX, METHOD_BUFFERED, FILE_ANY_ACCESS)

IOCTL_ADAPT_INDEX as 0x000

So lets see what matches and if it makes any sense

Taking the IOCTL 0x220000 we see that is function 0, buffered, so seems to match IOCTL_ADAPT_GET_DRIVER_VERSION  so now to check the code and see if it looks like that IOCTL is indeed looking for a driver version, this seems like a lucky place to start since a driver version is going to be obvious, Plus they are all in the Open call so filling in the basic startup stuff

void  CallDeviceIO_220000(DWORD *HANDLE)

{
      DWORD *pThis;
      DWORD *v2;

   pThis = HANDLE;
      if ( HANDLE[541] == -1 )                      // handle to device, is it not open?
      {
        HANDLE[11] = 0; // otherwise set output buffer to \0
      }
      else
      {
        outbutBuffer = HANDLE + 11;

          // handle, ioctl, ptr to outbuffer, size of buffer
        if ( !CallDeviceIO(HANDLE, 0x220000u, HANDLE + 11, 4u) || !pThis[530] )                                            

// failed
          *outputBuffer = 0;
      }

}

So it is asking for 4 bytes from the device, Lets look at the others

220000 Function 0x0 Buffered IOCTL_ADAPT_GET_DRIVER_VERSION

220004 Function 0x01 Buffered  IOCTL_ADAPT_GET_USBDI_VERSION Get the USBDI Version

220010 Function 0x04 Buffered IOCTL_ADAPT_GET_ADDRESS Get device address from driver

220008 Function 0x08 Unbuffered IOCTL_ADAPT_SEND_EP0_CONTROL_TRANSFER Send a raw packet to endpoint

220040 Function 0x10 Buffered IOCTL_ADAPT_GET_FRIENDLY_NAME

22003C Function 0xF Buffered IOCTL_ADAPT_GET_DEVICE_NAME

Confirm with CyUSB3/CyAPI SDK

Seems to make sense, looking back at the code  the IOCTLs that pass strings have larger buffers, (0x100) but before we go too far down the rabbit hole, lets look back at the Cypress SDK

And there it is in the Open function

GetUSBAddress();

GetDeviceName();

GetFriendlyName();

GetDriverVer();

GetUSBDIVer();

GetSpeed();

Great everything is matching, so now have verified this is the right SDK and it is acting as expected.

bool CCyUSBDevice::Open(UCHAR dev)

Calling this function and from the SDK know that the HANDLE above is pointing to this

typedef struct _USB_DEVICE_DESCRIPTOR {
        UCHAR   bLength;
        UCHAR   bDescriptorType;
        USHORT  bcdUSB;
        UCHAR   bDeviceClass;
        UCHAR   bDeviceSubClass;
        UCHAR   bDeviceProtocol;
        UCHAR   bMaxPacketSize0;
        USHORT  idVendor;
        USHORT  idProduct;
        USHORT  bcdDevice;
        UCHAR   iManufacturer;
        UCHAR   iProduct;
        UCHAR   iSerialNumber;
        UCHAR   bNumConfigurations;

} USB_DEVICE_DESCRIPTOR, *PUSB_DEVICE_DESCRIPTOR;

So at this point  it might be best to start rebuilding the NeodenCamera.dll with the CyUSB3 SDK, since again it is a simple enough DLL with just the specifics for chatting to the hardware.

Can we recreate the NeodenCamera.dll

Just to be clear, This doesn’t really need to be done this since how to use the existing DLL is now documented ,but for the *nix people they can recreate with CyAPi or libusb.

Loaded up VC2017,  created a DLL project, set it to MBCS added the CyUsb3 files to the project, turned off Precompiled headers for CyAPi.cpp  , added stdint.h and crtdbg.h to the stdafx.h , also need SetupAPI.lib for CyUsb3 added that to stdafx.h as well. Currently its set to be an X86 since the Neoden software is all 32 bit.

Using the information from the USB VideoCapture driver and filling out the basic functions that the DLL exports.

__declspec ( dllexport ) BOOL _cdecl img_capture ( int which_camera );

__declspec ( dllexport ) int _cdecl img_init();

__declspec ( dllexport ) BOOL _cdecl img_led ( int which_camera, int16_t mode );

__declspec ( dllexport ) int _cdecl img_read ( int which_camera, unsigned char * pFrameBuffer, int BytesToRead, int ms);

__declspec ( dllexport ) int _cdecl img_readAsy ( int which_camera, unsigned char * pFrameBuffer, int BytesToRead, int ms);

__declspec ( dllexport ) int _cdecl img_reset ( int which_camera );

__declspec ( dllexport ) BOOL _cdecl img_set_exp ( int which_camera, int16_t exposure );

__declspec ( dllexport ) BOOL _cdecl img_set_gain ( int which_camera, int16_t gain );

__declspec ( dllexport ) BOOL _cdecl img_set_lt ( int which_camera, int16_t a2, int16_t a3 );

__declspec ( dllexport ) BOOL _cdecl img_set_wh ( int which_camera, int16_t w, int16_t h );

They’re a little rough at the moment but they will be fixed up as work progresses

Forgot to add the .def so thats just (although the code is using the __declspec(dllexport)

EXPORTS

img_capture = img_capture @1

img_init @2

img_led @3

img_read @4

img_readAsy @5

img_reset @6

img_set_exp @7

img_set_gain @8

img_set_lt @9

img_set_wh @10

Next step is setting up the USB Device, so add CyUSB.h to stdafx.h and add a couple of CCyUSNDevice to init the USB

So lets enumerate the USB devices that match ours first. Also at this point I realise its easier to run this as an EXE so i change the type from DLL to EXE in the VC options, and add a WInMain

int WINAPI WinMain (
        HINSTANCE hInstance,    
        HINSTANCE hPrevInstance,
        LPSTR lpCmdLine,        
        int nCmdShow      

)

{
        return 0;

}

Now i can just run as an EXE don’t need a host to test.

Enumerating the USB

Ok lets enumerate the USB devices we want

// Create the CyUSBDevice

CCyUSBDevice* USBNeodenCamera = new CCyUSBDevice ( 0, CYUSBDRV_GUID, true );

int n = USBNeodenCamera->DeviceCount();

// for all Cypress devices found

for ( int i = 0; i < n; i++ ) {

    USBNeodenCamera->Open ( i );

    // Is it the Neoden?
        if ( USBNeodenCamera->VendorID != 0x52CB ) {
            continue;
        }

     std::string model = narrow ( std::wstring ( USBNeodenCamera->Product ) );
        std::string serial = narrow ( std::wstring ( USBNeodenCamera->SerialNumber ) );

    _RPT5 ( _CRT_WARN, “DID %04x:%04x, %s, %s, %s,%s\n”,
                USBNeodenCamera->VendorID, USBNeodenCamera->ProductID,
                USBNeodenCamera->DeviceName, USBNeodenCamera->DevPath,
                model.c_str(), serial.c_str()
              );

}

delete USBNeodenCamera;

Then test.

DID 52cb:52cb, B0001 Camera Shibz, \\?\usb#vid_52cb&pid_52cb#1&2d12bed1&0&0000#{ae18aa60-7f6a-11d4-97dd-00010229b959}, B0001 Camera Shibz,

DID 52cb:52cb, H0001 Camera Shibz, \\?\usb#vid_52cb&pid_52cb#1&2d12bed1&0&0001#{ae18aa60-7f6a-11d4-97dd-00010229b959}, H0001 Camera Shibz,

Perfect, both cameras found. the 0000 and 0001 are up and down cameras . both use the same driver

Since this is a simple DLL and that it is known that both cameras exist , that are always two. That does reduce the complexity a bit, so it can be noted either by the device index or init a global to point to each of the cameras when we find them. Shibz is slang for Shibuya, Tokyo not sure if there is another clue there or not. The names of the cameras do differ slightly with a H and B

After the enumeration, I figure that the img_led function is probably easiest to implement to start off with, poking through the original dll i see another IOCTL

0x220044 which decodes as Function 0x11, referring to the Cyioctl.h 0x11 is IOCTL_ADAPT_ABORT_PIPE, which aborts any current EndPoint transactions, a purge if you will, it’s another one of the CyApis functions

RetVal = (DeviceIoControl(hDevice,IOCTL_ADAPT_ABORT_PIPE,&Address,sizeof(UCHAR),NULL,0,&dwBytes,&ov)!=0);

So now we’ll need an EndPoint, time to dig some more. Lets pop up USBDeview on the machine

Wall of text time. Figure out the EndPoints

First Camera


    =========================== USB Port1 ===========================

Connection Status        : 0x01 (Device is connected)
Port Chain               : 5-1
PortAttributes           : 0x00000002 (Shared USB2)

      ======================== USB Device ========================

        +++++++++++++++++ Device Information ++++++++++++++++++
Device Description       : Camera Neoden Tech.
Device Path              : \\?\usb#vid_52cb&pid_52cb#5&1783ac8f&0&1#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
Device ID                : USB\VID_52CB&PID_52CB\5&1783AC8F&0&1
Hardware IDs             : USB\Vid_52cb&Pid_52cb&Rev_0001 USB\Vid_52cb&Pid_52cb
Driver KeyName           : {36FC9E60-C465-11CF-8056-444553540000}\0014 (GUID_DEVCLASS_USB)
Driver                   : System32\Drivers\CYUSB3.sys (Version: 1.2.3.10  Date: 2014-09-18)
Driver Inf               : C:\WINDOWS\inf\oem1.inf
Legacy BusType           : PNPBus
Class                    : USB
Class GUID               : {36FC9E60-C465-11CF-8056-444553540000} (GUID_DEVCLASS_USB)
Interface GUID           : {a5dcbf10-6530-11d2-901f-00c04fb951ed} (GUID_DEVINTERFACE_USB_DEVICE)
Service                  : CYUSB3
Enumerator               : USB
Location Info            : B0001 Camera Shibz
Manufacturer Info        : Cypress
Capabilities             : 0x84 (Removable, SurpriseRemovalOK)
Status                   : 0x0180600A (DN_DRIVER_LOADED, DN_STARTED, DN_DISABLEABLE, DN_REMOVABLE, DN_NT_ENUMERATOR, DN_NT_DRIVER)
Problem Code             : 0
Address                  : 1
Power State              : D0 (supported: D0, D3, wake from D0, wake from D3)

        +++++++++++++++++ Registry USB Flags +++++++++++++++++
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\UsbFlags
 GlobalDisableSerNumGen  : REG_BINARY 01
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\UsbFlags\52CB52CB0001
 osvc                    : REG_BINARY 00 00

        ---------------- Connection Information ---------------
Connection Index         : 0x01 (1)
Connection Status        : 0x01 (DeviceConnected)
Current Config Value     : 0x01
Device Address           : 0x01 (1)
Is Hub                   : 0x00 (no)
Number Of Open Pipes     : 0x01 (1)
Device Bus Speed         : 0x02 (High-Speed)
Pipe0ScheduleOffset      : 0x00 (0)
Data (HexDump)           : 01 00 00 00 12 01 00 01 00 00 00 40 CB 52 CB 52   ...........@.R.R
                           01 00 01 02 00 01 01 02 00 01 00 01 00 00 00 01   ................
                           00 00 00 07 05 82 02 00 02 00 00 00 00 00         ..............

    ---------------------- Device Descriptor ----------------------
bLength                  : 0x12 (18 bytes)
bDescriptorType          : 0x01 (Device Descriptor)
bcdUSB                   : 0x100 (USB Version 1.00)
bDeviceClass             : 0x00 (defined by the interface descriptors)
bDeviceSubClass          : 0x00
bDeviceProtocol          : 0x00
bMaxPacketSize0          : 0x40 (64 bytes)
idVendor                 : 0x52CB
idProduct                : 0x52CB
bcdDevice                : 0x0001
iManufacturer            : 0x01 (String Descriptor 1)
 Language 0x0409         : "Neoden HangZhou"
iProduct                 : 0x02 (String Descriptor 2)
 Language 0x0409         : "B0001 Camera Shibz"
iSerialNumber            : 0x00 (No String Descriptor)
bNumConfigurations       : 0x01 (1 Configuration)
Data (HexDump)           : 12 01 00 01 00 00 00 40 CB 52 CB 52 01 00 01 02   .......@.R.R....
                           00 01                                             ..

    ------------------ Configuration Descriptor -------------------
bLength                  : 0x09 (9 bytes)
bDescriptorType          : 0x02 (Configuration Descriptor)
wTotalLength             : 0x0019 (25 bytes)
bNumInterfaces           : 0x01 (1 Interface)
bConfigurationValue      : 0x01 (Configuration 1)
iConfiguration           : 0x00 (No String Descriptor)
bmAttributes             : 0x80
 D7: Bus Powered         : 0x01 (yes)
 D6: Self Powered        : 0x00 (no)
 D5: Remote Wakeup       : 0x00 (no)
 D4..0: Reserved, set 0  : 0x00
MaxPower                 : 0x32 (100 mA)
Data (HexDump)           : 09 02 19 00 01 01 00 80 32 09 04 00 00 01 FF 00   ........2.......
                           00 00 07 05 82 02 00 02 00                        .........

        ---------------- Interface Descriptor -----------------
bLength                  : 0x09 (9 bytes)
bDescriptorType          : 0x04 (Interface Descriptor)
bInterfaceNumber         : 0x00
bAlternateSetting        : 0x00
bNumEndpoints            : 0x01 (1 Endpoint)
bInterfaceClass          : 0xFF (Vendor Specific)
bInterfaceSubClass       : 0x00
bInterfaceProtocol       : 0x00
iInterface               : 0x00 (No String Descriptor)
Data (HexDump)           : 09 04 00 00 01 FF 00 00 00                        .........

        ----------------- Endpoint Descriptor -----------------
bLength                  : 0x07 (7 bytes)
bDescriptorType          : 0x05 (Endpoint Descriptor)
bEndpointAddress         : 0x82 (Direction=IN EndpointID=2)
bmAttributes             : 0x02 (TransferType=Bulk)
wMaxPacketSize           : 0x0200 (max 512 bytes)
bInterval                : 0x00 (never NAKs)
Data (HexDump)           : 07 05 82 02 00 02 00                              .......

      -------------------- String Descriptors -------------------
             ------ String Descriptor 0 ------
bLength                  : 0x04 (4 bytes)
bDescriptorType          : 0x03 (String Descriptor)
Language ID[0]           : 0x0409 (English - United States)
Data (HexDump)           : 04 03 09 04                                       ....
             ------ String Descriptor 1 ------
bLength                  : 0x20 (32 bytes)
bDescriptorType          : 0x03 (String Descriptor)
Language 0x0409          : "Neoden HangZhou"
Data (HexDump)           : 20 03 4E 00 65 00 6F 00 64 00 65 00 6E 00 20 00    .N.e.o.d.e.n. .
                           48 00 61 00 6E 00 67 00 5A 00 68 00 6F 00 75 00   H.a.n.g.Z.h.o.u.
             ------ String Descriptor 2 ------
bLength                  : 0x26 (38 bytes)
bDescriptorType          : 0x03 (String Descriptor)
Language 0x0409          : "B0001 Camera Shibz"
Data (HexDump)           : 26 03 42 00 30 00 30 00 30 00 31 00 20 00 43 00   &.B.0.0.0.1. .C.
                           61 00 6D 00 65 00 72 00 61 00 20 00 53 00 68 00   a.m.e.r.a. .S.h.
                           69 00 62 00 7A 00                                 i.b.z.

Second camera



    =========================== USB Port8 ===========================

Connection Status        : 0x01 (Device is connected)
Port Chain               : 5-8
PortAttributes           : 0x00000002 (Shared USB2)

      ======================== USB Device ========================

        +++++++++++++++++ Device Information ++++++++++++++++++
Device Description       : Camera Neoden Tech.
Device Path              : \\?\usb#vid_52cb&pid_52cb#5&1783ac8f&0&8#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
Device ID                : USB\VID_52CB&PID_52CB\5&1783AC8F&0&8
Hardware IDs             : USB\Vid_52cb&Pid_52cb&Rev_0001 USB\Vid_52cb&Pid_52cb
Driver KeyName           : {36FC9E60-C465-11CF-8056-444553540000}\0015 (GUID_DEVCLASS_USB)
Driver                   : System32\Drivers\CYUSB3.sys (Version: 1.2.3.10  Date: 2014-09-18)
Driver Inf               : C:\WINDOWS\inf\oem114.inf
Legacy BusType           : PNPBus
Class                    : USB
Class GUID               : {36FC9E60-C465-11CF-8056-444553540000} (GUID_DEVCLASS_USB)
Interface GUID           : {a5dcbf10-6530-11d2-901f-00c04fb951ed} (GUID_DEVINTERFACE_USB_DEVICE)
Service                  : CYUSB3
Enumerator               : USB
Location Info            : H0001 Camera Shibz
Manufacturer Info        : Cypress
Capabilities             : 0x84 (Removable, SurpriseRemovalOK)
Status                   : 0x0180600A (DN_DRIVER_LOADED, DN_STARTED, DN_DISABLEABLE, DN_REMOVABLE, DN_NT_ENUMERATOR, DN_NT_DRIVER)
Problem Code             : 0
Address                  : 8
Power State              : D0 (supported: D0, D3, wake from D0, wake from D3)

        +++++++++++++++++ Registry USB Flags +++++++++++++++++
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\UsbFlags
 GlobalDisableSerNumGen  : REG_BINARY 01
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\UsbFlags\52CB52CB0001
 osvc                    : REG_BINARY 00 00

        ---------------- Connection Information ---------------
Connection Index         : 0x08 (8)
Connection Status        : 0x01 (DeviceConnected)
Current Config Value     : 0x01
Device Address           : 0x04 (4)
Is Hub                   : 0x00 (no)
Number Of Open Pipes     : 0x01 (1)
Device Bus Speed         : 0x02 (High-Speed)
Pipe0ScheduleOffset      : 0x00 (0)
Data (HexDump)           : 08 00 00 00 12 01 00 01 00 00 00 40 CB 52 CB 52   ...........@.R.R
                           01 00 01 02 00 01 01 02 00 04 00 01 00 00 00 01   ................
                           00 00 00 07 05 82 02 00 02 00 00 00 00 00         ..............

    ---------------------- Device Descriptor ----------------------
bLength                  : 0x12 (18 bytes)
bDescriptorType          : 0x01 (Device Descriptor)
bcdUSB                   : 0x100 (USB Version 1.00)
bDeviceClass             : 0x00 (defined by the interface descriptors)
bDeviceSubClass          : 0x00
bDeviceProtocol          : 0x00
bMaxPacketSize0          : 0x40 (64 bytes)
idVendor                 : 0x52CB
idProduct                : 0x52CB
bcdDevice                : 0x0001
iManufacturer            : 0x01 (String Descriptor 1)
 Language 0x0409         : "Neoden HangZhou"
iProduct                 : 0x02 (String Descriptor 2)
 Language 0x0409         : "H0001 Camera Shibz"
iSerialNumber            : 0x00 (No String Descriptor)
bNumConfigurations       : 0x01 (1 Configuration)
Data (HexDump)           : 12 01 00 01 00 00 00 40 CB 52 CB 52 01 00 01 02   .......@.R.R....
                           00 01                                             ..

    ------------------ Configuration Descriptor -------------------
bLength                  : 0x09 (9 bytes)
bDescriptorType          : 0x02 (Configuration Descriptor)
wTotalLength             : 0x0019 (25 bytes)
bNumInterfaces           : 0x01 (1 Interface)
bConfigurationValue      : 0x01 (Configuration 1)
iConfiguration           : 0x00 (No String Descriptor)
bmAttributes             : 0x80
 D7: Bus Powered         : 0x01 (yes)
 D6: Self Powered        : 0x00 (no)
 D5: Remote Wakeup       : 0x00 (no)
 D4..0: Reserved, set 0  : 0x00
MaxPower                 : 0x32 (100 mA)
Data (HexDump)           : 09 02 19 00 01 01 00 80 32 09 04 00 00 01 FF 00   ........2.......
                           00 00 07 05 82 02 00 02 00                        .........

        ---------------- Interface Descriptor -----------------
bLength                  : 0x09 (9 bytes)
bDescriptorType          : 0x04 (Interface Descriptor)
bInterfaceNumber         : 0x00
bAlternateSetting        : 0x00
bNumEndpoints            : 0x01 (1 Endpoint)
bInterfaceClass          : 0xFF (Vendor Specific)
bInterfaceSubClass       : 0x00
bInterfaceProtocol       : 0x00
iInterface               : 0x00 (No String Descriptor)
Data (HexDump)           : 09 04 00 00 01 FF 00 00 00                        .........

        ----------------- Endpoint Descriptor -----------------
bLength                  : 0x07 (7 bytes)
bDescriptorType          : 0x05 (Endpoint Descriptor)
bEndpointAddress         : 0x82 (Direction=IN EndpointID=2)
bmAttributes             : 0x02 (TransferType=Bulk)
wMaxPacketSize           : 0x0200 (max 512 bytes)
bInterval                : 0x00 (never NAKs)
Data (HexDump)           : 07 05 82 02 00 02 00                              .......

      -------------------- String Descriptors -------------------
             ------ String Descriptor 0 ------
bLength                  : 0x04 (4 bytes)
bDescriptorType          : 0x03 (String Descriptor)
Language ID[0]           : 0x0409 (English - United States)
Data (HexDump)           : 04 03 09 04                                       ....
             ------ String Descriptor 1 ------
bLength                  : 0x20 (32 bytes)
bDescriptorType          : 0x03 (String Descriptor)
Language 0x0409          : "Neoden HangZhou"
Data (HexDump)           : 20 03 4E 00 65 00 6F 00 64 00 65 00 6E 00 20 00    .N.e.o.d.e.n. .
                           48 00 61 00 6E 00 67 00 5A 00 68 00 6F 00 75 00   H.a.n.g.Z.h.o.u.
             ------ String Descriptor 2 ------
bLength                  : 0x26 (38 bytes)
bDescriptorType          : 0x03 (String Descriptor)
Language 0x0409          : "H0001 Camera Shibz"
Data (HexDump)           : 26 03 48 00 30 00 30 00 30 00 31 00 20 00 43 00   &.H.0.0.0.1. .C.
                           61 00 6D 00 65 00 72 00 61 00 20 00 53 00 68 00   a.m.e.r.a. .S.h.
                           69 00 62 00 7A 00                                 i.b.z.

Thats a lot of data, but there are the endpoints, scanning with a quick cheat with the CyUsb API just running around the endpoints til it was valid, but that doesn’t give me the data as fast as this does.

Sniffing DeviceIO using a simplified Test App

Next thing to try is sniffing the DeviceIO going to the cameras, but there will likely be a lot of noise and the Neoden test app doesn’t seem to succesfully grab any data, so lets make a quick app, new project  in VS2017 again. Making a console app

image

Added the videoinput/opencv libs and headers for video capture, and create a simple image display

#include <iostream>

#include <stdint.h>

#include <stdlib.h>

#include “opencv2/highgui/highgui.hpp”

#include “opencv2/imgproc/imgproc.hpp”

#include “opencv2/opencv.hpp”

#pragma comment(lib,”comctl32.lib”)

#pragma comment(lib,”gdi32.lib”)

#pragma comment(lib,”vfw32.lib”)

using namespace cv;

using namespace std;

int main()

{
        int g_width = 1024, g_height = 1024;

    Mat cameraFrame ( g_width, g_height, CV_8UC3, Scalar ( 10, 100, 150 ) );;
        cv::Mat greyMat;
        flip ( cameraFrame, cameraFrame, 1 );
        cv::cvtColor ( cameraFrame, greyMat, CV_BGR2GRAY );
        Size size ( g_width, g_height );
        resize ( greyMat, greyMat, size );

    imshow ( “camera”, greyMat );

    cv::waitKey ( 0 )

}

Creating a .lib and .h for a DLL

Next step is linking to the NeodenCamera.dll which we can do with LoadLibrary or create a header and .lib so lets do that.

dumpbin /exports NeodenCamera.dll

take these

   1    0 00001840 img_capture
     2    1 000017D0 img_init
     3    2 00001870 img_led
     4    3 000019F0 img_read
     5    4 00001A30 img_readAsy
     6    5 000018B0 img_reset
     7    6 000018F0 img_set_exp
     8    7 00001930 img_set_gain
     9    8 00001970 img_set_lt
    10   9 000019B0 img_set_wh

convert it to

LIBRARY NeodenCamera.dll

EXPORTS
            img_capture
            img_init
            img_led
            img_read
            img_readAsy
            img_reset
            img_set_exp
            img_set_gain
            img_set_lt
            img_set_wh

save it as NeodenCamera.def, from VC folder use  (run vcvars32.bat)

lib /def:NeodenCamera.def /out:NeodenCamera.lib /machine:x86

This makes the .lib and .exp file, we just need the .LIB, add it to your project.

Finally we need the prototypes for the function, IDA or such can help with that. Typically these sorts of DLLs  use stack based argument passing as in standard C so they’re easier to figure out. if its C++ and they’re using mangled names you can demangle for at least the basics. There are lots of tutorials on IDA so rather than focus there i’ll just kickstart with what is known so far

bool _cdecl img_capture ( int which_camera );

int _cdecl img_init();

bool _cdecl img_led ( int which_camera, int16_t mode );

int _cdecl img_read ( int which_camera, unsigned char * pFrameBuffer, int BytesToRead, int dwMilliseconds);

int _cdecl img_readAsy ( int which_camera, unsigned char * pFrameBuffer, int BytesReturned, int dwMilliseconds);

int _cdecl img_reset ( int which_camera );

bool _cdecl img_set_exp ( int which_camera, int16_t exposure );

bool _cdecl img_set_gain ( int which_camera, int16_t gain );

bool _cdecl img_set_lt ( int which_camera, int16_t a2, int16_t a3 );

bool _cdecl img_set_wh ( int which_camera, int16_t w, int16_t h );

these are C linkage, the may have to be set to extern “C”  depending on the setup

Starting here, calling img_init() and noting the results . Running the app you should get a warning about the NeodenCamera.dll missing which is expected, so add the dll to the path or to the same folder as the test exe

calling img_init()

bool ret = img_init();

printf ( “%d = img_ini\nt”, ret );

yields

DLL 1->DLL_PROCESS_ATTACH

USB╔Φ▒╕┴¼╜╙╩²[2]!
╔Φ▒╕┤·║┼[0]  = B0001 Camera Shibz
╔Φ▒╕┤·║┼[1]  = H0001 Camera Shibz

1 = img_init

so far so good, but since that  that is a function with no parameters and hard to mess up. but it means the .def and .lib worked

Lets try to read the image from camera 5 (UP)

unsigned char buffer[ 1024 * 1024  ];

int main()

{
       int g_width = 1024, g_height = 1024;
       DWORD bytesToRead= g_width * g_height;
       bool ret = img_init();
       memset ( &buffer[0], 0xaa, sizeof ( buffer ) );
       printf ( “%d = img_init\n”, ret );
       // camera indices are to be 1 and 5(looking up)
       ret = img_readAsy ( 5, &buffer[0], bytesToRead, 1000 );
       printf ( “%d = img_readAsy, %d\n”, ret, bytesToRead);
       Mat cameraFrame ( Size ( g_width, g_height ), CV_8UC1, buffer, 1024 );
       imshow ( “camera”, cameraFrame );

      cv::waitKey ( 0 );

}

image

Great there is is the UP camera, I also think we have to init the width and height first. but after a run of the neoden software first and the settings stayed.

Lets try the down camera next, switch the 5 to 1

image

Neat.

Next add the width and height set, which seems to work

int main()

{
        int g_width = 1024, g_height = 1024;
        DWORD bytesToRead = g_width * g_height;

    memset ( &buffer[0], 0xaa, sizeof ( buffer ) );

    bool ret = img_init();
      printf ( “%d = img_init\n”, ret );

    ret = img_set_wh ( 1, g_width, g_height );
      printf ( “%d = img_set_wh(1,w,h)\n”, ret );

    ret = img_set_wh ( 5, g_width, g_height );
      printf ( “%d = img_set_wh(5,w,h)\n”, ret );

    // camera indices are to be 1 and 5(looking up)
      ret = img_readAsy ( 1, &buffer[0], bytesToRead, 1000 );
      printf ( “%d = img_readAsy, %d\n”, ret, bytesToRead );

    Mat cameraFrame ( Size ( g_width, g_height ), CV_8UC1, buffer, 1024 );

    imshow ( “camera”, cameraFrame );

    cv::waitKey ( 0 );

}

Thats enough to get started, the simpler the better.

Sniffing DeviceIO

The PNP software starts up with

img_init
img_set_exp 1 25
img_set_gain) 1 8
img_set_lt 1 624 496
img_set_wh 1 32 32
img_init
img_set_exp 5 100
img_set_lt 5 128 0
img_set_wh 5 1024 1024

Previously it wasn’t noted that the software calls init twice. also odd size for camera 1

From the test app

img_set_exp) 1 25
img_set_gain) 1 8
img_set_lt) 1 128 0
img_set_wh) 1 1024 1024
img_readAsy) 1 0x30d0030 1048576 1000 ms

Which all matches

Another great tool for spying on API’s is API Monitor using the X32 version and turning off everything but DeviceIoControl then running the test appl, clearing out the trace until the image appears, then switching the camera we see the following

Use the binoculars to bring up the search after loading X32 version of API monitor

 image

Search for DeviceIOControl (case insensitive) and then check them off in the API Filter

image

Then either use monitor new process or enable the capture of newly launched processes and it’ll ask to monitor the new process

image

Now run Neoden4 software or the TestApp previously made ( add published to github link)

image

Then API Monitor shows you the filtered functions we’re interested in so press the various manual test functions to see the results, it’ll also show the buffers being passed and the parameters.

DeviceIoControl ( 0x000000b8, 0x220024, 0x00c00020, 524326, 0x00c00020, 524326, 0x0018fc34, 0x0018fc98 )

FALSE    997 = Overlapped I/O operation is in progress.     0.0000831

DeviceIoControl ( 0x000000b8, 0x220024, 0x03840020, 524326, 0x03840020, 524326, 0x0018fc34, 0x0018fcac )

FALSE    997 = Overlapped I/O operation is in progress.     0.0000560



DeviceIoControl ( 0x000000b8, 0x00220020 , 0x002ca348, 44, 0x002ca348, 44, 0x0018fbd0, 0x0018fbf8 )
FALSE    997 = Overlapped I/O operation is in progress.     0.0000057

for img_led(5,0)

DeviceIoControl ( 0x000000bc, 0x00220020  , 0x0310a348, 44, 0x0310a348, 44, 0x0063fc54, 0x0063fc7c )

FALSE    997 = Overlapped I/O operation is in progress.     0.0000213
0000  40 b2 00 00 00 00 06 00 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 26 00 00 00 0022  06 00 00 00 b2 01 00 00 00 00        

for img_led(5,1)

DeviceIoControl ( 0x000000bc,0x00220020    , 0x0310a348, 44, 0x0310a348, 44, 0x0063fc54, 0x0063fc7c )

FALSE    997 = Overlapped I/O operation is in progress.     0.0000239
0000  40 b2 01 00 00 00 06 00 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 26 00 00 00 0022  06 00 00 00 b2 01 01 00 00 00       

Marked the differences, i’m not sure if img_led is used, or if i’m using it right anyway these camera boards tend to have a cypress FX CY7C68013 and either an fpga (if they’re UVC or need a lot of processing) or straight hooked to the GPIOs to do triggers, LED flash etc. So that could be where they are or it could be attached from the RS232 and the motor control board.

The Flash LEDs are not controlled via the camera dll, i think they’re a throwback to another version since we know the LEDs are connected to the head motor control board and controlled via CAN bus from the primary motor control board. They are included in my test harness from for serial control.



As a note be aware of the EP0 control since that also controls how the FX is programmed and erased


For capture of up(5)

DeviceIoControl ( 0x000000bc, 0x00220024, 0x03840020, 524326, 0x03840020, 524326, 0x0063fb84, 0x0063fbe8 )


FALSE    997 = Overlapped I/O operation is in progress.     0.0000777


DeviceIoControl ( 0x000000bc, 0x00220024, 0x03960020, 524326, 0x03960020, 524326, 0x0063fb84, 0x0063fbfc )
    FALSE    997 = Overlapped I/O operation is in progress.     0.0000560


Partial Buffer


0000  00 00 00 00 00 00 00 00 00 00 00 00 00 82 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 26 00 00 00
0022  00 00 08 00 01 01 01 01 01 01 01 01 01 01 01 01 00 01 01 01 01 01 00 01 00 01 01 01 01 01 01 01 01 01
0044  01 01 01 01 01 01 00 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 00 01 01 01 01 01 01 01
0066  01 01 01 01 01 01 01 01 00 01 01 01 01 01 01 01 00 01 01 01 00 01 00 01 01 01 01 01 01 01 01 01 01 01
0088  00 01 01 01 01 01 01 01 01 01 01 01 01 01 00 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 00 01
00aa  01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01
00cc  01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 00 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01
00ee  01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 00 01 01 01 01 01 00 01 00 01 00 01

DeviceIoControl ( 0x000000bc, 0x00220020  , 0x0310a348, 44, 0x0310a348, 44, 0x0063fb20, 0x0063fb48 ) 


FALSE    997 = Overlapped I/O operation is in progress.     0.0000063


0000  40 b1 00 00 00 00 06 00 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 26 00 00 00
0022  06 00 00 00 b1 fb 00 00 00 00
       

       

The image buffer looks about right, since it is mostly a black image, refreshing it would give a similar result (camera senors are a good source of noise)

0x00220024  is 0x22, IOCTL_ADAPT_SEND_NON_EP0_TRANSFER. buffered, any file


This IOCTL command is used to request Bulk, Interrupt or Isochronous data transfers across corresponding USB device endpoints.


0x00220020 is IOCTL_ADAPT_SEND_EP0_CONTROL_TRANSFER. buffered, any file


This command sends a control request to the default Control endpoint, endpoint zero.

So all making sense so far, back to the driver.


Decoding the above for IOCTL_ADAPT_SEND_EP0_CONTROL_TRANSFER

union {
       struct {
       UCHAR Recipient:5;
       UCHAR Type:2;
       UCHAR Direction:1;
       } bmRequest;
       UCHAR bmReq;


};


bmRequest.Recipient = 0; // Device


bmRequest.Type = 2; // Vendor


bmRequest.Direction = 1; // IN command (from Device to Host)


int iXmitBufSize = sizeof(SINGLE_TRANSFER) + bufLen; // The size of the two-part  structure


UCHAR *pXmitBuf = new UCHAR[iXmitBufSize]; // Allocate the memory


ZeroMemory(pXmitBuf, iXmitBufSize);


PSINGLE_TRANSFER pTransfer = (PSINGLE_TRANSFER)pXmitBuf; // The SINGLE_TRANSFER comes
    first


pTransfer->SetupPacket.bmRequest = bmReq;


pTransfer->SetupPacket.bRequest = ReqCode;


pTransfer->SetupPacket.wValue = Value;


pTransfer->SetupPacket.wIndex = Index;


pTransfer->SetupPacket.wLength = bufLen;


pTransfer->SetupPacket.ulTimeOut = TimeOut / 1000;


pTransfer->Reserved = 0;


pTransfer->ucEndpointAddress = 0x00; // Control pipe


pTransfer->IsoPacketLength = 0;


pTransfer->BufferOffset = sizeof (SINGLE_TRANSFER);


pTransfer->BufferLength = bufLen;

and IOCTL_ADAPT_SEND_NON_EP0_TRANSFER

PUCHAR CCyBulkEndPoint::BeginDataXfer(PCHAR buf, LONG bufLen, OVERLAPPED *ov)


{


if (hDevice == INVALID_HANDLE_VALUE) return NULL;


int iXmitBufSize = sizeof (SINGLE_TRANSFER) + bufLen;


PUCHAR pXmitBuf = new UCHAR[iXmitBufSize];


ZeroMemory(pXmitBuf, iXmitBufSize);


PSINGLE_TRANSFER pTransfer = (PSINGLE_TRANSFER)pXmitBuf;


pTransfer->Reserved = 0;


pTransfer->ucEndpointAddress = Address;


pTransfer->IsoPacketLength = 0;


pTransfer->BufferOffset = sizeof (SINGLE_TRANSFER);


pTransfer->BufferLength = bufLen;


// Copy buf  into pXmitBuf


UCHAR *ptr = (PUCHAR) pTransfer + pTransfer->BufferOffset;


memcpy(ptr, buf, bufLen);


DWORD dwReturnBytes;


DeviceIoControl(hDevice, IOCTL_ADAPT_SEND_NON_EP0_TRANSFER,
                  pXmitBuf, iXmitBufSize,
                  pXmitBuf, iXmitBufSize,
                  &dwReturnBytes, ov);


return  pXmitBuf;


}

So for the image transfer it looks like it is using

CCyUSBEndPoint::BeginBufferedXfer

So we can just follow the call chain CyApi and follow it up to

bool CCyControlEndPoint::Write(PUCHAR buf, LONG &bufLen)

Backtracking a tad


On a personal note I have used IDA for a while but sometimes will forget to use it with all the tools it has and just manually convert all the offsets. But there are useful tools like FLIRT and people have tried to put together libraries of FLIRT databases, but there are a lot of libraries to do that for.


Luckily for us Cypress publishes the PDB file for CyUsb3.sys so if you load it into IDA it’ll create all the structures that are in the PDB for you. After loading it and letting IDA figure itself all out, dump the typeinfo to an IDC, then reload the app(NeodenCamera.dll) that uses the CyUSB3.sys which is likely different and execute the IDC you just made, that will transfer the structs to the new RE project but not the offsets etc will will be wrong. Now keep a copy of the IDC somewhere you’ll forget all about for next time


As an example the _SINGLE_TRANSFER structure which has a union (also mapped)

00000000 _SINGLE_TRANSFER struc ; (sizeof=0x26, align=0x2, mappedto_45)

00000000 ___u0           $902C784530A83C47F9612DF5432F758B ?

0000000C reserved        db ?

0000000D ucEndpointAddress db ?

0000000E NtStatus        dd ?

00000012 UsbdStatus      dd ?

00000016 IsoPacketOffset dd ?

0000001A IsoPacketLength dd ?

0000001E BufferOffset    dd ?

00000022 BufferLength    dd ?

00000026 _SINGLE_TRANSFER ends

Typing  that in manually the first time, after forgetting the union… then redoing it, then thought there must be a better way, and of course there is, and this is one of those ways, but no doubt next time it’ll just get brute forced again

Now apply that struct to the code

SINGLE_TRANSFER *__thiscall CCyIsocEndPoint__BeginBufferedXfer(int this, void *inputBuffer, size_t bytesToWrite, LPOVERLAPPED lpOverlapped)


{
      int v4; // ebx
      int blocks; // esi
      unsigned int v7; // edi
      _SINGLE_TRANSFER *pTransfer; // esi
      DWORD dwReturnBytes; // [esp+Ch] [ebp-8h]
      DWORD iXmitBufSize; // [esp+10h] [ebp-4h]


  v4 = this;
      if ( *(_DWORD *)(this + 4) == -1 )            // HDEVICE
        return 0;
      blocks = (signed int)bytesToWrite / *(unsigned __int16 *)(this + 12);
      if ( (signed int)bytesToWrite % *(unsigned __int16 *)(this + 12) )
        ++blocks;
      if ( !blocks )
        return 0;
      v7 = 8 * blocks;
      iXmitBufSize = 8 * blocks + bytesToWrite + 38;
      pTransfer = (_SINGLE_TRANSFER *)operator new(iXmitBufSize);
      memset(pTransfer, 0, iXmitBufSize);
      pTransfer->reserved = 0;
      pTransfer->ucEndpointAddress = *(_BYTE *)(v4 + 10);
      pTransfer->IsoPacketLength = v7;
      v7 += 38;
      pTransfer->BufferOffset = v7;
      pTransfer->IsoPacketOffset = 38;              // SINGLE_TRANSFER
      pTransfer->BufferLength = bytesToWrite;
      memcpy(&pTransfer->SetupPacket.bmReqType._bf0 + v7, inputBuffer, bytesToWrite);
      dwReturnBytes = 0;
      DeviceIoControl(
        *(HANDLE *)(v4 + 4),
        0x220024u,
        pTransfer,
        iXmitBufSize,
        pTransfer,
        iXmitBufSize,
        &dwReturnBytes,
        lpOverlapped);                              // IOCTL_ADAPT_SEND_NON_EP0_TRANSFER
      *(_DWORD *)(v4 + 32) = GetLastError();
      return pTransfer;


}

The Cypress SDK contains the source for that function, let us see how it compares

PUCHAR CCyUSBEndPoint::BeginBufferedXfer ( PUCHAR buf, LONG bufLen, OVERLAPPED *ov )

{
        if ( hDevice == INVALID_HANDLE_VALUE ) { return NULL; }

    int iXmitBufSize = sizeof ( SINGLE_TRANSFER ) + bufLen;
        PUCHAR pXmitBuf = new UCHAR[iXmitBufSize];
        ZeroMemory ( pXmitBuf, iXmitBufSize );

    PSINGLE_TRANSFER pTransfer = ( PSINGLE_TRANSFER ) pXmitBuf;
        pTransfer->ucEndpointAddress = Address;
        pTransfer->IsoPacketLength = 0;
        pTransfer->BufferOffset = sizeof ( SINGLE_TRANSFER );
        pTransfer->BufferLength = bufLen;

    // Copy buf into pXmitBuf
        UCHAR *ptr = ( PUCHAR ) pTransfer + pTransfer->BufferOffset;
        memcpy ( ptr, buf, bufLen );

    DWORD dwReturnBytes;

    DeviceIoControl ( hDevice,
                          IOCTL_ADAPT_SEND_NON_EP0_TRANSFER,
                          pXmitBuf,
                          iXmitBufSize,
                          pXmitBuf,
                          iXmitBufSize,
                          &dwReturnBytes,
                          ov );

    UsbdStatus = pTransfer->UsbdStatus;
        NtStatus   = pTransfer->NtStatus;

    LastError = GetLastError();
        return pXmitBuf;

}

The compiler has optimised some things away and made some little tricks but it is close.




Reading an I2C EEPROM

It’s definitely been Cypress week around here, with the RGB Blinky Ball (which has a cypress psoc4) we wante d to ship a programmer, the Cypress one is $90 so not that. A bit banging version for the FX2LP ecists so we hooked that one up and it worked great. There is a a fork here PSOC

These boards are dirt cheap and are great for either reading logic at high speed or writing it.

image

It also comes with an AT24C128 eeprom for boot strapping the FX2LP.

image

Recalling that the Microchip PICCKIT can read EEPROMs with, and just happen to have one the desk for the One Key Keyboard.

It needs this software.
http://ww1.microchip.com/downloads/en/DeviceDoc/PICkit3%20Programmer%20Application%20v3.10.zip

The I2C Address of the chip is set as

A0 = 1 A1 = 0 A2 = 0 = 0xA3


image

Next you have to mod the PICKIT

> 24LC I2C bus devices:         Bus Speed-                 400kHz with Tools -> Fast Programming checked                 100kHz with Tools -> Fast Programming unchecked

        NOTE: Bus pullups are required for all
              programming operations.  400kHz requires
              2k Ohm pullups.

        NOTE: The I2C (24LC) Serial EEPROM devices require the following PICkit 3
              hardware changes to work properly:

              Remove TR3 from the PICkit 3.
              Remove R50 from the PICkit 3.

        Connections for 24LC devices
        ---------------------------------------
        PICkit 3 Pin             24LC Device Pin (DIP)
        (2) Vdd                  8 Vcc
        (3) GND                  4 Vss
        (5) PGC                  6 SCL (driven as push-pull)
        (6) PGM(LVP)             5 SDA (requires pullup)
                                 7 WP - disabled (GND)
                                 1, 2, 3 Ax pins
                                    Connect to Vdd or GND per
                                    datasheet and to set address

url

Load this “OS” into it

image

image

Hook up power, ground , sda and scl ,  Make sure before you add power you start a capture, since it only sends and clocks when transmitting you can trigger and capture or just run a few second capture that’ll give you time to switch on the power.

The PicKit 3 is meant to be modded , trying it first. remove the R50 4.7K, and the 5.0V Zener at TR3. The software continually resets the 3.3V to 5V so watch for that.  This is a clone of the Pickit 3 the lower right MELF is the diode TR3

imageT

This didn’t seem to work very well and the data just reapeated, and was very sparse i’ve seen enough code in binary to know its not right. Figured it was worth a shot.

So next to try is the Ginkgo box which is a USB to CAN/I2C/SPI/GPIO test box it is useful for all sorts of things and originally it was used to do automated testing on some SPI control systems. But also a no go I suspect it was the CAN firmware but the doc’s are less than clear about it (it was. you can reflash any firmware on to it and even though the label says I2C/SPI/CAN it has internal checks note: add to list  of next projects also there is an internal jumper if you set it you can recover the bad flash)

So lets just use a Logic Analyser, coincedentally we made a mini logic analyser that was based on the same 68013A chip at NSL called the AnnaLogic which is plenty capable to do this, but lets use\ the Logic Pro 16 instead.

image

It pops out immediatately and now it is easy to recognise the fimrmware since all the fx2lp bin’s I’ve seen to date have had a 222222 sequence before the last few bytes., next is to convert the trace from the Saleae to a bin and load it into my FX2 dev board. The text file export has the time stap and address, followed by the data byte, just extract that column after removing the setup code.

Time [s],Packet ID,Address,Data,Read/Write,ACK/NAK
0.343118400000000,,0xA1,,Read,NAK
0.343347040000000,1,0xA3,0x32,Read,NAK
0.343575520000000,2,0xA2,0x00,Write,ACK
0.343681120000000,2,0xA2,0x00,Write,ACK
0.343909600000000,3,0xA3,0xC2,Read,ACK

C2 is the start of the EEPROM

The logs show that after a tiny pause from the firmware download to the FX2LP it starts sending out on I2C address 0xBA

0.725436960000000,3,0xA3,0x00,Read,NAK

time gap

352480000000,4,0xBA,0x01,Write,ACK
0.738503360000000,4,0xBA,0x00,Write,ACK
0.738653760000000,4,0xBA,0x8C,Write,ACK

Noting that is likely a camera on the I2C addres 0xBA, so searching for I2C Camera 0xBA and the first hit is the MT9M001

Array Format (5:4): 1,280H x 1,024V
Monochrome sensor
Slave address 0xBA (SADDR=0)

It is pretty close to what we expected.  This camera is very popular for interfacing to the FX2LP. There are apparently different versions of the Neoden4 with a different camera, and that vendor uses similar sensors too. But we don’t really need the model yet.

On that MT9M001 sensor 0x8C is a reserved address, so not sure if its a good match

Next task on the list is to sniff the CAN busses, and because of the previous automotive woirk NSLLabs posses every CAN adapter known to humanity, so that should be straightforward..

…more later…

light.co L16 camera, fixing the lumen software

 

i picked up one of those L16 cameras from light.co  it arrived last week , charged it, plugged it in and installed the windows version of the lumen(beta) software.

took a few pictures, downloaded them  and clicked to open, and the software crashed, over and over. Opened a support “email/website” to their “award winning support” waited a few days, nothing, submitted again, still nothing, so since i’d like to see what the camera did i figured i’d take a look and see what was causing the crash..

i’d taken a few different pics over the days, but could only download them and not view them.

so this is what it did, clicking view always crashed it.

 

since it’s the 5th, i got the camera mid last week and wanted to see the results, and still nada from their support.

i clicked ‘Debug’, which opened up my visual studio, then took a look at the a crash, it is in the ceres solver, invalid instruction, so i see where this is going and i take a look at the disassembly and sure enough it is AVX(sandy bridge)  instruction which my older i7 CPU doesn’t support (contrary to the claims of the light.co website)

Ceres Solver is an open source library

http://ceres-solver.org/

i took a look at their version, a quick strings in the ceres.dll

c:\Users\srv-build\jenkins\workspace\CI-multi-platform-v2\CI_Projects\CI-WIN\3rdparty\ceres-solver-1.12.0\internal\ceres\trust_region_preprocessor.cc

the code is here

http://ceres-solver.org/installation.html

but since i’m using the windows version i grabbed this build,  (using submodules to get glog)

https://github.com/tbennun/ceres-windows

it needs Eigen

 http://eigen.tuxfamily.org/index.php?title=Main_Page

grabbed 3.3.4 of Eigen, extracted it to the same folder as the ceres-2015.sln file  and then renamed it to eigen

next opened the ceres-2015.sln in visual studio 2015, selected release and x64 build, built it and copied the ceres.dll to the lumen.exe folder.  after making a backup of the old non working one.

re ran it , opened the files again, and picked a picture, tested the focus .. and it worked.

and now i can view the images for the camera

the software is really, really slow, so i’d imagine they just switched on whatever optimisation settings they could without realising what that really meant, dat jenkins build server.

as for the camera itself, sorry to say but so far the software is indicative of the hardware, but i probably need some time to get used to it.

 

now i wonder where my award is

cheers,

 

 

 

 

since its being asked for, https://github.com/charlie-x/lumen-ceres-dll i put it here. its built exactly as i described it above

ChipCon SmartRF04 EB firmware reflashing

A while ago I picked up a CC1110 from eBay for cheap as we were looking at doing an IM-ME clone, Eventually got around back to the C11xx work.

ChipCon were bought by TI in about 2006, for the CC family, see the clue? the devboard I have is actually from the pre TI days, so I was amazed when in 2016 I downloaded the TI Smart RF software it connected and saw the board. The two dev boards also actually worked so lucked out on eBay again…

On plugging in it asked to update the firmware, which I did. . After that I could use Smart RF to control the board, but not from the board itself, no LCD display. So I went off and looked for old versions of Smart RF and older HEX files, no luck , OEMs always want to get rid of that stuff, they should make it all available but it is a support headache from the help vampyres, and TI’s forums had a few similar questions that mostly dead ended.

So I looked into the host chip, it’s a SI 8051F32, so needs a yet another debugger interface, I looked it up and see its USB Debug Adapter for about $30, not bad. It has an unusual shape and I recall having one, so I went and dug around and found it in a box, I actually had took it out of a box, looking through various devboards, looked at my hand and there it was, great…

So downloaded SI’s production programmer, still works, doesn’t read back only verify and program.. (is the verify download and compare, checksum or compare on chip) saw eclipse mentioned/java and went back to look for a different programming tool, SI have a utility dll from programming, read docs same deal only a memory downloader, check the memory map 0x0 – 0x3FFF with after 0x3DFF reserved.  Once I connect the USB Debug Adapter to the new software it want’s to update the firmware, here we go again, flashes and its OK after a USB reset.

Connected to the board, downloaded it to a “log file” which is literally a dump of the memory in either dec(with leading zeros)  or hex, and a cr\lf… quick sed script add commas to the dec, load it into an array, oh yeah, leading 0’s so it thinks its base 8 octal. redo it in hex output, add 0x, and end with , read it into an array write it out.

convert the newer TI supplied hex that didn’t work files to bin from the new 40 version, compare the two against the one I just read. they’re similar at the start, but it is shifted..  I realise the bootloader is there as as a separate hex, it’s loaded from 0x0 – 0x0800, so I chop that off and wrote out the file. they are now only slight differences, 0xFF where 0x00 is in the hex, this is likely a skip in the hex (unlikely since the hex file showed a single section) or the default erase byte .

Had I been watching the hex2bin output I’d have seen the start address of the firmware as 0x800, so at least independently verified, had too many windows open..

Lowest address  = 00000800
Highest address = 00003BDB
Pad Byte        = 0
8-bit Checksum = BE

So now all have to do is reflash the board with the code from the second board that I did not upgrade, so convert the .bin to .hex, either without the bootloader and reflash it with the TI tools, or clone one to the other and reflash with SI adapter.

Reflashed the whole thing in the end, which reverted it to 28 build.. Still no display when the EM is connected….so either flash on the EM module (which still works in Smart RF) mode or  theres something wrong with the EM module…

 

and yes reflashed the cc1110 and its back to life!  Probably should have tried that first Smile

 

SI USB chip docs

http://www.keil.com/dd/docs/datashts/silabs/c8051f32x.pdf

SI flash util

http://www.silabs.com/Support%20Documents/TechnicalDocs/an117.pdf

 

SmartRF flash programmer to redo the CC1110

http://www.ti.com/tool/flash-programmer

Reusing the RPM indicator

i wanted an rpm indicator on the mill, i have a handheld one but since it had one originally i thought why not use it.

since we’re on a vfd don’t need this lot anymore
apply bandsaw


i’m using a 12VDC wall wart to power it. you can use the old 120V if you want, just chop it off at the other side of the transformer or use the whole board.
Remove D1 (this converts the 12VAC from the transformer to 12VDC, you could cut all the way past L1, but this way you get reverse polarity diode protection and some filtering/smoothing.

attach a power plug.

add 12VDC

tadah!
now i just have to find where i put the optical pickup….