Verifying PIC24EP Clock Frequency by UART Module

In our previous article Configure PIC24 to run at 70 MIPS , I suggested a way to verify whether the PIC24E run at 70 MIPS by setting a timer output and measuring its period using an oscilloscope. If you are a normal person like me who does not have a scope sitting next to the computer, this approach might be infeasible (so why I proposed it in the first place? Doh!). Anyway, there is some other way that does not need a lab instrument. How about this? Let try setting up an asynchronous communication like UART. If the baud rate on the PIC24EP does not match that on the other side, say, our PC, then they couldn't talk. To set up the baud rate, we have to put FCY = 70M into the equation. If the clock deviates from that value just a little bit, communcation would fail. Above all, we have a chance to learn how to set up UART communcation on the PIC24EP.

A simple project goal is set. We want to control a timer by some commands sent via UART. After reset, timer 1 is set to blink an LED at 200 milliseconds period (so it changes state 5 times/sec). We should be able to turn the timer on/off by issuing command TON or TOFF. The PIC24EP must send some text response back. If this works, it verifies that our FCY is precisely at 70 MHz.

On our small prototype, an FT232R from Future Technology Int'l Ltd. is installed on board as USB to UART converter. So a simple terminal program can be used to test the connection. There is nothing complicated on the hardware part. We assign RX and TX of UART1 to pin 21 (RP42) and pin 22 (RP43), which are wired to pins TXD and RXD of FT232R, respectively. So most tasks worth discussion are on the software side. For someone unfamiliar with UART programming on a PIC family, a good place to start is PIC24EP datasheet and Family Reference Manual (DS705082B section 17. UART), where code examples are provided. A common to-do list is as follows (order is not quite relevant here).

  • Set up the peripheral pin select so that RP42 and RP43 serve as U1TX and U1RX, respectively.
  • Set up the same protocol as the other side: ex. 8-bit data, 1 stop bit, no parity, no hardware flow control
  • Select the same baud rate as that of the other side: ex. 9600, 19200, 57600, 115200, etc
  • Set up some other relevant parameters such as high/low speed, auto-baud enable bit
  • Compute and set baud rate parameter
  • Select communication scheme (polling or interrupt)
  • Write transmit/receive C functions

Let's examine some of these steps.

Peripheral Pin Select

Modern microcontrollers are designed for more flexibility. During old time when peripheral pin assignments are fixed, it is quite common for a programmer to realize that the derired functions are on the same pin. This is quite a constraint especially for a chip with low pin counts. Peripheral Pin Select is a feature to circumvent such problem. In PIC24EP, several peripherals can be assigned to physical pins in software. UART1 is one of them.

On our experiment board, the U1TX and U1RX are assigned to pin RP42 and RP43, respectively. This can be done using the following code (see examples and explanation in DS705098B: PIC24EP FRM, Section 10. I/O Ports).

 RPINR18bits.U1RXR = 43;  // U1RX to RP43
 RPOR4bits.RP42R = 1;     // U1TX to RP42

Select the protocol

In order to communicate successfully, both sides must use the same scheme, and the same baud rate (more on that later). The default in Windows driver is 8 bits data, 1 stop bit, no parity, often abbreviated as 8-N-1. This works fine for the example, so no need to change it. We also do not need hardware flow control in this simple example.

Select speed mode and compute baud parameter

Here comes the essence of this article. In order for the UART1 to run at desired baud rate, a parameter must be computed from the system clock frequency. Note in a high-end 16 bit PIC such as PIC24EP, the UART module has high/low speed mode select by setting BRGH bit. (0 = low speed, 1 = high speed). We will use BRGH = 1.

Using high speed mode, and given FCY=70e6 and the desired baud rate, we can compute a value to be written to U1BRG

U1BRG = FCY/(4*baud_rate) - 1

With this formula, values for common baud rates are precomputed and defined at the top of source code as follows.

 #define BRATE115200 150    // computed from 70e6/(4*115200) - 1 
 #define BRATE57600  303
 #define BRATE19200 910
 #define BRATE9600  1822
 

The higher the baud rate, the less error in clock frequency the module could tolerate. So to push the envelope, we will use BRATE115200, the highest baud rate our terminal program could handle.

The code below shows how to initialize UART1 with characteristics as described above

void initU1()  {
    U1MODEbits.STSEL = 0;    // 1-stop bit
    U1MODEbits.PDSEL = 0;    // No parity, 8-data bits
    U1MODEbits.ABAUD = 0;   // Auto-Baud disables
    U1MODEbits.BRGH = 1;  // high speed mode
    U1BRG = BRATE115200;
    U1MODEbits.UARTEN = 1;      // enable UART
    U1STAbits.UTXEN = 1;        // enable UART TX
    IFS0bits.U1RXIF = 0; //Clear RX interrupt flag
    IEC0bits.U1RXIE = 1; //Enable RX interrupt
}

