Category Archives: RS-485

C Electronics LEDs Microcontrollers PIC RS-232 RS-485

PIC Micro Modbus Update 3 : The 18F27J53 Port & tinyLED

We got our first snow yesterday; it’s a little early in the year for this and even more odd was it stuck around. The Puget Sound usually keeps us over freezing point (mostly) but Mother Nature had other plans; It was a chilly 22 deg F outside. I haven’t fully insulated my workshop yet, so despite my heater doing all it could it was 55 deg F in the workshop this morning. I can only put in ~2 hours of comfortable work at temperature so work on the my Modbus project was slow. I’ve mostly gotten the project ported to the new micro with a few exceptions.

On the breadboard is the PIC 18F27J53 development board from @atomsoft - I put some of my other favorite development boards next to it for size comparison... I believe that 18F26K22 board is no longer available.. I think I still have one or two unused boards.. better save them for a rainy day or sell them off as collector items :)
On the breadboard is the PIC 18F27J53 development board from @atomsoft – I put some of my other favorite development boards next to it for size comparison… I believe that 18F26K22 board is no longer available.. I think I still have one or two unused boards.. better save them for a rainy day or sell them off as collector items 🙂

Whenever I start a microcontroller project I try to get the basics out of the way:

1. Port I/O set up correctly? Inputs, Ouputs, Disable A/D converters, etc.

  • I think I have a problem with this or perhaps it’s my OSCCON; I did some additional reading of the specification sheet and wrote down some notes to check yet. My UART is not working.. I’m using the EUSART1 which is the “easy” one.. no peripheral pin select to worry about

2. Oscillator running? Sure, probably but is it running at the right speed?

  • Well, yes.. it took some tweaking as the device I’m using has the PLL that takes the crystal oscillator from 12MHz to 96MHz /2 .. then an additional CPU divider from there. I missed the bit about the CPUDIV register at first.. I was running my CPU at 48MHz ..  that’s okay though because I caught it fast. I always through in a little heartbeat LED blinker in my main code to test to ensure my clock is running at the right speed. I was way off.. did my reading and found I hadn’t set the CPUDIV configuration correctly (for my assumed frequency)
#pragma config CPUDIV = OSC3_PLL3// CPU System Clock Postscaler (CPU system clock divide by 3 from 48MHz)
Okay, clock is good.. 100ms test: check!
Okay, clock is good.. 100ms test: check!
  • However, now with reading the Errata I’ve found there is a problem with the ESUART receive vs. transmitter baud rate generator and it’s recommended that I run my CPUDIV at the OSC1 (1:1) frequency.. so I’ll change that. It’s still not my problem though, On boot my micro transmits some test but that’s not happening according to my logic analyzer.

3. Make sure your IO actually works!

  • Once I think I have everything set up I usually set up some test LEDs and/or the UART.. I’ll send data to the console.. or if it’s basic.. just some LED blinking. I am doing both in this project..

So as of now I have my code to the point my oscillator is running at the frequency I want it to, of course that will have to change which is annoying because the built-in __delay functions with XC8 don’t like running so fast so I have to go and build my own. Or use timers…

My ESUART is not working.. but I have about a dozen odd items to check. I found in the errata that the RX side of the UART gets it baud rate from somewhere other than where the TX side gets. The workaround is keeping your CPUDIV at 0x03,  1:1. I can tell by my logic analyzer the TX isn’t happening.. my other guesses are the TX is at 3.3V because that’s what the micro runs at.. my UART to USB interface runs at 5V. I can set the micro up to run open collector and use a pull-up resistor. I also need to double-check my OSCCON and finally there was some other information that under some circumstances if the interrupts are enabled after the UART is, somehow, the UART gets disabled.. so I can shuffle some code around to figure that out. There are also a few other tidbits to double-check and test.

I’m not going to drop the code in because it doesn’t work all the way.. but you might be interested in just checking out the code itself. You can find it on my new pastebin at: http://pastebin.com/7z8dBMW4 . I recently created this account to shuffle code between my laptop inside and my workbench computer.

