/* Soil Sensor Bootloader * 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 "bus.h" #include "crc.h" /* See the Sense/Net Bus Specification and the soil sensor manual. */ /* If set to 1 by this Sense/Net module, the main firmware will not be * started. */ volatile uint8_t ProgrammingMode; #define MAX_PACKET_SIZE 141 #define PACKET_DATA_OFFSET 11 /* Index of first data byte in packet. */ /* This microcontroller's unique ID. */ static const uint8_t const *ChipID = (uint8_t *) &DEVID0; /* 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; } /* TIM2 control functions. */ static enum delay_type_t {DELAY_FRAME, DELAY_PACKET} DelayMode; static volatile uint32_t EnumSlot; /* Enumeration time slot. */ static volatile uint32_t EnumTimerH; /* Enumeration timer high bytes. The low byte is TCNT2. */ static void timer_on(enum delay_type_t type){ if(type == DELAY_FRAME){ /* 672 us (frame spacing + margin) * to sense when the current packet has finished. */ TCCR2B = 0; TCNT2 = 0; OCR2A = 42; /* 672 us delay with 2Mhz clk/32 */ TCCR2B = _BV(CS21) | _BV(CS20); /* CLK/32 */ TIMSK2 = _BV(OCIE2A); DelayMode = DELAY_FRAME; } else if(type == DELAY_PACKET){ /* 928 us (1600us - 672 us) packet spacing, * to know when to start sending a reply. */ TCCR2B = 0; TCNT2 = 0; OCR2A = 58; /* 928 us delay with 2Mhz clk/32 */ TCCR2B = _BV(CS21) | _BV(CS20); /* CLK/32 */ TIMSK2 = _BV(OCIE2A); DelayMode = DELAY_PACKET; } } static void timer_off(void){ TCCR2B = 0; TCNT2 = 0; TIFR2 = _BV(OCF2A); } 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. */ /* Transmit the given buffer over the RS-485 bus. Don't call this function * while data is being received. */ 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. */ } PORTD |= _BV(PD2); /* Put RS-485 driver into transmit 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. */ Next = s + 1; End = s + length; UDR0 = *s; } /* Transmit interrupt. Runs after a frame (byte) is sent. */ ISR(USART_TX_vect){ if(Next == End){ Index = 0; /* Reset buffer index. */ PORTD &= ~_BV(PD2); /* Put RS-485 driver into receive mode. */ UCSR0B |= _BV(RXCIE0); /* Enable receive interrupt. */ if(ShouldReboot){ reboot(); } return; } UDR0 = *Next; Next++; } /*** Data reception and main Sense/Net bus logic. ***/ /* Handle a packet. This function implements the Sense/Net bus protocol. */ static void process_packet(void){ UCSR0B &= ~_BV(RXCIE0); /* Disable receive interrupt. */ enum {NO_REPLY, REPLY, ENUMERATE} mode = NO_REPLY; uint8_t status = 0; /* Valid range 0-7. */ uint8_t data_size = 0; timer_on(DELAY_PACKET); /* Start the timer now to avoid excess delay during CRC calculation. * If it's not needed, we'll stop it later. */ if(Index < 13) goto done; /* Too small. */ uint16_t crc = (Buf[Index - 1] << 8) + Buf[Index - 2]; if(calc_crc(Buf, Index - 2) != crc) goto done; /* CRC incorrect. */ if((Buf[0] & 0xF0) != 0xA0) goto done; /* Check for controller->node header. */ if(0 == memcmp(ChipID, Buf + 1, 9)){ /* The packet is addressed to this node. */ if((Buf[0] & 0x0F) == 0x06){ /* Reboot command. */ data_size = 0; status = 0x04; mode = REPLY; ShouldReboot = 1; } else if((Buf[0] & 0x0F) == 0x04){ /* Enter Programming Mode command. */ data_size = 0; status = 0x04; mode = REPLY; ProgrammingMode = 1; } else if((Buf[0] & 0x0F) == 0x05){ /* Write page command. */ if(boot_spm_busy()){ /* Busy programming FLASH. */ status = 0x05; mode = REPLY; goto done; } if(Index != 46){ /* Incorrect number of bytes. */ status = 0x02; mode = REPLY; goto done; } if(Buf[PACKET_DATA_OFFSET] > 191){ /* Page address beyond the main program space. */ status = 0x02; mode = REPLY; goto done; } uint16_t 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); status = 0x04; mode = REPLY; } else if((Buf[0] & 0x0F) == 0x07){ /* Erase page command. */ if(boot_spm_busy()){ /* Busy programming FLASH. */ status = 0x05; mode = REPLY; goto done; } if(Index != 14){ /* Incorrect number of bytes. */ status = 0x02; mode = REPLY; goto done; } if(Buf[PACKET_DATA_OFFSET] > 191){ /* Page address beyond the main program space. */ status = 0x02; mode = REPLY; goto done; } uint16_t page = ((uint16_t) Buf[PACKET_DATA_OFFSET]) << 5; boot_page_erase(page); status = 0x04; mode = REPLY; } else { /* Invalid command. */ status = 0x02; mode = REPLY; } } done: if(REPLY == mode){ /* A packet has been partially composed in the buffer. * The data has been written to the correct offset. */ Buf[0] = 0xD0 | status; memcpy(Buf + 1, ChipID, 9); Buf[10] = data_size; crc = calc_crc(Buf, 11 + data_size); Buf[PACKET_DATA_OFFSET + data_size] = crc & 0xFF; Buf[PACKET_DATA_OFFSET + data_size + 1] = crc >> 8; Index = 13 + data_size; } else if(NO_REPLY == mode){ Index = 0; PORTD &= ~_BV(PD2); /* Put RS-485 driver into receive mode. */ UCSR0B |= _BV(RXCIE0); /* Enable receive interrupt. */ timer_off(); } } /* Receive interrupt. Runs whenever a byte is received in normal operation. */ ISR(USART_RX_vect){ if(Index >= MAX_PACKET_SIZE){ /* Somehow, an overly large packet is being transmitted. * Truncate it, as it clearly isn't valid. */ Index = 0; } Buf[Index] = UDR0; Index++; timer_on(DELAY_FRAME); } /* Timeout interrupt. */ ISR(TIMER2_COMPA_vect){ if(DelayMode == DELAY_FRAME){ /* The current packet has just finished. */ timer_off(); process_packet(); } else if (DelayMode == DELAY_PACKET){ /* Time to send our reply packet. */ timer_off(); send(Buf, Index); } } /* Initialize the pins, peripherals, and interrupts for the UART. */ void init_bus(void){ /* Pins */ DDRD |= _BV(PD2); /* RE/DE control pin. */ PORTD &= ~_BV(PD2); /* Put RS-485 driver into receive mode. */ PORTD |= _BV(PD0); /* Enable pull-up resistor on RXD. */ /* UART */ cli(); UBRR0H = 0; UBRR0L = 12; /* 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. */ sei(); }