More Ruby software

At this point, I have a nice little 65C02 system with 64KBytes of RAM, a little “monitor” with serial interface. I can run Applesoft, ehBasic and BBC Basic.

Where now?

Well… without really trying (nor setting out to do this initially), I have more or less created a very much cut-down BBC Micro. I can load (BBC Micro) ROMs from the flash “filing system” on the host processor, I’m working on a “proper” filing system as the host has some spare RAM and NVRAM (only 8K and 4K respectively, but better than nothing!) and I’ve added a SPI access SD card. (More on the filing system later).

My ideal 8-bit system?

Right now (April 2019) prompted by a video by “the 8-bit Guy”, there are a few public projects where people are creating their “ideal” 8-bit micro system (although they’re all using the 65C816, a 16-bit chip) There is Daves one – now called The “Commander X64” and Stefany Allaires “Foenix” – these both have ideas stemmed from the Commodore 64, and Lenore Byrons “Neon816” which is fairly generic but she has indicated it’s inspired by 8-bit Daves ideal computer which is Commodore 64 based.

So, while I never initially thought about this route, I’m now wondering what my ideal 8-bit (or 16-bit) system might actually be..

Here are some thoughts: I think I want something based round mid 1980s technology and style but with a modern approach. 65C02 or 65C81 based and enough RAM to be usable and running at a good but stable speed. By the mid 80’s, my world revolved round the Apple II as a work computer (writing 6502 assembler, mostly) and I had a BBC Micro at home. I was also using the BBC Micro in a work environment writing lots and lots of BCPL. I simply didn’t get into the games side of it at all (The exception being text-based adventures and Elite)

The Apple II, while sturdy was feeling slow, although the ProDOS filing system was keeping it going. The BBC Micro was well established with a good, modern (for the 8-bit world) BASIC with named functions and procedures, local variables, a nod towards a more structured programming approach, and so on. It also had an “operating system” which was very comprehensive from an API point of view, but while it did support a command-line, there wasn’t really a command line “shell” as such. (I was also using Unix and Primos at that point) My thoughts had been to improve on the shell/command-line aspect of it with utilities loaded from “disk” as required. (and ROMs). Possibly a bit like the way CP/M works, although a very (very!) cut-down Unix would be nice…

Thinking about the underlying operating system – I came to the conclusion that the Acorn MOS would be hard to better on era-style hardware. It has a very well defined interface and although a lot is very BBC Micro specific, a lot is also very generic. For example simple character output and cursor positioning – there’s a standard call for that – which makes it very easy for me to translate those calls into standard ANSI terminal cursor movements to make a serial terminal work well, and also to a graphical video system when I build it. (I’ve yet to try some BBC Micro editor ROMs though and while I’d like to think they used the MOS, right now I don’t know!)

Bytes and Words

And so, I’ve slowly been working through the Acorn MOS calls, running a ROM or 2, seeing what they need and filling in the gaps. There are 2 main calls: OSBYTE and OSWORD. OSBYTE deals with small byte-wide data and OSWORD requires a larger parameter block. Typical OSBYTE calls may be to select output destination (e.g. printer), to read buffer status and perform keyscans, OSWORD for larger things like “read in a line of text with simple editing”. I have debug output so that when an un-implmented call happens, I get to know about it and can work out if I need to write a full implementation, or simply return with it unhanded.

Command line

The command line is something I do want to work on. Right now, I have implemented OSCLI which takes a line of text and parses and executes it. So with just a few lines of code, call the relevant OSWORD to read in a line, pass it to OSCLI and you have a very simple command-line interpreter. Ultimately I’d like to do console output redirection, similar to using the < and > functions of a typical Unix shell and this should be possible – I just write the code… And filing system…

It does leave me with a nagging thought that all I’ve done is re-create a poor-man’s BBC Micro though and I’m now thinking how I can modernise this while keeping a familiar feel to it all. One thing would be to move to using the 65C816 processor – this is the 16-bit successor to the 65C02 with a 24-bit segmented address bus (16MB of RAM in 64K banks) – however it wasn’t that well used back in the ’80’s. The Apple IIgs and the SuperNES both used it, but almost nothing else (There is the Acorn communicator, but there weren’t really aimed at the general public, sadly) However the ‘816 is almost 100% compatible with the 65C02 in emulation mode and can switch to 16-bit mode (and back again) very quickly, so it seems like a logical choice, however that’s for the next project. For now, I want the Ruby 6502 to the point where I feel it’s as good as it will get.

Back to the Ruby 6502 index page

Bring-up testing the 6502

Testing the Ruby 6502

After making it on breadboard, I was already relatively confident that it would work on stripboard and my initial testing went well. I could access the shared RAM from the ATmega, write test patterns into it, read them back and the initial bring-up of the 6502 went well. I wrote a tiny program to take the reset vector and loop to itself which let me probe some data and address lines to see that it was all working, and it was, so next step was to make it do something more useful …


Most old (and old-style!) 8-bit micros had some sort of “monitor” program that ran at boot time.An early example is WozMon. This would let you edit memory, maybe load programs from a punched tape or audio tape and provide a way for programs to print and take keyboard input. The Apple 1 worked like this.

Often it was hidden away and all it really was was a set of basic input/output subroutines (what we might now call a BIOS) which were used by the main BASIC ROM to communicate with the outside world.

The Apple II, PET (and Vic20/C64), TRS-80, ZX Spectrum, and many others all worked in a similar manner to this

The Apple II did have another trick up its sleeve… It had a full-blown “monitor” that could let you display and alter and move memory, disassemble 6502 instructions, a mini one-line at a time assembler as well as a few more features like some floating point subroutines and a pseudo 16-bit interpreter.

The BBC Micro was slightly different again – come 1981 and some ideas had changed, so it had what we might now recognise as an operating system with well-defined ways to call functions inside the OS to perform text input and output, graphics, sound, disk (and network) filing systems and so on. There was a command-line interface to this Machine OS (MOS), but for the most part, you’d turn the computer on, and half a second later, like most of the other micros of the day, a beep then you were in BASIC. The command-line interface was commonly accessed from BASIC (and other languages) by typing a star (*) then the command, so the phrase “star command” was popular.

The main alternative to this style of computer startup was CP/M. More popular on 8080 and Z80 systems. This loaded from disk and provided a set of basic input and output subroutines (ie. BIOS) and a command-line driven interface. Almost all commands were then loaded from disk into RAM to be executed. Many modern day systems work like this.

