News:

Let's find out together what makes a PIC Tick!

Main Menu

Procedure using PORT

Started by joesaliba, Jan 23, 2022, 01:37 PM

Previous topic - Next topic

joesaliba

Hi,

I have a routine to measure a pulse: -

Dim Rx_Sig      As TMR1L.Word       ' Variable used to hold Rx signal timing
Dim Pulse_Value as Word

Rx_Sig = 0
Pulse_Value = 0

while portc.0 = 1 : wend             ' Wait for low servo pulse
while portc.0 = 0 : wend             ' Wait for high servo pulse

TMR1ON = 1                          ' Turn ON timer 1 counter

while portc.0 = 1 : wend             ' Wait for low servo pulse

TMR1ON = 0                          ' Turn OFF timer 1 counter

Pulse_Value = rx_sig

As I now need to measure about 4 pulses from different PIC pin, I decide to have a go with a procedure, however, as PinGet needs a variable to work, and also sets the pin to input, there is a slight delay which is causing flactuations in my readings: -

proc Read_Rx(In_Pin as byte), rx_val

Dim Rx_Sig      As TMR1L.Word       ' Variable used to hold Rx signal timing
dim bPin as bit

Rx_Sig = 0                          ' Clear Rx_Sig variable

bPin = PinGet In_Pin                ' Synchronize pulse for reading

while bPin = 1
    bPin = PinGet In_Pin   
wend             

bPin = PinGet In_Pin

while bPin = 0
    bPin = PinGet In_Pin   
wend           

TMR1ON = 1                          ' Turn ON timer 1 counter

bPin = PinGet In_Pin                ' Get pulse duration

while bPin = 1
    bPin = PinGet In_Pin   
wend   

TMR1ON = 0                          ' Turn OFF timer 1 counter

result = rx_sig

endproc

Then to read from PORTC.0 and PORTC.1 for now I am using the following: -

Main:
read_rx(rxin)                   ' Select which pin to read
select rxin
    case 16
        rx_val1 = rx_val
        rxin = 17
    case 17
        rx_val2 = rx_val
        rxin = 16
endselect
print at 2,1, "C0 : - " , dec4 rx_val1
print at 3,1, "C1 : - " , dec4 rx_val2
print at 4,1, dec rxin
delayms 200
goto main

Is there a way for this purpose to pass the PORTC.x instead of a variable so I can get rid of the port pin setup to input and loading a variable, thus loosing time?

Thank you

Joe

top204

#1
When a Port.Pin is passed as a parameter, or loaded into a variable for access to it, it is created into an address for the Port and bit mask for the Pin, so it does not operate as fast as a direct Port.Pin access, but there is not other way of making a Port.Bit sit in a variable, and is what a lot of C and C++ compilers use to access pins in a microcontroller, because the languages were originally designed for microprocessors that did not have individual bit accessing mnemonics, and used Ands and Ors instead. You will see this in C and C++ as "1 shifted left by the bit number".

For direct access to the Port.Pin, you can use a preprocessor meta-macro to create inline code that uses the parameter as a piece of text and passes it to the code within it:

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Demonstrate a preprocessor meta-macro to access a Port.Pin directly and read a Timer1 value
' Written for the Positron8 compiler by Les Johnson
'
    Device = 18F25K20
    Declare Xtal = 16 
'
' Create variables

    Dim MyWord As Word
    Dim wTimer1 As TMR1L.Word
   
'----------------------------------------------------------------------
' Load a variable with the contents of Timer1
' Input     : pInPin holds the Port.Pin to sample
' Output    : pResult holds the value read from Timer1
' Notes     : None

$define Read_Rx(pInPin, pResult) '
    wTimer1 = 0                  '
    Repeat: Until pInPin = 0     '
    Repeat: Until pInPin = 1     '
    T1CONbits_TMR1ON = 1         '
    Repeat: Until pInPin = 0     ' 
    T1CONbits_TMR1ON = 0         '
    pResult = wTimer1
 
'----------------------------------------------------------------------
' The main program starts here
'
Main:  
    Read_Rx(PORTC.0, MyWord)    ' Read Timer1 into MyWord

The assembler code uses the Port.Pin (PORTC.0) directly, so it does not get any faster, but still remains more friendly to use in a program:

    clrf TMR1LH,0
    clrf TMR1L,0
_lbl__2
    btfsc PORTC,0,0
    bra _lbl__2
_lbl__3
_lbl__5
    btfss PORTC,0,0
    bra _lbl__5
_lbl__6
    bsf T1CON,0,0
_lbl__8
    btfsc PORTC,0,0
    bra _lbl__8
_lbl__9
    bcf T1CON,0,0
    movff TMR1L,MyWord
    movff TMR1LH,MyWordH

joesaliba

Thanks for your input Les.

Regards

Joe

joesaliba

Les,

