Monday, December 19, 2022

PIC32/6502 Blinky Robot

The PIC32 robot from my 2022 Project Goals is finally finished! Building it was a fun way to learn about the MIPS architecture and programming the PIC32. It was also a chance to work on a 6502 emulator that will be useful for other projects.

As far as robots go, this is not a very useful one since all it does is blink its eyes every few seconds. The interesting part is how it does the blinking. Rather than switching the LEDs on then delaying a while before switching them off, the robot runs several layers of emulation with the innermost emulated system blinking the LEDs as fast as possible. All the layers of emulators within emulators combine to produce enough overhead to slow the blinking down to a rate that is visible.

The first level of emulation is the 6502 emulator written in MIPS assembly mentioned in my post about Tali Forth 2 on Linux. The steps of each 6502 instruction are abstracted into a series of MIPS assembly macros and stored in a spreadsheet which let me design the system very quickly:

Op codeInstructionModeStep 1Step 2Step 3
0x6ERORABSDEREF_MEM_T1ROR_MEMT1_NZC
0x6FBBR6ZPRDEREF_MEMAND_BIT6BBR
0x70BVSRELT0_VBNE_T0
0x71ADCIZYDEREF_MEMADC_MEMA_NZC
0x72ADCIZPDEREF_MEMADC_MEMA_NZC

A Python script takes the spreadsheet saved as a CSV file and outputs an assembly file with the macros for each instruction:

    #0x6E - ROR ABS
    ROR_ABS:
        OP_MACRO 110, ROR, ABS, ROR_ABS, DEREF_MEM_T1, ROR_MEM, T1_NZC
        NEXT_MACRO

All that was left after that was filling out each macro for the instruction, address mode, and steps which didn't take long. 

Working with MIPS assembly for this was interesting since it differs from the 6502 in a lot of ways. For one, there is no flags register on MIPS so all the 6502 flags need to be calculated manually. One shortcut the emulator uses is to save the values that would generate flags but to delay the calculation until the flag is needed. The MIPS instructions INS and EXT are perfect for this since one register can store multiple saved byte values.

The second layer of emulation below the MIPS program emulating a 6502 is a 6502 program that emulates a 6502. Like the emulator described above, this one was very fast to set up using a spreadsheet of macro names for each instruction. Unlike most emulators, this one uses the same address range as the system it's emulating rather than setting aside a portion of memory for the emulated system. This lets emulated programs access system resources directly so that a program that writes to a memory-mapped peripheral, like the screen for example, works the same in emulation as outside of emulation without the overhead of address translation or range checks. Along those lines, I made a program that draws a bouncing ball in my JavaScript 6502 emulator to verify the functionality:

After this was working reliably, the next step was to implement eight bouncing balls at once. Each ball is assigned its own copy of emulator variables such as 6502 registers. A slight modification to the emulator makes it execute only one instruction before saving the emulator variables for the current ball then loading them for the next ball. This lets the emulator execute eight "threads" (one for each ball) one instruction at a time in round-robin fashion giving the illusion of multitasking. Because the emulator doesn't do any address translation, as mentioned above, all eight threads can share the same copy of the ball drawing program rather than having a separate copy in memory for each of the eight threads:


This proved that multiple copies of the emulator could coexist in memory at the same time. After reverting the emulator to the original version that doesn't run multiple threads, I tried having the emulator emulate itself. A variable in memory is incremented every time the emulator starts (whether it's running natively or in emulation) and the emulator switches to the target program once it hits the desired number of emulators in emulators. Nesting them six deep with an innermost target program that blinks one pixel on the screen gives a blink rate slow enough to see, just like the LEDs of the robot:


Moving the MIPS and 6502 versions of the emulator over to the PIC32 was relatively painless. The linker hung for around two minutes before crashing due to a stray .ORG statement left in from the test version of the 6502 emulator. Another hard thing to figure out was an exception that kept triggering in the PIC32 simulator. Luckily, the PIC32 offers a lot of information about what happened in the case of an exception, and it showed a read to a memory address beyond the end of RAM. One website I found had some useful information on exceptions. Digging a little further showed that the linker reserves 512 bytes at the beginning of RAM when the program is compiled in debug mode. Since the PIC32 program allocates all 64K of RAM to the 6502 address range, the extra memory for the debugger pushes the last 512 bytes of 6502 addresses out of range, so any reads or writes there cause an exception. Compiling the program in normal mode with no debugging eliminates the problem. 

The design for the robot is an SVG file like the schematic for my 7400 Logic Calculator. Unfortunately, coordinates in the SVG format are all hardcoded, so it's tough to adjust anything after writing the file out by hand. This time, I generated the SVG with a Python script. It only took 100 lines or so of Python to define a simple system for outputting SVG shapes using relative coordinates. My wife printed out the design on blue cardstock with silver lines on her Cricut machine. 100 units in the SVG correlated to 1.39 inches printed out instead of 1 inch or to 1 centimeter as I hoped. These seem to be PostScript points which are 72 per inch. Scaling the SVG to compensate was easy. The red lines are for planning and weren't printed.

Although the final version is a simple paper cutout on a PCB, the first version was 3D printed. The original plan was to put an OLED screen in the robot's belly to show the value of registers and let the user switch between different levels of emulation. In the end, this seemed like too much extra work on a project that has already stretched on much longer than intended. The 3D printed version also had a few problems: two of the tabs to hold the halves of the head together broke off, the finish on the downward facing side of the print came out very rough and needs to be sanded, and the DIP28 PIC32 is a little too big to fit in the space inside the body. Maybe I can glue the halves of the head together without the tabs and use this for another project.

The PCB is like the Electronic Birthday Card where some components go through the paper and all the wires are soldered flat on the back side. A 3xAAA battery pack is attached with headers on a separate board.

Now that this is finished, I can move on to the other projects on my list and use what I learned about programming the PIC32.

No comments:

Post a Comment