Serial.bi

User projects written in or related to FreeBASIC.
Post Reply
MrSwiss
Posts: 3910
Joined: Jun 02, 2013 9:27
Location: Switzerland

Serial.bi

Post by MrSwiss »

This is a "header only" implementation, of the four mostly used commands in "serial-communication".
(like Arduino or any other, serial controllable device which supports VCP over USB)

It does it with "full error checking" (often neglected) in code I've come across, so far.
I'm of the opinion, that this is required for "easy debugging", especially for beginners.

Advantages:
  • quicker results (in a test application)
  • a single #Incude statement
  • fast (uses Get/Put, instead of slower methods)
  • multiple devices supported (at once)
  • simplified fault finding
The four commands are:
  • Serial_Open()
    Serial_Close()
    Serial_TX(), transmit (aka: send)
    Serial_RX(), receive, with "built-in" timeout method
Serial.bi:

Code: Select all

' Serial.bi -- (c) 2019-06-24, MrSwiss
'
#Pragma Once

Declare Function Serial_Open (ByRef cmdln As String, ByRef exopt As String = "", ByRef err_ As Long = 0) As Long
Declare Function Serial_Close(ByVal d_no As UByte) As Boolean
Declare Function Serial_RX   (ByVal d_no As UByte, ByVal tmout As Double=0.0) As String
Declare Function Serial_TX   (ByVal d_no As UByte, ByRef msg As String) As Boolean

' implementations ...
Private Function Serial_Open( _         ' opens a serial port
    ByRef cmdln As String, _            ' basics: "COMn: baudrate, parity, data-bits, stop-bits"
    ByRef exopt As String = "", _       ' extended parameters (check FB-manual)
    ByRef err_  As Long = 0 _           ' optional: error no (open com error no.)
    ) As Long                           ' device number (if OK, > 0)
    If cmdln = "" Then Return -1        ' user ERROR

    If exopt <> "" Then _               ' if extended options present
        cmdln += (", " + exopt)         ' append them to cmdln

    Var d_no = FreeFile                 ' 1 to 255 (file/device number)
    err_ = Open Com(cmdln, As d_no)     ' try to open serial port
    If err_ = 0 Then Return d_no        ' OK: return device number
    Return 0                            ' ERROR Open Com(): return it (0)
End Function

Private Function Serial_Close( _        ' close aopen device (file too)
    ByVal d_no  As UByte _              ' device number
    ) As Boolean
    If d_no = 0 Then Return TRUE        ' user ERROR
    
    If Close(d_no) <> 0 Then _          ' close failed ERROR
        Return TRUE                     ' return it
    Return FALSE                        ' all OK
End Function

Private Function Serial_RX( _           ' receive serial data (read)
    ByVal d_no  As UByte, _             ' device number
    ByVal tmout As Double = 0.0 _       ' timeout in seconds
    ) As String
    If d_no = 0 Then _
        Return "ERROR: 'd_no' = 0"      ' user ERROR

    Var tlim = Timer + tmout            ' set timer limit (for timeout)
    Var nb = LOC(d_no)                  ' get amount of bytes (in buffer)
    While nb = 0                        ' try again until data or timeout
        Sleep(20, 1)                    ' wait 20 mS
        nb = LOC(d_no)                  ' try again if we have something
        If Timer > tlim Then _          ' check timeout
            Return "Serial timeout!"    ' quit (on timeout)
    Wend
    If nb > 0 Then                      ' only, if there is data to get
        Var sret = String(nb, 0)        ' allocate string memory IMPORTANT!
        If Get(d_no,, sret) = 0 Then    ' get data from serial buffer to string
            Return sret                 ' return read data
        Else
            Return "Get() failed!"      ' get failure
        EndIf
    End If
    Return ""                           ' nothing got (no errors)
End Function

Private Function Serial_TX( _           ' transmit serial data (send)
    ByVal d_no  As UByte, _             ' device number
    ByRef msg   As String _             ' data to send (transmit)
    ) As Boolean
    If d_no = 0 Then Return TRUE        ' user ERROR

    If Len(msg) > 0 Then                ' only, if there is data to send
        If Put(d_no,, msg) = 0 Then
            Return FALSE                ' success
        Else
            Return TRUE                 ' ERROR
        End If
    End If
End Function
' ----- EOF -----
MrSwiss
Posts: 3910
Joined: Jun 02, 2013 9:27
Location: Switzerland

Re: Serial.bi

Post by MrSwiss »

As a first example a test with a Arduino, that does "nothing" except acting as a
"loopback device". Whatever sent is instantly returned to the "sender".

Note:
from FB one can send a String, but the Arduino is returning single characters,
until the string is done ... (the way the Arduino Sketch is implemented)

