Helpful ReplyHot!"Best" way to handle port I/O??

Author
Pete F
New Member
  • Total Posts : 26
  • Reward points : 0
  • Joined: 2018/07/18 07:49:10
  • Location: 0
  • Status: offline
2018/08/10 08:02:30 (permalink)
3 (1)

"Best" way to handle port I/O??

Tools: MPLAB X IDE v4.15, Compiler 2.10, Harmony 2.06
Boards: PIC32MZ (DM320010), 168 to 132 pin starter kit adaptor (AC320006), PIC32 I/O expansion Board (DM320002)
 
If I go to Help, XC32 Toolchain, XC32 Peripheral Libraries, IO Port, it shows this form:
mbH14_value = PORTReadBits(IOPORT_H, BIT_14);
Which if I try an use it will not compile and searching harmony for c and h files with "PORTReadBits" in it does not show up.
 
Other places in generated code, I see this form:
mbH14_value = PLIB_PORTS_PinGet(PORTS_ID_0, PORT_CHANNEL_H, PORTS_BIT_POS_14);
 
I'm not exactly sure why help is leading me to forms that don't work.
Also can someone explain what PORTS_ID_x is?
 
Thanks,
-Pete
#1
DarioG
Allmächtig.
  • Total Posts : 54081
  • Reward points : 0
  • Joined: 2006/02/25 08:58:22
  • Location: Oesterreich
  • Status: offline
Re: "Best" way to handle port I/O?? 2018/08/10 08:16:24 (permalink)
0
I think that "ID" could identify one "set" of Ports, such as when in case of a dual core.
 
Of course, all of that "abstraction" is rather useless... !

GENOVA :D :D ! GODO
#2
simong123
Lab Member No. 003
  • Total Posts : 1269
  • Reward points : 0
  • Joined: 2012/02/07 18:21:03
  • Location: Future Gadget Lab (UK Branch)
  • Status: offline
Re: "Best" way to handle port I/O?? 2018/08/10 09:18:59 (permalink)
+1 (1)
Don't confuse the help in XC32, which I believe is for the old stanalone PLIB, with the PLIB which is part of Harmony.
Use the help which comes with Harmony (loacated in \microchip\harmony\v2_xx_xx\doc).
#3
malaugh
Super Member
  • Total Posts : 338
  • Reward points : 0
  • Joined: 2011/03/31 14:04:42
  • Location: San Diego
  • Status: offline
Re: "Best" way to handle port I/O?? 2018/08/10 09:19:37 (permalink) ☄ Helpfulby stephaneC 2018/09/17 01:11:31
+3 (3)
Personally I would just read and write to the registers directly rather than calling a function.
 
To read a bit from a port just use
 
MyBit  = PORTH & (1 << 14);
 
Or more likely, if the code needs to branch depending on a bit
 
if(PORTH & (1 << 14))
....
 
For Writing use the LATxCLR and LATxSET functions, for examp,le
 
LATHSET = (1 << 14) 
 
will set bit 14 of register H
 
The alternative is to work out how the Microchip functions work by digging through the mammoth and poorly written documentation, hoping that Microchip do not change their mind about the function calls, and end up with a slower and less flexible solution.
 
The PORTReadBits function you mention is referencing a function in Microchip's old and obsolete "PLIB" library, where PLIB_PORTS_PinGet references their new Harmony library. 
 
I will leave it up to you to decide whether using a functions that go obsolete at a whim , are documented by referencing older library, and have obscure arguments that are unnecessary is a better solution than rolling your own.
 
 
#4
simong123
Lab Member No. 003
  • Total Posts : 1269
  • Reward points : 0
  • Joined: 2012/02/07 18:21:03
  • Location: Future Gadget Lab (UK Branch)
  • Status: offline
Re: "Best" way to handle port I/O?? 2018/08/10 09:30:39 (permalink)
+1 (1)
I agree with malaugh.
 