Some other tidbits:

I recently picked up the Breadboard Buddy 6.2 and some tinyLED leds from the AtomSoft store at Tindie. The breadboard buddy serial interface worked fine when testing it. I did have to download a driver for Windows 7 but the price is nice; it’s been loopback tested.. and the power supplies are right on (3.3, 5V). The tinyLED despite being cheap was surprisingly exciting. I am in love with these things because they save so much space on my breadboard. I have two green and four white.. rumor is there might be some 4x and 8x LEDs coming out as well! It’s probably obvious I love to develop on a breadboard.. everything off dev board usually runs at 100KHz or less (I2C)… so the capacitance isn’t an issue.

 

An up close shot with the AtomSoftTech tinyLEDs in the background. I think my LEDs were my favorite purchase of the month.. they are small and save me a ton of breadboard room. Note: this breadboard was a free "reject" because the alignment was wrong.. so the LEDs don't line up correctly. In standard boards they're straight. I ordered a new breadboard just to confirm this.. it'll be a bit; it's coming from China of course.
An up close shot with the AtomSoftTech tinyLEDs in the background. I think my LEDs were my favorite purchase of the month.. they are small and save me a ton of breadboard room. Note: this breadboard was a free “reject” because the alignment was wrong.. so the LEDs don’t line up correctly. In standard boards they’re straight. I ordered a new breadboard just to confirm this.. it’ll be a bit; it’s coming from China of course.

The weather looks better later into this week so hopefully I can get in a bit more shop time and wrap this up. I’ve determined I am not going to use this device other than testing integration so I’ll use some preset registers being faux digital inputs. It will be a slam dunk for anyone to add in some hardware polling and adding on to this to make a full blow controller. If I do decide to take this further I’ll consider making the USB interface some kind of firmware update to configure the I/O without downloading code from MPLABX.

C Electronics Microcontrollers PIC RS-232 RS-485

PIC Micro Modbus Update 2 : Calculating CRC

Work continued this morning on my MODbus project. It was interrupted by sunshine which prompted yard work… While having a house/yard is worth the work and loss of “fun time”, there are certainly aspects of renting an apartment I miss 😀

The code I’m sharing this round could be a nice starting point for anything following along. I haven’t thoughly debugged it yet, but it is a good start. The modpoll program I am using sends a 0x01 0x03 0x00 0x00 0x00 0x00 0x0A CRC CRC.

A review of the modbus protocol reference guide shows us this is a query from the master to slave address 0x01 (Address 1), 0x03 Read Holding Register request starting at register 0x00 0x00 (4 0001, usually seen as 40001) and the request includes  10 registers, 0x00 0x0A, which would be 4 00001 to 4 00011. The code I’m sharing will  have the micro properly respond to the Modpoll software with a predefined response. (0x01, 0x03, <number of bytes>, 20 bytes of data, CRC, CRC). Before sending the canned response the software determines if the poll is for the address of the micro (0x01), it will also check to ensure the CRC is correct. If the CRC is not correct, or the address is wrong the micro will ignore the poll. I’m running straight serial at 9,600 baud but once I port the micro I will bump it up to 19200, 8N1 and throw a hardware RS485 interface on it.

The micro also determines its address now from two inputs during start-up. It can be address 0x01, 0x02, 0x03, and 0x04 depending on the switch settings. I will expand this to address up to d32 in the future. RS-485 specification only allows for 32 devices on the bus so there is no reason for me to address any device over 32.

Of note: The code will drop the master packet if it times out on the interrupt timer (Timer1) but my implementation is far more fault-friendly than specifications allow for. I am currently experiencing the occasional timeout at the master which I have not looked into yet.

Please note this code is “under construction” and is unoptimized. I’ve done many thing the long way because it was easy to debug spots and dump values to the uart for troubleshooting. This is what happens when you give someone a micro with way more flash than they need! 🙂