What do I do for Ruby?

After initial bring-up testing and writing some basic routines to talk back to the ATmega host processor via the shared memory interface, I started with an Apple II style of monitor. I wrote a somewhat simplified version which let me do things like dump and modify memory, memory tests, poke short bits of code and execute them. I made this with a well-defined interface for character IO and a few utility routines, so that other programs could use it for keyboard and screen input and output and so on.

This worked (and still works!) well. I can use it to run ehBasic and Applesoft BASIC and I’ve also had Apple Integer BASIC running under it too, as well as a few of my own programs. I also wrote a new implementation of Woz’s Sweet16 for it too – to make it go a little faster and remove a (re)location issue it has. (I have plans for a project that will need some 16-bit data manipulation so I thought this might be handy)

This is an example of it in operation:

GMon v1.0. Ready
Copyright (c) 2018, Gordon Henderson
% 0.ff   // Dump RAM from $0000 to $00FF
0000: 4C 3C C4 4C 3A CB 00 00  | L< L:    |
0008: 00 00 4C 99 D1 22 00 6B  |   L  " k |
0010: 00 00 00 04 00 00 00 00  |          |
0018: 00 00 00 00 00 00 00 00  |          |
0020: 00 00 00 00 00 00 00 00  |          |
0028: 00 00 00 00 00 00 00 00  |          |
0030: 00 00 FF DD 00 00 00 00  |          |
0038: 00 00 00 00 00 00 00 00  |          |
0040: 00 00 00 00 00 00 00 00  |          |
0048: 00 00 00 00 00 00 00 00  |          |
0050: 00 F0 55 52 00 07 50 C3  |   UR  P  |
0058: 00 00 00 00 00 00 38 CE  |       8  |
0060: 0A DD 78 00 00 00 03 01  |   x      |
0068: 08 65 09 6C 09 6C 09 00  |  e l l   |
0070: C0 00 C0 00 C0 C8 FF C8  |          |
0078: 00 62 00 00 00 00 08 00  |  b       |
0080: 00 01 82 00 00 00 00 00  |          |
0088: 88 00 05 00 6E 09 00 03  |     n    |
0090: 4C 01 00 00 6C 08 65 08  | L   l e  |
0098: 00 00 00 00 00 90 00 00  |          |
00A0: F0 00 00 00 00 84 F0 00  |          |
00A8: 00 00 00 00 00 0C C3 65  |        e |
00B0: 09 E6 B8 D0 02 E6 B9 AD  |          |
00B8: 08 02 C9 3A B0 0A C9 20  |    :     |
00C0: F0 EF 38 E9 30 38 E9 D0  |   8 08   |
00C8: 60 80 4F C7 52 00 00 00  | ` O R    |
00D0: 00 00 00 00 00 00 00 00  |          |
00D8: 00 00 00 00 00 00 00 00  |          |
00E0: 00 00 00 00 00 00 00 00  |          |
00E8: 00 00 00 00 00 00 00 00  |          |
00F0: 00 01 00 00 00 00 00 00  |          |
00F8: F8 00 00 00 00 00 00 00  |          |
% ?

Unknown command: "?"
% 400:a9 47 20 03 f0 60    // Enter small program to print 'G'

% 400.40f

0400: A9 47 20 03 F0 60 60 B9  |  G   ``  |
0408: 00 FF 99 00 04 C8 D0 F7  |          |
% 400g

Go @0400

Load up Applesoft:

-> Applesoft ... Loading @ $C000. OK: End @ $E880

Remember to set CAPS-LOCK to use BASIC

]PRINT 1/3


At this point I typed in a program (actually copy & paste as I don’t have any Applesoft filing operations working yet)


10 MAXITER = 20
20  LET C$ = " .,'~!^:;[/<&?oxOX#  "
30  FOR Y =  - 21 TO 21
40  FOR X =  - 21 TO 21
50 CREAL = X / 11
70 CIMAG = Y / 11
95 CT = 1
107 ZL = ZM + ZN
110  IF ZL > 4 THEN  GOTO 170
120 Z2 = ZM - ZN + CREAL
140 ZREAL = Z2
150 CT = CT + 1
170  PRINT  MID$ (C$,1 + CT,1);
180  NEXT X
185  PRINT ""
190  NEXT Y
200  END 

...,,,,'''''''''~~! !~~'',,,,,,,,,,,,,,,...
..,,,'''''''''~~~!![[ ~~~'',,,,,,,,,,,,,,..
.,,''''''''~~~~!!^<   ^!!~''',,,,,,,,,,,,,.
.,''''''''~~~~!^:;<  o;^^!~''',,,,,,,,,,,,.
.''''''''~~~!!; <     O/[ !'''',,,,,,,,,,,.
,'''''''~!!!!^;          <!~''',,,,,,,,,,,,
''''''~^^!!^^;           [^~'''',,,,,,,,,,,
'''~~!!<;;[:;o            <~'''',,,,,,,,,,,
'~~~!!^;#O  /#            :~~''',,,,,,,,,,,
~~~!!!;                   :~~''',,,,,,,,,,,
~~!^:;OO                  ^~~''',,,,,,,,,,,
~~!^:;OO                  ^~~''',,,,,,,,,,,
~~~!!!;                   :~~''',,,,,,,,,,,
'~~~!!^;#O  /#            :~~''',,,,,,,,,,,
'''~~!!<;;[:;o            <~'''',,,,,,,,,,,
''''''~^^!!^^;           [^~'''',,,,,,,,,,,
,'''''''~!!!!^;          <!~''',,,,,,,,,,,,
.''''''''~~~!!; <     O/[ !'''',,,,,,,,,,,.
.,''''''''~~~~!^:;<  o;^^!~''',,,,,,,,,,,,.
.,,''''''''~~~~!!^<   ^!!~''',,,,,,,,,,,,,.
..,,,'''''''''~~~!![[ ~~~'',,,,,,,,,,,,,,..
...,,,,'''''''''~~! !~~'',,,,,,,,,,,,,,,...


And that works well. I ran a few versions of this under Applesoft and ehBasic and use it as a sort of system soak test.

IO IO, it’s off to work I go …

The final thing to test was my single bit of IO decoding. I wrote a program, connected an oscilloscope to it and the scope trace wiggled as I expected it to. (Actually, not first time, I went through an iteration or 2 of my GAL programming to get it right). I hooked the signal up to an 8-bit latch with the inputs connected to the 8-bit address bus, then I could light up some LEDs.

This video is incrementing a counter each time it finishes a simple memory test:

Blinkenlights are always a good thing and a traditional Larson Scanner was written. (In Sweet 16)

Here is the code:

        jsr     strout
        .byte   "Larson 16",13,10,0


        set     r12,$0100       ; Sweet16 stack pointer
        set     r2,$00FF        ; Larson data end marker value
        set     r8,IO_REGISTER  ; Update LEDs

        set     r1,larsonData   ; Address of the data table
        ld      r8              ; Copy r8 - address of LEDs into r7 because ...
        st      r7

        ld      @r1             ; Loads and increments r1
        cpr     r2
        bz      loop0           ; Reached the end
        st      @r7             ; ... store increments

        bs      delay
        br      loop1

        set     r3,$0800
        dcr     r3
        bnz     sub1a
        .setcpu "65c02"

And data:

; larsonData:
;       Bit patterns for a 3-bit wide scan, there and back again.

.export larsonData
        .byte   %00000000
        .byte   %00000001
        .byte   %00000011
        .byte   %00000111
        .byte   %00011100
        .byte   %00111000
        .byte   %01110000
        .byte   %11100000
        .byte   %11000000
        .byte   %10000000
        .byte   %00000000
        .byte   %00000000
        .byte   %10000000
        .byte   %11000000
        .byte   %11100000
        .byte   %01110000
        .byte   %00111000
        .byte   %00011100
        .byte   %00001110
        .byte   %00000111
        .byte   %00000011
        .byte   %00000001
        .byte   %00000000
        .byte   $ff


But I wanted more…

After the Apple II, I would consider the BBC Micro to be my next favourite 8-bit micro system. Although they really tie for top place for many reasons. It was easy to say (back in the day) that the Beeb was better, faster, etc. but it also had 3 or 4 years advantage – and back then, 3 years was a long time and technology was changing really fast!

Anyway, after another “how hard can it be” moment I started to look at getting BBC Basic running. Fortunately a lot of hard work has already been done by others on this and Jonathan Harston in particular (See: so it was relatively easy. I had to write just 3 Acorn MOS “OSBYTE” calls to make a text-only BBC BASIC run.

Well, not quite – there was slightly more to it than that – mostly to do with IRQ/BRK handling, however I can now run BBC BASIC on Ruby and I’ll write about that soon..

Although before I close this one, just a note to say that once it was on stripboard and I’d written the code to do some memory tests and BASICs I decided to see how fast I could get it to run at… the W65C02 is rated to 14Mhz and I had been hoping I might get it up to 8Mhz – I did use some good fast memory but otherwise I didn’t really put much effort into the stipboard layout, and so once it was happy at 1Mhz, I tried 2Mhz, then 4 and 8Mhz crystals. At 8Mhz it was just as stable as 1Mhz and as I didn’t have any more crystals, I decided for a bit of fun to go for broke and tried the 16Mhz crystal I was using for the ATmega and just run a wire over the board. (See the green wire with a knot in it in the video above) I was quite amazed that it worked. And it continues to work just fine at 16Mhz although to save wires flying all over the board, I’m currently running it at 8Mhz.

Back to the main Ruby index page

Ruby 6502 – More hardware details

How does it work?

The 6502 (or W65C02S in our case) is really designed to have some ROM (or modern flash, etc.) to enable it to boot. At power on or reset, it reads an address out of a fixed location normally stored in ROM and jumps to it. That code then does what’s needed to make the system get going.

And here we have a small issue: The Ruby has no ROM…. So why not, and how to make it work?


One reason was to simplify the hardware. ROMs are typically, relatively slow, even by the very old 6502 standard. To make matters worse, the 6502 has a somewhat interesting way of accessing memory that means the memory has to be at least capable of responding in half the time you might expect. So for a 1Mhz 6502, you might expect a 1-microsecond access time, however it really needs just under 500nS. Take that to the maximum speed that a modern W65C02S is rated for which is 14Mhz and you need better than 70nS. In reality, you actually need better due to the latency/propagation delay of signals going from the 6502 through e.g. address decoder before it gets to the ROM chips. The same timings apply for RAMs too.

Also the 6502 only reads or writes the memory for half a clock cycle and this was taken advantage of by the various video generation circuitry of the early microcomputers such as the Apple II – 6502 runs flat-out, but does it’s RAM access in half a cycle, the video jumps in on the other half cycle and pulls data out to send to the display.

So we need (relatively) fast ROMs, and there weren’t any suitable. I was aiming for classic through hole technology too. There’s also the issue of having to reprogram them. I did this regularly over 30 years ago and it quickly becomes a chore, so I decided to go for an all RAM solution.

RAM chips are still available in through-hole technology, fast enough and with the capacity I needed, although I ended up with 2 32K x 8 bit chips as there were no suitable 64Kx8 ones.

The issue now – how to get data into the 6502 in the first place.

Enter the ATmega.

I had been experimenting with using an ATmega to generate composite video and had a system using an ATmega1284p which has 16KB of internal RAM and was generating a nice 320×240 pixel monochrome display, so I went about working out how to interface that to the memory system with the aim of using that as it’s video output. (I’ve since put that idea on hold – more on that later)

And this is where a modern version of the 6502 works well. The WDC 6502s which are still being made today have a couple of features that work well here. One is the BE or Bus Enable signal. Pull this low and the 6502 immediately tri-states all its address and data lines. The other is the Rdy signal. Pull this low and the 6502 stops at the end of the current clock cycle. So, it’s a simple matter of halting the 6502, disabling its bus, connecting the bus to the ATmega and having the ATmega fill the RAM, then reverse the process and let the 6502 boot. What could possibly go wrong …


The 6502 has a single Read/Write output pin. the RAM needs separate Read and Write inputs. There are also 2 RAM chips and I want to decode a block for some IO devices…

Enter the GAL…

A GAL is the pre-cursor to the FPGA. It’s a Generic Array of Logic. Initially one-time programmable (PALs), the GAL can be re-programmed 100 or so times. They were popular in the early 1980’s to the mid 90’s when the ULA was being used in home computers such as the BBC Micro and ZX Spectrum. In the Ruby it’s doing the job of sorting out the read and write signals, working out which enable signal to send to the RAM chips and enable some IO decoding to be performed. As a bonus I can hook up an LED or 2 to help with debugging if required.

There is a minor issue with GALs these days – Lattice stopped making them a few years ago an dsupplies from the mainstream outlets have all dried up. Atmel (now Microchip) are still making new GALs, however I lack the programmer to correctly program them and programmers that can program the Lattice ones simply won’t work, so right now I’m sourcing them from what appears to be reliable component recycles in China and so-far they’re all working perfectly.

The memory map

The 6502 divides it’s 64KB of memory up into 256 byte pages and it needs RAM in page 0 ($0000 -> $00FF) and page 1 ($0100 -> $01FF). This is for fast data storage and the stack. The reset vector (and interrupt vectors) are at the top of memory ($FFFA -> $FFFF) which is normally ROM, so I arranged the system so that the top page from $FF00 through $FFFF was the page that was shared with the ATmega. This needs 8 pins from the ATmega for the data bus and 8 for the address. Also a few more to control the 6502 Reset input, Rdy and BE signals, as well as read and write. The resulting memory map looks like:

$0000 -> $FDFF: RAM
$FE00 -> $FEFF: 256 bytes of IO space
$FF00 -> $FFFF: 256 bytes of RAM shared between the 6502 and ATmega

It’s quite simple and easy to decode inside the GAL and leaves the maximum amount of RAM for program and data usage.

On the breadboard version there was no real IO – just a pin I could connect to a ‘scope. On the stripboard version there was an 8-bit latch I could use and on the PCB version there is a WDC65C22S VIA with it’s dual parallel ports, timers and other features.

So at power on, the ATmega controls the system. It holds the 6502 in reset and pulls the BE pin low to cause the 6502 to tri-state it’s buses. It copies the minimal bootloader into the shared memory page which includes the reset vector then releases the memory (tri-states the pins used for address and data lines), lets the 6502 access the memory and takes it out of reset.

The 6502 wakes up, reads the reset vector and jumps to it. (This is $FF00 initially) That code relocates itself to a lower location in RAM then communicates with the ATmega to get more code to finish the bootstrap process.

The communication is fairly straightforward and relies on another trick of the W65C02S – the Rdy pin is bi-directional and there is a new instruction; WAI which causes the 6502 to halt, pull Rdy low then wait for an interrupt. The ATmega meanwhile has been reading the Rdy pin and when it sees it going low, it switches the bus RAM control over, reads a command and data out of the shared memory page and does whatever it’s commanded to do. For example, it might be asked to load block 5 of the bootloader, so it copies that block from it’s own internal flash to the shared memory then switches the bus control back to the 6502 and sends an interrupt to the 6502 which causes it to wake from the WAI sleep and carry on.

And that’s the key to the 6502s communications. Everything happens via the small page of shared RAM – a virtual serial port, simple ROM filing system and a full-featured filing system that can use the spare RAM on the ATmega or it’s NVRAM or an external SD card as file storage.

It’s not a million miles away from the BBC Micro, however in this case, once the 6502 has booted, it’s really the master of the system. The ATmega is a multi-function peripheral device that just helped to boot the 6502 in the first instance.

From now on, it’s 6502 code time and I’ll talk about that next.

Back to the Ruby 6502 index page

Ruby 6502 on Stripboard

Where next?

I had a barely working 6502 prototype system on my workbench. It was a little unstable and didn’t like being moved, but it did work and demonstrated to me that the concept was viable.

Initially I didn’t know what I wanted other than “6502”… However some 30-40 years ago I did make some 6502 single board computers (SBC) and did a lot of programming on the Apple II and BBC Micro. I have also collected a few old Apple II’s and BBC Micros/Masters and one thing I had in my mind was to “re master”, or “re-imagine” what a 6502 computer system might be like with todays ideas and thoughts.

So the first task was to get a stable system and make it run BASIC.


Enter stripboard. Popular in the UK it’s been the staple of electronics hobbyists for decades. With a hand-held cutter (or in my lazy case an electric drill with suitable bit) good soldering skills and the ability to visualise your circuit you can achieve almost anything…

And so here it is:

Ruby 6502 on stripboard

The chip layout differs slightly from the breadboard, from left to right: ATmega1284p, RAM (more on this shortly), 6502 and below that the GAL22v10.

There is no schematic for this – I knew what I wanted, printed out the data sheets for all the chips and set about wiring it up, one wire at a time.

Now that green wire that goes from next to the can oscillator on the right over to the left… It’s carrying a 16Mhz signal. These new WDC 65C02 chips can run at 14Mhz and so when I was buying bits I bought a few oscillators for it – the 16Mhz one was really for the ATmega, but on a whim I tried it in the 6502, after going 1, 2, 4 and 8Mhz. At 16Mhz, 2Mhz over the rated maximum and on stripboard it runs at 16Mhz and passes memory tests until the cows come home. That’s not too shabby, I reckon…


And so the memory. Looks a bit odd there, perhaps? That yellow wire going over the top. Maybe this image will give a clue:

Another view of the stripboard 6502

The RAM is 2 x 32KBx8 chips with one soldered on-top and it’s enable pin brought out separately. I’d not intended to do it that way, but why not – it works well and takes up less board space. I also know I’m not the first to do so either.

And for the curious, here is the back with all that lovely tin/lead solder:

The reverse of the Ruby 6502 stripboard

But what can it do at this stage?

By this time I’ve added a little 8-bit latch and stuck my old LED board onto it. It’s running a binary count with each count representing a single pass of a simple memory test.

Next I’ll write about how it works, the memory map and exactly what the ATmega is doing.

Back to the ruby 6502 index page

My 6502 system

A 6502 SBC: Working prototype

To mark my own use and knowledge of 40 years of the 6502, I decided to build my own little single board computer (SBC) based on it.

I had a few ideas in-mind – one was to make it a little faster than the old ones (but I really didn’t mind), one was to make it suitably “retro” and try to use the sorts of components available round about my target date – so this meant through hole components rather than surface mount. I also wanted to try to keep the hardware fairly simple.

My initial aim was for a stand-alone system with video output and keyboard input. Also a serial port for software download, blinkenlights, of-course and as much RAM as possible.

And then I hit a snag. Several, actually. The first one was that the old video controller chips are no-longer being made. Plenty on ebay, but…  Next was the keyboard interface – but PS/2 type keyboards are easy to come-by and relatively easy to use. Memory, and ROM/EEPROM… The 6502 has a unique (that I’m aware of!) way of accessing memory – it only accesses it on half a clock cycle rather than use a full clock cycle. The effect of this is that you actually need memory that’s twice as fast as you might first imagine. So for a 1Mhz 6502, you might think that memory with a 1 microsecond access time is OK, however the reality is that you need 500ns (or better) memory. The current 6502 chips from Western Design Centre run at speeds up to 14Mhz, so we’re looking at 70ns or better RAM. The issue there is that chips like RAM chips are moving more and more to using surface mount technology which I was trying to avoid. Fortunately I found some suitable memory chips – 32KB x 8bits, so I just needed 2 of them.

(And yes, I did say current up there – The 6502 is still being manufactured and you can buy them brand spanking new!)

EEPROMs were an issue – these days they’re big – much bigger than I need, both physically and capacity wise. Not real issue as you don’t need to use all their capacity, however they’re also not as fast as I’d have liked, so this means either slowing the clock during access or introducing wait-states to the 6502. This adds complexity – and more TTL IC’s, so more to go wrong.


At this point I had a look at what year I felt was right for my little SBC and settled on 1984. This was 6 years after the Apple II and 2 years after the BBC Micro. Programmable Logic Chips were in-use at that point – the BBC Micros (and others like the ZX Spectrum) used their fore-runner, the Ferranti ULA, so I felt that a PAL (actually a GAL – a reprogrammable PAL) might be appropriate.

I also looked at video, keyboard and thought about other peripherals like SPI and I2C an decided to build a system that had a separate IO processor to handle this – and I used an ATmega1284P. This generates the video, has a serial port and can interface to the 6502 using a shared memory system. This eliminated the EEPROM wait-state shenanigans as the system effectively became ROMless at that point – the ATmega poked the bootloader directly into the RAM of the 6502 and could control its reset line.

Plus modern..

The ATmega has about 50 times the transistor count of the 6502 runs faster, has more and getter IO, but sometimes you have to do something new to make something old work.

And in the best traditions of make it up as you go along, I started on breadboard – and this was the barely working prototype:

Ruby on a Breadboard

The ATmega is at the top-right. Middle of the board is 32KB of static RAM, to the left of that is the 6502 and under that is the Lattice 22v10 GAL. The LEDs are mostly for debugging – the are extra outputs from the GAL.

It was a bit unreliable – using ribbon cables (“jumper jerky”) isn’t the best as they tend to pull each other out but it did work and served to prove the concept that I was after. I ran some very small bits of 6502 code with that setup and wrote a lot of the ATmega code for the video and the shared memory interface to the 6502.

Although that worked, it wasn’t really reliable enough to enable me to do much with it, so the next step was to make something a little more sturdy and for that I went back to an old favourite of mine – stripboard, and more on that design in the next post…

Back to the Ruby 6502 index page

The WozMon

In 1976, Steve Wozniac wrote what’s commonly known simply as Wozmon.

Wozmon is a machine-code monitor program and for those who may not be aware, this is essentially code that starts to run in a microprocessor at initial power on. Lets be more specific, an older microprocessor system, not usually your modern Intel, AMD or ARM based system. We’re taking the old 8-bit micros here, and I’m specifically referring to the original 6502.

The monitor would let you examine and change memory. Some even had built-in disassemblers, debuggers and a whole host of tools to help you get going in a new machine. They would include subroutines that could be used by other programs, these basic input output subroutines, or BIOS as it came to be known were essential for bigger programs as it took that burden away from the programmer and helped to make programs run on different systems, or at least easier to port from one system to another.

Before the monitor there were LEDs (or even older; filament bulbs) and switches. Before that? you might have ended up soldering diodes to circuit boards. I kid you not.

Wozmon may not have been the first, but it was compact. Wozniac had 250 bytes to squeeze it all into. (6 bytes were reserved for the reset and interrupt vectors) ROMs and the means to program them were expensive in those days. He ended up with 2 bytes spare.

By todays standards Wozmon was crude. But it was also compact – if it didn’t understand what you typed then it would just print a backslash and let you start again, but however crude it may have been, it worked and let many early programmers enter code and use the computer that Wozmon ran on – the Apple 1.

After the Apple 1, there as the Apple][ (sometimes written as II, latterly as //, but rarely 2). That had more ROM space, so Wozmon was expanded and as computer got bigger and more complex eventually (we’re into the mid 1980’s now), the monitor for the various microprocessors was replaced by nothing more than the basic input/output subroutines and the BIOS as we know it today was born, although nowadays (c2018), the BIOS is bigger and more complex than an entire Apple ][ computer.

You may be wondering why I’m writing this – well, for no reason other than this year marks my 40th year of programming the 6502, I’ve built a little 6502 computer and written a monitor for it. The monitor resembles Wozmon – mostly because I used the Apple ][ variant of it back in 1978.

More to come – details of Ruby – my 40th anniversary 6502 computer.


The Gigatron is a TTL computer and when I first heard about it, I thought that with that name, I had to get one…

The Gigatron

The Gigatron

… and there it is.

So what is it?

It’s the brainchild of Marcel van Kervinck and Walter Belgers.

In essence, it’s an 8-bit computer (I’d struggle to say “micro” here as it’s not that small!) made almost entirely from TTL chips with a few discrete components thrown in for good measure. It has a reduced instruction set of just 8 instructions with 32KB of RAM and 64KW EEPROM (16-bit wide EEPROM, so 128KW of EEPROM in total) It comes in a very nice case with a game controller and sockets for power in (mini USB), 3.5mm audio jack output and VGA for colour video output. The game controller is a clone of the Nintendo Famicom controller.


TTL is Transistor-Transistor Logic. Essentially it’s a way to encapsulate many transistors into a single integrated circuit, or IC. The ICs are typically digital in nature and featured simple logic gates – AND/OR, etc. to more complex shift registers, latches and so on. The technology dates back to the early 1960’s but wasn’t standardised until Texas Instruments created their line of 74xx ICs in about 1966. Until then computers and other digital devices were made up from discrete components, transistors, diodes, resistors, etc. on large (or small) circuit boards. These new ICs allowed increased transistor density and lower power consumption.

The idea of making a computer of out of TTL is not new. DEC created the PDP-8/i computer in 1968 which was a successor to their PDP-8 computer (c1965) and was the first one they made almost entirely from TTL chips – the ‘i’ in PDP-8/i means Integrated Circuits.

And there are similarities between the Gigatron and the PDP-8 – I suspect Marcel and Walter are both PDP-8 enthusiasts in some way…

It’s a kit

Gigatron is supplied as a self-solder kit. I think it’s the biggest self-solder kit I’ve had in a very long time – if ever. However the (real, printed and bound) instruction manual is one of the best I’ve seen. Colour pictures and step by step instructions. Being a veteran solderer I assembled it in a slightly different order, but the supplied instructions do take you through some trouble shooting and tests along the way which for novice solderers will add confidence when you get going. Doing the multimeter checks and early power checks will be very worthwhile if you’re relatively new to this. (Also if you’re not – I’m a real fan of incremental testing rather than build a huge thing then try to debug it) The only thing I’d add to the instructions would be to get yourself a big lump of blue tack or something similar – I prefer using that to keep components on the PCB when turning it over to bending the leads out.

No Sockets?

The RAM and EEPROM chips are in sockets – the TTL chips not. Is this a big deal? I don’t think so. The anti-static foam sheet that all the chips come on is populated with the chips in the right locations and the right way round, however you should still check before putting a chip in and soldering it in. It is possible to de-solder and extract a TTL chip, but my experience is that it’s easier to simply snip the legs off, take the pins out one by one then solder in a new one – which is fine if you have spares, so just take your time and you shouldn’t have any issues.


The native instruction set is very RISC at just 8 instructions with a handful of addressing modes. It’s an 8-bit Harvard architecture machine with effectively five separate data paths, one of which is the ROM where the code lives and that’s a 16-bit data bus. Instructions are effectively 16-bits wide – one byte is the instruction (which is 3 bits for the instruction, 3 for the addressing mode and 2 to select the bus), and one byte for data. It features a single 8-bit accumulator and a 16-bit memory pointer register accessible as 2 x 8-bit registers, X and Y which allows for different addressing modes. The 16-bit instruction bus allows for operations like “load accumulator with an 8-bit value” to execute in one cycle. However for the most part you are not expected to write in this low-level code, but to write in code which they have designed as a virtual 16-bit CPU (vCPU) which comes with a high (ish) level compiled language to go with it.

So here you have a 3-level system – the native code which is almost like a microcode in todays terms, the virtual CPU (vCPU) interpreter which is written in the native code and the high level language; GCL – Gigatron Control Language. GCL runs from RAM.

Programming directly in the native micro code is possible, however, it’s a tightly constrained system as it is not just the code for your programs, but is also controlling the video and audio output and other IO such as the game controller and “blinkenlights”. This all happens behind the scenes as it were when you write programs in the vCPU or GCL codes. Also – the native micro code can only run from ROM.

Video Output

The video is a VGA standard output with a 6.25Mhz dot-clock. 6.25 Mhz is also the clock frequency of the processor, so the video is generated one instruction at a time which fetches a byte from RAM and sends it to the 6-bit colour DAC to generate 64 colours per pixel at a QQVGA resolution of 160 pixels by 120 lines. It’s a simple RGB encoding; 2 bits of red, 2 green and 2 blue. There are some hardware optimisations/trade-offs here though – essentially it’s an 8-bit output latch which uses 6 bits for the colour and 2 bits for the H and V sync signals. To use all 8 bits for colour would require an additional output latch to drive the video sync signals.

Generating that in software uses almost all the processing power of the cpu, but there are free cycles at the end of each line during the horizontal sync period and at the end of each frame during the vertical sync period, however to make things a little easier for user programs the system skips every 4th line and that time is used to run GCL programs. You can turn off the line skipping though – the system still works, just slower.

The video system is somewhat wasteful of memory – you lose 2 bits per byte and to optimise the lines, they are arranged in RAM as 120 lines of 160 bytes each, however hose lines are aligned to a 256 byte boundary, leaving 96 bytes per line unused. It is possible to load code or data there though.

Sound and Blinkenlights

The sound system is again driven by software. There is an 8-bit latch which is split into 2 x 4-bit outputs. 4 bits for the LEDs and 4 for the DAC and audio output filter. Sound tables are held in RAM and the micro code mixes 4 channels of sound into one to send to the hardware at fixed intervals. I’m not sure if it’s possible to change the LEDs from user code though – will need to investigate further.


The system comes with a 128KB EEPROM which contains the micro coded “operating system” (which is the drivers for the video, game controller, LEDs and sound), the vCPU interpreter and a selection of GCL coded programs – Classic Snake game and a racer program very much like one I played on the Apple II some 40 years ago… There is also a Mandelbrot program and some static images.

Writing your own

You can write your own code for the Gigatron, however as I type this, it’s somewhat tricky. Marcel and Walter are working on some development tools to make it easier, however following some hints I have been able to write a simple GCL program and make it run on the Gigatron. This involves pretending to write a ROM module, building the ROM, extracting the vCPU bytes for your module into a C language program – burning this program into an Arduino which is connected to the game port, then booting the Arduino – it then pretends to be a game controller to the Gigatron, enters the Loader application, then uses the game port to send the bytes of your program into the RAM of the Gigatron then starts it going. It’s not an easy process, but usable for now.

My first Gigatron program is simply a screen clear:

  Gigatron code to do some simple screen clearing
  -Gordon Henderson <>


0 c=


{ Clear line at a time, top to bottom }

$0800 p= q=

120 l=

  160 w=

    c q.
    q 1+ q=
    w 1- w=

  256 p+ p= q=

  l 1- l=

c 1+ c=

{ clear column at a time, left to right }

$0800 p= q=

160 w=

    c q.
    $100 q+ q=

  p 1+ p= q=

  w 1- w=

c 1+ c=


So there you have it. Right now it’s a very nice toy, if a little expensive at €160 (Currently €149.50 for early adopters) It does have a lot of potential though especially if, like me, you like esoteric little systems.

If you want to know more, hop over to their site:

Raspberry Pi Weather Station. Software 4 – The Hard Bits

I said I’d leave the hard parts to last – well, even with these done, we’re not done yet, but it’s time to tackle the rain fall and wind speed sensors. These are slightly different from the others in that they are not external “high level” devices to read, but are digital inputs directly to the Pi so we need to write the code on the Pi to make them work. Both these inputs can trigger at any time, so we could poll for them, but if the Pi is busy then we might miss the pulse, so we will use a software interrupt mechanism to read them.

In a traditional microcontroller, e.g. PIC or ATmega, an interrupt input would cause the CPU to stop what it’s doing then execute your code with a bare minimal of delay then carry on what it was doing before.

The Raspberry Pi runs a multi-user, multi-tasking operating system, and if you want to do this on the Pi, then you need to write your own code which runs inside the Linux kernel. This is considered hard. So there is another way. The Linux kernel allows us to mimic this at the user-level by implementing a virtual file system, then posting a file-changed update in response to a GPIO pin changing state. This is no-where near as fast as code running inside the kernel, however for most cases it’s usable. This is what we’ll use here.

wiringPi provides a nice easy way to wrap this up for you. You simply specify the pin number, the trigger condition (a pin going from high to low, low to high or both) and a function to call when this event happens. When the trigger happens, Linux detect it, puts your main program to sleep, executes the named function, then wakes up your main program again.

(If you are lucky to be running this on a multi-core system, then your main program may not be put to sleep, but carry on running on one core with the called function running in parallel on another core)

This seems easy, and it is, but there are a few pitfalls. One is variable handling. The C compiler is very clever and it can keep track of variables – and if you had a loop, reading a variable, with nothing else in that loop changing it – well, it might be treated as a constant variable, after all, it never changes, so … So your program may produce the wrong result if this variable is changed in some sort of interrupt code that the compiler doesn’t know about. To get round this we need to use the C keyword volatile This tells the compiler that it might change elsewhere, so produces the appropriate code to handle it.

There are other issues to do with accessing variables in concurrent or parallel running threads/programs, but that’s left for an exercise for another time…

Back to the rain gauge… It turned out to be relatively simple to write the code in the end. The function that gets called when the pin interrupt is triggered is only a few lines long:

 * rainDrop:
 *	"ISR" routine to count each tip of the rain gauge sea saw.
 *	The debouce strategy is to set a timer and if we get called again before
 *	the time has expired, then we reset the timer - so rather than just wait
 *	a fixed time, we need to see /at least/ the time period without any
 *	triggers before we count the next one.

void rainDrop (void)
  static struct timeval lastTime = { 0, 0 } ;

  struct timeval now, interval ;

// See if we're being called inside our debounce time

 gettimeofday (&now, NULL) ;
 timersub (&now, &lastTime, &interval) ;

 if (interval.tv_usec < (DEBOUNCE_TIME * 1000)) // Inside?
   lastTime = now ;                             // Reset for another period
   pthread_mutex_lock (&rainDropLock) ;
     ++rainDrops ;
   pthread_mutex_unlock (&rainDropLock) ;

 The rainDrops variable here is a global declared with the volatile keyword. It’s used elsewhere in the file, but most of the test is handling debounce. Mechanical switches bounce and reed relays are no exception. Bounces can cause false readings, so we need to cater for them.

The rain fall and wind speed functions need a separate initialiser function: Here is the rain fall one…

void startRain (void)
  rainDrops = 0 ;
  pullUpDnControl (RAIN, PUD_UP) ;
  wiringPiISR (RAIN, INT_EDGE_FALLING, rainDrop) ;

And at this point I realised the “pins.h” file had been good so-far, but I felt it could be better, so I renamed it to setup.h and created a separate setup.h file with all the sensor initialisations in it. This meant editing all the files to change pins.h to setup.h, changing the Makefile and of-course testing it.

The setup.c file contains one function to setup all the hardware which right now looks like:

void setupHardware (void)
  wiringPiSetup () ;

  mcp3422Setup (ADC_PIN_BASE, 0x69, 0,0) ; // To-Do: Check error codes...
  bmp180Setup  (BMP180_PIN_BASE) ;
  ds18b20Setup (GROUND_T_PIN_BASE, "0000053af458") ;
  htu21dSetup  (HTU21D_PIN_BASE) ;

  startRain      () ;
  startWindSpeed () ;

Note that the DS18B20 setup has the sensor serial number hard-coded into it. That’s telling me that maybe its better off somewhere else. Not sure where right now, but I’ll probably think of something.

At this point, I’ve tested all the hardware. The rain and wind direction sensors appear to be working – they’re just returning simple counts right now – will convert that into real numbers shortly, but it’s almost all there now.

I’ve been using this site for some thoughts about presentation:

These folks are not that far from me (about 15 miles away) although I’ve never popped in to say hello… They also have a nice webcam on Dartmoor: which is nice to look at some days…

Anyway, one thing on that site is a dew-point gauge – this is not a real sensor but it’s a calculated value based on the relative humidity and temperature – so I implemented a little file with the calculation in it, in the same way other sensor files are implemented in the system. I did the same for a wind chill feature too. These values can be computed at any time from the existing data, but I think it’s just as easy to do it here.

At this point, I’m making plans to move it outside… I’ve identified a location near the house. It’s not going to be in an optimal location whatsoever – the wind speed and direction will be wildly wild but it’ll do for a demonstration. I’ve installed a a Wi-Fi dongle and re-adjusted the network settings (editing /etc/network/interfaces – your setup may vary) to give me a static IP address on my Wi-Fi LAN) and tested them. It’s outside, but in-range of one of my access points, so that’ll be fine and all I need to do is run power to it. The prototype I have has a separate UBEC (Universal Battery Eliminator Circuit – popular in radio control setups to provide a 5v supply off the main power battery which may be 18v or higher) so I’ll wire that in and test it. Dave supplied me with a 24v power supply – I’ll have to extend the lead on it, but that’s just a 10 minute job with the soldering iron and some heat shrink sleeving.

Raspberry Pi Weather Station. Software 3 – Getting there

At this point I have a little “main” program that sits in a loop reading most of the sensors and simply printing them to the screen. I’ve created an individual little file for each sensor and given them names – like getHumidity() ; rather than something a little lower level – even if it’s just a one-line wrapper in many cases – e.g. this is the core humidity.c file:

#include <wiringPi.h>

#include "pins.h"
#include "humidity.h"

 * getHumidity:
 *	Return the relative humidity reading - result * 10

int getHumidity (void)
  return analogRead (HTU21D_HUMIDITY) ;

 * getTemperature2:
 *	Return the temperature read from the humidity sensor

int getTemperature2 (void)
  return analogRead (HTU21D_TEMP) ;

the humidity.h file looks like:

// Function prototypes 

extern int getHumidity (void) ;
extern int getTemperature2 (void) ;

which is just definitions (prototypes) for the functions so they can be used elsewhere. The compilier will check the usage against the definitions here and tell you if there are any mis-matches.

I’ve cracked on and created little functions/files for most of the sensors now – even though some are just 1-line calls, they allow the main program to read a little better, so we can easily change the main program using readable words than sensor names. e.g. the ground temperature probe is literally:

int getTemperature0 (void)
  return analogRead (GROUND_T) ;

The main program now looks like:

#include "airq.h"
#include "groundT.h"
#include "windDirection.h"
#include "pressure.h"
#include "humidity.h"

int main (void)
 wiringPiSetup () ;
 mcp3422Setup (ADC_PIN_BASE, 0x69, 0,0) ;
 bmp180Setup (BMP180_PIN_BASE) ;
 ds18b20Setup (GROUND_T_PIN_BASE, "0000053af458") ;
 htu21dSetup (HTU21D_PIN_BASE) ;

 for (;;)
   printf ("%4d, %10s", getAirQuality(), directions [getWindDirection ()]) ;
   printf (", %4d", getTemperature0 ()) ;
   printf (", %4d, %4d", getAirPressure (), getTemperature1 ()) ;
   printf (", %4d, %4d", getHumidity (), getTemperature2 ()) ;

   printf ("\n") ;

So nothing desperately difficult there. It’s printing nothing more than the values separated by commas – an example output:

 40, East, 131, 10177, 156, 511, 160
 40, East, 131, 10178, 156, 511, 160
 39, East, 131, 10178, 156, 512, 160
 40, East, 131, 10178, 156, 512, 160
 40, East, 131, 10177, 156, 512, 160
 40, East, 131, 10177, 156, 512, 160
 39, East, 131, 10178, 156, 512, 160

The 3 temperature sensors are a bit all over the place this morning – 13.1, 15.6 and 16.0, however it’s really just sitting in a pile on the floor – the ground temperature probe is somewhat distant from the others.

I’ve also noticed that reading the 1-wire sensor takes almost a second. Weird.

However – as I mentioned earlier, I’ve left the hard parts to last. The wind speed and rainfall sensors… Now it’s time to code them

Raspberry Pi Weather Station. Software 2: Make

Now it’s a matter of plumbing it all together. I have created a Makefile – by copying an existing one and changing it (I used the one from the gpio program because it’s similar) then I took the weather.c program and split it out into 2 files – one is still called weather.c and is the “main” program for the system, the other is now windDirection.c – and from this I created windDirection.h. The .h file has the function prototype and some data in it.

I also needed to create a new header file; pins.h. This will contain all the pin definitions for the sensors in the system.

Once that was running, I did the same for the air quality program – I deleted it’s main() section and created airq.h from it and integrated it into the Makefile.

A word on Makefiles before you dive in and start editing them yourself. This is what mine currently looks like:

# Makefile:
#	Raspberry Pi weather station project

ifneq ($V,1)
Q ?= @

#DEBUG	= -g -O0
CC	= gcc
CFLAGS	= $(DEBUG) -Wall -Wextra -Winline -pipe

LIBS    = -lwiringPi -lm

SRC	=	weather.c		\
		windDirection.c airq.c

OBJ	=	$(SRC:.c=.o)

all:		weather

weather:	$(OBJ)
	$Q echo [Link]
	$Q $(CC) -o $@ $(OBJ) $(LDFLAGS) $(LIBS)

	$Q echo [Compile] $<
	$Q $(CC) -c $(CFLAGS) $< -o $@

.PHONY:	clean
	$Q echo "[Clean]"
	$Q rm -f $(OBJ) weather *~ core tags *.bak

.PHONY:	tags
tags:	$(SRC)
	$Q echo [ctags]
	$Q ctags $(SRC)

.PHONY:	depend
	makedepend -Y $(SRC)


weather.o: pins.h windDirection.h
windDirection.o: pins.h windDirection.h
airq.o: pins.h airq.h

There is one very important thing to note when editing Makefiles: The lines that do not start on the left-hand edge start with a TAB character. That’s a proper, hard TAB and not 8 spaces. If you use spaces then it will all go horribly wrong. Trust me on this.

Makefiles were developed in the days when processors were slow (compared to today), disks were small (compared to today) and people wanted to make it easy for themselves. So in a Makefile, a line that starts with a TAB in deemed to be a rule continuation line. The time when this will trip you up is when you copy & paste a Makefile. If you ever do that, then double-check that its got the right stuff in it before trying to run it.

A quick explanation of what this makefile does – any how…

The lines near the top define variables which can be used later. e.g. DEBUG  = -O2. This lets me quickly change a few parameters. Note the CFLAGS containing -Wall and -Wextra. These are going to give me lots of whinges from the compiler which will pick out some silly mistakes.

Note the SRC line. It’s continued over 2 lines using a backslash.

Next is the OBJ line. It’s starting to become weird here, however what that line is saying is OBJ will contain what’s in SRC but change the .c into .o. We are going to compile the files, one at a time into object files which have a .o suffix, then we’ll link them together into an executable program. That way when we change one file, only that file needs compiling and not every file. (Although there are exceptions – later!)

Now we have lines which have a target followed by a dependency rule. The first target is all. This really refers to a file – which doesn’t exist, so it checks the dependency. This is weather, so all depends on weather. (which is also a file).

Carrying on, weather depends on OBJ – or all the .o files in OBJ – so if one of these has changed, then we re-do the weather rule – which is the final linking stage.

Under that, there is a target .c.o: This says that every .o file depends on a .c file, so if the .c file changes, then make the .o file. The commands under there run the compiler on that file.

The make system works recursively – it descends the file looking for things that depend on other things then performs the actions to satisfy the dependency.

Further down there are some fake (or phony) targets. These allow us to perform actions – e.g. make clean makes the clean target which in this case removes unwanted files.

The bottom part is computer generated – that’s why it says DO NOT DELETE. It’s generated by the makedepend script – called when I type make depend This goes through all the C source files, looking for the headers that they depend on. So if I change a header file, then it re-compiles the file that includes that header. Note the pins.h file – that is included in all files, so if I change that, then all files will be re-compiled…

Here are some examples:

$ make clean

This runs the clean rule and removes all the compiled stuff.

$ make
[Compile] weather.c
[Compile] windDirection.c
[Compile] airq.c

This compiles everything – needed after the clean.

$ touch airq.c
$ make
[Compile] airq.c

Here I touched (which updates the time-stamp) the airq.c file, then ran make. It compiled that file only, then did the link stage.

$ touch pins.h
$ make
[Compile] weather.c
[Compile] windDirection.c
[Compile] airq.c

and here I touched the pins.h file – which everything depends on, so it compiled the lot. I’ll crack on and start coding up little routines to sanitise the sensors now and see where I get…