I only use Harmony for the large, complex, peripherals on the 'MZ (e.g. USB, Ethernet) for which some abstracton is actually usefull.
 
For simple peripherals (Ports, SPI, I2C, timers etc.) I have my own library.
#5
moser
Super Member
  • Total Posts : 388
  • Reward points : 0
  • Joined: 2015/06/16 02:53:47
  • Location: Germany
  • Status: online
Re: "Best" way to handle port I/O?? 2018/08/13 02:03:48 (permalink)
+1 (1)
malaugh[...] and end up with a slower [...] solution.

 
While this point of malaughs argument might hold for most of the high level functions and drivers, it is not true for the PLIB. The Microchip Harmony Ports library is not slower. There is not a single real function call in the Ports Library. Everything is inline. Check for yourself: There is not a single .c file in \microchip\harmony\v<your_Harmony_version>\framework\peripheral\ports and its subdirectories. And this is also true for the complete peripheral library. Furthermore, the compiler will evaluate all constant expressions and replace them. Check the assembly. The compiler will reduce all of PLIB_PORTS_PinGet(), PLIB_PORTS_PinSet(), PLIB_PORTS_PinClear() to the same machine instructions as when writing down the registers directly. Well, ... unless you turn off optimization, but then you should not be worried about speed at all ...
 
Also for those three functions you won't see much difference in readability between PLIB and writing down registers names. However, for example for PLIB_PORTS_RemapOutput() or PLIB_PORTS_PinDirectionOutputSet(), I think the PLIB version is more speaking.
 
#6
malaugh
Super Member
  • Total Posts : 338
  • Reward points : 0
  • Joined: 2011/03/31 14:04:42
  • Location: San Diego
  • Status: offline
Re: "Best" way to handle port I/O?? 2018/08/13 08:22:22 (permalink)
0
Not sure about Moser's comment on the speed.   
 
I do not know about Harmony, and I do not have it on my computer, but I did a quick experiment on PLIB.   Below is the assembly listing for the direct and PLIB methods of checking a bit in a register.
 
The direct method has 3 instructions.
The PLIB method has 5 instructions, and calls a function (JAL instruction) of unknown length.
 
So the direct method is faster.
 

! BitValue = PORTReadBits( IOPORT_G, BIT_9 );
0x9D063DF8: ADDIU A0, ZERO, 6
0x9D063DFC: JAL PORTReadBits
0x9D063E00: ADDIU A1, ZERO, 512
0x9D063E04: ANDI V0, V0, 255
0x9D063E08: SB V0, 16(SP)
 
! BitValue = PORTG & (1 << 9);
0x9D063E0C: LUI V0, -16504
0x9D063E10: LW V0, 24976(V0)
0x9D063E14: SB ZERO, 16(SP)

 
 
#7
simong123
Lab Member No. 003
  • Total Posts : 1269
  • Reward points : 0
  • Joined: 2012/02/07 18:21:03
  • Location: Future Gadget Lab (UK Branch)
  • Status: offline
Re: "Best" way to handle port I/O?? 2018/08/13 09:22:48 (permalink)
+1 (1)
malaugh
Not sure about Moser's comment on the speed.   
 
I do not know about Harmony, and I do not have it on my computer, but I did a quick experiment on PLIB.   Below is the assembly listing for the direct and PLIB methods of checking a bit in a register.
 
The direct method has 3 instructions.
The PLIB method has 5 instructions, and calls a function (JAL instruction) of unknown length.
 
So the direct method is faster.
 

 
! BitValue = PORTReadBits( IOPORT_G, BIT_9 );
0x9D063DF8: ADDIU A0, ZERO, 6
0x9D063DFC: JAL PORTReadBits
0x9D063E00: ADDIU A1, ZERO, 512
0x9D063E04: ANDI V0, V0, 255
0x9D063E08: SB V0, 16(SP)
 
