Saturday, October 3, 2015

Servo Controller: Binary Serial Communication (Computer Side)

My previous post discussed the receiving end of the serial communications; now I'll cover the sending side.

My starting point was my Servo Control via .NET application described a couple posts ago. It already had the framework for sending and receiving over the serial port, so we just need to make the adjustments for binary.


First to change is the UI. The text input field and send button has been replaced by 3 buttons, 9 track bars, and 18 numeric up/down controls.

The buttons are named as follows:
btnAllDown, btnAllMiddle, btnAllUp

The track bars are named as follows (range 0 to 100):
trackBar11, trackBar12, trackBar13, trackBar21, trackBar22, trackBar23, trackBar31, trackBar32, trackBar33

The numeric up/downs are named as follows (range 0 to 4096):
nudServoLowerLimit11, nudServoLowerLimit12, nudServoLowerLimit13, nudServoLowerLimit21, nudServoLowerLimit22, nudServoLowerLimit23, nudServoLowerLimit31, nudServoLowerLimit32, nudServoLowerLimit33
nudServoUpperLimit11, nudServoUpperLimit12, nudServoUpperLimit13, nudServoUpperLimit21, nudServoUpperLimit22, nudServoUpperLimit23, nudServoUpperLimit31, nudServoUpperLimit32, nudServoUpperLimit33

Next I defined the structure that will be sent over the serial line. This structure must match the one on the receiving end. Since this structure will be reused for several commands, I have the first byte be the command and a built-in getBytes() function for convenience.

Warning! The Marshal commands in .NET will create a byte array for a given structure. However, by default the byte array created will reserve 2 bytes for any variables listed as a Byte (the structure below would be 6 bytes in length). For it to be compatible with the Arduino side of the communication, we must tag the structure "LayoutKind.Sequential, Pack :=1". Now a Byte variable will be treated as 1 byte and the structure returned will be 5 bytes in length.

    ' Must set Sequential with Pack = 1 so .NET creates a byte array
    ' with the Byte as 1 byte (and not as 2).
    <StructLayout(LayoutKind.Sequential, Pack:=1)> _
    Public Structure CommandSetServoValueStructure
        Public command As Byte
        Public servoNumber As UInt16
        Public newValue As UInt16

        Public Function getBytes() As Byte()
            Dim binaryBytes(5) As Byte
            Dim pointerCommand As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(Me))
            Marshal.StructureToPtr(Me, pointerCommand, False)
            Marshal.Copy(pointerCommand, binaryBytes, 0, Marshal.SizeOf(Me))
            Marshal.FreeHGlobal(pointerCommand)
            Return binaryBytes
        End Function
    End Structure

Each command will fill out the values of this structure. Sending it over the network is then a simple:

            Dim thisCommand As CommandSetServoValueStructure
            ' Fill in thisCommand as appropriate.
            Dim binaryBytes() As Byte = thisCommand.getBytes
            connectedSerialPort.Write(binaryBytes, 0, 5)

Write takes in the bytes to send, the starting index, and the number of bytes to send - which in our cause is the size of our structure.

Setting Servo Position



Command 1: Setting the Servo Position (in percent)


