Help with Macros

Hello, everyone! I'm working with the following code to communicate with an N64 controller:

#define N64_PIN 2
#define N64_PIN_DIR DDRD

// these two macros set arduino pin 2 to input or output, which with an
// external 1K pull-up resistor to the 3.3V rail, is like pulling it high or
// low. 
#define N64_HIGH    N64_PIN_DIR &= ~0x04
#define N64_LOW     N64_PIN_DIR |= 0x04
#define N64_QUERY  (PIND & 0x04)

I would like to modify the code so I can use these macros with configurable pins. Timing is critical -- I need to know exactly how many cycles each operation takes, because the N64 controller protocol is based around pulses that are 4 microseconds wide.

If you're interested, the rest of the code is on my Github page

Macros take zero cycles - they are evaluated at compile time, actually BEFORE the code is compiled, so you cannot use them for making run-time pin assignments.

Regards,
Ray L.

Hmm, it sounds like I should find a way of achieving this without using macros. Could someone help me understand what's really going on here, and write a function that would achieve the same thing?

I'm aware that I need to be careful, because the N64 controller is a 3.3V device, and the Arduino is 5V, so there's a possibility of frying it.

You could use addresses stored in pointers instead of constants. There are functions in the Arduino core to translate from Arduino Pin to port and bit.

uint8_t *N64_DDR = DDRD;  // Port D
uint8_t *N64_PIN = PIND;   // Port D
uint8_t N64_MASK = 0x04;  // Pin 2

#define N64_HIGH   (N64_DDR &= ~N64_MASK)
#define N64_LOW    (N64_DDR |= N64_MASK)
#define N64_QUERY  (*N64_PIN & N64_MASK)

Since the values are no longer compile-time constants the operations will take longer. They have to fetch pointers and data from SRAM and store temporary values in registers. You will have to look at the disassembly to determine the cycle count. Four microseconds is 64 instruction cycles so it might be OK.

There are a lot of posts on Google about the N64 controller and the Arduino. Try looking at those.

You can get quite a bit done in 4 µS - after all that is 64 clock cycles.

Ah yes, I thought that sounded familiar. I had done some work on the blasted thing myself. See below:

/*
  Nintendo N64 game controller interface.
  Author: Nick Gammon
  Date: 14 October 2014
  
  Helpful information:
    http://afermiano.com/index.php/n64-controller-protocol
    https://code.google.com/p/micro-64-controller/wiki/Protocol
  
  Note: Connect the controller data line to D2 via a pullup resistor 
        (I used 4.7 k) to the 3.3 V pin.
        
  Released for public use.
*/  

#include <avr/wdt.h>
#include <avr/builtins.h>

#define CONTROLLER false   // true to emulator controller, false to emulate console

#define N64_PIN 2
#define N64_PIN_DIR DDRD
// these two macros set arduino pin 2 to input or output, which with an
// external 1K pull-up resistor to the 3.3V rail, is like pulling it high or
// low.  These operations translate to 1 op code, which takes 2 cycles
#define N64_high()    N64_PIN_DIR &= ~0x04
#define N64_low()     N64_PIN_DIR |= 0x04
#define N64_query()  (PIND & 0x04)

#define nop asm volatile ("nop\n\t")

#define WANT_8MHZ false

// status data1:
const byte BUTTON_D_RIGHT = 0x01;
const byte BUTTON_D_LEFT = 0x02;
const byte BUTTON_D_DOWN = 0x04;
const byte BUTTON_D_UP = 0x08;
const byte BUTTON_START = 0x10;
const byte BUTTON_Z = 0x20;
const byte BUTTON_B = 0x40;
const byte BUTTON_A = 0x80;

// status data2:
const byte BUTTON_C_RIGHT = 0x01;
const byte BUTTON_C_LEFT = 0x02;
const byte BUTTON_C_DOWN = 0x04;
const byte BUTTON_C_UP = 0x08;
const byte BUTTON_R = 0x10;
const byte BUTTON_L = 0x20;

