Sunday, June 17, 2012

VB.net Serial Port Interface

Now that we know how to use the UART of our 8051, lets make a simple PC interface to demonstrate sending and receiving of data. Visual Basic dotNET 2010 will be used in this tutorial.

The goal of this tutorial is to write a simple application that can send data to be written to a port on the microcontroller, and received data from the status of a port  on the microcontroller.

Create a new project and add the following controls inside your form
  •  Groupbox
  • Three (3) labels
  •  Two (2) buttons
  • Combo box
  • Horizontal scroll bar
  • Textbox
  • Serial Port

Figure 1: The VB.net Serial Interface main form
Arrange the controls accordingly like the above image. The two buttons will be named as btnConnect and btnDisconnect , the combo box as cboCOMPorts, the horizontal scroll bar as ScrollData, the second label as lblDataValue which will indicate the value that is currently written on the serial port, and the textbox as txtRxData which will contain the received data from the 8051.

Let's start coding!

First add a reference to System.IO.Ports and System.Text so we can use the SerialPort namespace to get port names and stringbuilder by adding the following to your code.

Figure2: Import namespace

Declare an array that will hold the data to be sent out, in this tutorial we are only a single byte will be sent by the microcontroller so the array contains only 1 element.

           Dim outData As Byte() = New Byte(0) {}

Before going further, we type in the subroutines that are useful in different areas.

GetCOMPortList 
    It will get all the available serial port installed and populate the combo box so that we can select on what COM port to connect. This will be called during start up so we can get all the COM ports available, it can be also called by a timer to continuously scan for COM ports specially when using USB-to-Serial cables.

    Private Sub GetCOMPortList()
        Dim i As Integer
        Dim foundDifference = False

          'Search all the entrire serial port object and see if there are new serial ports from the last time
          'it was check, and update the list if any difference occurred.

        If cboCOMPorts.Items.Count = SerialPort.GetPortNames().Length Then
            For Each s As String In SerialPort.GetPortNames()
                If cboCOMPorts.Items(System.Math.Max(System.Threading.Interlocked.Increment(i), i - 1)).Equals(s) = False Then
                    foundDifference = True
                End If
            Next
        Else
            foundDifference = True
        End If


        If foundDifference = False Then
            Return
        End If

        cboCOMPorts.Items.Clear()

        For Each s As String In SerialPort.GetPortNames()
            cboCOMPorts.Items.Add(s)
        Next
        cboCOMPorts.SelectedIndex = 0

    End Sub



By default VB.net only supports reading and writing in ascii form, so we need a way to convert data to binary form and back to ascii for display in our GUI.

HexToByte
  This routine will convert the ascii hex data to binary form.

    Private Function HextoByte(ByVal msg As String) As Byte()
        msg = msg.Replace(" ", "")

        Dim combuffer As Byte() = New Byte(msg.Length \ 2 - 1) {}
        For i As Integer = 0 To msg.Length - 1 Step 2
            combuffer(i \ 2) = CByte(Convert.ToByte(msg.Substring(i, 2), 16))
        Next

        Return combuffer
    End Function


ByteToHex
   This routine will convert binary data to ascii hex

    Private Function BytetoHex(ByVal comByte As Byte()) As String
        Dim builder As New StringBuilder(comByte.Length * 3)
        For Each data As Byte In comByte
            builder.Append((Convert.ToString(data, 16).PadLeft(2, "0")))
        Next
        Return builder.ToString().ToUpper()
    End Function


Form Load Event
   During startup, we set default values for our controls and get the list of serial ports

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        lblDataValue.Text = ScrollData.Value.ToString()
        ScrollData.Enabled = False
        ScrollData.Maximum = 255
        ScrollData.LargeChange = 1
        btnDisconnect.Enabled = False
        txtRxData.Text = "00"


        GetCOMPortList()
    End Sub

