Adafruit USB + Serial RGB Backlight Character LCD Backpack VBScript API

System Requirements:

  • Adafruit USB Serial RGB Backlight Character LCD Backpack

The Problem:

I recently needed a fast, cheap and modular way to output small amounts of information from a headless Windows 7 appliance, something that didn’t require a large amount of electrical engineering work or assembly while at the same time working over USB.

I accidentally stumbled upon a product by Adafruit, who seem to be tightly aligned with the Raspberry Pi/Arduino, however USB is USB which means that making it cooperate with Win32 wouldn’t be particularly challenging.

Being the lazy person that I am, I never much care for the idea of having to repeatedly type out control commands through a command line parser to get what I want and quite frankly, getting a NT Command Prompt to reliably pass anything out to a Serial Port is more or less a futile exercise unless you are using Plink or replace the shell entirely with something more robust.

My solution was quite simple, create a O-O VBScript API for running it via CScript that can in turn be called from the NT command line where required.

Buy Adafruit USB Serial RGB Backlight Character LCD Backpack, and other Adafruit components or & help support this site.

 

More Info

This API does what I require it to do. I have implemented all of the main command syntax from the Adafruit documentation (links below). It is classful and self contained. As long as you can create a FileSystemObject and can find the COM Port, you can make use of it.

[Update 25/04/2019] You can now obtain this code directly from Github.

View: AdafruitUsbSerial on GitHub

' AdafruitUsbSerial Application Programming Interface v1.0.4

' © C:Amie | www.c-amie.co.uk 1996 - 2014

' Not for commercial reproduction without the express permission of the author

' No warranty is offered or implied as a result of downloading or using this APIClass AdafruitUsbSerialprivate m_ForReadingprivate m_SCREEN_OFF

private m_SCREEN_ON

private m_AUTO_SCROLL_ON

private m_AUTO_SCROLL_OFF

private m_CLEAR_SCREEN

private m_SET_STARTUP_SPLASH

private m_SET_CURSOR_POSITION

private m_SET_CURSOR_HOME

private m_SET_CURSOR_BACK

private m_SET_CURSOR_FORWARD

private m_SET_UNDERLINE_ON

private m_SET_UNDERLINE_OFF

private m_SET_BLINK_ON

private m_SET_BLINK_OFF

private m_SET_RGB

private m_SET_CONTRAST

private m_SET_BRIGHTNESSprivate m_iPortNumber

private m_byteCharacterLength

private m_bolDebug

private m_bolAutoScroll

private m_bolUnderlineCursor

private m_bolBlinkCursorprivate m_fso private

sub Class_Initialize
m_ForReading = 1
m_SCREEN_OFF = chr(254) & chr(70)
m_SCREEN_ON = chr(254) & chr(66)
m_AUTO_SCROLL_ON = chr(254) & chr(81)
m_AUTO_SCROLL_OFF = chr(254) & chr(82)
m_CLEAR_SCREEN = chr(254) & chr(88)
m_SET_STARTUP_SPLASH = chr(254) & chr(64)
m_SET_CURSOR_POSITION = chr(254) & chr(71)
m_SET_CURSOR_HOME = chr(254) & chr(72)
m_SET_CURSOR_BACK = chr(254) & chr(76)
m_SET_CURSOR_FORWARD = chr(254) & chr(77)
m_SET_UNDERLINE_ON = chr(254) & chr(74)
m_SET_UNDERLINE_OFF = chr(254) & chr(75)
m_SET_BLINK_ON = chr(254) & chr(83)
m_SET_BLINK_OFF = chr(254) & chr(84)
m_SET_RGB = chr(254) & chr(208)
m_SET_CONTRAST = chr(254) & chr(80)
m_SET_BRIGHTNESS = chr(254) & chr(153)

m_iPortNumber = 1
m_byteCharacterLength = 32
m_bolDebug = false
m_bolAutoScroll = true
m_bolUnderlineCursor = false
m_bolBlinkCursor = false

