Although both the NCE interface and the Mirrorbow IO board are USB they actually work as virtual serial ports. This has advantages and disadvantages! It makes the programming a lot simpler though.
Although I could do so (and have done in the past) I didn't really fancy writing any low-level control routines and there is nothing as standard within VBA. A hunt on the 'net found what looked to be a suitable set of routines which made calls directly to the Windows API (Application Program Interface). At first these seemed ideal, especially as they were free! Unfortunately I found that once you'd opened the virtual serial port you had to keep reading from or writing to it or it would lock up. Although there are ways round this it wasn't ideal.
Further searches came up with various commercial solutions (costing $150 upwards), the possibility of buying the full version of Visual Basic to get the Microsoft Comm Control (MSCOMM) or using the free alternative NETComm. I'm all for anything that's free so NETComm it was!
After some trial and error this has solved the control problems and I can now send commands to the DCC controller and read the inputs from the IO board without problems. The last problem to be resolved was I found the NCE USB board doesn't like two commands in very quick succession so it needs a short delay inserting.
Once NETComm is installed the first step is to add the relevant ActiveX control to an Access form. I've created a separate form for this, and actually added two controls so far - one for the NCE port and one for the IO board. At this stage you need to know which serial port numbers your boards have been configured to, you can do this via the hardware manager on Control Panel - System. You can then set this on the Control properties. You don't need to worry too much about setting the other properties as they can (and will) be changed later but giving the controls sensible names might help. Note that if you move the boards to a different USB port they may well renumber themselves. Obviously the form has to be open in Form View for the controls to work.
I've written a series of subroutines to simplify matters. Please note that these work for me, for what I'm doing, and with my set-up! I can't say whether they'll work for anyone else. How they work should be fairly self evident but I've added comments where this may help.
It's worth noting at this stage that the NCE uses hex codes whilst the IO board is all alphanumeric.
Although not essential it's useful to be able to quickly create command strings. The following help with this. There's also a routine to pause the program execution for a specific number of seconds which I found on the 'net - useful in the testing phase.
Function H2B(H As String) As String Rem Function to convert string of 2 digit Hex pairs to string of Bytes S = "" For I = 1 To Len(H) / 2 NH = Mid(H, 2 * (I - 1) + 1, 1) If NH >= "A" Then T = 10 + Asc(NH) - Asc("A") Else T = Asc(NH) - Asc("0") End If NH = Mid(H, 2 * (I - 1) + 2, 1) If NH >= "A" Then T = 16 * T + 10 + Asc(NH) - Asc("A") Else T = 16 * T + Asc(NH) - Asc("0") End If S = S & Chr(T) Next I H2B = S End Function
Function D2B(D As Long) As String Rem Function to create String from Decimal Rem High Byte First when done D2B = Chr(D) End Function
Function Pause(ByVal pSng_Secs As Single) 'Wait for the number of seconds given by pSng_Secs Dim lSng_Start As Single Dim lSng_End As Single
On Error GoTo Err_Pause lSng_Start = Timer lSng_End = Timer + pSng_Secs Do While Timer < lSng_End '' Correction if the timer moves over to a new day (midnight) '' 86400-num of secs in a day If Timer < lSng_Start Then lSng_End = lSng_End - 86400 Loop Err_Pause: Exit Function End Function
The following routines are used to initialise the controls and ports and then close them again.
For convenience I named my two controls NCE and INP. They're on a form called Comm. I've used the With statements just to reduce the amount of typing in the code.
Function NCEOpen() Rem Function to initialise Com Port With Forms!Comm.NCE .Settings = "9600,N,8,1" .RThreshold = 1 .SThreshold = 1 .InputMode = 0 If .PortOpen = True Then .PortOpen = False End If If .PortOpen = False Then .PortOpen = True End If If Err Then MsgBox Error$, 48 End With End Function
Function NCEClose() With Forms!Comm.NCE If .PortOpen = True Then .PortOpen = False End If If Err Then MsgBox Error$, 48 End With End Function
Function INPInit() Rem Function to initialise IO Board for Input Dim S As String With Forms!Comm.INP .Settings = "115200,N,8,1" .RThreshold = 1 .SThreshold = 1 .InputMode = 0 If .PortOpen = True Then .PortOpen = False End If If .PortOpen = False Then 'check if the serial port is open .PortOpen = True 'check if the serial port is open End If If Err Then MsgBox Error$, 48 'Display error in message box S = "DIR1 FF" 'Sets Port 1 to all input .Output = S S = "DIR2 FF" .Output = S S = "DIR3 FF" .Output = S Pause 1 'Gives the board time to initialise completely S = .InputData If S <> "AAA" Then 'A is the correct response from the DIR command - I've read all 3 in one go MsgBox "Incorrect Response " & S, vbOKOnly, "Warning" End If End With End Function
Function INPClose() With Forms!Comm.INP If .PortOpen = True Then .PortOpen = False End If If Err Then MsgBox Error$, 48 End With End Function
On the control form Ctr each point is represented by two straight lines. One is coloured green to represent the active path and the other yellow to represent the inactive path.
Function SetP(Pt As Integer) Rem Function to switch a point Rem Pt = Point Rem Uses the colour of the line on the control screen to determine current state of point Dim S As String L = "P" & Right("00" & Pt, 2) & "A" If Forms("Ctr").Controls(L).BorderColor = 64000 Then St = False Else St = True End If S = H2B("AD") & D2B(0) & Chr(Pt) & H2B(IIf(St, "03", "04")) & D2B(0) Forms!Comm.NCE.Output = S S = Forms!Comm.NCE.InputData If S <> "!" Then Debug.Print "Invalid Control Response " & S, vbOKOnly, "Warning" End If Forms("Ctr").Controls(L).BorderColor = IIf(St, 64000, 64250) L = "P" & Right("00" & Pt, 2) & "B" Forms("Ctr").Controls(L).BorderColor = IIf(St, 64250, 64000) End Function
There are two commands here. One thing I found is that for the momentum settings on the DCC decoder to work you have to set the speed to 0, not issue the stop command. You then need to issue a stop or you may get some creep and or the motor may continue to try and run.
Function MoveIt(Loc As Long, Dir As String, Spd As Long) Rem Function to set the speed of a loco in Forward or Reverse Dim S As String S = H2B("A2") & D2B(0) & D2B(Loc) & IIf(Dir = "R", H2B("03"), H2B("04")) & D2B(Spd) Forms!Comm.NCE.Output = S S = Forms!Comm.NCE.InputData If S <> "!" Then Debug.Print "Invalid Control Response " & S, vbOKOnly, "Warning" End If End Function
Function StopIt(Loc As Long, Dir As String) Rem Function to send positive Stop command to loco Dim S As String S = H2B("A2") & D2B(0) & D2B(Loc) & IIf(Dir = "R", H2B("05"), H2B("06")) & D2B(0) Forms!Comm.NCE.Output = S S = Forms!Comm.NCE.InputData If S <> "!" Then Debug.Print "Invalid Control Response " & S, vbOKOnly, "Warning" End If End Function
Simple routines to turn the lights on and off on the DMU set. Note that the direction of the lights is taken from the direction of travel. The only complication I had with this is how the functions are encoded to the USB controller - it's "LSB High", i.e. setting bit 4 is decimal 16 not decimal 8 as you would naturally expect. These routines will be made more sophisticated later as they do not remember the state set so when you turn the lights off it also turns off any other functions.
Function LightsOn(Loc As Long) Dim S As String S = H2B("A2") & D2B(0) & D2B(Loc) & H2B("07") & D2B(16) Forms!Comm.NCE.Output = S S = Forms!Comm.NCE.InputData If S <> "!" Then Debug.Print "Invalid Control Response " & S, vbOKOnly, "Warning" End If End Function
Function LightsOff(Loc As Long) Dim S As String S = H2B("A2") & D2B(0) & D2B(Loc) & H2B("07") & D2B(0) Forms!Comm.NCE.Output = S S = Forms!Comm.NCE.InputData If S <> "!" Then Debug.Print "Invalid Control Response " & S, vbOKOnly, "Warning" End If End Function
Obviously I wanted to test things! As I'd got my test layout set up in Setrack, with both a reed switch and block detector wired in, I developed some simple routines to drive a train. These were mostly based around the DMU set. This one is probably the most sophisticated. The decoder is set at short address 18. The set accelerates to a slow speed as it comes out of the bay platform, speeds up a bit as it goes over the points then accelerates to "full" speed. It slows down again to enter the passing loop before stopping. This is then reversed, however whilst all the other movements are timed the final stop is based on the input detection.
Function xx2() For I = 1 To 100 'Loop for multiple runs LightsOn 18 Pause 1 MoveIt 18, "F", 5 'Go forwards at speed 5 Pause 17 MoveIt 18, "F", 10 Pause 2 MoveIt 18, "F", 15 Pause 10 MoveIt 18, "F", 5 Pause 19 MoveIt 18, "F", 0 'Decelerate to a stop Pause 1 StopIt 18, "F" 'Positive stop command Pause 1 LightsOff 18 Pause 5 LightsOn 18 Pause 1 MoveIt 18, "R", 5 Pause 19 MoveIt 18, "R", 15 Pause 10 MoveIt 18, "R", 10 Pause 2 MoveIt 18, "R", 5 With Forms!Comm.INP S = .InputData S = "" While S <> "02" 'Loop until the input detector returns string 02 (detector is on bit 2 of port 1) S = "IN1" .Output = S S = .InputData Wend End With Pause 10 MoveIt 18, "R", 0 Pause 1 LightsOff 18 Pause 1 StopIt 18, "R" Pause 5 Next I End Function
Just a note on this! There is a standard that says a USB port should provide 500mA of current, however many systems (especially laptops) don't comply with this. The IO board in particular could, if all the inputs are used at the same time, need most of this 500mA. I've got my boards plugged in to a powered Belkin hub to ensure this is not a problem.