FreeBasic communication with Arduino logic error?

For issues with communication ports, protocols, etc.
MrSwiss
Posts: 3910
Joined: Jun 02, 2013 9:27
Location: Switzerland

Re: FreeBasic communication with Arduino logic error?

Post by MrSwiss »

You can change the port any time you like, but the Arduino must be connected to
the system (COM3 is default for Arduino, if not already used otherwise).
I assume that you're using Windows (on Linux it would be a TTY device).

"Control Panel\Hardware and Sound\Device Manager" then open "Ports (Com & Lpt)"
double click the "USB to Serial Port (COM3)" afterwards, in the "Port Setting - Tab",
click "Advanced" (here in the dropdown list, another port can be chosen).
coderJeff
Site Admin
Posts: 4313
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Re: FreeBasic communication with Arduino logic error?

Post by coderJeff »

Dr_D wrote:So, I can only get coms to work, if I use COM3. Is there an explanation for this? At first, I thought it was actually referring to the specific USB port my Arduino is hooked up to as COM3. However, it doesn't matter which USB port I connect to. Why is that?
For windows, I'm pretty sure this is how it works: When you plug your device (arduino) in to your PC for the first time, windows will automatically load a driver and assign a serial port that is not currently in use.

The arduino will communicate a vendor ID (VID), a product ID (PID), and a serial number (SN) to windows. The VID/PID will instruct windows on which driver to use, and the serial number is used to track information about the device.

Windows remembers the COM port assignment based on arduino VID/PID/SN by saving it in the registry. So it doesn't matter which USB port you plug it in to.

If you open Arduino IDE, and check Tools | Board Info, you should get something like

Code: Select all

BN: Arduino/Genuino Uno
VID: 0x2341
PID: 0x0001
SN: 5593534222226291C1A1
1) If you then open regedit and look in HKLM/SYSTEM/CurrentControlSet/Enum/USB/ you should see a list of all the USB hardware you have ever connected to your PC.
2) Then, look for the specific VID&PID/SN, to find where windows stores information about the device.
3) In my case, I have ./VID_2341&PID_0001/5593534222226291C1A1/. Then from there if I look under ./Device Parameters/PortName I see that it is set to COM7. Actually, if you find and change this value in the registry, then plug in the arduino, it will be assigned to a whatever COM port you choose here.
4) Also, you can also set the assigned com port through windows "Device Manager" by finding the USB device and looking at Properties / Advanced.
coderJeff
Site Admin
Posts: 4313
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Arduino Serial Communication Demo #4

Post by coderJeff »

Arduino Serial Communication Demo #4

For demo #4, we go back to basics and try to provide a solution for the issue raised in the opening post.
1) we want to send commands from the host (pc) and have the device (arduino) act on them
2) we really don't want to wait for a response. If there is no command (or response) the arduino and host should just keep going

Here is the design I came up with; what I am calling "One Byte Serial Protocol":

Code: Select all

/'
One Byte Serial Protocol

GENERAL I/O

	7 654 3210
	- --- ---- -------------------------
	0 100 mmmm general message (request)
	0 000 mmmm general message (false)
	0 001 mmmm general message (true)

	mmmm = message number (0 to 15)

ADDRESSABLE BIT I/O

	7 654 3210
	- --- ---- -----------------
	0 101 aaaa read bit request
	0 110 aaaa clear bit request
	0 111 aaaa set bit request

	0 010 aaaa status = false
	0 011 aaaa status = true

	aaaa = address (0 to 15)

RESERVED

	7 654 3210
	- --- ---- --------
	1 ddd dddd reserved

'/
There's quite a bit we can do with 8 bits of data. And actually, I'm going to reserve the high bit for future, so this first iteration of the design is just with 7 bits.

The key features of the 1-byte message to notice are:
1) 1 bit reserved
2) 3 bits for the command (or response)
3) 4 bits for an address

Now the address might refer to actual device hardware pins, or it might just refer to 16 different "things" which might be a hardware pin, or could also be a software controlled bit.

Here is an example of a simple command & response for an arduino input:
- Host writes &h50 to the serial port which means: request read bit, address 0
- Device receives &h50, reads a digital input and responds with:
- a) &h20, which means address 0 = low
- b) &h30, which means address 0 = high
- c) nothing, device ignored command

Here is an example of a simple command & response for an arduino output:
- Host writes &h74 to the serial port which means: request set bit, address 4
- Device receives &h74, write digital output high (or not), and responds with:
- a) &h24, which means address 4 = low (request denied)
- b) &h34, which means address 4 = high (request acknowledged)
- c) nothing, device ignored command

The main purpose for reserving the high bit, is that we probably want to expand on the protocol at some point to allow multi-byte messages, for example to transfer the value of analog values to/from the arduino. For now, we will keep it simple with just 16 addressable bits.

Now it is possible that either host or arduino could ignore commands, possibly on purpose without any response. For that we will need to maintain some timeout timers, but I'll save that for a later demo.

I'll post the code for the demo in the next post.
coderJeff
Site Admin
Posts: 4313
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Arduino Serial Communication Demo #4 (continued)

Post by coderJeff »

Arduino Serial Communication Demo #4 (continued)
FYI, read more description of demo #4 in the previous post

For this demo #4, we are using the following, hardware:

Code: Select all

         .---------.