// 8 bytes of data that we get from the controller
typedef struct {
    byte data1;
    byte data2;
    char stick_x;
    char stick_y;
} N64_status;

void N64_send (byte * output, byte length)
{
  byte bitOn;
  byte bits;
 
   while (length--)
    {
    PINB = bit (5); // toggle D13 
    byte currentByte = *output++;
    
    for (bits = 0; bits < 8; bits++)  // 5 cycles if staying in loop
      {
      bitOn = currentByte & 0x80;  // 2 cycles
      currentByte <<= 1;   // 1 cycle
      
      // ------- low for 1 uS
      PINB = bit (3); // toggle D11
      N64_low(); // start bit
      
      // we want 14 but subtract 2 for toggling the LED
      // and another 2 for the decision below
  #if WANT_8MHZ
      nop; nop; nop; // 3
  #else
      nop; nop; nop; nop; nop; nop; nop; nop; nop; nop; nop; // 11
  #endif
      // -------------------
      
      // -------low or high for 2 uS
      if (bitOn)  // test bit, 1 if high, 2 if low
        {
        N64_high(); // go high for one  - 2 cycles
        }
      else          
        {
        N64_low(); //  stay low for zero - 2 cycles
        nop;  // compensate for branch  - 1 cycle
        }
  #if WANT_8MHZ
      nop; nop; nop; nop; nop; nop; nop; nop;   // 8
      nop; nop; // 10
  #else
      nop; nop; nop; nop; nop; nop; nop; nop;   // 8
      nop; nop; nop; nop; nop; nop; nop; nop;   // 16
      nop; nop; nop; nop; nop; nop; nop; nop;   // 24
      nop; nop; // 26
  #endif
  
      // -------------------
  
      // ------- high for 1 uS
      N64_high(); //  stop bit must be high
      
      // we want 14 but subtract 7 because of loop overhead
  #if WANT_8MHZ
  #else    
      nop; nop; nop; nop; nop; nop; nop; nop;   // 7
  #endif
  
      // -------------------
      }  // end of each bit

    }
    
  // console stop bit - low for 1 uS then high
  N64_low(); // start bit (2 cycles)

#if WANT_8MHZ
  nop; nop; nop; nop; nop; nop;   // 6
#else    
  nop; nop; nop; nop; nop; nop; nop; nop;   // 8
  nop; nop; nop; nop; nop; nop;   // 14 
#endif

  N64_high(); 

}  // end of N64_send

void N64_get(byte * output, byte length)
{
  byte bits;

// controller will time out more slowly
  wdt_reset();  // pat the dog
#if CONTROLLER
  wdt_enable(WDTO_4S);
#else
 // wdt_enable(WDTO_30MS);
  wdt_enable(WDTO_8S);  // bootloader problems, arrgh!
#endif

  while (length--)
    {
    PINB = bit (5); // toggle D13 
    for (bits = 0; bits < 8; bits++)
      {
      // wait for start bit
      while (N64_query()) { }
        
      PINB = bit (3); // toggle D11
      
      // wait for 2 uS then check the line
#if WANT_8MHZ
      nop; nop; nop; nop; nop; nop; nop; nop;   // 8
      nop; nop;   // 10
#else
      nop; nop; nop; nop; nop; nop; nop; nop;   // 8
      nop; nop; nop; nop; nop; nop; nop; nop;   // 16 
      nop; nop; nop; nop; nop; nop; nop; nop;   // 24 
      nop; nop; nop; nop;  // 28
#endif
      PINB = bit (4); // toggle D12
        
      // shift left as the most significant bit comes first
      *output <<= 1;
      // or this bit in
      *output |= N64_query() != 0;
      
      // wait for line to go high again
      while (!N64_query()) { }
        
      }  // end of for each bit
    output++;
    }  // end of while each byte
 
  // wait for stop bit
  while (N64_query()) { }
  // then other end should let line go high
  while (!N64_query()) { }

  wdt_disable();  // disable watchdog
}  // end of N64_get