! BitValue = PORTG & (1 << 9);
0x9D063E0C: LUI V0, -16504
0x9D063E10: LW V0, 24976(V0)
0x9D063E14: SB ZERO, 16(SP)


That is the old PLIB. Harmony V2 PLIB (PLIB_PORTS_PinGet) uses macros and should compile to the three instruction as per the direct method.
#8
malaugh
Super Member
  • Total Posts : 338
  • Reward points : 0
  • Joined: 2011/03/31 14:04:42
  • Location: San Diego
  • Status: offline
Re: "Best" way to handle port I/O?? 2018/08/13 10:42:57 (permalink)
+1 (1)
simong123
That is the old PLIB. Harmony V2 PLIB (PLIB_PORTS_PinGet) uses macros and should compile to the three instruction as per the direct method.



Can you do me a favor. Run a simple test like mine on the Harmony function call, and post the disassembly.   As I said, I do not use Harmony so I do not have it installed on my machine.  
 
You may be correct, but logically it seem unlikely. the function call is 
 
PLIB_PORTS_PinGet(PORTS_ID_0, PORT_CHANNEL_H, PORTS_BIT_POS_14);
 
I may be wrong, but I believe that the PORTS_ID_0 and ports PORT_CHANNEL_H are aliases, so the code must have some kind of test for PORTS_ID_0 ( or may have in the future) and some kind of lookup to get the port address from PORT_CHANNEL_H.
 
#9
simong123
Lab Member No. 003
  • Total Posts : 1269
  • Reward points : 0
  • Joined: 2012/02/07 18:21:03
  • Location: Future Gadget Lab (UK Branch)
  • Status: offline
Re: "Best" way to handle port I/O?? 2018/08/13 15:44:17 (permalink)
0
It actually eventually uses an inlined function:-
PLIB_TEMPLATE bool PORTS_PinGet_MCU32_PPS( PORTS_MODULE_ID index , PORTS_CHANNEL channel , PORTS_BIT_POS bitPos )
{
    return (bool)((*(&PORTB + ((channel - 1) * 0x40)) >> bitPos) & 1);
}

The source:-
volatile uint32_t test;
void APP_Initialize ( void )
{
    /* Place the App state machine in its initial state. */
    appData.state = APP_STATE_INIT;

    test=PLIB_PORTS_PinGet(PORTS_ID_0, PORT_CHANNEL_H, PORTS_BIT_POS_14);
    /* TODO: Initialize your application's state machine and other
     * parameters.
     */
}

Generates:-
    .section .mdebug.abi32
    .previous
    .gnu_attribute 4, 4
    .section    .text,code
.Ltext0:
    .section    .text.APP_Initialize,code
    .align    2
    .globl    APP_Initialize
.LFB586 = .
    .file 1 "c:/microchip/harmony/v2_05_01/apps/nmitest/firmware/src/app.c"
    .loc 1 116 0
    .set    nomips16
    .set    nomicromips
    .ent    APP_Initialize
    .type    APP_Initialize, @function
APP_Initialize:
    .frame    $sp,0,$31        # vars= 0, regs= 0/0, args= 0, gp= 0
    .mask    0x00000000,0
    .fmask    0x00000000,0
    .set    noreorder
    .set    nomacro
# End mchp_output_function_prologue
    .loc 1 118 0
    sw    $0,%gp_rel(appData)($28)
.LVL0 = .
.LBB8 = .
.LBB9 = .
.LBB10 = .
    .file 2 "c:/microchip/harmony/v2_05_01/framework/peripheral/ports/templates/ports_portsread_mcu32_pps.h"
    .loc 2 63 0
    lui    $2,%hi(PORTB)
    addiu    $2,$2,%lo(PORTB)
    lw    $2,1536($2)
    ext    $2,$2,14,1
.LBE10 = .
.LBE9 = .
.LBE8 = .
    .loc 1 120 0
    sw    $2,%gp_rel(test)($28)
    j    $31
    nop


The relavent assembly is highlighted. So 3 lines for each.
 
