//----------------------------------------------------------------------------- // 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 . //------ // // An AVR 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 #include #include #include #include #include #include "ldmicro.h" #include "intcode.h" // not complete; just what I need typedef enum AvrOpTag { OP_VACANT, OP_ADC, OP_ADD, OP_ASR, OP_BRCC, OP_BRCS, OP_BREQ, OP_BRGE, OP_BRLO, OP_BRLT, OP_BRNE, OP_CBR, OP_CLC, OP_CLR, OP_COM, OP_CP, OP_CPC, OP_DEC, OP_EOR, OP_ICALL, OP_IJMP, OP_INC, OP_LDI, OP_LD_X, OP_MOV, OP_OUT, OP_RCALL, OP_RET, OP_RETI, OP_RJMP, OP_ROR, OP_SEC, OP_SBC, OP_SBCI, OP_SBR, OP_SBRC, OP_SBRS, OP_ST_X, OP_SUB, OP_SUBI, OP_TST, OP_WDR, } AvrOp; typedef struct AvrInstructionTag { AvrOp op; DWORD arg1; DWORD arg2; } AvrInstruction; #define MAX_PROGRAM_LEN 128*1024 static AvrInstruction AvrProg[MAX_PROGRAM_LEN]; static DWORD AvrProgWriteP; // For yet unresolved references in jumps static DWORD FwdAddrCount; // Fancier: can specify a forward reference to the high or low octet of a // 16-bit address, which is useful for indirect jumps. #define FWD_LO(x) ((x) | 0x20000000) #define FWD_HI(x) ((x) | 0x40000000) // Address to jump to when we finish one PLC cycle static DWORD BeginningOfCycleAddr; // Address of the multiply subroutine, and whether we will have to include it static DWORD MultiplyAddress; static BOOL MultiplyUsed; // and also divide static DWORD DivideAddress; static BOOL DivideUsed; // For EEPROM: we queue up characters to send in 16-bit words (corresponding // to the integer variables), but we can actually just program 8 bits at a // time, so we need to store the high byte somewhere while we wait. static DWORD EepromHighByte; static DWORD EepromHighByteWaitingAddr; static int EepromHighByteWaitingBit; // Some useful registers, unfortunately many of which are in different places // on different AVRs! I consider this a terrible design choice by Atmel. static DWORD REG_TIMSK; static DWORD REG_TIFR; #define REG_OCR1AH 0x4b #define REG_OCR1AL 0x4a #define REG_TCCR1A 0x4f #define REG_TCCR1B 0x4e #define REG_SPH 0x5e #define REG_SPL 0x5d #define REG_ADMUX 0x27 #define REG_ADCSRA 0x26 #define REG_ADCL 0x24 #define REG_ADCH 0x25 static DWORD REG_UBRRH; static DWORD REG_UBRRL; static DWORD REG_UCSRB; static DWORD REG_UCSRA; static DWORD REG_UDR; #define REG_OCR2 0x43 #define REG_TCCR2 0x45 #define REG_EEARH 0x3f #define REG_EEARL 0x3e #define REG_EEDR 0x3d #define REG_EECR 0x3c static int IntPc; static void CompileFromIntermediate(void); //----------------------------------------------------------------------------- // Wipe the program and set the write pointer back to the beginning. Also // flush all the state of the register allocators etc. //----------------------------------------------------------------------------- static void WipeMemory(void) { memset(AvrProg, 0, sizeof(AvrProg)); AvrProgWriteP = 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(AvrOp op, DWORD arg1, DWORD arg2) { if(AvrProg[AvrProgWriteP].op != OP_VACANT) oops(); AvrProg[AvrProgWriteP].op = op; AvrProg[AvrProgWriteP].arg1 = arg1; AvrProg[AvrProgWriteP].arg2 = arg2; AvrProgWriteP++; } //----------------------------------------------------------------------------- // 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(); DWORD i; for(i = 0; i < AvrProgWriteP; i++) { if(AvrProg[i].arg1 == addr) { AvrProg[i].arg1 = AvrProgWriteP; } else if(AvrProg[i].arg2 == FWD_LO(addr)) { AvrProg[i].arg2 = (AvrProgWriteP & 0xff); } else if(AvrProg[i].arg2 == FWD_HI(addr)) { AvrProg[i].arg2 = AvrProgWriteP >> 8; } } } //----------------------------------------------------------------------------- // Given an opcode and its operands, assemble the 16-bit instruction for the // AVR. Check that the operands do not have more bits set than is meaningful; // it is an internal error if they do not. Needs to know what address it is // being assembled to so that it generate relative jumps; internal error if // a relative jump goes out of range. //----------------------------------------------------------------------------- static DWORD Assemble(DWORD addrAt, AvrOp op, DWORD arg1, DWORD arg2) { #define CHECK(v, bits) if((v) != ((v) & ((1 << (bits))-1))) oops() switch(op) { case OP_ASR: CHECK(arg1, 5); CHECK(arg2, 0); return (9 << 12) | (2 << 9) | (arg1 << 4) | 5; case OP_ROR: CHECK(arg1, 5); CHECK(arg2, 0); return (9 << 12) | (2 << 9) | (arg1 << 4) | 7; case OP_ADD: CHECK(arg1, 5); CHECK(arg2, 5); return (3 << 10) | ((arg2 & 0x10) << 5) | (arg1 << 4) | (arg2 & 0x0f); case OP_ADC: CHECK(arg1, 5); CHECK(arg2, 5); return (7 << 10) | ((arg2 & 0x10) << 5) | (arg1 << 4) | (arg2 & 0x0f); case OP_EOR: CHECK(arg1, 5); CHECK(arg2, 5); return (9 << 10) | ((arg2 & 0x10) << 5) | (arg1 << 4) | (arg2 & 0x0f); case OP_SUB: CHECK(arg1, 5); CHECK(arg2, 5); return (6 << 10) | ((arg2 & 0x10) << 5) | (arg1 << 4) | (arg2 & 0x0f); case OP_SBC: CHECK(arg1, 5); CHECK(arg2, 5); return (2 << 10) | ((arg2 & 0x10) << 5) | (arg1 << 4) | (arg2 & 0x0f); case OP_CP: CHECK(arg1, 5); CHECK(arg2, 5); return (5 << 10) | ((arg2 & 0x10) << 5) | (arg1 << 4) | (arg2 & 0x0f); case OP_CPC: CHECK(arg1, 5); CHECK(arg2, 5); return (1 << 10) | ((arg2 & 0x10) << 5) | (arg1 << 4) | (arg2 & 0x0f); case OP_COM: CHECK(arg1, 5); CHECK(arg2, 0); return (9 << 12) | (2 << 9) | (arg1 << 4); case OP_SBR: CHECK(arg1, 5); CHECK(arg2, 8); if(!(arg1 & 0x10)) oops(); arg1 &= ~0x10; return (6 << 12) | ((arg2 & 0xf0) << 4) | (arg1 << 4) | (arg2 & 0x0f); case OP_CBR: CHECK(arg1, 5); CHECK(arg2, 8); if(!(arg1 & 0x10)) oops(); arg1 &= ~0x10; arg2 = ~arg2; return (7 << 12) | ((arg2 & 0xf0) << 4) | (arg1 << 4) | (arg2 & 0x0f); case OP_INC: CHECK(arg1, 5); CHECK(arg2, 0); return (0x4a << 9) | (arg1 << 4) | 3; case OP_DEC: CHECK(arg1, 5); CHECK(arg2, 0); return (0x4a << 9) | (arg1 << 4) | 10; case OP_SUBI: CHECK(arg1, 5); CHECK(arg2, 8); if(!(arg1 & 0x10)) oops(); arg1 &= ~0x10; return (5 << 12) | ((arg2 & 0xf0) << 4) | (arg1 << 4) | (arg2 & 0x0f); case OP_SBCI: CHECK(arg1, 5); CHECK(arg2, 8); if(!(arg1 & 0x10)) oops(); arg1 &= ~0x10; return (4 << 12) | ((arg2 & 0xf0) << 4) | (arg1 << 4) | (arg2 & 0x0f); case OP_TST: CHECK(arg1, 5); CHECK(arg2, 0); return (8 << 10) | ((arg1 & 0x10) << 4) | ((arg1 & 0x10) << 5) | ((arg1 & 0xf) << 4) | (arg1 & 0xf); case OP_SEC: CHECK(arg1, 0); CHECK(arg2, 0); return 0x9408; case OP_CLC: CHECK(arg1, 0); CHECK(arg2, 0); return 0x9488; case OP_IJMP: CHECK(arg1, 0); CHECK(arg2, 0); return 0x9409; case OP_ICALL: CHECK(arg1, 0); CHECK(arg2, 0); return 0x9509; case OP_RJMP: CHECK(arg2, 0); arg1 = arg1 - addrAt - 1; if(((int)arg1) > 2047 || ((int)arg1) < -2048) oops(); arg1 &= (4096-1); return (12 << 12) | arg1; case OP_RCALL: CHECK(arg2, 0); arg1 = arg1 - addrAt - 1; if(((int)arg1) > 2047 || ((int)arg1) < -2048) oops(); arg1 &= (4096-1); return (13 << 12) | arg1; case OP_RETI: return 0x9518; case OP_RET: return 0x9508; case OP_SBRC: CHECK(arg1, 5); CHECK(arg2, 3); return (0x7e << 9) | (arg1 << 4) | arg2; case OP_SBRS: CHECK(arg1, 5); CHECK(arg2, 3); return (0x7f << 9) | (arg1 << 4) | arg2; case OP_BREQ: CHECK(arg2, 0); arg1 = arg1 - addrAt - 1; if(((int)arg1) > 63 || ((int)arg1) < -64) oops(); arg1 &= (128-1); return (0xf << 12) | (arg1 << 3) | 1; case OP_BRNE: CHECK(arg2, 0); arg1 = arg1 - addrAt - 1; if(((int)arg1) > 63 || ((int)arg1) < -64) oops(); arg1 &= (128-1); return (0xf << 12) | (1 << 10) | (arg1 << 3) | 1; case OP_BRLO: CHECK(arg2, 0); arg1 = arg1 - addrAt - 1; if(((int)arg1) > 63 || ((int)arg1) < -64) oops(); arg1 &= (128-1); return (0xf << 12) | (arg1 << 3); case OP_BRGE: CHECK(arg2, 0); arg1 = arg1 - addrAt - 1; if(((int)arg1) > 63 || ((int)arg1) < -64) oops(); arg1 &= (128-1); return (0xf << 12) | (1 << 10) | (arg1 << 3) | 4; case OP_BRLT: CHECK(arg2, 0); arg1 = arg1 - addrAt - 1; if(((int)arg1) > 63 || ((int)arg1) < -64) oops(); arg1 &= (128-1); return (0xf << 12) | (arg1 << 3) | 4; case OP_BRCC: CHECK(arg2, 0); arg1 = arg1 - addrAt - 1; if(((int)arg1) > 63 || ((int)arg1) < -64) oops(); arg1 &= (128-1); return (0xf << 12) | (1 << 10) | (arg1 << 3); case OP_BRCS: CHECK(arg2, 0); arg1 = arg1 - addrAt - 1; if(((int)arg1) > 63 || ((int)arg1) < -64) oops(); arg1 &= (128-1); return (0xf << 12) | (arg1 << 3); case OP_MOV: CHECK(arg1, 5); CHECK(arg2, 5); return (0xb << 10) | ((arg2 & 0x10) << 5) | (arg1 << 4) | (arg2 & 0x0f); case OP_LDI: CHECK(arg1, 5); CHECK(arg2, 8); if(!(arg1 & 0x10)) oops(); arg1 &= ~0x10; return (0xe << 12) | ((arg2 & 0xf0) << 4) | (arg1 << 4) | (arg2 & 0x0f); case OP_LD_X: CHECK(arg1, 5); CHECK(arg2, 0); return (9 << 12) | (arg1 << 4) | 12; case OP_ST_X: CHECK(arg1, 5); CHECK(arg2, 0); return (0x49 << 9) | (arg1 << 4) | 12; case OP_WDR: CHECK(arg1, 0); CHECK(arg2, 0); return 0x95a8; 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; DWORD i; for(i = 0; i < AvrProgWriteP; i++) { DWORD w = Assemble(i, AvrProg[i].op, AvrProg[i].arg1, AvrProg[i].arg2); if(soFarCount == 0) soFarStart = i; soFar[soFarCount++] = (BYTE)(w & 0xff); soFar[soFarCount++] = (BYTE)(w >> 8); if(soFarCount >= 0x10 || i == (AvrProgWriteP-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; } } // end of file record fprintf(f, ":00000001FF\n"); } //----------------------------------------------------------------------------- // Make sure that the given address is loaded in the X register; might not // have to update all of it. //----------------------------------------------------------------------------- static void LoadXAddr(DWORD addr) { Instruction(OP_LDI, 27, (addr >> 8)); Instruction(OP_LDI, 26, (addr & 0xff)); } //----------------------------------------------------------------------------- // Generate code to write an 8-bit value to a particular register. //----------------------------------------------------------------------------- static void WriteMemory(DWORD addr, BYTE val) { LoadXAddr(addr); // load r16 with the data Instruction(OP_LDI, 16, val); // do the store Instruction(OP_ST_X, 16, 0); } //----------------------------------------------------------------------------- // Copy just one bit from one place to another. //----------------------------------------------------------------------------- static void CopyBit(DWORD addrDest, int bitDest, DWORD addrSrc, int bitSrc) { LoadXAddr(addrSrc); Instruction(OP_LD_X, 16, 0); LoadXAddr(addrDest); Instruction(OP_LD_X, 17, 0); Instruction(OP_SBRS, 16, bitSrc); Instruction(OP_CBR, 17, (1 << bitDest)); Instruction(OP_SBRC, 16, bitSrc); Instruction(OP_SBR, 17, (1 << bitDest)); Instruction(OP_ST_X, 17, 0); } //----------------------------------------------------------------------------- // Execute the next instruction only if the specified bit of the specified // memory location is clear (i.e. skip if set). //----------------------------------------------------------------------------- static void IfBitClear(DWORD addr, int bit) { LoadXAddr(addr); Instruction(OP_LD_X, 16, 0); Instruction(OP_SBRS, 16, bit); } //----------------------------------------------------------------------------- // Execute the next instruction only if the specified bit of the specified // memory location is set (i.e. skip if clear). //----------------------------------------------------------------------------- static void IfBitSet(DWORD addr, int bit) { LoadXAddr(addr); Instruction(OP_LD_X, 16, 0); Instruction(OP_SBRC, 16, bit); } //----------------------------------------------------------------------------- // Set a given bit in an arbitrary (not necessarily I/O memory) location in // memory. //----------------------------------------------------------------------------- static void SetBit(DWORD addr, int bit) { LoadXAddr(addr); Instruction(OP_LD_X, 16, 0); Instruction(OP_SBR, 16, (1 << bit)); Instruction(OP_ST_X, 16, 0); } //----------------------------------------------------------------------------- // Clear a given bit in an arbitrary (not necessarily I/O memory) location in // memory. //----------------------------------------------------------------------------- static void ClearBit(DWORD addr, int bit) { LoadXAddr(addr); Instruction(OP_LD_X, 16, 0); Instruction(OP_CBR, 16, (1 << bit)); Instruction(OP_ST_X, 16, 0); } //----------------------------------------------------------------------------- // Configure AVR 16-bit Timer1 to do the timing for us. //----------------------------------------------------------------------------- static void ConfigureTimer1(int cycleTimeMicroseconds) { int divisor = 1; int countsPerCycle; while(divisor <= 1024) { int timerRate = (Prog.mcuClock / divisor); // hertz double timerPeriod = 1e6 / timerRate; // timer period, us countsPerCycle = ((int)(cycleTimeMicroseconds / timerPeriod)) - 1; if(countsPerCycle < 1000) { Error(_("Cycle time too fast; increase cycle time, or use faster " "crystal.")); CompileError(); } else if(countsPerCycle > 0xffff) { if(divisor >= 1024) { Error( _("Cycle time too slow; decrease cycle time, or use slower " "crystal.")); CompileError(); } } else { break; } if(divisor == 1) divisor = 8; else if(divisor == 8) divisor = 64; else if(divisor == 64) divisor = 256; else if(divisor == 256) divisor = 1024; } WriteMemory(REG_TCCR1A, 0x00); // WGM11=0, WGM10=0 int csn; switch(divisor) { case 1: csn = 1; break; case 8: csn = 2; break; case 64: csn = 3; break; case 256: csn = 4; break; case 1024: csn = 5; break; default: oops(); } WriteMemory(REG_TCCR1B, (1<<3) | csn); // WGM13=0, WGM12=1 // `the high byte must be written before the low byte' WriteMemory(REG_OCR1AH, (countsPerCycle - 1) >> 8); WriteMemory(REG_OCR1AL, (countsPerCycle - 1) & 0xff); // Okay, so many AVRs have a register called TIFR, but the meaning of // the bits in that register varies from device to device... if(strcmp(Prog.mcu->mcuName, "Atmel AVR ATmega162 40-PDIP")==0) { WriteMemory(REG_TIMSK, (1 << 6)); } else { WriteMemory(REG_TIMSK, (1 << 4)); } } //----------------------------------------------------------------------------- // Write the basic runtime. We set up our reset vector, configure all the // I/O pins, then set up the timer that does the cycling. Next instruction // written after calling WriteRuntime should be first instruction of the // timer loop (i.e. the PLC logic cycle). //----------------------------------------------------------------------------- static void WriteRuntime(void) { DWORD resetVector = AllocFwdAddr(); int i; Instruction(OP_RJMP, resetVector, 0); // $0000, RESET for(i = 0; i < 34; i++) Instruction(OP_RETI, 0, 0); FwdAddrIsNow(resetVector); // set up the stack, which we use only when we jump to multiply/divide // routine WORD topOfMemory = (WORD)Prog.mcu->ram[0].start + Prog.mcu->ram[0].len - 1; WriteMemory(REG_SPH, topOfMemory >> 8); WriteMemory(REG_SPL, topOfMemory & 0xff); // zero out the memory used for timers, internal relays, etc. LoadXAddr(Prog.mcu->ram[0].start + Prog.mcu->ram[0].len); Instruction(OP_LDI, 16, 0); Instruction(OP_LDI, 18, (Prog.mcu->ram[0].len) & 0xff); Instruction(OP_LDI, 19, (Prog.mcu->ram[0].len) >> 8); DWORD loopZero = AvrProgWriteP; Instruction(OP_SUBI, 26, 1); Instruction(OP_SBCI, 27, 0); Instruction(OP_ST_X, 16, 0); Instruction(OP_SUBI, 18, 1); Instruction(OP_SBCI, 19, 0); Instruction(OP_TST, 18, 0); Instruction(OP_BRNE, loopZero, 0); Instruction(OP_TST, 19, 0); Instruction(OP_BRNE, loopZero, 0); // set up I/O pins BYTE isInput[MAX_IO_PORTS], isOutput[MAX_IO_PORTS]; BuildDirectionRegisters(isInput, isOutput); if(UartFunctionUsed()) { if(Prog.baudRate == 0) { Error(_("Zero baud rate not possible.")); return; } // bps = Fosc/(16*(X+1)) // bps*16*(X + 1) = Fosc // X = Fosc/(bps*16)-1 // and round, don't truncate int divisor = (Prog.mcuClock + Prog.baudRate*8)/(Prog.baudRate*16) - 1; double actual = Prog.mcuClock/(16.0*(divisor+1)); double percentErr = 100*(actual - Prog.baudRate)/Prog.baudRate; if(fabs(percentErr) > 2) { ComplainAboutBaudRateError(divisor, actual, percentErr); } if(divisor > 4095) ComplainAboutBaudRateOverflow(); WriteMemory(REG_UBRRH, divisor >> 8); WriteMemory(REG_UBRRL, divisor & 0xff); WriteMemory(REG_UCSRB, (1 << 4) | (1 << 3)); // RXEN, TXEN for(i = 0; i < Prog.mcu->pinCount; i++) { if(Prog.mcu->pinInfo[i].pin == Prog.mcu->uartNeeds.txPin) { McuIoPinInfo *iop = &(Prog.mcu->pinInfo[i]); isOutput[iop->port - 'A'] |= (1 << iop->bit); break; } } if(i == Prog.mcu->pinCount) oops(); } if(PwmFunctionUsed()) { 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(); } for(i = 0; Prog.mcu->dirRegs[i] != 0; i++) { if(Prog.mcu->dirRegs[i] == 0xff && Prog.mcu->outputRegs[i] == 0xff) { // skip this one, dummy entry for MCUs with I/O ports not // starting from A } else { WriteMemory(Prog.mcu->dirRegs[i], isOutput[i]); // turn on the pull-ups, and drive the outputs low to start WriteMemory(Prog.mcu->outputRegs[i], isInput[i]); } } ConfigureTimer1(Prog.cycleTime); // and now the generated PLC code will follow BeginningOfCycleAddr = AvrProgWriteP; // Okay, so many AVRs have a register called TIFR, but the meaning of // the bits in that register varies from device to device... int tifrBitForOCF1A; if(strcmp(Prog.mcu->mcuName, "Atmel AVR ATmega162 40-PDIP")==0) { tifrBitForOCF1A = 6; } else { tifrBitForOCF1A = 4; } DWORD now = AvrProgWriteP; IfBitClear(REG_TIFR, tifrBitForOCF1A); Instruction(OP_RJMP, now, 0); SetBit(REG_TIFR, tifrBitForOCF1A); Instruction(OP_WDR, 0, 0); } //----------------------------------------------------------------------------- // 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(); if(IntCode[IntPc].op == INT_ELSE) { IntPc++; DWORD endBlock = AllocFwdAddr(); Instruction(OP_RJMP, endBlock, 0); FwdAddrIsNow(condFalse); CompileFromIntermediate(); FwdAddrIsNow(endBlock); } else { FwdAddrIsNow(condFalse); } if(IntCode[IntPc].op != INT_END_IF) oops(); } //----------------------------------------------------------------------------- // Call a subroutine, using either an rcall or an icall depending on what // the processor supports or requires. //----------------------------------------------------------------------------- static void CallSubroutine(DWORD addr) { if(Prog.mcu->avrUseIjmp) { Instruction(OP_LDI, 30, FWD_LO(addr)); Instruction(OP_LDI, 31, FWD_HI(addr)); Instruction(OP_ICALL, 0, 0); } else { Instruction(OP_RCALL, addr, 0); } } //----------------------------------------------------------------------------- // Compile the intermediate code to AVR native code. //----------------------------------------------------------------------------- static void CompileFromIntermediate(void) { DWORD addr, addr2; int bit, bit2; DWORD addrl, addrh; DWORD addrl2, addrh2; for(; IntPc < IntCodeLen; IntPc++) { 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); WriteMemory(addrl, a->literal & 0xff); WriteMemory(addrh, a->literal >> 8); break; case INT_INCREMENT_VARIABLE: { MemForVariable(a->name1, &addrl, &addrh); LoadXAddr(addrl); Instruction(OP_LD_X, 16, 0); LoadXAddr(addrh); Instruction(OP_LD_X, 17, 0); // increment Instruction(OP_INC, 16, 0); DWORD noCarry = AllocFwdAddr(); Instruction(OP_BRNE, noCarry, 0); Instruction(OP_INC, 17, 0); FwdAddrIsNow(noCarry); // X is still addrh Instruction(OP_ST_X, 17, 0); LoadXAddr(addrl); Instruction(OP_ST_X, 16, 0); break; } case INT_IF_BIT_SET: { DWORD condFalse = AllocFwdAddr(); MemForSingleBit(a->name1, TRUE, &addr, &bit); IfBitClear(addr, bit); Instruction(OP_RJMP, condFalse, 0); CompileIfBody(condFalse); break; } case INT_IF_BIT_CLEAR: { DWORD condFalse = AllocFwdAddr(); MemForSingleBit(a->name1, TRUE, &addr, &bit); IfBitSet(addr, bit); Instruction(OP_RJMP, condFalse, 0); CompileIfBody(condFalse); break; } case INT_IF_VARIABLE_LES_LITERAL: { DWORD notTrue = AllocFwdAddr(); MemForVariable(a->name1, &addrl, &addrh); LoadXAddr(addrl); Instruction(OP_LD_X, 16, 0); LoadXAddr(addrh); Instruction(OP_LD_X, 17, 0); Instruction(OP_LDI, 18, (a->literal & 0xff)); Instruction(OP_LDI, 19, (a->literal >> 8)); Instruction(OP_CP, 16, 18); Instruction(OP_CPC, 17, 19); Instruction(OP_BRGE, notTrue, 0); CompileIfBody(notTrue); break; } case INT_IF_VARIABLE_GRT_VARIABLE: case INT_IF_VARIABLE_EQUALS_VARIABLE: { DWORD notTrue = AllocFwdAddr(); MemForVariable(a->name1, &addrl, &addrh); LoadXAddr(addrl); Instruction(OP_LD_X, 16, 0); LoadXAddr(addrh); Instruction(OP_LD_X, 17, 0); MemForVariable(a->name2, &addrl, &addrh); LoadXAddr(addrl); Instruction(OP_LD_X, 18, 0); LoadXAddr(addrh); Instruction(OP_LD_X, 19, 0); if(a->op == INT_IF_VARIABLE_EQUALS_VARIABLE) { Instruction(OP_CP, 16, 18); Instruction(OP_CPC, 17, 19); Instruction(OP_BRNE, notTrue, 0); } else if(a->op == INT_IF_VARIABLE_GRT_VARIABLE) { DWORD isTrue = AllocFwdAddr(); // true if op1 > op2 // false if op1 >= op2 Instruction(OP_CP, 18, 16); Instruction(OP_CPC, 19, 17); Instruction(OP_BRGE, notTrue, 0); } else oops(); CompileIfBody(notTrue); break; } case INT_SET_VARIABLE_TO_VARIABLE: MemForVariable(a->name1, &addrl, &addrh); MemForVariable(a->name2, &addrl2, &addrh2); LoadXAddr(addrl2); Instruction(OP_LD_X, 16, 0); LoadXAddr(addrl); Instruction(OP_ST_X, 16, 0); LoadXAddr(addrh2); Instruction(OP_LD_X, 16, 0); LoadXAddr(addrh); Instruction(OP_ST_X, 16, 0); break; case INT_SET_VARIABLE_DIVIDE: // Do this one separately since the divide routine uses // slightly different in/out registers and I don't feel like // modifying it. MemForVariable(a->name2, &addrl, &addrh); MemForVariable(a->name3, &addrl2, &addrh2); LoadXAddr(addrl2); Instruction(OP_LD_X, 18, 0); LoadXAddr(addrh2); Instruction(OP_LD_X, 19, 0); LoadXAddr(addrl); Instruction(OP_LD_X, 16, 0); LoadXAddr(addrh); Instruction(OP_LD_X, 17, 0); CallSubroutine(DivideAddress); DivideUsed = TRUE; MemForVariable(a->name1, &addrl, &addrh); LoadXAddr(addrl); Instruction(OP_ST_X, 16, 0); LoadXAddr(addrh); Instruction(OP_ST_X, 17, 0); break; case INT_SET_VARIABLE_ADD: case INT_SET_VARIABLE_SUBTRACT: case INT_SET_VARIABLE_MULTIPLY: MemForVariable(a->name2, &addrl, &addrh); MemForVariable(a->name3, &addrl2, &addrh2); LoadXAddr(addrl); Instruction(OP_LD_X, 18, 0); LoadXAddr(addrh); Instruction(OP_LD_X, 19, 0); LoadXAddr(addrl2); Instruction(OP_LD_X, 16, 0); LoadXAddr(addrh2); Instruction(OP_LD_X, 17, 0); if(a->op == INT_SET_VARIABLE_ADD) { Instruction(OP_ADD, 18, 16); Instruction(OP_ADC, 19, 17); } else if(a->op == INT_SET_VARIABLE_SUBTRACT) { Instruction(OP_SUB, 18, 16); Instruction(OP_SBC, 19, 17); } else if(a->op == INT_SET_VARIABLE_MULTIPLY) { CallSubroutine(MultiplyAddress); MultiplyUsed = TRUE; } else oops(); MemForVariable(a->name1, &addrl, &addrh); LoadXAddr(addrl); Instruction(OP_ST_X, 18, 0); LoadXAddr(addrh); Instruction(OP_ST_X, 19, 0); break; case INT_SET_PWM: { int target = atoi(a->name2); // PWM frequency is // target = xtal/(256*prescale) // so not a lot of room for accurate frequency here int prescale; int bestPrescale; int bestError = INT_MAX; int bestFreq; for(prescale = 1;;) { int freq = (Prog.mcuClock + prescale*128)/(prescale*256); int err = abs(freq - target); if(err < bestError) { bestError = err; bestPrescale = prescale; bestFreq = freq; } if(prescale == 1) { prescale = 8; } else if(prescale == 8) { prescale = 64; } else if(prescale == 64) { prescale = 256; } else if(prescale == 256) { prescale = 1024; } else { break; } } if(((double)bestError)/target > 0.05) { Error(_("Target frequency %d Hz, closest achievable is " "%d Hz (warning, >5%% error)."), target, bestFreq); } DivideUsed = TRUE; MultiplyUsed = TRUE; MemForVariable(a->name1, &addrl, &addrh); LoadXAddr(addrl); Instruction(OP_LD_X, 16, 0); Instruction(OP_LDI, 17, 0); Instruction(OP_LDI, 19, 0); Instruction(OP_LDI, 18, 255); CallSubroutine(MultiplyAddress); Instruction(OP_MOV, 17, 19); Instruction(OP_MOV, 16, 18); Instruction(OP_LDI, 19, 0); Instruction(OP_LDI, 18, 100); CallSubroutine(DivideAddress); LoadXAddr(REG_OCR2); Instruction(OP_ST_X, 16, 0); // Setup only happens once MemForSingleBit("$pwm_init", FALSE, &addr, &bit); DWORD skip = AllocFwdAddr(); IfBitSet(addr, bit); Instruction(OP_RJMP, skip, 0); SetBit(addr, bit); BYTE cs; switch(bestPrescale) { case 1: cs = 1; break; case 8: cs = 2; break; case 64: cs = 3; break; case 256: cs = 4; break; case 1024: cs = 5; break; default: oops(); break; } // fast PWM mode, non-inverted operation, given prescale WriteMemory(REG_TCCR2, (1 << 6) | (1 << 3) | (1 << 5) | cs); FwdAddrIsNow(skip); break; } case INT_EEPROM_BUSY_CHECK: { MemForSingleBit(a->name1, FALSE, &addr, &bit); DWORD isBusy = AllocFwdAddr(); DWORD done = AllocFwdAddr(); IfBitSet(REG_EECR, 1); Instruction(OP_RJMP, isBusy, 0); IfBitClear(EepromHighByteWaitingAddr, EepromHighByteWaitingBit); Instruction(OP_RJMP, done, 0); // Just increment EEARH:EEARL, to point to the high byte of // whatever we just wrote the low byte for. LoadXAddr(REG_EEARL); Instruction(OP_LD_X, 16, 0); LoadXAddr(REG_EEARH); Instruction(OP_LD_X, 17, 0); Instruction(OP_INC, 16, 0); DWORD noCarry = AllocFwdAddr(); Instruction(OP_BRNE, noCarry, 0); Instruction(OP_INC, 17, 0); FwdAddrIsNow(noCarry); // X is still REG_EEARH Instruction(OP_ST_X, 17, 0); LoadXAddr(REG_EEARL); Instruction(OP_ST_X, 16, 0); LoadXAddr(EepromHighByte); Instruction(OP_LD_X, 16, 0); LoadXAddr(REG_EEDR); Instruction(OP_ST_X, 16, 0); LoadXAddr(REG_EECR); Instruction(OP_LDI, 16, 0x04); Instruction(OP_ST_X, 16, 0); Instruction(OP_LDI, 16, 0x06); Instruction(OP_ST_X, 16, 0); ClearBit(EepromHighByteWaitingAddr, EepromHighByteWaitingBit); FwdAddrIsNow(isBusy); SetBit(addr, bit); FwdAddrIsNow(done); break; } case INT_EEPROM_READ: { MemForVariable(a->name1, &addrl, &addrh); int i; for(i = 0; i < 2; i++) { WriteMemory(REG_EEARH, ((a->literal+i) >> 8)); WriteMemory(REG_EEARL, ((a->literal+i) & 0xff)); WriteMemory(REG_EECR, 0x01); LoadXAddr(REG_EEDR); Instruction(OP_LD_X, 16, 0); if(i == 0) { LoadXAddr(addrl); } else { LoadXAddr(addrh); } Instruction(OP_ST_X, 16, 0); } break; } case INT_EEPROM_WRITE: MemForVariable(a->name1, &addrl, &addrh); SetBit(EepromHighByteWaitingAddr, EepromHighByteWaitingBit); LoadXAddr(addrh); Instruction(OP_LD_X, 16, 0); LoadXAddr(EepromHighByte); Instruction(OP_ST_X, 16, 0); WriteMemory(REG_EEARH, (a->literal >> 8)); WriteMemory(REG_EEARL, (a->literal & 0xff)); LoadXAddr(addrl); Instruction(OP_LD_X, 16, 0); LoadXAddr(REG_EEDR); Instruction(OP_ST_X, 16, 0); LoadXAddr(REG_EECR); Instruction(OP_LDI, 16, 0x04); Instruction(OP_ST_X, 16, 0); Instruction(OP_LDI, 16, 0x06); Instruction(OP_ST_X, 16, 0); break; case INT_READ_ADC: { MemForVariable(a->name1, &addrl, &addrh); WriteMemory(REG_ADMUX, (0 << 6) | // AREF, internal Vref odd (0 << 5) | // right-adjusted MuxForAdcVariable(a->name1)); // target something around 200 kHz for the ADC clock, for // 25/(200k) or 125 us conversion time, reasonable int divisor = (Prog.mcuClock / 200000); int j = 0; for(j = 1; j <= 7; j++) { if((1 << j) > divisor) break; } BYTE adcsra = (1 << 7) | // ADC enabled (0 << 5) | // not free running (0 << 3) | // no interrupt enabled j; // prescaler setup WriteMemory(REG_ADCSRA, adcsra); WriteMemory(REG_ADCSRA, (BYTE)(adcsra | (1 << 6))); DWORD waitForFinsh = AvrProgWriteP; IfBitSet(REG_ADCSRA, 6); Instruction(OP_RJMP, waitForFinsh, 0); LoadXAddr(REG_ADCL); Instruction(OP_LD_X, 16, 0); LoadXAddr(addrl); Instruction(OP_ST_X, 16, 0); LoadXAddr(REG_ADCH); Instruction(OP_LD_X, 16, 0); LoadXAddr(addrh); Instruction(OP_ST_X, 16, 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_RJMP, noSend, 0); LoadXAddr(addrl); Instruction(OP_LD_X, 16, 0); LoadXAddr(REG_UDR); Instruction(OP_ST_X, 16, 0); FwdAddrIsNow(noSend); ClearBit(addr, bit); DWORD dontSet = AllocFwdAddr(); IfBitSet(REG_UCSRA, 5); // UDRE, is 1 when tx buffer is empty Instruction(OP_RJMP, dontSet, 0); SetBit(addr, bit); FwdAddrIsNow(dontSet); break; } case INT_UART_RECV: { MemForVariable(a->name1, &addrl, &addrh); MemForSingleBit(a->name2, TRUE, &addr, &bit); ClearBit(addr, bit); DWORD noChar = AllocFwdAddr(); IfBitClear(REG_UCSRA, 7); Instruction(OP_RJMP, noChar, 0); SetBit(addr, bit); LoadXAddr(REG_UDR); Instruction(OP_LD_X, 16, 0); LoadXAddr(addrl); Instruction(OP_ST_X, 16, 0); LoadXAddr(addrh); Instruction(OP_LDI, 16, 0); Instruction(OP_ST_X, 16, 0); FwdAddrIsNow(noChar); break; } case INT_END_IF: case INT_ELSE: return; case INT_SIMULATE_NODE_STATE: case INT_COMMENT: break; default: oops(); break; } } } //----------------------------------------------------------------------------- // 16x16 signed multiply, code from Atmel app note AVR200. op1 in r17:16, // op2 in r19:18, result low word goes into r19:18. //----------------------------------------------------------------------------- static void MultiplyRoutine(void) { FwdAddrIsNow(MultiplyAddress); DWORD m16s_1; DWORD m16s_2 = AllocFwdAddr(); Instruction(OP_SUB, 21, 21); Instruction(OP_SUB, 20, 20); Instruction(OP_LDI, 22, 16); m16s_1 = AvrProgWriteP; Instruction(OP_BRCC, m16s_2, 0); Instruction(OP_ADD, 20, 16); Instruction(OP_ADC, 21, 17); FwdAddrIsNow(m16s_2); Instruction(OP_SBRC, 18, 0); Instruction(OP_SUB, 20, 16); Instruction(OP_SBRC, 18, 0); Instruction(OP_SBC, 21, 17); Instruction(OP_ASR, 21, 0); Instruction(OP_ROR, 20, 0); Instruction(OP_ROR, 19, 0); Instruction(OP_ROR, 18, 0); Instruction(OP_DEC, 22, 0); Instruction(OP_BRNE, m16s_1, 0); Instruction(OP_RET, 0, 0); } //----------------------------------------------------------------------------- // 16/16 signed divide, code from the same app note. Dividend in r17:16, // divisor in r19:18, result goes in r17:16 (and remainder in r15:14). //----------------------------------------------------------------------------- static void DivideRoutine(void) { FwdAddrIsNow(DivideAddress); DWORD d16s_1 = AllocFwdAddr(); DWORD d16s_2 = AllocFwdAddr(); DWORD d16s_3; DWORD d16s_4 = AllocFwdAddr(); DWORD d16s_5 = AllocFwdAddr(); DWORD d16s_6 = AllocFwdAddr(); Instruction(OP_MOV, 13, 17); Instruction(OP_EOR, 13, 19); Instruction(OP_SBRS, 17, 7); Instruction(OP_RJMP, d16s_1, 0); Instruction(OP_COM, 17, 0); Instruction(OP_COM, 16, 0); Instruction(OP_SUBI, 16, 0xff); Instruction(OP_SBCI, 17, 0xff); FwdAddrIsNow(d16s_1); Instruction(OP_SBRS, 19, 7); Instruction(OP_RJMP, d16s_2, 0); Instruction(OP_COM, 19, 0); Instruction(OP_COM, 18, 0); Instruction(OP_SUBI, 18, 0xff); Instruction(OP_SBCI, 19, 0xff); FwdAddrIsNow(d16s_2); Instruction(OP_EOR, 14, 14); Instruction(OP_SUB, 15, 15); Instruction(OP_LDI, 20, 17); d16s_3 = AvrProgWriteP; Instruction(OP_ADC, 16, 16); Instruction(OP_ADC, 17, 17); Instruction(OP_DEC, 20, 0); Instruction(OP_BRNE, d16s_5, 0); Instruction(OP_SBRS, 13, 7); Instruction(OP_RJMP, d16s_4, 0); Instruction(OP_COM, 17, 0); Instruction(OP_COM, 16, 0); Instruction(OP_SUBI, 16, 0xff); Instruction(OP_SBCI, 17, 0xff); FwdAddrIsNow(d16s_4); Instruction(OP_RET, 0, 0); FwdAddrIsNow(d16s_5); Instruction(OP_ADC, 14, 14); Instruction(OP_ADC, 15, 15); Instruction(OP_SUB, 14, 18); Instruction(OP_SBC, 15, 19); Instruction(OP_BRCC, d16s_6, 0); Instruction(OP_ADD, 14, 18); Instruction(OP_ADC, 15, 19); Instruction(OP_CLC, 0, 0); Instruction(OP_RJMP, d16s_3, 0); FwdAddrIsNow(d16s_6); Instruction(OP_SEC, 0, 0); Instruction(OP_RJMP, d16s_3, 0); } //----------------------------------------------------------------------------- // Compile the program to REG 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 CompileAvr(char *outFile) { FILE *f = fopen(outFile, "w"); if(!f) { Error(_("Couldn't open file '%s'"), outFile); return; } if(setjmp(CompileErrorBuf) != 0) { fclose(f); return; } // Here we must set up the addresses of some registers that for some // stupid reason move around from AVR to AVR. if(strcmp(Prog.mcu->mcuName, "Atmel AVR ATmega16 40-PDIP")==0 || strcmp(Prog.mcu->mcuName, "Atmel AVR ATmega32 40-PDIP")==0 || strcmp(Prog.mcu->mcuName, "Atmel AVR ATmega162 40-PDIP")==0 || strcmp(Prog.mcu->mcuName, "Atmel AVR ATmega8 28-PDIP")==0) { REG_TIMSK = 0x59; REG_TIFR = 0x58; REG_UBRRH = 0x40; REG_UBRRL = 0x29; REG_UCSRB = 0x2a; REG_UCSRA = 0x2b; REG_UDR = 0x2c; } else { REG_TIMSK = 0x57; REG_TIFR = 0x56; REG_UBRRH = 0x98; REG_UBRRL = 0x99; REG_UCSRB = 0x9a; REG_UCSRA = 0x9b; REG_UDR = 0x9c; } WipeMemory(); MultiplyUsed = FALSE; MultiplyAddress = AllocFwdAddr(); DivideUsed = FALSE; DivideAddress = AllocFwdAddr(); AllocStart(); // Where we hold the high byte to program in EEPROM while the low byte // programs. EepromHighByte = AllocOctetRam(); AllocBitRam(&EepromHighByteWaitingAddr, &EepromHighByteWaitingBit); WriteRuntime(); IntPc = 0; CompileFromIntermediate(); if(Prog.mcu->avrUseIjmp) { Instruction(OP_LDI, 30, (BeginningOfCycleAddr & 0xff)); Instruction(OP_LDI, 31, (BeginningOfCycleAddr >> 8)); Instruction(OP_IJMP, 0, 0); } else { Instruction(OP_RJMP, BeginningOfCycleAddr, 0); } MemCheckForErrorsPostCompile(); if(MultiplyUsed) MultiplyRoutine(); if(DivideUsed) DivideRoutine(); WriteHexFile(f); fclose(f); char str[MAX_PATH+500]; sprintf(str, _("Compile successful; wrote IHEX for AVR to '%s'.\r\n\r\n" "Remember to set the processor configuration (fuses) correctly. " "This does not happen automatically."), outFile); CompileSuccessfulMessage(str); }