PDA

View Full Version : Custom array of ports using array?



blainecf
- 13th June 2006, 17:35
Is it possible to make an array, where each element equates to a non-consecutive port.

Example:

RELAY VAR BIT[10]

' (obviously, this doesn't work, but it does crash the compiler)
SYMBOL RELAY[1] = PORTB.6
SYMBOL RELAY[2] = PORTC.4
SYMBOL RELAY[3] = PORTD.2
...

' cycle through relays
for i = 1 to 3
RELAY[i] = %1
PAUSE 1000
RELAY[i] = %0
PAUSE 300
next i

schu4647
- 13th June 2006, 17:50
I don't see why that wouldn't work. Try removing the symbols. I don't see the need for them. I declared relay at the top.

schu4647
- 13th June 2006, 17:50
I should say the Symbol command.

schu4647
- 13th June 2006, 17:53
Plus I think an array should start with variable 0 not 1. So it would be 0 through 9.

SteveB
- 13th June 2006, 18:58
blainecf,
I am not sure this will work like you expect. When you declare an array variable, it is acually pointing to a series of consecutive RAM addresses. So later, when you use the variable, it goes to the address and uses the value currently at that address. What you are looking to do is create an array that is actually pointing to the addresses represented by the individual pins (which are not even consecutive).

After playing with it a little and looking at the .lst file, I don't know if this can be done in PBP. Once you have created the array, any further references to the array are modifying/reading the contents, not the address. The only info in the manual I see is assigning a label to a specifc pin. Which is handled using different ASM commands.

Maybe one of the real gurus could chime in here and set me straight if I'm wrong (Melanie? Darrel?). Or provide a workaround to do the job.

Hope to get a better answer,
Steve

paul borgmeier
- 13th June 2006, 19:25
See these

http://www.picbasic.co.uk/forum/showthread.php?t=3753


http://www.picbasic.co.uk/forum/showthread.php?t=544

and then ....

Paul Borgmeier
Salt Lake City, Utah
USA

paul borgmeier
- 13th June 2006, 20:02
Although slightly contrived and uses more RAM - this does it.



Relay var Byte[4]
x var byte
i var byte

Relay[1] = 14 'RB6
Relay[2] = 20 'RC4
Relay[3] = 26 'RD2
main:
' cycle through relays
for i = 1 to 3
x = relay[i]
porta.0[x]=1
PAUSE 1000
porta.0[x]=0
PAUSE 300
next i

Paul Borgmeier
Salt Lake City, Utah
USA

blainecf
- 13th June 2006, 22:37
Bummer,

It's these little things that make programming clean and respectable.

Still, it seems a shame that I can create an aliassed variable that is equivilent to a pin, but not an array element. Seems like the same type of technology.

So close, yet so far.

Thanks so much for your input, everyone.

SteveB
- 14th June 2006, 03:04
Paul:
Thanks for "chiming in" with a solution. I just want to make sure I understand what your code is trying to do so I can learn from it. I suspected there was a way to shoehorn this idea into PBP.


Relay[1] = 14 'RB6 is 14 bits offset from RA0
Relay[2] = 20 'RC4 is 20 bits offset from RA0
Relay[3] = 26 'RD2 is 26 bits offset from RA0