I copied the above and first it gave me error `Item `Read_Rx' not found!

Then I moved the meta macro to the beginning of code and now is saying `Item `pInPin' not found.

What am I doing wrong please?


top204

A meta-macro, or any preprocessor $define, must be placed before the code that is using it in a program.

I copied the above code from the ProtonIDE and placed it onto the forum, and I have just copied the above code listing and pasted it into the ProtonIDE and it compiles fine.

Make sure you have not removed any of the comment characters from the line ends of the $define Read_Rx, because these are line continuations for the preprocessor. Without the line continuations, the code will not recognise the texts in the parameter positions, because it will destroy the code block created by them.

See the section on the preprocessor within the compiler's manual.


joesaliba

Ok, it looks to be my mistake.

For clarity I added a line between the meta macro and made these errors. Adding a ' to the blank line solved the problem.

Wow. That is super stable Les.

Thank you so much

Joe

joesaliba

Was doing my reply while you was posting. Yes it was the comment.

Joe

m.kaviani

I think the best way to measure the pulse in different pins is that a pulse has a rising and falling edge. so we consider a high priority interrupt on pin
by edge detection option. at the rising edge first interrupt will happen. now start a timer with a specific time. now change the interrupt on change by toggling the
to falling edge.
in the next interrupt, for example, Int0 stop the timer and measure the value of the timer to calculate the pulse period.
if you want to measure the whole pulse duration from beginning a pulse to the beginning of the next pulse don't toggle the edge.     

m.kaviani

read this post which I was used to detecting zero crossing. I hope it can help.
https://protoncompiler.com/index.php/topic,816.0.html

joesaliba

Thank you Kaviani for your input.

I used that method in past, but I have a board and the pins I need to measure do not have interrupt on change.

Regards

Joe

m.kaviani

hi Joe,
did you test the "pulsein" command in the proton?

I had a project to read Samsung remote control IR signal to control room lighting.
as you know those pulses are around 400 or 200 microseconds. But it works fine.
no need to interrupt and be available on any input pin.
there is a disadvantage in this method which you must measure pulses in different pins
one by one.
like :

dim var1 as word
dim var2 as word

var1 = pulsein portc.1,low
var2 = pulsein portc.2,low


joesaliba

Yes, but it always return a byte value.
Example, if I am measuring 1924uS it returns 192. Although the manual says: -

QuoteThe variable can be either a Word or a Byte . If the variable is a word, the value returned by
PulseIn can range from 1 to 65535 units.

it still return 192 and not 1924. Maybe I am misunderstanding the manual.

Having said that, I usually use the PulseIn command but not in this case.

PulseIn makes my life much easier specially when used with: -

Declare Pulsin_Maximum = 2300
without additional code to test if pulse timing is greater than 2300us in this case.

Regards

Joe



Dompie

But the next sentence in the manual:
QuoteThe units are dependant on the frequency of the crystal used. If a 4MHz crystal is used, then
each unit is 10us, while a 20MHz crystal produces a unit length of 2us.

And that could explain 192 insteat of 1924?

Johan

John Drew

Joe,
Might be a coincidence or a silly
comment but are you sure there's not a problem with your print routine.
192 is first three digits of 1924.
John

david

Hi,
I've used PulseIn on 4MHz and it did give 10uS resolution so I'd expect a maximum value of around 200 for a full pulse width.
Much better to use Tmr1 on 4MHz and get direct 1uS intervals using your initial code which is very similar to mine-

 Clear timer1       'counter reset
 T1CON=0       'measure servo pulse
 While Rxin =0:Wend      'wait while low
 T1CON.0=1       'counter on
 While Rxin=1 :Wend      'wait while high
 T1CON.0=0       'counter off
 Return    

I haven't attempted measuring more than one pin so I'll watch with interest.  Some receivers send the channel data sequentially but others send all channels at once which is really the only way to provide say 14 channels and yet still preserve the 20mS frame rate.

Cheers,
David

joesaliba

Quote from: david on Jan 25, 2022, 12:39 PMHi,
I've used PulseIn on 4MHz and it did give 10uS resolution so I'd expect a maximum value of around 200 for a full pulse width.
Much better to use Tmr1 on 4MHz and get direct 1uS intervals using your initial code which is very similar to mine-

 Clear timer1       'counter reset
 T1CON=0       'measure servo pulse
 While Rxin =0:Wend      'wait while low
 T1CON.0=1       'counter on
 While Rxin=1 :Wend      'wait while high
 T1CON.0=0       'counter off
 Return    

I haven't attempted measuring more than one pin so I'll watch with interest.  Some receivers send the channel data sequentially but others send all channels at once which is really the only way to provide say 14 channels and yet still preserve the 20mS frame rate.

Cheers,
David

That is what I use David, and I go a further step to ensure that I got the high pulse correctly: -

