SIMPLE2 - A Simple Compiler for PIC12/PIC16 chips ================================================= Version 2.02 - November 13, 2012 SIMPLE2 converts a very simple high-level-language directly into assembler instructions for the gpasm or mpasm assembler. The SIMPLE2 compiler code was converted from the original SIMPLE compiler from 2001, the biggest difference is the original SIMPLE produced Parallax instructions and required the TechTools CVASM16 assembler, whereas SIMPLE2 outputs standard Microchip instructions for modern PIC assemblers. The language itself was not changed other than adding a raw command to permit passing assembly code that starts in column 1 (the asm command always starts in column 2), and the case of names is preserved rather than converting everything to lowercase. I've used the original SIMPLE in several commercial projects (the latest was in equipment that tests assembled PCB's for proper response for a product the company I work for makes), it still works fine however it is uncertain how much longer CVASM16 and dos in general will be viable so I rewrite it as SIMPLE2 to use current PIC assemblers under Windows or Linux. SIMPLE works quite differently than other compilers in that it does not process variable and bit names or any other chip-specific features, instead the program source must include in asm or raw statements anything the assembler needs to work, and the user must use the register and bit names specified in the processor include file. Labels are passed directly to the assembly output, and there is no checking to make sure variables and label targets actually exist. This may seem limiting to programmers used to "big" compilers, but it provides an ability that no other compiler has: it can compile small chucks of code that can be directly inserted into other assembly programs. Fundamentally, SIMPLE is just another way to write machine-code programs for small PIC processors using a much more readable language - the user still must be familiar with the processor being programmed, and all the initialization steps that are needed to make an assembly program work are still needed in a SIMPLE program. SIMPLE works best with "Midrange" PICs using a 14-bit core such as the 8-pin PIC12F629 or PIC12F675, the 14-pin PIC16F684, the 18-pin PIC16F84, the 28-pin PIC16F876, or the 40-pin PIC16F877. It should also work with the tiny 6-pin PIC10F200-series parts if asm statements are used for option and tristate, and RAMBASE is used to start variables at 8 or 16 as required by the part. It can also be used with 12-bit core parts such as the old PIC16C56, but it does not generate bank select instructions and subroutine targets are limited to the first 256 words of each 512-word bank. 14-bit cores have 2048-word banks so bank selection is only needed for large programs. SIMPLE is not for PIC18 or later PICs, for PIC18 I use the SDCC C compiler. SIMPLE does not support 16-bit math - if you need some of that assembly subroutines can be used but if you need a lot of math processing then you should use another compiler or chip. SIMPLE and small PICs in general are meant for "bread and butter" processing applications where a few inputs need to be processed to control a few outputs, and not much ram and program memory is needed to do the job. I use (or can use) SIMPLE for things like... Monitoring switch inputs and setting outputs in response. Measuring analog voltages and comparing to limits to control other stuff. Controlling digital potentiometers and other peripheral chips. Timers and oscillators (although have to pay attention to the output). Replacing complex logic with a single inexpensive chip. Using a serial connection to translate to and from bit patterns. Small solar-powered autonomous robots. Usually apps like these require less than 1KW program memory and only a few bytes of ram. Most PICs also include 256 bytes to 1Kbyte of eeprom for storing configurations, and many have multiple 10-bit analog inputs that can be used to measure voltages. On-chip PIC peripherals can be used from SIMPLE by setting and reading the appropriate bits and registers, or by including "example" code in asm statements. A Simple Blink Demo ------------------- Here is a SIMPLE2 program that blinks an LED attached to GPIO bit 0 of a PIC12F675 chip (pin 7, of course using a 470 ohm or so series resistor)... ------- begin demo.sim ---------------------------------------------- rem Blink an LED attached to pin 7 of a PIC12F675 chip rem assembler-specific directives asm list p=12f675 asm radix dec asm include asm __CONFIG _WDT_OFF & _MCLRE_OFF & _PWRTE_ON & _CP_OFF & _CPD_OFF & _INTRC_OSC_NOCLKOUT asm errorlevel -302 ;suppress messages about not being in bank 0 define hiregs = STATUS.RP0 ;for banking define pindirs = 111110B ;make GPIO.0 an output define outpin = GPIO.0 ;output pin define ConstA = 11 ;delay constants (about 1hz) define ConstB = 86 define ConstC = 166 byte CounterA ;for timing delay byte CounterB byte CounterC GOTO start ;jump to actual start asm ORG 8 start: ;start of program code GPIO = 0 ;clear GPIO register INTCON = 0 ;no interrupts ADCON0 = 0 ;disable A-D T1CON = 0 ;disable timer CMCON = 7 ;disable compare bitset hiregs ;select high registers TRISIO = #pindirs ;set pin directions ANSEL = 0 ;set all analog inputs to digital PIE1 = 0 ;disable peripheral interrupts IOC = 0 ;disable interrupt on change WPU = 0 ;disable weak pullups OPTION_REG = 11001101B ;typical options bitclear hiregs blinkloop: bitset outpin gosub delay bitclear outpin gosub delay goto blinkloop delay: loop CounterC from #ConstC loop CounterB from #ConstB loop CounterA from #ConstA next CounterA next CounterB next CounterC return asm end ------- end demo.sim ------------------------------------------------ The code is written to be used with the open-source gpasm assembler (part of the gputils package), to use Microchip's MPASM assembler under Linux, change the include line to: asm include Running "simple2 demo.sim" from a command line converts the SIMPLE source into the following assembly source file... ------- begin demo.asm ---------------------------------------------- ;compiled by SIMPLE version 2.02 ;rem Blink an LED attached to pin 7 of a PIC12F675 chip ;rem assembler-specific directives list p=12f675 radix dec include __CONFIG _WDT_OFF & _MCLRE_OFF & _PWRTE_ON & _CP_OFF & _CPD_OFF & _INTRC_OSC_NOCLKOUT errorlevel -302 ;suppress messages about not being in bank 0 #define hiregs STATUS,RP0 ;for banking #define pindirs 111110B ;make GPIO.0 an output #define outpin GPIO,0 ;output pin #define ConstA 11 ;delay constants #define ConstB 86 #define ConstC 166 CounterA EQU 40 ;for timing delay CounterB EQU 41 CounterC EQU 42 GOTO start ;jump to actual start ORG 8 start ;start of program code ;GPIO = 0 ;clear GPIO register CLRF GPIO ;INTCON = 0 ;no interrupts CLRF INTCON ;ADCON0 = 0 ;disable A-D CLRF ADCON0 ;T1CON = 0 ;disable timer CLRF T1CON ;CMCON = 7 ;disable compare MOVLW 7 MOVWF CMCON ;bitset hiregs ;select high registers BSF hiregs ;TRISIO = #pindirs ;set pin directions MOVLW pindirs MOVWF TRISIO ;ANSEL = 0 ;set all analog inputs to digital CLRF ANSEL ;PIE1 = 0 ;disable peripheral interrupts CLRF PIE1 ;IOC = 0 ;disable interrupt on change CLRF IOC ;WPU = 0 ;disable weak pullups CLRF WPU ;OPTION_REG = 11001101B ;typical options MOVLW 11001101B MOVWF OPTION_REG ;bitclear hiregs BCF hiregs blinkloop ;bitset outpin BSF outpin ;gosub delay CALL delay ;bitclear outpin BCF outpin ;gosub delay CALL delay GOTO blinkloop delay ;loop CounterC from #ConstC MOVLW ConstC MOVWF CounterC local__10 ; loop CounterB from #ConstB MOVLW ConstB MOVWF CounterB local__20 ; loop CounterA from #ConstA MOVLW ConstA MOVWF CounterA local__30 ; next CounterA DECFSZ CounterA,1 GOTO local__30 ; next CounterB DECFSZ CounterB,1 GOTO local__20 ;next CounterC DECFSZ CounterC,1 GOTO local__10 ;return RETLW 0 end ------- end demo.asm ------------------------------------------------ ...and that's why I prefer SIMPLE to assembly.. CMCON = 7 is easier to understand than MOVLW 7, MOVWF CMCON. To turn into a hex file for programming into the PIC chip, run "gpasm demo.asm" from a command line. Although this simple demo doesn't do any math or use if/then/else/endif, it demonstrates the basics of how SIMPLE2 goes about converting the code... Labels are passed without the trailing ":". Variable/bit names and defines are passed straight through except for converting "." to "," to separate bytes and bits - this is done for anything that might refer to a bit including define. Byte variables are numbered numerically starting from location 40 decimal unless rambase is used to override. The assembler command "radix dec" is required to properly parse numbers. Raw decimal numbers are automatically converted to immediate form, if immediate constants are needed then specify using "#". It is ok to use # for decimal numbers to for clarity. If labels are needed for spaghetti-generation, the compiler generates labels in the form of "local__" or "ifnext__" or "ifend__" followed by a single digit nest level 1-9 followed by a number that is incremented as needed to generate unique labels. The labeling approach limits nesting to 9 levels but that has never been an issue for me... usually if a program nests that deeply it probably needs to be restructured anyway. More advanced examples ---------------------- Here is another LED blink program, but this one exercises more of the features of the SIMPLE language... ------- begin blink.sim --------------------------------------------- rem Simple code to make a PIC12F675 blink GP0 on and off rem but only if it correctly computes other stuff first (11/8/12) ;this comment not output to assembly file raw ;this text passed as-is to assembly file rem assembler-specific directives asm list p=12f675 asm radix dec asm include asm __CONFIG _WDT_OFF & _MCLRE_OFF & _PWRTE_ON & _CP_OFF & _CPD_OFF & _INTRC_OSC_NOCLKOUT asm errorlevel -302 ;suppress messages about not being in bank 0 define hiregs = STATUS.RP0 ;for banking define pindirs = 00000000B ;all outputs define outpin = GPIO.0 ;output pin define ConstA = 11 ;delay constants define ConstB = 86 define ConstC = 166 byte CounterA ;for timing delay byte CounterB byte CounterC GOTO start ;jump to actual start asm ORG 8 start: ;start of program code GPIO = 0 INTCON = 0 ADCON0 = 0 T1CON = 0 CMCON = 7 bitset hiregs ANSEL = 0 PIE1 = 0 WPU = 0 IOC = 0 OPTION_REG = 11001101B TRISIO = #pindirs bitclear hiregs rem validate stuff rem goto hang if error byte VarA byte VarB byte VarC VarA = 10 VarB = 5 VarC = VarA + VarB VarC = VarC + VarA if VarC <> 25 goto hang if VarC < 25 goto hang if VarC > 25 goto hang if VarC = VarA goto hang if VarC <= VarA goto hang if VarA >= VarC goto hang if VarC = 25 then if VarC >= 25 then if VarC <= 25 then increment VarA if VarA >= 10 then VarA = VarA - 2 if VarA <= 8 then goto hang else decrement VarA VarA = VarA + 1 VarA = VarA - 1 if VarA = 8 goto domorecomp endif endif endif endif endif goto hang domorecomp: VarA = 10 VarB = 11 if VarA >= 10 then if VarB <= 11 then if VarA > 9 then if VarB < 12 then if VarA < VarB then if VarB > VarA then goto compok endif endif endif endif endif endif goto hang compok: array ArrayD(5) ArrayD(1) = 2 ArrayD(3) = 4 ArrayD(5) = 7 VarB = ArrayD(5) if VarB <> 7 goto hang VarB = ArrayD(1) if VarB <> 2 goto hang VarA = 3 VarB = ArrayD(VarA) if VarB <> 4 goto hang rem test bit stuff byte Bits bitset Bits.0 bitcopy Bits.1 = Bits.0 bitcopy Bits.2 = not Bits.1 if not Bits.0 goto hang if not Bits.1 goto hang if Bits.2 goto hang if Bits.0 then if Bits.1 then if not Bits.2 then goto bitsok endif endif endif goto hang bitsok: rem test other stuff VarA = 10000001B shift VarA left if VarA.1 then if not VarA.0 then if not VarA.7 then bitset VarA.7 shift VarA right if not VarA.7 then if VarA.6 then goto shiftok endif endif endif endif endif goto hang shiftok: VarA = 20 VarB = 3 VarA = VarA + 2 VarA = VarA - VarB if VarA <> 19 goto hang VarA = #0xFF ;must use # for immediate VarA = not VarA if VarA <> 0 goto hang VarA = VarA or #00000101B if VarA <> #00000101B goto hang VarA = VarA and #00000110B if VarA <> #00000100B goto hang VarA = VarA xor #11111111B if VarA <> #11111011B goto hang blinkloop: bitset outpin gosub delay bitclear outpin gosub delay goto blinkloop delay: loop CounterC from #ConstC loop CounterB from #ConstB loop CounterA from #ConstA next CounterA next CounterB next CounterC return hang: goto hang asm end ------- end blink.sim ----------------------------------------------- ...and this is why I really like SIMPLE... it lets me use nested if/then/else and do simple byte comparisons and math without having to fumble around with how to express it in assembly (which is extremely error-prone). Here is some of the SIMPLE2 assembly output... ;VarC = VarA + VarB MOVF VarA,0 MOVWF VarC MOVF VarB,0 ADDWF VarC,1 ;VarC = VarC + VarA MOVF VarA,0 ADDWF VarC,1 ... ;if VarC <= VarA goto hang MOVF VarC,0 SUBWF VarA,0 BTFSC 3,0 GOTO hang ... ; if VarC <= 25 then MOVLW 25^0xFF ADDWF VarC,0 BTFSC 3,0 GOTO ifnext__30 ... ; endif ifnext__30 ... ;bitcopy Bits.2 = not Bits.1 BTFSC Bits,1 BCF Bits,2 BTFSS Bits,1 BSF Bits,2 Although the HLL is, well, simple, it almost exactly matches how I think when I am writing PIC code, and in the 11+ years since I made the original SIMPLE I haven't been inspired to change much about it, it does just about all I want a HLL PIC language to do. The things I would sort of like, like for/next, while/wend or do/loop|until are almost just as easy to specify directly using goto, comparisons and labels, and in my opinion adding these things to the compiler would be unnecessary complication that increases the chances of a compiler bug. I don't consider myself a "great" programmer (plainly evident by looking at the simple2 compiler source:-), but I do have to write critical embedded apps for chips soldered to a board, a significant bug would mean a very expensive product recall and rework - the code does not have to be efficient or satisfy anyone's ideas about what code should look like, but it absolutely must work correctly. I prefer if programs are simple enough that I can prove correctness by tracing through every possible execution path - fancy code constructs make it harder to do that. I could easily fix the 9-nest limit but I'm inclined not to... for one thing it keeps the label format reasonably simple and short, but also to force me not to nest that deeply which can cause bugs due to all the extra code paths it causes. It is very easy to forget a needed else if the structure is too complex. The blink.sim program checks a lot of the language but it does not cover all possible if comparison forms, so I wrote another test program... ------- begin testcomp.sim ------------------------------------------ rem Test the simple2 compiler's comparisons 11/9/12 rem bitset bitclear bitcopy shift left/right loop if/then/else/endif and rem other common operations already tested in the blink.sim example. rem This program focuses on attempting to test all forms of comparisons... rem if var1 = var2 goto label rem if var1 < var2 goto label rem if var1 > var2 goto label rem if var1 <= var2 goto label rem if var1 >= var2 goto label rem if var1 <> var2 goto label rem if var1 = number goto label rem if var1 < number goto label rem if var1 > number goto label rem if var1 <= number goto label rem if var1 >= number goto label rem if var1 <> number goto label rem if var1 = var2 then rem if var1 < var2 then rem if var1 > var2 then rem if var1 <= var2 then rem if var1 >= var2 then rem if var1 <> var2 then rem if var1 = number then rem if var1 < number then rem if var1 > number then rem if var1 <= number then rem if var1 >= number then rem if var1 <> number then rem ...each test done with enough inputs to show correctness rem If pass then GPIO bit 0 is set high, otherwise low if error. rem Note... 100% coverage not guaranteed, writing test code is not easy... rem assembler-specific directives asm list p=12f675 asm radix dec asm include asm __CONFIG _WDT_OFF & _MCLRE_OFF & _PWRTE_ON & _CP_OFF & _CPD_OFF & _INTRC_OSC_NOCLKOUT asm errorlevel -302 ;suppress messages about not being in bank 0 define hiregs = STATUS.RP0 ;for banking define pindirs = 00000000B ;all outputs define outpin = GPIO.0 ;output pin rambase 32 byte var1 byte var2 GOTO start ;jump to actual start asm ORG 8 start: ;start of program code GPIO = 0 INTCON = 0 ADCON0 = 0 T1CON = 0 CMCON = 7 bitset hiregs ANSEL = 0 PIE1 = 0 WPU = 0 IOC = 0 OPTION_REG = 11001101B TRISIO = #pindirs bitclear hiregs rem if var1 = var2 goto label var1 = 20 var2 = 21 if var1 = var2 goto hang var1 = 20 var2 = 20 if var1 = var2 goto veqok goto hang veqok: rem if var1 < var2 goto label var1 = 30 var2 = 30 if var1 < var2 goto hang var1 = 31 var2 = 30 if var1 < var2 goto hang var1 = 30 var2 = 31 if var1 < var2 goto vltok goto hang vltok: rem if var1 > var2 goto label var1 = 30 var2 = 30 if var1 > var2 goto hang var1 = 30 var2 = 31 if var1 > var2 goto hang var1 = 31 var2 = 30 if var1 > var2 goto vgtok goto hang vgtok: rem if var1 <= var2 goto label var1 = 51 var2 = 50 if var1 <= var2 goto hang var1 = 50 var2 = 50 if var1 <= var2 goto vlteok1 goto hang vlteok1: var1 = 49 var2 = 50 if var1 <= var2 goto vlteok2 goto hang vlteok2: rem if var1 >= var2 goto label var1 = 50 var2 = 51 if var1 >= var2 goto hang var1 = 50 var2 = 50 if var1 >= var2 goto vgteok1 goto hang vgteok1: var1 = 50 var2 = 49 if var1 >= var2 goto vgteok2 goto hang vgteok2: rem if var1 <> var2 goto label var1 = 50 var2 = 50 if var1 <> var2 goto hang var1 = 50 var2 = 51 if var1 <> var2 goto vneok goto hang vneok: rem if var1 = number goto label var1 = 30 if var1 = 31 goto hang var1 = 30 if var1 = 29 goto hang var1 = 30 if var1 = 30 goto ceqok goto hang ceqok: rem if var1 < number goto label var1 = 40 if var1 < 40 goto hang var1 = 40 if var1 < 39 goto hang var1 = 40 if var1 < 41 goto cltok goto hang cltok: rem if var1 > number goto label var1 = 40 if var1 > 40 goto hang var1 = 40 if var1 > 41 goto hang var1 = 40 if var1 > 39 goto cgtok goto hang cgtok: rem if var1 <= number goto label var1 = 20 if var1 <= 19 goto hang var1 = 40 if var1 <= 40 goto clteok1 goto hang clteok1: var1 = 39 if var1 <= 40 goto clteok2 goto hang clteok2: rem if var1 >= number goto label var1 = 20 if var1 >= 21 goto hang var1 = 40 if var1 >= 40 goto cgteok1 goto hang cgteok1: var1 = 39 if var1 >= 38 goto cgteok2 goto hang cgteok2: rem if var1 <> number goto label var1 = 50 if var1 <> 50 goto hang var1 = 49 if var1 <> 50 goto cneok goto hang cneok: rem do if/then checks... for these don't reset the variables each time rem if var1 = var2 then var1 = 50 var2 = 51 if var1 = var2 then goto hang else var2 = 50 if var1 = var2 then goto vteqok endif endif goto hang vteqok: rem if var1 < var2 then var1 = 60 var2 = 60 if var1 < var2 then goto hang else var1 = 61 if var1 < var2 then goto hang else var1 = 59 if var1 < var2 then goto vtltok endif endif endif goto hang vtltok: rem if var1 > var2 then var1 = 60 var2 = 60 if var1 > var2 then goto hang else var1 = 59 if var1 > var2 then goto hang else var1 = 61 if var1 > var2 then goto vtgtok endif endif endif goto hang vtgtok: rem if var1 <= var2 then var1 = 61 var2 = 60 if var1 <= var2 then goto hang else var1 = 60 if var1 <= var2 then var1 = 59 if var1 <= var2 then goto vtlteok endif endif endif goto hang vtlteok: rem if var1 >= var2 then var1 = 60 var2 = 61 if var1 >= var2 then goto hang else var1 = 61 if var1 >= var2 then var1 = 62 if var1 >= var2 then goto vtgteok endif endif endif goto hang vtgteok: rem if var1 <> var2 then var1 = 50 var2 = 50 if var1 <> var2 then goto hang else if var1 = var2 then goto vtneok endif endif goto hang vtneok: rem if var1 = number then var1 = 25 if var1 = 26 then goto hang else if var1 = 25 then goto cteqok endif endif goto hang cteqok: rem if var1 < number then var1 = 35 if var1 < 35 then goto hang else if var1 < 34 then goto hang else if var1 < 36 then goto ctltok endif endif endif goto hang ctltok: rem if var1 > number then var1 = 35 if var1 > 35 then goto hang else if var1 > 36 then goto hang else if var1 > 34 then goto ctgtok endif endif endif goto hang ctgtok: rem if var1 <= number then var1 = 50 if var1 <= 49 then goto hang else if var1 <= 50 then if var1 <= 51 then goto ctlteok endif endif endif goto hang ctlteok: rem if var1 >= number then var1 = 50 if var1 >= 51 then goto hang else if var1 >= 50 then if var1 >= 49 then goto ctgteok endif endif endif goto hang ctgteok: rem if var1 <> number then var1 = 70 if var1 <> 70 then goto hang else if var1 <> 69 then goto ctneok endif endif goto hang ctneok: rem if all tests ok... bitset outpin hang: goto hang asm end ------- end testcomp.sim -------------------------------------------- Sure enough it found a bug in my first-draft conversion, a simple typo that broke only one of the 24 possible if forms. Between this, blink.asm and some other tests I've done, most of the new code has been validated, but there might be a few unexercised code paths. I trust it enough now to start using for my own code, but until it gets more usage time I'll carefully check the output to make sure it is doing the right thing. For anyone else, use it at your own risk, do your own testing, and check back every now and then to see if the program has been updated. SIMPLE2 Language Reference -------------------------- Here is the output of simple2 --help... ---------------------------------------------------------------------------- A Simple Compiler for PIC12/PIC16 chips Converts Simple HLL to MPASM/GPASM code Version 2.02 11/13 Copyright 2012 Terry Newton Syntax: SIMPLE [infile[.sim] [outfile[.asm]]] This compiler does only minimal syntax checking, all assembler and chip-specific stuff must be passed to the assembler using asm lines. The assembler itself does the actual parameter and syntax checking, if an error occurs, look up the number in the asm file to determine which simple source line caused the error. An asm include or other include file (might need UC) must be near the beginning to define the chip being used, refer to the include file for register and bit names. Typically use something like: asm __CONFIG _WDT_OFF & _MCLRE_OFF & _INTRC_OSC_NOCLKOUT & etc to configure chip features, this is specific to the assembler and chip. Include asm radix dec near the beginning to default to decimal numbers. An asm end at the end of the source is required for most assemblers. Use # before immediate constants, decimal numbers are assumed to be constants but precede hex, binary and symbolic constants with #. When defining constants, don't use # in the define, just when used. Use define symbol = address to reference absolute ram locations. Numbers assumed to be decimal, use xxH for hex or xxxxxxxB for binary. Parms passed as-is but for bit references '.' is converted to ','. Supports up to 9 levels of nested if/then/else or loop/next statements. The Simple language ------------------- byte a define variable a to next ram location (40) define myconst = 10 define myconst to mean 10 decimal define c = 22h define c to mean 22 hex define mybit = c.0 define bit mybit to bit 0 of c array d(8) define d to next ram (41) and allocate 8 bytes rambase 55 set internal ram counter to 55 (or say 37H) byte b define variable b to the next ram (55) a = 1 set variable a to 1 (#1 is ok too) b = #myconst set b to the number defined by myconst !ra = #00001111b set port A direction bits 0-3 to input (no longer supported, use bitset STATUS.RP0 etc to select high regs) c = ra set variable c to port A (use correct name) c = b - a subtracts a from b and stores in c a = a + 10 adds 10 to a a = b and c set a to b AND c (for each bit 1 if both 1) b = b or c set b to b OR c (for each bit 1 if either 1) a = a xor b xor a with b (for each bit 1 if different) c = not a set c to inverse of a (for each bit 1 if 0) a = myarray(c) set a to contents of location myarray+c myarray(5) = 2 set location myarray+5 to the number 2 (array elements start at 0, index is added to the base address) if a > b goto label branch if contents of a is greater than b if a < b goto label branch if a is less than b if a >= b goto label branch if a is greater than or equal to b if a <= b goto label branch if a is less than or equal to b if a <> b goto label branch if a is not equal to b if not mybit goto label branch if mybit is clear (not set) if c = #myconst then if contents of c is equal to the number statements defined by myconst, do statement(s) else else is optional, do next statement(s) statements if c is not equal to #myconst endif endif required for blocks, one word. (and similar comparison forms, only 2nd parm can be a constant) loop a from 10 set a to 10 (or var) and labels loop point statements statements executed 10 times next a decrease a by 1 and if not 0 loop again increment b same as b = b + 1 decrement b same as b = b - 1 gosub label call subroutine at label (can be external) goto label branch to label label: internal labels end with : return return from subroutine call shift a left shift bits in a to the left, lsb cleared shift a right shift bits to the right, msb cleared bitset mybit set the bit 'mybit' to 1 bitclear mybit clear the bit 'mybit' to 0 bitcopy c.0 = mybit copy mybit into bit 0 of variable c bitcopy c.1 = not mybit copy inverse of mybit into bit 1 of c bitcopy mybit = ra.0 copy port A bit 0 into mybit bitcopy mybit = not mybit invert the state of mybit asm line to assemble include assembly instructions (col2+) raw line to assemble pass line directly to assembler (col1) (use raw for directives, formatted assembly w/labels etc) rem comment line comment, included in output ;comment line not included in output unless after instr ---------------------------------------------------------------------------- Here are more detailed explanations... byte array rambase ------------------ Use byte variablename to allocate a single byte variable. Use array arrayname(constant) to allocate multiple bytes, constant must be a decimal number or a hex number in the form of ddH where dd is a hex number. By default ram locations begin at location 40 (2D hex), use rambase location to reset the ram pointer to another location, where location is a decimal or xxH number. For example... rambase 50 byte tentry ;comment array table(8) byte tindex ...compiles to... ;rambase 50 tentry EQU 0x32 ;comment ;array table(8) table EQU 0x33 tindex EQU 0x3B define ------ define name = string tells the assembler that anytime name is used, substitute string instead. Note - any "." characters in string will be converted to "," for compatibility with Microchip bit addressing. Use define to define constants or to redefine register and bit names to something more readable. For example, define hiregs = STATUS.RP0 lets bank 1 registers be accessed by doing bitset hiregs instead of bitset STATUS.RP0, and define outpin = GPIO.0 lets a PIC12F pin be set or cleared using a name instead of having to specify port.bit. Output code for define constant = 60 is... #define constant 60 ;any comments Output code for define mybit = byte.0 is... #define mybit byte,0 ;any comments Assignments ----------- Assigning values to variables or copying variables is done using... varname = number where number is a decimal number varname = #number same with # added to make it clear it's immediate varname = #xxxxxxxxB where the x's are a bit pattern varname = #0xFF where FF is a hex number varname = #EEH where EE is a hex number varname = #constant where constant is a previously defined constant varname = anothervar copies another variable to a variable arrayname(index) = number/var set location arrayname+index to number/var varname = arrayname(index) copy arrayname+index to a variable (index can be a number/constant or another variable) Note - array elements begin at 0, so if array MyArray(10) is used, then array elements MyArray(0) through MyArray(9) can be safely accessed. There is no checking to make sure array indexes are valid or if the target is actually declared as an array, variable(index) works with any variable. All of ram can be accessed as an array by doing define ram = 0 then using ram(variable) or ram(address). Note - array accesses can only be used in simple assignment statements, copy the array elements to a variable first before comparing or doing math. Code generation... ;VarA = 10 MOVLW 10 MOVWF VarA ;VarA = VarB MOVF VarB,0 MOVWF VarA ;VarB = ArrayD(5) MOVLW ArrayD MOVWF 4 MOVLW 5 ADDWF 4,1 MOVF 0,0 MOVWF VarB ;ArrayD(3) = 4 MOVLW ArrayD MOVWF 4 MOVLW 3 ADDWF 4,1 MOVLW 4 MOVWF 0 ;ArrayD(3) = var1 MOVLW ArrayD MOVWF 4 MOVLW 3 ADDWF 4,1 MOVF var1,0 MOVWF 0 ;ram(ramptr) = 10 MOVLW ram MOVWF 4 MOVF ramptr,0 ADDWF 4,1 MOVLW 10 MOVWF 0 ;ram(ramptr) = var1 MOVLW ram MOVWF 4 MOVF ramptr,0 ADDWF 4,1 MOVF var1,0 MOVWF 0 ;var1 = ram(ramptr) MOVLW ram MOVWF 4 MOVF ramptr,0 ADDWF 4,1 MOVF 0,0 MOVWF var1 Assignments with math and logic ------------------------------- SIMPLE does not support complex math expressions or anything besides 8-bit math and logic performed one step at a time (however any expression supported by the assembler may be used in constants provided there are no spaces). Math and logic must be done one step at a time. There is no multiply or divide unless subroutines are supplied to do that... if the underlying machine code can't do something without a subroutine, neither can SIMPLE. Generally this is not an issue for the kinds of control apps performed by small PIC chips, occasionally I might need 16 bit shifts or add/subtract subroutines (typically for dealing with A-D conversion results) but it is easy to add such code in asm statements. If an application needs a whole lot of math then it's probably better to use a PIC18-series chip with C or another compiler, or perhaps a PIC16 compiler that supports 16-bit math. Myself, I'm fine with just using 16-bit math subs when needed. The following math and logic instruction forms are supported... byte var1 byte var2 byte var3 var1 = 21 var2 = 25 define constant = 5 var2 = var2 + #constant ;use # if not a decimal number ;var2=25+5=30 var1 = var1 + var2 ;self reference must be first ;var1=21+30=51 var2 = var1 + #constant ;var2=51+5=56 var3 = var1 + var2 ;var3=51+56=107 var1 = var3 ;for testing ;var1=107 var2 = var2 - #constant ;var2=56-5=51 var1 = var1 - var2 ;var1=107-51=56 var2 = var1 - 2 ;var2=56-2=54 var3 = var1 - var2 ;var3=56-54=2 increment var3 ;var3=2+1=3 decrement var3 ;var3=3-1=2 var1 = var1 + 1 ;optimized to an increment ;var1=56+1=57 var1 = var1 - 1 ;optimized to a decrement ;var1=57-1=56 var1 = var1 + var3 ;for testing to propagate var3 ;var1=56+2=58 var3 = var2 + 1 ;var3=54+1=55 var3 = var2 - 1 ;var3=55-1=54 var1 = var1 and var2 ;var1=58 and 54=50 var2 = var1 and var3 ;var2=50 and 54=50 var1 = var2 and 49 ;var1=50 and 49=48 var3 = var3 and #constant ;var3=54 and 5=4 var1 = var1 or var2 ;var1=50 or 50=50 var2 = var1 or var3 ;var2=50 or 4=54 var1 = var2 or #constant ;var1=54 or 5=55 var3 = var3 or #1010B ;var3=4 or 10=14 var1 = var1 xor var2 ;var1=55 xor 54=1 var2 = var1 xor var3 ;var2=1 xor 14=15 var1 = var2 xor #constant ;var1=15 xor 5=10 var3 = var3 xor #0xFF ;var3=14 xor 255=241 var1 = not var1 ;var1=not 10=245 var3 = not var1 ;var3=not 245=10 err: ;hang here if results not as expected if var3 <> 10 goto err This chunk of code compiles to the following assembly instructions... var1 EQU 46 var2 EQU 47 var3 EQU 48 ;var1 = 21 MOVLW 21 MOVWF var1 ;var2 = 25 MOVLW 25 MOVWF var2 #define constant 5 ;var2 = var2 + #constant ;use # if not a decimal number ;var2=25+5=30 MOVLW constant ADDWF var2,1 ;var1 = var1 + var2 ;self reference must be first ;var1=21+30=51 MOVF var2,0 ADDWF var1,1 ;var2 = var1 + #constant ;var2=51+5=56 MOVF var1,0 MOVWF var2 MOVLW constant ADDWF var2,1 ;var3 = var1 + var2 ;var3=51+56=107 MOVF var1,0 MOVWF var3 MOVF var2,0 ADDWF var3,1 ;var1 = var3 ;for testing ;var1=107 MOVF var3,0 MOVWF var1 ;var2 = var2 - #constant ;var2=56-5=51 MOVLW constant SUBWF var2,1 ;var1 = var1 - var2 ;var1=107-51=56 MOVF var2,0 SUBWF var1,1 ;var2 = var1 - 2 ;var2=56-2=54 MOVF var1,0 MOVWF var2 MOVLW 2 SUBWF var2,1 ;var3 = var1 - var2 ;var3=56-54=2 MOVF var1,0 MOVWF var3 MOVF var2,0 SUBWF var3,1 ;increment var3 ;var3=2+1=3 INCF var3,1 ;decrement var3 ;var3=3-1=2 DECF var3,1 ;var1 = var1 + 1 ;optimized to an increment ;var1=56+1=57 INCF var1,1 ;var1 = var1 - 1 ;optimized to a decrement ;var1=57-1=56 DECF var1,1 ;var1 = var1 + var3 ;for testing to propagate var3 ;var1=56+2=58 MOVF var3,0 ADDWF var1,1 ;var3 = var2 + 1 ;var3=54+1=55 MOVF var2,0 MOVWF var3 INCF var3,1 ;var3 = var2 - 1 ;var3=55-1=54 MOVF var2,0 MOVWF var3 DECF var3,1 ;var1 = var1 and var2 ;var1=58 and 54=50 MOVF var2,0 ANDWF var1,1 ;var2 = var1 and var3 ;var2=50 and 54=50 MOVF var1,0 MOVWF var2 MOVF var3,0 ANDWF var2,1 ;var1 = var2 and 49 ;var1=50 and 49=48 MOVF var2,0 MOVWF var1 MOVLW 49 ANDWF var1,1 ;var3 = var3 and #constant ;var3=54 and 5=4 MOVLW constant ANDWF var3,1 ;var1 = var1 or var2 ;var1=50 or 50=50 MOVF var2,0 IORWF var1,1 ;var2 = var1 or var3 ;var2=50 or 4=54 MOVF var1,0 MOVWF var2 MOVF var3,0 IORWF var2,1 ;var1 = var2 or #constant ;var1=54 or 5=55 MOVF var2,0 MOVWF var1 MOVLW constant IORWF var1,1 ;var3 = var3 or #1010B ;var3=4 or 10=14 MOVLW 1010B IORWF var3,1 ;var1 = var1 xor var2 ;var1=55 xor 54=1 MOVF var2,0 XORWF var1,1 ;var2 = var1 xor var3 ;var2=1 xor 14=15 MOVF var1,0 MOVWF var2 MOVF var3,0 XORWF var2,1 ;var1 = var2 xor #constant ;var1=15 xor 5=10 MOVF var2,0 MOVWF var1 MOVLW constant XORWF var1,1 ;var3 = var3 xor #0xFF ;var3=14 xor 255=241 MOVLW 0xFF XORWF var3,1 ;var1 = not var1 ;var1=not 10=245 COMF var1,1 ;var3 = not var1 ;var3=not 245=10 MOVF var1,0 MOVWF var3 COMF var3,1 err ;hang here if results not as expected ;if var3 <> 10 goto err MOVLW 10 SUBWF var3,0 BTFSS 3,2 GOTO err Here are 16-bit add, subtract and negate subroutines... rem compute 2500-1500 ACCbLO = #2500&255 ACCbHI = #2500>>8 ACCaLO = #1500&255 ACCaHI = #1500>>8 gosub D_sub rem add 2000 to result ACCaLO = #2000&255 ACCaHI = #2000>>8 gosub D_add rem ACCb should be 3000 errD: if ACCbLO <> #3000&255 goto errD if ACCbHI <> #3000>>8 goto errD .... rem 16 bit add/subtract/negate rem ACCbLO and ACCbHI is the accumulator rem gosub D_add to add ACCaLO and ACCaHI to the accumulator rem gosub D_sub to subract ACCaLO and ACCaHI from the accumulator rem gosub neg_A to 2's complement ACCaLO and ACCaHI rem note - D_sub uses an extra stack level rem adapted from Microchip AN526 16 bit add/sub routines byte ACCaLO byte ACCaHI byte ACCbLO byte ACCbHI rem ACCb=ACCb-ACCa D_sub: gosub neg_A rem ACCb=ACCb+ACCa D_add: asm movf ACCaLO,W asm addwf ACCbLO,F asm btfsc STATUS,C asm incf ACCbHI,F asm movf ACCaHI,W asm addwf ACCbHI,F return rem ACCa=not ACCa neg_A: asm comf ACCaLO,F asm incf ACCaLO,F asm btfsc STATUS,Z asm decf ACCaHI,F asm comf ACCaHI,F return Using bits and bitset bitclear bitcopy -------------------------------------- Bits are extensively used in control programs to read and control pins, control on-chip PIC peripherals, and general flag usage to save ram. Bits are specified in SIMPLE by byte.bit or byte,bit where byte is a variable or defined memory location, and bit is a digit or a defined constant (variables cannot be used to specify bits). The byte.bit format is for convenience and how the original SIMPLE worked with the Parallax instruction set, "." is changed to "," for gpasm/mpasm format, otherwise the specification is passed as written to the assembly source. For convenience, define can be used to specify bits by name, after define outpin = GPIO.0 then a simple bitset outpin can be used. This is also useful for naming PIC control bits so the register doesn't always have to be specified, but still have to pay attention to banking... bitset STATUS.RP0 (or define hiregs = STATUS.RP0 then use bitset hiregs) must be done before accessing bits in registers past location 0x7F, and bitclear STATUS.RP0 done afterwards for normal ram access. The bitcopy instruction copies a bit or an inverted bit to another bit in the form of: bitcopy destination.bit = [not] source.bit When using the bitcopy bit = not bit form, if the two bits are the same then code is generated to invert the bit. The following demonstrates bit manipulation... byte flags define bit0 = flags.0 define bit1 = flags.1 define bit2 = flags.2 define bit3 = flags.3 bitset bit0 bitclear bit1 bitcopy bit2 = bit0 bitcopy bit3 = not bit1 bitcopy bit3 = not bit3 if not bit2 goto hang if bit3 goto hang This compiles to the following assembly code... ;rem more bit stuff flags EQU 0x34 #define bit0 flags,0 #define bit1 flags,1 #define bit2 flags,2 #define bit3 flags,3 ;bitset bit0 BSF bit0 ;bitclear bit1 BCF bit1 ;bitcopy bit2 = bit0 BTFSS bit0 BCF bit2 BTFSC bit0 BSF bit2 ;bitcopy bit3 = not bit1 BTFSC bit1 BCF bit3 BTFSS bit1 BSF bit3 ;bitcopy bit3 = not bit3 BTFSC bit3 GOTO $ + 3 BSF bit3 GOTO $ + 2 BCF bit3 ;if not bit2 goto hang BTFSS bit2 GOTO hang ;if bit3 goto hang BTFSC bit3 GOTO hang Using bitcopy bit = not bit to invert a bit generates 5 instructions. A more efficient way is to XOR the variable containing the bit with a constant with the bit to flip set... define bit3xor = 00001000B flags = flags xor #bit3xor #define bit3xor 00001000B ;flags = flags xor #bit3xor MOVLW bit3xor XORWF flags,1 ...just 2 instructions, but not as readable and requires another define. Shifts ------ SIMPLE provides two shift forms... shift variable left shift variable right Code generation... ;shift var1 left BCF 3,0 RLF var1,1 ;shift var1 right BCF 3,0 RRF var1,1 The left shift always clears bit 0 and the right shift always clears bit 7. To do true rotates (rare but possibly needed) use something like... rem rotate var1 left bitcopy STATUS.C = var1.7 asm RLF var1,F rem rotate var2 right bitcopy STATUS.C = var2.0 asm RRF var2,F 16-bit shifts can be done by inserting code like... asm BCF STATUS,C asm RRF somevar_high,F asm RRF somevar_low,F asm BCF STATUS,C asm RLF somevar_low,F asm RLF somevar_high,F For rotates replace the asm BCF with an appropriate bitcopy. For this little amount of code it's probably always more efficient if the shift code is inserted in-line rather than making into subroutines to avoid the overhead of setting and retreving the values, but it is possible to use a memory pointer to call 16 bit shift subroutines... byte var_low byte var_high var_low = #00110011B var_high = #01010101B word_pointer = #var_low gosub shift16left gosub shift16left gosub shift16right err1: if var_low <> #01100110B goto err1 if var_high <> #00101010B goto err1 .... rem 16 bit shift subroutines rem word_pointer = 2 byte ram loc to shift byte word_pointer define FSreg = 4 define FSmem = 0 rem shift (word_pointer) left shift16left: FSreg = word_pointer shift FSmem left increment FSreg asm RLF FSmem,F return rem shift (word_pointer) right shift16right: FSreg = word_pointer increment FSreg shift FSmem right decrement FSreg asm RRF FSmem,F return Conditional branches -------------------- SIMPLE supports comparisons and branches in the forms of... if variable operator variable|constant goto label if variable operator variable|constant then if bitname goto label if not bitname goto label if bitname then if not bitname then The operator can be = < > <= >= or <>. The word after the operator can be a variable, decimal number, or #constant. As with all constants in SIMPLE (except for the parameter for rambase), always use # for named constants or constants like #10000001B or #0xFF. (as it turns out, constants like 1000001B 0xEE 55H actually do work for immediate values when simple2 is compiled with FreeBASIC, but it's not supposed to work, especially the 0xEE case.. so officially, you should always use # for any immediate values other than plain decimal numbers, and even for decimal numbers I often include the # anyway for clarity). Forms ending with "then" must be closed using endif and can optionally use an else block that is run if the expression is not true... if var1 <= var2 then ;this code runs if var1 is less than or equal to var2 else ;this code runs if var1 is greater than var2 endif Up to 9 levels of if/endif can be used. Byte comparison/branch code generation... ;if var1 = var2 goto hang MOVF var2,0 SUBWF var1,0 BTFSC 3,2 GOTO hang ;if var1 < var2 goto hang MOVF var2,0 SUBWF var1,0 BTFSS 3,0 GOTO hang ;if var1 > var2 goto hang MOVF var1,0 SUBWF var2,0 BTFSS 3,0 GOTO hang ;if var1 <= var2 goto hang MOVF var1,0 SUBWF var2,0 BTFSC 3,0 GOTO hang ;if var1 >= var2 goto hang MOVF var2,0 SUBWF var1,0 BTFSC 3,0 GOTO hang ;if var1 <> var2 goto hang MOVF var2,0 SUBWF var1,0 BTFSS 3,2 GOTO hang ;if var1 = 31 goto hang MOVLW 31 SUBWF var1,0 BTFSC 3,2 GOTO hang ;if var1 < 40 goto hang MOVLW 40 SUBWF var1,0 BTFSS 3,0 GOTO hang ;if var1 > 40 goto hang MOVLW 40^0xFF ADDWF var1,0 BTFSC 3,0 GOTO hang ;if var1 <= 19 goto hang MOVLW 19^0xFF ADDWF var1,0 BTFSS 3,0 GOTO hang ;if var1 >= 21 goto hang MOVLW 21 SUBWF var1,0 BTFSC 3,0 GOTO hang ;if var1 <> 50 goto hang MOVLW 50 SUBWF var1,0 BTFSS 3,2 GOTO hang ;if var1 = var2 then MOVF var2,0 SUBWF var1,0 BTFSS 3,2 GOTO ifnext__10 .....code for true ;else GOTO ifend__10 ifnext__10 .....code for false ;endif ifend__10 (if no else is used the endif label is ifnext__[level][usage number]) (omitting the branch structure from the rest of these, true code follows) ;if var1 < var2 then MOVF var2,0 SUBWF var1,0 BTFSC 3,0 GOTO ifnext__11 ;if var1 > var2 then MOVF var1,0 SUBWF var2,0 BTFSC 3,0 GOTO ifnext__12 ;if var1 <= var2 then MOVF var1,0 SUBWF var2,0 BTFSS 3,0 GOTO ifnext__13 ;if var1 >= var2 then MOVF var2,0 SUBWF var1,0 BTFSS 3,0 GOTO ifnext__14 ;if var1 <> var2 then MOVF var2,0 SUBWF var1,0 BTFSC 3,2 GOTO ifnext__15 ;if var1 = 26 then MOVLW 26 SUBWF var1,0 BTFSS 3,2 GOTO ifnext__16 ;if var1 < 35 then MOVLW 35 SUBWF var1,0 BTFSC 3,0 GOTO ifnext__17 ;if var1 > 35 then MOVLW 35^0xFF ADDWF var1,0 BTFSS 3,0 GOTO ifnext__18 ;if var1 <= 49 then MOVLW 49^0xFF ADDWF var1,0 BTFSC 3,0 GOTO ifnext__19 ;if var1 >= 51 then MOVLW 51 SUBWF var1,0 BTFSS 3,0 GOTO ifnext__110 ;if var1 <> 70 then MOVLW 70 SUBWF var1,0 BTFSC 3,2 GOTO ifnext__111 ;if Bits.2 goto hang BTFSC Bits,2 GOTO hang Bit branch code generation... ;if Bits.2 goto hang BTFSC Bits,2 GOTO hang ;if not Bits.1 goto hang BTFSS Bits,1 GOTO hang ; if Bits.1 then BTFSS Bits,1 GOTO ifnext__22 ; if not Bits.2 then BTFSC Bits,2 GOTO ifnext__32 16-bit compares are trickier but the general idea is to compare the high bytes, and if equal then compare the low bytes. Here is a somewhat bloated subroutine for doing 16 bit compares... rem compare16 - a subroutine for 16 bit unsigned compare rem set c16n1 and c16n1h to a number rem set c16n2 and c16n2h to another number rem gosub compare16 rem returns c16result = 1 (bit 0) if equal rem c16result = 2 (bit 1) if c16n1 < c16n2 rem c16result = 4 (bit 2) if c16n1 > c16n2 rem input bytes are not changed byte c16n1 byte c16n1h byte c16n2 byte c16n2h byte c16temp byte c16result compare16: c16result = 0 ;opt to clrf bitset c16result.2 ;default 4 for gt rem check for equal if c16n1h = c16n2h then if c16n1 = c16n2 then c16result = 0 bitset c16result.0 ;return 1 for equal else ;check for LT in low byte c16temp = c16n1 - c16n2 if not STATUS.C then ;c16n1 < c16n2 asm RRF c16result,F ;carry already clear so just shift endif ;return 2 for lt (high bytes equal) endif else ;check for LT in high byte c16temp = c16n1h - c16n2h if not STATUS.C then ;c16n1h < c16n2h asm RRF c16result,F ;return 2 for lt in high bytes endif endif return Example usage... define equal = c16result.0 define lessthan = c16result.1 define greaterthan = c16result.2 c16n1 = variable_low c16n1h = variable_high c16n2 = #33555&255 ;compare to 33555 c16n2h = #33555>>8 gosub compare16 if greaterthan goto code_for_greaterthan if lessthan goto code_for_lessthan ...code for equal The code after the gosub compare16 determines what kind of comparison is done, to simulate if c16n1[h] <= c16n2[h] goto code_for_lessthanorequal, do... if equal goto code_for_lessthanorequal if lessthan goto code_for_lessthanorequal Looping ------- The loop instruction sets a variable to a value (another variable or constant) then repeats a block of statements decrementing the variable until the value becomes 0. Use like... byte counter byte variable loop counter from 10 ;this code executes 10 times ;with counter ranging from 10 to 1 next counter variable = 15 loop counter from variable ;this code executes 15 times next counter Up to 9 levels of loop/next can be used (in addition to if/endif levels). This is the only built-in looping mechanism, other traditional kinds of loops can be easily simulated using conditional branches... byte var1 ;for var1 = 7 to 10...next var1 var1 = 7 loop1: ;code increment var1 if var1 <= 10 goto loop1 byte var1 var1 = 0 ;while var1 < 10...wend loop2: if var1 >= 10 goto loop2end increment var1 ;or whatever goto loop2 loop2end: ...or... byte var1 var1 = 0 ;while var1 < 10...wend loop2a: if var1 < 10 then increment var1 ;or whatever goto loop2a endif byte var1 var1 = 0 ;do...until var1 = 10 loop3: increment var1 ;or whatever if var1 <> 10 goto loop3 Loops can be done with a bit controlling the exit... byte var1 define bit7 = var1.7 var1 = 0 ;do...until bit7 is high loop4: increment var1 ;or whatever if not bit7 goto loop4 Basically, loops in SIMPLE are done just like they'd be done in assembly. Code generation for loop/next... ;loop CounterC from #ConstC MOVLW ConstC MOVWF CounterC local__10 .....code in the loop ;next CounterC DECFSZ CounterC,1 GOTO local__10 ;loop CounterC from VarC MOVF VarC,0 MOVWF CounterC local__11 .....code in the loop ;next CounterC DECFSZ CounterC,1 GOTO local__11 Using eeprom and the analog inputs ---------------------------------- The following program for a PIC12F675 demonstrates how to write and read eeprom data and read analog inputs, on a PicKit 1 it blinks LED D7 according to the position of the trimmer. This is just one way to do it, the analog read subroutine is a bit bigger than it has to be for something more adapted to a specific purpose. Analog read code for other PICs such as the PIC16F684 is similar but the control bits are in different registers in different banks. The ReadAD subroutine was designed to be easy to use, only setup requirement is the tristate bit for whatever input is being read has to be set to input. To use just set adchannel to the channel to read, gosub ReadAD, and a 10-bit result is put in voltagehigh and voltagelow (right justified so only bottom two bits of voltagehigh are used), and a 8-bit result is put in voltagebyte. The AD converter is powered only when reading, so some delay is needed to stabilize. The delay is controlled by the number in the loop adtemp from 100 line, which produces a delay of about 1/3 of a millisecond before reading. It works with lower values for faster conversion but accuracy goes down. ------- begin demoadee.sim ------------------------------------------ rem EE subs and analog input test for PIC12F675... rem if AN0 > 2V then blink an LED attached to pin 6 rem if AN0 > 4V then light the LED solid rem otherwise don't light LED rem assembler-specific directives asm list p=12f675 asm radix dec asm include asm __CONFIG _WDT_OFF & _MCLRE_OFF & _PWRTE_ON & _CP_OFF & _CPD_OFF & _INTRC_OSC_NOCLKOUT asm errorlevel -302 ;suppress messages about not being in bank 0 define hiregs = STATUS.RP0 ;for banking define pindirs = 100001B ;make GPIO.0,6 input, GPIO.1-4 output define outpin = GPIO.1 ;output pin GOTO start ;jump to actual start asm ORG 8 start: ;start of program code GPIO = 0 ;clear GPIO register INTCON = 0 ;no interrupts ADCON0 = 0 ;disable A-D (for now) T1CON = 0 ;disable timer CMCON = 7 ;disable compare bitset hiregs ;select high registers TRISIO = #pindirs ;set pin directions ANSEL = 0 ;set all analog inputs to digital PIE1 = 0 ;disable peripheral interrupts IOC = 0 ;disable interrupt on change WPU = 0 ;disable weak pullups OPTION_REG = #11001101B ;typical options bitclear hiregs eeaddress = 10 eebyte = 100 gosub WriteEE eeaddress = 20 eebyte = 200 gosub WriteEE eeaddress = 10 gosub ReadEE ee_err1: if eebyte <> 100 goto ee_err1 eeaddress = 20 gosub ReadEE ee_err2: if eebyte <> 200 goto ee_err2 mainloop: adchannel = 0 gosub ReadAD if voltagebyte > 204 goto solid if voltagebyte > 102 goto blink bitclear outpin goto mainloop blink: bitset outpin gosub delay bitclear outpin gosub delay goto mainloop solid: bitset outpin goto mainloop delay: milliseconds = 250 gosub MSdelay gosub MSdelay return rem 256-byte eeprom subroutines (128-byte for '629/675) byte eeaddress ;set to address to read/write byte eebyte ;data to write or data read ReadEE: asm MOVF eeaddress,W asm BSF STATUS,RP0 asm MOVWF EEADR asm BSF EECON1,RD asm MOVF EEDAT,W asm BCF STATUS,RP0 asm MOVWF eebyte return WriteEE: rem assumes interupts are disabled! asm MOVF eeaddress,W asm BSF STATUS,RP0 asm MOVWF EEADR asm BCF STATUS,RP0 asm MOVF eebyte,W asm BSF STATUS,RP0 asm MOVWF EEDAT asm BSF EECON1,WREN asm MOVLW 0x55 asm MOVWF EECON2 asm MOVLW 0xAA asm MOVWF EECON2 asm BSF EECON1,WR waitforeewrite: asm BTFSC EECON1,WR asm GOTO waitforeewrite asm BCF EECON1,WREN asm BCF STATUS,RP0 return rem read analog pin for PIC12F675 rem tristate register must already be set to input rem this is chip-specific, code is similar for other rem PICs but control bits are in different registers rem This sub is kind of bloated in an attempt to make GP byte adchannel ;set to channel to read, 0-3 byte voltagelow ;lower 8 bits of 10-bit result byte voltagehigh ;upper 2 bits of 10-bit result byte voltagebyte ;8 bit result byte adtemp ReadAD: bitset STATUS.RP0 ANSEL = #00000001B bitclear STATUS.RP0 if adchannel > 0 then loop adtemp from adchannel bitset STATUS.RP0 shift ANSEL left bitclear STATUS.RP0 next adtemp endif adtemp = adchannel shift adtemp left shift adtemp left ADCON0 = adtemp bitset ADCON0.ADFM bitset ADCON0.ADON loop adtemp from 100 ;delay for AD to power up next adtemp bitset ADCON0.GO_DONE WaitForVoltage: if ADCON0.GO_DONE goto WaitForVoltage bitset STATUS.RP0 asm MOVF ADRESL,W bitclear STATUS.RP0 asm MOVWF voltagelow bitclear ADCON0.ADON voltagehigh = ADRESH voltagebyte = voltagelow shift voltagebyte right shift voltagebyte right bitcopy voltagebyte.6 = voltagehigh.0 bitcopy voltagebyte.7 = voltagehigh.1 return rem delay for specified milliseconds (4mhz clock) byte milliseconds ;set to #ms to delay byte delaycounter byte innercounter MSdelay: loop delaycounter from milliseconds rem execute 997 instructions (1ms - overhead) innercounter = #249 MSdelay1: asm NOP asm DECFSZ innercounter,F asm GOTO MSdelay1 asm NOP next delaycounter return asm end ------- end demoadee.sim -------------------------------------------- Wrapping it up -------------- The above descriptions should cover most aspects of the SIMPLE2 compiler, hopefully adequately enough to use. However, successfully using SIMPLE still requires detailed knowledge of the particular PIC being programmed to know what register names to use (they're in the include file), how to initialize the chip (often peripherals must be disabled to use certain pins) and how to access on-chip peripherals like eeprom storage and the analog to digital converter. The required information is in the PIC datasheets but it takes quite a bit of study to understand it, SIMPLE makes no attempt to hide the chip-specific stuff for a few reasons, for one trying to abstract such things would add lots of code to the compiler and it would no longer be "simple", but mainly because I don't want to hide chip-specific stuff. When writing control code I need full access to everything because I never know what I might need to do, by keeping the focus on just another less error-prone way to express assembly code, I can still do anything I can do with assembly faster and without adding much bloat. License info... 'The simple2 BASIC source code and binary is Copyright 2012 Terry Newton. 'May be redistributed provided the source code is included, the copyright 'remains intact, and any changes are documented with attribution and date. 'The assembly output code may be used for any purpose without restrictions. 'Provided as-is and without warranty. The sample code is public domain, do whatever you want with it. ---------------------------------------------------------------------------- This doc file created 11/11/12, last modified 11/13/12 Terry Newton (wtn90125@yahoo.com)