set m_fso = CreateObject("Scripting.FileSystemObject")
end sub

private sub Class_Terminate
set m_fso = nothing
end sub

' PROPERTIES
public property get PortNumber
PortNumber = m_iPortNumber
end property

public property let PortNumber(ByRef iIn)
m_iPortNumber = iIn
end property

public property get CharacterLength
CharacterLength = m_byteCharacterLength
end property

public property let CharacterLength(ByRef byteIn)
m_byteCharacterLength = byteIn
end property

public property get Debug()
Debug = m_bolDebug
end property

public property let Debug(ByRef bolIn)
m_bolDebug = bolIn
end property

public property get AutoScroll()
AutoScroll = m_bolAutoScroll
end property

public property let AutoScroll(ByRef bolIn)
if (bolIn) then
me.write(m_AUTO_SCROLL_ON)
else
me.write(m_AUTO_SCROLL_OFF)
end if
m_bolAutoScroll = bolIn
end property

public property get Underline()
Underline = m_bolUnderlineCursor
end property

public property let Underline(ByRef bolIn)
if (bolIn) then
me.write(m_SET_UNDERLINE_ON)
else
me.write(m_SET_UNDERLINE_OFF)
end if
m_bolUnderlineCursor = bolIn
end property

public property get Blink()
Blink = m_bolBlinkCursor
end property

public property let Blink(ByRef bolIn)
if (bolIn) then
me.write(m_SET_BLINK_ON)
else
me.write(m_SET_BLINK_OFF)
end if
m_bolBlinkCursor = bolIn
end property

' METHODS
public sub clearScreen()
me.write(m_CLEAR_SCREEN)
end sub public sub screenOn()
me.write(m_SCREEN_ON)
end sub

public sub screenOff()
me.write(m_SCREEN_OFF)
end sub

public sub changeSplashScreen(ByVal strIn)
strIn = Left(strIn, m_byteCharacters)
' Force it to be exactly 32 characters by padding
do while (Len(strIn) < m_byteCharacters)
strIn = (strIn & " ")
loop
me.clearScreen()
me.home()
me.write(m_SET_STARTUP_SPLASH)
me.write(strIn)
end sub

public sub backlight(ByRef byteR, ByRef byteG, ByRef byteB)
me.write(m_SET_RGB)
me.write(chr(byteR))
me.write(chr(byteG))
me.write(chr(byteB))
end sub

' Valid Range 0 - 255. Values between 180 and 220 are suggested
public sub contrast(ByRef byteIn)
me.write(m_SET_CONTRAST)
me.write(chr(byteIn))
end sub

' Valid Range 0 - 255.
public sub brightness(ByRef byteIn)
me.write(m_SET_BRIGHTNESS)
me.write(chr(byteIn))
end sub

public sub setCursorPosition(ByRef iX, ByRef iY)
me.write(m_SET_CURSOR_POSITION)
me.write(chr(iX))
me.write(chr(iY))
end sub

public sub home()
me.write(m_SET_CURSOR_HOME)
end sub

public sub back()
me.write(m_SET_CURSOR_BACK)
end sub

public sub goBack(ByRef iIn)
Dim i
for i = 1 to iIn
me.write(m_SET_CURSOR_BACK)
next
end sub

public sub forward()
me.write(m_SET_CURSOR_FORWARD)
end sub

public sub goForward(ByRef iIn)
Dim i
for i = 1 to iIn
me.write(m_SET_CURSOR_FORWARD)
next
end sub

public sub delete()
me.write(m_SET_CURSOR_BACK)
me.write(" ")
me.write(m_SET_CURSOR_BACK)
end sub

public sub write(ByRef strIn)
Dim serialWriter
if (me.Debug) then
wscript.echo strIn
end if
set serialWriter = m_fso.CreateTextFile("COM" & m_iPortNumber & ":",True)
serialWriter.Write(strIn)
serialWriter.Close()
set serialWriter = nothing
end sub

