PIC - PIC Is a Compiler for 8-bit PIC micro controllers
A native compiler for 8-bit PIC micro controllers written in Common Lisp. The host language is a pretty small subset of ML-like language and the target language is 8-bit PIC micro controller assembly. Common Lisp is the compiler language.
Following is an example of LED blinking with PIC12F683 micro controller.
init function is one of the compiler's special functions, where the micro controller's SFR(Special Function Registers) are initialized. Following
main function is also the compiler's special function and program's main routine is executed here.
mdelay1 function and
mdelay macro are for delaying. Note that since 8-bit PIC handles only 8-bit unsigned integers, nested loops are needed for delaying more than 255 msec (950 msec here).
loop are predefined macros for the compiler.
(defpic init () (progn (setreg :gpio #x0) ; clera GP0-5 (setreg :cmcon0 #x07) ; disable comparator (setbank1) ; switch to bank 1 (setreg :trisio #x08) ; only GP3 is outputinputmode (setreg :ansel #x00) ; disable analog IO (setreg :ioc #x00) ; disable interruption (setbank0) ; switch to bank 0 (setreg :intcon #x00))) ; disable interruption (defpic main () (progn (setreg :gpio #x20) ; set GP5 to high (mdelay 50) ; delay for 50 msec (setreg :gpio #x00) ; set GP5 to low (mdelay 950) ; delay for 950 msec (main))) ; repeat (defpic mdelay1 () (loop #xf8 ; 0xF8 is a magic number to delay 0)) ; for 1 msec (defpicmacro mdelay (n) (unless (<= 0 n 65535) (error "The value ~S is invalid." n)) (multiple-value-bind (q r) (truncate n 256) (if (= q 0) `(loop ,r (mdelay1)) `(loop ,q (loop ,r (mdelay1))))))
pic-compile function compiles and outputs the complete assembly for the PIC functions to standard output. The output assembly is expected to be assembled with Microchip's MPASM assembler.
PIC> (pic-compile) INCLUDE"p12f683.inc" list p=12f683 __CONFIG _CP_OFF & _CPD_OFF & _WDT_OFF & _BOD_ON & _IESO_OFF& _PWRTE_ON & _INTOSCIO & _MCLRE_OFF CBLOCK 020h L0,L1,L2,L3,L4,L5,L6,L7 ; local registers I0,I1,I2,I3,I4,I5,I6,I7 ; input registers NULL ; null registers SP,STMP,STK ; stack registers ENDC ORG 0 GOTO MAIN ... END ; No value
Since PIC is not available on Quicklisp distribution yet, please use its local-projects feature.
$ ~/quicklisp/local-projects $ git clone git://github.com/takagi/pic.git
(ql:quickload :pic) from
REPL to load it. I will request PIC to Quicklisp soon.
DEFPIC name arguments expression
Defines a PIC function. At least,
main special function must be defined.
pic-disassemble shows the compiled assembly of a PIC function.
DEFPICMACRO name arguments form
Defines a PIC macro.
pic-macroexpand returns an expansion for a PIC macro form.
Compiles and outputs the complete assembly for PIC functions defined with
defpic macro to
*standard-output*. The output assembly is expected to be assembled with Microchip's MPASM assembler.
Shows the compiled assembly of a function specified with
[Function] pic-macroexpand, pic-macroexpand1
PIC-MACROEXPAND form => expansion, expanded-p PIC-MACROEXPAND1 form => expansion, expanded-p
Returns a macro expansion of
form is a macro form, then
pic-macroexpand1 expands the macro form call once.
pic-macroexpand repeatedly expands
form until it is no longer a macro form. If
form is a macro form, then the
expansion is a macro expansion and
t. Otherwise, the
expansion is the given
Clears all defined PIC functions. PIC macros are not cleared, which individually do not affect compiled assembly.
The compiler has only unsigned 8-bit integer as its data structure.
The compiler has the following syntax.
- arithmetic operations
- conditional branches
- variable bindings
- variable reference
- local function definitions
- function applications
- writing to registers
Literal for 8-bit unsigned integers is only allowed for now.
(- 2 1)
Subtraction for two 8-bit unsigned integers is only allowed for now.
(if (= x 0) 42 0)
Equality testing for 8-bit unsigned integers is only allowed for now.
Variable bindings and its reference
(let ((x 42)) (let ((y 1)) (- x y)))
Binds an expression to a variable and reference it. Only one variable is bound for each
let form for now.
Local function definitions
(let ((foo (x) (+ x 42))) (foo 100))
Locally defines a function. It can be called recursively. Making closures and having free variables are not allowed for now.
Calls a function which is defined with
defpic macro or a local function definition. Tail calls are compiled into
GOTO instruction, not
(loop 42 (do-something))
Releatedly executes the body part for the specified times.
Writing to registers
(setreg :gpio #x20)
Writes a value into the micro controller's SFR (Special Function Registers). This is an only syntax that causes side effects.
The compiler has the macro feature as well as Common Lisp does. Its macros are defined with
defpicmacro Common Lisp macro. The following PIC macros are predefined.
Sequentially evaluates the given expressions. Actually they are expanded into a series of let bindings, where temporal assignments should be removed properly in optimization.
(progn (setreg :gpio #x00) (setreg :gpio #x01) (setreg :gpio #x02) (setreg :gpio #x04)) ==> (let ((tmp (setreg :gpio #x00))) (let ((tmp (setreg :gpio #x01))) (let ((tmp (setreg :gpio #x02))) (setreg :gpio #x03))))
Consumes a instruction cycle.
(nop) ==> (setreg :null #x00)
Sets the current bank to bank 0.
(setbank0) ==> (setreg :status #x00)
Sets the current bank to bank 1.
(setbank1) ==> (setreg :status #x20)
The compiler has the following special functions. Required to be defined is only
intr are optional.
Just after the micro controller is powered,
init special function is called. Then,
main special function is called soon after
init special function has returned.
intr special function is called when an interruption occurs.
init is called soon after the micro controller is powered. It must have no arguments. Expected is that some initializing operations are done in
init special function.
(defpic init () (setbank1) (setreg :trisio #x08) ; set pin 4 only to output mode (setbank0) (setreg :gpio #x00)) ; set GPIO to low
main is called next to
init special function. It must have no arguments. Expected is that main operations are done in
main special function. Often it ends with a recursive call to itself to make infinite loop.
(defpic main () (do-main-operations) (main)) ; call itself to repeat the main operations
intr is called when an interruption occurs. It must have no arguments. Expected is that some operations to accept interruptions are done in
intr special function. It naturally returns with
RETFIE instruction to the address where the interruption has occured.
(defpic intr () (do-some-interruption-operations))
The host language is a pretty small subset of ML-like language and the target language is 8-bit PIC micro controller assembly. Common Lisp is the compiler language. Overall design of the compiler is based on MinCaml.
The compiler uses the following 'pseudo-registers' allocated in a particular part in the data memory for function calling.
- input pseudo-registers
- local pseudo-registers
On calling a function, its parameters are stored to be passed in the input pseudo-registers. Return values are stored in W register. Functions can use the local pseudo-registers freely for themselves. To avoid destroying the values in a caller function's 'alive' local pseudo-registers, they are saved in the software stack before calling. After returning from the callee function, they are restored. Here 'alive' means the registers are used after returning from the callee function. About the software stack, see the next section.
The compiler uses a software stack for saving the values in local pseudo-registers. The term 'software stack' means distinguishing it from the micro controller's hardware stack. It begins from the address
STK upwords and the stack pointer is stored in the address
SP. For pushing a content in W register on top of the stack,
_PUSH_STACK assembler macro does the work. Conversely,
_POP_STACK assembler macro pops back a value on top of the stack into W register. Currently, saving values in local pseudo-registers is the only usage of the software stack.
Why no closures?
To adopt closures, calling to indirect address is required. However 8-bit PIC assembly's
CALL instruction accepts only immediates for its destination address. Although calling to indirect address is possible with writing an address directly into the program counter, it is accompanied by the following instricates:
- managing not only
- calculating the return address
- managing return address manually without the hardware stack
Because of them, closures are not adopted for now.
- Masayuki Takagi (firstname.lastname@example.org)
Copyright (c) 2015 Masayuki Takagi (email@example.com)
Licensed under the MIT License.