The Connect Button
   As the name implies, it will open the serial port selected, add the following to the Click event

    Private Sub btnConnect_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnConnect.Click

        outData(0) = Convert.ToByte(lblDataValue.Text)

        Try
            SerialPort1.PortName = cboCOMPorts.Items(cboCOMPorts.SelectedIndex).ToString()
            SerialPort1.BaudRate = 9600
            SerialPort1.Open()
            SerialPort1.Write(outData, 0, 1)
            btnDisconnect.Enabled = True
            ScrollData.Enabled = True
            btnConnect.Enabled = False
        Catch ex As Exception
            btnDisconnect.PerformClick()
        End Try
    End Sub

The Disconnect Button
   Used to close the current connection, add the following to the Click event

    Private Sub btnDisconnect_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnDisconnect.Click
        Try
            SerialPort1.DiscardInBuffer()
            SerialPort1.DiscardOutBuffer()
            SerialPort1.Close()
            ScrollData.Value = 0
            ScrollData.Enabled = False
            btnConnect.Enabled = True
            btnDisconnect.Enabled = False
        Catch ex As Exception

        End Try
    End Sub


The Scroll Bar
   The scrollbar has a maximum value of 255 or 0xFF which means data that can be sent out covers the whole value of a PORT in the 8051, every time the scroll bar changes value, it will be reflected on the label beside it and will be sent out to the serial port.

    Private Sub ScrollData_Scroll(ByVal sender As System.Object, ByVal e As System.Windows.Forms.ScrollEventArgs) Handles ScrollData.Scroll
        lblDataValue.Text = ScrollData.Value.ToString("X")

        outData(0) = Convert.ToByte(ScrollData.Value)
        SerialPort1.Write(outData, 0, 1)
    End Sub


Receiving data from the Microcontroller
   The received data from the microcontroller will be displayed to the textbox by the following code. This routine is called everytime data is received.

    Private Delegate Sub DisplayDelegate(ByVal displayChar As String)

    Private Sub DisplayCharacter(ByVal displayChar As String)
        txtRxData.Text = displayChar
    End Sub