Clear timer1    'counter reset
 T1CON=0 'measure servo pulse
while Rxin = 1 : wend    ' Wait for low servo pulse      <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
While Rxin =0:Wend      'wait while low
 T1CON.0=1 'counter on
 While Rxin=1 :Wend      'wait while high
 T1CON.0=0 'counter off
 Return  

I wait for the high pulse because if I enter this routine when the pulse is already high I can take a false reading, i.e., only measure half pulse.

Therefore, if pulse was high when I enter the measure routine I wait for it to finish, then I wait for the low pulse to finish, and then I take a full high pulse width.

This has it's drawback, as if you disconnect an input it will stay in that routine forever.
What I am working can have as much as four channel inputs, therefore, at startup I check how many channels I have connected and the software will suite as necessary.

Otherwise I have something else in my mind that even if you disconnect a channel during operation it still keeps going, but it will involve some small math which can loose a bit of time.

Regards

Joe

joesaliba

Quote from: John Drew on Jan 25, 2022, 12:31 PMJoe,
Might be a coincidence or a silly
comment but are you sure there's not a problem with your print routine.
192 is first three digits of 1924.
John

No, it is because using PulsIn @ 4MHz will take measurements in 10uS steps, therefore I can get the result x10 and get a nearly correct answer.

For this particular code I am doing that is ok, but I have other application which will need the full measured uS rather than result / 10.

Regards

Joe

joesaliba

Quote from: Dompie on Jan 25, 2022, 09:20 AMBut the next sentence in the manual:
And that could explain 192 insteat of 1924?

Johan

I know, but even running @ 20MHz will give me more weird result.

Running 4MHz with a 10us resolution is straight forward to get an idea of the pulse length: - 192 x 10 = 1920.

Running at 20MHz if I am not mistaken, as the resolution is 2us reading for 1924uS pulse will be 1924 / 2 = 962. Therefore much worse to convert this number to what I need it.

Regards

Joe

joesaliba

@top204

Les,

How can I exit early from a meta macro if a condition is met please?

I tried using a label within the macro but it does not compile.

Joe

top204

#19
That's a good question Joe, and not something I have thought about before, so I had a bit of a head scratch and came up with a way of doing it that is not convoluted. A label cannot be used within the meta-macro because it will be created everytime the inline code is created and cause duplicate label errors, but the code below solves this problem.

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Demonstrate exiting early from a preprocessor meta-macro with inline code within it
' Written for the Positron8 compiler by Les Johnson
'
    Device = 18F25K20
    Declare Xtal = 16
'
' Setup USART1
'
    Declare Hserial_Baud = 9600
    Declare HRSOut_Pin = PORTC.6
'
' Create a variable for the demo
'
    Dim MyByte As Byte = 0

'----------------------------------------------------------------------
' Demonstrate exiting early for an inline meta-macro
' Creates a loop with only one iteration, so the Break command still works within it
' And jumps over the Until directive
'
$define TestMacro()          '
    Repeat                   '
        MyByte = MyByte + 10 '
        If MyByte = 10 Then  '
            Break            '
        EndIf                '
        MyByte = 100         '
    Until 1 = 1

'----------------------------------------------------------------------
' The main program starts here
'
Main:
    TestMacro()                 ' Run the inline meta-macro and add 10 to MyByte
    HRSOutLn Dec MyByte         ' Transmit the value of MyByte to a serial terminal. It will be "10", and not 100
    TestMacro()                 ' Run the inline meta-macro again, to make sure it is working
    HRSOutLn Dec MyByte         ' Transmit the value of MyByte to a serial terminal. It will be "100", because the inline code will only exit early if MyByte = 10


As can be seen in the code listing above, a Repeat-Until loop is created, but it only has one iteration, so it will never jump back to itself because 1 is always equal to 1, but the code within it will still be operated upon because it is a Repeat directive and not a While directive. However, because it is, technically, still a loop to the parser, the Break command works within it and will jump over the Until directive, thus jumping over any code in front of it, and as many Break commands as required can be placed within the "loop" with conditions to jump to the end of the code, and any loops within the main loop do not use any stack SFRs, so you can also have multiple comparisons and loops within the main single iteration Repeat-Until loop. TaDa!!!! Exiting early from an inline meta-macro. :-)

The underlying assembler code, simply, produces internal labels for the exits, and standard Bra or Goto mnemonics for the Breaks, so does not produce any unrequired code:

_lbl__2
    movlw 10
    addwf MyByte,F,0
    movlw 10
    cpfseq MyByte,0
    bra _lbl__6
    bra _lbl__3
_lbl__6
    movlw 100
    movwf MyByte,0
_lbl__4
_lbl__3

The "bra _lbl__3" mnemonic is the Break command, and jumps over the "_lbl__4" label, which is the Repeat-Until's internal Continue label.