chipKIT® Development Platform

Inspired by Arduino™

read and write digital pins through registers

Created Sun, 04 Aug 2013 21:41:42 +0000 by tsalzmann


tsalzmann

Sun, 04 Aug 2013 21:41:42 +0000

Hi, I'm trying to figure out how to read and write digital pins using registers. I already have the following:

volatile uint32_t *P;
uint16_t B;

void setup(){
  pinMode(13, OUTPUT);
  P = portOutputRegister(digitalPinToPort(13));
  B = digitalPinToBitMask(13);
}

void loop(){
}

In this case I already have the Port address and the bit but I don't know how to make a define to read or write digital pins. I would like to have something like this:

#define sbi(p, b) (........)
#define cbi(p, b) (........)
#define read(p, b) (........)

I found the following defines for sbi and cbi that actually work with the variables I created. But I don't know why and how it works:

#define cbi(reg, bitmask) (*(reg + 1)) = bitmask
#define sbi(reg, bitmask) (*(reg + 2)) = bitmask

Please help!


majenko

Sun, 04 Aug 2013 22:47:57 +0000

The PIC32 has a set of registers per IO port: TRIS, PORT and LAT. Those are further broken down into CLR, SET and INV.

For instance, TRISA is at offset BF88,6000. TRISACLR is at 6004, TRISASET at 6008 and TRISAINV is 600C.

The "Port Output Register" corresponds to the LAT entry, which is 0x20 more than TRIS. So, that is at 0xBF886020.

All values are 32-bit, so reg+1 equals an address increment of 4 bytes.

So the cbi macro for port A equates to LATA + 1, which is 0xBF886020 + 4. The LATACLR register is at LATA+0x04, so that is where that is now pointing.

So you set the LATACLR register to the bitmask.

The same goes for the sbi, where it's +2, which equates to +8 bytes. That's the LATASET register.

By the way, the SET registers set the defined bits in the main register, and the CLR registers clear the defined bits. INV toggles the defined bits.


tsalzmann

Sun, 11 Aug 2013 21:08:03 +0000

Thanks! Now I understand everything! But how could I now read from the port using registers? PORTx?


majenko

Sun, 11 Aug 2013 21:27:59 +0000

That is correct.

Pin RB3 for example is PORTB & 0b1000

The registers are all 32 bit, but only the lower 16 bits are actually used by the IO ports.

The three main port manipulation registers are:

TRISx, AKA TRIState - set the pin to be input (1) or output (0).

LATx, aka LATch - Write to this to set the output value of a pin. Reading from this port gives you the value you wrote to the latch, not the value of the actual pin itself.

PORTx - read the actual value of a pin. Also, if you write to this register it actually writes to the LATx register instead.

For a good reference: PIC32 FRM - Section 12. I/O Ports


tsalzmann

Sun, 11 Aug 2013 22:00:36 +0000

Ok... So just to make everything clear, if I use 'PortInputRegister()' instead of 'PortOutputRegister()' I will be accessing PORTx? If so, how would the code for reading a port look like?

#define readP(a, b) (???????)

volatile uint32_t *P;
uint16_t B;

P = portInputRegister(digitalPinToPort(13));
B = digitalPinToBitMask(13);

majenko

Sun, 11 Aug 2013 22:46:01 +0000

uint16_t BitValue = (*P) & B;

That will either be B or 0. If you want to reduce it to 1 or 0 you can:

unsigned char PinValue = ((*P) & B) ? 1 : 0);

HunterR

Sun, 26 Apr 2015 11:26:36 +0000

ASCII picture of the register setup for visual learners such as myself:

FOR PIC32:

[TRISA                       A       6000] <- portModeRegister(PORTA) directs here
    [  controls input/           ACLR    6004]
    [  oputput/high impedance    ASET    6008]
    [                            AINV    600C]
    -----------------------------------------
    [PORTA                       A       6010] <- portInputRegister(PORTA) directs here
    [    input state (read-only) ACLR    6014]
    [    (attmpted changes       ASET    6018]
    [    redirected to LAT)      AINV    601C]
    -----------------------------------------
    [LATA                        A       6020] <-- portOutputRegister(PORTA) directs here
    [  output state              ACLR    6024]
    [                            ASET    6028]
    [                            AINV    601C]
    
    PIC32 uses 32-bit addresses pointer (e.g. PORTA)
    (uint32_t*) myPointer+1 ===> moves over 4 bytes
    (since what it is pointing is it 32-bits wide)
    
                TRISA+1    == ACLR
                TRISA+2    == ASET
                (and so on)

