1403 lines
47 KiB
C++
1403 lines
47 KiB
C++
//-----------------------------------------------------------------------------
|
|
// 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 <windows.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);
|
|
}
|