/* 
 * File:   main.c
 * Author: Charles M Douvier
 * Contact at: http://iradan.com
 *
 * Created on November 8, 2014, 2:37 PM
 *
 * Target Device:
 * 16F1509 on Tautic 20 pin dev board
 *
 * Project:
 *
 *
 * Version:
 * 1.0
 *
 */
#ifndef _XTAL_FREQ
#define _XTAL_FREQ 8000000 //8Mhz FRC internal osc
#define __delay_us(x) _delay((unsigned long)((x)*(_XTAL_FREQ/4000000.0)))
#define __delay_ms(x) _delay((unsigned long)((x)*(_XTAL_FREQ/4000.0)))
#endif

#include 

//config bits
#pragma config FOSC=INTOSC, WDTE=OFF, PWRTE=OFF, MCLRE=ON, CP=OFF, BOREN=ON, CLKOUTEN=OFF, IESO=OFF, FCMEN=OFF
#pragma config WRT=OFF, STVREN=OFF, LVP=OFF

#define _XTAL_FREQ 8000000 //defined for delay

    char    ctxt[10], buf1, testcrc[4];    //buff serial string
    volatile unsigned int     ping, isrcall, index, reading, new_rx;
    int address, crc_high, crc_low, i;
    unsigned int result;
    int AV1, AV2, AV3, AV4, BV1, BI1;               //fake inputs for testing

/*
 *  Interrupt Service
 */
void interrupt ISR() {
/*    *    if (PIR1bits.RCIF)          // see if interrupt caused by incoming data
    *{
    *    parity_rx = RCSTA1bits.RX9D;
    *    uart_data = RCREG;     // read the incoming data
    *    PIR1bits.RCIF = 0;      // clear interrupt flag
    *}
        */
    if (PIR1bits.RCIF)          // see if interrupt caused by incoming data
    {
        isrcall = 0x01;
        char temp;
        temp = RCREG;     // read the incoming data
        buf1 = temp;
        if(temp==address && reading==0)      //if my address..
        {
            index = 0;                  //reset index
            reading = 1;                //from now on go to else if
            LATCbits.LATC1 = 1;
            new_rx = 0;
            ping = 1;
        }
        else if(reading == 1)           //in middle of GPS sentence
        {
            //TODO reset timeout timer
            ctxt[index] = temp;         //load it up
            index++;                    //increment index
            ping = 1;                   //this is for debugging
            if(index > 6)              //1+7 = master frame.
                {
                index = 0;              //reset index
                reading = 0;            //no longer storing the string
                new_rx = 1;             //"ding"
                T1CONbits.TMR1ON = 0;
                }
        }

        //PIR3bits.RC2IF = 0;      // clear interrupt flag
    }
    //RCSTA2bits.FERR = 0;    //Clear errors
    //RCSTA2bits.OERR = 0;

    //time out timer, if tripped new_rx=0;
    if (PIR1bits.TMR1IF)
    {
        new_rx=0;
        ping = 0;
        T1CONbits.TMR1ON = 0;
        PIR1bits.TMR1IF = 0;
        if (reading) {
        reading = 0;
        LATCbits.LATC0 = 1;
        }
    }
}

void init_io(void) {

    ANSELA = 0x00; // all port A pins are digital I/O
    ANSELB = 0x00; // all port B pins are digital I/O
    ANSELC = 0x00; // all port B pins are digital I/O

    LATC = 0x00;

    TRISAbits.TRISA0 = 0; // output
    TRISAbits.TRISA1 = 0; // output
    TRISAbits.TRISA2 = 0; // output
    TRISAbits.TRISA3 = 0; // output
    TRISAbits.TRISA4 = 0; // output
    TRISAbits.TRISA5 = 0; // output

    TRISBbits.TRISB4 = 0; // RB4 = nc
    TRISBbits.TRISB5 = 1; // RB5 = nc
    TRISBbits.TRISB6 = 0; // RB6 = nc
    TRISBbits.TRISB7 = 0; // RB7 = nc
    
    TRISCbits.TRISC0 = 0; // output
    TRISCbits.TRISC1 = 0; // output
    TRISCbits.TRISC2 = 0; // output
    TRISCbits.TRISC3 = 0; // output
    TRISCbits.TRISC4 = 1; // address input
    TRISCbits.TRISC5 = 1; // address input
    TRISCbits.TRISC6 = 1; // input
    TRISCbits.TRISC7 = 1; // input    
}