Each track bar sets the servo's position by percentage (from 0 to 100).

    Private Sub sendCommandSetServoPercentToSerial(ByVal servoNumber As Integer, ByVal value_percent As Integer)
        Try
            Dim commandSetServoPercent As CommandSetServoValueStructure
            commandSetServoPercent.command = 1
            commandSetServoPercent.servoNumber = servoNumber
            commandSetServoPercent.newValue = value_percent

            Dim binaryBytes() As Byte = commandSetServoPercent.getBytes
            connectedSerialPort.Write(binaryBytes, 0, 5)
        Catch ex As Exception
        End Try
    End Sub

    Private Sub trackBar11_ValueChanged(sender As Object, e As EventArgs) Handles trackBar11.ValueChanged
        Try
            If Not isPopulatingSettings Then
                sendCommandSetServoPercentToSerial(0, trackBar11.Value)
            End If
        Catch ex As Exception
        End Try
    End Sub

    Private Sub trackBar21_ValueChanged(sender As Object, e As EventArgs) Handles trackBar21.ValueChanged
        Try
            If Not isPopulatingSettings Then
                sendCommandSetServoPercentToSerial(1, trackBar21.Value)
            End If
        Catch ex As Exception
        End Try
    End Sub

    Private Sub trackBar31_ValueChanged(sender As Object, e As EventArgs) Handles trackBar31.ValueChanged
        Try
            If Not isPopulatingSettings Then
                sendCommandSetServoPercentToSerial(2, trackBar31.Value)
            End If
        Catch ex As Exception
        End Try
    End Sub

    Private Sub trackBar12_ValueChanged(sender As Object, e As EventArgs) Handles trackBar12.ValueChanged
        Try
            If Not isPopulatingSettings Then
                sendCommandSetServoPercentToSerial(3, trackBar12.Value)
            End If
        Catch ex As Exception
        End Try
    End Sub

    Private Sub trackBar22_ValueChanged(sender As Object, e As EventArgs) Handles trackBar22.ValueChanged
        Try
            If Not isPopulatingSettings Then
                sendCommandSetServoPercentToSerial(4, trackBar22.Value)
            End If
        Catch ex As Exception
        End Try
    End Sub

    Private Sub trackBar32_ValueChanged(sender As Object, e As EventArgs) Handles trackBar32.ValueChanged
        Try
            If Not isPopulatingSettings Then
                sendCommandSetServoPercentToSerial(5, trackBar32.Value)
            End If
        Catch ex As Exception
        End Try
    End Sub

    Private Sub trackBar13_ValueChanged(sender As Object, e As EventArgs) Handles trackBar13.ValueChanged
        Try
            If Not isPopulatingSettings Then
                sendCommandSetServoPercentToSerial(6, trackBar13.Value)
            End If
        Catch ex As Exception
        End Try
    End Sub

    Private Sub trackBar23_ValueChanged(sender As Object, e As EventArgs) Handles trackBar23.ValueChanged
        Try
            If Not isPopulatingSettings Then
                sendCommandSetServoPercentToSerial(7, trackBar23.Value)
            End If
        Catch ex As Exception
        End Try
    End Sub

    Private Sub trackBar33_ValueChanged(sender As Object, e As EventArgs) Handles trackBar33.ValueChanged
        Try
            If Not isPopulatingSettings Then
                sendCommandSetServoPercentToSerial(8, trackBar33.Value)
            End If
        Catch ex As Exception
        End Try
    End Sub

Setting Servo Range



Command 2: Setting the Servo Upper Limit


