Calculate TMR0 reload value at compile time?


Closed Thread
Results 1 to 4 of 4
  1. #1
    Join Date
    Oct 2005
    Location
    Sweden
    Posts
    3,521

    Default Calculate TMR0 reload value at compile time?

    Hi,
    I'm working on a MODBUS RTU slave "driver" for PBP. MODBUS messages are sent in frames and the slave is detecting the end of frame by using TMR0 as a timeout timer, generating an interrupt when it overflows.

    The MODBUS specification says that for baudrates over 19200 baud the interframe delay can be fixed to 1750us but at 19200 baud and below it should be 3.5 "character times", example: 2400baud, 8 databits, 1 start, 1stop, 1 parity = 3.5*11bits/2400bits per second=16ms.

    I'll divide the actual question in two:

    1) Let's say I fix the baudrate at 38400 baud. Can I, and if so how, make the compiler/assembler calculate the correct TMR0 reload value (for 1750us in this case) for me based on the DEFINE'd oscillator speed?

    2) If the above is possible can I extend on that and have it calculate the correct reload value for different baudrates as well? Fixed at 1750us for baudrated over 19200 and 3.5 characters "long" for 19200 baud and below.

    Thanks!
    /Henrik.

  2. #2
    Join Date
    Oct 2005
    Location
    Sweden
    Posts
    3,521


    Did you find this post helpful? Yes | No

    Default

    Hi,
    I'll take a stab at answering my own question. After playing around for the last couple of days this is what I've come up with. If anyone more familiar with conditional assembly etc sees something wrong or better ways of doing things I'm all ears.

    The following calculates the reload values for TMR0 based on the defined oscillator speed and a baudrate you tell it.

    Code:
    DEFINE OSC 8
     
    ' This define is used for calculating the proper timer reload value only.
    ' You still have to setup the USART properly. Either thru DEFINE HSER_BAUD
    ' or by setting the USART control registers manually.
    DEFINE MODBUS_BAUDRATE 9600
     
    ' Change this to 0 to remove the messages.
    DEFINE MODBUS_DEBUG 1
     
    ASM
        ; Calculate number of ns per instruction cycle based on the defined
        ; oscillator speed. If user don't DEFINE OSC it gets set to 4 by default.
        #define nS_per_cycle #v((1000/(OSC/4)))
     
        ifdef MODBUS_BAUDRATE
             ; If the defined baudrate is above 19200 the interframe delay is fixed at 1750us.
             ; If the baudrate is 19200 or below then the interframe delay should equal the time
             ; it takes to receive 3.5 characters. Here we calculate with 11 bits per character.
             ; 1 startbit, 8 databits, 1 parity, 1 stopbit. 
             ; If parity is not used the MODBUS specification says there should be 2 stopbits instead
             ; sp there's no difference. (Even parity should be the default though).
     
             if MODBUS_BAUDRATE < 19201
             ; Delay is (1 / (baudrate/11)) * 3.5 seconds
               #define MB_Frame_Timeout_ns #v((100000000/(MODBUS_BAUDRATE / 11)) * 35)
             else
               #define MB_Frame_Timeout_ns 1750000
             endif
     
             ; Calculate proper interframe delay in number of instruction cycles.
             #define MB_Frame_Timeout_cycles #v(MB_Frame_Timeout_ns / nS_per_cycle)
     
             ; Calculate the 16 bit reload value for TMR0
             #define MB_Timer_Reload_Value #v(65536 - MB_Frame_Timeout_cycles)
     
             ; Calculate the individual high and low byte form TMR0 reload
             #define MB_Timer_Reload_High #v(MB_Timer_Reload_Value / 256)
             #define MB_Timer_Reload_Low #v(MB_Timer_Reload_Value - (MB_Timer_Reload_High * 256))
     
             ; If we want to see the debug information display the debug information.
             ifdef MODBUS_DEBUG
                if (MODBUS_DEBUG == 1)
                   messg MODBUS baudrate defined to: MODBUS_BAUDRATE BAUD
                   messg PIC cycletime: #v(nS_per_cycle)ns
                   messg Interframe timeout: #v(MB_Frame_Timeout_ns / 1000)us or MB_Frame_Timeout_cycles cycles at the defined oscillator speed (#v(OSC)MHz
                   messg TMR 0 High reload value: MB_Timer_Reload_High
                   messg TMR 0 Low reload value: MB_Timer_Reload_Low
                endif
             endif
     
             ; If the calculated number of cycles overflows 16bits we can't do it without changing the prescaler.
             if MB_Frame_Timeout_cycles > 65535
                ERROR MODBUS driver: Baudrate set too low or oscillator speed to high.
             endif
     
        else
             ; The MODBUS_BAUDRATE isn't defined so we can't calculate proper delay values.
             ERROR Undefined baudrate for MODBUS driver. Can't calculate timing. Please DEFINE MODBUS_BAUDRATE.
        endif
    ENDASM
    Now, the high and low byte of the timer reload value are in the MB_Timer_Reload_High and MB_Timer_Reload_Low constants respectively. In my current interrupt service routine where I reload the timer I have this code:
    Code:
    TMR0H = 221
    TMR0L = 210
    (Only part of actual ISR)

    The above is what I want to change so that instead of having the values hardcoded it uses the previously calculated values. Looking at the assembly listing for the above code I see that PBP uses its MOVE?CB macro so my thoughts are to simply do something like:
    Code:
    ASM
        ; Don't try to use the constants if we haven't defined them. They don't get defined when
        ; MODBUS_BAUDRATE isn't defined. This is simply done to supress the errors that would otherwise
        ; get generated by not having the constants defined.
        ifdef MB_Timer_Reload_High
            ifdef MB_Timer_Reload_Low
                MOVE?CB MB_Timer_Reload_High, TMR0H 
                MOVE?CB MB_Timer_Reload_Low, TMR0L
            endif
        endif
    ENDASM
    (Again, only part of actual ISR)

    Now, using DT-INTS, are there any paging/banking problems I might run into by doing this. The idea here is that the code should be as generic as possible and I want to make sure that I'm not just lucky that it works here.

    Thanks!
    /Henrik.

  3. #3
    Join Date
    Jul 2003
    Location
    Colorado Springs
    Posts
    4,959


    Did you find this post helpful? Yes | No

    Default

    Henrick,

    The "method" looks OK, but I think you'll have some problems with the math due to integer truncation.

    &nbsp; &nbsp; #define nS_per_cycle #v((1000/(OSC/4)))

    That line will work ok IF the OSC freq. is an exact multiple of 4mhz.
    But if OSC is 10mhz then (OSC/4) is 2.5 and the .5 gets dropped. Then nS_per_cycle would be 500, which is wrong for 10mhz.

    Same as with PBP, do the multiplication first to avoid the integer problem.

    &nbsp; &nbsp; #define nS_per_cycle #v(1000*4/OSC)
    Of course, they are constants, so you could just (4000/OSC).

    The same thing happens with ...
    &nbsp; &nbsp; #define MB_Frame_Timeout_ns #v((100000000/(MODBUS_BAUDRATE / 11)) * 35)

    There are no banking issues to deal with in your code.

    hth,
    DT

  4. #4
    Join Date
    Oct 2005
    Location
    Sweden
    Posts
    3,521


    Did you find this post helpful? Yes | No

    Default

    Darrel,
    Thank you!
    I can't believe I didn't catch that one. I noticed the truncation, I even have it display the calculated results yet I never noticed the ns per cycle number being way off...

    I don't think the error in the timeout calculation would have ill effect in real life but I changed it to:
    #define MB_Frame_Timeout_ns #v((100000000 * 11 / MODBUS_BAUDRATE) * 35)

    Thanks, as always it does help a lot.

    /Henrik.

Members who have read this thread : 1

You do not have permission to view the list of names.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts