Updating the Copper while it is running

The copper can be reprogrammed while it is running, it doesn’t need stopping or resetting.

As I explained before, the code we write to make our list of copper instructions is not the same as the code the copper runs. Our code must be uploaded into the copper’s own RAM first. But nothing says we can’t do it while the code is running.

So if we update the copper as it runs, we can feed it new instructions to make effects that alter over time.

There are two ways to do this

  • Upload the whole copper list every frame

  • Change specific lines of the copper list every frame

The second method is faster as you only have to set a few bytes, but can be difficult if a lot of the list needs changing. The first method is easiest, but can slow the machine down if you have a long copper list.

The way to choose the most appropriate method is to look at your copper instructions and work out how many need changing each frame. If it’s only a few, and the program is quite large then use the second method. If your code is quite short, use either as it probably makes no difference.

Uploading each frame

Here is a piece of code that will manipulate a copper list then upload the whole of it, it runs each frame

// Variables used to count and keep track of the screen
uint8_t yPos = 16;
uint8_t frameCounter = 10;
uint8_t dir = 0;
uint8_t index = 3;

void UpdateCopperBars()
{
    // Alter specific parts of the copper list
    for (uint8_t i = 0; i < 72; i++) {
        gradientBars[index+i*3] = CU_WAIT(yPos+i*2,0);
    }

    // This is the same code as we used to upload the copper list at the start
    uint8_t *copperPtr = gradientBars;
    uint16_t copperProgLen = sizeof gradientBars / sizeof *gradientBars;
    for (int j = 0; j < copperProgLen*2; j++) {
        IO_NEXTREG_REG = REG_COPPER_DATA;
        IO_NEXTREG_DAT = copperPtr[j];
    }

    IO_NEXTREG_REG = REG_COPPER_CONTROL_H;
    IO_NEXTREG_DAT = 0xC0;
    IO_NEXTREG_REG = REG_COPPER_CONTROL_L;
    IO_NEXTREG_DAT = 0;

    // Code to manipulate the counters
    if (--frameCounter == 0) {
        frameCounter = 10;
        if (dir == 0) {
            yPos++;
            if (yPos > 144) dir = 1;
        } else {
            yPos--;
            if (yPos == 0) dir = 0;
        }
    }
}

As you can see, this is the same code as we used when initialising the copper.

Change specific lines of the copper list every frame

This is almost the same, but requires a bit more precision

// Variables to keep track of the screen location
uint8_t scrollClouds = 0;
uint8_t scrollMountains = 0;
uint8_t scrollGround = 0;

void UpdateParallaxScroll()
{
    // Change the lines in our copper list - not actually necessary really
    parallaxScroll[3] = CU_MOVE(REG_LAYER_2_OFFSET_X,scrollClouds);
    parallaxScroll[14] = CU_MOVE(REG_LAYER_2_OFFSET_X,scrollMountains);
    parallaxScroll[27] = CU_MOVE(REG_LAYER_2_OFFSET_X,scrollGround);

    uint8_t *copperPtr = parallaxScroll;

    // Choose the specific parts of the copper's own list to modify, and upload the changes
    IO_NEXTREG_REG = REG_COPPER_CONTROL_L;
    IO_NEXTREG_DAT = 7;
    IO_NEXTREG_REG = REG_COPPER_DATA;
    IO_NEXTREG_DAT = copperPtr[7];
    IO_NEXTREG_REG = REG_COPPER_CONTROL_L;
    IO_NEXTREG_DAT = 29;
    IO_NEXTREG_REG = REG_COPPER_DATA;
    IO_NEXTREG_DAT = copperPtr[29];
    IO_NEXTREG_REG = REG_COPPER_CONTROL_L;
    IO_NEXTREG_DAT = 55;
    IO_NEXTREG_REG = REG_COPPER_DATA;
    IO_NEXTREG_DAT = copperPtr[55];

    // Tell the copper to carry on
    IO_NEXTREG_REG = REG_COPPER_CONTROL_H;
    IO_NEXTREG_DAT = 0xC0;
    IO_NEXTREG_REG = REG_COPPER_CONTROL_L;
    IO_NEXTREG_DAT = 0;

    // Update the counters
    if (--frameCounter == 0) {
        scrollClouds++;
        scrollMountains += 2;
        scrollGround += 3;
        frameCounter = 10;
    }
}

There’s some specifics with this method that are important - debugging a malfunctioning copper list is difficult. At best you won’t see anything, at worse your entire machine will crash in quite interesting ways as your code scribbles all over random registers.

The list of instructions we have is stored using 16 bit integers, but those 16 bits need uploading to the copper as two 8bit integers. The first part of each 16 bits is the command, the second 8 bits are the actual value.

So in our example command 3 in our list is the “Set the X Scroll Register to nnn” command with the first 8 bits being that command, and the second 8 bits being how far to scroll.

And as you can see we select byte 7 of our already uploaded copper list because byte 6 is “Set the X scroll register to …” half, and byte 7 is how far.

This is then the same with byte 29 (16 bit byte 14 is 8 bit byte 28 and 29) and 55 of the following lines.

Something to be aware of is messing with the copper can upset the Next’s delicate screen synchronisation and some TVs can have issues with staying locked onto the image. My own Next is connected through a cheap HDMI splitter which seems to occasionally (and randomly!) lose sync with the video signal if I have complex copper instructions running, so if you’re creating complex and unique effects test them well on a variety of devices just to make sure they work as you expect.