简体   繁体   中英

LUT in a macro C

I am currently working on setting up a framework in C for usage between several microcontrollers. The framework has to carry all device specific code, so the application only contains the abstract usage of peripherals (like SerialPort_Read, write, SetBaudRate, etc etc.)

One of the things I am struggling with to find a solution for in C are the I/O pin map. I've seen projects (like the very very popular Arduino) where the pin map is putten in a LUT (look up table) which is used during runtime. However, this LUT will never be modified during runtime, so there is no use to have this in the memory. For example, this function resolves some bit indexes and registers from some 'const uint' tables, and either sets or clears a bit:

void pinMode(uint8_t pin, uint8_t mode)
{
        uint8_t bit = digitalPinToBitMask(pin);
        uint8_t port = digitalPinToPort(pin);
        volatile uint8_t *reg;

        if (port == NOT_A_PIN) return;

        // JWS: can I let the optimizer do this?
        reg = portModeRegister(port);

        if (mode == INPUT) { 
                uint8_t oldSREG = SREG;
                cli();
                *reg &= ~bit;
                SREG = oldSREG;
        } else {
                uint8_t oldSREG = SREG;
                cli();
                *reg |= bit;
                SREG = oldSREG;
        }
}

Because this is actual C code running on the controller it's draining effiency and speed. I'd rather define some sort of macro that does the same thing, but is already resolved during compilation to a 'one-liner' that can be compiled much more efficiently:

GPIO_Write(PORTA, 5, 1); // Write '1' to pin 5 on PORTA
> LATA |= 1<<5; // Sets bit 5 high
GPIO_Tris(PORTA, 4, OUTPUT); // Set pin 4 on PORTA to output
> PORTA &= ~(1<<4); // sets pin 4 as output I/O type

Does anyone know if it's possible (and how) to define and use a look-up table with a macro in C?

At this moment I am using the MicroChip C30 compiler, which I believe is based in GCC. It's supposed to be portable between different compilers, including MicroChip C18, C32 and in further also ARM and AVR.

For your specific example, something along these lines will work:

#define WRITE_PORTA LATA
#define GPIO_Write(port, pin, value)         \
    (value ? WRITE_##port |=  (1U << (pin))  \        
           : WRITE_##port &= ~(1U << (pin)))

#define INPUT  0
#define OUTPUT 1
#define GPIO_Tris(port, pin, direction)                     \
     ((direction) == INPUT ? port |=  (1U << (pin))  \
                           : port &= ~(1U << (pin)))

You'll have to make sure to define LATA and PORTA in a way the system will understand - in particular trying to overload its meaning the way it seems to be in your example might be hard to resolve.

Which processor or microcontroller are you targeting? You might be underestimating the usefulness of the LUT.

For many processors, the LUT does more than map a 'logical' pin number to a single value, the 'physical' pin number. The LUT maps the 'logical' pin number to several pieces of information.

In general, the 'logical' pin is mapped to the port address of the appropriate read/input or write/output register, and the bit offset within the read or write register. So the pin value, on many MCU's, is really mapped to a struct. It might also include a mapping to the data direction register and fields within it, as well as registers which set the state of pull-up or pull-down resistors.

For example, I have code to multiplex a 8x8 display. At run-time, I need to use pinMode to turn a pin from an output to a high impedance input, and so that information needs to be encoded somehow.

It is possible to do this sort of thing, with some ingenuity, on some MCU's. ARM MCU's (and I believe 8051, though I've never used one) using 'bit band addressing' http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dai0179b/CHDJHIDF.html

This assigns a unique memory address for each port pin, and fixed offsets can derive the address of the pin for the other data register, and other functions. This is not magic, the code encodes the information that is often stored in the LUT.

For other MCU's, they really do need both port and bit position, so it is two values for each pin number.

If you are willing to ditch the idea of using integers for pins, and instead use names, like P0, P1, then you could initialise a lot of const struct 's, one per pin name, and your functions would take the const struct values. The struct would contain the initialised port and bit offset or bit mask values. The compiler may be able to optimise for speed. This would avoid having a LUT, but would still use similar amounts of space for pins that are used. You might be able to arrange it so that unused pins would not need to be included in the code, and hence saving space.

Edit: If you are willing to use C++, I'd suggest C++ templates which can give a much better solution than macros. They can be type safe, and are often easier to debug (if you have hardware debugging, eg JTAG and gdb)

Consider the following macro:

#define write(port, pin, value) do { \
  if (value) \
    LAT##port |= 1 << pin; \
  else \
    LAT##port &= ~(1 << pin); \
} while (0)

Usage:

write(A, 3, 1);   // compiles to LATA |= 1 << 3;
write(B, 2, 0);   // compiles to LATB &= ~(1 << 2);

Is that the kind of thing you were after?

I've seen it done ( https://github.com/triffid/Teacup_Firmware/blob/Gen7/arduino.h ) with a couple macros:

/// Read a pin
#define     _READ(IO)                   (IO ## _RPORT & MASK(IO ## _PIN))
/// write to a pin
#define     _WRITE(IO, v)           do { if (v) { IO ## _WPORT |= MASK(IO ## _PIN); } else { IO ## _WPORT &= ~MASK(IO ## _PIN); }; } while (0)

/// set pin as input
#define     _SET_INPUT(IO)      do { IO ## _DDR &= ~MASK(IO ## _PIN); } while (0)
/// set pin as output
#define     _SET_OUTPUT(IO)     do { IO ## _DDR |=  MASK(IO ## _PIN); } while (0)



//  why double up on these macros? see http://gcc.gnu.org/onlinedocs/cpp/Stringification.html

/// Read a pin wrapper
#define     READ(IO)                    _READ(IO)
/// Write to a pin wrapper
#define     WRITE(IO, v)            _WRITE(IO, v)
/// set pin as input wrapper
#define     SET_INPUT(IO)           _SET_INPUT(IO)
/// set pin as output wrapper
#define     SET_OUTPUT(IO)      _SET_OUTPUT(IO)

with:

#define DIO0_PIN       PIND0
#define DIO0_RPORT     PIND
#define DIO0_WPORT     PORTD
#define DIO0_PWM       &OCR0B
#define DIO0_DDR       DDRD

#define DIO1_PIN       PIND1
#define DIO1_RPORT     PIND
#define DIO1_WPORT     PORTD
#define DIO1_PWM       &OCR2B
#define DIO1_DDR       DDRD
...

You could modify the macros to take straight integers rather than DIOn

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM