This is the second part of the article : PC to PIC communication
In this article we'll see how to write on the LCD screen from the PC.
In this article I'll talk about:
- Some preparations first
- Waiting for the start signal
- Parsing the received commands
- Executing commands received from the PC
- The VBA program
Requires:
- PIC programmer board
- 2x16 LCD E-Block
- COM Port or USB to Serial
- RS232 to TTL converter
- 16F877A
Some preparations first
First of all, you need to decide what commands you want to make.
As said before, we'll write on the LCD screen so:
- we'll need to be able to send a string to the microcontroller and write it on the LCD screen.
- we'll need to be able to move the cursor to the begin position of each line.
- we'll need to be able to clear the screen from the PC.
The second thing I decided to do is to use a command-acknowledge-like communication.
This means that when a command is sent to the microcontroller, it'll respond by giving a default answer.
This default answer will be the startbyte + the sent command + an acknowledge byte + endbyte.
The third thing I decided to do is to use a startbyte and endbyte so the microcontroller knows when it receives a command from the PC and when it needs to stop reading.
I used '<' as startbyte and '>' as endbyte.
The fourth thing to do is to decide the names of the commands sent by the PC.
As we will write on the display, I choose to use "WD" (Write Display) as the name of the command.
Here's an example of a command to send to the PIC to write on the LCD screen:
"<WDHello World>" (without quotes)
Waiting for the start signal:
Open up flowcode, create a new file, choose the 16F877A (or any other PIC with USART I/O) and set the oscillator to 19660800 Hz.
Add the RS232 and LCD component to your flowcode program.
Now we'll start with the code:
Note: The code you'll see isn't the code made by flowcode. It's my interpretation of the program. For the correct program see the attached files.
Code: Select all
int counter;
char command[2];
char data_buffer[32];
char cmd_type, x_pos, y_pos, RS232_input;
void main(void)
{
delay_s(1);
LCDDisplay_Start();
LCDDisplay_Clear();
while (1)
{
ReadStartByte();
// rest of the code seen later
- a counter
- an array of 2 bytes to store the command
- an array of 32 bytes to store the received data
- a byte to store the type of the command
- a byte to store the x and another one to store the y position
- a byte to store the received char from the PC
The main function:
It contains a delay for the LCD display to start up correctly.
Then it initialize the display and clears it with two functions from the LCD component.
Next comes the main loop.
There we'll wait for the startbyte and then we will read the command.
Code: Select all
void ReadStartByte()
{
do
{
RS232_input = ReceiveChar();
} while (RS232_input != '<');
}
Nothing difficult here.
We stay in the loop while the RS232_input byte isn't the startbyte '<'.
To read a char we'll use the ReceiveChar function.
Code: Select all
char ReceiveChar()
{
char input;
do
{
input = RS232_ReceiveChar(10);
} while (input == 255);
return input;
}
First we'll declare a byte to receive the char.
Next we'll loop while the received char is 255 (FF hex).
The RS232_ReceiveChar(char nTimeout) is a function from the RS232 component and the "10" is the timeout argument.
To end the function we'll return the received char.
Parsing the received commands:
Back to the main function, we'll add a new function:
Code: Select all
int counter;
char command[2];
char data_buffer[32];
char cmd_type, x_pos, y_pos, RS232_input;
void main(void)
{
delay_s(1);
LCDDisplay_Start();
LCDDisplay_Clear();
while (1)
{
ReadStartByte();
ParseCommand();
// rest of the code seen later
This is how it looks like:
Code: Select all
void ParseCommand()
{
cmd_type = 255;
command[0] = 0;
command[1] = 0;
command[0] = ReceiveChar();
command[1] = ReceiveChar();
if (command[0] == 'W' && command[1] == 'D')
cmd_type = 1;
}
Then we reset the array command, and we read to bytes.
Next we'll check if the received command is "WD" which is done by the if statement.
Note: As we only have one command, there's only one if statement.
If the received command is unknown, the cmd_type variable will have the value 255, which is used to tell the microcontroller there's an unknown command received and thus sending an error string back to the PC.
Executing commands received from the PC:
Now we know which command has been sent, we can execute it.
Back to the main function to add the last function:
Code: Select all
int counter;
char command[2];
char data_buffer[32];
char cmd_type, x_pos, y_pos, RS232_input;
void main(void)
{
delay_s(1);
LCDDisplay_Start();
LCDDisplay_Clear();
while (1)
{
ReadStartByte();
ParseCommand();
ExecuteCommand();
}
}
We can do three things with "WD" command.
- writing on the display
- setting the X and Y position
- clearing the display
This is how it looks like:
Code: Select all
void ExecuteCommand()
{
counter = 0;
if (cmd_type == 1)
{
ReceiveData();
if (data_buffer[0] == 0)
{
LCDDisplay_Clear();
}
else if (data_buffer[0] >= 1 && data_buffer[0] <= 16 &&
data_buffer[1] >= 1 && data_buffer[1] <= 2)
{
x_pos = data_buffer[0] - 1;
y_pos = data_buffer[1] - 1;
LCDDisplay_SetCursor(x_pos, y_pos);
}
else
{
counter = 0;
while (data_buffer[counter] != '>')
{
LCDDisplay_WriteAscii(data_buffer[counter]);
counter++;
if (counter >= 32)
break;
}
}
SendAcknowledge();
}
else
SendNoAcknowledge();
}
First it sets the counter to 0.
Then it'll check which command has been received which is done by the first if statement.
If the cmd_type equals 1, then we'll start reading the received bytes with the ReceiveData function.
Next we'll check if the first byte of the received data is 0. (The data starts after "<WD")
If this byte is 0 then we'll clear the screen.
Else we'll check if the first byte is between 1 and 16 and if the second byte is between 1 and 2.
If thats the case we'll decrease by 1 the value of those two bytes and store them in x_pos and y_pos.
Then we'll set the cursor on the x_pos and y_pos which is done by the LCDDisplay_SetCursor functions from the LCD component.
Else we'll write the received data on the LCD screen.
This is done by setting the counter back to 0 and then loop while data_buffer[counter] isn't equal to the endbyte '>'.
In the while loop we'll write the data_buffer[counter] char on the LCD and we'll increase the value of counter by 1.
To avoid buffer overflow, I added an if statement to prevent the counter to be greater than or equal to 32.
Once we reach the end of the cmd_type if statement, we'll send an Acknowledge string which is done by the SendAcknowledge(), to tell the PC everything went ok.
If the cmd_type isn't known by the microcontroller, it will send a NoAcknowledge string which is done by the SendNoAcknowledge(), to tell the PC there's an error.
Code: Select all
void ReceiveData()
{
do
{
data_buffer[counter] = ReceiveChar();
counter++;
} while (counter < 32 && data_buffer[counter] != '>');
}
It enters a while loop to receive the data and store it in the data_buffer array.
The loop is executed while the counter is less than 32 to avoid buffer overflow and while the data_buffer[counter] byte isn't equal to the endbyte '>'.
Code: Select all
void SendAcknowledge()
{
RS232_SendChar('<');
RS232_SendChar(command[0]);
RS232_SendChar(command[1]);
RS232_SendChar(6);
RS232_SendChar('>');
}
It sends a string to the PC.
This string contains the startbyte, the previously received command, the acknowledge byte (see ASCII table, decimal value 6) and the endbyte.
Code: Select all
void SendNoAcknowledge()
{
RS232_SendChar('<');
RS232_SendChar(command[0]);
RS232_SendChar(command[1]);
RS232_SendChar(21);
RS232_SendChar('>');
}
It sends a string to the PC.
This string contains the startbyte, the previously received command, the noacknowledge byte (see ASCII table, decimal value 21) and the endbyte.
Voilà, you have now made a firmware for your PIC microcontroller.
Now we need to do the PC program.
The VBA program:
Here we'll use the knowledge and functions from the Part 1 of this article.
Open up Excel, go to the Developer tab and click on the Visual Basic icon.
Insert a Userform and add 2 TextBoxes, 1 CommandButton and 1 MSComm.
Press F7 to see the code.
Now, we'll copy-paste some functions/sub seen in the previous article.
Code: Select all
Function ReceiveAnswer(end_char As String) As String
Dim buffer As String
Dim char As String
Dim out_of_time As Boolean
Dim time As Single
buffer = ""
time = Timer()
Do
char = MSComm1.Input
buffer = buffer & char
out_of_time = Timer() > time + 2
Loop Until char = end_char Or out_of_time
If out_of_time <> False Then
ReceiveAnswer = buffer
Else
ReceiveAnswer = "Out Of Time"
End If
End Function
Function InitializePort(COM As Integer)
MSComm1.CommPort = COM
MSComm1.Settings = "9600,N,8,1"
MSComm1.InputLen = 1
MSComm1.PortOpen = True
End Function
Private Sub UserForm_Initialize()
Dim COM As Integer
COM = InputBox("Set COM Port :")
Call InitializePort(COM)
End Sub
This will simplify the way to send data to the microcontroller.
Code: Select all
Function SendData(data As String) As String
Dim buffer As String
Dim counter As Integer
MSComm1.Output = data
buffer = ReceiveAnswer(">")
counter = 1
Do While ((counter < 3) And (buffer = "Out Of Time"))
MSComm1.Output = data
buffer = ReceiveAnswer(">")
counter = counter + 1
Loop
If counter >= 3 Then
MsgBox("Error : Out of time")
SendData = "Error"
Else
If buffer = Mid(data, 1, 3) & Chr(6) & ">" Then
SendData = buffer
Else
MsgBox("Error : No Acknowledge received, " & buffer)
SendData = "Error"
EndIf
EndIf
End Function
We send the data to through the COM port.
We receive the answer from the PIC and store it in the string buffer.
Then we initialize counter to 1.
If the buffer is equal to "Out Of Time", this means no answer has been read in 2 seconds, we'll send the data again.
If it fails again, it will try again.
This is done 3 times, so if the PIC doesn't answer within 6 seconds, then there's a problem...
If the counter is higher than or equal to 3 a window will open with the message "Error: Out of time" and then we'll return "Error".
Else if the buffer is equal to the 3 first chars of the data string + the acknowledge byte + endbyte then we'll return the buffer.
If it isn't, then a window will open with the message "Error: No Acknowledge received, " + the buffer string and then we'll return "Error".
Next we'll code the function to clear the screen when the CommandButton is pressed.
Code: Select all
Private Sub CommandButton1_Click()
TextBox1.Text = ""
TextBox2.Text = ""
Call SendData("<WD" & Chr(0) & ">")
End Sub
This will be parsed by the microcontroller and it'll clear the screen.
To end this article, we'll add two more sub functions.
Those for the TextBoxes.
Code: Select all
Private Sub TextBox1_Change()
Call SendData("<WD" & Chr(0) & ">")
Call SendData("<WD" & Chr(1) & Chr(2) & ">")
Call SendData("<WD" & TextBox2.Text & ">")
Call SendData("<WD" & Chr(1) & Chr(1) & ">")
Call SendData("<WD" & TextBox1.Text & ">")
End Sub
Private Sub TextBox2_Change()
Call SendData("<WD" & Chr(0) & ">")
Call SendData("<WD" & TextBox1.Text & ">")
Call SendData("<WD" & Chr(1) & Chr(2) & ">")
Call SendData("<WD" & TextBox2.Text & ">")
End Sub
Let's say the first TextBox changes from nothing to "Hello".
TextBox1_Change() will be executed.
This will send the command to clear the screen to the microcontroller.
Next it will set the x and y position to [0;1], that's the beginning of the second line.
Next it will send the command to write what's in the second TextBox on the LCD screen.
After that it sets the x and y position to the beginning of the first line.
And then he'll send the command to write what's in the first TextBox.
Now, there will be "Hello" written on the LCD screen, on the first line.
This method is used to prevent the text on the second line to be cleared.
A same method is used for the TextBox2_Change sub function.
Yes, it's finally done !
This is a video I made about my first test communicating with the PIC : http://www.youtube.com/watch?v=tpr_6DcKj9c
Hope you appreciated the 2 parts of the article.
Leave a comment if you liked it

Best regards,
Nicolas L. F., aka Spanish_dude / pyroesp
PS: To run your VBA program you just need to click on the play-like button.