public sub teletype(ByRef strIn, ByRef iDelayMs)
Dim i
Dim iLen
iLen = Len(strIn)
for i = 1 to iLen
me.write(Mid(strIn, i, 1))
WScript.Sleep(iDelayMs)
next
end sub

public function testComPort(ByRef byteNumber)
Dim serialWriter
if (me.Debug) then
wscript.echo "Attempting communications with COM" & byteNumber
end if
On Error Resume Next
set serialWriter = m_fso.CreateTextFile("COM" & byteNumber & ":",True)
serialWriter.Write("Initialising...")
serialWriter.Close()
set serialWriter = nothing
if (err.number = 0) then
testComPort = true
else
testComPort = false
end if
On Error Goto 0
end function

End Class

Copy it into your VBScript project file or into a dedicated class file and include it. Once it is in scope, the example below shows a general usage pattern for the main API.

It is recommended that all projects include and set the first 8 lines shown below, just so that you can ensure that you are tuning your project in the way that you want it. The remainder of the code shows examples of how to use the functions.

As a result of the USB driver allocating COM ports in a fairly dynamic way under Win32, you cannot expect to hard code your COM Port inside the project – particularly if the physical USB port that the backpack is connected to changes. Consequently, you can use testComPort() to attempt to locate the correct port as shown below. The function will terminate on the first port that it finds with an active serial output line available, if you have multiple active serial ports available on your project, the function may find the wrong port.

Finally, Adafruit recommends – at a minimum -adding a 10 millisecond delay between each command, which is not shown below. You should use WScript.Sleep(10) to achieve similar under VBScript. If you don’t, everything shown below with the exception of the executing of the Teletype macro will occur in well under a second.

Dim usbSerial

set usbSerial = new AdafruitUsbSerial

    usbSerial.PortNumber = 3              ' Set to COM3

    usbSerial.Debug = true                ' Inputs will be written back to WScript

    usbSerial.CharacterLength = 32        ' 32 is the default

    usbSerial.AutoScroll = true           ' Enable/Disable Auto Scroll

    usbSerial.Underline = true            ' Enable Cursor Underline

    usbSerial.Blink = true                ' Enable Cursor Blink' Find the first live COM Port if you don't know where it is

Dim iComPort

for iComPort = 1 to 30

    if (usbSerial.testComPort(iComPort)) then

        usbSerial.PortNumber = iComPort

        Exit For

    end if

next' Write Text

usbSerial.write("some text")' Write on both lines

usbSerial.write("line one" & vblf & "line two")' Clear the screen

usbSerial.clearScreen()' Screen Off

usbSerial.screenOff()

' Screen On
usbSerial.screenOn()

' Change the Backlight Colour
call usbSerial.backlight(255, 0, 255) ' Sets the RGB values (Fuchsia in this case)

' Set the screen brightness
usbSerial.brightness(180) ' 0 - 255

' Set the screen contrast
usbSerial.contrast(180) ' 0 - 255

' Set the Cursor Position
usbSerial.home()                  ' Moves to character 1, row 1
usbSerial.back()                  ' Moves the cursor back 1 character
usbSerial.forward()               ' Moves the cursor forward 1 character
usbSerial.goBack(5)               ' Steps the cursor back 5 characters
usbSerial.goForward(6)            ' Progresses the cursor forward 6 characters
usbSerial.setCursorPosition(5,1)  ' Sets the cursor to Character 5 on Row 1
usbSerial.delete()                ' Moves the cursor back 1 and clears the previous character

' Teletype (Macro)
call usbSerial.teletype("this will teletype out", 100) ' Write the text, with a 0.1 second character delay

' Change the Adafruit Splash Screen (Auto truncated/padded to usbSerial.CharacterLength)
usbSerial.changeSplashScreen("This is a splash screen message")