FB test code:

Code: Select all

' Serial_bi-Test1.bas -- (c) 2019-07-31, MrSwiss
'
' compile: -s gui
'

#Include "Serial.bi"                    ' "once" specifier NOT needed! (built-in)

ScreenRes(640, 480, 32)                 ' screen setup
Width 80, 30                            ' large font

Dim As Long     dn, er, el              ' device number | Open Com() error no. | error level
Dim As String   c_cmd = "COM3: 19200, n, 8, 1", _   ' basic COM-Port settings
                c_ext = "cs0, ds0, cd0, rs"         ' extended COM-Port settings
' NOTE: the string split done above is optional (for better readabilty) _
' you can still put it all into c_cmd and omit c_ext, if preferred ...
' e.g.  dn = Serial_Open(c_cmd,, er) '' er is optional too (debugging mostly)

dn = Serial_Open(c_cmd, c_ext, er)      ' if OK then: dn > 0 | er = 0
If dn < 1 Then                          ' error treatment                          
    If dn = 0 Then
        Print "Open Com() failed! Err No.: "; Str(er) : el = 1
    Else
        Print "User ERROR"; : el = 2    ' only if c_cmd = ""
    End If : Sleep : End el             ' error code (returned by this prog.)
End If

' all stuff that's only needed, if above is successful
Const           timeout = 0.25          ' 250 mS timeout span (Double)
Dim As String   outData, inData, iMSG = _   ' long strings to variables!
                "type command, then press [Enter]" + Chr(10) + _
                "just [Enter] to quit/exit program "

Print iMSG : Print : iMSG = Left(iMSG, 32)  ' just keep first line

Do
    outData = ""                        ' clear transmit string
    While InKey() <> "" : Wend          ' clear keyboard buffer
    Line Input; iMSG, outData           ' get user input
    Print                               ' add LF
    If outData = "" Then
        Exit Do                         ' user wants to quit
    Else 
        If Serial_TX(dn, outData) Then  ' transmit to device
            Print "Send DATA failed!"   ' on error, show message
        End If
        inData = ""                     ' clear receiver string
        inData = Serial_RX(dn, timeout) ' get receive buffer content
        If inData = "" Then
            Print "No DATA received!"   ' no errors otherwise
        Else
            Print inData                ' timeout/error message or DATA
        End If
    End If

    Sleep(20, 1)                        ' wait 20 mS
Loop

If Serial_Close(dn) Then _              ' with error checking!
    Print "Serial_Close("; Str(dn); ") failed!" : sleep
' ----- EOF -----
The Arduino Sketch (C-code):

Code: Select all

// ASCII_LoopBack19200 -- (c) 2019-06-25, MrSwiss
// no global variables

void setup() {
  Serial.begin(19200);                                // start serial port at 19200 bps and wait for port to open
  while (!Serial) {                                   // wait for serial port to connect
  delay(1);                                           // needed for native USB port only
  }
}

void loop() {
  int inByte = 0;                                     // incoming serial byte (local variable)
  
  if (Serial.available() > 0) {                       // if we can get a valid byte
    inByte = Serial.read();                           // get byte
    if (inByte != 0) {
      Serial.println(char(inByte));                     // send input straight back to sender                                        
    } else {                                            // nothing to get
      delay(50);                                        // wait 50 ms before next check
    }
  delay(1);
  }
}
coderJeff
Site Admin
Posts: 4313
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Re: Serial.bi

Post by coderJeff »

Not a bad start. Main feature, I think: Serial_RX() waits for some response (maybe not full response) and times out otherwise.

As for speed, PUT vs PRINT, or GET vs INPUT(), I don't know; processing multiple bytes at once will be faster, which all these fbc statements can do. At 9600 baud it probably won't matter even if were doing 1 byte at a time, as we are only moving approx 1 byte per millisecond. At the higher communication speeds, might start to notice a difference by processing multiple bytes at once. Anyway, having the throughput in mind is a nice addition.

