Torque: Programming the PIC10F200

This article is a hands-on introduction to the Torque assembler, showing how to use it to write a program for the PIC10F200 microcontroller.

The language

Torque is a lightweight meta-assembler that gives you the tools to take an instruction set specification from a datasheet and turn it into an expressive and ergonomic programming language. The Torque language is bare-bones, providing only integers, bit sequences, labels, and macro expansion, but it’s general enough to be able to lever the right bits into the right order to write a program for any processor.

The main project page has downloads, source code, and the full user manual.

The microcontroller

The PIC10F200 is the least powerful microcontroller offered by Microchip, priced around $1 USD each. It uses a 12-bit fixed-width instruction format, with 256 words of program memory, 16 bytes of general-purpose memory, and 4 input/output pins. It runs at 1 million instructions per second and draws around 350 μW while doing so.

The datasheet can be found here.

The program

The program we’ll be writing will be that enduring classic, the blinking LED. We’ll turn a pin on, wait half a second, turn the pin off, wait another half a second, and then loop.

This is enough of a task to show off the various language features without getting bogged down by algorithms and circuit design. If you want to jump to the completed program listing, click here.

Getting started

The PIC10F200 has an instruction set with 33 instructions, each encoded as a 12-bit word.

The first step will be to assemble a program containing just a single instruction in order to demonstrate the basic features of a Torque program. We’ll use the GOTO instruction, using it to jump back to the beginning of the program.

The GOTO instruction

The datasheet lists the full instruction set in a table on page 46. The column titled ‘12-Bit Opcode’ shows the sequence of bits used for each instruction. The GOTO instruction uses the 12-bit sequence 101k_kkkk_kkkk, where the bits marked k contain the address to jump to.

The GOTO instruction in the datasheet

This can be translated directly into the Torque language using a ‘word template’, a fixed-width sequence of bits into which other values can be packed. The syntax for a word template that represents the GOTO instruction jumping to address zero is:

#1010_0000_0000

This is an entire valid Torque program, assembling to a single GOTO instruction. Assemble it with the following command, where inhx32 is the Intel Hex format used by Microchip’s programming tools:

tq program.tq output.hex --format=inhx32

A better way with macros

In the previous example, the instruction is an unreadable binary sequence and the address is hard-coded to be zero. We can do a lot better.

Macros provide a way to associate a name with a fragment of code, allowing us to reuse that fragment by invoking the macro name later on in the program. We can define a macro for the GOTO instruction from before, and then invoke it to insert the instruction into the program:

%GOTO  #1010_0000_0000 ;

GOTO
GOTO

The first line defines the macro, and the following lines invoke it twice, assembling to two 12-bit words. But we’re still not quite where we want to be, we want to be able to pass any address to the instruction instead of having it hard-coded.

Passing in values

Macros don’t just have to expand to a static code fragment, they can also receive values as arguments. We can make a small change to our macro definition from before:

%GOTO:k  #101k_kkkk_kkkk ;

GOTO:1
GOTO:0

This new GOTO macro takes a single integer value as an argument, which is given the name k, and that value is packed into the k field of the word template each time the macro is invoked. Integers can be given in decimal, hexadecimal, or binary, as 29, 0x1D, or 0b11101.

Named fields in word templates work by searching for a macro with that name that expands to an integer, and then that integer is packed into the bits of the field. Arguments inside a macro definition are treated as regular macros, expanding to the value that was passed.

This program also assembles to two 12-bit words. It will jump to address 1, which is the address of the second GOTO, and then will jump to address 0, which is the address of the first GOTO.

Addressing with labels

Finally, instead of using an integer literal as an address, we can have Torque automatically calculate an address value from a label.

%GOTO:k  #101k_kkkk_kkkk ;

@one
GOTO:two
@two
GOTO:one

Labels are treated as regular macros that expand to the address of the next word in the program.

Writing the program

We now know most of the Torque language, and can build up to the full program.

Quick architecture overview

The PIC10F200 contains 24 8-bit ‘file registers’, and one 8-bit ‘working register’ (the accumulator). 16 of the file registers are general-purpose, to be used however we wish.

Most instructions will operate on any given file register (called f) and the accumulator (called W), with the result of an operation being stored back into either one depending on the state of the bit marked d. For example, the ADDWF instruction #0001_11df_ffff will add the values of the accumulator and the register f, writing the result back to f if d is set.

Configuring an output pin

We need to configure the first input-output pin (called GP0) to be an output pin, as all pins are set to be inputs by default.

We can do this with the TRIS instruction in the datasheet. This will write the contents of the accumulator to the hidden TRISGPIO register controlling the mode of each pin.

%SET-W:k   #1100_kkkk_kkkk ;  ( Write value k to W  )
%SET-GPIO  #0000_0000_0110 ;  ( Write W to TRISGPIO )

SET-W:0b1110 SET-GPIO