The top numeric up/down sets the upper range for the servo.

    Private Sub sendCommandSetServoUpperLimitToSerial(ByVal servoNumber As Integer, ByVal newUpper_pulseLength As Integer)
        Try
            Dim commandSetServoUpperLimit_pulseLength As CommandSetServoValueStructure
            commandSetServoUpperLimit_pulseLength.command = 2
            commandSetServoUpperLimit_pulseLength.servoNumber = servoNumber
            commandSetServoUpperLimit_pulseLength.newValue = newUpper_pulseLength

            Dim binaryBytes() As Byte = commandSetServoUpperLimit_pulseLength.getBytes
            connectedSerialPort.Write(binaryBytes, 0, 5)
        Catch ex As Exception
        End Try
    End Sub

    Private Sub nudServoUpperLimit11_ValueChanged(sender As Object, e As EventArgs) Handles nudServoUpperLimit11.ValueChanged
        Try
            If Not isPopulatingSettings Then
                sendCommandSetServoUpperLimitToSerial(0, nudServoUpperLimit11.Value)
                sendCommandSetServoPercentToSerial(0, trackBar11.Value)
            End If
        Catch ex As Exception
        End Try
    End Sub

    Private Sub nudServoUpperLimit21_ValueChanged(sender As Object, e As EventArgs) Handles nudServoUpperLimit21.ValueChanged
        Try
            If Not isPopulatingSettings Then
                sendCommandSetServoUpperLimitToSerial(1, nudServoUpperLimit21.Value)
                sendCommandSetServoPercentToSerial(1, trackBar21.Value)
            End If
        Catch ex As Exception
        End Try
    End Sub

    Private Sub nudServoUpperLimit31_ValueChanged(sender As Object, e As EventArgs) Handles nudServoUpperLimit31.ValueChanged
        Try
            If Not isPopulatingSettings Then
                sendCommandSetServoUpperLimitToSerial(2, nudServoUpperLimit31.Value)
                sendCommandSetServoPercentToSerial(2, trackBar31.Value)
            End If
        Catch ex As Exception
        End Try
    End Sub

    Private Sub nudServoUpperLimit12_ValueChanged(sender As Object, e As EventArgs) Handles nudServoUpperLimit12.ValueChanged
        Try
            If Not isPopulatingSettings Then
                sendCommandSetServoUpperLimitToSerial(3, nudServoUpperLimit12.Value)
                sendCommandSetServoPercentToSerial(3, trackBar12.Value)
            End If
        Catch ex As Exception
        End Try
    End Sub

    Private Sub nudServoUpperLimit22_ValueChanged(sender As Object, e As EventArgs) Handles nudServoUpperLimit22.ValueChanged
        Try
            If Not isPopulatingSettings Then
                sendCommandSetServoUpperLimitToSerial(4, nudServoUpperLimit22.Value)
                sendCommandSetServoPercentToSerial(4, trackBar22.Value)
            End If
        Catch ex As Exception
        End Try
    End Sub

    Private Sub nudServoUpperLimit32_ValueChanged(sender As Object, e As EventArgs) Handles nudServoUpperLimit32.ValueChanged
        Try
            If Not isPopulatingSettings Then
                sendCommandSetServoUpperLimitToSerial(5, nudServoUpperLimit32.Value)
                sendCommandSetServoPercentToSerial(5, trackBar32.Value)
            End If
        Catch ex As Exception
        End Try
    End Sub

    Private Sub nudServoUpperLimit13_ValueChanged(sender As Object, e As EventArgs) Handles nudServoUpperLimit13.ValueChanged
        Try
            If Not isPopulatingSettings Then
                sendCommandSetServoUpperLimitToSerial(6, nudServoUpperLimit13.Value)
                sendCommandSetServoPercentToSerial(6, trackBar13.Value)
            End If
        Catch ex As Exception
        End Try
    End Sub

    Private Sub nudServoUpperLimit23_ValueChanged(sender As Object, e As EventArgs) Handles nudServoUpperLimit23.ValueChanged
        Try
            If Not isPopulatingSettings Then
                sendCommandSetServoUpperLimitToSerial(7, nudServoUpperLimit23.Value)
                sendCommandSetServoPercentToSerial(7, trackBar23.Value)
            End If
        Catch ex As Exception
        End Try
    End Sub

    Private Sub nudServoUpperLimit33_ValueChanged(sender As Object, e As EventArgs) Handles nudServoUpperLimit33.ValueChanged
        Try
            If Not isPopulatingSettings Then
                sendCommandSetServoUpperLimitToSerial(8, nudServoUpperLimit33.Value)
                sendCommandSetServoPercentToSerial(8, trackBar33.Value)
            End If
        Catch ex As Exception
        End Try
    End Sub

Command 3: Setting the Servo Lower Limit


