## Safety switch

Now that we are fairly confident that (1) the compiler works, (2) it
produces code for our processor, (3) the resulting binary does not
contain any unwanted code; we’d like to build something that will run on
the actual hardware. But first we want to make sure we can go back to a
working firmware, or to flash a different program.

The stock firmware does this by handling a special USB request, upon
receiving which it reboots in ISP DFU mode. But our experimental
programs will not have USB handling until much later.

So we will establish a rule: In any unfamiliar situation, reboot to ISP.

And, in the beginning, every situation is unfamiliar.

So let’s write a function to reboot to ISP, and call it immediately upon
entering `main`.
Flashing our first program

We now want to test our first program. It should start up and
immediately reboot the device† into the bootloader.

† Which, for the time being, is no longer a keyboard, as it temporarily
loses the ability of reporting key presses to the host system.

To update the firmware code, we are supposed to use either the Truly
Ergonomic DFU tool, or the generic DFU tool from the [Megawin
MG84FL54BD][MG84FL54BD] SDK. They are functionally identical, it’s just
the logo that differs.

[MG84FL54BD]: http://www.megawin.com.tw/megawin_EN/ProductShow.asp?ID=175

Unfortunately, we can’t just run them under Wine, as it does not
emulate/bridge USB access yet. And I don’t feel like setting up a
separate Windows PC just to tinker with a keyboard.

Luckily, [VirtualBox][], with the Extension Pack installed, gives the
guest system access to USB devices. So install VirtualBox and the
Extension Pack, set up Windows inside it, and install Guest
Additions. (Additions, among other features, gives mouse pointer
integration, better video support, and shared folders.)

[VirtualBox]: https://www.virtualbox.org/

Inside the guest Windows, we need to create a directory and unpack the
Truly Ergonomic DFU tool in it. We will also set up a Shared Folder so
that our compiled program is available to the Windows VM. Copy the
`main.ihx` file into the directory with the DFU tool, and rename it
`reboot.hex`.

The DFU tool operates like this:

* You tell it to load a firmware (point to a .hex file) and then to
  Upgrade.
* It tries to find an attached USB device with the IDs of Truly
  Ergonomic Keyboard in DFU mode (VID 0E6A, PID 030B).
* If none such device is attached, it finds a Truly Ergonomic Keyboard
  in its normal mode (PID 030C), sends to it a request to reboot in
  DFU mode and waits until it does.
* In both cases, when a device with PID 030B is found, the DFU tool
  sends it a series of requests transmitting the firmware code, and
  finally directs it to reboot back in application mode.

In order for this to work from within VirtualBox, we need to connect the
keyboard to the virtual machine before clicking Upgrade. We do this by
right-clicking the USB connector icon on the bottom of the VirtualBox
window and selecting Truly Ergonomic Keyboard from the popup menu. This
may be tricky if you have two identical keyboards attached. So I first
disconnect my main keyboard (or just switch off its port on my USB hub),
leaving only the test device attached. Fortunately, the DFU tool can be
operated using only the mouse.

Note that the keyboard has a protection DIP switch on its bottom. If set
to ON, the keyboard will not reboot in DFU mode when requested. So set
it to OFF on your test subject device and leave ON on the workhorse.

When the DFU tool reboots the keyboard in DFU mode, it is no longer
connected to the VM. We could connect it again using the popup menu, but
that gets tedious very quickly. Luckily, VirtualBox lets us set up a
filter that will always connect devices satisfying its conditions.

Open the Machine menu and click Settings. On the USB tab, click the
button with the plus sign. A menu pops up, listing your devices that can
be connected. Select the one that says Unknown device, Vendor ID 0E6A,
Product ID 030B. A filter will be created. Click OK.

Now, the DFU tool probably has already timed out waiting for the device.
Cycle the power to the keyboard. Connect it to the VM. Click Upgrade
again. This time, the filter will kick in and automatically connect the
keyboard (which has rebooted in the DFU mode). The DFU tool will notice
that and send it the new firmware. Then the keyboard will reboot with
the new firmware. Which, as you remember, automatically and immediately
reboots back to the DFU mode. The filter will again kick in and connect
it to the VM. You can check that this happened by right-clicking the USB
connector icon and seeing that the Unknown device (VID 0E6A, PID 030B)
is checked.

If you like to keep your spare in working condition, you can now reflash
the original firmware. This time, the device is already in DFU mode and
connected to the VM, so just load the .hex file and click Upgrade.
## Pin I/O