Here we set pin GP0 to be an output pin by clearing the lowest-order bit of the TRISGPIO register.

Toggling the pin

To turn the GP0 pin on and off, we set and clear the lowest-order bit of the GPIO register (register 0x06).

%GOTO:k       #101k_kkkk_kkkk ;  ( Jump to address k         )
%SET-BIT:f:b  #0101_bbbf_ffff ;  ( Set bit b of register f   )
%CLR-BIT:f:b  #0100_bbbf_ffff ;  ( Clear bit b of register f )

%GPIO  0x06 ;
%GP0      0 ;

@start
  SET-BIT:GPIO:GP0
  CLR-BIT:GPIO:GP0
  GOTO:start

Here we turn the pin on, then off, and then loop from the top.

Slowing down the program

Lastly, we need to slow the loop down so that the flashes are visible, otherwise the LED will flash 250,000 times a second. We can create a delay by using a long loop, using registers to store our loop counters.

The general-purpose registers start from register address 0x10. We can create macros to associate names with each of the first three general-purpose registers:

%LOOP1  0x10 ;
%LOOP2  0x11 ;
%LOOP3  0x12 ;

Conditional logic on the PIC10F200 works by skipping the next instruction if a condition is true. We can combine the DEC-SKIP instruction (called DECFSZ in the datasheet) with the GOTO instruction to decrement our loop counter and jump to the top of the loop. When the loop counter reaches zero, the DEC-SKIP instruction will skip over the GOTO, breaking us out of the loop.

%DEC-SKIP:f  #0010_111f_ffff   ;  ( Decrement register f, skip next instruction if zero )
%NEXT:f:k    DEC-SKIP:f GOTO:k ;  ( Decrement register f, jump to address k if not zero )

We need to calculate the correct value for each loop counter in order to delay for exactly half a second, or 500,000μs. Each instruction takes 1μs to execute, or 2μs if a jump occurs. Our NEXT macro takes 3μs when looping back to the top, and 2μs when breaking.

The following example will set LOOP1 to zero and then repeatedly decrement it 256 times until it reaches zero again, taking a total duration of 769μs:

%SET-W:k    #1100_kkkk_kkkk ;  ( Write value k to W          )
%SET-f:reg  #0000_001f_ffff ;  ( Write W to register f       )
%SET:f:k    SET-W:k SET-f:f ;  ( Write value k to register f )

SET:LOOP1:0
@loop
  NEXT:LOOP1:loop

We can extend the duration further if we nest multiple loops within one other, using maths to determine the correct value for each loop counter in order to take a total duration of exactly 500,000μs:

@delay
  SET:LOOP1:127
  @loop1
    SET:LOOP2:207
    @loop2
      SET:LOOP3:5
      @loop3
        NEXT:LOOP3:loop3
      NEXT:LOOP2:loop2
    NEXT:LOOP1:loop1

Bringing it all together

The completed Torque program is below. The CALL and RETURN instructions have been introduced to allow us to call the delay code from two places.

%SET-GPIO       #0000_0000_0110 ;  ( Write W to TRISGPIO register       )
%SET-W:k        #1100_kkkk_kkkk ;  ( Write value k to W                 )
%SET-f:f        #0000_001f_ffff ;  ( Write W to register f              )
%SET:f:k        SET-W:k SET-f:f ;  ( Write value k to register f        )
%GOTO:k         #101k_kkkk_kkkk ;  ( Jump to address k                  )
%CALL:k         #1001_kkkk_kkkk ;  ( Call the subroutine at address k   )
%RETURN         #1000_0000_0000 ;  ( Return from the current subroutine )
%SET-BIT:f:b    #0101_bbbf_ffff ;  ( Set bit b of register f            )
%CLR-BIT:f:b    #0100_bbbf_ffff ;  ( Clear bit b of register f          )
%DEC-SKIP:f     #0010_111f_ffff ;  ( Decrement f, skip forward if zero  )
%NEXT:f:k     DEC-SKIP:f GOTO:k ;  ( Decrement f, jump to k if not zero )

%GPIO   0x06 ;
%GP0       0 ;
%LOOP1  0x10 ;
%LOOP2  0x11 ;
%LOOP3  0x12 ;

( Configure GP0 to be an output pin. )
SET-W:0b1110 SET-GPIO

( Blink the GP0 pin. )
@start
  SET-BIT:GPIO:GP0 CALL:delay
  CLR-BIT:GPIO:GP0 CALL:delay
  GOTO:start

( Delay for half a second. )
@delay
  SET:LOOP1:127
  @loop1
    SET:LOOP2:207
    @loop2
      SET:LOOP3:5
      @loop3
        NEXT:LOOP3:loop3
      NEXT:LOOP2:loop2
    NEXT:LOOP1:loop1
  RETURN

This can be assembled with Torque using the following command, which will output a .hex file in the Intel Hex format used by Microchip’s programming tools:

tq program.tq output.hex --format=inhx32

Further reading