This array is establishing an offset from bit.0 on PORTA. I assume that most pics have PORTA-PORTX at consecutive regiester addresses (I only have a couple of datasheets to reference, but that's what I see)



for i = 1 to 3
x = relay[i]
porta.0[x]=1
PAUSE 1000
porta.0[x]=0
PAUSE 300
next i

With the Relay[] array containing the correct offets, one can now use that with porta.0[x] to select the desire bit. Since porta.0 points to a bit, the offset [x] is in bits as well.
Did miss this "feature" in the PBP manual? Can I extend this idea further in that the inclusion of [X] to any reference that points to a register will actually cause an offset reference in accordance to the base reference. ( ie, if it was a word variable that [x] was added to, [x] would actually reference a register x*2 bytes offset?).


Steve

paul borgmeier
- 14th June 2006, 05:37
Steve,

Yes, you are 100% correct in the logic - that is exactly how it works. This “trick” is not in the manual but first appeared on the PicBasic List in 2002 (http://list.picbasic.com/forum/messages/4011/4739.html? ).

I remember being excited when this particular email came because it solved a problem I was having at the time. The links posted in my reply #6 above by Bruce and Melanie also clearly demonstrate this trick's use.

As I understand it, it works for all SF/GP Register locations, not just PORT bits, hence the indexed locations must be sequential like you mentioned. I always check the datasheet if I am going to use it on sequential Ports to make sure they are indeed sequential (and they always have been for the PICs I have used).


Paul

Ingvar
- 14th June 2006, 09:16
Since portpins rarely move(constants) you could use the LOOKUP statement to get the offset values. Should free up some RAM. The LOOKUP could ofcourse be placed in a subroutine if you need to access it from many different locations in your program. That way saving ROM.


x var byte
i var byte

main:
for i = 0 to 3
' a b c d
' 0 6 4 2
LOOKUP i, [0,14,20,26], x
porta.0[x]=1
PAUSE 1000
porta.0[x]=0
PAUSE 300
next i

/Ingvar

paul borgmeier
- 14th June 2006, 15:19
Nice improvement! - The code for what blaine originally wanted to do is getting pretty clean considering it is an undocumented feature.

Best,

Paul

Bruce
- 15th June 2006, 03:58
Nice job Paul & Ingvar..;o}

Yet another version.


DEFINE OSC 4

' BASIC/ASM version = 172 words

x var word
i var byte
RelayNum var PORTA

' High byte = FSR / Low byte = port pin (or multiple pins)
RLY1 CON %0000010100000001 ' PORTA|Bit0
RLY2 CON %0000011001000000 ' PORTB|Bit6
RLY3 CON %0000011100010000 ' PORTC|Bit4
RLY4 CON %0000100000000100 ' PORTD|Bit2

ADCON1 = 7
PORTA = 0 : PORTB = 0 : PORTC = 0 : PORTD = 0
TRISA = 0 : TRISB = 0 : TRISC = 0 : TRISD = 0

ASM
RelayOn macro
movf _x+1,w ; high byte = pointer to port file reg
movwf FSR ; load FSR
movf _x,w ; low byte = port bit to change
xorwf INDF,f ; toggle port pin
endm
ENDASM

ASM
RelayOff macro
movf _x,w ; reload port pin to toggle
xorwf INDF,f ; do it
endm
ENDASM

main:
for i = 0 to 3
LOOKUP2 i, [RLY1,RLY2,RLY3,RLY4], x ' lookup file reg & XOR pattern
@ RelayOn
PAUSE 500
@ RelayOff
PAUSE 500
next i

END

Darrel Taylor
- 15th June 2006, 05:47
I agree, good stuff!

Here's another one.
INCLUDE "VirtualPort.bas"

Relays VAR BYTE : Relays = 0

ASM
RelayPort macro
Vpin 0, PORTB, 6 ; RELAY1
Vpin 1, PORTC, 4 ; RELAY2
Vpin 2, PORTD, 2 ; RELAY3
endm

OutputPort RelayPort ; Set Port to OUTPUT
ENDASMThen, here's a test loop to cycle thru the relays...
i VAR BYTE

Main:
for i = 0 to 2
Relays = DCD i
@ WritePort _Relays, RelayPort
PAUSE 500
next i
Goto MainCode size = 104 words, 65 without the PAUSE.

or you can just...
Relays = %010
@ WritePort _Relays, RelayPortto turn on RELAY2 by itself.
Code size = 21 words.

http://www.pbpgroup.com/Vport/VirtualPort.bas.txt

HTH,
    DT

paul borgmeier
- 15th June 2006, 07:44
Bruce, Darrel,

You guys are amazing yet again! I continue to learn and what good fun.

Paul

Edit - I am an ASL student (Assembly Second Language) student and removed some code from this post that was not correct.-pb

Bruce
- 15th June 2006, 17:28
Just keeps getting better & better ehh...;o}

Nice work Darrel.

SteveB
- 15th June 2006, 18:47
Bruce and Darrel,
You guys cheated(lol). Paul and Ingvar got the job done tidely with PBP. You had to resort to Assembly;).

This is what I love about this forum. Bright, capable folks working together to make a good product (PBP) work even better! I knew in my initial post that there just had to be a way to do this, it just was not documented and was beyond my basic skills. Great learning oppurtunity. I think this is worth putting in the FAQ (in some form) for others to reference later.

Even though it wasn't my post originally, thanks guys for the great lessons.

Cheers,
Steve

Darrel Taylor
- 16th June 2006, 01:39
Thanks Bruce, Paul and Steve,

And yes I did cheat, by using a module that I had already written.