' Clean up and free resources
set usbSerial = nothing

Thanks to a structured API it is as easy as that!

View: Adafruit: Command Reference
View: Adafruit: Sending Text

See Also

View: Adafruit

Buy Adafruit USB Serial RGB Backlight Character LCD Backpack, and other Adafruit components from Amazon & help support this site:

Microsoft Surface Size Comparison Chart

System Requirements:

  • Microsoft Surface RT, Surface Pro
  • Microsoft Surface 2, Surface 2 Pro
  • Microsoft Surface 3, Surface Pro 3
  • Microsoft Surface Pro 4
  • Microsoft Surface Pro (2017), 5th Gen

The Problem:

I couldn’t find a good relative size comparison guide for the Microsoft Surface, so I created one.

The image below is relatively sized in millimetres at 72dpi and covers both Generation One devices, both Generation Two and the Surface 3 / Surface Pro 3 Third Generation devices.

Please do not hot-link! Please link to http://www.c-amie.co.uk/qlink/?id=127

Or if you prefer it tabulated

Length (mm)
Width (mm)
Depth (mm)
Weight (g)
Surface RT
275
172
9.3
680
Surface Pro
275
173
13.5
910
Surface 2
275
173
8.9
676
Surface Pro 2
275
173
13.5
900
Surface Pro 3
290
201
9.1
800
Surface 3
267
187
8.7
622
Surface Pro 4
292
201
8.5
785
Surface Pro (2017)
292
201
8.5
785

Updated to include the Surface 3 01/04/2015

Updated to include the Surface 5 and 5 (2017) 29/04/2018

The Samsung Universal Print Driver causes the printer to print documents without white space, including paragraph whitespace, breaks, page margins and header footer margins

System Requirements:

  • Samsung CLP-300N
  • Samsung Universal Print Driver

The Problem:

It’s a Samsung printer driver problem reprise! For those not familiar with the self evident truth that Samsung have never quite managed to develop a competency in writing printer drivers, you may with to familiarise yourself with the last inexplicable printer driver problem that I found and just how unhelpful Samsung themselves were about my trying to report it to them.

The latest native driver for the CLP-300N is a release from the 20th October 2009. It is also possible to get it working via Windows Update using this driver under Windows 7. However, for the last year I have been experiencing a problem: Microsoft Word will not print unless it is in greyscale.

I’ve tried uninstalling the print drivers, altering the default and enforced printer settings – all of which are set to colour – and the instant you leave the printer configuration dialogue in Word 2010, the driver resets back to greyscale. Inexplicable and a problem that is not present in other applications; including other parts of Office 2010, Adobe reader etc.

My short-term fix was to print to PDF and then print from Adobe Reader directly to the CLP-300N. However this afternoon I was annoyed enough by it to fix it.

It’s obviously the driver. So I took it off again and ever hopeful attempted to see if there was a new Samsung driver – there wasn’t. Yet I did notice that like most printer manufacturers, the vogue at present is to offer universal PCL print drivers, for which the CLP-300N downloads page on Samsung.com did offer a 2013-03-09 (2013-03-15 signed) driver reportedly of version 2.50.02.00.03.

Fantastic! It fixed the problem and Word 2010 now printed in colour for the first time in a year without even a reboot. Except…

If we pretend that this was my document

<header>

Dear Sir,

Bla, bla, bla.

Bla, bla, bla.

Bla, bla, bla.

Bla, bla, bla.

Yours faithfully,

C:Amie

<footer>
<bottom of A4 page>

What I actually got was

<header>
Dear Sir,
Bla, bla, bla.
Bla, bla, bla.
Bla, bla, bla.
Bla, bla, bla.
Yours faithfully,
C:Amie
<footer><bottom of A4 page>

Oops.

 

The Fix

There isn’t any point in going into troubleshooting details on this, I spent about 20 minutes wasting paper on it and the driver doesn’t work. I’m not the only person to see this either.