KEY0 --->|         |---> LED0
KEY1 --->| Arduino |---> LED1
KEY2 --->|         |---> LED2
KEY3 --->|         |---> LED3
         '---------'
              |
           Serial
              |
         .---------.
KYBD --->|  host   |---> DISPLAY
         '---------'
For my example, since the switches I am using (KEY0 to KEY3) can be open, I've opted to enable the pullup resistors on the board. This has the effect of inverting the KEY status. It doesn't really matter though, because we are going to correct it in our command/response loop.

First the Arduino sketch:
Obviously, your exact construction of the circuit may, differ. Use the constants to configure which keys & leds are connected to each pin.

Code: Select all


/* note: set the led_?_pin and keypad_?_pin
 *  constants to match physical connections to
 *  the board
 */

#define keypad_0_pin 7 /* INPUT, button 0, ON/OFF */
#define keypad_1_pin 6 /* INPUT, button 1, ON/OFF */
#define keypad_2_pin 5 /* INPUT, button 2, ON/OFF */
#define keypad_3_pin 4 /* INPUT, button 3, ON/OFF */

#define led_0_pin 12 /* OUTPUT, LED 0, ON/OFF */
#define led_1_pin 11 /* OUTPUT, LED 1, ON/OFF */
#define led_2_pin 10 /* OUTPUT, LED 2, ON/OFF */
#define led_3_pin 9  /* OUTPUT, LED 3, ON/OFF */

void setup() {
  Serial.begin( 9600 );
  while( !Serial )
    delay(1);

  pinMode( keypad_0_pin, INPUT );
  pinMode( keypad_1_pin, INPUT );
  pinMode( keypad_2_pin, INPUT );
  pinMode( keypad_3_pin, INPUT );

  // the keypad is only switches, so turn
  // on the internal pull-up resistors.
  // Note: button is pressed when digitalRead() == LOW
  digitalWrite( keypad_0_pin, HIGH );
  digitalWrite( keypad_1_pin, HIGH );
  digitalWrite( keypad_2_pin, HIGH );
  digitalWrite( keypad_3_pin, HIGH );

  pinMode( led_0_pin, OUTPUT );
  pinMode( led_1_pin, OUTPUT );
  pinMode( led_2_pin, OUTPUT );
  pinMode( led_3_pin, OUTPUT );

  // write the general message ACK code, 
  // just to let the host know we are online
  Serial.write( 0x10 );

}

/*
 * write a message to Serial to respond to
 * adressable bit request
 */
void answer( uint8_t address, boolean status )
{
  uint8_t msg = (address & 0xf);

  if( status )
    msg |= 0x30; // bit is true
  else
    msg |= 0x20; // bit is false

  // send the answer
  Serial.write( msg );
}

void loop() {

  uint8_t msg ;
  if( Serial.available() > 0 )
    msg = Serial.read();
  else
    msg = -1;

  uint8_t cmd = msg & 0xf0; // command is upper 4 bits
  uint8_t adr = msg & 0x0f; // address is lower 4 bits
  
  /* bit addresses (for the serial protocol )
   *  0 = Keypad 0 (w/ pullup resistor, LOW=pressed)
   *  1 = Keypad 1 (w/ pullup resistor, LOW=pressed)
   *  2 = Keypad 2 (w/ pullup resistor, LOW=pressed)
   *  3 = Keypad 3 (w/ pullup resistor, LOW=pressed)
   *  4 = LED 0 (HIGH=ON) 
   *  5 = LED 1 (HIGH=ON)
   *  6 = LED 2 (HIGH=ON)
   *  7 = LED 3 (HIGH=ON)
   */

  switch( cmd )
  {
    case 0x50: // request, read bit
    {
      switch( adr )
      {
        case 0:
          answer( 0, digitalRead( keypad_0_pin )==LOW );
          break; 
        case 1:
          answer( 1, digitalRead( keypad_1_pin )==LOW );
          break; 
        case 2:
          answer( 2, digitalRead( keypad_2_pin )==LOW );
          break; 
        case 3:
          answer( 3, digitalRead( keypad_3_pin )==LOW );
          break; 
        default:
          break; 
      }
      break;
    }
    case 0x60: // request, clear bit (to LOW)
    {
      switch( adr )
      {
        case 4:
          digitalWrite( led_0_pin, LOW );
          answer( 4, false );
          break;
        case 5:
          digitalWrite( led_1_pin, LOW );
          answer( 5, false );
          break;
        case 6:
          digitalWrite( led_2_pin, LOW );
          answer( 6, false );
          break;
        case 7:
          digitalWrite( led_3_pin, LOW );
          answer( 7, false );
          break;
      }
      break;
    }
    case 0x70: // request, set bit (to HIGH)
    {
      switch( adr )
      {
        case 4:
          digitalWrite( led_0_pin, HIGH );
          answer( 4, true );
          break;
        case 5:
          digitalWrite( led_1_pin, HIGH );
          answer( 5, true );
          break;
        case 6:
          digitalWrite( led_2_pin, HIGH );
          answer( 6, true );
          break;
        case 7:
          digitalWrite( led_3_pin, HIGH );
          answer( 7, true );
          break;
      }
      break;
    }
    default:
      break;
  }
}
And the demo host program in fbc:

Code: Select all

#ifdef __FB_LINUX__
	const DEVICE_COM = "/dev/ttyACM0"
#else
	const DEVICE_COM = "COM7"
#endif
const DEVICE_FILE_NO = 1

/'
One Byte Serial Protocol

GENERAL I/O

	7 654 3210
	- --- ---- -------------------------
	0 100 mmmm general message (request)
	0 000 mmmm general message (false)
	0 001 mmmm general message (true)

	mmmm = message number (0 to 15)

ADDRESSABLE BIT I/O

	7 654 3210
	- --- ---- -----------------
	0 101 aaaa read bit request
	0 110 aaaa clear bit request
	0 111 aaaa set bit request

	0 010 aaaa status = false
	0 011 aaaa status = true

	aaaa = address (0 to 15)

RESERVED

	7 654 3210
	- --- ---- --------
	1 ddd dddd reserved

'/

function msgToString( byval msg as ubyte ) as string

	'' command is upper 4 bits
	dim cmd as ubyte = msg and &hf0

	'' address is lower 4 bits
	dim adr as ubyte = msg and &h0f

	select case cmd
	case &h00: function = "general NAK"
	case &h10: function = "general ACK"
	case &h20: function = "bit " & adr & " is false"
	case &h30: function = "bit " & adr & " is true"
	case &h40: function = "general msg #" & adr
	case &h50: function = "read bit " & adr
	case &h60: function = "clear bit " & adr
	case &h70: function = "set bit " & adr
	case else
		function = "reserved"
	end select

end function

'' show request on screen and send to device
sub request( byval msg as ubyte )

	'' show a human readable string for the command
	color 10
	print "command = " & hex(msg,2) & ", " & msgToString( msg )

	'' write to the device
	put #DEVICE_FILE_NO,,msg
end sub

'' show response on screen
sub response( byval msg as ubyte )

	'' show a human readable string for the command
	color 13
	print "response = " & hex(msg,2) & ", " & msgToString( msg )

end sub

'' ----------------
'' MAIN
'' ----------------

if( open com(DEVICE_COM & ":9600,n,8,1,cs0,ds0,cd0,rs" As #DEVICE_FILE_NO) <> 0 ) then
	print "error connecting to arduino"
	end 1
end if

print "press F1 for help, ESCAPE to exit"

do
	dim k as string = inkey

	select case k
	case chr(27) '' escape
		exit do
	case chr(&hff, &h3b) '' F1 - help
		color 7
		print "Commands:"
		print
		print "ESC          Exit program"
		print "0, 1, 2, 3   Read bit 0, 1, 2, 3   (buttons)"
		print "a, b, c, d   Set bit 4, 5, 6, 7    (LEDs)"
		print "A, B, C, D   Clear bit 4, 5, 6, 7  (LEDs)"
		print
	case "0": request( &h50 ) '' read bit 0
	case "1": request( &h51 ) '' read bit 1
	case "2": request( &h52 ) '' read bit 2
	case "3": request( &h53 ) '' read bit 3
	case "A": request( &h64 ) '' clear bit 4
	case "B": request( &h65 ) '' clear bit 5
	case "C": request( &h66 ) '' clear bit 6
	case "D": request( &h67 ) '' clear bit 7
	case "a": request( &h74 ) '' set bit 4
	case "b": request( &h75 ) '' set bit 5
	case "c": request( &h76 ) '' set bit 6
	case "d": request( &h77 ) '' set bit 7
	case is > ""
		color 7
		print "key = ";
		for i as integer = 1 to len(k)
			print hex(asc(k,i),2); " ";
		next
		print
	end select

	dim msg as ubyte = 0

	while( loc(DEVICE_FILE_NO) > 0 )
		get #DEVICE_FILE_NO,,msg
		response( msg )
	wend

	sleep 25

loop

close #DEVICE_FILE_NO
color 7
I've tried to keep the listings a simple as possible. Even though there is an opportunity to factor some common code, I have left the repetition in the code in the hopes that it is easiest to see the pattern.

If anyone reading this has some experience with multi-threaded programs, you may start to see some parallels with serial communication. We have 2 separate threads (fbc & arduino) each doing their own thing. They do not share any data. However, all data is communicated and serialized though the serial port connection. The serial port also provides a kind of hardware mutex in that reading on one side will not interfere with writing on the other side. The only thing missing so far is some kind of conditional wait for synchronizing. However, ideally, we want both sides to just keep on running and never wait for the other side. This will be possible if each side of the serial connection only acts on each others responses.

Summary thought: our goal is to write this control scheme / design so that neither side of the serial port communication is ever waiting for the other side to finish. -- more on this in a later (maybe next) demo.
Dr_D
Posts: 2451
Joined: May 27, 2005 4:59
Contact:

Re: FreeBasic communication with Arduino logic error?

Post by Dr_D »

Hey guys, I have another question... more like a problem I'm unable to solve myself, so I'm sorry if this is a lengthy post.

I've wired up a binary clock led display using some SN74HC595N IC's as shift registers because this project requires more pins than the Arduino offers. To get to the meat of it, I'm having trouble reading the data that FB sends using CoderJeff's serial coms library. Like I said, it's a binary clock, so from FB, I'm just sending a string of 18 characters, where each character is either 1 or 0, and then I add the terminator character to the end of the string. I'm having difficulty translating this string into the data I need in Arduino to control the leds.

So, this is the 18 character string I'm sending from the FreeBASIC side...

Code: Select all

sHour   = bin(val(mid(aTime,1)), 6)
	sMinute = bin(val(mid(aTime,4)), 6)
	sSecond = bin(val(mid(aTime,7)), 6)
	
	dim as string k = sHour+sMinute+sSecond+chr(13)
On the Arduino side, I've tried several things, but nothing has worked so far. As a matter of fact, it seems like some of my tests have only returned garbage. How can I read the string on the Arduino side, bit by bit?


On a side note, I even tried adding this to your class MessageBuffer...

Code: Select all

public:

	char myChar( int i )
	{
	    return msg_buffer[i];
	}
BasicCoder2
Posts: 3906
Joined: Jan 01, 2009 7:03
Location: Australia

Re: FreeBasic communication with Arduino logic error?

Post by BasicCoder2 »

Dr_D wrote: How can I read the string on the Arduino side, bit by bit?
A string is a list of characters. Read each character one at a time in a for loop.

Are you using any online tutorial example?

Test your Arduino code using the Serial Monitor.

Type in your 18 long string in the edit box then click the Send button.
It would probably help if you posted the Arduino code.
In the main loop I imagine it reads the string and then loops through each character.
If the character is '1' then it pulses out a HIGH. If the character is a '0' then it pulses out LOW?

If each shift register is only to display 6 LEDS then you need send out 2 dummy LOWS as well.
Dr_D
Posts: 2451
Joined: May 27, 2005 4:59
Contact:

Re: FreeBasic communication with Arduino logic error?

Post by Dr_D »

I'll post the arduino code when I get home... But, yeah, I'm iterating through the list, and I seem to keep getting garbage. I didn't know about serial monitor... Thanks!
Dr_D
Posts: 2451
Joined: May 27, 2005 4:59
Contact:

Re: FreeBasic communication with Arduino logic error?

Post by Dr_D »

Here is my Arduino sketch...

Code: Select all

//This is the Arduino tutorial for daisy-chaining shift registers that I followed...
//https://www.marginallyclever.com/2017/02/daisy-chain-74hc595n-shift-registers/

#include "MessageBuffer.h"

static MessageBuffer msg;

const int SER = 12;   // (595 pin 14)serial input line goes to the *first* shift register... daisy chain, so each QH(PIN 9) goes to the next SER, etc, etc...
const int SRCLK = 11; // (595 pin 11)the serial clock line, or the sync pulse, normal state is low... write, high, back to low, etc...
const int SRCLR = 10; // (595 pin 10)clear memory line, normal state is high... drop to low then back to high to clear memory...
const int RCLK = 9;   // (595 pin 12)copy register line, normal state is low... send high then back to low to copy states...
const int OE = 8;     // (595 pin 13)enable output line... send low to enable, high to disable...

#define TOTAL_SHIFT_PINS  18

void setup()
{
  Serial.begin( 9600 );
  while ( !Serial )
    delay(1);

  Serial.print( "Arduino is online - demo 3\n" );

  pinMode(SER, OUTPUT);
  pinMode(SRCLK, OUTPUT);
  pinMode(SRCLR, OUTPUT);
  pinMode(RCLK, OUTPUT);
  pinMode(OE, OUTPUT);


  SR_clearRegisters();
  SR_copyToStorage();
  SR_turnOutputsOn();
}

void loop()
{
  int testText[18];
  int cnt = 0;
  while ( Serial.available() > 0 )
  {
    int16_t ch = Serial.read();
    if (cnt < 18) testText[cnt] = (int)ch;
    cnt += 1;
    if ( msg.addChar( ch ) )
      break;
  }

  if ( msg.isComplete() )
  {
    if ( strcmp( "hello", msg.text() ) == 0 )
    {
      Serial.print( "HELLO back at you!\n" );
    }

    else
    {
      //this is just a test case to make sure the IC/led logic is working properly
      int inData[18]; //= {0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
      for (int i = 0; i < TOTAL_SHIFT_PINS; i++)
      {
        inData[i] = random(0,2);
      }      

      SR_turnOutputsOff();
      for (int i = 0; i < TOTAL_SHIFT_PINS; i++)
      {
        if (testText[i] == 0)
        //switch this out to test the wiring with rtandom values...
        //if (inData[i] == 0)
        {
          SR_shiftDataIn(LOW);
        }
        else
        {
          //ditto...
          SR_shiftDataIn(HIGH);
        }
        SR_copyToStorage();
      }
      SR_turnOutputsOn();

    }


    // we are done with the command message now, so clear it.
    msg.clear();
  }
}




void SR_turnOutputsOn()
{
  digitalWrite(OE, LOW);
}

void SR_turnOutputsOff()
{
  digitalWrite(OE, HIGH);
}

void SR_clearRegisters()
{
  digitalWrite(SRCLR, LOW);
  digitalWrite(SRCLR, HIGH);
}

void SR_shiftDataIn(int data)
{
  digitalWrite(SER, data);
  digitalWrite(SRCLK, HIGH);
  digitalWrite(SRCLK, LOW);
}

void SR_copyToStorage()
{
  digitalWrite(RCLK, HIGH);
  digitalWrite(RCLK, LOW);
}
coderJeff
Site Admin
Posts: 4313
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Arduino Serial Communication Concepts

Post by coderJeff »

Dr_D wrote:On the Arduino side, I've tried several things, but nothing has worked so far. As a matter of fact, it seems like some of my tests have only returned garbage. How can I read the string on the Arduino side, bit by bit?
I will try to describe a couple of concepts that apply to both the Arduino sketch and the fbc program, that may help to get an understanding of what's going on (or why the results are not the expected results). And, in the next post, I will give a demo, hopefully that shows all this clearly.

1) Know the message

The possibilities for a format / protocol for a message sent over a serial wire are countless. Some message formats are defined in standards. Here we are creating a custom message format.

The question that has to answered is, what is a valid message? Once that is defined, some of the other programming becomes easier, as we check for either a good message or a bad message. If it's a good message, use it. If it's a bad message, then recover; either ignore it and wait for the next message, or report an error back to the sender.

Dr_D, in your case the message is a binary time code:
- 18 bytes, either "1" or "0"
- followed by chr(13), aka CR, carriage return
- get anything else? it's a bad message



2) Serial communication is separate from program execution.