FOR ARDUINO: (this is my understanding, I may be wrong --) Similar, but I think you READ and WRITE from the same register. (There is no separate PORT/LAT. They are one and the same.) There is a DDR [DataDirectionRegister] register commensurate to the TRIS register, however.

Also there is no sub-split into 3 "clear, set, and invert" registers. On PIC32, you can set one bit and not care about the rest... just write that bit to "set" and only those slots are changed. On Arduino, you have to be careful to read what's in the register first, change it, then put it back. (Can be slow.)


majenko

Sun, 26 Apr 2015 12:07:50 +0000

In the core there is a very handy struct: p32_ioport.

uint32_t portNo = digitalPinToPort(pinNo);
p32_ioport *port = (p32_ioport *)portRegisters(portNo);

You then have access to all the registers as members of the struct:

port->tris.reg = 0x3922;
port->lat.set = 0x0040;
port->ansel.clr = 0x0002;

etc.

Each register has a "val", "set", "clr" and "inv" sub-member. The registers are named "ansel", "tris", "port", "lat", "odc", "cnpu", "cnpd", "cncon", "cnen" and "cnstat". Not all chips have all those though. "ansel", "cnpu", "cnpd", "cncon", "cnen" and "cnstat" are specific to the MX1xx and MX2xx MX47x and MZ chips, so don't exist in the struct for other chips.

In DisplayCore I have this helper function:

p32_ioport *DisplayCore::getPortInformation(uint8_t pin, uint32_t *mask) {
    uint32_t portno = digitalPinToPort(pin);
    if (portno == NOT_A_PIN) {
        return NULL;
    }
    if (mask != NULL) {
        *mask = digitalPinToBitMask(pin);
    }
    return (p32_ioport *)portRegisters(portno);
}

Give it a pin number and a pointer to a uint32_t and it returns the p32_ioport struct and places the pin mask into the uint32_t parameter for you (or returns NULL if the pin you have specified is not a real pin).


george4657

Sun, 26 Apr 2015 15:22:14 +0000

Have not worked on this in a long time so correct me if I am wrong. I only worked with chipkit Max32 so this may apply to it only. I thought I checked and "digitalRead(23)" did a direct read of the register for pin 23 with no overhead.

For digital write I wrote FastPins.h file. which defines every pin for Example #define SetPin23 LATCSET = 1<< 3 #define ZeroPin23 LATCCLR = 1<< 3 #define TogglePin23 LATCINV = 1<< 3

example usage ** in setup
pinMode(33, OUTPUT); // E4

                    ** in loop
                    if( bufferVal.bitData.dirY == 1 )  SetPin33;
                    else         ZeroPin33;

HunterR

Sun, 26 Apr 2015 19:46:33 +0000

I think that is board-specific. I'm also not sure how it can be instantiated (used in a class). Most ChipSelect pins are known at compiler time... so how do you keep the class the same, but "variable-ize" 1 assembler instruction? Can you call "asm(<some variable>)"?

Here's my method based on the original approach. It's completely board- and architecture- agnostic because of the #defines (architecture-specific) which can be ported/changed easily.

(in your class's constructor somewhere)
    volatile uint32_t *P;
    uint16_t B;
    
    P = portOutputRegister(digitalPinToPort(SPICS));
    B = digitalPinToBitMask(SPICS);


(#defined in header somewhere)
//==============================================
    //Fast writes using PIC32 LAT register
    // http://chipkit.net/forum/viewtopic.php?t=2494
    
    #define cbi(reg,bitmask) (*(reg+1)) = bitmask
    #define sbi(reg,bitmask) (*(reg+2)) = bitmask
    #define ibi(reg,bitmask) (*(reg+3)) = bitmask
    
    #define CHIP_SELECT()   cbi(P, B)
    #define CHIP_UNSELECT() sbi(P, B)
//==============================================

(Or, if you are using Arduino)
    #define CHIP_SELECT()   *P &amp;= ~B
    #define CHIP_UNSELECT() *P |= B       
    
(when you need to actually use the code)
    CHIP_SELECT();
    CHIP_UNSELECT();

It can be dynamic instantiated and really speeds things up, especially on Arduino.