diff options
author | akshay-c | 2019-01-30 12:23:44 +0530 |
---|---|---|
committer | akshay-c | 2019-01-30 12:23:44 +0530 |
commit | 4196481f74afb84e5cc59cdf00c06c1ca1becab7 (patch) | |
tree | b531deb0466897691f08f9076b7012592f026664 /ldmicro/pic16.cpp | |
download | LDmicroQt-4196481f74afb84e5cc59cdf00c06c1ca1becab7.tar.gz LDmicroQt-4196481f74afb84e5cc59cdf00c06c1ca1becab7.tar.bz2 LDmicroQt-4196481f74afb84e5cc59cdf00c06c1ca1becab7.zip |
First commit
Diffstat (limited to 'ldmicro/pic16.cpp')
-rw-r--r-- | ldmicro/pic16.cpp | 1628 |
1 files changed, 1628 insertions, 0 deletions
diff --git a/ldmicro/pic16.cpp b/ldmicro/pic16.cpp new file mode 100644 index 0000000..d45eabb --- /dev/null +++ b/ldmicro/pic16.cpp @@ -0,0 +1,1628 @@ +//----------------------------------------------------------------------------- +// Copyright 2007 Jonathan Westhues +// +// This file is part of LDmicro. +// +// LDmicro is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// LDmicro is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with LDmicro. If not, see <http://www.gnu.org/licenses/>. +//------ +// +// A PIC16... assembler, for our own internal use, plus routines to generate +// code from the ladder logic structure, plus routines to generate the +// runtime needed to schedule the cycles. +// Jonathan Westhues, Oct 2004 +//----------------------------------------------------------------------------- +#include "linuxUI.h" +#include <math.h> +#include <stdio.h> +#include <setjmp.h> +#include <stdlib.h> + +#include "ldmicro.h" +#include "intcode.h" + +// not complete; just what I need +typedef enum Pic16OpTag { + OP_VACANT, + OP_ADDWF, + OP_ANDWF, + OP_CALL, + OP_BSF, + OP_BCF, + OP_BTFSC, + OP_BTFSS, + OP_GOTO, + OP_CLRF, + OP_CLRWDT, + OP_COMF, + OP_DECF, + OP_DECFSZ, + OP_INCF, + OP_INCFSZ, + OP_IORWF, + OP_MOVLW, + OP_MOVF, + OP_MOVWF, + OP_NOP, + OP_RETFIE, + OP_RETURN, + OP_RLF, + OP_RRF, + OP_SUBLW, + OP_SUBWF, + OP_XORWF, +} Pic16Op; + +#define DEST_F 1 +#define DEST_W 0 + +#define STATUS_RP1 6 +#define STATUS_RP0 5 +#define STATUS_Z 2 +#define STATUS_C 0 + +typedef struct Pic16InstructionTag { + Pic16Op op; + DWORD arg1; + DWORD arg2; +} Pic16Instruction; + +#define MAX_PROGRAM_LEN 128*1024 +static Pic16Instruction PicProg[MAX_PROGRAM_LEN]; +static DWORD PicProgWriteP; + +// Scratch variables, for temporaries +static DWORD Scratch0; +static DWORD Scratch1; +static DWORD Scratch2; +static DWORD Scratch3; +static DWORD Scratch4; +static DWORD Scratch5; +static DWORD Scratch6; +static DWORD Scratch7; + +// The extra byte to program, for the EEPROM (because we can only set +// up one byte to program at a time, and we will be writing two-byte +// variables, in general). +static DWORD EepromHighByte; +static DWORD EepromHighByteWaitingAddr; +static int EepromHighByteWaitingBit; + +// Subroutines to do multiply/divide +static DWORD MultiplyRoutineAddress; +static DWORD DivideRoutineAddress; +static BOOL MultiplyNeeded; +static BOOL DivideNeeded; + +// For yet unresolved references in jumps +static DWORD FwdAddrCount; + +// As I start to support the paging; it is sometimes necessary to pick +// out the high vs. low portions of the address, so that the high portion +// goes in PCLATH, and the low portion is just used for the jump. +#define FWD_LO(x) ((x) | 0x20000000) +#define FWD_HI(x) ((x) | 0x40000000) + +// Some useful registers, which I think are mostly in the same place on +// all the PIC16... devices. +#define REG_INDF 0x00 +#define REG_STATUS 0x03 +#define REG_FSR 0x04 +#define REG_PCLATH 0x0a +#define REG_INTCON 0x0b +#define REG_PIR1 0x0c +#define REG_PIE1 0x8c +#define REG_TMR1L 0x0e +#define REG_TMR1H 0x0f +#define REG_T1CON 0x10 +#define REG_CCPR1L 0x15 +#define REG_CCPR1H 0x16 +#define REG_CCP1CON 0x17 +#define REG_CMCON 0x1f + +#define REG_TXSTA 0x98 +#define REG_RCSTA 0x18 +#define REG_SPBRG 0x99 +#define REG_TXREG 0x19 +#define REG_RCREG 0x1a + +#define REG_ADRESH 0x1e +#define REG_ADRESL 0x9e +#define REG_ADCON0 0x1f +#define REG_ADCON1 0x9f + +#define REG_T2CON 0x12 +#define REG_CCPR2L 0x1b +#define REG_CCP2CON 0x1d +#define REG_PR2 0x92 + +// These move around from device to device. +static DWORD REG_EECON1; +static DWORD REG_EECON2; +static DWORD REG_EEDATA; +static DWORD REG_EEADR; +static DWORD REG_ANSEL; +static DWORD REG_ANSELH; + +static int IntPc; + +static void CompileFromIntermediate(BOOL topLevel); + +//----------------------------------------------------------------------------- +// A convenience function, whether we are using a particular MCU. +//----------------------------------------------------------------------------- +static BOOL McuIs(char *str) +{ + return strcmp(Prog.mcu->mcuName, str) == 0; +} + +//----------------------------------------------------------------------------- +// Wipe the program and set the write pointer back to the beginning. +//----------------------------------------------------------------------------- +static void WipeMemory(void) +{ + memset(PicProg, 0, sizeof(PicProg)); + PicProgWriteP = 0; +} + +//----------------------------------------------------------------------------- +// Store an instruction at the next spot in program memory. Error condition +// if this spot is already filled. We don't actually assemble to binary yet; +// there may be references to resolve. +//----------------------------------------------------------------------------- +static void Instruction(Pic16Op op, DWORD arg1, DWORD arg2) +{ + if(PicProg[PicProgWriteP].op != OP_VACANT) oops(); + + PicProg[PicProgWriteP].op = op; + PicProg[PicProgWriteP].arg1 = arg1; + PicProg[PicProgWriteP].arg2 = arg2; + PicProgWriteP++; +} + +//----------------------------------------------------------------------------- +// Allocate a unique descriptor for a forward reference. Later that forward +// reference gets assigned to an absolute address, and we can go back and +// fix up the reference. +//----------------------------------------------------------------------------- +static DWORD AllocFwdAddr(void) +{ + FwdAddrCount++; + return 0x80000000 | FwdAddrCount; +} + +//----------------------------------------------------------------------------- +// Go back and fix up the program given that the provided forward address +// corresponds to the next instruction to be assembled. +//----------------------------------------------------------------------------- +static void FwdAddrIsNow(DWORD addr) +{ + if(!(addr & 0x80000000)) oops(); + + BOOL seen = FALSE; + DWORD i; + for(i = 0; i < PicProgWriteP; i++) { + if(PicProg[i].arg1 == addr) { + // Insist that they be in the same page, but otherwise assume + // that PCLATH has already been set up appropriately. + if((i >> 11) != (PicProgWriteP >> 11)) { + Error(_("Internal error relating to PIC paging; make program " + "smaller or reshuffle it.")); + CompileError(); + } + PicProg[i].arg1 = PicProgWriteP; + seen = TRUE; + } else if(PicProg[i].arg1 == FWD_LO(addr)) { + PicProg[i].arg1 = (PicProgWriteP & 0x7ff); + seen = TRUE; + } else if(PicProg[i].arg1 == FWD_HI(addr)) { + PicProg[i].arg1 = (PicProgWriteP >> 8); + } + } + if(!seen) oops(); +} + +//----------------------------------------------------------------------------- +// Given an opcode and its operands, assemble the 14-bit instruction for the +// PIC. Check that the operands do not have more bits set than is meaningful; +// it is an internal error if they do. +//----------------------------------------------------------------------------- +static DWORD Assemble(Pic16Op op, DWORD arg1, DWORD arg2) +{ +#define CHECK(v, bits) if((v) != ((v) & ((1 << (bits))-1))) oops() + switch(op) { + case OP_ADDWF: + CHECK(arg2, 1); CHECK(arg1, 7); + return (7 << 8) | (arg2 << 7) | arg1; + + case OP_ANDWF: + CHECK(arg2, 1); CHECK(arg1, 7); + return (5 << 8) | (arg2 << 7) | arg1; + + case OP_BSF: + CHECK(arg2, 3); CHECK(arg1, 7); + return (5 << 10) | (arg2 << 7) | arg1; + + case OP_BCF: + CHECK(arg2, 3); CHECK(arg1, 7); + return (4 << 10) | (arg2 << 7) | arg1; + + case OP_BTFSC: + CHECK(arg2, 3); CHECK(arg1, 7); + return (6 << 10) | (arg2 << 7) | arg1; + + case OP_BTFSS: + CHECK(arg2, 3); CHECK(arg1, 7); + return (7 << 10) | (arg2 << 7) | arg1; + + case OP_CLRF: + CHECK(arg1, 7); CHECK(arg2, 0); + return (3 << 7) | arg1; + + case OP_CLRWDT: + return 0x0064; + + case OP_COMF: + CHECK(arg2, 1); CHECK(arg1, 7); + return (9 << 8) | (arg2 << 7) | arg1; + + case OP_DECF: + CHECK(arg1, 7); CHECK(arg2, 1); + return (3 << 8) | (arg2 << 7) | arg1; + + case OP_DECFSZ: + CHECK(arg1, 7); CHECK(arg2, 1); + return (11 << 8) | (arg2 << 7) | arg1; + + case OP_GOTO: + // Very special case: we will assume that the PCLATH stuff has + // been taken care of already. + arg1 &= 0x7ff; + CHECK(arg1, 11); CHECK(arg2, 0); + return (5 << 11) | arg1; + + case OP_CALL: + CHECK(arg1, 11); CHECK(arg2, 0); + return (4 << 11) | arg1; + + case OP_INCF: + CHECK(arg1, 7); CHECK(arg2, 1); + return (10 << 8) | (arg2 << 7) | arg1; + + case OP_INCFSZ: + CHECK(arg1, 7); CHECK(arg2, 1); + return (15 << 8) | (arg2 << 7) | arg1; + + case OP_IORWF: + CHECK(arg2, 1); CHECK(arg1, 7); + return (4 << 8) | (arg2 << 7) | arg1; + + case OP_MOVLW: + CHECK(arg1, 8); CHECK(arg2, 0); + return (3 << 12) | arg1; + + case OP_MOVF: + CHECK(arg1, 7); CHECK(arg2, 1); + return (8 << 8) | (arg2 << 7) | arg1; + + case OP_MOVWF: + CHECK(arg1, 7); CHECK(arg2, 0); + return (1 << 7) | arg1; + + case OP_NOP: + return 0x0000; + + case OP_RETURN: + return 0x0008; + + case OP_RETFIE: + return 0x0009; + + case OP_RLF: + CHECK(arg1, 7); CHECK(arg2, 1); + return (13 << 8) | (arg2 << 7) | arg1; + + case OP_RRF: + CHECK(arg1, 7); CHECK(arg2, 1); + return (12 << 8) | (arg2 << 7) | arg1; + + case OP_SUBLW: + CHECK(arg1, 8); CHECK(arg2, 0); + return (15 << 9) | arg1; + + case OP_SUBWF: + CHECK(arg1, 7); CHECK(arg2, 1); + return (2 << 8) | (arg2 << 7) | arg1; + + case OP_XORWF: + CHECK(arg1, 7); CHECK(arg2, 1); + return (6 << 8) | (arg2 << 7) | arg1; + + default: + oops(); + break; + } +} + +//----------------------------------------------------------------------------- +// Write an intel IHEX format description of the program assembled so far. +// This is where we actually do the assembly to binary format. +//----------------------------------------------------------------------------- +static void WriteHexFile(FILE *f) +{ + BYTE soFar[16]; + int soFarCount = 0; + DWORD soFarStart = 0; + + // always start from address 0 + fprintf(f, ":020000040000FA\n"); + + DWORD i; + for(i = 0; i < PicProgWriteP; i++) { + DWORD w = Assemble(PicProg[i].op, PicProg[i].arg1, PicProg[i].arg2); + + if(soFarCount == 0) soFarStart = i; + soFar[soFarCount++] = (BYTE)(w & 0xff); + soFar[soFarCount++] = (BYTE)(w >> 8); + + if(soFarCount >= 0x10 || i == (PicProgWriteP-1)) { + StartIhex(f); + WriteIhex(f, soFarCount); + WriteIhex(f, (BYTE)((soFarStart*2) >> 8)); + WriteIhex(f, (BYTE)((soFarStart*2) & 0xff)); + WriteIhex(f, 0x00); + int j; + for(j = 0; j < soFarCount; j++) { + WriteIhex(f, soFar[j]); + } + FinishIhex(f); + soFarCount = 0; + } + } + + StartIhex(f); + // Configuration words start at address 0x2007 in program memory; and the + // hex file addresses are by bytes, not words, so we start at 0x400e. + // There may be either 16 or 32 bits of conf word, depending on the part. + if(McuIs("Microchip PIC16F887 40-PDIP") || + McuIs("Microchip PIC16F886 28-PDIP or 28-SOIC")) + { + WriteIhex(f, 0x04); + WriteIhex(f, 0x40); + WriteIhex(f, 0x0E); + WriteIhex(f, 0x00); + WriteIhex(f, (Prog.mcu->configurationWord >> 0) & 0xff); + WriteIhex(f, (Prog.mcu->configurationWord >> 8) & 0xff); + WriteIhex(f, (Prog.mcu->configurationWord >> 16) & 0xff); + WriteIhex(f, (Prog.mcu->configurationWord >> 24) & 0xff); + } else { + if(Prog.mcu->configurationWord & 0xffff0000) oops(); + WriteIhex(f, 0x02); + WriteIhex(f, 0x40); + WriteIhex(f, 0x0E); + WriteIhex(f, 0x00); + WriteIhex(f, (Prog.mcu->configurationWord >> 0) & 0xff); + WriteIhex(f, (Prog.mcu->configurationWord >> 8) & 0xff); + } + FinishIhex(f); + + // end of file record + fprintf(f, ":00000001FF\n"); +} + +//----------------------------------------------------------------------------- +// Generate code to write an 8-bit value to a particular register. Takes care +// of the bank switching if necessary; assumes that code is called in bank +// 0. +//----------------------------------------------------------------------------- +static void WriteRegister(DWORD reg, BYTE val) +{ + if(reg & 0x080) Instruction(OP_BSF, REG_STATUS, STATUS_RP0); + if(reg & 0x100) Instruction(OP_BSF, REG_STATUS, STATUS_RP1); + + Instruction(OP_MOVLW, val, 0); + Instruction(OP_MOVWF, (reg & 0x7f), 0); + + if(reg & 0x080) Instruction(OP_BCF, REG_STATUS, STATUS_RP0); + if(reg & 0x100) Instruction(OP_BCF, REG_STATUS, STATUS_RP1); +} + +//----------------------------------------------------------------------------- +// Call a subroutine, that might be in an arbitrary page, and then put +// PCLATH back where we want it. +//----------------------------------------------------------------------------- +static void CallWithPclath(DWORD addr) +{ + // Set up PCLATH for the jump, and then do it. + Instruction(OP_MOVLW, FWD_HI(addr), 0); + Instruction(OP_MOVWF, REG_PCLATH, 0); + Instruction(OP_CALL, FWD_LO(addr), 0); + + // Restore PCLATH to something appropriate for our page. (We have + // already made fairly sure that we will never try to compile across + // a page boundary.) + Instruction(OP_MOVLW, (PicProgWriteP >> 8), 0); + Instruction(OP_MOVWF, REG_PCLATH, 0); +} + +// Note that all of these are single instructions on the PIC; this is not the +// case for their equivalents on the AVR! +#define SetBit(reg, b) Instruction(OP_BSF, reg, b) +#define ClearBit(reg, b) Instruction(OP_BCF, reg, b) +#define IfBitClear(reg, b) Instruction(OP_BTFSS, reg, b) +#define IfBitSet(reg, b) Instruction(OP_BTFSC, reg, b) +static void CopyBit(DWORD addrDest, int bitDest, DWORD addrSrc, int bitSrc) +{ + IfBitSet(addrSrc, bitSrc); + SetBit(addrDest, bitDest); + IfBitClear(addrSrc, bitSrc); + ClearBit(addrDest, bitDest); +} + +//----------------------------------------------------------------------------- +// Handle an IF statement. Flow continues to the first instruction generated +// by this function if the condition is true, else it jumps to the given +// address (which is an FwdAddress, so not yet assigned). Called with IntPc +// on the IF statement, returns with IntPc on the END IF. +//----------------------------------------------------------------------------- +static void CompileIfBody(DWORD condFalse) +{ + IntPc++; + CompileFromIntermediate(FALSE); + if(IntCode[IntPc].op == INT_ELSE) { + IntPc++; + DWORD endBlock = AllocFwdAddr(); + Instruction(OP_GOTO, endBlock, 0); + + FwdAddrIsNow(condFalse); + CompileFromIntermediate(FALSE); + FwdAddrIsNow(endBlock); + } else { + FwdAddrIsNow(condFalse); + } + + if(IntCode[IntPc].op != INT_END_IF) oops(); +} + +//----------------------------------------------------------------------------- +// Compile the intermediate code to PIC16 native code. +//----------------------------------------------------------------------------- +static void CompileFromIntermediate(BOOL topLevel) +{ + DWORD addr, addr2; + int bit, bit2; + DWORD addrl, addrh; + DWORD addrl2, addrh2; + DWORD addrl3, addrh3; + + // Keep track of which 2k section we are using. When it looks like we + // are about to run out, fill with nops and move on to the next one. + DWORD section = 0; + + for(; IntPc < IntCodeLen; IntPc++) { + // Try for a margin of about 400 words, which is a little bit + // wasteful but considering that the formatted output commands + // are huge, probably necessary. Of course if we are in our + // last section then it is silly to do that, either we make it + // or we're screwed... + if(topLevel && (((PicProgWriteP + 400) >> 11) != section) && + ((PicProgWriteP + 400) < Prog.mcu->flashWords)) + { + + // Jump to the beginning of the next section + Instruction(OP_MOVLW, (PicProgWriteP >> 8) + (1<<3), 0); + Instruction(OP_MOVWF, REG_PCLATH, 0); + Instruction(OP_GOTO, 0, 0); + // Then, just burn the last of this section with NOPs. + while((PicProgWriteP >> 11) == section) { + Instruction(OP_MOVLW, 0xab, 0); + } + section = (PicProgWriteP >> 11); + // And now PCLATH is set up, so everything in our new section + // should just work + } + IntOp *a = &IntCode[IntPc]; + switch(a->op) { + case INT_SET_BIT: + MemForSingleBit(a->name1, FALSE, &addr, &bit); + SetBit(addr, bit); + break; + + case INT_CLEAR_BIT: + MemForSingleBit(a->name1, FALSE, &addr, &bit); + ClearBit(addr, bit); + break; + + case INT_COPY_BIT_TO_BIT: + MemForSingleBit(a->name1, FALSE, &addr, &bit); + MemForSingleBit(a->name2, FALSE, &addr2, &bit2); + CopyBit(addr, bit, addr2, bit2); + break; + + case INT_SET_VARIABLE_TO_LITERAL: + MemForVariable(a->name1, &addrl, &addrh); + WriteRegister(addrl, a->literal & 0xff); + WriteRegister(addrh, a->literal >> 8); + break; + + case INT_INCREMENT_VARIABLE: { + MemForVariable(a->name1, &addrl, &addrh); + DWORD noCarry = AllocFwdAddr(); + Instruction(OP_INCFSZ, addrl, DEST_F); + Instruction(OP_GOTO, noCarry, 0); + Instruction(OP_INCF, addrh, DEST_F); + FwdAddrIsNow(noCarry); + break; + } + case INT_IF_BIT_SET: { + DWORD condFalse = AllocFwdAddr(); + MemForSingleBit(a->name1, TRUE, &addr, &bit); + IfBitClear(addr, bit); + Instruction(OP_GOTO, condFalse, 0); + CompileIfBody(condFalse); + break; + } + case INT_IF_BIT_CLEAR: { + DWORD condFalse = AllocFwdAddr(); + MemForSingleBit(a->name1, TRUE, &addr, &bit); + IfBitSet(addr, bit); + Instruction(OP_GOTO, condFalse, 0); + CompileIfBody(condFalse); + break; + } + case INT_IF_VARIABLE_LES_LITERAL: { + DWORD notTrue = AllocFwdAddr(); + DWORD isTrue = AllocFwdAddr(); + DWORD lsbDecides = AllocFwdAddr(); + + // V = Rd7*(Rr7')*(R7') + (Rd7')*Rr7*R7 ; but only one of the + // product terms can be true, and we know which at compile + // time + BYTE litH = (a->literal >> 8); + BYTE litL = (a->literal & 0xff); + + MemForVariable(a->name1, &addrl, &addrh); + + // var - lit + Instruction(OP_MOVLW, litH, 0); + Instruction(OP_SUBWF, addrh, DEST_W); + IfBitSet(REG_STATUS, STATUS_Z); + Instruction(OP_GOTO, lsbDecides, 0); + Instruction(OP_MOVWF, Scratch0, 0); + if(litH & 0x80) { + Instruction(OP_COMF, addrh, DEST_W); + Instruction(OP_ANDWF, Scratch0, DEST_W); + Instruction(OP_XORWF, Scratch0, DEST_F); + } else { + Instruction(OP_COMF, Scratch0, DEST_W); + Instruction(OP_ANDWF, addrh, DEST_W); + Instruction(OP_XORWF, Scratch0, DEST_F); + } + IfBitSet(Scratch0, 7); // var - lit < 0, var < lit + Instruction(OP_GOTO, isTrue, 0); + Instruction(OP_GOTO, notTrue, 0); + + FwdAddrIsNow(lsbDecides); + + // var - lit < 0 + // var < lit + Instruction(OP_MOVLW, litL, 0); + Instruction(OP_SUBWF, addrl, DEST_W); + IfBitClear(REG_STATUS, STATUS_C); + Instruction(OP_GOTO, isTrue, 0); + Instruction(OP_GOTO, notTrue, 0); + + FwdAddrIsNow(isTrue); + CompileIfBody(notTrue); + break; + } + case INT_IF_VARIABLE_EQUALS_VARIABLE: { + DWORD notEqual = AllocFwdAddr(); + + MemForVariable(a->name1, &addrl, &addrh); + MemForVariable(a->name2, &addrl2, &addrh2); + Instruction(OP_MOVF, addrl, DEST_W); + Instruction(OP_SUBWF, addrl2, DEST_W); + IfBitClear(REG_STATUS, STATUS_Z); + Instruction(OP_GOTO, notEqual, 0); + Instruction(OP_MOVF, addrh, DEST_W); + Instruction(OP_SUBWF, addrh2, DEST_W); + IfBitClear(REG_STATUS, STATUS_Z); + Instruction(OP_GOTO, notEqual, 0); + CompileIfBody(notEqual); + break; + } + case INT_IF_VARIABLE_GRT_VARIABLE: { + DWORD notTrue = AllocFwdAddr(); + DWORD isTrue = AllocFwdAddr(); + DWORD lsbDecides = AllocFwdAddr(); + + MemForVariable(a->name1, &addrl, &addrh); + MemForVariable(a->name2, &addrl2, &addrh2); + + // first, a signed comparison of the high octets, which is + // a huge pain on the PIC16 + DWORD iu = addrh2, ju = addrh; + DWORD signa = Scratch0; + DWORD signb = Scratch1; + + Instruction(OP_COMF, ju, DEST_W); + Instruction(OP_MOVWF, signb, 0); + + Instruction(OP_ANDWF, iu, DEST_W); + Instruction(OP_MOVWF, signa, 0); + + Instruction(OP_MOVF, iu, DEST_W); + Instruction(OP_IORWF, signb, DEST_F); + Instruction(OP_COMF, signb, DEST_F); + + Instruction(OP_MOVF, ju, DEST_W); + Instruction(OP_SUBWF, iu, DEST_W); + IfBitSet(REG_STATUS, STATUS_Z); + Instruction(OP_GOTO, lsbDecides, 0); + + Instruction(OP_ANDWF, signb, DEST_F); + Instruction(OP_MOVWF, Scratch2, 0); + Instruction(OP_COMF, Scratch2, DEST_W); + Instruction(OP_ANDWF, signa, DEST_W); + Instruction(OP_IORWF, signb, DEST_W); + Instruction(OP_XORWF, Scratch2, DEST_F); + IfBitSet(Scratch2, 7); + Instruction(OP_GOTO, isTrue, 0); + + Instruction(OP_GOTO, notTrue, 0); + + FwdAddrIsNow(lsbDecides); + Instruction(OP_MOVF, addrl, DEST_W); + Instruction(OP_SUBWF, addrl2, DEST_W); + IfBitClear(REG_STATUS, STATUS_C); + Instruction(OP_GOTO, isTrue, 0); + + Instruction(OP_GOTO, notTrue, 0); + + FwdAddrIsNow(isTrue); + CompileIfBody(notTrue); + break; + } + case INT_SET_VARIABLE_TO_VARIABLE: + MemForVariable(a->name1, &addrl, &addrh); + MemForVariable(a->name2, &addrl2, &addrh2); + + Instruction(OP_MOVF, addrl2, DEST_W); + Instruction(OP_MOVWF, addrl, 0); + + Instruction(OP_MOVF, addrh2, DEST_W); + Instruction(OP_MOVWF, addrh, 0); + break; + + // The add and subtract routines must be written to return correct + // results if the destination and one of the operands happen to + // be the same registers (e.g. for B = A - B). + + case INT_SET_VARIABLE_ADD: + MemForVariable(a->name1, &addrl, &addrh); + MemForVariable(a->name2, &addrl2, &addrh2); + MemForVariable(a->name3, &addrl3, &addrh3); + + Instruction(OP_MOVF, addrl2, DEST_W); + Instruction(OP_ADDWF, addrl3, DEST_W); + Instruction(OP_MOVWF, addrl, 0); + ClearBit(Scratch0, 0); + IfBitSet(REG_STATUS, STATUS_C); + SetBit(Scratch0, 0); + + Instruction(OP_MOVF, addrh2, DEST_W); + Instruction(OP_ADDWF, addrh3, DEST_W); + Instruction(OP_MOVWF, addrh, 0); + IfBitSet(Scratch0, 0); + Instruction(OP_INCF, addrh, DEST_F); + break; + + case INT_SET_VARIABLE_SUBTRACT: + MemForVariable(a->name1, &addrl, &addrh); + MemForVariable(a->name2, &addrl2, &addrh2); + MemForVariable(a->name3, &addrl3, &addrh3); + + Instruction(OP_MOVF, addrl3, DEST_W); + Instruction(OP_SUBWF, addrl2, DEST_W); + Instruction(OP_MOVWF, addrl, 0); + ClearBit(Scratch0, 0); + IfBitSet(REG_STATUS, STATUS_C); + SetBit(Scratch0, 0); + + Instruction(OP_MOVF, addrh3, DEST_W); + Instruction(OP_SUBWF, addrh2, DEST_W); + Instruction(OP_MOVWF, addrh, 0); + IfBitClear(Scratch0, 0); // bit is carry / (not borrow) + Instruction(OP_DECF, addrh, DEST_F); + break; + + case INT_SET_VARIABLE_MULTIPLY: + MultiplyNeeded = TRUE; + + MemForVariable(a->name1, &addrl, &addrh); + MemForVariable(a->name2, &addrl2, &addrh2); + MemForVariable(a->name3, &addrl3, &addrh3); + + Instruction(OP_MOVF, addrl2, DEST_W); + Instruction(OP_MOVWF, Scratch0, 0); + Instruction(OP_MOVF, addrh2, DEST_W); + Instruction(OP_MOVWF, Scratch1, 0); + + Instruction(OP_MOVF, addrl3, DEST_W); + Instruction(OP_MOVWF, Scratch2, 0); + Instruction(OP_MOVF, addrh3, DEST_W); + Instruction(OP_MOVWF, Scratch3, 0); + + CallWithPclath(MultiplyRoutineAddress); + + Instruction(OP_MOVF, Scratch2, DEST_W); + Instruction(OP_MOVWF, addrl, 0); + Instruction(OP_MOVF, Scratch3, DEST_W); + Instruction(OP_MOVWF, addrh, 0); + break; + + case INT_SET_VARIABLE_DIVIDE: + DivideNeeded = TRUE; + + MemForVariable(a->name1, &addrl, &addrh); + MemForVariable(a->name2, &addrl2, &addrh2); + MemForVariable(a->name3, &addrl3, &addrh3); + + Instruction(OP_MOVF, addrl2, DEST_W); + Instruction(OP_MOVWF, Scratch0, 0); + Instruction(OP_MOVF, addrh2, DEST_W); + Instruction(OP_MOVWF, Scratch1, 0); + + Instruction(OP_MOVF, addrl3, DEST_W); + Instruction(OP_MOVWF, Scratch2, 0); + Instruction(OP_MOVF, addrh3, DEST_W); + Instruction(OP_MOVWF, Scratch3, 0); + + CallWithPclath(DivideRoutineAddress); + + Instruction(OP_MOVF, Scratch0, DEST_W); + Instruction(OP_MOVWF, addrl, 0); + Instruction(OP_MOVF, Scratch1, DEST_W); + Instruction(OP_MOVWF, addrh, 0); + break; + + case INT_UART_SEND: { + MemForVariable(a->name1, &addrl, &addrh); + MemForSingleBit(a->name2, TRUE, &addr, &bit); + + DWORD noSend = AllocFwdAddr(); + IfBitClear(addr, bit); + Instruction(OP_GOTO, noSend, 0); + + Instruction(OP_MOVF, addrl, DEST_W); + Instruction(OP_MOVWF, REG_TXREG, 0); + + FwdAddrIsNow(noSend); + ClearBit(addr, bit); + + DWORD notBusy = AllocFwdAddr(); + Instruction(OP_BSF, REG_STATUS, STATUS_RP0); + Instruction(OP_BTFSC, REG_TXSTA ^ 0x80, 1); + Instruction(OP_GOTO, notBusy, 0); + + Instruction(OP_BCF, REG_STATUS, STATUS_RP0); + SetBit(addr, bit); + + FwdAddrIsNow(notBusy); + Instruction(OP_BCF, REG_STATUS, STATUS_RP0); + + break; + } + case INT_UART_RECV: { + MemForVariable(a->name1, &addrl, &addrh); + MemForSingleBit(a->name2, TRUE, &addr, &bit); + + ClearBit(addr, bit); + + // If RCIF is still clear, then there's nothing to do; in that + // case jump to the end, and leave the rung-out clear. + DWORD done = AllocFwdAddr(); + IfBitClear(REG_PIR1, 5); + Instruction(OP_GOTO, done, 0); + + // RCIF is set, so we have a character. Read it now. + Instruction(OP_MOVF, REG_RCREG, DEST_W); + Instruction(OP_MOVWF, addrl, 0); + Instruction(OP_CLRF, addrh, 0); + // and set rung-out true + SetBit(addr, bit); + + // And check for errors; need to reset the UART if yes. + DWORD yesError = AllocFwdAddr(); + IfBitSet(REG_RCSTA, 1); // overrun error + Instruction(OP_GOTO, yesError, 0); + IfBitSet(REG_RCSTA, 2); // framing error + Instruction(OP_GOTO, yesError, 0); + + // Neither FERR nor OERR is set, so we're good. + Instruction(OP_GOTO, done, 0); + + FwdAddrIsNow(yesError); + // An error did occur, so flush the FIFO. + Instruction(OP_MOVF, REG_RCREG, DEST_W); + Instruction(OP_MOVF, REG_RCREG, DEST_W); + // And clear and then set CREN, to clear the error flags. + ClearBit(REG_RCSTA, 4); + SetBit(REG_RCSTA, 4); + + FwdAddrIsNow(done); + break; + } + case INT_SET_PWM: { + int target = atoi(a->name2); + + // So the PWM frequency is given by + // target = xtal/(4*prescale*pr2) + // xtal/target = 4*prescale*pr2 + // and pr2 should be made as large as possible to keep + // resolution, so prescale should be as small as possible + + int pr2; + int prescale; + for(prescale = 1;;) { + int dv = 4*prescale*target; + pr2 = (Prog.mcuClock + (dv/2))/dv; + if(pr2 < 3) { + Error(_("PWM frequency too fast.")); + CompileError(); + } + if(pr2 >= 256) { + if(prescale == 1) { + prescale = 4; + } else if(prescale == 4) { + prescale = 16; + } else { + Error(_("PWM frequency too slow.")); + CompileError(); + } + } else { + break; + } + } + + // First scale the input variable from percent to timer units, + // with a multiply and then a divide. + MultiplyNeeded = TRUE; DivideNeeded = TRUE; + MemForVariable(a->name1, &addrl, &addrh); + Instruction(OP_MOVF, addrl, DEST_W); + Instruction(OP_MOVWF, Scratch0, 0); + Instruction(OP_CLRF, Scratch1, 0); + + Instruction(OP_MOVLW, pr2, 0); + Instruction(OP_MOVWF, Scratch2, 0); + Instruction(OP_CLRF, Scratch3, 0); + + CallWithPclath(MultiplyRoutineAddress); + + Instruction(OP_MOVF, Scratch3, DEST_W); + Instruction(OP_MOVWF, Scratch1, 0); + Instruction(OP_MOVF, Scratch2, DEST_W); + Instruction(OP_MOVWF, Scratch0, 0); + Instruction(OP_MOVLW, 100, 0); + Instruction(OP_MOVWF, Scratch2, 0); + Instruction(OP_CLRF, Scratch3, 0); + + CallWithPclath(DivideRoutineAddress); + + Instruction(OP_MOVF, Scratch0, DEST_W); + Instruction(OP_MOVWF, REG_CCPR2L, 0); + + // Only need to do the setup stuff once + MemForSingleBit("$pwm_init", FALSE, &addr, &bit); + DWORD skip = AllocFwdAddr(); + IfBitSet(addr, bit); + Instruction(OP_GOTO, skip, 0); + SetBit(addr, bit); + + // Set up the CCP2 and TMR2 peripherals. + WriteRegister(REG_PR2, (pr2-1)); + WriteRegister(REG_CCP2CON, 0x0c); // PWM mode, ignore lsbs + + BYTE t2con = (1 << 2); // timer 2 on + if(prescale == 1) + t2con |= 0; + else if(prescale == 4) + t2con |= 1; + else if(prescale == 16) + t2con |= 2; + else oops(); + WriteRegister(REG_T2CON, t2con); + + FwdAddrIsNow(skip); + break; + } + +// A quick helper macro to set the banksel bits correctly; this is necessary +// because the EEwhatever registers are all over in the memory maps. +#define EE_REG_BANKSEL(r) \ + if((r) & 0x80) { \ + if(!(m & 0x80)) { \ + m |= 0x80; \ + Instruction(OP_BSF, REG_STATUS, STATUS_RP0); \ + } \ + } else { \ + if(m & 0x80) { \ + m &= ~0x80; \ + Instruction(OP_BCF, REG_STATUS, STATUS_RP0); \ + } \ + } \ + if((r) & 0x100) { \ + if(!(m & 0x100)) { \ + m |= 0x100; \ + Instruction(OP_BSF, REG_STATUS, STATUS_RP1); \ + } \ + } else { \ + if(m & 0x100) { \ + m &= ~0x100; \ + Instruction(OP_BCF, REG_STATUS, STATUS_RP1); \ + } \ + } + + case INT_EEPROM_BUSY_CHECK: { + DWORD isBusy = AllocFwdAddr(); + DWORD done = AllocFwdAddr(); + MemForSingleBit(a->name1, FALSE, &addr, &bit); + + WORD m = 0; + + EE_REG_BANKSEL(REG_EECON1); + IfBitSet(REG_EECON1 ^ m, 1); + Instruction(OP_GOTO, isBusy, 0); + EE_REG_BANKSEL(0); + + IfBitClear(EepromHighByteWaitingAddr, EepromHighByteWaitingBit); + Instruction(OP_GOTO, done, 0); + + // So there is not a write pending, but we have another + // character to transmit queued up. + + EE_REG_BANKSEL(REG_EEADR); + Instruction(OP_INCF, REG_EEADR ^ m, DEST_F); + EE_REG_BANKSEL(0); + Instruction(OP_MOVF, EepromHighByte, DEST_W); + EE_REG_BANKSEL(REG_EEDATA); + Instruction(OP_MOVWF, REG_EEDATA ^ m, 0); + EE_REG_BANKSEL(REG_EECON1); + Instruction(OP_BCF, REG_EECON1 ^ m, 7); + Instruction(OP_BSF, REG_EECON1 ^ m, 2); + Instruction(OP_MOVLW, 0x55, 0); + Instruction(OP_MOVWF, REG_EECON2 ^ m, 0); + Instruction(OP_MOVLW, 0xaa, 0); + Instruction(OP_MOVWF, REG_EECON2 ^ m, 0); + Instruction(OP_BSF, REG_EECON1 ^ m, 1); + + EE_REG_BANKSEL(0); + + ClearBit(EepromHighByteWaitingAddr, EepromHighByteWaitingBit); + + FwdAddrIsNow(isBusy); + // Have to do these explicitly; m is out of date due to jump. + Instruction(OP_BCF, REG_STATUS, STATUS_RP0); + Instruction(OP_BCF, REG_STATUS, STATUS_RP1); + SetBit(addr, bit); + + FwdAddrIsNow(done); + break; + } + case INT_EEPROM_WRITE: { + MemForVariable(a->name1, &addrl, &addrh); + + WORD m = 0; + + SetBit(EepromHighByteWaitingAddr, EepromHighByteWaitingBit); + Instruction(OP_MOVF, addrh, DEST_W); + Instruction(OP_MOVWF, EepromHighByte, 0); + + EE_REG_BANKSEL(REG_EEADR); + Instruction(OP_MOVLW, a->literal, 0); + Instruction(OP_MOVWF, REG_EEADR ^ m, 0); + EE_REG_BANKSEL(0); + Instruction(OP_MOVF, addrl, DEST_W); + EE_REG_BANKSEL(REG_EEDATA); + Instruction(OP_MOVWF, REG_EEDATA ^ m, 0); + EE_REG_BANKSEL(REG_EECON1); + Instruction(OP_BCF, REG_EECON1 ^ m, 7); + Instruction(OP_BSF, REG_EECON1 ^ m, 2); + Instruction(OP_MOVLW, 0x55, 0); + Instruction(OP_MOVWF, REG_EECON2 ^ m, 0); + Instruction(OP_MOVLW, 0xaa, 0); + Instruction(OP_MOVWF, REG_EECON2 ^ m, 0); + Instruction(OP_BSF, REG_EECON1 ^ m, 1); + + EE_REG_BANKSEL(0); + break; + } + case INT_EEPROM_READ: { + int i; + MemForVariable(a->name1, &addrl, &addrh); + WORD m = 0; + for(i = 0; i < 2; i++) { + EE_REG_BANKSEL(REG_EEADR); + Instruction(OP_MOVLW, a->literal+i, 0); + Instruction(OP_MOVWF, REG_EEADR ^ m, 0); + EE_REG_BANKSEL(REG_EECON1); + Instruction(OP_BCF, REG_EECON1 ^ m, 7); + Instruction(OP_BSF, REG_EECON1 ^ m, 0); + EE_REG_BANKSEL(REG_EEDATA); + Instruction(OP_MOVF, REG_EEDATA ^ m , DEST_W); + EE_REG_BANKSEL(0); + if(i == 0) { + Instruction(OP_MOVWF, addrl, 0); + } else { + Instruction(OP_MOVWF, addrh, 0); + } + } + break; + } + case INT_READ_ADC: { + BYTE adcs; + + MemForVariable(a->name1, &addrl, &addrh); + + if(Prog.mcuClock > 5000000) { + adcs = 2; // 32*Tosc + } else if(Prog.mcuClock > 1250000) { + adcs = 1; // 8*Tosc + } else { + adcs = 0; // 2*Tosc + } + + int goPos, chsPos; + if(McuIs("Microchip PIC16F887 40-PDIP") || + McuIs("Microchip PIC16F886 28-PDIP or 28-SOIC")) + { + goPos = 1; + chsPos = 2; + } else { + goPos = 2; + chsPos = 3; + } + WriteRegister(REG_ADCON0, (BYTE) + ((adcs << 6) | + (MuxForAdcVariable(a->name1) << chsPos) | + (0 << goPos) | // don't start yet + // bit 1 unimplemented + (1 << 0)) // A/D peripheral on + ); + + WriteRegister(REG_ADCON1, + (1 << 7) | // right-justified + (0 << 0) // for now, all analog inputs + ); + if(strcmp(Prog.mcu->mcuName, + "Microchip PIC16F88 18-PDIP or 18-SOIC")==0) + { + WriteRegister(REG_ANSEL, 0x7f); + } + if(McuIs("Microchip PIC16F887 40-PDIP") || + McuIs("Microchip PIC16F886 28-PDIP or 28-SOIC")) + { + WriteRegister(REG_ANSEL, 0xff); + WriteRegister(REG_ANSELH, 0x3f); + } + + // need to wait Tacq (about 20 us) for mux, S/H etc. to settle + int cyclesToWait = ((Prog.mcuClock / 4) * 20) / 1000000; + cyclesToWait /= 3; + if(cyclesToWait < 1) cyclesToWait = 1; + + Instruction(OP_MOVLW, cyclesToWait, 0); + Instruction(OP_MOVWF, Scratch1, 0); + DWORD wait = PicProgWriteP; + Instruction(OP_DECFSZ, Scratch1, DEST_F); + Instruction(OP_GOTO, wait, 0); + + SetBit(REG_ADCON0, goPos); + DWORD spin = PicProgWriteP; + IfBitSet(REG_ADCON0, goPos); + Instruction(OP_GOTO, spin, 0); + + Instruction(OP_MOVF, REG_ADRESH, DEST_W); + Instruction(OP_MOVWF, addrh, 0); + + Instruction(OP_BSF, REG_STATUS, STATUS_RP0); + Instruction(OP_MOVF, REG_ADRESL ^ 0x80, DEST_W); + Instruction(OP_BCF, REG_STATUS, STATUS_RP0); + Instruction(OP_MOVWF, addrl, 0); + + // hook those pins back up to the digital inputs in case + // some of them are used that way + WriteRegister(REG_ADCON1, + (1 << 7) | // right-justify A/D result + (6 << 0) // all digital inputs + ); + if(strcmp(Prog.mcu->mcuName, + "Microchip PIC16F88 18-PDIP or 18-SOIC")==0) + { + WriteRegister(REG_ANSEL, 0x00); + } + if(McuIs("Microchip PIC16F887 40-PDIP") || + McuIs("Microchip PIC16F886 28-PDIP or 28-SOIC")) + { + WriteRegister(REG_ANSEL, 0x00); + WriteRegister(REG_ANSELH, 0x00); + } + break; + } + case INT_END_IF: + case INT_ELSE: + return; + + case INT_SIMULATE_NODE_STATE: + case INT_COMMENT: + break; + + default: + oops(); + break; + } + if(((PicProgWriteP >> 11) != section) && topLevel) { + // This is particularly prone to happening in the last section, + // if the program doesn't fit (since we won't have attempted + // to add padding). + Error(_("Internal error relating to PIC paging; make program " + "smaller or reshuffle it.")); + CompileError(); + } + } +} + +//----------------------------------------------------------------------------- +// Configure Timer1 and Ccp1 to generate the periodic `cycle' interrupt +// that triggers all the ladder logic processing. We will always use 16-bit +// Timer1, with the prescaler configured appropriately. +//----------------------------------------------------------------------------- +static void ConfigureTimer1(int cycleTimeMicroseconds) +{ + int divisor = 1; + int countsPerCycle; + + while(divisor < 16) { + int timerRate = (Prog.mcuClock / (4*divisor)); // hertz + double timerPeriod = 1e6 / timerRate; // timer period, us + countsPerCycle = (int)(cycleTimeMicroseconds / timerPeriod); + + if(countsPerCycle < 1000) { + Error(_("Cycle time too fast; increase cycle time, or use faster " + "crystal.")); + CompileError(); + } else if(countsPerCycle > 0xffff) { + if(divisor >= 8) { + Error( + _("Cycle time too slow; decrease cycle time, or use slower " + "crystal.")); + CompileError(); + } + } else { + break; + } + divisor *= 2; + } + + WriteRegister(REG_CCPR1L, countsPerCycle & 0xff); + WriteRegister(REG_CCPR1H, countsPerCycle >> 8); + + WriteRegister(REG_TMR1L, 0); + WriteRegister(REG_TMR1H, 0); + + BYTE t1con = 0; + // set up prescaler + if(divisor == 1) t1con |= 0x00; + else if(divisor == 2) t1con |= 0x10; + else if(divisor == 4) t1con |= 0x20; + else if(divisor == 8) t1con |= 0x30; + else oops(); + // enable clock, internal source + t1con |= 0x01; + WriteRegister(REG_T1CON, t1con); + + BYTE ccp1con; + // compare mode, reset TMR1 on trigger + ccp1con = 0x0b; + WriteRegister(REG_CCP1CON, ccp1con); +} + +//----------------------------------------------------------------------------- +// Write a subroutine to do a 16x16 signed multiply. One operand in +// Scratch1:Scratch0, other in Scratch3:Scratch2, result in Scratch3:Scratch2. +//----------------------------------------------------------------------------- +static void WriteMultiplyRoutine(void) +{ + DWORD result3 = Scratch5; + DWORD result2 = Scratch4; + DWORD result1 = Scratch3; + DWORD result0 = Scratch2; + + DWORD multiplicand0 = Scratch0; + DWORD multiplicand1 = Scratch1; + + DWORD counter = Scratch6; + + DWORD dontAdd = AllocFwdAddr(); + DWORD top; + + FwdAddrIsNow(MultiplyRoutineAddress); + + Instruction(OP_CLRF, result3, 0); + Instruction(OP_CLRF, result2, 0); + Instruction(OP_BCF, REG_STATUS, STATUS_C); + Instruction(OP_RRF, result1, DEST_F); + Instruction(OP_RRF, result0, DEST_F); + + Instruction(OP_MOVLW, 16, 0); + Instruction(OP_MOVWF, counter, 0); + + top = PicProgWriteP; + Instruction(OP_BTFSS, REG_STATUS, STATUS_C); + Instruction(OP_GOTO, dontAdd, 0); + Instruction(OP_MOVF, multiplicand0, DEST_W); + Instruction(OP_ADDWF, result2, DEST_F); + Instruction(OP_BTFSC, REG_STATUS, STATUS_C); + Instruction(OP_INCF, result3, DEST_F); + Instruction(OP_MOVF, multiplicand1, DEST_W); + Instruction(OP_ADDWF, result3, DEST_F); + FwdAddrIsNow(dontAdd); + + + Instruction(OP_BCF, REG_STATUS, STATUS_C); + Instruction(OP_RRF, result3, DEST_F); + Instruction(OP_RRF, result2, DEST_F); + Instruction(OP_RRF, result1, DEST_F); + Instruction(OP_RRF, result0, DEST_F); + + Instruction(OP_DECFSZ, counter, DEST_F); + Instruction(OP_GOTO, top, 0); + + Instruction(OP_RETURN, 0, 0); +} + +//----------------------------------------------------------------------------- +// Write a subroutine to do a 16/16 signed divide. Call with dividend in +// Scratch1:0, divisor in Scratch3:2, and get the result in Scratch1:0. +//----------------------------------------------------------------------------- +static void WriteDivideRoutine(void) +{ + DWORD dividend0 = Scratch0; + DWORD dividend1 = Scratch1; + + DWORD divisor0 = Scratch2; + DWORD divisor1 = Scratch3; + + DWORD remainder0 = Scratch4; + DWORD remainder1 = Scratch5; + + DWORD counter = Scratch6; + DWORD sign = Scratch7; + + DWORD dontNegateDivisor = AllocFwdAddr(); + DWORD dontNegateDividend = AllocFwdAddr(); + DWORD done = AllocFwdAddr(); + DWORD notNegative = AllocFwdAddr(); + DWORD loop; + + FwdAddrIsNow(DivideRoutineAddress); + Instruction(OP_MOVF, dividend1, DEST_W); + Instruction(OP_XORWF, divisor1, DEST_W); + Instruction(OP_MOVWF, sign, 0); + + Instruction(OP_BTFSS, divisor1, 7); + Instruction(OP_GOTO, dontNegateDivisor, 0); + Instruction(OP_COMF, divisor0, DEST_F); + Instruction(OP_COMF, divisor1, DEST_F); + Instruction(OP_INCF, divisor0, DEST_F); + Instruction(OP_BTFSC, REG_STATUS, STATUS_Z); + Instruction(OP_INCF, divisor1, DEST_F); + FwdAddrIsNow(dontNegateDivisor); + + Instruction(OP_BTFSS, dividend1, 7); + Instruction(OP_GOTO, dontNegateDividend, 0); + Instruction(OP_COMF, dividend0, DEST_F); + Instruction(OP_COMF, dividend1, DEST_F); + Instruction(OP_INCF, dividend0, DEST_F); + Instruction(OP_BTFSC, REG_STATUS, STATUS_Z); + Instruction(OP_INCF, dividend1, DEST_F); + FwdAddrIsNow(dontNegateDividend); + + Instruction(OP_CLRF, remainder1, 0); + Instruction(OP_CLRF, remainder0, 0); + + Instruction(OP_BCF, REG_STATUS, STATUS_C); + + Instruction(OP_MOVLW, 17, 0); + Instruction(OP_MOVWF, counter, 0); + + loop = PicProgWriteP; + Instruction(OP_RLF, dividend0, DEST_F); + Instruction(OP_RLF, dividend1, DEST_F); + + Instruction(OP_DECF, counter, DEST_F); + Instruction(OP_BTFSC, REG_STATUS, STATUS_Z); + Instruction(OP_GOTO, done, 0); + + Instruction(OP_RLF, remainder0, DEST_F); + Instruction(OP_RLF, remainder1, DEST_F); + + Instruction(OP_MOVF, divisor0, DEST_W); + Instruction(OP_SUBWF, remainder0, DEST_F); + Instruction(OP_BTFSS, REG_STATUS, STATUS_C); + Instruction(OP_DECF, remainder1, DEST_F); + Instruction(OP_MOVF, divisor1, DEST_W); + Instruction(OP_SUBWF, remainder1, DEST_F); + + Instruction(OP_BTFSS, remainder1, 7); + Instruction(OP_GOTO, notNegative, 0); + + Instruction(OP_MOVF, divisor0, DEST_W); + Instruction(OP_ADDWF, remainder0, DEST_F); + Instruction(OP_BTFSC, REG_STATUS, STATUS_C); + Instruction(OP_INCF, remainder1, DEST_F); + Instruction(OP_MOVF, divisor1, DEST_W); + Instruction(OP_ADDWF, remainder1, DEST_F); + + Instruction(OP_BCF, REG_STATUS, STATUS_C); + Instruction(OP_GOTO, loop, 0); + + FwdAddrIsNow(notNegative); + Instruction(OP_BSF, REG_STATUS, STATUS_C); + Instruction(OP_GOTO, loop, 0); + + FwdAddrIsNow(done); + Instruction(OP_BTFSS, sign, 7); + Instruction(OP_RETURN, 0, 0); + + Instruction(OP_COMF, dividend0, DEST_F); + Instruction(OP_COMF, dividend1, DEST_F); + Instruction(OP_INCF, dividend0, DEST_F); + Instruction(OP_BTFSC, REG_STATUS, STATUS_Z); + Instruction(OP_INCF, dividend1, DEST_F); + Instruction(OP_RETURN, 0, 0); +} + +//----------------------------------------------------------------------------- +// Compile the program to PIC16 code for the currently selected processor +// and write it to the given file. Produce an error message if we cannot +// write to the file, or if there is something inconsistent about the +// program. +//----------------------------------------------------------------------------- +void CompilePic16(char *outFile) +{ + FILE *f = fopen(outFile, "w"); + if(!f) { + Error(_("Couldn't open file '%s'"), outFile); + return; + } + + if(setjmp(CompileErrorBuf) != 0) { + fclose(f); + return; + } + + WipeMemory(); + + AllocStart(); + + Scratch0 = AllocOctetRam(); + Scratch1 = AllocOctetRam(); + Scratch2 = AllocOctetRam(); + Scratch3 = AllocOctetRam(); + Scratch4 = AllocOctetRam(); + Scratch5 = AllocOctetRam(); + Scratch6 = AllocOctetRam(); + Scratch7 = AllocOctetRam(); + + // Allocate the register used to hold the high byte of the EEPROM word + // that's queued up to program, plus the bit to indicate that it is + // valid. + EepromHighByte = AllocOctetRam(); + AllocBitRam(&EepromHighByteWaitingAddr, &EepromHighByteWaitingBit); + + DWORD progStart = AllocFwdAddr(); + // Our boot vectors; not necessary to do it like this, but it lets + // bootloaders rewrite the beginning of the program to do their magic. + // PCLATH is init to 0, but apparently some bootloaders want to see us + // initialize it again. + + Instruction(OP_BCF, REG_PCLATH, 3); + Instruction(OP_BCF, REG_PCLATH, 4); + Instruction(OP_GOTO, progStart, 0); + Instruction(OP_NOP, 0, 0); + Instruction(OP_NOP, 0, 0); + Instruction(OP_NOP, 0, 0); + Instruction(OP_NOP, 0, 0); + Instruction(OP_NOP, 0, 0); + FwdAddrIsNow(progStart); + + // Now zero out the RAM + Instruction(OP_MOVLW, Prog.mcu->ram[0].start + 8, 0); + Instruction(OP_MOVWF, REG_FSR, 0); + Instruction(OP_MOVLW, Prog.mcu->ram[0].len - 8, 0); + Instruction(OP_MOVWF, Scratch0, 0); + + DWORD zeroMem = PicProgWriteP; + Instruction(OP_CLRF, REG_INDF, 0); + Instruction(OP_INCF, REG_FSR, DEST_F); + Instruction(OP_DECFSZ, Scratch0, DEST_F); + Instruction(OP_GOTO, zeroMem, 0); + + DivideRoutineAddress = AllocFwdAddr(); + DivideNeeded = FALSE; + MultiplyRoutineAddress = AllocFwdAddr(); + MultiplyNeeded = FALSE; + + ConfigureTimer1(Prog.cycleTime); + + // Set up the TRISx registers (direction). 1 means tri-stated (input). + BYTE isInput[MAX_IO_PORTS], isOutput[MAX_IO_PORTS]; + BuildDirectionRegisters(isInput, isOutput); + + if(McuIs("Microchip PIC16F877 40-PDIP") || + McuIs("Microchip PIC16F819 18-PDIP or 18-SOIC") || + McuIs("Microchip PIC16F88 18-PDIP or 18-SOIC") || + McuIs("Microchip PIC16F876 28-PDIP or 28-SOIC") || + McuIs("Microchip PIC16F887 40-PDIP") || + McuIs("Microchip PIC16F886 28-PDIP or 28-SOIC")) + { + REG_EECON1 = 0x18c; + REG_EECON2 = 0x18d; + REG_EEDATA = 0x10c; + REG_EEADR = 0x10d; + } else if(McuIs("Microchip PIC16F628 18-PDIP or 18-SOIC")) { + REG_EECON1 = 0x9c; + REG_EECON2 = 0x9d; + REG_EEDATA = 0x9a; + REG_EEADR = 0x9b; + } else { + oops(); + } + + if(McuIs("Microchip PIC16F887 40-PDIP") || + McuIs("Microchip PIC16F886 28-PDIP or 28-SOIC")) + { + REG_ANSEL = 0x188; + REG_ANSELH = 0x189; + } else { + REG_ANSEL = 0x9b; + } + + if(strcmp(Prog.mcu->mcuName, "Microchip PIC16F877 40-PDIP")==0) { + // This is a nasty special case; one of the extra bits in TRISE + // enables the PSP, and must be kept clear (set here as will be + // inverted). + isOutput[4] |= 0xf8; + } + + if(strcmp(Prog.mcu->mcuName, "Microchip PIC16F877 40-PDIP")==0 || + strcmp(Prog.mcu->mcuName, "Microchip PIC16F819 18-PDIP or 18-SOIC")==0 || + strcmp(Prog.mcu->mcuName, "Microchip PIC16F876 28-PDIP or 28-SOIC")==0) + { + // The GPIOs that can also be A/D inputs default to being A/D + // inputs, so turn that around + WriteRegister(REG_ADCON1, + (1 << 7) | // right-justify A/D result + (6 << 0) // all digital inputs + ); + } + + if(strcmp(Prog.mcu->mcuName, "Microchip PIC16F88 18-PDIP or 18-SOIC")==0) { + WriteRegister(REG_ANSEL, 0x00); // all digital inputs + } + + if(strcmp(Prog.mcu->mcuName, "Microchip PIC16F628 18-PDIP or 18-SOIC")==0) { + // This is also a nasty special case; the comparators on the + // PIC16F628 are enabled by default and need to be disabled, or + // else the PORTA GPIOs don't work. + WriteRegister(REG_CMCON, 0x07); + } + + if(McuIs("Microchip PIC16F887 40-PDIP") || + McuIs("Microchip PIC16F886 28-PDIP or 28-SOIC")) + { + WriteRegister(REG_ANSEL, 0x00); // all digital inputs + WriteRegister(REG_ANSELH, 0x00); // all digital inputs + } + + if(PwmFunctionUsed()) { + // Need to clear TRIS bit corresponding to PWM pin + int i; + for(i = 0; i < Prog.mcu->pinCount; i++) { + if(Prog.mcu->pinInfo[i].pin == Prog.mcu->pwmNeedsPin) { + McuIoPinInfo *iop = &(Prog.mcu->pinInfo[i]); + isOutput[iop->port - 'A'] |= (1 << iop->bit); + break; + } + } + if(i == Prog.mcu->pinCount) oops(); + } + + int i; + for(i = 0; Prog.mcu->dirRegs[i] != 0; i++) { + WriteRegister(Prog.mcu->outputRegs[i], 0x00); + WriteRegister(Prog.mcu->dirRegs[i], ~isOutput[i]); + } + + if(UartFunctionUsed()) { + if(Prog.baudRate == 0) { + Error(_("Zero baud rate not possible.")); + fclose(f); + return; + } + + // So now we should set up the UART. First let us calculate the + // baud rate; there is so little point in the fast baud rates that + // I won't even bother, so + // bps = Fosc/(64*(X+1)) + // bps*64*(X + 1) = Fosc + // X = Fosc/(bps*64)-1 + // and round, don't truncate + int divisor = (Prog.mcuClock + Prog.baudRate*32)/(Prog.baudRate*64) - 1; + + double actual = Prog.mcuClock/(64.0*(divisor+1)); + double percentErr = 100*(actual - Prog.baudRate)/Prog.baudRate; + + if(fabs(percentErr) > 2) { + ComplainAboutBaudRateError(divisor, actual, percentErr); + } + if(divisor > 255) ComplainAboutBaudRateOverflow(); + + WriteRegister(REG_SPBRG, divisor); + WriteRegister(REG_TXSTA, 0x20); // only TXEN set + WriteRegister(REG_RCSTA, 0x90); // only SPEN, CREN set + } + + DWORD top = PicProgWriteP; + + IfBitClear(REG_PIR1, 2); + Instruction(OP_GOTO, PicProgWriteP - 1, 0); + + Instruction(OP_BCF, REG_PIR1, 2); + + Instruction(OP_CLRWDT, 0, 0); + IntPc = 0; + CompileFromIntermediate(TRUE); + + MemCheckForErrorsPostCompile(); + + // This is probably a big jump, so give it PCLATH. + Instruction(OP_CLRF, REG_PCLATH, 0); + Instruction(OP_GOTO, top, 0); + + // Once again, let us make sure not to put stuff on a page boundary + if((PicProgWriteP >> 11) != ((PicProgWriteP + 150) >> 11)) { + DWORD section = (PicProgWriteP >> 11); + // Just burn the last of this section with NOPs. + while((PicProgWriteP >> 11) == section) { + Instruction(OP_MOVLW, 0xab, 0); + } + } + + if(MultiplyNeeded) WriteMultiplyRoutine(); + if(DivideNeeded) WriteDivideRoutine(); + + WriteHexFile(f); + fclose(f); + + char str[MAX_PATH+500]; + sprintf(str, _("Compile successful; wrote IHEX for PIC16 to '%s'.\r\n\r\n" + "Configuration word (fuses) has been set for crystal oscillator, BOD " + "enabled, LVP disabled, PWRT enabled, all code protection off.\r\n\r\n" + "Used %d/%d words of program flash (chip %d%% full)."), + outFile, PicProgWriteP, Prog.mcu->flashWords, + (100*PicProgWriteP)/Prog.mcu->flashWords); + CompileSuccessfulMessage(str); +} |