The bottom numeric up/down sets the lower range for the servo.

    Private Sub sendCommandSetServoLowerLimitToSerial(ByVal servoNumber As Integer, ByVal newLower_pulseLength As Integer)
        Try
            Dim commandSetServoLowerLimit_pulseLength As CommandSetServoValueStructure
            commandSetServoLowerLimit_pulseLength.command = 3
            commandSetServoLowerLimit_pulseLength.servoNumber = servoNumber
            commandSetServoLowerLimit_pulseLength.newValue = newLower_pulseLength

            Dim binaryBytes() As Byte = commandSetServoLowerLimit_pulseLength.getBytes
            connectedSerialPort.Write(binaryBytes, 0, 5)
        Catch ex As Exception
        End Try
    End Sub

    Private Sub nudServoLowerLimit11_ValueChanged(sender As Object, e As EventArgs) Handles nudServoLowerLimit11.ValueChanged
        Try
            If Not isPopulatingSettings Then
                sendCommandSetServoLowerLimitToSerial(0, nudServoLowerLimit11.Value)
                sendCommandSetServoPercentToSerial(0, trackBar11.Value)
            End If
        Catch ex As Exception
        End Try
    End Sub

    Private Sub nudServoLowerLimit21_ValueChanged(sender As Object, e As EventArgs) Handles nudServoLowerLimit21.ValueChanged
        Try
            If Not isPopulatingSettings Then
                sendCommandSetServoLowerLimitToSerial(1, nudServoLowerLimit21.Value)
                sendCommandSetServoPercentToSerial(1, trackBar21.Value)
            End If
        Catch ex As Exception
        End Try
    End Sub

    Private Sub nudServoLowerLimit31_ValueChanged(sender As Object, e As EventArgs) Handles nudServoLowerLimit31.ValueChanged
        Try
            If Not isPopulatingSettings Then
                sendCommandSetServoLowerLimitToSerial(2, nudServoLowerLimit31.Value)
                sendCommandSetServoPercentToSerial(2, trackBar31.Value)
            End If
        Catch ex As Exception
        End Try
    End Sub

    Private Sub nudServoLowerLimit12_ValueChanged(sender As Object, e As EventArgs) Handles nudServoLowerLimit12.ValueChanged
        Try
            If Not isPopulatingSettings Then
                sendCommandSetServoLowerLimitToSerial(3, nudServoLowerLimit12.Value)
                sendCommandSetServoPercentToSerial(3, trackBar12.Value)
            End If
        Catch ex As Exception
        End Try
    End Sub

    Private Sub nudServoLowerLimit22_ValueChanged(sender As Object, e As EventArgs) Handles nudServoLowerLimit22.ValueChanged
        Try
            If Not isPopulatingSettings Then
                sendCommandSetServoLowerLimitToSerial(4, nudServoLowerLimit22.Value)
                sendCommandSetServoPercentToSerial(4, trackBar22.Value)
            End If
        Catch ex As Exception
        End Try
    End Sub

    Private Sub nudServoLowerLimit32_ValueChanged(sender As Object, e As EventArgs) Handles nudServoLowerLimit32.ValueChanged
        Try
            If Not isPopulatingSettings Then
                sendCommandSetServoLowerLimitToSerial(5, nudServoLowerLimit32.Value)
                sendCommandSetServoPercentToSerial(5, trackBar32.Value)
            End If
        Catch ex As Exception
        End Try
    End Sub

    Private Sub nudServoLowerLimit13_ValueChanged(sender As Object, e As EventArgs) Handles nudServoLowerLimit13.ValueChanged
        Try
            If Not isPopulatingSettings Then
                sendCommandSetServoLowerLimitToSerial(6, nudServoLowerLimit13.Value)
                sendCommandSetServoPercentToSerial(6, trackBar13.Value)
            End If
        Catch ex As Exception
        End Try
    End Sub

    Private Sub nudServoLowerLimit23_ValueChanged(sender As Object, e As EventArgs) Handles nudServoLowerLimit23.ValueChanged
        Try
            If Not isPopulatingSettings Then
                sendCommandSetServoLowerLimitToSerial(7, nudServoLowerLimit23.Value)
                sendCommandSetServoPercentToSerial(7, trackBar23.Value)
            End If
        Catch ex As Exception
        End Try
    End Sub

    Private Sub nudServoLowerLimit33_ValueChanged(sender As Object, e As EventArgs) Handles nudServoLowerLimit33.ValueChanged
        Try
            If Not isPopulatingSettings Then
                sendCommandSetServoLowerLimitToSerial(8, nudServoLowerLimit33.Value)
                sendCommandSetServoPercentToSerial(8, trackBar33.Value)
            End If
        Catch ex As Exception
        End Try
    End Sub



Set All


The set all buttons are just shortcuts for changing every track bar to 0%, 50%, or 100%.


    Private Sub setAll(ByVal newValue_percent As Integer)
        Try
            trackBar11.Value = newValue_percent
            trackBar21.Value = newValue_percent
            trackBar31.Value = newValue_percent
            trackBar12.Value = newValue_percent
            trackBar22.Value = newValue_percent
            trackBar32.Value = newValue_percent
            trackBar13.Value = newValue_percent
            trackBar23.Value = newValue_percent
            trackBar33.Value = newValue_percent
        Catch ex As Exception
        End Try
    End Sub

    Private Sub btnAllDown_Click(sender As Object, e As EventArgs) Handles btnAllDown.Click
        Try
            setAll(0)
        Catch ex As Exception
        End Try
    End Sub

    Private Sub btnAllMiddle_Click(sender As Object, e As EventArgs) Handles btnAllMiddle.Click
        Try
            setAll(50)
        Catch ex As Exception
        End Try
    End Sub

    Private Sub btnAllUp_Click(sender As Object, e As EventArgs) Handles btnAllUp.Click
        Try
            setAll(100)
        Catch ex As Exception
        End Try
    End Sub

