When accessing (reading, setting, clearing, or toggling) a single pin, byte access using Cortex-M0 instruction set should often yield more compact code.
For example, when you set pin 23 of port A on SAMC21, you do not actually need to do
*(volatile uint32_t *)0x41000018 = 1 << 23; . It'd compile to
0: 2280 movs r2, #128
2: 4b02 ldr r3, [pc, #8]
4: 0412 lsls r2, r2, #16
6: 601a str r2, [r3]
:
c: 41000018 .word 0x41000018
It is better done using
*(volatile uint8_t *)0x4100001a = 1 << 7; as it has the exact same effect, but should compile to
0: 2280 movs r2, #128
2: 4b02 ldr r3, [pc, #8]
4: 701a strb r2, [r3]
:
c: 41000018 .word 0x4100001a
i.e. the shift left by 16 bits (
lsls r2, r2, #16) is not needed.
I don't have a SAMC21 at hand, but I do have a Teensy LC with a Freescale MKL26Z64VFT4 MCU, also Cortex-M0+. For this one, I do know the actual 32-bit registers:
- GPIOx_PDOR: Port x data output register.
When written to, sets the output pins to states matching the value. Bits corresponding to input/unbounded pins are ignored.
When read from, input and tristate bits yield an undefined value. - GPIOx_PDIR: Port x data input register.
When read from, each bit in the output corresponds to an input pin state. Output and unbounded pins read as zeroes. Read-only. - GPIOx_PSOR: Port x data set register.
When written to, sets each output pin corresponding to a set bit high. Pins corresponding to zero bits are left unchanged. Reads zero. - GPIOx_PCOR: Port x data clear register.
When written to, clears each output pin corresponding to a set bit. Pins corresponding to zero bits are left unchanged. Reads zero. - GPIOx_PTOR: Port x data toggle register.
When written to, toggles the state of each output pin corresponding to a set bit. Pins corresponding to zero bits are left unchanged. Reads zero. - GPIOx_PDDR: Port x data direction register.
A set bit corresponds to an output pin, and a clear bit to an input pin. - PORTx_PCRn: Pin control register n for port x.
These control the pin multiplexing, interrupts, filters, slew rate, and pullup/down.
There are also
FGPIOx_PyzR fast GPIO register aliases, that have a different address, but complete in a single cycle. For this particular chip, there are five ports (
x):
A,
B,
C,
D, and
E, although there are only 36 GPIO pins or so. All above registers are 32-bit, but support 8- and 16-bit accesses too.
There are also two write-only registers,
PORTx_GPCLR and
PORTx_GPCHR, that only support 32-bit accesses. The high 16 bits specify the pins (set bit corresponding to output pins whose state is affected), and the low 16 bits specify their corresponding states.
PORTx_GPCLR affects pins 0-15, and
PORTx_GPCHR pins 16-31. These are especially powerful, because a single 32-bit write can set and clear either the low or high 16 pins in a single port.
It is obvious that if we group related pins in the same port, we can modify/read their states extremely efficiently.
8-bit and 16-bit accesses are useful, because they let us divide the port into four or two groups of pins, that can be manipulated completely independently.
In my case, the board exposes pins 1 and 2 of port A, pins 0, 1, 2, 3, 16, and 17 of port B, pins 0 to 7 of port C, pins 0 to 7 of port D, and pins 20, 21, 24, 25, 29, and 30 of port E. Byte access to least significant byte of port A allows me to control PA0..PA3 independently of PA16,PA17 (byte access to second-most significant byte of the same register). Similarly, byte access to port E separates the pins into two groups, pins 20 and 21, and pins 24, 25, 29, and 30.
So, essentially, I have three independent groups of two consecutive pins (PA1,PA2; PA16,PA17; and PE20,PE21), two independent groups of four pins (PB0-PB3, and PE24,25,29,30), and two independent groups of eight consecutive pins (PC0-PC7, PD0-PD7).
It makes design (choosing which pins to use for what, especially since there are alternate uses for almost every pin) harder, in the sense that to really squeeze everything out from such a chip, you need to think about the use cases before writing the firmware. I consider that a fun part, myself.
Looking at the
datasheet for the SAM C20/C21 family in detail, it does say that PORT aperture starts at 0x41000000 on page 43, and 28.7 (page 491) lists the offsets. The base address for port A is 0x41000000, 0x41000080 for port B, 0x41000100 for port C, 0x41000180 for port D, 0x41000200 for port E, and so on.
The registers are very much similar, just named a bit differently: Microchip uses
OUT for
GPIOx_PDOR,
IN for
GPIOx_PDIR,
DIR for
GPIOx_PDDR,
OUTCLR for
GPIOx_PCOR,
OUTSET for
GPIOx_PSOR,
OUTTGL for
GPIO_PTOR, and so on. It has additional registers to set specific pins in a port as outputs (
DIRSET), inputs (
DIRCLR), or even toggle the direction (
DIRTGL), but I don't see ones matching
PORTx_GPCHR or
PORTx_GPCLR .
This means that
GPIOA_PDOR on SAMC20 and SAMC21 is at address 0x41000010,
GPIOB_PDOR at 0x41000090, and so on.
While there are differences, the logical idea/pattern behind GPIO pin access is the same as for the one I described for another Cortex M0+ MCU (Teensy LC), above in this post.
Of course, if you use Microchip's ASF in Atmel Studio, you won't have direct access to those.