summaryrefslogtreecommitdiff
path: root/ldmicro/avr.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'ldmicro/avr.cpp')
-rw-r--r--ldmicro/avr.cpp1402
1 files changed, 1402 insertions, 0 deletions
diff --git a/ldmicro/avr.cpp b/ldmicro/avr.cpp
new file mode 100644
index 0000000..848426d
--- /dev/null
+++ b/ldmicro/avr.cpp
@@ -0,0 +1,1402 @@
+//-----------------------------------------------------------------------------
+// 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/>.
+//------
+//
+// 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 <linuxUI.h>
+#include <stdio.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <math.h>
+
+#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);
+}