Additional Support Code


Imports System
Imports System.IO.Ports
Imports System.Runtime.InteropServices

Public Class FormMain

    Private _isInitializingForm As Boolean = True
    Private _isLoadingConfiguration As Boolean = False

    Public ReadOnly Property isPopulatingSettings As Boolean
        Get
            If _isInitializingForm Then
                Return True
            End If
            If _isLoadingConfiguration Then
                Return True
            End If
            Return False
        End Get
    End Property

    ''' <summary>
    ''' Definite a delegate to transfer incoming data from the background
    ''' (serial) thread to the foreground (UI) thread.
    ''' </summary>
    ''' <param name="incomingBuffer"></param>
    ''' <remarks></remarks>
    Private Delegate Sub ProcessIncomingTextDelegate(ByVal incomingBuffer As String)

    ''' <summary>
    ''' The serial port object that will communicate.
    ''' </summary>
    ''' <remarks></remarks>
    Private WithEvents connectedSerialPort As New SerialPort()

    Public Sub New()

        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        _isInitializingForm = False
    End Sub

    Private Sub loadConfigurationValues()
        Try
            _isLoadingConfiguration = True

            Try
                nudServoUpperLimit11.Value = 550
                nudServoUpperLimit12.Value = 550
                nudServoUpperLimit13.Value = 550
                nudServoUpperLimit21.Value = 550
                nudServoUpperLimit22.Value = 550
                nudServoUpperLimit23.Value = 550
                nudServoUpperLimit31.Value = 550
                nudServoUpperLimit32.Value = 550
                nudServoUpperLimit33.Value = 550

                nudServoLowerLimit11.Value = 200
                nudServoLowerLimit12.Value = 200
                nudServoLowerLimit13.Value = 200
                nudServoLowerLimit21.Value = 200
                nudServoLowerLimit22.Value = 200
                nudServoLowerLimit23.Value = 200
                nudServoLowerLimit31.Value = 200
                nudServoLowerLimit32.Value = 200
                nudServoLowerLimit33.Value = 200
            Catch ex As Exception
            End Try

            _isLoadingConfiguration = False
        Catch ex As Exception
        End Try
    End Sub

    Private Sub FormMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Try
            loadConfigurationValues()

            refreshPortsList()

            If ddlCOMPort.SelectedIndex >= 0 Then
                ' Port is found, attempt to connect to it.
                connectSerialPort()
            Else
                ' Update which controls are enabled (so disconnect button is disabled at startup).
                updateConnectedEnableFields()
            End If
        Catch ex As Exception
        End Try
    End Sub

    Private Sub btnConnect_Click(sender As Object, e As EventArgs) Handles btnConnect.Click
        Try
            connectSerialPort()
        Catch ex As Exception
        End Try
    End Sub

    Private Sub btnDisconnect_Click(sender As Object, e As EventArgs) Handles btnDisconnect.Click
        Try
            disconnectSerialPort()
        Catch ex As Exception
        End Try
    End Sub

    Private Sub connectedSerialPort_DataReceived(sender As Object, e As SerialDataReceivedEventArgs) Handles connectedSerialPort.DataReceived
        Try
            ' Received data from the serial port, send it for processing.
            processIncomingData(connectedSerialPort.ReadExisting())
        Catch ex As Exception
        End Try
    End Sub

    Private Sub connectedSerialPort_ErrorReceived(sender As Object, e As SerialErrorReceivedEventArgs) Handles connectedSerialPort.ErrorReceived
        Try
        Catch ex As Exception
        End Try
    End Sub

    Private Sub btnRefreshPorts_Click(sender As Object, e As EventArgs) Handles btnRefreshPorts.Click
        Try
            refreshPortsList()
        Catch ex As Exception
        End Try
    End Sub

    ''' <summary>
    ''' Returns if the serial port is currently connected.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private ReadOnly Property isSerialPortConnected
        Get
            Return connectedSerialPort.IsOpen()
        End Get
    End Property

    ''' <summary>
    ''' Update whether or not each UI control should be enabled based on
    ''' the connection state.
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub updateConnectedEnableFields()
        Try
            Dim isConnected As Boolean = isSerialPortConnected
            btnConnect.Enabled = Not isConnected
            btnDisconnect.Enabled = isConnected
            ddlCOMPort.Enabled = Not isConnected
            btnRefreshPorts.Enabled = Not isConnected
        Catch ex As Exception
        End Try
    End Sub

    ''' <summary>
    ''' Connect to the selected serial port.
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub connectSerialPort()
        Try
            If ddlCOMPort.SelectedIndex >= 0 Then
                connectedSerialPort.PortName = ddlCOMPort.SelectedItem
                connectedSerialPort.BaudRate = 9600

                connectedSerialPort.WriteTimeout = -1
                connectedSerialPort.ReadTimeout = -1

                connectedSerialPort.ReadBufferSize = 4096
                connectedSerialPort.WriteBufferSize = 2048

                connectedSerialPort.Parity = IO.Ports.Parity.None
                connectedSerialPort.StopBits = IO.Ports.StopBits.One
                connectedSerialPort.DataBits = 8
                connectedSerialPort.Open()

                'Threading.Thread.Sleep(1000)

                ' Send Extended ASCII Code 128 for binary mode.
                Dim binaryBytes() As Byte = {128}
                connectedSerialPort.Write(binaryBytes, 0, 1)
            End If
            updateConnectedEnableFields()
        Catch ex As Exception
        End Try
    End Sub

    ''' <summary>
    ''' Disconnect the serial port.
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub disconnectSerialPort()
        Try
            connectedSerialPort.Close()
            updateConnectedEnableFields()
        Catch ex As Exception
        End Try
    End Sub

    ''' <summary>
    ''' Detect available serial ports and update the COM port list.
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub refreshPortsList()
        Try
            ' Keep track of the selected port so we can reselect it after fresh.
            Dim currentlySelectedPort As String = ddlCOMPort.SelectedItem

            ' Get the available ports and update the dropdown list.
            Dim availablePorts As Array = IO.Ports.SerialPort.GetPortNames()
            ddlCOMPort.Items.Clear()
            ddlCOMPort.Items.AddRange(availablePorts)

            ' Reselect the previously selected port as a default.
            If currentlySelectedPort IsNot Nothing Then
                If ddlCOMPort.Items.Contains(currentlySelectedPort) Then
                    ddlCOMPort.SelectedItem = currentlySelectedPort
                End If
            End If

            If ddlCOMPort.SelectedIndex = -1 AndAlso ddlCOMPort.Items.Count > 0 Then
                ' No port selected but one is available, default to it.
                ddlCOMPort.SelectedIndex = 0
            End If
        Catch ex As Exception
        End Try
    End Sub

    ''' <summary>
    ''' Function to process incoming data from the serial port.
    ''' </summary>
    ''' <param name="incomingBuffer"></param>
    ''' <remarks></remarks>
    Private Sub processIncomingData(ByVal incomingBuffer As String)
        Try
            If txtReceive.InvokeRequired Then
                ' On background (serial port) thread. Invoke a delegate to call this
                ' function again on the foreground (UI) thread.
                Dim incomingTextDelegate As New ProcessIncomingTextDelegate(AddressOf processIncomingData)
                Me.Invoke(incomingTextDelegate, New Object() {incomingBuffer})
            Else
                ' On foreground (UI) thread. Process the message.
                txtReceive.Text += incomingBuffer
            End If
        Catch ex As Exception
        End Try
    End Sub

   ' ---------- CODE FROM ABOVE ----------

End Class

In Operation


At startup...

The Board Manager automatically connects to the first COM port it detects and sends the binary input command.


Setting some of the servo values...


And the servos in action...





Copyright (c) 2015 Clinton Kam
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

No comments:

Post a Comment