byte buf [4];

void setup ()
  {
  pinMode (N64_PIN, INPUT);  // do not make OUTPUT or INPUT_PULLUP!
  Serial.begin (115200);
  Serial.println ("Starting ...");

// debugging
  pinMode (10, OUTPUT);    
  pinMode (11, OUTPUT);    
  pinMode (12, OUTPUT);    
  pinMode (13, OUTPUT);    
  }  // end of setup

void loop ()
  {
  PINB = bit (2); // toggle D10
  
#if CONTROLLER
  noInterrupts ();
  N64_get (buf, 1);  // wait for query
  byte sendBuf [] = { 0x05, 0x00, 0x02 };
  N64_send (sendBuf, sizeof sendBuf);
  interrupts ();
#else
  byte sendBuf [1] = { 0x00 };  // identify controller
  /*
  noInterrupts ();
  N64_send (sendBuf, sizeof sendBuf);
  N64_get (buf, 3);  // get ID
  interrupts ();
  char printBuf [30];
  sprintf (printBuf, "ID: %02X %02X %02X", buf [0], buf [1], buf [2]);
  Serial.println (printBuf);
  Serial.flush ();
  */
  noInterrupts ();
  sendBuf [0] = 0x01;  // poll controller
  N64_send (sendBuf, sizeof sendBuf);
  N64_status status;
  N64_get ((byte *) &status, sizeof status);  // get ID
  interrupts ();
  
  static N64_status oldStatus;
 
  if (memcmp (&status, &oldStatus, sizeof status) != 0)
    {
    if (status.stick_x || oldStatus.stick_x)
      {
      Serial.print ("X: ");
      Serial.print ((int) status.stick_x);
      Serial.print (" ");
      }
    if (status.stick_y || oldStatus.stick_y)
      {
      Serial.print ("Y: ");
      Serial.print ((int) status.stick_y);
      Serial.print (" ");
      }     
    oldStatus = status;
    if (status.data2 &  BUTTON_C_RIGHT)
      Serial.print (F("C right "));
    if (status.data2 &  BUTTON_C_LEFT)
      Serial.print (F("C left "));
    if (status.data2 &  BUTTON_C_UP)
      Serial.print (F("C up "));
    if (status.data2 &  BUTTON_C_DOWN)
      Serial.print (F("C down "));
    if (status.data2 &  BUTTON_R)
      Serial.print (F("R "));
    if (status.data2 &  BUTTON_L)
      Serial.print (F("L "));
    
    if (status.data1 &  BUTTON_D_RIGHT)
      Serial.print (F("D right "));
    if (status.data1 &  BUTTON_D_LEFT)
      Serial.print (F("D left "));
    if (status.data1 &  BUTTON_D_UP)
      Serial.print (F("D up "));
    if (status.data1 &  BUTTON_D_DOWN)
      Serial.print (F("D down "));
    if (status.data1 &  BUTTON_START)
      Serial.print (F("Start "));
    if (status.data1 &  BUTTON_Z)
      Serial.print (F("Z "));
    if (status.data1 &  BUTTON_B)
      Serial.print (F("B "));
    if (status.data1 &  BUTTON_A)
      Serial.print (F("A "));
    
    if (status.data1 == 0 && status.data2 == 0)
      Serial.print (F("(no buttons)"));
    Serial.println ();
    Serial.flush ();
    }
    
  delay (200);
#endif
  
  }  // end of loop

It is, admittedly, timing dependent. But still ...

Hi again, Nick! Yes, I've incorporated your work into my project -- I hope that's okay. I'll be sure to give you credit in the final version.

I was just wondering if there were an alternative to using macros to send signals to the controller. Perhaps calling pinMode() instead would work?

You can just type it... But pinMode and digitalWrite are slow.

Indeed, they are a lot slower and will throw the timings out, perhaps by too much.