Reputation: 507
Using the Arduino IDE for an Arduino Uno...
I can successfully send to a port using macros such as PORTB
but I cannot figure out how to send data to a port that is defined in a variable like so:
uint8_t pin = 0; // any value...
uint8_t port = digitalPinToPort(pin);
uint8_t *portreg = portModeRegister(port);
uint8_t portsfr = _SFR_IO_ADDR(port);
asm volatile
(
// other asm instructions...
"out %[port], %[masklo] \n\t"
::
[port] "I" (_SFR_IO_ADDR(PORTB)) // works
// [port] "I" (_SFR_IO_ADDR(port)) // doesn't compile
// [port] "I" (portreg) // doesn't compile
// [port] "I" (portsfr) // doesn't compile
);
I found the following article which seams related but doesn't show an example how to: avr gcc inline asm variable input operand
Upvotes: 1
Views: 1722
Reputation: 4654
Note, that ANY I/O register can be addressed thru it's memory location. So, all you need is to utilize the pointer, that points to that memory location.
portModeRegister(port)
returns a such pointer to a DDRx register or the specified port. There also portOutputRegister(port)
witch returns pointer for a PORTx register and portInputRegister()
for a PINx.
volatile uint8_t * p_port = portOutputRegister(port);
*p_port = xxx; // writing to the PORTx by a pointer
uint8_t yyy = *p_port; // reading the PORTx value
also, you can assign the pointer to the register directly, using PORTx macros:
volatile uint8_t * p_port = &PORTB;
Lifehack
Since in ATmega328P (which is in Arduino), and also in a lot of other AVRs, port registers all are placed in consequent locations in the same order, usually in this: PIXx, DDRx, PORTx. So, instead of storing three pointers, you can utilize only the lower one (i.e. PINx), and then access the pointer as array:
volatile uint8_t * p_base = portInputRegister(port); // obtaining pointer to PINx
p_base[1] = ddr_val; // writing the DDRx value: offset + 1
p_base[2] = port_val; // writing the PORTx value: offset + 2
uint8_t pin_val pin_val = p_base[0]; // reading the PINx value: offset 0
Upvotes: 1
Reputation: 87416
The AVR's in
, out
, sbi
, and cbi
instructions only take literal I/O addresses, so the register they are accessing must be known at compile time. The pololu-led-strip-arduino library is an example of a library that uses templates to get around that: a template argument specifies an Arduino pin number, and the compiler looks up the proper I/O addresses at compile time and bakes it into the assembly code of the function.
If that approach does not work for you, a simpler approach is to use a switch
statement to convert the pin number (or some other specification of what pin to use) from something unknown at compile time to something that is:
switch (pin_number)
{
case 1:
// some assembly using pin "1"
break;
case 2
// some assembly using pin "2"
break;
// ...
}
A third approach: The AVR instruction set does have support for pointers, and I believe you can set up a pointer to point at an I/O register and then read and write from the register through the pointer. So you should try compiling some code like this and look at the disassembly listing provided by your toolchain to see how it is done in assembly:
void write_to_reg_through_pointer(uint16_t value) {
volatile unsigned char * volatile ptr = &PORTB;
*ptr = value;
}
Upvotes: 1
Reputation: 6063
The AVR instruction set doesn't support non-immediate arguments to the IN and OUT instructions. So, there's simply no machine instruction to do exactly what you want.
So, without referring to self-modifying code which naturally forbids itself on a system running from flash, you cannot achieve what you want.
The number of possible ports on an AVR is, however limited, typically 3 - So it would be easy and relatively cheap to implement using a switch-case and some enums:
typedef enum {PortA, PortB, PortC} portNo;
void out (portNo, value) {
switch (portNo) {
case PortA:
out (PORTA, value);
break;
...
Upvotes: 0