📄 tinyplc.c
字号:
/*
* Released under the GNU GPL. See http://www.gnu.org/licenses/gpl.txt
*
* This program is part of TinyPLC
*
* TinyPLC 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 Foundatation; version 2 of the License.
*
* TinyPLC 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.
*/
/*
* My thanks to my employer, Athena Controls (www.athenacontrols.com)
* for allowing me to open source this project.
*/
/*
* TinyPLC kernel
* Processor: Atmel MEGA1281 or Atmel MEGA2561
* Compiler: Codevision AVR C compiler, 1.24.8e
*
* TinyPLC editor/compiler
* Processor: PC-clone, VESA video (mode 0x105, 1024x768x256)
* Compiler: DeSmet C, version 2.51 (PCC version 1.2)
*
* Revision history:
* Programmer Date Comments
* ---------------- --------- ---------------------------------------------
* William Couture 3/19/2006 Original code
* William Couture 9/13/2006 Modify for GPL release
*/
#pragma regalloc-
/* automatic register allocation OFF for now */
#include <mega1281.h>
#include <ctype.h>
#include "tinyplc.h"
REGISTER BYTE linetrue;
REGISTER BYTE tempvar;
REGISTER BYTE r1;
REGISTER BYTE r2;
REGISTER BYTE runflag;
REGISTER BYTE debugflag;
REGISTER WORD program_ip;
REGISTER BYTE program_valid;
REGISTER WORD program_len;
REGISTER BYTE hardrun;
REGISTER BYTE lasthardrun;
#pragma regalloc+
/* now that we have our variables allocated to regs, let the compiler use the rest */
BYTE softrun;
BYTE program[MAX_PROGRAM + 2];
/* +2 for paranoia about R1 and R2 and checksum length */
BYTE message[MAX_MSG_SIZE];
WORD message_ptr;
WORD p_len;
WORD incoming_len;
BYTE message_status;
WORD run_nodecount; /* for debugging */
WORD program_timer;
BYTE program_abort;
eeprom WORD sacrificial_goat; /* do not use EEPROM location 0 due to powerup bug on some chips */
eeprom WORD eeprom_program_len;
eeprom BYTE eeprom_program[MAX_PROGRAM];
eeprom WORD eeprom_program_checksum;
struct bytes_t
{
BYTE b0;
BYTE b1;
BYTE b2;
BYTE b3;
};
struct words_t
{
WORD w0;
WORD w1;
};
struct timercounter_t
{
BYTE b0;
BYTE b1;
WORD w;
};
union
{
struct bytes_t b;
struct words_t w;
struct timercounter_t t;
DWORD v;
BYTE bytes[4];
} dataregs[PLC_MEMORY_LOCS], valdest, val1, val2;
/* make valdest, val1, val2 global to avoid passing */
BYTE initstack[STACKSIZE];
BYTE initstackptr;
BYTE resultstack[STACKSIZE];
BYTE resultstackptr;
volatile DWORD tick_counter;
volatile BYTE status_led_pwm;
BYTE status_led_counter;
volatile WORD seconds_counter;
volatile BYTE seconds_tick;
volatile BYTE newtick;
volatile BYTE xmittick;
#if TOTAL_ANALOGS > 0
enum
{
ADC_SETTLING_1,
ADC_SETTLING_2,
ADC_VALID
};
BYTE adc_state;
BYTE adc_channel;
BYTE adc_tick_counter;
WORD adc_filter[TOTAL_ANALOGS];
#endif
interrupt [TIM0_COMPA] void heartbeat_isr(void)
{
status_led_pwm++;
tick_counter++;
newtick = TRUE;
if (++seconds_counter == HEARTBEAT_FREQUENCY)
{
seconds_counter = 0;
seconds_tick++;
if (program_timer)
if (--program_timer == 0)
program_abort = TRUE;
}
if (RUNSTOP_SWITCH == RUNSTOP_RUN)
hardrun = RUNSTOP_RUN;
else
hardrun = RUNSTOP_STOP;
if (hardrun == RUNSTOP_STOP)
runflag = FALSE;
if (xmittick)
xmittick--;
#if TOTAL_ANALOGS > 0
if (++adc_tick_counter == ADC_CONVERSION_TIME) /* ADC should be ready */
{
adc_tick_counter = 0;
if (ADCSRA & ADCSRA_ADSC) /* DONE bit != 0, not finished */
{
/* worry about ADC internal errors later */
}
else
{
if (adc_state == ADC_VALID)
{
adc_filter[adc_channel] -= adc_filter[adc_channel] / ADC_FILTER_STRENGTH;
adc_filter[adc_channel] += ADCW;
}
}
if (++adc_state > ADC_VALID)
{
adc_state = ADC_SETTLING_1;
if (++adc_channel >= TOTAL_ANALOGS)
adc_channel = 0;
ADMUX = ADMUX_VALUE | adc_channel;
}
ADCSRA = ADCSRA_GO; /* start next ADC conversion */
}
#endif /* TOTAL_ANALOGS */
}
BYTE tx_buf[TXBUF_SIZE];
BYTE tx_buf_in; /* index to next byte to be written into tx_buf[] */
volatile BYTE tx_buf_out; /* index to next byte to be read from tx_buf[] */
BYTE rx_buf[RXBUF_SIZE];
BYTE rx_buf_in; /* index to next byte to be written into rx_buf[] */
BYTE rx_buf_out; /* index to next byte to be read from rx_buf[] */
BYTE rx_byte, rx_temp;
static BYTE stopmsg[5] = { MSG_BYTE0, MSG_BYTE1, MSG_STOP, 0, 0 };
interrupt [USART0_RXC] void usart0_receive(void)
{
BYTE i, j, k;
rx_temp = UCSR0A;
rx_byte = UDR0;
if (!(rx_temp & FRAMING_ERROR))
{
rx_buf[rx_buf_in] = rx_byte;
if (rx_byte == 0)
{
for (i = 0, j = 4, k = rx_buf_in; i < 5; i++, j--)
{
if (rx_buf[k] != stopmsg[j])
break;
k = (k == 0) ? RXBUF_SIZE - 1 : k - 1;
}
if (i == 5)
{
runflag = FALSE;
softrun = FALSE;
}
}
rx_temp = (rx_buf_in == RXBUF_SIZE - 1) ? 0 : rx_buf_in + 1;
if (rx_temp != rx_buf_out)
rx_buf_in = rx_temp;
}
}
interrupt [USART0_TXC] void usart0_tx_complete(void)
{
UCSR0B = UCSR0B_INIT; /* turn off TX, stuff in buffer will finish xmiting */
}
interrupt [USART0_UDRE] void usart0_datareg_empty(void)
{
if (tx_buf_out == tx_buf_in) /* buffer empty */
{
UCSR0B = UCSR0B_FINISH; /* turn off UDRIE0, stuff in buffer will finish xmiting */
}
else
{
UDR0 = tx_buf[tx_buf_out];
tx_buf_out = (tx_buf_out == TXBUF_SIZE - 1) ? 0 : tx_buf_out + 1;
}
}
void xmit_char(BYTE ch)
{
BYTE temp;
DI();
UCSR0B = UCSR0B_XMIT; /* enable transmitter */
temp = (tx_buf_in == TXBUF_SIZE - 1) ? 0 : tx_buf_in + 1;
if (temp != tx_buf_out) /* if buffer not full */
{
tx_buf[tx_buf_in] = ch;
tx_buf_in = temp;
}
EI();
}
void xmit_wait(BYTE ch)
{
BYTE temp;
temp = (tx_buf_in == TXBUF_SIZE - 1) ? 0 : tx_buf_in + 1;
xmittick = 2;
while ((temp == tx_buf_out) && (xmittick)) /* if buffer not full */
;
if (xmittick) /* if not timeout */
xmit_char(ch);
}
void xmit_str(char flash *s)
{
while (*s)
xmit_char(*s++);
}
void system_init(void)
{
PET_WATCHDOG();
WDTCSR = WDTCSR_UNLOCK; /* unlock WDTCSR register to update Watchdog params */
WDTCSR = WDTCSR_SET_WDT_PERIOD; /* set WDT period to 2 seconds */
DDRA = PORTA_DIRECTIONS;
PORTA = PORTA_DEFAULT;
DDRB = PORTB_DIRECTIONS;
PORTB = PORTB_DEFAULT;
DDRC = PORTC_DIRECTIONS;
PORTC = PORTC_DEFAULT;
DDRD = PORTD_DIRECTIONS;
PORTD = PORTD_DEFAULT;
DDRE = PORTE_DIRECTIONS;
PORTE = PORTE_DEFAULT;
DDRF = PORTF_DIRECTIONS;
PORTF = PORTF_DEFAULT;
DDRG = PORTG_DIRECTIONS;
PORTG = PORTG_DEFAULT;
TCCR0A = TCCR0A_INIT; /* init TMR0 for "heartbeat" */
TIMSK0 = TIMSK0_INIT;
OCR0A = TMR0_PERIOD;
TCCR0B = TCCR0B_INIT;
UCSR0C = UCSR0C_INIT; /* init USART 0 */
UCSR0B = UCSR0B_INIT;
UBRR0H = UBRR_VALUE >> 8;
UBRR0L = (UBRR_VALUE & 0xff);
#if TOTAL_ANALOGS > 0
ADCSRA = ADCSRA_INIT; /* init ADC */
DIDR0 = DIDR0_VALUE; /* set pins for analog input */
ADMUX = ADMUX_VALUE | adc_channel;
ADCSRA = ADCSRA_GO; /* start first ADC conversion */
adc_tick_counter = 0;
#endif
EI();
program_timer = 0; /* get ready for incoming message */
program_abort = FALSE;
message_ptr = 0;
message_status = MSG_STATUS_WAITING;
incoming_len = 0xffff; /* invalid! */
}
/* set the real outputs */
void service_outputs(void)
{
/* NOTE: this could be done as a for() loop, such as
* for (i = 0; i < TOTAL_OUTPUTS; i++)
* {
* val = dataregs[FIRST_OUTPUT + i].v ? OUTPUT_ON : OUTPUT_OFF;
* switch (i)
* {
* #if TOTAL_OUTPUTS > 0
* case 0:
* DIG_OUT_0 = val;
* break;
* #endif
* { etc }
* }
* }
* but the CASE statement would still need be setup for the
* number of outputs.
*/
#if TOTAL_OUTPUTS > 0
DIG_OUT_0 = dataregs[FIRST_OUTPUT + 0].v ? OUTPUT_ON : OUTPUT_OFF;
#endif
#if TOTAL_OUTPUTS > 1
DIG_OUT_1 = dataregs[FIRST_OUTPUT + 1].v ? OUTPUT_ON : OUTPUT_OFF;
#endif
#if TOTAL_OUTPUTS > 2
DIG_OUT_2 = dataregs[FIRST_OUTPUT + 2].v ? OUTPUT_ON : OUTPUT_OFF;
#endif
#if TOTAL_OUTPUTS > 3
DIG_OUT_3 = dataregs[FIRST_OUTPUT + 3].v ? OUTPUT_ON : OUTPUT_OFF;
#endif
#if TOTAL_OUTPUTS > 4
DIG_OUT_4 = dataregs[FIRST_OUTPUT + 4].v ? OUTPUT_ON : OUTPUT_OFF;
#endif
#if TOTAL_OUTPUTS > 5
DIG_OUT_5 = dataregs[FIRST_OUTPUT + 5].v ? OUTPUT_ON : OUTPUT_OFF;
#endif
#if TOTAL_OUTPUTS > 6
DIG_OUT_6 = dataregs[FIRST_OUTPUT + 6].v ? OUTPUT_ON : OUTPUT_OFF;
#endif
#if TOTAL_OUTPUTS > 7
DIG_OUT_7 = dataregs[FIRST_OUTPUT + 7].v ? OUTPUT_ON : OUTPUT_OFF;
#endif
#if TOTAL_OUTPUTS > 8
DIG_OUT_8 = dataregs[FIRST_OUTPUT + 8].v ? OUTPUT_ON : OUTPUT_OFF;
#endif
#if TOTAL_OUTPUTS > 9
DIG_OUT_9 = dataregs[FIRST_OUTPUT + 9].v ? OUTPUT_ON : OUTPUT_OFF;
#endif
#if TOTAL_OUTPUTS > 10
DIG_OUT_10 = dataregs[FIRST_OUTPUT + 10].v ? OUTPUT_ON : OUTPUT_OFF;
#endif
#if TOTAL_OUTPUTS > 11
DIG_OUT_11 = dataregs[FIRST_OUTPUT + 11].v ? OUTPUT_ON : OUTPUT_OFF;
#endif
#if TOTAL_OUTPUTS > 12
DIG_OUT_12 = dataregs[FIRST_OUTPUT + 12].v ? OUTPUT_ON : OUTPUT_OFF;
#endif
#if TOTAL_OUTPUTS > 13
DIG_OUT_13 = dataregs[FIRST_OUTPUT + 13].v ? OUTPUT_ON : OUTPUT_OFF;
#endif
#if TOTAL_OUTPUTS > 14
DIG_OUT_14 = dataregs[FIRST_OUTPUT + 14].v ? OUTPUT_ON : OUTPUT_OFF;
#endif
#if TOTAL_OUTPUTS > 15
DIG_OUT_15 = dataregs[FIRST_OUTPUT + 15].v ? OUTPUT_ON : OUTPUT_OFF;
#endif
#if TOTAL_OUTPUTS > 16
DIG_OUT_16 = dataregs[FIRST_OUTPUT + 16].v ? OUTPUT_ON : OUTPUT_OFF;
#endif
#if TOTAL_OUTPUTS > 17
DIG_OUT_17 = dataregs[FIRST_OUTPUT + 17].v ? OUTPUT_ON : OUTPUT_OFF;
#endif
#if TOTAL_OUTPUTS > 18
DIG_OUT_18 = dataregs[FIRST_OUTPUT + 18].v ? OUTPUT_ON : OUTPUT_OFF;
#endif
#if TOTAL_OUTPUTS > 19
DIG_OUT_19 = dataregs[FIRST_OUTPUT + 19].v ? OUTPUT_ON : OUTPUT_OFF;
#endif
#if TOTAL_OUTPUTS > 20
DIG_OUT_20 = dataregs[FIRST_OUTPUT + 20].v ? OUTPUT_ON : OUTPUT_OFF;
#endif
#if TOTAL_OUTPUTS > 21
#error This program is only setup to handle up to 21 outputs
#endif
}
void gather_inputs()
{
BYTE i;
/* NOTE: this could be done as a for() loop, such as
* for (i = 0; i < TOTAL_INPUTS; i++)
* {
* switch (i)
* {
* #if TOTAL_INPUTS > 0
* case 0:
* val = (DIG_IN_0 == INPUT_ON) ? ON : OFF;
* break;
* #endif
* { etc }
* }
* dataregs[FIRST_INPUT + i] = val;
* }
* but the CASE statement would still need be setup for the
* number of inputs.
*/
#if TOTAL_INPUTS > 0
dataregs[FIRST_INPUT + 0].v = (DIG_IN_0 == INPUT_ON) ? ON : OFF;
#endif
#if TOTAL_INPUTS > 1
dataregs[FIRST_INPUT + 1].v = (DIG_IN_1 == INPUT_ON) ? ON : OFF;
#endif
#if TOTAL_INPUTS > 2
dataregs[FIRST_INPUT + 2].v = (DIG_IN_2 == INPUT_ON) ? ON : OFF;
#endif
#if TOTAL_INPUTS > 3
dataregs[FIRST_INPUT + 3].v = (DIG_IN_3 == INPUT_ON) ? ON : OFF;
#endif
#if TOTAL_INPUTS > 4
dataregs[FIRST_INPUT + 4].v = (DIG_IN_4 == INPUT_ON) ? ON : OFF;
#endif
#if TOTAL_INPUTS > 5
dataregs[FIRST_INPUT + 5].v = (DIG_IN_5 == INPUT_ON) ? ON : OFF;
#endif
#if TOTAL_INPUTS > 6
dataregs[FIRST_INPUT + 6].v = (DIG_IN_6 == INPUT_ON) ? ON : OFF;
#endif
#if TOTAL_INPUTS > 7
dataregs[FIRST_INPUT + 7].v = (DIG_IN_7 == INPUT_ON) ? ON : OFF;
#endif
#if TOTAL_INPUTS > 8
dataregs[FIRST_INPUT + 8].v = (DIG_IN_8 == INPUT_ON) ? ON : OFF;
#endif
#if TOTAL_INPUTS > 9
dataregs[FIRST_INPUT + 9].v = (DIG_IN_9 == INPUT_ON) ? ON : OFF;
#endif
#if TOTAL_INPUTS > 10
dataregs[FIRST_INPUT + 10].v = (DIG_IN_10 == INPUT_ON) ? ON : OFF;
#endif
#if TOTAL_INPUTS > 11
dataregs[FIRST_INPUT + 11].v = (DIG_IN_11 == INPUT_ON) ? ON : OFF;
#endif
#if TOTAL_INPUTS > 12
dataregs[FIRST_INPUT + 12].v = (DIG_IN_12 == INPUT_ON) ? ON : OFF;
#endif
#if TOTAL_INPUTS > 13
dataregs[FIRST_INPUT + 13].v = (DIG_IN_13 == INPUT_ON) ? ON : OFF;
#endif
#if TOTAL_INPUTS > 14
dataregs[FIRST_INPUT + 14].v = (DIG_IN_14 == INPUT_ON) ? ON : OFF;
#endif
#if TOTAL_INPUTS > 15
dataregs[FIRST_INPUT + 15].v = (DIG_IN_15 == INPUT_ON) ? ON : OFF;
#endif
#if TOTAL_INPUTS > 16
dataregs[FIRST_INPUT + 16].v = (DIG_IN_16 == INPUT_ON) ? ON : OFF;
#endif
#if TOTAL_INPUTS > 17
dataregs[FIRST_INPUT + 17].v = (DIG_IN_17 == INPUT_ON) ? ON : OFF;
#endif
#if TOTAL_INPUTS > 18
dataregs[FIRST_INPUT + 18].v = (DIG_IN_18 == INPUT_ON) ? ON : OFF;
#endif
#if TOTAL_INPUTS > 18
dataregs[FIRST_INPUT + 19].v = (DIG_IN_19 == INPUT_ON) ? ON : OFF;
#endif
#if TOTAL_INPUTS > 20
dataregs[FIRST_INPUT + 20].v = (DIG_IN_20 == INPUT_ON) ? ON : OFF;
#endif
#if TOTAL_INPUTS > 21
#error This program is only setup to handle up to 21 inputs
#endif
#if TOTAL_ANALOGS > 0
for (i = 0; i < TOTAL_ANALOGS; i++)
dataregs[FIRST_ANALOG + i].v = (adc_filter[i] + ADC_FILTER_ROUNDING) / ADC_FILTER_STRENGTH;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -