Home
JAQForum Ver 24.01
Log In or Join  
Active Topics
Local Time 20:00 27 Nov 2024 Privacy Policy
Jump to

Notice. New forum software under development. It's going to miss a few functions and look a bit ugly for a while, but I'm working on it full time now as the old forum was too unstable. Couple days, all good. If you notice any issues, please contact me.

Forum Index : Microcontroller and PC projects : Little prod needed with serial input...

Author Message
Grogster

Admin Group

Joined: 31/12/2012
Location: New Zealand
Posts: 9308
Posted: 03:48pm 19 Jun 2013
Copy link to clipboard 
Print this post

Hello everyone.


I have finally got around to playing with the serial ports.

Problem is, that I can't read anything from the serial port buffer.

Here is my code:


Cls

Open "com2:2400, 128, SerialInt, 15" As #2

start:
Print CLR$(Green) "Waiting for data..."

Loopything:
Print @(100,150) + CLR$(Blue) Time$
GoTo loopything


SerialInt:
Print CLR$(Red) "Data received."
Input$(20, #2)
Print CLR$(Yellow) input$
IReturn


This is just a VERY simple tester, so I can get used to working with the serial ports. All it does, is open COM2 with 128 byte buffer, a serial interrupt loop, and at least 15 bytes must be present in the buffer before the interrupt loop takes place.

This would essentially seem to be working, as when I run the program:

- "Waiting for data..." shows up
- Clock shows up and "Ticks"
- If I send LESS then 15 bytes, nothing happens, which sounds right.
- When I send COM2 about 25 bytes, I get the "Data received." message.
- No data is displayed, and I get a question mark asking for some kind of input.

First version of my tinkering code had Line Input #2, a$ as the reading command, but this did not work, and after reading the manual some more, decided that Line Input is only for input from SD card files.

More reading led me to the INPUT$ function(page 38), which states that "When reading from a serial communications port, this will return as many characters as are waiting in the receive buffer up to 'nbr'"

Great.


But the INPUT$(20, #2) is not working - what am I doing wrong?
The 20 is to tell the code I want 20 characters from the serial receive buffer.

Also, while I am here, how do you reset the serial receive pointer?

IE: If I read 20 bytes from the buffer in one command, how do I read more in the next command, OR is it simply along the lines of: Read once - here are your bytes - the rest of the buffer is now discarded and the pointer automatically goes back to the beginning of the buffer memory for the next serial that comes along.

ADDITIONAL: Just for the forum members information, Data being sent to MM is from a PICAXE chip, 5v type, True-type comms, 2400 baud 8N1 - pretty much standard. I don't have a pull-up to D0 on the MM - perhaps I should add one?
Edited by Grogster 2013-06-21
Smoke makes things work. When the smoke gets out, it stops!
 
BobD

Guru

Joined: 07/12/2011
Location: Australia
Posts: 935
Posted: 04:06pm 19 Jun 2013
Copy link to clipboard 
Print this post

Not sure about this so test it. The input$ is a function. You use it to get the data from the serial port then you use it again but you have already emptied the serial port. Try assigning the first input$ to a variable then print the variable.

edit
Looking further, when you display your data you may have to put a delay in the program so you can actually see the data before "Waiting for data" clobbers it.Edited by BobD 2013-06-21
 
Grogster

Admin Group

Joined: 31/12/2012
Location: New Zealand
Posts: 9308
Posted: 04:08pm 19 Jun 2013
Copy link to clipboard 
Print this post

AHHHHH - you have a point there - let me check that....

EDIT: Kudos, BobD!

That was it exactly.

I made that mistake ages ago with INKEY$ - surprised I did not think of that.

On the buffer pointer - am I correct then, that once you issue a command such as A$=Input$(20, #2), that the process of copying the data out of the buffer, empties it, so you can't go back an read again from the same data?

Read everything in one go, in other words, and sort it out from there, because reading from the buffer, empties it as part of the process - correct?Edited by Grogster 2013-06-21
Smoke makes things work. When the smoke gets out, it stops!
 
BobD

Guru

Joined: 07/12/2011
Location: Australia
Posts: 935
Posted: 04:15pm 19 Jun 2013
Copy link to clipboard 
Print this post

Scratch that crap of mine in the edit to my last posting.

You may be able to do this
SerialInt:
Print CLR$(Red) "Data received."
' removed Input$(20, #2)
Print CLR$(Yellow) input$(20, #2)
IReturn
 
Grogster

Admin Group

Joined: 31/12/2012
Location: New Zealand
Posts: 9308
Posted: 04:20pm 19 Jun 2013
Copy link to clipboard 
Print this post

Will try the changed code shortly.

I have discovered that the receive buffer does NOT discard what is there, and the pointer stays at the position of the last byte sent to the code.

I used the text: "The quick brown fox jumps over the lazy dog." as a test, and as soon as I send that, the code goes through about three loops automatically. I could understand that, as with each loop, there is still at least 15 bytes left in the buffer that have NOT been read into the code yet, so it loops...

Will try your new code now Bob...

EDIT: Print CLR$(Yellow) Input$(15, #2) works fine. Edited by Grogster 2013-06-21
Smoke makes things work. When the smoke gets out, it stops!
 
MicroBlocks

Guru

Joined: 12/05/2012
Location: Thailand
Posts: 2209
Posted: 05:40pm 19 Jun 2013
Copy link to clipboard 
Print this post

Grogster,

Always read everything when you get an interrupt when data is available.
It is good practice and will save you some difficult debugging time later.

In the PIC32 (rs232 peripheral) the serial data once it gets into the PIC32 gets moved into a small buffer that is part of the peripheral. When that buffer meets a threshold an interrupt is generated which should be handled as quickly as possible. The Program that handles it normally stores that in a larger memory buffer right away and exits the interrupt routine. The main program loop then checks for data available in that memory buffer.

MMBasic uses bitbanging for rs232 as the peripherals are all used by the VGA and SD card. This bitbanging responds to a startbit and then starts counting to read the level of the pin somewhere close to the middle of a pulse. It then assembles a byte and stores it in memory buffer (128 bytes in your example). When it has 15 bytes in that memory buffer MMBasic gets control through the interrupt. Be aware that serial data is still coming in and depending on the speed it can go really quick.

Because of that you need to handle the interrupt as quickly as possible and at the same time empty that memory buffer so that it can be used again.
If you read all available data in a string the memory buffer is empty and your program will get time to process it. When you read only what you need you have a risk that the memory buffer will overrun.

When that happens you should first check why that happens, because it often signals that a part of your program is not fast enough. Don't just make the buffer bigger and hope for the best.

In short, when you get an interrupt, read all data, store it in a string (which actually limits it to 255 bytes per Input$().
If then number of bytes read is 255 that means you have read the maximum amount in one time and need to use another Input$.
The easiest way to do that is like this

[code]
SerialInt:
DO
a$ = INPUT$(255, #2)
'user function Store to be written, only add to 'Store' when a$ contains data
if a$ <> "" then Store(a$)
LOOP UNTIL a$ =""
IReturn
[/code]


These interrupt problems are often solved in a "producer-store-consumer" fashion.
It means the interrupt routine 'produces' data and hands it over to the store for storage.
A consumer then requests the store for data.
It is one of the easier ways to handle these kind of situations.
The only 'difficult' part is the store, because the store has to handle the bookkeeping.

It works like this:
Store receives data from a producer. Store looks for available storage room and stores the data there. It updates a counter to know how much data is present.

Store gets a request from a consumer. Store looks at the counter to see if data is available. If the counter is zero the Store returns an empty answer "" or goes into a loop waiting for data to arrive.

When the store receives data from the producer while there is no more storage left you have a problem! It is the right place to find out this problem. First you check if the store can be more efficient. The you either speed up the consumer or make the producer produce less. If that fails your only solution is more storage.
The great thing about a store is that you have one place to handle the storage. If memory is a problem you can allocated more string space or if there is a lot of data add a serial RAM chip and store it there. The producer and consumer could care less and don't have to be changed. Isolating storage is ideal and makes programs manageable.

Note: There can be multiple producers and multiple consumers. For instance you have a temperature sensor, pressure sensor and moisture sensor. All three would be producers of data. If there is only one consumer then you can put the logic to distinguish the messagestype in the consumer, if there is a consumer for each type of message the store needs to store the data of each type in a separate storage and keep a counter for each. Alternatively you can have three stores. The concept is very flexible and lets you make the choice where to do the most work. I prefer to do the work in the store so that both producer and consumer can be made very simple.
Meanwhile the main program can display information wait for user input etcetera while all this happens in the 'background'.


A sample of a consumer:
[code]
consumer:
a$ = GetDataFromStore()
if a$ <> "" then ProcessData
return
[/code]
You can put a call to this consumer in an endless loop or preferably call it with a timer.

If you want i can make a complete simple 'producer-store-consumer' sample, but it is also a great way to practice programming.

Maybe this is all overkill. :) As you probably notice i always have speed and storage needs and instead of just buying more RAM or a faster cpu starting with the software is often best. This 'producer-store-consumer' solution has served me well over many years in all kinds of environments.

Edited by TZAdvantage 2013-06-21
Microblocks. Build with logic.
 
Grogster

Admin Group

Joined: 31/12/2012
Location: New Zealand
Posts: 9308
Posted: 06:57pm 19 Jun 2013
Copy link to clipboard 
Print this post

WOW!

That was a big post, TZA!

In my case, I have adhered to the KISS principle as much as I can with the writing of the software.

With respect to the wireless door/window transmitter units, they send out 21-byte messages when tripped. The message is actually 20 bytes, but there is a CR hanging off the end of the message, so technically, it is 21 bytes.

Once I got the routine working from post #1, I changed the code a bit, and now the test loop only ever looks for 21 bytes, and when there is 21 bytes in the receiver buffer, it reads all 21 bytes into A$ in one go, then IReturns.


Cls

Open "com2:2400, 256, SerialInt, 21" As #2

start:
Print CLR$(Green) "Waiting for data..."

Loopything:
Print @(100,150) + CLR$(Blue) Time$
GoTo loopything


SerialInt:
Print CLR$(Red) "Data received."
'A$=Input$(21, #2)
'Print CLR$(Yello) A$
Print CLR$(Yellow) Input$(21, #2)
Print CLR$(White) "Little pause..."
Pause 500
Print CLR$(Red) "[IReturn]"
IReturn


This TEST ROUTINE, does everything I expect of it.
If the message is 21 bytes, it is shown on the screen.
Any other message length LESS then 21 bytes is ignored.

In the event of message lengths of more then 21 bytes(hopefully not possible by design), I would have to have a think on how to have the code handle that, as I DON'T want the next Input$ command to read in extra bytes, if they are not part of a valid message. So long as I only ever send and receive 21-byte messages, then PROVIDED no garbled comms happens, there should not be an issue.

However, garbled comms is something you really have to consider, so I would love to know how you can force the data buffer to be limited to 21 bytes.

Is it as simple as specifying 42 as the buffer size? (21 byte receive buffer, 21 byte transmit buffer)

The message format is: [ABCD]000ABCDEFGHIJK<cr>
[ABCD](including square brackets) is a qualifier - this MUST be received first
000ABCDEFGHIJK is the message, and <cr> is the CR on the end.

I am thinking about having the serial interrupt routine simply read the bytes from the buffer into A$(for example), then IReturn - this keeps the interrupt as short as possible, but at this point, the system knows there is some serial data it needs to look at, but does not know exactly what at this stage.

Then in a main loop, simply If LEN(A$)<>0 then the code knows there is something in there(in A$) to process. If the length of A$=0, then the code knows there is nothing there to process.

Needs more tinkering, but that is the basic idea.

Edited by Grogster 2013-06-21
Smoke makes things work. When the smoke gets out, it stops!
 
BobD

Guru

Joined: 07/12/2011
Location: Australia
Posts: 935
Posted: 07:15pm 19 Jun 2013
Copy link to clipboard 
Print this post

Graeme,
maybe your transmitters only transmit 21 bytes in each message but how many transmitters do you have and are they blocked from sending almost simultaneously? Do they check for received carrier before sending? Could you end up having multiple receives in the buffer before you can process the first one? Do the senders (door / window units) transmit asynchronously or do you need to poll them? Just thinking out loud, don't really know what your equipment is.
 
Grogster

Admin Group

Joined: 31/12/2012
Location: New Zealand
Posts: 9308
Posted: 07:33pm 19 Jun 2013
Copy link to clipboard 
Print this post

@ BobD - Can have any number of transmitters, but at the moment, only about 10. They transmit only, there is no RSSI to check to see if there is a carrier. Technically it is possible for transmission conflicts between units if they are set off at precisely the same time, but the odds on that are very very slim, unless two different burglers are trying to break into the same place at the same time - not that likely, and even in that event, they would have to be opening two different doors or windows within about a quarter a second of each other for a conflict to happen. Transmission time is about 200mS per cycle. Yes, could end up with multiple receives in the buffer - this is one of the things I would like to be able to deal with. Senders re-transmit until they are reset with an RFID tag by the staff. Transmission cycles once set off, are every 2 minutes(plus a random extra time figure added to that) until reset. That way, even if two were set off within seconds of each other, both would have different repeat-cycle times of 2 minutes + the extra random time delay. Transmitter is hidden in wall cavity, trip-switch on door or window etc is normally-closed, so if someone cuts the cable or tries to destroy the switch, the unit goes off automatically.Edited by Grogster 2013-06-21
Smoke makes things work. When the smoke gets out, it stops!
 
MicroBlocks

Guru

Joined: 12/05/2012
Location: Thailand
Posts: 2209
Posted: 08:12pm 19 Jun 2013
Copy link to clipboard 
Print this post

Welcome to the world of serial communications and asynchronisity (sp) where everything is not KISS. Communications is always garbled or times out.
[quote]
I am thinking about having the serial interrupt routine simply read the bytes from the buffer into A$(for example), then IReturn - this keeps the interrupt as short as possible, but at this point, the system knows there is some serial data it needs to look at, but does not know exactly what at this stage.
[/quote]
This is almost good but you have the risk of overwriting A$ and loose data.

You should do that in a 'store'; Just a fancy name for a subroutine that handles storage and retrieval.

Use the serialint routine from my previous post together with the store.
the store could look like this: (not tested just as a start)
[code]
DIM Storage$

FUNCTION Store(message$)
LOCAL MsgLength
MsgLength = LEN(message$)
IF (LEN(Storage$) + MsgLength > 255) THEN
'The error will stop program execution. Good while testing
ERROR "Not enough storage available!"
'In normal mode set MsgLength to -1.
'This will inform the caller that the message is not stored.
MsgLength = -1;
ELSE
Storage$ = $Storage$ + message$
ENDIF
Store = MessageLength
END FUNCTION
[/code]

Store would then return then number of characters stored or -1 if the store has run out of space. If this occurs then this should always be tried to be solved in the consumer first (make it faster) and if that fails then solve it in the store by providing more storage (many times not really a great solution).
I test these thing rigorously and then double the storage to be sure for production. A 100% margin is great for things in the field.

[quote]
Then in a main loop, simply If LEN(A$)<>0 then the code knows there is something in there(in A$) to process. If the length of A$=0, then the code knows there is nothing there to process.

Needs more tinkering, but that is the basic idea.
[/quote]
Yes, that is how you do it.
If you use the above Store function you would need to let the retrieve function do the checking if there is a message available.
like this:
[code]
MessageLength = 21
FUNCTION Retrieve()
LOCAL Message$
LOCAL SeparatorPosition
retry:
IF LEN(Storage$) >= MessageLength THEN
'At least one possible message in storage
'Find the message separator
SeperatorPosition = INSTR(Storage$,CHR$(13))

IF SeperatorPosition <> MessageLength THEN
'Corrupt message (too short or too long) at the beginning of the Storage
'Correct it by removing the corrupt part
Storage$ = MID$(Storage$, SeperatorPosition + 1)
GOTO Retry
ENDIF

'Extract the message
Retrieve$ = MID$(Storage$, 1, MessageLength)
'Remove from storage
Storage$ = MID$(Storage, MessageLength)

ELSE
'Storage contains less then MessageLength characters. No complete message available
Retrieve$ = ""
ENDIF

END FUNCTION
[/code]
You the get a message and check its length
[code]
A$=Retrieve$()
if LEN(A$)>0 then Process(A$)
[/code]
I like to use a timer for the retrieval and processing of messages. That will leave your main loop free from the clutter and can be used for GUI and user input.
In your case checking every second is i think fast enough.
[code]
SETTICK 1000, ProcessMessages

ProcessMessages:
A$=Retrieve$()
IF LEN(A$) > 0 then Process(A$)
IRETURN
[/code]

Here the interrupt is used to process data instead of responding to a hardware event.
In those cases you can use more time. If it is a long process you can divide it into smaller steps and implement a state machine. But that is a whole other subject.

You then have to write a Process subroutine that actually does something with it.
If you set it up like above you have all the different task neatly seperated. Every function does one task. Easy to debug and maintain. Also you can use it as a template for many other project again and again. Basically you only need to change the interrupt routine and the Process routine. Store can stay the same or be expanded for more storage depending onthe needs.

You might need to correct the MID$() functions in the Retrieve function. You might need to substract or add one to the messagelength to get it right.

Hmm. A long post again. Edited by TZAdvantage 2013-06-21
Microblocks. Build with logic.
 
Grogster

Admin Group

Joined: 31/12/2012
Location: New Zealand
Posts: 9308
Posted: 09:49pm 19 Jun 2013
Copy link to clipboard 
Print this post

WOW!

Lots more info for me to read through and try - thanks, TZA.

I will have a go at the store thing, but I need to re-read your posts a few times, and some of that code is getting a little advanced for me. Still, I am not afraid to learn something new. It never ceases to amaze me what the MM can actually do, compared to other platforms.
Smoke makes things work. When the smoke gets out, it stops!
 
Print this page


To reply to this topic, you need to log in.

© JAQ Software 2024