The following code sends the minimum amount of data to the Maestro, which uses
three ‘servo’ channels to control three ‘output’ channels such that a string of
ShiftBrites and/or ShiftBars change according to the light data you send. Most
of this code is cut-and-paste ready if you follow the simple setup steps.
'SETUP FOR A MINI-MAESTRO (NOT A MICRO-MAESTRO-- AT LEAST NOT WITHOUT ADAPTATION).
Channel 0 as Output. Call it DATA.
Channel 1 as Output. Call it CLOCK.
Channel 2 as Output. Call it LATCH.
Channel 3 as Servo. Call it BLUE. Not really a servo; it is color component data.
Channel 4 as Servo. Call it RED. Not really a servo; it is color component data.
Channel 5 as Servo. Call it GREEN. Not really a servo; it is color component data.
‘Cut and paste this script into the Maestro Script tab, then click
’“Apply Settings”. Similar to the example provided by Pololu. Thanks, folks!
sub SendCLOCK # The control computer and send_bit
# use this for all data shifts.
0 0 8000 0 servo servo # Toggle CLOCK (channel 0). Channel
# 0 to 8000 then to 0. Right->left.
Return
sub SendLATCH # The control computer calls this
# one time after all data shifts. 0 1 8000 1 servo servo # Toggle LATCH (channel 1). Target=1;
# Value=8000. Target=1. Value=0.
quit
Sub SendFront # Send two bits of front matter
0 send_bit # Set DATA (channel 2) to < 6000 (clear)
0 send_bit # "Address" clear for color. DATA=Clear.
quit
sub SetBlue # Color component in 10 bits
3 get_position
SendTenBits # Send ten bits of BLUE
quit
sub SetRed # Color component in 10 bits
4 get_position
SendTenBits # Send ten bits of BLUE
quit
sub SetGreen # Color component in 10 bits
5 get_position
SendTenBits # Send ten bits of BLUE
quit
sub SendTenBits
# User computer put 3 10-bit color component
# values into 3 'servo' channels.
# One is currently top of stack. Send 10 bits.
512 # 512 = ten bit counter/mask. Ten right-shifts.
begin # Start a loop
dup # Duplicate the top value in the stack. First
# 1000000000 then 100000000 then 10000000, etc.
while # If the new top value = 0, jump to loop end.
over # Copy the 'position' value to top of stack.
# Not really servo position but color component.
over # Copy the counter/mask to top pf stack.
# That is the bitmask (512, 256, 128, etc.)
bitwise_and # Boolean AND the top two values in the stack.
# Now the top value in the stack is 0 or is
# 512, 256, 128, etc. 0 or !0.
send_bit # Send 0 for a result of 0 or >= 6000 for !0.
1 shift_right # Right-shift countermask (argument=1) bit.
# Mask/2 and counter less 1. Repeat the loop
repeat # for ten bits until counter=0, then leave loop.
drop # Drop the top stack value, the original 512.
drop # Drop the top stack value, the color.
return
sub send_bit # sends a single bit
if 8000 else 0 endif # If the value in the stack is non-zero,
# then put a value >= 6000 in the stack
# else put < 6000 in the stack (clear).
2 servo # Target=DATA (channel 2). Value =
# clear (<6000) or set (>5999).
SendClock # Toggle CLOCK bit (channel 0).
Return
#Ensure the control mode didn't get messed up by noise
sub ControlMode # The control computer might call this
# once for each light and then
# call SendLATCH before it sends
# Color data.
0 send_bit # This bit does not matter
1 send_bit # the "address" bit - 1 means control mode
0 send_bit # ATB
0 send_bit # ATB
0 send_bit # N/A
1 send_bit # Dot Correction 2
1 send_bit # Dot Correction 2
0 send_bit # Dot Correction 2
0 send_bit # Dot Correction 2
1 send_bit # Dot Correction 2
0 send_bit # Dot Correction 2
0 send_bit # Dot Correction 2
0 send_bit # N/A
0 send_bit # N/A
0 send_bit # N/A
1 send_bit # Dot Correction 1
1 send_bit # Dot Correction 1
1 send_bit # Dot Correction 1
1 send_bit # Dot Correction 1
0 send_bit # Dot Correction 1
0 send_bit # Dot Correction 1
0 send_bit # Dot Correction 1
0 send_bit # N/A
0 send_bit # Clock Mode
0 send_bit # Clock Mode
1 send_bit # Dot Correction 0
1 send_bit # Dot Correction 0
0 send_bit # Dot Correction 0
0 send_bit # Dot Correction 0
1 send_bit # Dot Correction 0
0 send_bit # Dot Correction 0
0 send_bit # Dot Correction 0
quit
Note: We don’t call the last subroutine above in this example, but it may be useful. See the MaceTech
documentation for ShiftBrites for an exceedingly brief explanation of Control Mode, which you may have
to mess with if your ShiftBrites or ShiftBars get serial noise that undesirably blank out the lights.
'SETUP FOR THIS VB.NET EXAMPLE.
'========================================
'The important code is enclosed like this
'========================================
'The rest of the code is provided because it is a) useful as-is in your project; or b) handy for explaining
'the context of the code you may pull out of this example for your project.
'Add a public array named paryMaestroData() as Byte
'Add a public 2-dimension array (making the assumption that you are changing light patterns over
'time) named paryData() as Integer. Populate it with light data so that each light (first dimension) at each
'timeslice (second dimension) uses this 10-bits-per-color-component format when converted to binary:
' 1111111111 1111111111 1111111111
' ---Blue--- ----Red--- ---Green--
'Add a public integer named pintDataTimeslice (this example assumes you'll want to change lights over time)
'Add a public string named pstrCOMCommandPort
'Add a public string named pstrCOMTTLPort
'Add a public boolean named pbooUseHardware
'To your Windows Form:
'Add a COM Port named SerialPort1
'Add a groupbox named grpCOM. In it:
' Add a combobox named cboCOMPort
' Add a combobox named cboTTLPort
' Add a button named btnCOMOkay
'Add a NumericUpDown control named nudLightCount. Minimum = 0; Maximum = lights in your string.
'Add this event code as a handy way to select the USB ports
Private Sub btnCOMOkay_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCOMOkay.Click
Dim L, I, U As Integer
'Test the caption
If btnCOMOkay.Text = "ON" Then
'Change the text to identify the COM comboboxes
grpCOM.Text = "COM (USB) Master Port. Slave Port. Pick the ports (see Start|Control Panel|Hardware and Sound|Devices and Printers[Device Manager]|Ports(COM & LPT) or equivalent), then 'Pick.'"
'Change this button's caption
btnCOMOkay.Text = "Pick"
'Enable and show these controls
cboCOMPort.Enabled = True
cboTTLPort.Enabled = True
cboCOMPort.Visible = True
cboTTLPort.Visible = True
'Create the COM port list
COMList()
ElseIf btnCOMOkay.Text = "Pick" Then
'Disable these controls
cboCOMPort.Enabled = False
cboTTLPort.Enabled = False
'Disable and hide this button
btnCOMOkay.Enabled = False
btnCOMOkay.Visible = False
'Ensure both indices have been set and both or neither is '[None]'
If ((cboCOMPort.SelectedIndex < 1) Or (cboTTLPort.SelectedIndex < 1)) Then
'The user is not using hardware
pbooNoHardware = True
'No changes allowed
grpCOM.Text = "COM (USB) Master Port. Slave Port. Restart to select COM ports. There is no COM Port output now."
'Show the user has selected no COM ports
cboCOMPort.Text = "[None]"
cboTTLPort.Text = "[None]"
pstrCOMCommandPort = "[None]"
pstrCOMTTLPort = "[None]"
'Show certain controls
ControlHide(&H50005) '000 0000 0101 0000 0000 0000 0101
Else
'Remember the COM port names
pstrCOMCommandPort = cboCOMPort.Items(cboCOMPort.SelectedIndex)
pstrCOMTTLPort = cboTTLPort.Items(cboTTLPort.SelectedIndex)
'Show this textbox
txtCOMOut.Enabled = True
txtCOMOut.Visible = True
'Get the width of the COM textbox less a little bit for new entries to prepend the text shown therein
pintCOMOutWidth = ((txtCOMOut.Width / pcintCharWidth8pnt) - 10)
'No changes allowed
grpCOM.Text = "COM (USB) Master Port. Slave Port. Restart to reselect COM ports. Here is the most recent output to the Maestro:"
'Standard baud rates: 9600, 14400, 19200, 28800, 33600, 56000
'Set up the public COMMPort
comUSB = My.Computer.Ports.OpenSerialPort(pstrCOMCommandPort, 56000, Ports.Parity.None, 8, Ports.StopBits.One)
comUSB.DiscardInBuffer()
comUSB.DiscardNull = False
comUSB.Handshake = Ports.Handshake.None
comUSB.WriteTimeout = -1
'There may have been power-up serial errors on the Maestro. Clear those errors, and meanwhile, initialize the Maestro.
I = -1
For L = 0 To (pintPuppetCount - 1)
'Get the ID
U = paryPuppetNumberAssociatedControllerID(L)
'Compare to the previous ID
If U <> I Then
'Clear errors and initialize the Maestro
MaestroClearError(U)
'Send the Maestro the Go Home command
MaestroGoHome(U)
End If
'Avoid the initial value after the first time
I = U
Next
End If
End If
End Sub
'Add some functions and subroutines
'=============================================================
Function COMIn() As Byte
Dim bytIn As Byte = 255
If comUSB.BytesToRead > 0 Then bytIn = comUSB.ReadByte
COMIn = bytIn
End Function
'=============================================================
Sub COMList()
'No COM port
cboCOMPort.Items.Add("[None]")
cboTTLPort.Items.Add("[None]")
'Show all available COM ports
For Each sp As String In My.Computer.Ports.SerialPortNames
cboCOMPort.Items.Add(sp)
cboTTLPort.Items.Add(sp)
Next
End Sub
'=============================================================
Sub COMOut(ByVal strCOMPort As String, ByVal bytOut() As Byte, ByVal intCount As Integer, ByVal booSubroutine As Boolean)
Dim L, Cntr As Integer
Dim bytIn As Byte
Dim strOut As String
Dim strTemp As String = ""
Dim strCOMIn As String = ""
Try
'Send a byte array to the selected serial port
comUSB.Write(bytOut, 0, intCount)
'Show the bytes that may change
For L = 1 To UBound(bytOut)
strTemp += Format(bytOut(L))
If L < UBound(bytOut) Then strTemp += ","
Next
'Collect the string of most recent data
strOut = txtCOMOut.Text & "|" & strTemp
'Cull the length
If strOut.Length > 1000 Then strOut = Microsoft.VisualBasic.Right(strOut, 100)
'Show the data that went out and any that came in
txtCOMOut.Text = strOut
'Move the caret to the end
txtCOMOut.SelectionStart = txtCOMOut.Text.Length
'Scroll to the end
txtCOMOut.ScrollToCaret()
'Test the subroutine flag
If booSubroutine = True Then
'Clear the buffer from last time
comUSB.DiscardInBuffer()
'Make a query and look for a response
Cntr = 0
bytIn = 255
Do Until bytIn = 1
'Send the 'Get Script Status' command array prepared at startup
comUSB.Write(parySubroutine, 0, 3)
'Bump the counter
Cntr += 1
'Test the counter
If Cntr > 100 Then Exit Do
'Test the response
bytIn = COMIn()
Loop
End If
Catch E As Exception
' Let the user know what went wrong.
Console.WriteLine("Communications cannot be established via the USB port:")
Console.WriteLine(E.Message)
txtCOMOut.Text = E.Message & " " & Microsoft.VisualBasic.Left(txtCOMOut.Text, txtCOMOut.Width / pcintCharWidth8pnt)
End Try
End Sub
'===============================================================
'The routines that work together and with the COM port to shift light data values out to one or more ShiftBrites or ShiftBars
'===============================================================
Sub LightShift()
Dim L, Pntr, intData, intColor As Integer
Dim B, bytLightCount As Byte
'The Pololu ShiftBrite or ShiftBar (with Satellite01 LED module) are used for lighting. Many lights can be used. Each
'has a shift register. When there is any change, we send out enough data to set all existing lights at 32 bits each.
'This example assumes < 256 lights in your string.
'Are we using hardware?
If pbooNoHardware = True Then Exit Sub
'How many lights are there?
bytLightCount = CByte(nudLightCount.Value)
'Constrain
If bytLightCount = 0 Then Exit Sub
'Set Multiple Targets (Mini Maestro 12, 18, and 24 only) in Pololu protocol:
'0xAA, device number, 0x1F, number of targets, first channel number, first target low bits, first target high bits,
'second target low bits, second target high bits…
'"This command simultaneously sets the targets for a contiguous block of channels. The first byte specifies how many
'channels are in the contiguous block; this is the number of target values you will need to send. The second byte
'specifies the lowest channel number in the block. The subsequent bytes contain the target values for each of the
'channels, in order by channel number, in the same format as the Set Target command above. For example, to set
'channel 3 to 0 (off) and channel 4 to 6000 (neutral), you would send the following bytes:
'0xAA, 0x00, 0x1F, 0x02, 0x03, 0x00, 0x00, 0x70, 0x2E"
'One multiple targets command for each light
For B = 0 To (bytLightCount - 1)
'Reset the pointer
Pntr = 0
'Prepare an array for one light to receive one multiple targets command
ReDim paryMaestroData(10) 'For each light: AA, device, 0x1F, starting target, target count, low, high, low, high, low, high.
'&HAA
paryMaestroData(Pntr) = &HAA : Pntr += 1
'Device
paryMaestroData(Pntr) = 0 : Pntr += 1
'Multiple Targets Command
paryMaestroData(Pntr) = &H1F : Pntr += 1
'Number of targets
paryMaestroData(Pntr) = 3 : Pntr += 1
'First target number
paryMaestroData(Pntr) = 3 : Pntr += 1
'Get the light data. We don't care if it is used or not because we still have to write the data to the COLOR channel.
intData = paryData(B, pintDataTimeslice)
'Three channels per light
For I = 3 To 5
'Which color
Select Case I
Case 3 'Blue
intColor = (((intData And &H3FF00000) >> 20) * 4) 'Data is 10-bit. Multiply by 4. Servo 1/4 usec divides by 4.
Case 4 'Red
intColor = (((intData And &HFFC00) >> 10) * 4) 'Data is 10-bit. Multiply by 4. Servo 1/4 usec divides by 4.
Case 5 'Green
intColor = ((intData And &H3FF) * 4) 'Data is 10-bit. Multiply by 4. Servo 1/4 usec divides by 4.
End Select
'Low data
paryMaestroData(Pntr) = (intColor And &H7F) : Pntr += 1
'High data
paryMaestroData(Pntr) = ((intColor And &H3F80) >> 7) : Pntr += 1
Next
'Talk to the COM port. Three 10-bit settings are in three separate 'servo' channels. No bits shift yet. Not a subroutine.
COMOut(pstrCOMCommandPort, paryMaestroData, paryMaestroData.Length, False)
'Shift 32 bits in four separate COM port calls
For I = 2 To 5
'Reset the pointer
Pntr = 0
'Prepare an array to call one scripted subroutine
ReDim paryMaestroData(3) 'For each light: AA, device, 0x27, subroutine number.
'Scripted subroutine number is I
Pntr = MaestroBuildArray(Pntr, 0, &H27, -1, I)
'Talk to the COM port. Two or ten bits shift into or further into ShiftBrite or ShiftBar registers
COMOut(pstrCOMCommandPort, paryMaestroData, paryMaestroData.Length, True)
Next
Next
'Reset the pointer
Pntr = 0
'Prepare the array for the LATCH script subroutine command, which sets the ShiftBrite(s)/ShiftBar(s).
ReDim paryMaestroData(3) 'One time AA, device, 0x27, pcintShiftSubLatch.
'Make COM start the script that sets LATCH after 32 bits for each light are sent. Subroutine pcintShiftSubLatch (last argument) sets LATCH.
Pntr = MaestroBuildArray(Pntr, 0, &H27, -1, pcintShiftSubLatch) 'Channel -1 means no channel is used
'Talk to the COM port. All the foregoing shifted data will now light all your lights at once according to the data you sent each light.
COMOut(pstrCOMCommandPort, paryMaestroData, paryMaestroData.Length, True)
End Sub
Function MaestroBuildArray(ByVal Pntr As Integer, ByVal bytDevice As Byte, ByVal bytCommand As Byte, ByVal intChannel As Integer, ByVal intData As Integer) As Integer
'&HAA
paryMaestroData(Pntr) = &HAA : Pntr += 1
'Device
paryMaestroData(Pntr) = bytDevice : Pntr += 1
'Command
paryMaestroData(Pntr) = bytCommand : Pntr += 1
'Channel? intChannel can be negative, meaning no channel is used.
If intChannel > -1 Then
'Channel
paryMaestroData(Pntr) = CByte(intChannel) : Pntr += 1
End If
'Data
paryMaestroData(Pntr) = (intData And &H7F) : Pntr += 1
'If command is 4, send to Target, then data is in two bytes.
If (bytCommand = &H4) Then
paryMaestroData(Pntr) = ((intData And &H3F80) >> 7)
Pntr += 1
End If
'Return Pntr
MaestroBuildArray = Pntr
End Function
'=============================================================
'Upon exit
Private Sub frmMain_FormClosed(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed
comUSB.Close()
comUSB.Dispose()
End Sub
There would appear to be innumerable ways to make the above code more efficient, especially in Sub LightShift(). Most of them don’t work! 'The Maestro somehow loses information. Say you called one script subroutine that calls SendTenBits three times for Blue, Red and Green. 'Much better, right? It doesn’t work. The rule seems to be, One Maestro command for one serial transmission, and one script subroutine 'at a time. Otherwise it all just doesn’t work as expected. So I hope this helps save you a lot of work! I sure do love the Maestro!