Our last test program just rebooted to ISP immediately on startup. Next
thing we want to test some pin input/output.

Let’s recall the pin assignments from disassembling the stock firmware.

* P3.1 [in] DIP switch 4
* P3.2 [in] DIP switch 3
* P3.5 [out] LED 3 (Num Lock by default)
* P3.6 [out] LED 1 (Caps Lock)
* P3.7 [out] LED 2 (Scroll Lock)
* P4.1 [in] DIP switch 2
* P4.2 [in] DIP switch 1
* P4.3 [in] DIP switch 5

Also, we have a bunch of pins connected to the key matrix (P1.0÷7,
P2.0÷7, P3.3, P3.4 select a column, then P0.0÷7 give states of the keys
in that column). But that’s for later.

All these I/O pins are negated: a 0 value indicates a LED, a DIP switch
or a matrix line is active.

This time we will first query the DIP switch 4. If it is in the ON
position (0), we proceed to the main program. If OFF, we reboot to the
ISP mode.

The main program will turn on the middle LED and then just do nothing.
## Tapping the timer

Let’s now see how interrupts are handled in SDCC.

The microcontroller has three timers. The original firmware uses Timer 0
in order to implement the delay routine. It programs this timer to work
in Mode 1. In this mode, the TH0:TL0 registers contain a 16-bit counter
which is incremented every 12 ticks of the system clock running at
12MHz (in other words, each microsecond).

Note that if we used the full capability of the 16-bit timer counter and
used the timer interrupt as a completion signal, we could measure delays
of up to 6.5 milliseconds. This is way too short, so we resort to
counting interrupts. For convenience, we arrange it so that a timer
interrupt happens each millisecond while the timer is running.

The delay routine accepts as argument an 8-bit unsigned number of
milliseconds, stores in a variable, sets the timer count to −1000, and
enables the timer. After that, it waits until the delay count becomes 0.
Meanwhile, the timer interrupt service routine gets called whenever the
timer count overflows, decrements the delay count (if not already zero),
and resets the timer to −1000. (The hexadecimal for −1000 in two’s
complement code is `0xFC18`.)

The 8-bit size for the delay count means we are limited to delays of up
to 255 milliseconds, or roughly ¼s. For larger delays, we can call
`delay(250)` two, three or four times.

To illustrate that the timer works, we will blink the middle LED with a
period of 500 milliseconds.
Split into separate files

Up to now, we developed everything in a single `main.c` file. However,
already a couple of groups of functionality have emerged:

* Timer and all related things.
* Controller-specific things such as `reboot_to_isp`.

Soon we will start adding definitions for all Megawin-specific special
function registers, there are lots of them, and it’s not desirable to
clutter up `main.c` with them. So this seems a good time to split our
program into several compilation units.

* Timer goes into `timer.h` and `timer.c`.
* `reboot_to_isp` goes into `mg84fl54bd.h` and its corresponding `.c`.

The `Makefile` becomes a bit more complex, as it now has to separately
compile each `.c` file and then link the final program from object
files.
Typing out MG84FL54-specific registers

In the next part we will start using the functionality specifically
provided by our microcontroller, the Megawin [MG84FL54BD][]. It has
quite a few special function registers (SFRs) about which SDCC’s
standard headers know nothing. So we will need to list them. They go in
the controller-specific header file.

[MG84FL54BD]: http://www.megawin.com.tw/megawin_EN/ProductShow.asp?ID=175

Megawin’s SFRs come in two varieties. There are ordinary SFRs located in
the direct addressable memory at `0x80` through `0xFF`; and there are USB
SFRs at extended memory addresses `0xFF00` through `0xFFFF`.

Many registers are bit masks (with each bit having separate meaning),
and a few are bit-addressable (the controller can access an individual
bit without using bitwise operations).

The syntax for a direct SFR is:

```c
__sfr __at(0xD5) KBPATN;
```

(It is technically a bit mask register, but its bits don’t have
individual names in the manual so we don’t bother.)

For a bit mask register, we want to define symbolic constants for each
significant bit:

```c
__sfr __at(0xAD) AUXIE;
enum { EUSB = 0x80, ETWSI = 0x40, EKB = 0x20, ESPI = 0x01 };
```

For bit-addressable registers, SDCC provides a mechanism of giving names
to each individual bit:

