/* Soil Sensor Firmware * Sense/Net (RS-485) Bus * Sensiplicity Systems * * Copyright 2015 * * Department of Botany and Plant Pathology * Center for Genome Research and Biocomputing * Oregon State University * Corvallis, OR 97331 * * This program is not free software; you can not redistribute it and/or * modify it at all. * * This program 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. */ #include #include #include #include #include #include "bus.h" #include "moisture.h" #include "time.h" #include "light.h" #include "crc.h" /* See the Sense/Net Bus Specification. */ /* If set to 1 by this Sense/Net module, the main firmware will not be * started. */ volatile uint8_t ProgrammingMode; /* Put the RS-485 transceiver into transmit mode. */ static void tx_mode(void){ PORTD |= _BV(PD2); PORTC |= _BV(PC2); UCSR0B &= ~_BV(RXCIE0); /* Disable receive interrupt. */ } /* Put the RS-485 transceiver into receive mode. */ static void rx_mode(void){ PORTD &= ~_BV(PD2); PORTC &= ~_BV(PC2); UCSR0B |= _BV(RXCIE0); /* Enable receive interrupt. */ } #define MAX_PACKET_SIZE 141 #define START_BYTE 0x01 #define ESCAPE_BYTE 0x1B #define END_BYTE 0x03 /* This microcontroller's unique ID. */ static const uint8_t const *ChipID = (uint8_t *) &DEVID0; /* If !0, the LED should be blinked by the main() function. */ volatile uint8_t LEDBlink; /* Soil Sensor Type Codes * Index this array by the number of zones to get the type code. */ const uint16_t TypeCode[] = { 0x0000, 0x0001, 0x0002, 0x0000, 0x0004, }; /* Reboot the device. */ static void reboot(void){ wdt_enable(WDTO_15MS); while(1); } /* Reset the watchdog timer when starting up, to prevent getting * stuck in a continuous reboot cycle. */ void wdt_init(void) __attribute__((naked)) __attribute__((section(".init3"))); void wdt_init(void){ MCUSR = 0; wdt_disable(); return; } static uint8_t Buf[MAX_PACKET_SIZE]; /* Packet send/receive buffer. */ static uint8_t Index; /* Next free space in receive buffer/number of bytes in buffer. */ /*** Data transmission interrupt and functions ***/ /* Variables used by the data transmission interrupt. Don't touch. */ static volatile uint8_t *Next; /* The next character to be transmitted. */ static volatile uint8_t *End; /* The character after the final character to be transmitted. */ static volatile uint8_t ShouldReboot; /* If !0, reboot after finishing the current transmission. */ static volatile uint8_t SentEscape; /* If !0, an escape byte has just been sent. */ /* Transmit the given buffer over the RS-485 bus. Don't call this function * while data is being received. * Appends start and end bytes and inserts escape characters where necessary. */ void send(uint8_t *s, uint16_t length){ if(0 == length)return; while(Next != End){ /* send() has been called while a transmission is still in progress. * Hang until it completes. */ } tx_mode(); /* The ADM4850 takes 2000ns to transition between enabled and disabled. * The following two lines of code should take at least four clock cycles, * ensuring that the driver is ready by the time we start sending. */ SentEscape = 0; Next = s; End = s + length; UDR0 = START_BYTE; } /* Transmit interrupt. Runs after a frame (byte) is sent. */ ISR(USART_TX_vect){ if(Next == End){ UDR0 = END_BYTE; Next++; } else if(Next > End){ Index = 0; /* Reset buffer index. */ rx_mode(); if(ShouldReboot){ reboot(); } Next = 0; End = 0; return; } else if(*Next == START_BYTE || *Next == ESCAPE_BYTE || *Next == END_BYTE){ if(SentEscape){ UDR0 = *Next; Next++; SentEscape = 0; } else { UDR0 = ESCAPE_BYTE; SentEscape = 1; } } else { UDR0 = *Next; Next++; } } /*** Data reception and main Sense/Net bus logic. ***/ #define CMD_GET 0xA0 #define CMD_ENUM 0xA1 #define CMD_FAST_ENUM 0xA2 #define CMD_BLINK 0xA3 #define CMD_PROGMODE 0xA4 #define CMD_FLASH 0xA5 #define CMD_REBOOT 0xA6 #define CMD_ERASE 0xA7 #define STATUS_DATA 0xD0 #define STATUS_ENUM_TYPECODE 0xD1 #define STATUS_INVALID 0xD2 #define STATUS_ERROR 0xD3 #define STATUS_READY 0xD4 #define STATUS_BUSY 0xD5 #define STATUS_ENUM_INCOMPLETE 0xD7 #define MIN_SIZE 13 #define PACKET_DATA_OFFSET 11 /* Index of first data byte in packet. */ /* Insert the given status, data, and CRC into the packet buffer and send the packet. */ void stuff_send(uint8_t status_code, uint8_t size, const void *data){ Buf[0] = status_code; memcpy(Buf + 1, ChipID, 9); Buf[10] = size; if(size > 0)memcpy(Buf + PACKET_DATA_OFFSET, data, size); uint16_t crc = calc_crc(Buf, PACKET_DATA_OFFSET + size); Buf[PACKET_DATA_OFFSET + size] = crc & 0xFF; Buf[PACKET_DATA_OFFSET + size + 1] = crc >> 8; send(Buf, PACKET_DATA_OFFSET + size + 2); } /* Handle a packet. This function implements the Sense/Net bus protocol. */ void process_packet(void){ if(Index < MIN_SIZE /* Too small. */ || ((uint16_t) (Buf[Index - 1] << 8) + Buf[Index - 2]) != calc_crc(Buf, Index-2) /* Bad CRC */ || (memcmp(ChipID, Buf + 1, 9) && Buf[0] != CMD_ENUM && Buf[0] != CMD_FAST_ENUM) /* Not addressed to this packet and not an enum command. */ ){ return; } uint16_t page; switch(Buf[0]){ case CMD_FAST_ENUM: stuff_send(STATUS_ENUM_TYPECODE, 2, &(TypeCode[4])); break; case CMD_BLINK: LEDBlink = 1; break; case CMD_PROGMODE: ProgrammingMode = 1; stuff_send(STATUS_READY, 0, NULL);; break; case CMD_FLASH: if(boot_spm_busy()){ /* Busy programming FLASH. */ stuff_send(STATUS_BUSY, 0, NULL); } if(Index != 46){ /* Incorrect number of bytes. */ stuff_send(STATUS_INVALID, 0, NULL); } if(Buf[PACKET_DATA_OFFSET] > 191){ /* Page address beyond the main program space. */ stuff_send(STATUS_INVALID, 0, NULL); } page = ((uint16_t) Buf[PACKET_DATA_OFFSET]) << 5; uint8_t *target = Buf + PACKET_DATA_OFFSET + 1; /* Fill page buffer: */ for (uint8_t i=0; i<32; i+=2){ uint16_t word = *target++; word += (*target++) << 8; boot_page_fill (page + i, word); } boot_page_write(page); stuff_send(STATUS_READY, 0, NULL); break; case CMD_REBOOT: ShouldReboot = 1; stuff_send(STATUS_READY, 0, NULL); break; case CMD_ERASE: if(boot_spm_busy()){ /* Busy programming FLASH. */ stuff_send(STATUS_BUSY, 0, NULL); } if(Index != 14){ /* Incorrect number of bytes. */ stuff_send(STATUS_INVALID, 0, NULL); } if(Buf[PACKET_DATA_OFFSET] > 191){ /* Page address beyond the main program space. */ stuff_send(STATUS_INVALID, 0, NULL); } page = ((uint16_t) Buf[PACKET_DATA_OFFSET]) << 5; boot_page_erase(page); stuff_send(STATUS_READY, 0, NULL); break; default: stuff_send(STATUS_INVALID, 0, NULL); break; } } /* Receive interrupt. Runs whenever a byte is received in normal operation. */ ISR(USART_RX_vect){ if(Index >= MAX_PACKET_SIZE){ /* An overly large packet is being transmitted. * Truncate it, as it clearly isn't valid. */ Index = 0; } static uint8_t escaped; uint8_t byte = UDR0; if(escaped){ escaped = 0; Buf[Index] = byte; Index++; } else if(byte == START_BYTE){ Index = 0; } else if(byte == ESCAPE_BYTE){ escaped = 1; } else if(byte == END_BYTE){ process_packet(); } else { Buf[Index] = byte; Index++; } } /* USART Start bit interrupt. Monitors bus traffic during enumeration, to prevent collisions. */ ISR(USART_START_vect){ } /* Timeout interrupt. */ ISR(TIMER2_COMPA_vect){ } /* Initialize the pins, peripherals, and interrupts for the UART. */ void init_bus(void){ /* Pins */ DDRD |= _BV(PD2); /* RE control pin. */ DDRC |= _BV(PC2); /* DE control pin. */ PORTD |= _BV(PD0); /* Enable pull-up resistor on RXD. */ /* UART */ cli(); UBRR0H = 0; UBRR0L = 12; /* 19200 Baud */ UCSR0A = _BV(U2X0); /* Double speed mode */ UCSR0B = _BV(RXEN0) | _BV(TXEN0) | _BV(RXCIE0) | _BV(TXCIE0); /* Enable transmit, receive, and interrupts. */ UCSR0C = _BV(UCSZ01) | _BV(UCSZ00); /* 8 data bits, one stop bit. */ rx_mode(); /* Set random seed. */ srand(calc_crc(ChipID, 9)); sei(); }