Shop OBEX P1 Docs P2 Docs Learn Events
PULSOUT, PWM, or HIGH / LOW simulator for RPM — Parallax Forums

PULSOUT, PWM, or HIGH / LOW simulator for RPM

eagletalontimeagletalontim Posts: 1,399
edited 2011-11-30 07:50 in General Discussion
I have a project I am working with that will need a 5v square wave output to simulate RPM of a vehicle. The program I am using reads the RPM correctly when connected to my vehicle, but I cannot figure out how to simulate the RPM signal from an SX chip. I have tried the PWM function, PULSOUT function, and HIGH / LOW with no luck. I am running the simulator chip at 4mhz and the input chip at 5mhz. The input chip has a 7 segment display that will show a 1 if the RPM is within 300 to 12,000. If it is lower than 300, it will show an L. If it is Higher, it will show an H. Every test so far shows an L. Any help is appreciated!

Comments

  • wasswass Posts: 151
    edited 2011-11-26 22:25
    You need to use a command that will generate a continuous square wave at a specified frequency. You could use a HIGH command followed by a LOW command followed by a PAUSE command (for timing) and put them in a loop. Or you could use PULSOUT in a loop. But the easiest way to get an accurate square wave is the FREQOUT (or SOUND) command. FREQOUT takes duration as a parameter but it can be at most 255 ms, you you probably need to put that in a loop too. Keep in mind that the Freq parameter is in Hz, you'll need to convert to RPM.
  • eagletalontimeagletalontim Posts: 1,399
    edited 2011-11-27 00:13
    I have tried the HIGH and LOW commands, but even with no pause between them, the display said L which means it was lower than 300 RPM. Is there some formula I need to use to get the pause between the HIGH and LOW command to register as a valid RPM? I was trying to base everything off of 1000 RPM. Since my car is a 4 cylinder, there will be 2 pulses per RPM....1 and 3, then 2 and 4 fire at the same time on the coil pack. This means that there is a certain time that the output should be HIGH, and there is a certain time it will be LOW. With knowing that 1000 RPM is 16.667 revolutions per second, I can divide that by 2 which will give me the gap between sparks, 8.333 revolutions per 500 milliseconds. Is there a way to use those numbers to calculate the PAUSE or PAUSEUS delay or do I need to know how many milliseconds the signal is HIGH before going LOW? I have no way to get that information :(
  • wasswass Posts: 151
    edited 2011-11-27 08:45
    You should post your full code here, it will tell us what clock speed you're using, specifically the oscillator setting should match the FREQ parameter on the DEVICE line.


    If they do match something like:

    DO
    HIGH pin
    PAUSE 30
    LOW pin
    PAUSE 30
    LOOP

    Will produce a pulse roughly every 60 milliseconds that lasts for 30 milliseconds. (I says "roughly" becasue this code does not take into account the time if take for the PIN command to execute nor the DO/LOOP, but they are insignificant if you're running the SX at a decent speed.)
  • eagletalontimeagletalontim Posts: 1,399
    edited 2011-11-27 17:30
    Here is the code I have for the simulator :

    [PHP]
    DEVICE SX28, OSCHS2, TURBO, STACKX, OPTIONX
    FREQ 50_000_000
    'IRC_CAL IRC_FAST
    ID "RPMTEST"

    '
    ' IO Pins
    '
    idx VAR BYTE
    LED PIN RB.0 OUTPUT ' LED pin

    '
    ' Constants
    '

    Delay CON 1 ' time LED is on

    ' =========================================================================
    PROGRAM Start
    ' =========================================================================

    '
    ' Program Code
    '

    Start:
    DO
    high LED
    PAUSE Delay
    low LED
    PAUSE Delay
    LOOP
    [/PHP]

    Then, Here is the function I use for RPM. I don't want to post all of the code since it is a product I sell but am trying to upgrade. This chip runs at 5Mhz with a crystal.

    [PHP]
    getrpm:
    PULSIN RPM_signal, 0, pWidth0
    PULSIN RPM_signal, 1, pWidth1
    pWidth0 = pWidth0 + pWidth1
    pWidth0 = pWidth0 * 2
    dividendMSW = $005B
    dividendLSW = $8D80
    rpm = 1
    overflow = 0
    DO
    doneBit = rpm.15
    rpm = rpm << 1
    IF overflow = 1 THEN
    rpm.0 = 1
    dividendMSW = dividendMSW - pWidth0
    ELSE
    IF dividendMSW >= pWidth0 THEN
    rpm.0 = 1
    dividendMSW = dividendMSW - pWidth0
    ENDIF
    ENDIF
    overflow = dividendMSW.15
    dividendMSW = dividendMSW << 1
    dividendMSW.0 = dividendLSW.15
    dividendLSW = dividendLSW << 1
    LOOP UNTIL doneBit = 1
    rpm = rpm << 1
    rpm.0 = overflow
    IF dividendMSW >= pWidth0 THEN
    rpm.0 = 1
    ENDIF
    RETURN rpm
    ENDFUNC
    [/PHP]

    I cannot change the frequency of the RPM input chip so I need to have the simulator work with it. I have tried the default 4Mhz, then 5Mhz, then 40Mhz, and finally 50Mhz. Even with 1 millisecond between the high and low, the readout of the input chip display is "L" for low. I really need to get this working since this will be my testing chip to connect to my product.
  • ZootZoot Posts: 2,227
    edited 2011-11-27 19:09
    Is there a reason you are not doing this in an ISR (Interrupt Service Routine). That way you could just load a counter with whatever simulated RPM you want out and it would rip from there.
  • eagletalontimeagletalontim Posts: 1,399
    edited 2011-11-27 21:41
    I tried an ISR that was posted in one of my threads about a year ago that was said to work perfect. I did everything as told and ran it at 50Mhz, but still got an RPM of 1 no matter if at idle (750rpm) or at 7000 rpm. I did no modifications to the code posted before and could not get any readings from it. I even tried about a week ago with the same code just to see if I may have missed something or ran the signal to the wrong pin. Still no luck. I have the "getrpm" function working correctly so I figured I would leave it for now. Eventually, I will be moving to the Prop anyways. But for now, I need to figure some way to simulate RPM so I can test the product before sending it out the door.
  • wasswass Posts: 151
    edited 2011-11-27 22:02
    Have you verified that your SX chip used as the simulator is working correctly and that the 50MHz oscillator is working. Have you looked at the output on an a oscilloscope or checked it with a frequency counter?

    Also, since your product works with a real engine running at 12 volts (?) and the simulator has to be running at a much lower voltage are you feeding the input into the device under test in a way that assures an accurate simulation?
  • eagletalontimeagletalontim Posts: 1,399
    edited 2011-11-28 06:26
    I don't have an O-Scope unfortunately, so testing the output with the other chip is all I can do. I have tried several chips and 50Mhz resonators to verify the SX chip did not have a bad pin for the output pin and that the resonator was not the issue. I have also ran the chip at the default 4Mhz without a crystal or resonator to see if that made any difference. What I have been using is an LED to ensure there is voltage out of the pin and that it is pulsing. Setting the PAUSE different causes the LED to blink at a visible interval which seems steady to the eye. I know that does not mean anything, but I should be able to get some kind of read out from the other chip by changing the PAUSE delay to the correct values. Both circuits share a common ground so the pulse has a complete circuit and the positive power comes from the same source for both circuits. The only thing I can think I don't have correct is the timing for how long the output pin goes HIGH and how long it stays LOW between pulses. Once I get that figured out, I am sure it will work perfectly.
  • ZootZoot Posts: 2,227
    edited 2011-11-28 07:58
    Post your full current code for the LED test circuit and your RPM simulator.
  • eagletalontimeagletalontim Posts: 1,399
    edited 2011-11-28 08:40
    I did post the full code to the simulator, but I cannot post the full code to the chip that receives the RPM signal since it is a product I sell. The function "getrpm" is just used as rpm = getrpm. The header for the chip is :

    [PHP]
    DEVICE SX28, OSCXT2, TURBO, STACKX, OPTIONX
    FREQ 5_000_000

    ' then here is the rpm verifier :

    IF rpm > 300 THEN
    IF rpm < 12000 THEN
    isrpm = 1
    ELSE
    isrpm = 0
    LIMP_MODE 0
    ENDIF
    ELSE
    isrpm = 0
    LIMP_MODE 1
    ENDIF

    ' the function LIMP_MODE just changes the display to show an "H" for High RPM or an "L" for Low RPM. If it the RPM is in range, it just shows a "1"
    [/PHP]
  • ZootZoot Posts: 2,227
    edited 2011-11-28 10:46
    I don't see full code for the "reader" but for the simulator (i.e. to output a given rpm pulse train on an SX pin), I would just set up a counter for 50% duty cycle in the interrupt. See below. This should toggle the LED at the defined (CON) RPM, within 1ms.
    DEVICE         SX28, OSCHS2, TURBO, STACKX, OPTIONX 
    FREQ         50_000_000 
    'IRC_CAL        IRC_FAST 
    ID         "RPMTEST" 
    
    ' ------------------------------------------------------------------------- 
    ' IO Pins 
    ' ------------------------------------------------------------------------- 
    LED         PIN     RB.0     OUTPUT    ' LED pin 
    
    ' ------------------------------------------------------------------------- 
    ' Constants 
    ' ------------------------------------------------------------------------- 
    
    rpm CON 3000 ' desired rpm out (frequency)
    rpmCalc CON ( 1000 / ( rpm / 60 ) ) / 2 ' not used in code, shown for example
    ' ( 1000ms / ( rpm / 60secsPerMin ) ) / 50% duty cycle
    
    ' ------------------------------------------------------------------------- 
    ' Variables
    ' ------------------------------------------------------------------------- 
    
    freq VAR Word ' user calculated frequency
    cntr VAR Word ' work variable for ISR
    
    
    ' ------------------------------------------------------------------------- 
    INTERRUPT 1_000 ' 1ms "ticks"
    ' ------------------------------------------------------------------------- 
    GOTO Interrupt_Handler
      
    
    ' ========================================================================= 
    PROGRAM Start 
    ' ========================================================================= 
    
    
    ' ------------------------------------------------------------------------- 
    ' Program Code 
    ' ------------------------------------------------------------------------- 
    
    Start: 
    
     LED = 0
     cntr = 0
     freq = rpm
     freq = freq / 60 ' revs per second
     freq = 1000 / freq ' convert to isr "ticks"
     freq = freq / 2 ' divide by 2 for 50% duty cycle
    ' alternatively you could just use computed constant, e.g.
    ' freq = rpmCalc
     
    
    Main:
    
      DO 
    
      LOOP  
    
    
    Interrupt_Handler:
      IF cntr > freq THEN 
         LED = ~LED ' toggle led
         cntr = 0 ' reset counter
      ENDIF    
      cntr = cntr + 1
      RETURNINT
    
  • eagletalontimeagletalontim Posts: 1,399
    edited 2011-11-29 15:21
    The code you posted does seem to work after modifying a few things. I wound up making a program that displays the RPM on a 16 digit display. The highest RPM value I can create is 7600 RPM. I am needing to get to 12,000 if possible.
  • ZootZoot Posts: 2,227
    edited 2011-11-29 16:12
    Use a longer ISR interval or use a larger than Word sized counter (you would have to handle the carry on the third byte yourself), e.g.
    cntr VAR Word ' contains cntr_LSB and cntr_MSB - two bytes
    cntr_HSB VAR Byte ' will be third byte of counter -- highest byte
    
    cntr = cntr + 1
    IF C = 1 THEN
      INC cntr_HSB
    ENDIF
    
    
  • eagletalontimeagletalontim Posts: 1,399
    edited 2011-11-29 16:33
    I am beginning to think it is something to do with the remainders left after the division happens. I have done some simple math and found that :

    ((60 / RPM) * 1000) / 4 = Delay Between HIGH / LOW

    So if I want 4000 RPM, this is what would calculate in the code :

    60 / 4000 = 0.015 (Would be __REMAINDER or __WREMAINDER)
    0.015 * 1000 = 15 (Number of Milliseconds for 1 complete revolution) - Not used when calculated in chip since __REMAINDER = 15 anyways
    15 / 4 = 3.75 (Rev Time / 4 to give 2 HIGH and 2 LOW periods during 1 revolution)

    The problem I am running into is the remainder. Since 4000 RPM = 3.75 Milliseconds and 5000 RPM = 3 Milliseconds, I need to use the remainder somehow. The ISR is a little confusing since I don't usually use them due to me using PULSIN, SERIN, SEROUT in many of my projects.

    What I think is also hindering me from getting it right is the fact that some remainders are infinite like .333333333333.... That is higher than the WORD max of 65k. This would majorly throw off calculations.
  • ZootZoot Posts: 2,227
    edited 2011-11-29 19:26
    The accuracy depends on the interrupt period. I used a relatively slow interrupt period just as an example. You could use a much faster interrupt and get better precision.
  • eagletalontimeagletalontim Posts: 1,399
    edited 2011-11-30 03:08
    Would I be able to use the Interrupt with ANALOGIN to read a POT so I can adjust RPM on the fly? The original code you posted when wrote to the chip also throws a message that the interrupt rate will be 1001.6. Every time I change it, it never matches what I am changing it too. I have tried 10_000 and 100_000, but it still changes it. This will throw off accuracy as well. The part of the code that says 1000 / fr has to be changed to 275 / fr in order for the RPMs to be even close to the desired RPM. With 5000 set as the desired RPM, I get 2100 on the display. Here is the code I am using to display the RPM :

    [PHP]
    DEVICE SX28, OSCXT2, TURBO, STACKX, OPTIONX
    FREQ 5_000_000


    '
    ' IO Pins
    '

    LcdTx PIN RB.0 ' LCD serial connection
    TachIn PIN RB.4 INPUT SCHMITT
    '
    ' Constants
    '

    LcdBaud CON "T19200" ' or T2400, or T9600

    LcdBkSpc CON $08 ' move cursor left
    LcdRt CON $09 ' move cursor right
    LcdLF CON $0A ' move cursor down 1 line
    LcdCls CON $0C ' clear LCD (need 5 ms delay)
    LcdBLon CON $11 ' backlight on
    LcdBLoff CON $12 ' backlight off
    LcdOn1 CON $16 ' LCD on; no crsr, no blink
    LcdLine1 CON $80 ' move to line 1, column 0
    LcdLine2 CON $94 ' move to line 2, column 0

    Baud2400 CON 80
    Baud4800 CON 40
    Baud9600 CON 20
    Baud19K2 CON 10
    Baud38K4 CON 5

    Baud1x0 CON Baud38K4 ' 1 bit period (ISR counts)
    Baud1x5 CON Baud1x0 * 3 / 2 ' 1.5 bit periods

    LF CON 10
    CLS CON 12
    CR CON 13

    idx VAR Byte ' loop counter
    line1 VAR Byte(16) ' line 1 buffer
    line2 VAR Byte(16) ' line 2 buffer

    cmd VAR Byte
    temp1 VAR Byte ' subroutine work vars
    temp2 VAR Byte
    temp3 VAR Byte
    temp4 VAR Byte
    temp5 VAR Byte
    tmpW1 VAR Word
    tmpW2 VAR Word

    nStr VAR Byte(5)

    rpm VAR WORD
    smallrpm VAR Byte
    dividendMSW VAR WORD
    dividendLSW VAR WORD
    overflow VAR BIT
    doneBit VAR BIT

    ' =========================================================================
    PROGRAM Start
    ' =========================================================================


    '
    ' Subroutine Declarations
    '

    wait SUB 1, 2 ' delay in milliseconds
    LCD_OUT SUB 1, 2 ' send byte {+ count} to LCD
    UPDATE_L1 SUB 0 ' update line 1 of LCD
    UPDATE_L2 SUB 0 ' update line 2 of LCD
    PUT_DIGIT FUNC 1,1
    GET_RPM FUNC 2, 0
    store SUB 4

    '
    ' Program Code
    '

    Start:
    PLP_A = %1011 ' pull up unused pins
    PLP_B = %0001_0001
    TRIS_C = %00000000
    HIGH LcdTx
    wait 50 ' let LCD initialize
    LCD_OUT LcdOn1 ' no cursor or blink
    LCD_OUT LcdCls ' clear the LCD
    wait 5
    PUT line1, "RPM Reader"
    UPDATE_L1

    Main:
    smallrpm = GET_RPM
    watch smallrpm
    break
    PUT line2, "RPM : "
    UPDATE_L2
    store smallrpm, 5, 2, 2
    UPDATE_L2
    wait 200
    GOTO Main

    wait:
    temp1 = __PARAM1 ' get milliseconds
    IF __PARAMCNT = 1 THEN ' if no multiplier
    temp2 = 1 ' set to 1
    ELSE ' else
    temp2 = __PARAM2 ' get multiplier
    ENDIF
    IF temp1 > 0 THEN ' no delay if either 0
    IF temp2 > 0 THEN
    PAUSE temp1 * temp2 ' do the delay
    ENDIF
    ENDIF
    RETURN

    store:
    temp1 = __PARAM1
    temp2 = __PARAM2
    temp3 = __PARAM3
    cmd = __PARAM4
    temp4 = 0
    temp5 = 0
    FOR idx = temp2 TO 15
    IF temp3 = 2 THEN
    PUT line2(idx), " "
    ELSE
    PUT line1(idx), " "
    ENDIF
    NEXT
    STR nStr, temp1
    DO WHILE temp4 < 3
    IF nStr(temp4) <> 0 THEN
    temp5 = 1
    ENDIF
    IF temp5 <> 0 THEN
    IF temp3 = 2 THEN
    PUT line2(temp2), nStr(temp4)
    ELSE
    PUT line1(temp2), nStr(temp4)
    ENDIF
    INC temp4
    INC temp2
    ENDIF
    LOOP
    IF cmd <> 0 THEN
    DEC temp2
    FOR idx = 1 to cmd
    INC temp2
    IF temp3 = 2 THEN
    PUT line2(temp2), "0"
    ELSE
    PUT line1(temp2), "0"
    ENDIF
    NEXT
    ENDIF
    RETURN

    UPDATE_L1:
    LCD_OUT LcdLine1
    wait 5
    FOR idx = 0 TO 15
    LCD_OUT line1(idx) ' transfer buffer
    NEXT
    RETURN

    UPDATE_L2:
    LCD_OUT LcdLine2
    wait 5
    FOR idx = 0 TO 15
    LCD_OUT line2(idx) ' transfer buffer
    NEXT
    RETURN

    LCD_OUT:
    temp1 = __PARAM1 ' char to send
    IF temp1 = $00 THEN
    SEROUT LcdTx, LcdBaud, " "
    ELSE
    SEROUT LcdTx, LcdBaud, temp1 ' transmit to LCD
    ENDIF
    RETURN

    FUNC PUT_DIGIT
    temp1 = __PARAM1 ' copy value
    LOOKUP temp1, $3F,$06,$5B,$4F,$66,$5E,$79, temp1
    RETURN temp1
    ENDFUNC

    FUNC GET_RPM
    PULSIN TachIn, 0, tmpW1
    PULSIN TachIn, 1, tmpW2
    watch tmpW1
    watch tmpW2
    tmpW1 = tmpW1 + tmpW2
    tmpW1 = tmpW1 * 2
    dividendMSW = $005B
    dividendLSW = $8D80
    rpm = 1
    overflow = 0
    DO
    doneBit = rpm.15
    rpm = rpm << 1
    IF overflow = 1 THEN
    rpm.0 = 1
    dividendMSW = dividendMSW - tmpW1
    ELSE
    IF dividendMSW >= tmpW1 THEN
    rpm.0 = 1
    dividendMSW = dividendMSW - tmpW1
    ENDIF
    ENDIF
    overflow = dividendMSW.15
    dividendMSW = dividendMSW << 1
    dividendMSW.0 = dividendLSW.15
    dividendLSW = dividendLSW << 1
    LOOP UNTIL doneBit = 1
    rpm = rpm << 1
    rpm.0 = overflow
    IF dividendMSW >= tmpW1 THEN
    rpm.0 = 0
    ENDIF
    rpm = rpm / 100
    RETURN rpm
    ENDFUNC
    [/PHP]
  • ZootZoot Posts: 2,227
    edited 2011-11-30 07:50
    ANALOGIN to read a POT so I can adjust RPM on the fly?

    You can still do sigma-delta ADC (like ANALOGIN) but you would need to move the ADC code to the interrupt as well. As far as accuracy, you may want to check the sxlist ISR calculator -- it lets you look up various clock frequency / ISR rate combinations to see how dead nuts on it will be.

    http://www.sxlist.com/techref/scenix/isrcalc.asp
Sign In or Register to comment.