I started writing VirtualPort in response to this post from Osiris
http://www.picbasic.co.uk/forum/showthread.php?t=3554

He was looking for a way to have a 16-bit port that was scattered across random pins. By the time I got it working, Melanie had already given him what he needed, so I didn't bother posting it.

But, I soon realized that it was going to be more usefull than originally intended so continued to add more macro's and features, and have managed to do some pretty cool stuff with it.

Here's how the 16-bit port for Osiris looked...
MyPort VAR Word SYSTEM
ASM
MyPortPins macro ; these define which bits go with which pins
Vpin 0, PORTB,0
Vpin 1, PORTC,4
Vpin 2, PORTA,0
Vpin 3, PORTC,1
Vpin 4, PORTC,3
Vpin 5, PORTC,0
Vpin 6, PORTA,1
Vpin 7, PORTC,5
Vpin 8, PORTB,2
Vpin 9, PORTB,3
Vpin 10, PORTC,2
Vpin 11, PORTC,0
Vpin 12, PORTB,4
Vpin 13, PORTB,6
Vpin 14, PORTB,5
Vpin 15, PORTC,7
endm
ENDASM

@ OutputPort MyPortPins
@ WritePort MyPort, MyPortPins

@ InputPort MyPortPins
@ ReadPort MyPort, MyPortPins


This creates a full 16-bit bi-directional port that can be placed on ANY pins in ANY order.<HR>
Additional macros can also allow control of Direction, Enable, Latch pulses etc. in the same structure. Here's an 8-bit port with flow control
Direction VAR PORTB.5
Enable VAR PORTB.1

ASM
Port8_Read macro
PinLow _Direction ; Set direction pin for Read
Vpin 0, PORTB,0
Vpin 1, PORTC,4
Vpin 2, PORTA,0
Vpin 3, PORTC,1
Vpin 4, PORTC,3
Vpin 5, PORTC,0
Vpin 6, PORTA,1
Vpin 7, PORTC,5
PulseLow Enable, 2 ; Low pulse for 2uS
endm

Port8_Write macro
PinHigh _Direction ; Set direction pin for Write
Vpin 0, PORTB,0
Vpin 1, PORTC,4
Vpin 2, PORTA,0
Vpin 3, PORTC,1
Vpin 4, PORTC,3
Vpin 5, PORTC,0
Vpin 6, PORTA,1
Vpin 7, PORTC,5
PulseLow Enable, 2 ; Low pulse for 2uS
endm
ENDASM

@ OutputPort Port8_Write
@ WritePort MyPort, Port8_Write

@ InputPort Port8_Read
@ ReadPort MyPort, Port8_ReadOr that can be simplified like this...
ASM
Port8_Pins macro
Vpin 0, PORTB,0
Vpin 1, PORTC,4
Vpin 2, PORTA,0
Vpin 3, PORTC,1
Vpin 4, PORTC,3
Vpin 5, PORTC,0
Vpin 6, PORTA,1
Vpin 7, PORTC,5
PulseLow Enable, 2 ; Low pulse for 2uS
endm

Port8_Read macro
PinLow _Direction ; Set direction pin for Read
Port8_Pins
endm

Port8_Write macro
PinHigh _Direction ; Set direction pin for Write
Port8_Pins
endm
ENDASM<hr>

Another similar question that keeps popping up every now and then, is how to use an LCD that's not connected to a contiguous nibble of a port.

This program allows you to do that, and also ties it in with LCDOUT for a "transparent" interface.
INCLUDE "HighJack.bas"
@ HighJack LCDOUT, _LCDsend

Clear
INCLUDE "VirtualPort.bas"

;----[ Change these to match your LCD ]---------------------------------------
LCD_DB4 VAR PORTB.0
LCD_DB5 VAR PORTB.4
LCD_DB6 VAR PORTC.6
LCD_DB7 VAR PORTC.3
LCD_RS VAR PORTD.1
LCD_E VAR PORTD.0
LCD_Lines CON 2 ' # of Lines on LCD, 1 or 2+
LCD_DATAUS CON 50 ' Data delay time in us
LCD_COMMANDUS CON 2000 ' Command delay time in us


;----[Virtual LCD Port --- 4bit mode]----------------(Don't Change)-----------
ASM
LCD_Port_HNIB macro ; Port definition for LCD High Nibble
Vbit LCDCDFLAG, _LCD_RS ; Select Command/Data register
Vpin 4, _LCD_DB4 ; Put the High Nibble on the bus
Vpin 5, _LCD_DB5
Vpin 6, _LCD_DB6
Vpin 7, _LCD_DB7
PulseLow _LCD_E, 2 ; pulse the Enable Pin for 2 us
endm
;-----------------------
LCD_Port_LNIB macro ; Port definition for LCD Low Nibble
Vpin 0, _LCD_DB4 ; Put the Low Nibble on the bus
Vpin 1, _LCD_DB5
Vpin 2, _LCD_DB6
Vpin 3, _LCD_DB7
PulseLow _LCD_E, 2 ; pulse the Enable Pin for 2 us
endm

OutputPort LCD_Port_HNIB ; Set LCD bus to OUTPUT
ENDASM


;----[Main program starts here]-----------------------------------------------
LoopCount VAR WORD


Main:
lcdout $FE,$80, dec LoopCount
LoopCount = LoopCount + 1
goto Main

;----[Send a byte to the Virtual LCD PORT]--------called by LCDOUT------------
TempChar var byte
Char VAR Byte
LCD_Initialized VAR FLAGS.1
RSS VAR FLAGS.0 ; LCD RS State

;-------
LCDsend:
@ MOVE?AB _TempChar ; Get char sent by LCDOUT, in WREG
if !LCD_Initialized then LCD_Init ; Make sure LCD was Initialized
LCDAfterInit:
if TempChar = $FE then RSS=0 : goto LCDsendDone ; next char is Command
Char = TempChar
LCDsendCOM:
@ WritePort _Char, LCD_Port_HNIB ; send the High Nibble
LCDsendShortCOM:
@ WritePort _Char, LCD_Port_LNIB ; send the Low Nibble

if LCD_RS OR (Char > 2) then
PAUSEUS LCD_DATAUS ; Data delay time in us
else
PAUSEUS LCD_COMMANDUS ; Command delay
endif
if LCD_Initialized then RSS = 1 ; Set RS to Data next time
LCDsendDone:
return

;----[Initialize the LCD]-----------------------------------------------------
LCD_Init:
LOW LCD_RS : HIGH LCD_E ; Start with RS LOW and Enable High
Char = 3 : gosub LCDsendShortCOM : pause 6
gosub LCDsendShortCOM : PAUSE 1
gosub LCDsendShortCOM : PAUSE 1
Char = 2 : gosub LCDsendShortCOM : PAUSE 1 ; Start 4-bit mode
Char = $28 : gosub LCDsendCOM ; Function Set, 4-bit, 2-line, 5x7
Char = $0C : gosub LCDsendCOM ; Display ON
Char = $01 : gosub LCDsendCOM ; Clear Screen
Char = $06 : gosub LCDsendCOM ; Entry Mode
LCD_Initialized = 1 ; LCD has been Initialized
goto LCDAfterInit


Of course this relies on another program I wrote, "HighJack", to actually link in to LCDOUT. But nobody bothered to respond to this thread
http://www.picbasic.co.uk/forum/showthread.php?t=3290

so I never bothered posting it.
<br>

SteveB
- 16th June 2006, 04:18
Darrel,
I am slowly worming my way through your code and I noticed in VituralPort the local variable "Vaddr" in the the Vpin macro. What's it for? I did a search for it and I only see it being assigned a value (in two places), but otherwise never used.

Also, you mentioned "HighJack", but did not post it.

BTW, I have been following your "Wish List" (http://www.picbasic.co.uk/forum/showthread.php?t=3564) thread. Congrats. Get a good chair!:D And did you set a new avatar?

Wish you all success,
Steve

Darrel Taylor
- 16th June 2006, 04:54
Excellent!

<table><tr><td valign=top>http://www.darreltaylor.com/files/Bug_report.gif</td><td>Thanks Steve. That's a bug. Thanks for finding it. The Vaddr allows it to work with WORD variables for the 16-bit ports. Then, these lines...
if (PortAction == 0) ; read
MOVE?TT P, Pbit, PortVar, VarBit
else
if (PortAction == 1) ; write
MOVE?TT PortVar, VarBit, P, Pbit
Should have used Vaddr instead of PortVar. I got the VarBit, but missed the Vaddr.

I will make the changes. &nbsp;http://www.pbpgroup.com/Vport/VirtualPort.bas.txt

Yeah, that new chair idea is sounding pretty good :)
and, yup spruced up the avatar a bit. Hopefully it's not too annoying.

Read the part about HighJack again.</td></tr></table>

Thanks again,
&nbsp; &nbsp; DT

SteveB
- 17th June 2006, 21:27
Alright. I've gotten though the code but still have one question. Why use seperate delay code? It's commented as "Similar to PAUSEUS, but ASM interrupt compatible".
Yet I'm missing something here. Why is this more compatible with ASM interrupts then the PBP PAUSEUS routine? I've been using the PAUSEUS in my current project, with both high and low priority ASM interrupts (18F4620), without any problems. The delays are slightly longer than requested, due to the interrupt occuring in the delay loop (trival for most apps), but no other ill effects I've noticed. This 'error' in delay time would occur with any delay based on code exectuing timing and a loop, unless interrupts were disabled prior to entering the loop. So this can't be the reason.

Also, I think the PBP routine could be easily called by inserting the following where the delay is required ("_us" is a predefined word variable/literal containing the delay in us):



movlw (_us) >> 8
movwf R0 + 1
movlw low (_us)
call PAUSEUSL ; Delay for us
PAUSEUS_USED = 1


One thought that just occured to me. Becuase you use a macro, your not pushing anything onto the stack. So you are avoiding any possible overflow due to interrupts occuring during the delay. Is this it?

Steve

Darrel Taylor
- 17th June 2006, 23:02
It's not that it's OK to "Be interrupted", but it's OK to be in the routine that does the interrupting.

PAUSEUS has a couple shortcomings. One, is that it uses PBP's system variables (R0-R8). So it corrupts PBP's flow if it's used in an ASM interrupt without "Instant Interrupts".

The second is that PAUSEUS has a minimum delay of around 24us @ 4mhz since it figures out the delay Count at run time.

The DelayUS macro can pause for a little as 1us by calculating the delay at Compile Time, and adding a single NOP if that's all that's needed. Or a 3cycle loop and 2 NOP's if running at 20mhz. And it doesn't use any PBP system vars.

It can be useful in other programs as well, so I'll show it here again for those that aren't as diligent as you Steve.
VP_DelayCount VAR WORD BANK0 ; Used for DelayUS only

ASM
;----[Similar to PAUSEUS, but ASM interrupt compatible]---(accurate to 1uS)--
DelayUS macro T
local InstCount, LoopCount, DelayLoop, LoopsDone, LeftOver, Offset
; -- Calculate number of 3 cycle loops plus leftover nop's to execute --
InstCount = ((OSC*10/4)*T+5)/10 ; Inst cycles required for delay (Rounded UP)
LoopCount = InstCount / 3
if (LoopCount > 255)
Offset = (LoopCount >> 8) * 7 + 4
else
Offset = 0
endif
; -- Adjust for HighByte --
InstCount = InstCount - Offset
if (Offset > (LoopCount & 0FFh))
InstCount = InstCount - 4
endif
LoopCount = InstCount / 3
if (LoopCount > 255)
Offset = (LoopCount >> 8) * 7
else
Offset = 0
endif
LeftOver = InstCount % 3
;-------------------------------------------
if (LoopCount > 0)
MOVE?CW LoopCount, _VP_DelayCount
DelayLoop
decfsz _VP_DelayCount, F ; 1
goto DelayLoop ; 2 3 per loop, under 256

if (LoopCount > 255)
movf _VP_DelayCount + 1, W ; 1
btfsc STATUS, Z ; 1
goto LoopsDone ; 2 4 on last loop

decf _VP_DelayCount + 1, F ; 1
goto DelayLoop ; 2 7 per highbyte count
LoopsDone
endif
endif
if (LeftOver > 0)
nop
endif
if (LeftOver > 1)
nop
endif
endm
ENDASM

SteveB
- 18th June 2006, 01:43
Ok,
It's not that it's OK to "Be interrupted", but it's OK to be in the routine that does the interrupting.
This makes much more sense than what I was thinking.

On thing that should be mentioned. It is generally best to keep interrupt service routines (ISR) as short as possible, so that additional interrupts are not "missed" while handling the current interrupt. So putting a pause into an ISR must be used with caution, and with a good understanding of how it will effect other timing related items. One exception to this would be the 16-bit PICs with High and Low priority interrupts (which are supported by PBP), where a pause in a low prority interrupt would not prevent a high priority interrupt from executing. So all the time critical inturrupts could be High Priority.

Steve