Compared to serial communication, the statements in loop() procedure execute fast. It's possible that some serial data has be received, but there might be more data on the way. And if the next byte isn't quite ready to be read, there is no more data immediately available, so execution continues.

So, there's no guarantee that the call to loop() or the serial statements are going to synchronize.


3) Non-blocking (no wait) serial communication

There are some kinds of communication where each end of the conversation waits for some amount of time (or forever) for the other side to communicate. Though I feel, if my memory serves me correctly, that this is usually controlled with some hardware hand-shaking. We are not using any hardware flow control, and we are not waiting on the other device to communicate or finish it's message. Because, if the two devices get out of sync, we can deadlock; just like threading where 2 threads are each waiting for the other, and therefore never continue.

For us, whenever we poll for serial input, we just read what's there and move on, even if we only get part of a message, or no message at all.


4) The Arduino loop() is a procedure (not actually a loop)

The loop() is called many times, over and over. It's just like a procedure, so local variables are destroyed and reset on next entry. The only way to preserve state between calls is to use static storage.

In our case, since we are non-blocking, possibly only receiving part messages at a time, we need to preserve the current state at the end of loop() so we can continue the next time loop() is entered.


I could go on a little more, but let's get the demo code posted...
coderJeff
Site Admin
Posts: 4313
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Arduino Serial Communication Demo #5

Post by coderJeff »

Arduino Serial Communication Demo #5

For this demo, we don't need to build anything. We are using:
- Serial Port
- LED_BUILTIN for an output

We are also not using any classes. Hopefully the program listings show the sequence more than anything. This demo should feel like it goes with the concepts listed in the previous post.

Image

fbc host program:
- note that we are using chr(13) for the "end of message" character, just to be different. In previous demos, I had been using chr(10) as the end of message character.

Code: Select all

#ifdef __FB_LINUX__
	const DEVICE_COM = "/dev/ttyACM0"
#else
	const DEVICE_COM = "COM7"
#endif
const DEVICE_FILE_NO = 1

'' ----------------
'' MAIN
'' ----------------

