4 The Controller

In this section we introduce a program that reads the Controller data and operates a square shape based on those values.

The source code is located in the nu1 directory below the sample directory.


4.1 Controller Routine

The main routine of nu1 is shown below:

/*  global variable  */
NUContData	contdata[1]; /* Controller 1 read data */
u8 contPattern;		     /* pattern of the connected Controller */

/*------------------------
	main
--------------------------*/
void mainproc(void)
{
  /*  initialize graphic  */
  nuGfxInit();

  /* initialize controller manager */
  contPattern = nuContInit();

  /* initialize for stage00() */
  initStage00();
  /* register callback */
  nuGfxFuncSet((NUGfxFunc)stage00);
  /*  turn display on*/
  nuGfxDisplayOn();

  while(1)
    ;
}

In order to read the Controller data you must initialize the SI manager and controller manager with the nuContInit function.

nuContInit returns the bit pattern of the connected Controller.

The updateGame00() function of stage00.c reads the Controller data and updates variables based on the values.

The updateGame00() function moves the square according to Control Stick data, switches the direction of rotation according to data from the A Button, and accelerates the rotation according to the length of time that the B Button is pressed.

/* extern NUContData    contdata[1]; read Controller 1 data */

void updateGame00(void)
{  
  static float vel = 1.0;

  /* read Controller 1 data */
  nuContDataGetEx(contdata,0);

  /* change display position according to Control Stick data  */
  triPos_x = contdata->stick_x;
  triPos_y = contdata->stick_y;

  /* reverse direction with A button */
  if(contdata[0].trigger & A_BUTTON)
    {
      vel = -vel;
      osSyncPrintf("A button Push\n");
    }

  /* accelerate rotation during B button push */
  if(contdata[0].button & B_BUTTON)
    theta += vel * 3.0;
  else
    theta += vel;
}

"updateGame00()" first reads the data of Controller 1 into the NUContData structure contdata[] using nuContDataGetEx().

The trigger data is set in the trigger member of the NUContData structure, and the button status is set in the button member of the NUContData structure, so processing of each button can be branched with the "&" bit.

(To read about button names see osContGetReadData in the N64 Function Reference Manual.)

Control Stick data can be read with the stick_x, stick_y members.


4.2 Debug Console

For software debugging, it is convenient to output the current variable values and messages to the TV screen.

NuSystem has a function to display console output on the screen.

Up to four console windows can be displayed.

You can also give attributes to the windows/characters.

For details, please refer to the debug-related functions (nuDebConDisp, etc.) in the NuSystem Function Manual.

If a Controller Pak is inserted, the makeDL00() function of stage00.c will display the current XY value of the Control Stick. If a Controller Pak is not inserted, the function will output a message to that effect.

The first character in the nuDebCon* function argument specifies the window number.

/* contPattern is the value returned by nuContInit() */
  if(contPattern & 0x1)
    {
      /*  changes character's display position */
      nuDebConTextPos(0,12,23);
      /* printf is not available, so character string is created using sprintf of N64OS */
      sprintf(conbuf,"triPos_x=%5.1f",triPos_x);
      /* display created character on screen */
      nuDebConCPuts(0, conbuf);

      /*Y vertex displayed in same way*/
      nuDebConTextPos(0,12,24);
      sprintf(conbuf,"triPos_y=%5.1f",triPos_y);
      nuDebConCPuts(0, conbuf);
    }
  else
    {
      nuDebConTextPos(0,9,24);
      nuDebConCPuts(0, "Controller1 not connect");
    }

  /*  character written to frame buffer */
  nuDebConDisp(NU_SC_SWAPBUFFER);

"nuDebConDisp()" produces the display list internally and activates the graphics task.

The argument, called internally by nuDebConDisp, is the frame buffer swap flag passed to the argument of the nuGfxTaskStart function.

By using the N64OS debug function osSyncPrintf() you can output a message to the host computer rather than display on the screen.

In the sample, a message is output when the A Button is pushed.

The symptoms differ depending on the development tool, but running a process that frequently outputs messages can lead to problems, so you should avoid methods that monitor variables for every frame.


4.3 Dual Display List Buffers

In the sample in the previous section, nu0, there is only one display list buffer. Here we present a sample which swaps between the use of two display list buffers.

/*   The following are defined in graphic.c
  Gfx          gfx_glist[2][GFX_GLIST_LEN];
  Dynamic      gfx_dynamic[2];
  u32          gfx_gtask_no = 0;
*/

void makeDL00(void)
{
  Dynamic* dynamicp;
  char conbuf[20]; 

  /* specify display list buffers */
  dynamicp = &gfx_dynamic[gfx_gtask_no];
  glistp = &gfx_glist[gfx_gtask_no][0];

     :
     :

  /* swap display list buffer  */
  gfx_gtask_no ^= 1;
}

When there are two display list buffers, the RCP can read the display list value and process a graphic. At the same time, the CPU can safely create the next display list.

With the RCP and CPU being run in parallel, the check by pendingGfx is less stringent than the check made in the nu0 software sample.

void stage00(int pendingGfx)
{
  /* process graphic if 2 or fewer RCP tasks are being processed or waiting for processing */
  if(pendingGfx < 3)
    /* create display list/start task process  */
    makeDL00();		

  /* advance game process */
  updateGame00(); 
}

Because debug console is a separate task, the task number increases by 1, so be careful. This is why the call to makeDL() is not made unless pendingGfx is less than 3.

When this is done, creation of the next display list can even begin in the middle of the current graphics task.

Of course, in this sample the RCP draw is completed within 1 frame, so there is really no need for dual display list buffers.

But dual display list buffers are extremely efficient when the display list takes a long to time to build.


4.4 Dropped Frames and Dropped Processes

In the stage00() function described above, if there are 3 or more pending graphic task processes, then makeDL00 will be skipped and the display will not be updated.

But updateGame() is still processed, and the square's rotation parameter will be updated in each retrace frame.

The result is that the rotation speed will remain stable, but omissions (dropped frames) become possible in the display.

By making the following changes to stage00(), makeDL00() and updateGame00() are rewritten so they operate at the same time, and the square's rotation parameter will also be updated when there are 3 or more pending graphic task processes.

The result is that the speed will slow (a process drop).

void stage00(int pendingGfx)
{
  /* process graphic if 1 or fewer RCP tasks are being processed or waiting for processing */
  if(pendingGfx < 3)
    {
      /* create display list, start task process */
      makeDL00();		
      /* advance game process */
      updateGame00(); 
    }
}

Try experimenting with frame drops and process drops by replacing shadetri() with something for which the display list takes a long time to build.