View: Ubuntu Forums

The simple fix is to go back to the Samsung downloads page for printers and don’t select a model number. Head directly to the Samsung Universal Print driver page and download the latest release (2013-09-23 of version 2.50.04.00.08 as of writing).

This fixes the problem.

After last time, I’m not going to waste my phone bill or breath attempting to report it to Samsung.

If you want my advice, don’t buy their printers. If you don’t want my advice, don’t follow it.

Printing to an O’Neil Route Printer RP2000-8000 Line Mode (COM) Printer using embedded Visual Basic (eVB) on Windows CE and Windows Mobile

System Requirements:

  • Microsoft embedded Visual Basic
  • Windows CE 2.11, 3.0, 4.0, 4.1, 4.2, 5.0
  • Pocket PC 2000, 2002
  • Windows Mobile 2003, 2003SE, 5.0

The Problem:

Although this article specifically addresses a problem that I had between a Symbol PDT8046 and the O’Neil Route Printer RP2000-8000, the issue and resolution should be applicable to any serial (COM) port based line printer.

If you have a raw ascii line printer, you can check to see if the printer works by echoing directly from the stdout into the COM port. Under DOS or the Windows command shell on the PC or Pocket DOS on Windows CE/Windows Mobile issue the following commands

mode com1: 9600,n,8,1,r
mode lpt1:=com1:echo I am a fish > lpt1

This will send I am a fish and a line feed to COM1 at 9600bps with no parity. Note that Pocket DOS doesn’t support the “,r” parameter.

So now that you have ascertained that the printer actually works, when you use the Microsoft Communications Control (Microsoft Comm Control) for embedded Visual Basic (eVB) to send the same data, why is it that nothing happens what so ever.

More Info

Let us look at the VB

‘ Set to COM1, 9600bps, no Parity.
‘ Enable DTR Flow Control, disable RTS Flow Control, disable handshaking
msComm.CommPort = 1
msComm.Settings = “9600,n,8,1,r”
msComm.DTREnable = truemsComm.RTSEnable = false
msComm.Handshaking = 0

‘ Attempt to get control of the COM port if it isn’t available
On Error Resume Next
if (msComm.PortOpen) then
msComm.PortOpen = false
end if
On Error Goto 0

‘ Open the port, Send the message, shutdown the port
msComm.PortOpen = true
msComm.Output = “I am a fish” & Chr(13) & Chr(10)
msComm.PortOpen = false

This is a logical flow of execution and basically what on the surface the DOS version is doing. Yet the message I am a fish will never arrive at the printer. In fact, the printer will not even acknowledge the presence of the message.

The Fix

This code is in fact completely logical, and is right (for the O’Neil RP2000-8000 anyway). However after some frustration and experimentation I discovered that there are two important pieces of the puzzle missing, one of them strongly recommended and the other very much mandatory.

Issue 1: Sleeping Printer

The O’Neil printer will go into standby mode after a couple of minutes of idle time so as to conserve power. If the printer is in sleep mode and you send data to it, it probably will not be awake to receive information and send it to the print head fast enough; best case nothing happens and worst case less than half of your message gets printed.

The O’Neil hardware manual has a very short section on this. In order to wake the printer up (or ensure that it is awake) you need to send a string of ASCII NULL characters (0x00) at the printer. For ONeil line mode printers operating at 9600bps you need to send this string 6+ times. For 38,400bps printers you should send this 24+ times. In our VB world that means:

msComm.Output = Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0)

7 times is a charm, however the manual also states that you should wait 150ms before sending data to the printer. See the next section on implementing Sleep().

If you repeat your test now, your printer should at least wake up even if nothing else happens

Issue 2: Speed = Distance / Time

I could wax lyrical on this, but Ill keep it brief.