void uart_xmit(unsigned int mydata_byte) {

    while(!TXSTAbits.TRMT);    // make sure buffer full bit is high before transmitting
    TXREG = mydata_byte;       // transmit data
}

void write_uart(const char *txt)
{
                                //this send a string to the TX buffer
                                //one character at a time
       while(*txt)
       uart_xmit(*txt++);

}

void serial_init(void)
{
    //9600 8N1
    // calculate values of SPBRGL and SPBRGH based on the desired baud rate
    //
    // For 8 bit Async mode with BRGH=0: Desired Baud rate = Fosc/64([SPBRGH:SPBRGL]+1)
    // For 8 bit Async mode with BRGH=1: Desired Baud rate = Fosc/16([SPBRGH:SPBRGL]+1)

    TXSTAbits.BRGH=1;       // select low speed Baud Rate (see baud rate calcs below)
    TXSTAbits.TX9=0;        // select 8 data bits
    TXSTAbits.TXEN = 1;     // enable transmit


    RCSTAbits.SPEN=1;       // serial port is enabled
    RCSTAbits.RX9=0;        // select 8 data bits
    RCSTAbits.CREN=1;       // receive enabled

    SPBRGL=51;  // here is calculated value of SPBRGH and SPBRGL
    SPBRGH=0;

    PIR1bits.RCIF=0;        // make sure receive interrupt flag is clear
    PIE1bits.RCIE=1;        // enable UART Receive interrupt
    INTCONbits.PEIE = 1;    // Enable peripheral interrupt
    INTCONbits.GIE = 1;     // enable global interrupt

         __delay_ms(50);        // give time for voltage levels on board to settle

}

void run_timer (void) {
        T1CONbits.TMR1ON = 0;
        TMR1=0;
        TMR1L=0x00;
        TMR1H=0xAA;
        T1CONbits.TMR1CS = 0x01;
        T1CONbits.T1CKPS = 0x01;
        PIE1bits.TMR1IE = 1;
        T1CONbits.TMR1ON = 1;


//        PIR1bits.TMR1IF=0;
}



void check_my_address(void) {
    /*
     *  Determine what address the unit is based on DIP switches
     *  PORTCbits.RC4   Switch 1
     *  PORTCbits.RC5   Switch 2
    */

    if (PORTCbits.RC4 & PORTCbits.RC5) address = 0x04;
    if (!PORTCbits.RC4 & PORTCbits.RC5) address = 0x03;
    if (PORTCbits.RC4 & !PORTCbits.RC5) address = 0x02;
    if (!PORTCbits.RC4 & !PORTCbits.RC5) address = 0x01;

}

void send_mpoll (void) {
               //testing response
            uart_xmit (address);    //address
            uart_xmit (ctxt[0]);    //function
            uart_xmit (0x14);       //20 bytes back
            uart_xmit (0x01);
            uart_xmit (0x02);
            uart_xmit (0x03);
            uart_xmit (0x04);
            uart_xmit (0x05);
            uart_xmit (0x04);
            uart_xmit (0x03);
            uart_xmit (0x02);
            uart_xmit (0x01);
            uart_xmit (0x00);
            uart_xmit (0x01);
            uart_xmit (0x02);
            uart_xmit (0x03);
            uart_xmit (0x04);
            uart_xmit (0x05);
            uart_xmit (0x04);
            uart_xmit (0x03);
            uart_xmit (0x02);
            uart_xmit (0x01);
            uart_xmit (0x00);
            uart_xmit (0x77);       //2 byte CRC
            uart_xmit (0xA8);
}