Note that in both your case and the Harmony case the shift and mask are is omitted by the compiler as the result is discarded, only the read from volatile is performed.
 
Edit:- Oops, I wasn't storing the result.
Actually in your direct port read example the disassembly is incorrect. It does not perform the shift and mask and store the result. Perhaps you need to make BitValue volatile.
 
Where the two examples are comparable (the actual load from the port) Harmony takes 3 ins, the direct read 2.
In the harmony disassy the addiu could be replaced by just fixing up the 1536 in the lw
post edited by simong123 - 2018/08/13 16:02:23
#10
moser
Super Member
  • Total Posts : 388
  • Reward points : 0
  • Joined: 2015/06/16 02:53:47
  • Location: Germany
  • Status: online
Re: "Best" way to handle port I/O?? 2018/08/14 02:03:18 (permalink)
+1 (1)
Test function:
 
#include <xc.h>
#include "peripheral/peripheral.h"

bool t1;
bool t2;
bool t3;
bool t4;

void test_pin_set_and_clear_and_get()
{
    _nop();
    PLIB_PORTS_PinSet(PORTS_ID_0, PORT_CHANNEL_A, PORTS_BIT_POS_15);
    PLIB_PORTS_PinSet(PORTS_ID_0, PORT_CHANNEL_B, PORTS_BIT_POS_14);
    PLIB_PORTS_PinSet(PORTS_ID_0, PORT_CHANNEL_C, PORTS_BIT_POS_13);
    PLIB_PORTS_PinSet(PORTS_ID_0, PORT_CHANNEL_D, PORTS_BIT_POS_12);
    PLIB_PORTS_PinClear(PORTS_ID_0, PORT_CHANNEL_E, PORTS_BIT_POS_11);
    PLIB_PORTS_PinClear(PORTS_ID_0, PORT_CHANNEL_F, PORTS_BIT_POS_10);
    PLIB_PORTS_PinClear(PORTS_ID_0, PORT_CHANNEL_G, PORTS_BIT_POS_9);
    PLIB_PORTS_PinClear(PORTS_ID_0, PORT_CHANNEL_H, PORTS_BIT_POS_8);
    t1 = PLIB_PORTS_PinGet(PORTS_ID_0, PORT_CHANNEL_A, PORTS_BIT_POS_10);
    t2 = PLIB_PORTS_PinGet(PORTS_ID_0, PORT_CHANNEL_B, PORTS_BIT_POS_9);
    t3 = PLIB_PORTS_PinGet(PORTS_ID_0, PORT_CHANNEL_C, PORTS_BIT_POS_8);
    t4 = PLIB_PORTS_PinGet(PORTS_ID_0, PORT_CHANNEL_D, PORTS_BIT_POS_7);
    _nop();
}

 
For the assembly (PIC32MZ EFM, XC32 v1.42) see attached image.
post edited by moser - 2018/08/14 02:09:25

Attachment(s)

Attachments are not available: Download requirements not met
#11
moser
Super Member
  • Total Posts : 388
  • Reward points : 0
  • Joined: 2015/06/16 02:53:47
  • Location: Germany
  • Status: online
Re: "Best" way to handle port I/O?? 2018/08/15 04:28:21 (permalink)
+1 (1)
malaughI may be wrong, but I believe that the PORTS_ID_0 and ports PORT_CHANNEL_H are aliases, so the code must have some kind of test for PORTS_ID_0 ( or may have in the future) and some kind of lookup to get the port address from PORT_CHANNEL_H.

 
Btw, they are not aliases, they are enums, which means they are just numeric constants. Lets have a look at:
PLIB_PORTS_PinGet(PORTS_ID_0, PORT_CHANNEL_H, PORTS_BIT_POS_14);

 
The ID is used in all drivers, to name the modules, if there is more than one. However, there is of course one single set of ports. Therefore, as Dario has already written the PORTS_ID_0 is just useless. It is defined like this:
typedef enum {
 
    PORTS_ID_0 = 0,
    PORTS_NUMBER_OF_MODULES = 1

} PORTS_MODULE_ID;