```c
__sfr __at(0xE8) P4;
__sbit __at(0xE8) P4_0;
__sbit __at(0xE9) P4_1;
__sbit __at(0xEA) P4_2;
__sbit __at(0xEB) P4_3;
```

For `xdata` SFRs, there is no special syntax in SDCC. So we need to
declare them as absolute address `xdata` variables, with a `volatile`
qualifier so that the optimizer does not meddle with them:

```c
volatile unsigned char __xdata __at(0xFFC0) DCON;
```

USB SFRs can also be bit mask registers.

But it’s a bit tiring to type out all this boilerplate so let’s employ
some preprocessor magic. We would like the following syntax:

```c
SFR(0xD5, KBPATN); // byte SFR
SFR_(0xAD, AUXIE, EUSB, ETWSI, EKB, _, _, _, _, ESPI); // bitmask SFR
BIT_SFR(0xE8, P4, _, _, _, _, P4_3, P4_2, P4_1, P4_0); // bit-addressable SFR
XSFR(0xFFD8, UADDR); // byte xdata SFR
XSFR_(0xFFC0, DCON, _, _, _, SRWKP, EP3DIR, _, _, _); // bitmask xdata SFR
```

To achieve this, we define a few macros. The easy ones:

```c
#define SFR(address, name) __sfr __at(address) name
#define XSFR(address, name) volatile unsigned char __xdata __at(address) name
```

Bit masks:

```c
#define BITS(b7, b6, b5, b4, b3, b2, b1, b0) \
    enum { b7 = 0x80, b6 = 0x40, b5 = 0x20, b4 = 0x10, \
        b3 = 0x08, b2 = 0x04, b1 = 0x02, b0 = 0x01 }
#define SFR_(address, name, b7, b6, b5, b4, b3, b2, b1, b0) \
    SFR(address, name); \
    BITS(b7, b6, b5, b4, b3, b2, b1, b0)
#define XSFR_(address, name, b7, b6, b5, b4, b3, b2, b1, b0) \
    XSFR(address, name); \
    BITS(b7, b6, b5, b4, b3, b2, b1, b0)
```

Bit-addressable:

```c
#define BIT_SFR(address, name, b7, b6, b5, b4, b3, b2, b1, b0) \
    SFR(address, name); \
    __sbit __at(address) b0; \
    __sbit __at(address + 1) b1; \
    __sbit __at(address + 2) b2; \
    __sbit __at(address + 3) b3; \
    __sbit __at(address + 4) b4; \
    __sbit __at(address + 5) b5; \
    __sbit __at(address + 6) b6; \
    __sbit __at(address + 7) b7
```

Now we only have to define the magic underscore “don’t care” macro. We
want it to expand each time into a unique unused identifier. Luckily,
the SDCC compiler has an extension macro `__COUNTER__` just for this
purpose.

```c
#define CATENATE(a, b) a ## b
#define UNUSED(x) CATENATE(UNUSED_, x)
#define _ UNUSED(__COUNTER__)
```

(Two auxiliary macros are needed to force expansion of `__COUNTER__`
before `##` catenation.)
## Reboot on any key

While trying to implement the basics of USB, I got tired of continuously
toggling the safety DIP (reboot as ISP → reflash → reboot normally → it
doesn’t work as expected → repeat). Moreover, it is said that DIP
switches have a limited resource. Would be much easier if I could reboot
to ISP mode with a press of a key — we don’t have any use for them yet.

This is where the keypad interrupt comes in handy.

First, we set up the column pins as open-drain outputs:

```c
void keypad_init(void)
{
    P1M0 = 0xFF;
    P1M1 = 0xFF;
    P2M0 = 0xFF;
    P2M1 = 0xFF;
    P3M0 = 0x18;
    P3M0 = 0x18;
```

Then we activate all columns:

```c
    P1 = 0;
    P2 = 0;
    P3 &= 0xE7;
```

And set up the keypad interrupt to trigger when any row goes active:

```c
    KBMASK = 0xFF;
    KBPATN = 0xFF;
    KBCON &= ~PATN_SEL;
    AUXIE |= EKBI;
}
```

The interrupt handler will just reboot to ISP mode.

(While coding this change, I stumbled at the SDK manual calling a few
register bits with inconsistent names (`PATN_SEL` vs `PTNS`, `EKBI` vs
`EKB`). I added aliases for these bits.)