There are a couple of buffers at play in the form of the OS and the printer, the embedded operating system and the eVB runtime processor. All of which cause delays. It follows something similar to this

  1. User Sends Print Request
  2. eVB Runtime Parses the command and sends it to the native code of the machine
  3. Operating System queues it in RAM as it gets processed through and send on its merry way
  4. Operating System queues it in the COM port transmit (TX) buffer
  5. Printer receives the message and queues it in the print buffer (512 bytes I *think* on the RP2000-8000)
  6. Printer processes it forward to the print head and the line at the front of the queue is printed

eVB does not care about steps 3 – 6, neither is it aware of them. As far as eVB is concerned, it has finished with the msComm.Output() command as soon as it has received an OK from the operating system that the requests has been through the processor and the data is in a queue.

Referring back to our original code

‘ Set to COM1, 9600bps, no Parity.
‘ Enable DTR Flow Control, disable RTS Flow Control, disable handshaking
msComm.CommPort = 1
msComm.Settings = “9600,n,8,1,r”
msComm.DTREnable = truemsComm.RTSEnable = false
msComm.Handshaking = 0

‘ Attempt to get control of the COM port if it isn’t available
On Error Resume Next
if (msComm.PortOpen) then
msComm.PortOpen = false
end if
On Error Goto 0

‘ Open the port, Send the message, shutdown the port
msComm.PortOpen = true
msComm.Output = “I am a fish” & Chr(13) & Chr(10)      <—— My work as VB is now over
msComm.PortOpen = false                                    <—— Shutdown the COM port

What is happening? eVB thinks that everything has been delivered and shuts down the COM port before the data has left the sending device and is safely received into the printers hardware buffer.

What is the solution? Well, you can be as eloquent or in-eloquent as you like. You can do a byte by byte check of data into the VB transmit buffer, ensuring that this has gone before you progress through the output stream. Alternatively if you are feeling lazy you could use the threading model and implement a fixed length sleep action. If however you are feeling somewhere between the two extremes you can try and weight the sleep timer based upon message length. Something along the lines of

delay (ms) = Absolute(((Length(Message) / ((Printer Bit Rate * 0.50) / 8 )) * 1000))

A weighting of 0.66 is assumed so that the link speed and buffer writing can be running at only 1/2 of line speed. Division by 8 is used to convert from Bits Per Second to Bytes Per Second. Multiplication by 1000 converts the value to milliseconds.

or for the RP2000-8000 with message “I am a fish <cr><lf>” [13 characters]

delay (ms) = Abs(13 / ((9600 * 0.66) / 8) * 1000)

or 21 ms

Onto this you may want to add a standard value that reflects the time the message takes to leave the operating system onto the wire. 50 – 100ms should do.

In order to use the thread sleep function in eVB you have to import the export from the C++ corelib as follows

Declare Sub Sleep Lib “Coredll.dll” (ByVal dwMilliseconds As Integer)

Dim strMessage
strMessage = “I am a fish” & Chr(13) & Chr(10)

‘ Set to COM1, 9600bps, no Parity.
‘ Enable DTR Flow Control, disable RTS Flow Control, disable handshaking
msComm.CommPort = 1
msComm.Settings = “9600,n,8,1,r”
msComm.DTREnable = true

msComm.RTSEnable = false
msComm.Handshaking = 0

‘ Attempt to get control of the COM port if it isn’t available
On Error Resume Next
if (msComm.PortOpen) then
msComm.PortOpen = false
end if
On Error Goto 0

‘ Open the port, Send the message, shutdown the port
msComm.PortOpen = true
msComm.Output = strMessage
call Sleep((Abs(((Len(strMessage) / ((9600 * 0.50) / 8)) * 1000)) + 50))
msComm.PortOpen = false

Note: This code is highly summarised it will not work directly as a copy/paste, you need to modularise it and create the msComm object.

With the sleep function in place and the correct tuning you should now find that the data gets to the printer before eVB closes the COM port.

Remember that you need to implement the function of Sleep(150) after sending the wake-up NULL string to the printer.