Otherwise,
The caller of Serial_RX() will still have to sort out message starts and stops (maybe EOL is end of message, it's up to user). So some buffering and processing needs to be done with the data received by the caller of Serial_RX().

Serial_RX() can exit without receiving the full message, which is more likely the if the device sends longer messages. Also, Serial_RX() could return a valid message or an error code in the same result, so device can never send a message that is equal to Serial_RX()'s error code message. For consistency with Serial_TX() and Serial_Close(), maybe Serial_RX() could also return boolean = FALSE for success, and pass a string parameter to Serial_RX() instead as the receive buffer.
BasicCoder2
Posts: 3906
Joined: Jan 01, 2009 7:03
Location: Australia

Re: Serial.bi

Post by BasicCoder2 »

It seems to be doing what mine did? Returns the previous input rather than the current input?
Image
MrSwiss
Posts: 3910
Joined: Jun 02, 2013 9:27
Location: Switzerland

Re: Serial.bi

Post by MrSwiss »

BasicCoder2 wrote:It seems to be doing what mine did? Returns the previous input rather than the current input?
Nope, not on my systems. Everything is A-OK.

Please check:
are you also using my Aduino Sketch? (from the same post?)
speed: 19200 baud (PC & Arduino)
timeout: 0.25 (or larger)
length of USB cable: 6 ft max. (1.8 m), good quality (longer may introduce more lag)

Otherwise: try to increase timeout setting, until you're getting a decent result ...
(that's the reason to have it, in the first place)
D.J.Peters
Posts: 8586
Joined: May 28, 2005 3:28
Contact:

Re: Serial.bi

Post by D.J.Peters »

@MrSwiss don't use USB cables from $ store :-)

you can send and receive MB's per seconds with a 3m cable not only 115,000 BITS (1,5m) without any problems

USB is a differential signal +D -D and and well shielded.

Think about serial HDMI !
1 byte = 1 start bit + 8 data bits + 1 stop bit (this is the lowest /oldest HDMI specs)

10 bits for red + 10 bits for green + 10 bits for blue

30 bits per color * 1920 × 1080 pixels = 62,208,008 bits per frame * 60 frames per second
= 3,732,480,008 bits = 466,560,008 bytes = ~444 MB

444 MB per second without digital audio :-)

you mean 19200 baud > 1.8 m (longer may introduce more lag)

If this are true in your setup then you have (1) a bad cable (2) or your serial to USB adapter has a power=voltage problem !

(3) Don't wiggle the cable like a snake (or a ordinary coil) this will produce signal reflections and interference's !

I have different DSO's (digital signal oscilloscopes from 20Mhz to 100MHz)
running with FreeBASIC to capture signals over cheap chinese serial to USB converters without any problems.

Joshy
coderJeff
Site Admin
Posts: 4313
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Re: Serial.bi

Post by coderJeff »

BasicCoder2 wrote:It seems to be doing what mine did? Returns the previous input rather than the current input?
Yes, I noticed that too. It happens if there is anything in the fbc host comm buffer before the first Serial_TX() call. For example, I used a different sketch and had a Serial.Print() in Setup(), which got the synchronization off track right from the start.

It's like this:
1) We are waiting at fbc's INPUT statement, but for some reason, there's already some data in the COM buffer waiting to be read.
2) We enter the command and hit ENTER
3) Serial_TX() sends the data, and it's on its way to the device
4) Serial_RX() gets called and sees right away there is already something to read (which was there from before).
5) Serial_RX() reads out the data before there's time for the latest command (you just entered) to be answered by the device.
6) Whatever was in the COM buffer gets printed.
7) And we loop back around to the INPUT statement.
8) By this time, we've probably got the response from the device and the answer is sitting in the COM buffer.

So, the only way this scheme can work, is if the device never sends data unless it is asked to, and always responds completely before the timeout value.
coderJeff
Site Admin
Posts: 4313
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Re: Serial.bi

Post by coderJeff »

MrSwiss wrote: <snip>

Code: Select all

void setup() {
  Serial.begin(19200);                                // start serial port at 19200 bps and wait for port to open
  while (!Serial) {                                   // wait for serial port to connect
  delay(1);                                           // needed for native USB port only
  }
}
}
</snip>
This appears to be a good practice. For some boards, while(!Serial) always returns true, so doesn't matter, but I guess it doesn't hurt to have it in there either.

I was hoping this would solve a problem I had on Linux where I was getting bad data coming from Serial.Print() in setup(), but it has no effect on Arduino UNO. Turns out that the board was getting reset twice when opening the COM port from fbc, but not always, and very quickly. It might be that I am running Linux in a VM. A clunky fix for me was to add a delay(1000) in setup() before sending anything to the Serial.print(), just in case it got reset (twice).
MrSwiss
Posts: 3910
Joined: Jun 02, 2013 9:27
Location: Switzerland

Re: Serial.bi

Post by MrSwiss »

@coderJeff,
nice summary ...

There may also exist a certain amount of differences, depending on the model
of Arduino used. Mine is a "old" Duemillanove/Diecimilla: ATMEGA 168/16 MHz.
D.J.Peters wrote:@MrSwiss don't use USB cables from $ store :-)
There seems to be a misunderstanding: I don't have problems, BasicCoder2 has them.
Secondly I'm definitely all for "high quality cables" (for any type of connection).
Thirdly I don't use any sort of converter, USB-A to USB-B cable.
Post Reply