Implement transmit/receive functions

Assuming the UART initialization is done correctly, our PIC24EC can communicate with its partner, in this case a host PC. The goal of this application is to receive some simple commands, take action, and send back responses. All messages are in ASCII format. The application software must therefore be able to access receive/transmit buffers of UART1.

Let's first look at the transmit functions. We only want to send back to host some short text messages such as "ready," "Timer 1 ON," "Timer 1 OFF." This does not take much time to execute, so no interrupt-based scheme is needed. Indeed, we just write some normal C functions for the task.

void PutChrU1(unsigned char c)
{   
    while(U1STAbits.UTXBF) {}
    U1TXREG = c;
}

void PutStrU1( char *data)
{
  do
  {  // Transmit a byte
    if(*data>0x01) {PutChrU1(*data);}
  } while( *data++ );
}
 

The baseline function to send a character is PutChrU1( ), which is called by PutStrU1( ) to transmit a string of characters. For example, PutStrU1("Ready\r\n"); would send a ready message followed by line feed and carriage return.

Now consider the receiving part. Since we do not know when a message would arrive, it is better to use an ISR instead of a polling scheme. The ISR for UART receiver is named _U1RXInterrupt(). In general, it is a good practice to keep an ISR short an simple. So in a real application, we might only want to read from the receive buffer and put in somewhere in the memory, then exit from ISR. The command is then interpreted elsewhere. For this simple example consisting only 2 commands, we choose to do everything inside the ISR because we do not want to overwhelm the reader with codes irrelevant to the main topic of this article.

So the complete UART1 ISR is implemented as follows

void __attribute__((interrupt, auto_psv)) _ISR _U1RXInterrupt()
{ 
     char dat;
     while(U1STAbits.URXDA)   {
 dat=U1RXREG&0xff; // clean up by zeroing upper 8-bit
             dat=toupper(dat);          // convert to uppercase character
             if((dat=='\r')||(dat=='\n'))  {  // response when [Enter] is hit
                      PutStrU1("\r\nReady\r\n");
              }
              INB[Rx1Cnt]=dat ;    // put in global buffer
            //-------------naive command interpreter ------------
            if(INB[Rx1Cnt-2]=='T' && INB[Rx1Cnt-1]=='O' && INB[Rx1Cnt]=='N' ){
                  _T1IE = 1;            // enable timer 1
                  PutStrU1("\r\nTimer 1 ON\r\n");
           }
            if(INB[Rx1Cnt-3]=='T' && INB[Rx1Cnt-2]=='O' && INB[Rx1Cnt-1]=='F' &&    INB[Rx1Cnt]=='F' )   {
                  LEDA = 0;
                 _T1IE = 0;          // disable timer 1
                 PutStrU1("\r\nTimer 1 OFF\r\n");
            }
            Rx1Cnt++;INB[Rx1Cnt]=0;
            if (Rx1Cnt>=maxINB-1) { Rx1Cnt=maxINB-1; } // should stay only in allocated buffer
      }
      IFS0bits.U1RXIF = 0; // clear interrupt flag
}

A couple of points worth mentioning.

  • In order for the program to work correctly, the data read from URXDA must be cleaned up by zeroing the upper 8-bit part. Then the characters are converted to upper case. This simplifies command interpreation.
  • Receiving characters are pushed into a global buffer INB. While doing so, the ISR checks whether a string of "TON" or "TOFF" is found. If that is the case, timer 1 is turned on or off by setting _T1IE = 1 or _T1IE = 0, respectively, where _T1IE represents interrrupt enable bit of timer 1. A text response is transmitted to host using PutStrU1( ) function.
  • Whenever a user presses [Enter], a response "Ready" is sent.
  • As a C programmer should always beware, never write beyond the allocated buffer size.
  • Do not forget to clear interrupt flag before leaving the ISR.

For your convenience, the complete C source file for this example can be downloaded here.

uart_ main.c

As shown in the video below, UART communication is established successfully at 115200 bps. Timer 1 can be controlled via TON and TOFF commands as desired. This result also verifies that PIC24E is indeed running at 70 MIPS.

The experiment was captured using a webcam included with my monitor. Sorry for the poor quality. I just want to show it alongside the messages in terminal program.

Comments

Popular posts from this blog

An Introduction to Finite State Machine Design

PIC24E I2C communication with MCP4725

A Note on Output Compare (PWM) Module of PIC24E