/* 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 "bus.h" #include "moisture.h" #include "time.h" #include "light.h" #include "crc.h" /* See the Sense/Net Bus Specification. */ #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; /* 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[] PROGMEM = { 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; } /* TIM2 control functions. */ static enum delay_type_t {DELAY_FRAME, DELAY_PACKET, DELAY_PRENUM, DELAY_ENUM, DELAY_NONE} DelayMode = DELAY_NONE; 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){ TCCR2B = 0; TCNT2 = 0; if(type == DELAY_FRAME){ /* 672 us (frame spacing + margin) * to sense when the current packet has finished. */ OCR2A = 42; /* 672 us delay with 2Mhz clk/32 */ TCCR2B = _BV(CS21) | _BV(CS20); /* CLK/32 */ } else if(type == DELAY_PACKET){ /* 928 us (1600us - 672 us) packet spacing, * to know when to start sending a reply. */ OCR2A = 58; /* 928 us delay with 2Mhz clk/32 */ TCCR2B = _BV(CS21) | _BV(CS20); /* CLK/32 */ } else if(type == DELAY_ENUM){ /* Main enumeration delay. An enumeration time slot has already been chosen; * the delay just needs to be started. */ OCR2A = EnumSlot & 0x000000FF; TCCR2B = _BV(CS21); /* CLK / 8 */ } else if (type == DELAY_PRENUM){ /* Delay a constant 5ms. */ OCR2A = 39; /* 5ms delay with clk/256 */ TCCR2B = _BV(CS22) | _BV(CS21); /* CLK/256 */ } TIMSK2 = _BV(OCIE2A); DelayMode = type; } static void timer_off(void){ TCCR2B = 0; TCNT2 = 0; TIFR2 = _BV(OCF2A); DelayMode = DELAY_NONE; } /* Measurement data structure. */ volatile struct measurement_data_t MeasurementData; 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. */ DelayMode = DELAY_NONE; 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( (Buf[0] & 0x0F) == 0x01 /* Enumeration command */ ||(Buf[0] & 0x0F) == 0x02){ /* Fast Enumeration command. */ memcpy_PF(Buf + PACKET_DATA_OFFSET, (uint_farptr_t) TypeCode + 2 * num_zones(), 2); data_size = 2; status = 0x01; if((Buf[0] & 0x0F) == 0x01){ /* Enumeration command */ mode = ENUMERATE; } else { /* Fast enumerate - reply immediately. */ mode = REPLY; } goto done; } if(0 == memcmp(ChipID, Buf + 1, 9)){ /* The packet is addressed to this node. */ if((Buf[0] & 0x0F) == 0x00){ /* Get Data command. */ MeasurementData.uptime = get_uptime(); memcpy(Buf + PACKET_DATA_OFFSET, (struct measurement_data_t *) &MeasurementData, sizeof(struct measurement_data_t)); data_size = sizeof(struct measurement_data_t); status = 0x00; mode = REPLY; } else if((Buf[0] & 0x0F) == 0x03){ /* Blink command.. */ LEDBlink = 1; mode = NO_REPLY; } else if((Buf[0] & 0x0F) == 0x06){ /* Reboot command. */ data_size = 0; status = 0x04; mode = REPLY; ShouldReboot = 1; goto done; } 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(); } if(ENUMERATE == mode){ /* Start the 5ms pre-delay, compose the enumeration packet, * choose a random time slot, and configure interrupts. */ timer_on(DELAY_PRENUM); Buf[0] = 0xD1; memcpy(Buf + 1, ChipID, 9); Buf[10] = 2; crc = calc_crc(Buf, 13); Buf[PACKET_DATA_OFFSET + 2] = crc & 0xFF; Buf[PACKET_DATA_OFFSET + 3] = crc >> 8; Index = 15; EnumTimerH = 0; EnumSlot = 2000 + (((uint32_t) rand() << 16) | ((uint32_t) rand())) % 500000; UCSR0D |= _BV(RXSIE); /* Enable start bit interrupt. */ } } /* 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); } /* USART Start bit interrupt. Monitors bus traffic during enumeration, to prevent collisions. */ ISR(USART_RXS_vect){ if(DELAY_NONE == DelayMode){ //1. Block sleep during enumeration by setting a flag from bus.c //2. Enable the start bit interrupt only during sleep //3. The sleep command (called from main) runs in a while loop. If it wakes early, // it sleeps for ~80% of a frame period, then enters a busy loop until it // detects that the byte received interrupt has fired. } else { if((EnumSlot - (EnumTimerH | TCNT2)) < 500){ /* If the byte is too close to our time slot, push the timeslot forward * by 256 + (rand() % 512) time slots, each 4us long. */ EnumSlot += 1000 + (rand() % 512); OCR2A = EnumSlot & 0x000000FF; } } } /* Timeout interrupt. */ ISR(TIMER2_COMPA_vect){ if(DelayMode == DELAY_ENUM){ /* Increment enum counter and send the packet if necessary. */ if((EnumTimerH & 0xFFFFFF00) == (EnumSlot & 0xFFFFFF00)){ /* Time to send enumeration packet. */ timer_off(); UCSR0D &= ~_BV(RXSIE); /* Disable start bit interrupt. */ send(Buf, Index); } EnumTimerH += 256; return; } else 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); } else if(DelayMode == DELAY_PRENUM){ /* Start main enumeration cycle. */ timer_off(); timer_on(DELAY_ENUM); } } /* 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; /* 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. */ /* Set random seed. */ srand(calc_crc(ChipID, 9)); sei(); }