//did the chicken come before the egg?
//this MODbus CRC bit was gently borrowed from the internet..
//I can't determine who wrote it.. it shows up in an Ardiuno Sketch for MODbus
// Modbus over serial line - RTU Slave Arduino Sketch
//with referenced of Juan Pablo Zometa, Samuel Marco, Andras Tucsni, Philip Costigan
//It also shows up on a number of websites and forums uncredited... many PIC or C based ..
//so who knows, but I didn't write it
//The MODbus CRC-16 function is outlined on the Modicon site for reference

unsigned int modbus_crc(unsigned char buf[], int start, int cnt)
{
	int 	i,j;
   unsigned temp, temp2, flag;

   temp=0xFFFF;

   for (i=start; i<cnt; i++){
   	temp=temp ^ buf[i];

      for (j=1; j<=8; j++){
      	flag=temp & 0x0001;
         temp=temp >> 1;
         if (flag) temp=temp ^ 0xA001;
         }
      }
   /*** Reverse byte order. ***/
   temp2=temp >> 8;
   temp= (temp << 8) | temp2;
   return(temp);
}

int check_master_crc (void) {

    int diff, diff1, diff2;       //what we send back
    char data[6];
    result = 0x0000;
    crc_high = 0x00;
    crc_low = 0x00;

    data[0] = address;  //this is ugly but all master queries are 6bytes+CRC so it'll do
    data[1] = ctxt[0];  //function
    data[2] = ctxt[1];  //start_addressH
    data[3] = ctxt[2];  //start_addressL
    data[4] = ctxt[3];  //# of regH
    data[5] = ctxt[4];  //# of regL

    result = modbus_crc(data,0,6);
    crc_high = result >> 8;
    crc_low = result & 0x00FF;

    diff1 = ctxt[5] ^ crc_high;
    diff2 = ctxt[6] ^ crc_low;
    diff = diff1 + diff2;
    //this spits back an XORed value between calculated and received CRC
    return(diff);
}

int main(void) {

    // set up oscillator control register, using internal OSC at 4MHz.
    OSCCONbits.IRCF = 0x0E; //set OSCCON IRCF bits to select OSC frequency 8MHz
    OSCCONbits.SCS = 0x02; //set the SCS bits to select internal oscillator block
    OPTION_REGbits.nWPUEN = 0; // enable weak pullups (each pin must be enabled individually)
    new_rx=0;

    init_io();
    serial_init();
    run_timer();

    check_my_address();

    LATCbits.LATC0 = 1;
    LATCbits.LATC1 = 1;

    __delay_ms(500);
    LATCbits.LATC0 = 0;
    LATCbits.LATC1 = 0;

    if (address == 0x01){
        LATCbits.LATC0 = 1;
    }
    if (address == 0x02) {
        LATCbits.LATC1 = 1;
    }
    __delay_ms(500);
    __delay_ms(500);
    LATCbits.LATC0 = 0;
    LATCbits.LATC1 = 0;


    INTCONbits.GIE = 1;
    INTCONbits.PEIE = 1;

    while (1) {

        //If read data, not me and then if no 232 traffic for >800us then any xmit over
        //104 us (9600) x 8 quiet periods minus some for wiggle.

        if (ping) {
            run_timer();
        }

        if (new_rx) {       //this device was polled
            //testing response
            i = check_master_crc();     //check master query crc
            if (i==0x00) {              //CRC Ok?
              send_mpoll();             //send test response to known test query
            }

            new_rx = 0;
        }

        //testcrc[0] = 0xFF;
        //testcrc[1] = 0x02;      //right now this is only thing processed


        //result = modbus_crc(testcrc,0,2);
        //crc_high = result >> 8;
        //crc_low = result & 0x00FF;


        __delay_ms(50);
        LATCbits.LATC0 = 0;
        LATCbits.LATC1 = 0;
    }
}

Some screenshots I took of my success!

Finally, good data!
Finally, good data!
A good MODbus query and response.
A good MODbus query and response.

 

C PIC RS-485