Remember, we added a serial port component, on its Data Received event put the following code.

    Private Sub serialPort1_DataReceived(ByVal sender As Object, ByVal e As SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
        Dim rx As Integer
        rx = SerialPort1.BytesToRead
        Dim comBuff As Byte() = New Byte(rx - 1) {}
        SerialPort1.Read(comBuff, 0, rx)
        txtRxData.Invoke(New DisplayDelegate(AddressOf DisplayCharacter), New Object() {BytetoHex(comBuff)})
    End Sub



The Project file of this tutorial can be downloaded from THIS LINK

Friday, June 15, 2012

Using the UART

Universal  Asynchronous Receive/Transmit is a form of serial communications used in conjunction with RS-232, RS-485 or RS-422. The 89S52 has one UART port, and the important registers that we will look are TMOD, SCON and SBUF registers. More info regarding this registers can be found on the datasheet.

Initializing Serial Port

1. Set Timer1 as Mode 2, Timer1 then acts as the baudrate generator.
    To do this, load TMOD with 0x20.
2. Load TH1with the desired value according to the baudrate.
    To calculate the value of TH1 we use the formula:

        TH1 = -((Crystal Frequency/12)/32) /desired Baud Rate

       Example: Value of TH1 for a baud rate of 9600 and a crystal frequency of 3.579545MHz

           Divide crystal frequency by 12

                       3579545 / 12 = 298295.4167

          As you can see, if we divide the crystal frequency by 12, the result is not a whole number, if the error is so little, then we can disregard it else choose a crystal frequency that is divisible by 12 to come up with 0% error such as 11.0592MHz or 22.1184MHz.

     Divide resulting value by 32
             
                      298295.4167/32 = 9321.731771

   Divide the resulting value by the desired baud rate

                       9321.731771/9600 = -0.971013726 ~= -1

         We have about 3% error with this crystal frequency

To get the value of TH1

     Subtract computed value from 0xFF and add 1

 TH1 = 0xFF

3. Load the SCON register with the standard 8-bit UART parameters, Mode1 with 8-bits data, no parity, 1 start bit, 1 stop bit and receive enabled.

4. Start Timer 1
5. Load SBUF with the character to be transferred
6. Check if TI is set, if set, clear it.

The Code


#include <at89x52.h>

void UARTputch (unsigned char ch)
{
    TI = 0;                //Clear TI
    SBUF = ch;        //Load data to SBUF
    while(!TI);          //Wait for transmission to finish
}

unsigned char UARTgetch()
{
     while(!RI);        //wait for a byte to receive
     RI = 0;             //Clear RI
     return(SBUF);  //return the contents of SBUF
}

void main (void)
{
  unsigned char rx_byte;
  TMOD = 0x20;     //Timer1 Mode2 Auto Reload
  TH1 = 0xFF;         //9600 baud at 3.579545MHz
  SCON = 0x50;     //UART parameters
  TR1 = 1;               //Start timer

   while(1)
    {
       rx_byte = UARTgetch();
       UARTputch(rx_byte);
   }  
}

The code presented above will echo a single byte that has been sent to the 8051 back to the sender.


                   


Hardware Delay

This tutorial is all about using the internal timers/counters of the 8051 to produce a delay. The AT89S52 has three (3) 16-bit Timers/Counters that can be used to produce a more accurate delay, rather than approximation like that of the software based delay. These timers has different modes, and functions that can be set thru the SFR registers.

In this tutorial, we will be concerned of Timer0 operating at Mode 1. The important registers that we will look at are the TMOD, THx, TLx and TCON registers.

I. The TMOD Register

      The TMOD register is the one responsible for the operation of Timer0 and Timer 1, the lower nibble is for Timer 0 and upper nibble for Timer1.

Figure 1: TMOD Register
a. Gate - Clear to enable Timer x whenever the TRx bit is set. Set to enable Timer x only while the INTx pin is high and TRx bit is set.
b. C/T - Clear for timer operation: timer 1x counts the divided-down system clock. Set for Counter operation: timer x counts negative transitions on external pin Tx.
c. M1/M0 - Timer x mode bits

II. TCON Register
    The TCON register is the one responsible for the control of TIMER0 and TIMER1.

Figure 2: TCON Register

a. IT0 - Interrupt 0 Type Control Bit - Clear to select low level active (level triggered) for external interrupt 0 (INT0#). Set to select falling edge active (edge triggered) for external interrupt 0.
b. IE0 - Interrupt 0 Edge Flag - Cleared by hardware when interrupt is processed if edge-triggered (see IT0). Set by hardware when external interrupt is detected on INT0# pin.
c. IT1 - Interrupt 1 Type Control Bit - Clear to select low level active (level triggered) for external interrupt 1 (INT1#).Set to select falling edge active (edge triggered) for external interrupt 1.
d. IE1 - Interrupt 1 Edge Flag - Cleared by hardware when interrupt is processed if edge-triggered.
Set by hardware when external interrupt is detected on INT1# pin.
e. TR0 -Timer 0 Run Control Bit - Clear to turn off timer/counter 0. Set to turn on timer/counter 0.
f. TF0 - Timer 0 Overflow Flag - Cleared by hardware when processor vectors to interrupt routine.
Set by hardware on timer/counter overflow, when the timer 0 register overflows.
g. TR1 - Timer 1 Run Control Bit - Clear to turn off timer/counter 1. Set to turn on timer/counter 1.
h. TF1 - Timer 1 Overflow Flag - Cleared by hardware when processor vectors to interrupt routine.
Set by hardware on timer/counter overflow, when the timer 1 register overflows.

III. THx and TLx Register
    The THx and TLx registers represent the value of the timer count. The 16 bit count is divided between THx which represents upper byte and TLx which represents lower byte.

Figure 3: THx and TLx Register

Steps in using Mode 1


1. Load TMOD register a value indicating which timer to be used.
2. Load THx and TLx with initial values.
    To calculate
     
     XXYY(hex) = 65536 - (delay / (12/xtal freq.))

   XX = THx Value
   YY = TLx Value
   Crystal Frequency is in Hertz.
3. Start timer by setting TRx (TRx = 1).
4. Monitor the Timer flag TFx to see if it is set.
5. Stop timer (TRx = 0)
6. Clear the timer flag for the next round
7. Go back to step 2.

Example: You want to make a 10ms delay with a 3.58MHz crystal using TIMER0.

Lets compute for the value of THx and TLx

XXYY = 65536 - (10x10^-3 / (12/3.58x10^-6)
XXYY = 62553.04
 since we have a decimal point, we will drop it
 so XXYY = 62553 = 0xF459
 THx = 0xF4
 TLx = 0x59

Implementation:


    #include <at89x52.h>
void delay (void)
{
   TMOD = 0x01;   //Timer0 Mode 1
   TL0 = 0x59;    //Computed Value of TLx
   TH0 = 0xF4;    //Computed Value of THx
   TR0 = 1;       //Start timer 0
   while(!TF0);   //Wait for overflow
   TR0 = 0;       //Stop Timer 0
   TF0 = 0;       //Clear interrupt flag
}
 
void main (void)
{
   while(1)
    {
        P2_0 ^= 1;
        delay();
     }
}
 
The program above will create a 100Hz square wave at P2.0.



Friday, May 25, 2012

Reading Port Status

From the previous examples, we seen how easy to output values to the ports of the 8051. Now we will learn how to read port status. For the following examples, beside from the LEDs connected at Port2, we will connect two switches on P3.6 and P3.7 as seen on the diagram below.



 In SDCC, ports can directly read. For instance if we want to read the status of the switch connected at P3.6, we can detect the if it was pressed using the following line of codes.

if(!P3_6)
{
  ...your code here;
}
The if statement above will test the value of P3.6 if it is  zero since we have the port pulled to VCC, so when the switch is pressed, it will be pulled to ground, so the value of P3.6 becomes zero. The value of P3.6 can also be read by reading whole 8 bits of P3 and masking its value to separate P3.6 from others. It can be done by the following line of codes.

unsigned char switch_value = 0; // a variable where port value will be stored.

value = P3 & 0x40;

if(value == 0x00)
  {
      ..your code here;
  }
We mask the value of the port such that other bits are read as zero, and the bit position of P3.6 is 1 so that we can detect the change if its status changes from 1 to 0.

Example Application :
 if P3.6 is pressed , all LEDs connected to PORT2 will light, else LEDs will be off.

#include <at89x52.h>

void main (void)
{
   unsigned char sw_value = 0;
   P2 = 0;

      while(1)
         {
            sw_value = P3 & 0x40;
           
            if(sw_value == 0){
                P2 = 0xFF;
             }
            else{
               P2 = 0x00;
             }
         }
}
Demo of the above code:


With the basic knowledge of writing and reading the ports is the basic building blocks in more advance projects.



















 

Sunday, May 20, 2012

More LED's: Binary Counter

In this example we will display the binary values to LEDs connected to PORT2. P2_0 represents the least significant bit (LSB) and P2_3 represents the most significant bit (MSB).

For example, if we want to display binary count from 0(0x00) to 15(0x0F), we can hard code the values or another approach is use a variable and increment its value by 1 and display its value to PORT2.


Example 1: Hard Coded values

#include <at89x52.h>

void delay (void)
{
    unsigned int i;
     for(i=0; i<0x7FFF; i++);
}

void main (void)
{
   P2 = 0; //initially turn LEDS off

      while(1)
         {
                P2 = 0x00;
                delay();
                P2 = 0x01;
                delay();
                P2 = 0x02;
                delay();
                P2 = 0x03;
                delay();
                P2 = 0x04;
                delay();
                P2 = 0x05;
                delay();
                P2 = 0x06;
                delay();
                P2 = 0x07;
                delay();
                P2 = 0x08;
                delay();
                P2 = 0x09;
                delay();
                P2 = 0x0A;
                delay();
                P2 = 0x0B;
                delay();
                P2 = 0x0C;
                delay();
                P2 = 0x0D;
                delay();
                P2 = 0x0E;
                delay();
                P2 = 0x0F;
                delay();
         } 
}

Example 2: Improved version

#include <at89x52.h>

void delay (void)
{
    unsigned int i;
     for(i=0; i<0x7FFF; i++);

}

void main (void)
{
   unsigned char val = 0  //the value of this variable will be transferred to P2
   P2 = 0; //initially turn off all LEDs


    while(1)
       {
            P2 = val;  //transfer val to P2
                 val++; //increment val by 1
            if(val>=0x0F) //if we reach 0x0F then reset value
               val = 0;

             delay();

       }

}


Saturday, May 19, 2012

Working With GPIO

Now that we have our tools, we are now ready to start! For our first lesson we will learn how to turn an LED on and off. Writing and reading a port in SDCC can be directly done. So to write to a PORT is simply writing 1 to turn it on or 0 to turn it off.

To write to the whole 8 bits of the PORT, its simply "Px = value;" where x is the port number and value is an 8 bit data, and to write to a specific pin its "Px_y", where x is the port number and y is bit number.

   Examples:

     Write 0x05 to Port 1
  
                    P1 = 0x05;

     Make Port3.4 high

                     P3_4 = 1;


Now that we know the basic syntax, Lets keep going!

For the following examples, we will be using the following connections to the microcontroller.


Example 1:

   Turn on LED connected at Port 2-0.

  1. Open M-IDE and create a new file called led.c and save to your working folder.
  2. Copy and paste the following codes:
 #include <at89x52.h>

void main (void)
{

  P2 = 0; //initially turn off PORT2
 
         while(1)
         {
            P2_0 = 1;
         }
}
 3. Build the program and load the hex to the microcontroller to see the result.

 Example 2:

  1. Create a new file called blink.c and save to your working folder.
  2. Copy paste the following codes:

      To blink a LED we turn it on and off, but since the microcontroller is running fast that we cannot see, we will introduce a delay in between.We will introduce a software delay. This delay routine is just a counter that will waster CPU process before doing another instruction. Now lets make the LED connected to P2_0 blink.

#include <at89x52.h>

void delay (void)
{
    unsigned int i;                 //this for loop will just count up to 0xFFFF

     for(i=0; i<0xFFFF; i++)
     
}

void main (void)
{
    P2 = 0; //initially turn off all LEDs

    while(1)
       {
              P2_0 = 1;
              delay();
              P2_0 = 0;
              delay();
      }
}
 3. Build the program and load the hex to the microcontroller to see the result.


Led Blink Demo









Getting Started: The Tools

In order to learn, we need tools, to learn a microcontroller we need the microcontroller itself, some I/O modules, a programmer and an IDE to compile our program.

The IDE

      M-IDE for the 8051 can be downloaded freely from opcube packed with ASEM-51 assembler and SDCC C compiler. This IDE is easy to use, free and also comes with a simulator. It can be downloaded from here


Input/Output Modules

    This is a microcontroller independent Microcontroller Training Module proudly designed and made in the Philippines by e-Gizmo Mechatronix Central. It features almost all the peripherals needed for learning such as Buttons, LED's, Seven Segment, LCD, RS232, Analog sources, relay, motor drivers, Temp Sensor, RTC, matrix keypad, I/O expander and a Rotary Encoder. Any arduino compatible board can be plugged-in.



The Microcontroller
   For use with my trainer, I made an Arduino compatible board for the 8051, though the small breadboard on the center can also be used for plugging in any MCU for experiment.



Programmer
  This is my DIY programmer for programming 8051 and AVR via ISP. It is based on USBASP with modified firmware to support the 8051. Project files for the programmer can be found at 8051projects.info