Serial communication is both easy and hard. Easy if we have some medium
level APIs that take care of sending and receiving data (and we do have them), hard
if we want to guarrantee data consistency and error free transfers. We usually use
a protocol. The best way to get ourselves confused when starting to learn something
is to start from the end. So for now let's throw protocols out of the window.
Let's meet the APIs...
The serial port must be configured (opened), we have LibSrlPortOpen. We may want
to send or receive one byte, we have LibSrlSendByte, LibSrlRecvByte, or multiple
bytes LibSrlSendBlock, LibSrlRecvBlock and we close the port LibSrlPortClose.
Let's see them working. Why don't we move a rectangle over the PV screen and
tell the PC its position so it can show it too? Just to be fair we also do the
opposite.
PC |
Pocket Viewer |
|
|
Now by looking at the pictures, don't think the serial cable changes
rects into circles and vice versa ;) I talked about protocols and errors but
this... The pictures are not simultaneous shots of the same session or you could
ask me "Why didn't you rotate one of them?"
Based on this, you could make 2 players "TicTacToe" over the serial cable.
Here's PV point of view.
#include <stdrom.h>
#include "define.h"
#include "libc.h"
We have a screen, some rect and an event listener.
#define SCREEN_X 0
#define SCREEN_Y 0
#define SCREEN_W 160
#define SCREEN_H 160
#define RECT_W 10
#define RECT_H 10
#define EVENT_TCH 1
#define EVENT_CRADLE 4
#define EVENT_BLD1 8
Here's our cast, two actors, one is black the other white.
#define TYPE_BLACK 0
#define TYPE_WHITE 1
int rem_x, rem_y;
int loc_x, loc_y;
How do we open a serial port? We select one IB_SRL_COM2 (9 pins one), tell it how
fast we want the sequence of bits to run IB_SRL_19200BPS, no parity check
IX_SRL_NONE (just like google says "i'm feeling lucky") and so on. Notice we don't
allow hardware handshaking IX_SRL_NOFLOW (9 pins serial allows all 4 flow control
methods). If everything's alright we can start.
bool SrlOpen()
{
SRL_STAT srl;
srl.port = IB_SRL_COM2;
srl.speed = IB_SRL_19200BPS;
srl.parit = IX_SRL_NONE;
srl.datab = IX_SRL_8DATA;
srl.stopb = IX_SRL_1STOP;
srl.fctrl = IX_SRL_NOFLOW;
if (LibSrlPortOpen(&srl) != IW_SRL_NOERR)
{
LibPutMsgDlg("LibSrlPortOpen error");
return FALSE;
}
return TRUE;
}
We know we have to receive a couple of bytes each time the remote point changes
the position of its piece. We test for the first coordinate (LibSrlRecvByte's not
blocking), if there is nothing yet we return accordingly, if we get the byte, we
know there's a second one. We get it too and return them both.
bool SrlPoll(int *x, int *y)
{
byte data;
if (LibSrlRecvByte(&data) == IW_SRL_NODATA)
return FALSE;
*x = data;
LibSrlRecvByte(&data);
*y = data;
return TRUE;
}
When we make a move we have to notify the remote point, we just send it the
new position. We can't fail "we're feeling lucky" ;)
bool SrlSend(int x, int y)
{
LibSrlSendByte(IB_FOLLOW_BUSY, (byte)x);
LibSrlSendByte(IB_FOLLOW_BUSY, (byte)y);
return TRUE;
}
An old friend
void PollEvents(TCHSTS far *tsts, byte event_mask)
{
union REGS reg;
reg.x.ax = 0x0200 | event_mask;
reg.x.di = FP_OFF(tsts);
reg.x.es = FP_SEG(tsts);
int86(0x50, ®, ®);
}
The next functions let us control the action. See how laziness improves our skills.
bool InRect(int px, int py, int x, int y, int w, int h)
{
return (x < px && px < x + w) && (y < py && py < y + h);
}
void DoMoveRect(int x, int y, int type)
{
int rect_x1, rect_y1;
int rect_x2, rect_y2;
rect_x1 = x - RECT_W / 2;
rect_y1 = y - RECT_H / 2;
rect_x2 = x + RECT_W / 2;
rect_y2 = y + RECT_H / 2;
if (type == TYPE_BLACK)
LibGdsReverse(rect_x1, rect_y1, rect_x2, rect_y2);
else
LibGdsBox(rect_x1, rect_y1, rect_x2, rect_y2);
}
void UpdateDisplay()
{
LibClrDisp();
DoMoveRect(rem_x, rem_y, TYPE_BLACK);
DoMoveRect(loc_x, loc_y, TYPE_WHITE);
LibPutDisp();
}
void main()
{
TCHSTS tsts;
bool bExit = FALSE;
rem_x = 3 * SCREEN_W / 4;
rem_y = 80;
loc_x = SCREEN_W / 4;
loc_y = 80;
UpdateDisplay();
SrlOpen();
for (;bExit == FALSE;)
{
We poll everything. We poll the touch pad and in case of a local move we update
the screen and send the move to the other end of the serial cable. If we have
better things to do we "bExit". While if it's a rainy day we can always check
for a couple of bytes from the serial line, once we have them we update the
screen.
PollEvents(&tsts, EVENT_TCH);
if (tsts.y != -1 || tsts.x != -1)
{
if (InRect(tsts.x, tsts.y,
SCREEN_X + RECT_W / 2,
SCREEN_Y + RECT_H / 2,
SCREEN_W - RECT_W,
SCREEN_H - RECT_H))
{
loc_x = tsts.x;
loc_y = tsts.y;
UpdateDisplay();
SrlSend(loc_x, loc_y);
}
if (tsts.y >= SCREEN_H) bExit = TRUE;
}
if (SrlPoll(&rem_x, &rem_y)) UpdateDisplay();
}
Ok it's not a rainy day. So we go out, but... don't forget to close the
door (LibSrlPortClose) and clear the buffers (LibSrlTxBufClr, LibSrlRxBufClr).
LibSrlRxBufClr();
LibSrlTxBufClr();
LibSrlPortClose();
LibJumpMenu();
}
"Hey wait a moment!" i hear you say. "How do we test it? We need the second half,
the PC point of view".
Here it comes! This is the same program seen through the eyes of Visual Basic.
I won't comment the code. I just want you notice that 1) the communication parameters
must match (see OpenComm). 2) Visual basic is event driven, we don't do the polling,
we get notified, so everytime we receive some data from the remote point,
"MSComm1_OnComm" is fired and we read two bytes, this is possible because of this
setting "MSComm1.RThreshold = 2" which tells the system to wait for 2 bytes
before triggering. 3) Sending data is as easy as throwing a protocol out of
the window "MSComm1.Output = Chr(...)"
Create an empty VB6 project. Just put a PictureBox and a MSComm control
(Project\Components menu) and copy the code.
Show Code
Dim point_rem(1) As Byte
Dim point_loc(1) As Byte
Private Sub OpenComm()
MSComm1.CommPort = 2
MSComm1.Settings = "19200,N,8,1"
MSComm1.RThreshold = 2
MSComm1.InputMode = comInputModeBinary
MSComm1.OutBufferSize = 100
MSComm1.Handshaking = comNone
MSComm1.PortOpen = True
End Sub
Private Sub CloseComm()
MSComm1.PortOpen = False
End Sub
Private Sub Command1_Click()
MSComm1.Output = Chr(CByte(Text1))
MSComm1.Output = Chr(CByte(Text2))
End Sub
Private Sub Form_Load()
Picture1.AutoRedraw = True
Picture1.ScaleMode = 3
Picture1.ScaleWidth = 160
Picture1.ScaleHeight = 160
point_rem(0) = 160 / 4
point_rem(1) = 80
point_loc(0) = 3 * 160 / 4
point_loc(1) = 80
DoAction
OpenComm
MSComm1.Output = Chr(point_loc(0))
MSComm1.Output = Chr(point_loc(1))
End Sub
Private Sub Form_Resize()
Picture1.Width = Form1.ScaleWidth
Picture1.Height = Form1.ScaleHeight
Picture1.ScaleMode = 3
Picture1.ScaleWidth = 160
Picture1.ScaleHeight = 160
DoAction
End Sub
Private Sub Form_Unload(Cancel As Integer)
CloseComm
End Sub
Private Sub MSComm1_OnComm()
If MSComm1.CommEvent = 2 Then
X = MSComm1.Input
point_rem(0) = X(0)
point_rem(1) = X(1)
DoAction
End If
End Sub
Private Sub Picture1_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
If Button <> vbLeftButton Then Exit Sub
If (X - 5 >= 0) And (X + 5 < 160) And (Y - 5 >= 0) And (Y + 5 < 160) Then
point_loc(0) = X
point_loc(1) = Y
DoAction
MSComm1.Output = Chr(CByte(X))
MSComm1.Output = Chr(CByte(Y))
End If
End Sub
Private Sub DoAction()
Caption = point_rem(0) & " " & point_rem(1)
Picture1.Cls
Picture1.FillStyle = vbSolid
Picture1.FillColor = vbBlack
Picture1.Circle (point_loc(0), point_loc(1)), 5
Picture1.FillStyle = 1
Picture1.Circle (point_rem(0), point_rem(1)), 5
End Sub
|