PIC Micro Modbus Update 1 : Breadboard mock-up

I was sitting in my workshop looking for something other than painting/staining… I have project overload for sure, so I was looking for something that was 180 degrees out from what I had been working on. On my whiteboard I have one of those “been-on-the-TODO-list-for-at-least two-years” projects aptly named “MODbus”.. After determining that a BACnet controller wasn’t going to run on a 8-bit micro (many moons ago) I figured I’d try it’s much lighter weight cousin MODbus. It was time.

MODbus is a older-than-dirt communications protocol for industrial process/power controls. It’s generally seen in the wild as MODbus/RTU which is a RS-485 protocol ran at 9600 or 19200 baud. Of course there are a ton of variations and it wouldn’t be a comm protocol these days unless someone decided to make a MODbus/IP.. but that’s beyond the scope. Google, as always, is your friend. Super basic protocol the master polls the loop with the address, a basic command, and what data points it’s affecting. The slave responds with it’s address, the command the resulting data as required. The error correction is CRC 16bits.. never used CRC before.. seen it a million times, so this will be fun. In the beginning I’ll just ignore it and send it some pre-determined correct data as needed.

 

Step 1.. pick a micro, because I don’t feel like making process flow charts just yet.

I might try Jason’s new dev-board that’s on the way now, hopefully it’ll pop in tomorrow or next week. For now I’m working on the 16F1509. I don’t think it’ll stay on this just because I feel like mixing it up a bit. I’ll switch it to the ’14K22 at the very least, the ‘1509 was in the board already and wanted to get going on this project. Note: I didn’t even successfully complete Step 1. 😀

 

Step 2. Determine what’s a good target for a successful test?

Well I like to break dev up into little pieces.. so I have as simple of goals as possible:

2a. I just wanted to prove to myself my 8x DIP switch was read to choose a slave device address. Check, no brainer.. that was easy. CPU timing seemed off through… oops:! 0x0D is 4MHZ.. I want 0x0E! (8).

2b. I got the serial going, okay… sending 0x2E (“.”).

2c. I found a MODbus master polling simulator that will work for the next 29 days (it’s 198x tech people, why are we charging for the basic tools?) .. I’ll build one of those later… and now..

2d. Have the PIC recognize that it’s being polled. FAIL!

 

That’s where I ended off for the evening. Scratching my head why my received address isn’t recognized (when it does it should output a pulse on a known good output).. the LA didn’t pick up anything but it is going through interrupt for the RX. I guess it’ll be a nice Saturday morning project with some tea.

 

The logic analyzer ascreenshot. I am getting good packets.. just not recognizing them yet.
The logic analyzer ascreenshot. I am getting good packets.. just not recognizing them yet.

In the screenshot I didn’t rename the channels, so Channel 1 is RX (from the PC running the sim), TX is channel 3. I’m dumping the RX buffer into a temp variable and transmitting it when it makes it through my main loop.

No sense in sharing the code just yet.. it’s a hacked up pile of festering dog crap. The breadboard even looks a little chaotic!

 

Mock-up of my MODbus controller breadboard style version 0.00001.
Mock-up of my MODbus controller breadboard style version 0.00001.

So why MODbus? Why not! I have four reasons for taking up this project, now more than ever.

1. I would like to implement a small comm system in our new home.. MODbus is light weight enough to do it on 8bit micros, an I can use something more powerful for control if I want to later.

2. At work our BACnet supervisors (master units) are getting native MODbus drivers in the next sub-version (instead of using integrators); that’s cool, I want to test it out.

3. I want to turn the whole thing into an article of 0xEE.net after I’m done… bonus.

4. why not? I’d like to experience.. plus I already told co-workers I would so no way I can fail now!

C Electronics Interface RS-485

TIA-485 PIC tutorial

I just finished up a basic tutorial of using TIA-485 (RS-485/EIA-485) with a PIC micro on 0xEE.net.. this will be part one of at least 2 or 3 articles.

 

 0xEE.net PIC Talks RS-485