if( open com(DEVICE_COM & ":9600,n,8,1,cs0,ds0,cd0,rs" for binary As #DEVICE_FILE_NO) <> 0 ) then
	print "error connecting to arduino"
	end 1
end if

print "press F1 for help, ESCAPE to exit"

do
	dim k as string = inkey

	select case k
	case chr(27) '' escape
		exit do
	case chr(&hff, &h3b) '' F1 - help
		color 7
		print "Commands:"
		print
		print "ESC          Exit program"
		print "t            show current time"
		print "w            write current time to arduino"
		print

	'' display current time? or write timecode to device?
	case "t", "w"
		dim as string aTime, sHour, sMinute, sSecond, sTimeCode
		aTime   = time
		sHour   = bin(val(mid(aTime,1,2)), 6)
		sMinute = bin(val(mid(aTime,4,2)), 6)
		sSecond = bin(val(mid(aTime,7,2)), 6)
		sTimeCode = sHour+sMinute+sSecond+chr(13)

		color 12
		print "Time     = "; aTime

		if( k = "w" ) then
			color 10
			print "TimeCode = "; sTimeCode
			put #DEVICE_FILE_NO,,sTimeCode
		end if
	end select

	'' show whatever the device sends us...
	while( loc(DEVICE_FILE_NO) > 0 )
		dim b as ubyte
		get #DEVICE_FILE_NO,,b
		color 13
		select case b
		case 32 to 127
			print chr(b);
		case else
			print "<"; hex(b,2); ">";
		end select

		'' CR or LF?  don't care, just print a new line
		if( b = 10 or b = 13 ) then
			print
		end if
	wend

	sleep 25, 1

loop

close #DEVICE_FILE_NO
color 7
And the arduino sketch:

Code: Select all

/*
 * Time Code Message Format:
 * hhhhhhmmmmmmssssss<CR>
 * 
 * hhhhhh = hours in binary text format ('0' or '1' characters only)
 * mmmmmm = minutes in binary text format ('0' or '1' characters only)
 * ssssss = seconds in binary text format ('0' or '1' characters only)
 * <CR>   = end of message character chr(13)
 * 
 * 19 bytes total
 */

void setup() {

  // use the on-board LED to show something
  pinMode(LED_BUILTIN, OUTPUT);
  
  Serial.begin( 9600 );
  while( !Serial )
    delay(1);

  Serial.print( "Arduino is online - demo 5\n" );
}

void loop()
{
  // preserve inTimeCode[] and inCount between calls,
  // we may not receive the entire message in one loop through
  static uint8_t inTimeCode[19];
  static int inCount = 0;
  int16_t ch = -1;

  // read one char at a time (basically guarantees we never
  // see the whole message in one call to loop()
  ch = Serial.read();

  // valid char? add it to inTimeCode[]
  if( ch != -1 )
  {
    inTimeCode[inCount] = ch;
    inCount += 1;
  }

  // buffer full, but no CR? it's bad, start over
  if( (ch != 13) && (inCount == 19) )
  {
    inCount == 0;
    Serial.write( "bad time code, too long" );
    Serial.write( 13 );
  }

  // got a CR, but buffer not full yet? it's bad, start over
  if( (ch == 13) && (inCount != 19) )
  {
    inCount == 0;
    Serial.write( "bad time code, too short" );
    Serial.write( 13 );
  }

  // got a CR and buffer full? maybe it's OK... let's check
  if( (ch == 13) && (inCount == 19) )
  {
    for( int i = 0; i < 18; ++i )
    {
      if( (inTimeCode[i] != '0') && (inTimeCode[i] != '1') )
      {
        // bad data, start over
        inCount == 0;
        Serial.write( "bad time code, invalid data" );
        Serial.write( 13 );
        break;
      }
    }
    // we already know last char is CR, so no need to check that
  }

  // still have a full buffer?  it has to
  // be good... we threw all the bad data away
  if( inCount == 19 )
  {
    // toggle the status of LED_BUILTIN to show we got a valid time code
    digitalWrite( LED_BUILTIN, !digitalRead( LED_BUILTIN ) );
    
    // send it back to the host, one bit at a time for fun...
    for( int i = 0; i < 18; ++i )
    {
      Serial.write( inTimeCode[i] );
      Serial.write( 32 );
    }
    Serial.write( 13 );
    
    // done with the valid time code, start over, so we can get the next one
    inCount = 0;
  }
}
Dr_D
Posts: 2451
Joined: May 27, 2005 4:59
Contact:

Re: FreeBasic communication with Arduino logic error?

Post by Dr_D »

Thanks dude. This is awesome. I need to take some time to wrap my head around it. I still don't understand why it isn't over when if(serial) is over... but all I can do is learn what's going on. Thanks again. :)

EDIT:
At the time of this post, I haven't even read most of it...
coderJeff
Site Admin
Posts: 4313
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Re: FreeBasic communication with Arduino logic error?

Post by coderJeff »

No problem.

Overall, we are only using a handful of commands for serial communication. Open COM, Loc(), Get#, Put#, etc. And the use of those commands is not so different from file I/O operations. Except for file I/O, the OS and drivers takes care of the timing, buffering, and flow control, with the hardware, and we hardly need to be concerned with it. With serial communication, the issues are much more exposed, especially when devices have limited resources or speeds of components are very different.

I think I have a fairly good understanding, but I may not explain it well, so I appreciate the comments and feedback. It kind of helps direct my next effort.
coderJeff
Site Admin
Posts: 4313
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Arduino Serial Communication Demo #6

Post by coderJeff »

Arduino Serial Communication Demo #6

This demo is a simulation of serial communication. We don't need any connected devices for this program to run.

It is intended to show what's happening in the serial communication as the message gets passed from program to serial port to device, etc.

Image

Things to try:
- type on the keyboard to start messages
- try toggling buffering on/off for different components (messages will need an enter to be processed)
- try changing the speeds of the host, serial com-link, and device, to see what happens with message delivery
- try flooding the buffers with lots of key presses to overflow buffers and drop characters

Here is the listing. Sorry, the code is not well documented, as the main purpose for this program is the simulation.

Code: Select all

'' Serial Communication Simulation
''
'' displays graphical simulation of
'' host communicating with device
'' over serial

'' character size
const SX = 8, SY = 8

'' helper macro to clamp values
#macro clamp( arg, min, max )
	if( arg < min ) then
		arg = min
	elseif( arg > max ) then
		arg = max
	end if
#endmacro

'' Timer class to help manage timers
'' when created or after reset()
'' counts number of ticks
'' status is true (signalled) when
'' current time > duration
type OnDelayTimer
	static time0 as integer
	declare static sub nexttick()

	time1 as integer
	duration as integer
	declare constructor( byval ondelay as integer )
	declare function status() as boolean
	declare sub reset()
end type
dim shared OnDelayTimer.time0 as integer

constructor OnDelayTimer( byval in_duration as integer )
	duration = in_duration
end constructor

function OnDelayTimer.status() as boolean
	return (time0 >= time1+duration)
end function

sub OnDelayTimer.nexttick()
	time0 += 1
end sub

sub OnDelayTimer.reset()
	time1 = time0
end sub

enum TIMER_ENUM
	TIMER_NONE = -1
	TIMER_GLOBAL = 0
	TIMER_HOST
	TIMER_SERIAL
	TIMER_DEVICE
	TIMER_COUNT
end enum

dim timers( 0 to TIMER_COUNT-1 ) as OnDelayTimer = _
	{ _
		OnDelayTimer( 0 ), _
		OnDelayTimer( 10 ), _
		OnDelayTimer( 30 ), _
		OnDelayTimer( 20 ) _
	}

enum STYLE_ENUM
	STYLE_BOX
	STYLE_WIRE
end enum

'' component class to manage the on-screen components

type Component
	title as string
	row1 as integer
	col1 as integer
	rows as integer
	cols as integer
	text as string
	scroll as boolean
	maxRemoveChar as integer
	timerId as TIMER_ENUM
	style as STYLE_ENUM
	eolChar as integer
	borderColor as integer
	declare constructor _
		( _
			byref in_title as string, _
			byval in_row1 as integer, byval in_col1 as integer, _
			byval in_rows as integer, byval in_cols as integer, _
			byval in_color as integer, _
			byval in_timerId as TIMER_ENUM = TIMER_NONE, _
			byval in_style as STYLE_ENUM = STYLE_BOX _
		)
	declare sub add( byref k as const string )
	declare function remove( byval count as integer = 0, byval eol as integer = 0 ) as string
	declare sub render()
end type

constructor Component _
	( _
		byref in_title as string, _
		byval in_row1 as integer, byval in_col1 as integer, _
		byval in_rows as integer, byval in_cols as integer, _
		byval in_color as integer, _
		byval in_timerId as TIMER_ENUM = TIMER_NONE, _
		byval in_style as STYLE_ENUM = STYLE_BOX _
	)
	title = in_title
	row1 = in_row1
	col1 = in_col1
	rows = in_rows
	cols = in_cols
	borderColor = in_color
	timerId = in_timerId
	style = in_style
end constructor

sub Component.add( byref k as const string )
	if( scroll ) then
		text &= k
		while( len(text) > rows * cols )
			text = mid( text, cols+1 )	
		wend
	else
		text = left( text & k, rows * cols )
	end if
end sub

function Component.remove( byval count as integer = 0, byval eol as integer = 0 ) as string
	if( count <= 0 ) then
		count = len(text)
	end if
	if( eol > 0 ) then
		count = instr( text, chr(eol) )
	end if
	function = left( text, count )
	text = mid( text, count + 1 )
end function

sub Component.render()
	dim x1 as single = (col1-1) * SX
	dim y1 as single = (row1-1) * SY
	dim x2 as single = (col1+cols-1) * SX
	dim y2 as single = (row1+rows-1) * SY

	if( style = STYLE_WIRE ) then
		line ( x1+SX\2, y1+SY\2 ) - ( x2, y2-SY\2 ), 8, , &hf0f0

		'' text
		for row as integer = 0 to rows-1
			dim s as string = mid( text, row*cols+1, cols )
			draw string ( (x1 + x2 + len(s)*SX)\2, y1+(row*SY) ), s, 15
		next

	else
		'' title
		dim s as string = title
		if( eolChar <> 0 ) then
			s &= " (need CR)"
		end if

		draw string( x1 - SX\2, y1-(2*SY) ), s, 7

		'' fill
		if( text > "" ) then
			line ( x1-2, y1-2 ) - ( x2+2, y2+2 ), 1, bf
		end if

		'' border
		line ( x1-3, y1-3 ) - ( x2+3, y2+3 ), borderColor, b

		'' text
		for row as integer = 0 to rows-1
			draw string ( x1, y1+(row*SY) ), mid( text, row*cols+1, cols ), 15
		next
	end if

end sub

enum COMPONENT_ID
	CMP_DISPLAY
	CMP_HOST_IN
	CMP_HOST_RX
	CMP_WIRE1
	CMP_DEV_TX
	CMP_DEV_OUT
	CMP_DEV_IN
	CMP_DEV_RX
	CMP_WIRE2
	CMP_HOST_TX
	CMP_HOST_OUT
	CMP_KEYBOARD

	COMPONENT_COUNT
end enum

'' ----
'' MAIN
'' ----

dim components( 0 to COMPONENT_COUNT-1 ) as Component = _
	{ _
		Component( "Display",     5,  5, 5, 16, 15, TIMER_HOST ), _
		Component( "Host In",    15,  5, 1, 16, 14, TIMER_HOST ), _
		Component( "Serial In",  20,  5, 1, 16,  8, TIMER_SERIAL ), _
		Component( "Wire 1",     20, 21, 1, 38,  8, TIMER_SERIAL, STYLE_WIRE ), _
		Component( "Serial Out", 20, 60, 1, 16,  8, TIMER_DEVICE ), _
		Component( "Device Out", 25, 60, 1, 16, 14, TIMER_DEVICE ), _
		Component( "Device In",  30, 60, 1, 16, 14, TIMER_DEVICE ), _
		Component( "Serial In",  35, 60, 1, 16,  8, TIMER_SERIAL ), _
		Component( "Wire 2",     35, 21, 1, 38,  8, TIMER_SERIAL, STYLE_WIRE ), _
		Component( "Serial Out", 35,  5, 1, 16,  8, TIMER_HOST ), _
		Component( "Host Out",   40,  5, 1, 16, 14, TIMER_HOST ), _
		Component( "Keyboard",   45,  5, 1, 16, 15, TIMER_NONE ) _
	}

components( CMP_DISPLAY ).scroll = true
components( CMP_HOST_TX ).maxRemoveChar = 1
components( CMP_DEV_TX ).maxRemoveChar = 1
components( CMP_WIRE1 ).maxRemoveChar = 1
components( CMP_WIRE2 ).maxRemoveChar = 1

dim page as integer = 0
screenres 640, 480, 8, 2
screenset 1-page, page
dim paused as boolean

do
	if( not paused ) then
		timers( TIMER_GLOBAL ).nexttick()
	end if
		
	dim k as string = inkey

	'' input
	select case k
	case chr(27) '' escape
		exit do
	case chr(9)
		paused = not paused
	case chr(13) '' enter
		components( CMP_KEYBOARD ).add( k )
	case chr(&hff,&h3b) '' F1
		timers( TIMER_HOST ).duration += 1
	case chr(&hff,&h54) '' Shift+F1
		timers( TIMER_HOST ).duration -= 1
	case chr(&hff,&h3c) '' F2
		timers( TIMER_SERIAL ).duration += 1
	case chr(&hff,&h55) '' Shift+F2
		timers( TIMER_SERIAL ).duration -= 1
	case chr(&hff,&h3d) '' F3
		timers( TIMER_DEVICE ).duration += 1
	case chr(&hff,&h56) '' Shift+F3
		timers( TIMER_DEVICE ).duration -= 1
	case chr(&hff,&h3e) '' F4
		components( CMP_DISPLAY ).remove()
	case chr(&hff,&h3f) '' F5
		components( CMP_HOST_OUT ).eolChar xor=13
	case chr(&hff,&h40) '' F6
		components( CMP_DEV_IN ).eolChar xor=13
	case chr(&hff,&h41) '' F7
		components( CMP_DEV_OUT ).eolChar xor=13
	case chr(&hff,&h42) '' F8
		components( CMP_HOST_IN ).eolChar xor=13
	case chr(&hff,&h43) '' F9
		for i as integer = 0 to COMPONENT_COUNT-1
			components( i ).text = ""
			components( i ).eolChar = 0
		next
	case chr(32) to chr(126)
		components( CMP_KEYBOARD ).add( k )
	end select

	clamp( timers( TIMER_HOST ).duration, 0, 100 )
	clamp( timers( TIMER_SERIAL ).duration, 0, 100 )
	clamp( timers( TIMER_DEVICE ).duration, 0, 100 )

	'' simulation - move the data through the pipe line
	for i as integer = 0 to COMPONENT_COUNT-2
		if( timers( components(i).timerId ).status() ) then
			components(i).add( components(i+1).remove( components(i+1).maxRemoveChar, components(i+1).eolChar ) )
		end if
	next

	'' reset all elapsed timers
	for i as integer = 1 to TIMER_COUNT-1
		if( timers(i).status() ) then
			timers(i).reset()
		end if
	next

	'' render
	cls
	for i as integer = 0 to COMPONENT_COUNT-1
		components(i).render()
	next

	if( paused ) then
		color 12: locate 27, 20: print "---<<< P A U S E D >>>---"
	end if

	locate 5, 30
	color 15: print using "Host Speed  : ###%"; 100 - timers( TIMER_HOST ).duration;
	color  7: print "  (F1/Shift+F1 to adjust)"

	locate 7, 30
	color 15: print using "Serial Speed: ###%"; 100 - timers( TIMER_SERIAL ).duration;
	color  7: print "  (F2/Shift+F2 to adjust)"

	locate 9, 30
	color 15: print using "Device Speed: ###%"; 100 - timers( TIMER_DEVICE ).duration;
	color  7: print "  (F3/Shift+F3 to adjust)"

	color 10: locate 48, 5: print "Type keys to start"
	color  7: locate 50, 5: print "ESC to EXIT"

	color  7: locate 40, 30: print "F4 to clear display"
	color 14
	locate 42, 30: print "F5 to toggle 'Host Out' buffering"
	locate 44, 30: print "F6 to toggle 'Device In' buffering"
	locate 46, 30: print "F7 to toggle 'Device Out' buffering"
	locate 48, 30: print "F8 to toggle 'Host In' buffering"

	color 7
	locate 50, 30: print "F9 to reset everything"

	'' page flip
	page = 1-page
	screenset 1-page, page
	sleep 25, 1
loop
A couple notes about the listing:
- the simulation is constructed using a singly linked list of components
- data moves from one component to the next based on timers and other properties
- the simulation "timing" is done by "number of frames" and there is a simple hard-coded sleep 25 for display timing
- TAB will pause simulation
Dr_D
Posts: 2451
Joined: May 27, 2005 4:59
Contact:

Re: FreeBasic communication with Arduino logic error?

Post by Dr_D »

/*
Well, I've come to a conclusion. I don't think a serial connection is suitable for a binary clock. I can now pass the data correctly, but unpredictability of the timing of when the buffer is actually full isn't reliable enough for something like a clock. However, I have learned a ton. I can make my Christmas lights program now, which doesn't rely on any certain time interval. Program the data, send it, then it doesn't matter when it gets there... You'll just have to wait. lol
I'd like to say thanks again, to everyone who has participated in this. I've never dabbled with serial communication before, and I have learned a lot. :)
*/


Edit:

Here's a picture of it... It does work, but as I said, the reliability of the serial connection is an issue.

Image

Edit+1:
Ok... I can't explain it, but the whole system fails, once in a while. It recovers on the next send, but I can't explain why all the lights drop off for one frame. Like everything drops off... No lights at all... Then, it recovers and displays the correct time again. I can't figure it out because it's so unpredictable.
BasicCoder2
Posts: 3906
Joined: Jan 01, 2009 7:03
Location: Australia

Re: FreeBasic communication with Arduino logic error?

Post by BasicCoder2 »

Your image doesn't show up for me.

I had to hold the right button down over the icon to get the image url and paste that into google in order to view the image.

I use,
https://postimages.org/

I will have to order some of those SN74HC595N IC's to give it a go.

Are you going to post your code? Maybe someone will figure out the reason you get the once in a while system fail?
Post Reply