But it is just not used, as you can see below. PORT_CHANNEL_H and PORTS_BIT_POS_14 are also enums, defined in the simplest way: 
typedef enum {
 
    PORT_CHANNEL_A = 0x00,
    PORT_CHANNEL_B = 0x01,
    PORT_CHANNEL_C = 0x02,
    PORT_CHANNEL_D = 0x03,
    PORT_CHANNEL_E = 0x04,
    PORT_CHANNEL_F = 0x05,
    PORT_CHANNEL_G = 0x06,
    PORT_CHANNEL_H = 0x07,
    PORT_CHANNEL_J = 0x08,
    PORT_CHANNEL_K = 0x09
 
} PORTS_CHANNEL;
typedef enum {
 
     PORTS_BIT_POS_0 = 0,
     PORTS_BIT_POS_1 = 1,
     PORTS_BIT_POS_2 = 2,
     PORTS_BIT_POS_3 = 3,
     PORTS_BIT_POS_4 = 4,
     PORTS_BIT_POS_5 = 5,
     PORTS_BIT_POS_6 = 6,
     PORTS_BIT_POS_7 = 7,
     PORTS_BIT_POS_8 = 8,
     PORTS_BIT_POS_9 = 9,
     PORTS_BIT_POS_10 = 10,
     PORTS_BIT_POS_11 = 11,
     PORTS_BIT_POS_12 = 12,
     PORTS_BIT_POS_13 = 13,
     PORTS_BIT_POS_14 = 14,
     PORTS_BIT_POS_15 = 15
 
} PORTS_BIT_POS;

 
Then note, that the definition of PLIB_PORTS_PinGet() just translates to PORTS_PinGet_MCU32_PPS(). 
PLIB_INLINE_API bool PLIB_PORTS_PinGet(PORTS_MODULE_ID index, PORTS_CHANNEL channel, PORTS_BIT_POS bitPos)
{
    return PORTS_PinGet_MCU32_PPS(index, channel, bitPos);
}

Note, that it is inline, because of
#define PLIB_INLINE_API extern inline

So we have:
PLIB_TEMPLATE bool PORTS_PinGet_MCU32_PPS( PORTS_MODULE_ID index , PORTS_CHANNEL channel , PORTS_BIT_POS bitPos )
{
    return (bool)((*(&PORTB + ((channel - 1) * 0x40)) >> bitPos) & 1);
}

This one is inline, too, because of
#define PLIB_TEMPLATE PLIB_INLINE
and
#define PLIB_INLINE extern inline

 
And the rest is just some math, to calculate the address of the register. It takes the position of the PORTB register, and calculates the correct register by just multiplying the channel with 0x40,  because they are aligned in 0x40 addresses. And then reads from the calculated address and shifts by the bit position, then masks the one bit, and converts it to a boolean.
 
So lets inline, and insert the constants, and we have for our call from above just:
{
    return (bool)((*(&PORTB + ((0x07 - 1) * 0x40)) >> 14) & 1);
}

((0x07 - 1) * 0x40) is constant and the compiler can evaluate it at compile time. The address of PORTB is also constant. That's all what is needed for implementing the read instruction quite simple. Then the bit positions and the masking is fixed, and therefore all of this is implemented with a EXT instruction. And then typically you store it somewhere, and use it later, so you have a store instruction. Otherwise, the compiler will find out, that you don't use the result. Doing a shift and a store for something which is not used, is quite pointless. This save him a bit extraction instruction and a store instruction. As simong123 pointed out, that happend in your assembly, malaugh, so you end up with just two instruction (the third one is just the stack pointer), but your bit extract and store is both missing.
post edited by moser - 2018/08/15 04:30:59
#12
Jump to:
© 2018 APG vNext Commercial Version 4.5