aboutsummaryrefslogtreecommitdiffstats
path: root/firmware/octoclock/lib
diff options
context:
space:
mode:
Diffstat (limited to 'firmware/octoclock/lib')
-rw-r--r--firmware/octoclock/lib/CMakeLists.txt37
-rw-r--r--firmware/octoclock/lib/arp_cache.c87
-rw-r--r--firmware/octoclock/lib/arp_cache.h33
-rw-r--r--firmware/octoclock/lib/clkdist.c182
-rw-r--r--firmware/octoclock/lib/enc28j60.c322
-rw-r--r--firmware/octoclock/lib/gpsdo.c37
-rw-r--r--firmware/octoclock/lib/init.c175
-rw-r--r--firmware/octoclock/lib/network.c405
-rw-r--r--firmware/octoclock/lib/serial.c156
-rw-r--r--firmware/octoclock/lib/state.c122
-rw-r--r--firmware/octoclock/lib/udp_handlers.c167
-rw-r--r--firmware/octoclock/lib/usart.c49
12 files changed, 1772 insertions, 0 deletions
diff --git a/firmware/octoclock/lib/CMakeLists.txt b/firmware/octoclock/lib/CMakeLists.txt
new file mode 100644
index 000000000..006af287a
--- /dev/null
+++ b/firmware/octoclock/lib/CMakeLists.txt
@@ -0,0 +1,37 @@
+#
+# Copyright 2014 Ettus Research LLC
+#
+# This program 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.
+#
+# 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. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+SET(lib_files
+ arp_cache.c
+ clkdist.c
+ enc28j60.c
+ gpsdo.c
+ init.c
+ network.c
+ state.c
+ udp_handlers.c
+ usart.c
+)
+
+IF(OCTOCLOCK_DEBUG)
+ LIST(APPEND lib_files serial.c)
+ENDIF(OCTOCLOCK_DEBUG)
+
+ADD_LIBRARY(octoclock ${lib_files})
+SET_TARGET_PROPERTIES(octoclock
+ PROPERTIES COMPILE_FLAGS "${CMAKE_C_FLAGS} -O2"
+)
diff --git a/firmware/octoclock/lib/arp_cache.c b/firmware/octoclock/lib/arp_cache.c
new file mode 100644
index 000000000..558632d7f
--- /dev/null
+++ b/firmware/octoclock/lib/arp_cache.c
@@ -0,0 +1,87 @@
+/* -*- c++ -*- */
+/*
+ * Copyright 2009-2011,2014 Ettus Research LLC
+ *
+ * This program 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.
+ *
+ * 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. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include "arp_cache.h"
+#include <stddef.h>
+
+typedef struct {
+ struct ip_addr ip;
+ eth_mac_addr_t mac;
+} arp_cache_t;
+
+#define NENTRIES 8 // power-of-2
+
+static size_t nentries;
+static size_t victim;
+static arp_cache_t cache[NENTRIES];
+
+void
+arp_cache_init(void)
+{
+ nentries = 0;
+ victim = 0;
+}
+
+// returns non-negative index if found, else -1
+static int
+arp_cache_lookup(const struct ip_addr *ip)
+{
+ int i;
+ for (i = 0; i < nentries; i++)
+ if (cache[i].ip.addr == ip->addr)
+ return i;
+
+ return -1;
+}
+
+static int
+arp_cache_alloc(void)
+{
+ if (nentries < NENTRIES)
+ return nentries++;
+
+ int i = victim;
+ victim = (victim + 1) % NENTRIES;
+ return i;
+}
+
+void
+arp_cache_update(const struct ip_addr *ip,
+ const eth_mac_addr_t *mac)
+{
+ int i = arp_cache_lookup(ip);
+ if (i < 0){
+ i = arp_cache_alloc();
+ cache[i].ip = *ip;
+ cache[i].mac = *mac;
+ }
+ else {
+ cache[i].mac = *mac;
+ }
+}
+
+bool
+arp_cache_lookup_mac(const struct ip_addr *ip,
+ eth_mac_addr_t *mac)
+{
+ int i = arp_cache_lookup(ip);
+ if (i < 0)
+ return false;
+
+ *mac = cache[i].mac;
+ return true;
+}
diff --git a/firmware/octoclock/lib/arp_cache.h b/firmware/octoclock/lib/arp_cache.h
new file mode 100644
index 000000000..38ac0a198
--- /dev/null
+++ b/firmware/octoclock/lib/arp_cache.h
@@ -0,0 +1,33 @@
+/* -*- c++ -*- */
+/*
+ * Copyright 2009-2011,2014 Ettus Research LLC
+ *
+ * This program 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.
+ *
+ * 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. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef INCLUDED_ARP_CACHE_H
+#define INCLUDED_ARP_CACHE_H
+
+#include <lwip/ip_addr.h>
+#include <net/eth_mac_addr.h>
+#include <stdbool.h>
+
+void arp_cache_init(void);
+
+void arp_cache_update(const struct ip_addr *ip,
+ const eth_mac_addr_t *mac);
+
+bool arp_cache_lookup_mac(const struct ip_addr *ip,
+ eth_mac_addr_t *mac);
+
+#endif /* INCLUDED_ARP_CACHE_H */
diff --git a/firmware/octoclock/lib/clkdist.c b/firmware/octoclock/lib/clkdist.c
new file mode 100644
index 000000000..ed29510b6
--- /dev/null
+++ b/firmware/octoclock/lib/clkdist.c
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2014 Ettus Research LLC
+ *
+ * This program 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.
+ *
+ * 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. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <avr/io.h>
+
+#include <octoclock.h>
+#include <clkdist.h>
+#include <state.h>
+
+#define wait() for(uint16_t u=14000; u; u--) asm("nop");
+
+#define CLK (PA0) // Shift by 0 bits
+#define CE_ (PA1) // Is really the "Chip Disable" signal, as Hi disables SPI
+#define MOSI (PA2)
+#define MISO (PA3)
+#define PD_ (PA4)
+#define SYNC_ (PA5)
+
+// Table of 32-bit constants to be written to the TI chip's registers. These are
+// from the "Special Settings" on Page 35 of the datasheet.
+// For the GPS's 10 MHz output
+static uint32_t table_Pri_Ref[] = {
+ Bits_32(1,01010100,0,0), // Reg 0
+ Bits_32(1,01010100,0,0), // Outputs LVCMOS Positive&Negative Active - Non-inverted
+ Bits_32(1,01010100,0,0),
+ Bits_32(1,01010100,0,0),
+ Bits_32(1,01010100,0,0), // All have output divide ratio to be 1; Aux Output is OFF
+ Bits_32(0,0,1001,11010100), // Reg 5 LVCMOS in; p31 of TI datasheet
+ Bits_32(1,0,0010000,0), // Reg 6 // SCAS863A <96> NOVEMBER 2008 <96> REVISED JUNE 2011
+ Bits_32(1,01000000,0,0), // Reg 7
+ Bits_32(0,0,1,10000000) // Reg8 Status/Control
+};
+
+// For the External 10 MHz input LVDS with external termination,
+// Effectively DC coupled
+static uint32_t table_Sec_Ref[] = {
+ Bits_32(0001,01010100,0,100000), // Reg 0 -- use Secondary Reference for all channels
+ Bits_32(0001,01010100,0,100000), // Outputs LVCMOS Positive&Negative Active - Non-inverted
+ Bits_32(0001,01010100,0,100000),
+ Bits_32(0001,01010100,0,100000),
+ Bits_32(0001,01010100,0,100000),
+ Bits_32(0,0,1,10011011), // Reg 5, Failsafe OFF b5.11 = 0
+ Bits_32(1,0,10000,0), // Reg 6; try again
+ Bits_32(1,01000000,0,0),
+ Bits_32(0,0,1,10000000) // Reg8 Status/Control
+};
+
+// Table 19 conflicts with Tables 5 thru 9 - in how LVCMOS outputs are defined
+// extra error in Table 9, for bits 24 and 25
+static int table_size = sizeof (table_Pri_Ref) / sizeof(uint32_t);
+
+static void set_bit(uint8_t bit_number, Levels bit_value) {
+
+ if(bit_value == Hi)
+ PORTA |= 1<<bit_number;
+ else
+ PORTA &= ~ (1<<bit_number);
+}
+
+static bool get_bit(uint8_t bit_number) {
+ asm("nop");
+
+ uint8_t portA = PINA;
+ return (portA & 1<< bit_number) > 0 ? true : false;
+}
+
+// Send 32 bits to TI chip, LSB first.
+// Don't worry about reading any bits back at this time
+static void send_SPI(uint32_t bits) {
+
+ // Basically, when the clock is low, one can set MOSI to anything, as it's
+ // ignored.
+ set_bit(CE_, Lo); // Start SPI transaction with TI chip
+
+ // Send each bit, LSB first, add a bit of delay before the clock, and then
+ // toggle the clock line.
+ for (uint8_t i=0; i<32; i++) {
+ set_bit(MOSI, ((bits & (1UL<<i)) ? Hi : Lo) );
+ asm("nop");
+ set_bit(CLK, Hi);
+ set_bit(CLK, Lo);
+ }
+
+ // OK, transaction is over
+ set_bit(CE_, Hi);
+}
+
+static uint32_t receive_SPI() {
+ uint32_t bits = 0;
+
+ set_bit(CE_, Hi); // Make sure we're inactive
+ set_bit(CLK, Lo); // and clk line is inactive, too
+ set_bit(MOSI,Lo); // Make our bit output zero, for good measure
+ set_bit(CE_, Lo); // Start SPI transaction with TI chip; MOSI is don't care
+
+ // For each bit we are receiving, prep, clock in the bit LSB first
+ for (uint8_t i=0; i<32; i++){
+ bits >>= 1;
+ set_bit(CLK, Hi);
+ if( get_bit(MISO) ) bits |= 0x80000000;
+ set_bit(CLK, Lo);
+ }
+
+ // OK, transaction is over
+ set_bit(CE_, Hi);
+
+ // Ditch the lower 4 bits, which only contain the address
+ return (uint32_t)(bits >> 4);
+}
+
+void setup_TI_CDCE18005(TI_Input_10_MHz which_input) {
+ // Send the table of data to init the clock distribution chip. Uses SPI.
+ uint32_t temp;
+
+ if(which_input == Primary_GPS) {
+ for(uint8_t i=0; i<table_size; i++){
+ temp = table_Pri_Ref[i]<<4;
+ temp |= i;
+ send_SPI(temp); // Make sure the register's address is in the LSBs
+ }
+ } else {
+ // is Secondary_Ext -- External 10 MHz input from SMA connector
+ for(uint8_t i=0; i<table_size; i++){
+ temp = table_Sec_Ref[i]<<4;
+ temp |= i;
+ // Make sure the register's address is in the LSBs
+ send_SPI(temp);
+ }
+ }
+}
+
+void reset_TI_CDCE18005(void) {
+ // First, reset the chip. Or, if you will, pull /SYNC low then high
+ set_bit(CE_, Hi);
+ set_bit(PD_, Lo);
+ wait();
+
+ // Out of Power Down state
+ set_bit(PD_, Hi);
+ wait();
+
+ set_bit(SYNC_, Lo);
+ wait();
+ set_bit(SYNC_, Hi);
+
+ wait();
+}
+
+uint32_t get_TI_CDCE18005(CDCE18005 which_register){
+ uint32_t get_reg_value = 0;
+ get_reg_value = (0xf0 & (which_register << 4)) | Read_Command;
+
+ // This tells the TI chip to send us the reg. value requested
+ send_SPI(get_reg_value);
+ return receive_SPI();
+}
+
+bool check_TI_CDCE18005(TI_Input_10_MHz which_input,
+ CDCE18005 which_register) {
+
+ if(which_input == Primary_GPS){
+ uint32_t read_value = get_TI_CDCE18005(which_register);
+ return read_value == table_Pri_Ref[which_register];
+ } else {
+ uint32_t read_value = get_TI_CDCE18005(which_register);
+ return read_value == table_Sec_Ref[which_register];
+ }
+}
diff --git a/firmware/octoclock/lib/enc28j60.c b/firmware/octoclock/lib/enc28j60.c
new file mode 100644
index 000000000..0e8c1fa3c
--- /dev/null
+++ b/firmware/octoclock/lib/enc28j60.c
@@ -0,0 +1,322 @@
+/*! \file enc28j60.c \brief Microchip ENC28J60 Ethernet Interface Driver. */
+//*****************************************************************************
+//
+// File Name : 'enc28j60.c'
+// Title : Microchip ENC28J60 Ethernet Interface Driver
+// Author : Pascal Stang (c)2005
+// Created : 9/22/2005
+// Revised : 5/19/2014
+// Version : 0.1
+// Target MCU : Atmel AVR series
+// Editor Tabs : 4
+//
+// Description : This driver provides initialization and transmit/receive
+// functions for the Microchip ENC28J60 10Mb Ethernet Controller and PHY.
+// This chip is novel in that it is a full MAC+PHY interface all in a 28-pin
+// chip, using an SPI interface to the host processor.
+//
+//*****************************************************************************
+
+#include <octoclock.h>
+
+#include <net/enc28j60.h>
+#include <net/enc28j60conf.h>
+
+#include <avr/io.h>
+#include <util/delay.h>
+
+u08 Enc28j60Bank;
+u16 NextPacketPtr;
+
+u08 enc28j60ReadOp(u08 op, u08 address)
+{
+ u08 data;
+
+ // assert CS
+ ENC28J60_CONTROL_PORT &= ~(1<<ENC28J60_CONTROL_CS);
+
+ // issue read command
+ SPDR = op | (address & ADDR_MASK);
+ while(!(SPSR & (1<<SPIF)));
+ // read data
+ SPDR = 0x00;
+ while(!(SPSR & (1<<SPIF)));
+ // do dummy read if needed
+ if(address & 0x80)
+ {
+ SPDR = 0x00;
+ while(!(inb(SPSR) & (1<<SPIF)));
+ }
+ data = SPDR;
+
+ // release CS
+ ENC28J60_CONTROL_PORT |= (1<<ENC28J60_CONTROL_CS);
+
+ return data;
+}
+
+void enc28j60WriteOp(u08 op, u08 address, u08 data)
+{
+ // assert CS
+ ENC28J60_CONTROL_PORT &= ~(1<<ENC28J60_CONTROL_CS);
+
+ // issue write command
+ SPDR = op | (address & ADDR_MASK);
+ while(!(SPSR & (1<<SPIF)));
+ // write data
+ SPDR = data;
+ while(!(SPSR & (1<<SPIF)));
+
+ // release CS
+ ENC28J60_CONTROL_PORT |= (1<<ENC28J60_CONTROL_CS);
+}
+
+void enc28j60ReadBuffer(u16 len, u08* data)
+{
+ // assert CS
+ ENC28J60_CONTROL_PORT &= ~(1<<ENC28J60_CONTROL_CS);
+
+ // issue read command
+ SPDR = ENC28J60_READ_BUF_MEM;
+ while(!(SPSR & (1<<SPIF)));
+ while(len--)
+ {
+ // read data
+ SPDR = 0x00;
+ while(!(SPSR & (1<<SPIF)));
+ *data++ = SPDR;
+ }
+ // release CS
+ ENC28J60_CONTROL_PORT |= (1<<ENC28J60_CONTROL_CS);
+}
+
+void enc28j60WriteBuffer(u16 len, u08* data)
+{
+ // assert CS
+ ENC28J60_CONTROL_PORT &= ~(1<<ENC28J60_CONTROL_CS);
+
+ // issue write command
+ SPDR = ENC28J60_WRITE_BUF_MEM;
+ while(!(SPSR & (1<<SPIF)));
+ while(len--)
+ {
+ // write data
+ SPDR = *data++;
+ while(!(SPSR & (1<<SPIF)));
+ }
+ // release CS
+ ENC28J60_CONTROL_PORT |= (1<<ENC28J60_CONTROL_CS);
+}
+
+void enc28j60SetBank(u08 address)
+{
+ // set the bank (if needed)
+ if((address & BANK_MASK) != Enc28j60Bank)
+ {
+ // set the bank
+ enc28j60WriteOp(ENC28J60_BIT_FIELD_CLR, ECON1, (ECON1_BSEL1|ECON1_BSEL0));
+ enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, (address & BANK_MASK)>>5);
+ Enc28j60Bank = (address & BANK_MASK);
+ }
+}
+
+u08 enc28j60Read(u08 address)
+{
+ // set the bank
+ enc28j60SetBank(address);
+ // do the read
+ return enc28j60ReadOp(ENC28J60_READ_CTRL_REG, address);
+}
+
+void enc28j60Write(u08 address, u08 data)
+{
+ // set the bank
+ enc28j60SetBank(address);
+ // do the write
+ enc28j60WriteOp(ENC28J60_WRITE_CTRL_REG, address, data);
+}
+
+u16 enc28j60PhyRead(u08 address)
+{
+ u16 data;
+
+ // Set the right address and start the register read operation
+ enc28j60Write(MIREGADR, address);
+ enc28j60Write(MICMD, MICMD_MIIRD);
+
+ // wait until the PHY read completes
+ while(enc28j60Read(MISTAT) & MISTAT_BUSY);
+
+ // quit reading
+ enc28j60Write(MICMD, 0x00);
+
+ // get data value
+ data = enc28j60Read(MIRDL);
+ data |= enc28j60Read(MIRDH);
+ // return the data
+ return data;
+}
+
+void enc28j60PhyWrite(u08 address, u16 data)
+{
+ // set the PHY register address
+ enc28j60Write(MIREGADR, address);
+
+ // write the PHY data
+ enc28j60Write(MIWRL, data);
+ enc28j60Write(MIWRH, data>>8);
+
+ // wait until the PHY write completes
+ while(enc28j60Read(MISTAT) & MISTAT_BUSY);
+}
+
+void enc28j60Init(u08* macaddr)
+{
+ // initialize I/O
+ sbi(ENC28J60_CONTROL_DDR, ENC28J60_CONTROL_CS);
+ sbi(ENC28J60_CONTROL_PORT, ENC28J60_CONTROL_CS);
+
+ // setup SPI I/O pins
+ sbi(ENC28J60_SPI_PORT, ENC28J60_SPI_SCK); // set SCK hi
+ sbi(ENC28J60_SPI_DDR, ENC28J60_SPI_SCK); // set SCK as output
+ cbi(ENC28J60_SPI_DDR, ENC28J60_SPI_MISO); // set MISO as input
+ sbi(ENC28J60_SPI_DDR, ENC28J60_SPI_MOSI); // set MOSI as output
+ sbi(ENC28J60_SPI_DDR, ENC28J60_SPI_SS); // SS must be output for Master mode to work
+ // initialize SPI interface
+ // master mode
+ sbi(SPCR, MSTR);
+ // select clock phase positive-going in middle of data
+ cbi(SPCR, CPOL);
+ // Data order MSB first
+ cbi(SPCR,DORD);
+ // switch to f/4 2X = f/2 bitrate
+ cbi(SPCR, SPR0);
+ cbi(SPCR, SPR1);
+ sbi(SPSR, SPI2X);
+ // enable SPI
+ sbi(SPCR, SPE);
+
+ // perform system reset
+ enc28j60WriteOp(ENC28J60_SOFT_RESET, 0, ENC28J60_SOFT_RESET);
+ // check CLKRDY bit to see if reset is complete
+ _delay_us(51);
+
+ // do bank 0 stuff
+ // initialize receive buffer
+ // 16-bit transfers, must write low byte first
+ // set receive buffer start address
+ NextPacketPtr = RXSTART_INIT;
+ enc28j60Write(ERXSTL, RXSTART_INIT&0xFF);
+ enc28j60Write(ERXSTH, RXSTART_INIT>>8);
+ // set receive pointer address
+ enc28j60Write(ERXRDPTL, RXSTART_INIT&0xFF);
+ enc28j60Write(ERXRDPTH, RXSTART_INIT>>8);
+ // set receive buffer end
+ // ERXND defaults to 0x1FFF (end of ram)
+ enc28j60Write(ERXNDL, RXSTOP_INIT&0xFF);
+ enc28j60Write(ERXNDH, RXSTOP_INIT>>8);
+ // set transmit buffer start
+ // ETXST defaults to 0x0000 (beginnging of ram)
+ enc28j60Write(ETXSTL, TXSTART_INIT&0xFF);
+ enc28j60Write(ETXSTH, TXSTART_INIT>>8);
+
+ // do bank 2 stuff
+ // enable MAC receive
+ enc28j60Write(MACON1, MACON1_MARXEN|MACON1_TXPAUS|MACON1_RXPAUS);
+ // bring MAC out of reset
+ enc28j60Write(MACON2, 0x00);
+ // enable automatic padding and CRC operations
+ enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, MACON3, MACON3_PADCFG0|MACON3_TXCRCEN|MACON3_FRMLNEN);
+ // set inter-frame gap (non-back-to-back)
+ enc28j60Write(MAIPGL, 0x12);
+ enc28j60Write(MAIPGH, 0x0C);
+ // set inter-frame gap (back-to-back)
+ enc28j60Write(MABBIPG, 0x12);
+ // Set the maximum packet size which the controller will accept
+ enc28j60Write(MAMXFLL, MAX_FRAMELEN&0xFF);
+ enc28j60Write(MAMXFLH, MAX_FRAMELEN>>8);
+
+ // do bank 3 stuff
+ // write MAC address
+ // NOTE: MAC address in ENC28J60 is byte-backward
+ enc28j60Write(MAADR5, macaddr[0]);
+ enc28j60Write(MAADR4, macaddr[1]);
+ enc28j60Write(MAADR3, macaddr[2]);
+ enc28j60Write(MAADR2, macaddr[3]);
+ enc28j60Write(MAADR1, macaddr[4]);
+ enc28j60Write(MAADR0, macaddr[5]);
+
+ // no loopback of transmitted frames
+ enc28j60PhyWrite(PHCON2, PHCON2_HDLDIS);
+
+ // switch to bank 0
+ enc28j60SetBank(ECON1);
+ // enable interrutps
+ enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, EIE, EIE_INTIE|EIE_PKTIE);
+ // enable packet reception
+ enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN);
+}
+
+void enc28j60PacketSend(unsigned int len1, unsigned char* packet1, unsigned int len2, unsigned char* packet2)
+{
+ //Errata: Transmit Logic reset
+ enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRST);
+ enc28j60WriteOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_TXRST);
+
+ // Set the write pointer to start of transmit buffer area
+ enc28j60Write(EWRPTL, TXSTART_INIT&0xff);
+ enc28j60Write(EWRPTH, TXSTART_INIT>>8);
+ // Set the TXND pointer to correspond to the packet size given
+ enc28j60Write(ETXNDL, (TXSTART_INIT+len1+len2));
+ enc28j60Write(ETXNDH, (TXSTART_INIT+len1+len2)>>8);
+
+ // write per-packet control byte
+ enc28j60WriteOp(ENC28J60_WRITE_BUF_MEM, 0, 0x00);
+
+ // copy the packet into the transmit buffer
+ enc28j60WriteBuffer(len1, packet1);
+ if(len2>0) enc28j60WriteBuffer(len2, packet2);
+
+ // send the contents of the transmit buffer onto the network
+ enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRTS);
+}
+
+unsigned int enc28j60PacketReceive(unsigned int maxlen, u08* buf)
+{
+ u16 rxstat;
+ u16 len;
+
+ // check if a packet has been received and buffered
+ if( !enc28j60Read(EPKTCNT) )
+ return 0;
+
+ // Set the read pointer to the start of the received packet
+ enc28j60Write(ERDPTL, (NextPacketPtr));
+ enc28j60Write(ERDPTH, (NextPacketPtr)>>8);
+ // read the next packet pointer
+ NextPacketPtr = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
+ NextPacketPtr |= enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8;
+ // read the packet length
+ len = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
+ len |= enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8;
+ // read the receive status
+ rxstat = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
+ rxstat |= enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8;
+
+ // limit retrieve length
+ // (we reduce the MAC-reported length by 4 to remove the CRC)
+ len = MIN(len, maxlen);
+
+ // copy the packet from the receive buffer
+ enc28j60ReadBuffer(len, buf);
+
+ // Move the RX read pointer to the start of the next received packet
+ // This frees the memory we just read out
+ enc28j60Write(ERXRDPTL, (NextPacketPtr));
+ enc28j60Write(ERXRDPTH, (NextPacketPtr)>>8);
+
+ // decrement the packet counter indicate we are done with this packet
+ enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PKTDEC);
+
+ return len;
+}
diff --git a/firmware/octoclock/lib/gpsdo.c b/firmware/octoclock/lib/gpsdo.c
new file mode 100644
index 000000000..a6a7daaca
--- /dev/null
+++ b/firmware/octoclock/lib/gpsdo.c
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2014 Ettus Research LLC
+ *
+ * This program 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.
+ *
+ * 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. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <avr/interrupt.h>
+
+#include <octoclock.h>
+#include <gpsdo.h>
+#include <usart.h>
+
+void send_gpsdo_cmd(char* buf, uint8_t size){
+ for(uint8_t i = 0; i < size; i++) usart_putc(buf[i]);
+}
+
+//Serial out
+ISR(USART1_RX_vect){
+ gpsdo_buf[gpsdo_state.pos] = UDR1;
+
+ if(gpsdo_state.pos == (POOLSIZE-1)){
+ gpsdo_state.num_wraps++;
+ gpsdo_state.pos = 0;
+ }
+ else gpsdo_state.pos++;
+}
diff --git a/firmware/octoclock/lib/init.c b/firmware/octoclock/lib/init.c
new file mode 100644
index 000000000..827ccb376
--- /dev/null
+++ b/firmware/octoclock/lib/init.c
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2014 Ettus Research LLC
+ *
+ * This program 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.
+ *
+ * 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. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Welcome to the firmware code for the USRP Octoclock accessory product!
+ *
+ * Notes regarding this firmware:
+ * NOT in M103 compatibility mode
+ * CKOPT full rail-to-rail
+ * xtal osc
+ * 16K CK (16K clock cycles)
+ * additional delay 65ms for Crystal Oscillator
+ * slowly rising power
+ *
+ * These settings are very conservative. If a lower power oscillator is
+ * required, change CKOPT to '1' (UNPROGRAMMED).
+ *
+ * M103C = [ ]
+ * WDTON = [ ]
+ * OCDEN = [ ]
+ * JTAGEN = [X]
+ * SPIEN = [X]
+ * EESAVE = [X]
+ * BOOTSZ = 4096W_F000
+ * BOOTRST = [X]
+ * CKOPT = [X]
+ * BODLEVEL = 2V7
+ * BODEN = [ ]
+ * SUT_CKSEL = EXTHIFXTALRES_16KCK_64MS
+ *
+ * EXTENDED = 0xFF (valid)
+ * HIGH = 0x80 (valid)
+ * LOW = 0xFF (valid)
+ *
+ */
+
+#include <avr/io.h>
+
+#include <octoclock.h>
+
+void setup_atmel_io_ports(){
+/*
+ * PORT A
+ *
+ * pin# Sig Our Functional Name
+ *
+ * p51 PA0 CLK_CDCE to U205 pin 24 -- L-->H edge latches MOSI and MISO in CDCE18005
+ * p50 PA1 CE_CDCE Low = Chip Enabled for SPI comm to U205 pin 25
+ * p49 PA2 MOSI_CDCE Goes to CDCE18005 - U205 pin 23
+ * p48 PA3 MISO_CDCE Input Comes from U205 pin 22
+ * p47 PA4 PD_CDCE Low = Chip is in Power-Down state; is Hi for normal operation U205 pin 12
+ * p46 PA5 SYNC_CDCE Low = Chip is sync'd with interal dividers; Hi for normal operation U205 pin 14
+ * p45 PA6 PPS_SEL Low --> PPS_EXT selected; Hi -> PPS_GPS selected; to U203 pin 1
+ * p44 PA7 gps_lock Input Comes from M9107 - U206 pin 3
+ *
+ */
+
+// /pd_cdcd, /sync_code, /ce need to be 1 (disabled) to start
+// all bits are outputs, except PA7 (gps_lock) and PA3 (MISO_CDCE) are inputs
+PORTA = Bits_8(00110010);
+DDRA = 1<<DDA6 | 1<<DDA5 | 1<<DDA4 | 1<<DDA2 | 1<<DDA1 | 1<<DDA0;
+
+/*
+ * Port B
+ *
+ * pin# Sig Our Functional Name
+ *
+ * p10 PB0 Ethernet /SEN
+ * p11 PB1 Ethernet SCLK
+ * p12 PB2 Ethernet MOSI
+ * p13 PB3 Ethernet MISO
+ * p14 PB4 Not connected, set as output with value 0
+ * p15 PB5 Ethernet /RESET -- Set to HI for normal use, weak input
+ * p16 PB6 Ethernet /WOL --- Wake on LAN -- set, weak input
+ * p17 PB7 Not connected, set as output with value 0
+ *
+ */
+
+PORTB = Bits_8(01100001); // Initial Value is all zeros
+DDRB = 1<<DDB2 | 1<<DDB4 | 1<<DDB7; // MOSI is an output; the Not Connected pins are also outputs
+
+/*
+ * Port C
+ *
+ * pin# Sig Our Functional Name
+ *
+ * p34 PC0 Not connected, set as output with value 0
+ * p35 PC1 Reference Select Switch INPUT
+ * p36 PC2 Not connected, set as output with value 0
+ * p37 PC3 Not connected, set as output with value 0
+ * p38 PC4 Not connected, set as output with value 0
+ * p40 PC5 "Top LED" of D103 3-stack of green LEDs
+ * p41 PC6 "Middle LED"
+ * p43 PC7 "Bottom LED"
+ *
+ */
+
+PORTC = 0; // Initial Value is all zeros
+DDRC = ~( 1<<DDC1 ); // All bits are outputs, except PC1. including the 5 Not Connected bits
+
+/*
+ * Port D
+ *
+ * pin# Sig Our Functional Name
+ *
+ * p25 PD0 Ethernet /INT input
+ * p26 PD1 GPS NMEA bit, (INT1) INPUT
+ * p27 PD2 GPS Serial Out (RXD) INPUT
+ * p28 PD3 GPS Serial In (TXD) OUTPUT
+ * p29 PD4 GPS Present, INPUT hi = Present
+ * p30 PD5 Not connected, set as output with value 0
+ * p31 PD6 Not connected, set as output with value 0
+ * p32 PD7 Not connected, set as output with value 0
+ *
+ */
+
+PORTD = 0; // Initial Value is all zeros
+DDRD = 1<<DDD3;
+
+/*
+ * Port E
+ *
+ * pin# Sig Dir Our Functional Name
+ *
+ * p2 PE0 In avr_rxd (Also MOSI [PDI] when used for SPI programming of the chip)
+ * p3 PE1 Out avr_txd (Also MISO [PDO] when used for SPI programming of the chip)
+ * p4 PE2 In avr_cts
+ * p5 PE3 Out avr_rts
+ * p6 PE4 In PPS_GPS
+ * p7 PE5 In PPS_EXT_n
+ * p8 PE6 In Not Connected
+ * p9 PE7 In Not Connected
+ *
+ */
+
+PORTE = 0;
+DDRE = 1<<DDE1; // make outputs, set to zero. PE1 is usart0 TXD
+
+/*
+ * Port F
+ *
+ * Split into 2 nibbles; goes to Amp/Filter board to select ENABLE and two bits
+ * to select band one bit per nibble is not connected.
+ *
+ * pin Sig Dir Our Functional Name
+ *
+ * p61 PF0 Out J117 pin 3 (J117 pins 1 and 2 are GND)
+ * p60 PF1 Out J117 pin 4
+ * p59 PF2 Out J117 pin 5
+ * p58 PF3 Out J117 pin 6
+ * p57 PF4 Out J118 pin 3 (J118 pins 1 and 2 are GND)
+ * p56 PF5 Out J118 pin 4
+ * p55 PF6 Out J118 pin 5
+ * p54 PF7 Out J118 pin 6
+ *
+ */
+
+PORTF = 0; // Initial Value is all zeros; be sure ENABLE bits are active high!!!!
+DDRF = 0xff; // All bits are outputs
+
+}
diff --git a/firmware/octoclock/lib/network.c b/firmware/octoclock/lib/network.c
new file mode 100644
index 000000000..b7de18f3d
--- /dev/null
+++ b/firmware/octoclock/lib/network.c
@@ -0,0 +1,405 @@
+/*
+ * Copyright 2009-2012,2014 Ettus Research LLC
+ *
+ * This program 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.
+ *
+ * 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. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdint.h>
+#include <string.h>
+
+#include <avr/eeprom.h>
+
+#include <lwip/ip.h>
+#include <lwip/udp.h>
+#include <lwip/icmp.h>
+
+#include <octoclock.h>
+#include <network.h>
+
+#include <net/enc28j60.h>
+#include <net/eth_hdr.h>
+#include <net/if_arp.h>
+#include <net/ethertype.h>
+
+#include "arp_cache.h"
+
+/***********************************************************************
+ * Constants + Globals
+ **********************************************************************/
+static const size_t out_buff_size = 512;
+static const eth_mac_addr_t BCAST_MAC_ADDR = {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}};
+#define MAX_UDP_LISTENERS 10
+
+/***********************************************************************
+ * 16-bit one's complement sum
+ **********************************************************************/
+static uint32_t chksum_buffer(
+ uint16_t *buf, size_t nshorts,
+ uint32_t initial_chksum
+){
+ uint32_t chksum = initial_chksum;
+ for (size_t i = 0; i < nshorts; i++) chksum += buf[i];
+
+ while (chksum >> 16) chksum = (chksum & 0xffff) + (chksum >> 16);
+
+ return chksum;
+}
+
+/***********************************************************************
+ * Listener registry
+ **********************************************************************/
+static eth_mac_addr_t _local_mac_addr;
+static struct ip_addr _local_ip_addr;
+void register_addrs(const eth_mac_addr_t *mac_addr, const struct ip_addr *ip_addr){
+ _local_mac_addr = *mac_addr;
+ _local_ip_addr = *ip_addr;
+}
+
+struct listener_entry {
+ unsigned short port;
+ udp_receiver_t rcvr;
+};
+
+static struct listener_entry listeners[MAX_UDP_LISTENERS];
+
+void init_udp_listeners(void){
+ for (int i = 0; i < MAX_UDP_LISTENERS; i++)
+ listeners[i].rcvr = NULL;
+}
+
+static struct listener_entry *
+find_listener_by_port(unsigned short port)
+{
+ port = ntohs(port);
+
+ for (int i = 0; i < MAX_UDP_LISTENERS; i++){
+ if (port == listeners[i].port)
+ return &listeners[i];
+ }
+ return 0;
+}
+
+static struct listener_entry *
+find_free_listener(void)
+{
+ for (int i = 0; i < MAX_UDP_LISTENERS; i++){
+ if (listeners[i].rcvr == NULL)
+ return &listeners[i];
+ }
+ abort();
+}
+
+void
+register_udp_listener(int port, udp_receiver_t rcvr)
+{
+ struct listener_entry *lx = find_listener_by_port(port);
+ if (lx)
+ lx->rcvr = rcvr;
+ else {
+ lx = find_free_listener();
+ lx->port = port;
+ lx->rcvr = rcvr;
+ }
+}
+
+/*!
+ * low level routine to assembly an ethernet frame and send it.
+ *
+ * \param dst destination mac address
+ * \param ethertype ethertype field
+ * \param buf0 first part of data
+ * \param len0 length of first part of data
+ * \param buf1 second part of data
+ * \param len1 length of second part of data
+ * \param buf2 third part of data
+ * \param len2 length of third part of data
+ */
+static void
+send_pkt(
+ eth_mac_addr_t dst, int ethertype,
+ const void *buf0, size_t len0,
+ const void *buf1, size_t len1,
+ const void *buf2, size_t len2
+){
+ //assemble the ethernet header
+ eth_hdr_t ehdr;
+ ehdr.dst = dst;
+ ehdr.src = _local_mac_addr;
+ ehdr.ethertype = ethertype;
+
+ //grab an out buffer and pointer
+ //select the output buffer based on type of packet
+ uint8_t *p;
+ p = buf_out;
+ size_t total_len = 0;
+
+ //create a list of all buffers to copy
+ const void *buffs[] = {&ehdr, buf0, buf1, buf2};
+ size_t lens[] = {sizeof(ehdr), len0, len1, len2};
+
+ //copy each buffer into the out buffer
+ for (size_t i = 0; i < sizeof(buffs)/sizeof(buffs[0]); i++){
+ total_len += lens[i]; //use full length (not clipped)
+ size_t bytes_remaining = out_buff_size - (size_t)(p - (uint8_t*)buf_out);
+ if (lens[i] > bytes_remaining) lens[i] = bytes_remaining;
+ memcpy(p, buffs[i], lens[i]);
+ p += lens[i];
+ }
+
+ //ensure that minimum length requirements are met
+ if (total_len < 64) total_len = 64; //60 + ctrl word
+
+ //For some reason, the ENC28J60 won't send the CRC
+ //if you don't tell it to send another byte after
+ //the given packet
+ enc28j60PacketSend(total_len+1, buf_out, 0, 0);
+}
+
+static void
+send_ip_pkt(struct ip_addr dst, int protocol,
+ const void *buf0, uint16_t len0,
+ const void *buf1, uint16_t len1)
+{
+ struct ip_hdr ip;
+ _IPH_VHLTOS_SET(&ip, 4, 5, 0);
+ _IPH_LEN_SET(&ip, (IP_HLEN + len0 + len1));
+ _IPH_ID_SET(&ip, 0);
+ _IPH_OFFSET_SET(&ip, htons(IP_DF)); /* don't fragment */
+ _IPH_TTL_SET(&ip, 64);
+ _IPH_PROTO_SET(&ip, protocol);
+ _IPH_CHKSUM_SET(&ip, 0);
+ ip.src.addr = htonl(_local_ip_addr.addr);
+ ip.dest = dst;
+
+ _IPH_CHKSUM_SET(&ip, ~chksum_buffer(
+ (uint16_t *) &ip, sizeof(ip)/sizeof(int16_t), 0
+ ));
+
+ eth_mac_addr_t dst_mac;
+ bool found = arp_cache_lookup_mac(&ip.dest, &dst_mac);
+ if (!found) return;
+
+ send_pkt(dst_mac, htons(ETHERTYPE_IPV4),
+ &ip, sizeof(ip), buf0, len0, buf1, len1);
+}
+
+void
+send_udp_pkt(int src_port, struct socket_address dst,
+ const void *buf, size_t len)
+{
+ struct udp_hdr udp _AL2;
+ udp.src = htons(src_port);
+ udp.dest = htons(dst.port);
+ udp.len = htons(UDP_HLEN + len);
+ udp.chksum = 0;
+
+ send_ip_pkt(dst.addr, IP_PROTO_UDP,
+ &udp, sizeof(udp), buf, len);
+}
+
+static void
+handle_udp_packet(struct ip_addr src_ip, struct ip_addr dst_ip,
+ struct udp_hdr *udp, size_t len)
+{
+ unsigned char *payload = ((unsigned char *) udp) + UDP_HLEN;
+ int payload_len = len - UDP_HLEN;
+
+ struct listener_entry *lx = find_listener_by_port(udp->dest);
+ if (lx){
+ struct socket_address src = make_socket_address(src_ip, ntohs(udp->src));
+ struct socket_address dst = make_socket_address(dst_ip, ntohs(udp->dest));
+ lx->rcvr(src, dst, payload, payload_len);
+ }
+}
+
+static void
+handle_icmp_packet(struct ip_addr src, struct ip_addr dst,
+ struct icmp_echo_hdr *icmp, size_t len)
+{
+ switch (icmp->type){
+ case ICMP_DUR: // Destination Unreachable
+ if (icmp->code == ICMP_DUR_PORT){ // port unreachable
+ //filter out non udp data response
+ struct ip_hdr *ip = (struct ip_hdr *)(((uint8_t*)icmp) + sizeof(struct icmp_echo_hdr));
+ struct udp_hdr *udp = (struct udp_hdr *)(((char *)ip) + IP_HLEN);
+ uint8_t protocol = ntohs(ip->_ttl_proto) & 0xff;
+ if (protocol != IP_PROTO_UDP) break;
+
+ struct listener_entry *lx = find_listener_by_port(udp->src);
+ if (lx){
+ struct socket_address src = make_socket_address(ip->src, udp->src);
+ struct socket_address dst = make_socket_address(ip->dest, udp->dest);
+ lx->rcvr(src, dst, NULL, 0);
+ }
+ }
+ break;
+
+ case ICMP_ECHO:{
+ const void *icmp_data_buff = ((uint8_t*)icmp) + sizeof(struct icmp_echo_hdr);
+ uint16_t icmp_data_len = len - sizeof(struct icmp_echo_hdr);
+
+ struct icmp_echo_hdr echo_reply;
+ echo_reply.type = 0;
+ echo_reply.code = 0;
+ echo_reply.chksum = 0;
+ echo_reply.id = icmp->id;
+ echo_reply.seqno = icmp->seqno;
+ echo_reply.chksum = ~chksum_buffer( //data checksum
+ (uint16_t *)icmp_data_buff,
+ icmp_data_len/sizeof(int16_t),
+ chksum_buffer( //header checksum
+ (uint16_t *)&echo_reply,
+ sizeof(echo_reply)/sizeof(int16_t),
+ 0)
+ );
+
+ send_ip_pkt(
+ src, IP_PROTO_ICMP,
+ &echo_reply, sizeof(echo_reply),
+ icmp_data_buff, icmp_data_len
+ );
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+static void
+send_arp_reply(struct arp_eth_ipv4 *req, eth_mac_addr_t our_mac)
+{
+ struct arp_eth_ipv4 reply _AL4;
+ reply.ar_hrd = req->ar_hrd;
+ reply.ar_pro = req->ar_pro;
+ reply.ar_hln = req->ar_hln;
+ reply.ar_pln = req->ar_pln;
+ reply.ar_op = htons(ARPOP_REPLY);
+ memcpy(reply.ar_sha, &our_mac, 6);
+ memcpy(reply.ar_sip, req->ar_tip, 4);
+ memcpy(reply.ar_tha, req->ar_sha, 6);
+ memcpy(reply.ar_tip, req->ar_sip, 4);
+
+ eth_mac_addr_t t;
+ memcpy(t.addr, reply.ar_tha, 6);
+ send_pkt(t, htons(ETHERTYPE_ARP), &reply, sizeof(reply), 0, 0, 0, 0);
+}
+
+static void
+handle_arp_packet(struct arp_eth_ipv4 *p, size_t size)
+{
+ if (size < sizeof(struct arp_eth_ipv4))
+ return;
+
+ if (ntohs(p->ar_hrd) != ARPHRD_ETHER
+ || ntohs(p->ar_pro) != ETHERTYPE_IPV4
+ || p->ar_hln != 6
+ || p->ar_pln != 4)
+ return;
+
+ if(ntohs(p->ar_op) == ARPOP_REPLY){
+ struct ip_addr ip_addr;
+ memcpy(&ip_addr, p->ar_sip, sizeof(ip_addr));
+ eth_mac_addr_t mac_addr;
+ memcpy(&mac_addr, p->ar_sha, sizeof(mac_addr));
+ arp_cache_update(&ip_addr, &mac_addr);
+ }
+
+ if (ntohs(p->ar_op) != ARPOP_REQUEST)
+ return;
+
+ struct ip_addr sip;
+ struct ip_addr tip;
+
+ memcpy(&(sip.addr), &(p->ar_sip), 4);
+ memcpy(&(tip.addr), &(p->ar_tip), 4);
+ sip.addr = ntohl(sip.addr);
+ tip.addr = ntohl(tip.addr);
+
+ if(memcmp(&tip, &_local_ip_addr, sizeof(_local_ip_addr)) == 0){ //They're looking for us
+ send_arp_reply(p, _local_mac_addr);
+ }
+}
+
+void
+handle_eth_packet(size_t recv_len)
+{
+ eth_hdr_t *eth_hdr = (eth_hdr_t *)buf_in;
+ uint16_t ethertype = htons(eth_hdr->ethertype);
+
+ if (ethertype == ETHERTYPE_ARP){
+ struct arp_eth_ipv4 *arp = (struct arp_eth_ipv4 *)(buf_in + sizeof(eth_hdr_t));
+ handle_arp_packet(arp, recv_len-ETH_HLEN);
+ }
+ else if (ethertype == ETHERTYPE_IPV4){
+ struct ip_hdr *ip = (struct ip_hdr *)(buf_in + sizeof(eth_hdr_t));
+
+ if (_IPH_V(ip) != 4 || _IPH_HL(ip) != 5) // ignore pkts w/ bad version or options
+ return;
+
+ if (_IPH_OFFSET(ip) & (IP_MF | IP_OFFMASK)) // ignore fragmented packets
+ return;
+
+ // filter on dest ip addr (should be broadcast or for us)
+ bool is_bcast = memcmp(&eth_hdr->dst, &BCAST_MAC_ADDR, sizeof(BCAST_MAC_ADDR)) == 0;
+ struct ip_addr htonl_local_ip_addr;
+ htonl_local_ip_addr.addr = htonl(_local_ip_addr.addr);
+
+ bool is_my_ip = memcmp(&ip->dest, &htonl_local_ip_addr, sizeof(_local_ip_addr)) == 0;
+ if (!is_bcast && !is_my_ip) return;
+
+ arp_cache_update(&ip->src, (eth_mac_addr_t *)(((char *)buf_in)+6));
+
+ switch (_IPH_PROTO(ip)){
+ case IP_PROTO_UDP:
+ handle_udp_packet(ip->src, ip->dest, (struct udp_hdr *)(((char *)ip) + IP_HLEN), (recv_len-ETH_HLEN-IP_HLEN));
+ break;
+
+ case IP_PROTO_ICMP:
+ handle_icmp_packet(ip->src, ip->dest, (struct icmp_echo_hdr *)(((char *)ip) + IP_HLEN), (recv_len-ETH_HLEN-IP_HLEN));
+ break;
+
+ default: // ignore
+ break;
+ }
+ }
+ else
+ return; // Not ARP or IPV4, ignore
+}
+
+void network_init(void){
+
+ /*
+ * Read MAC address from EEPROM and initialize Ethernet driver. If EEPROM is blank,
+ * use default MAC address instead.
+ */
+ eeprom_read_block((void*)octoclock_mac_addr.addr, (void*)OCTOCLOCK_EEPROM_MAC_ADDR, 6);
+ if(!memcmp(&octoclock_mac_addr, blank_eeprom_mac, 6)) memcpy(octoclock_mac_addr.addr, default_mac, 6);
+
+ enc28j60Init((uint8_t*)&octoclock_mac_addr);
+
+ eeprom_read_block((void*)&octoclock_ip_addr, (void*)OCTOCLOCK_EEPROM_IP_ADDR, 4);
+ eeprom_read_block((void*)&octoclock_dr_addr, (void*)OCTOCLOCK_EEPROM_DR_ADDR, 4);
+ eeprom_read_block((void*)&octoclock_netmask, (void*)OCTOCLOCK_EEPROM_NETMASK, 4);
+
+ //In case of blank EEPROM, load default values
+ if(octoclock_ip_addr.addr == blank_eeprom_ip || octoclock_dr_addr.addr == blank_eeprom_ip ||
+ octoclock_netmask.addr == blank_eeprom_ip){
+ octoclock_ip_addr.addr = default_ip;
+ octoclock_dr_addr.addr = default_dr;
+ octoclock_netmask.addr = default_netmask;
+ }
+
+ register_addrs(&octoclock_mac_addr, &octoclock_ip_addr);
+}
diff --git a/firmware/octoclock/lib/serial.c b/firmware/octoclock/lib/serial.c
new file mode 100644
index 000000000..298cdff8d
--- /dev/null
+++ b/firmware/octoclock/lib/serial.c
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2014 Ettus Research LLC
+ *
+ * This program 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.
+ *
+ * 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. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdbool.h>
+
+#include <octoclock.h>
+#include <serial.h>
+
+#include <avr/pgmspace.h>
+#include <util/delay.h>
+#include <avr/io.h>
+#include <avr/interrupt.h>
+
+void serial_init(volatile uint8_t* port, uint8_t index){
+ *port |= _BV(index);
+}
+
+static void _serial_tx_send(uint8_t* buffer, volatile uint8_t* port, uint8_t index){
+ const uint8_t delay = BAUD_115200_DELAY;
+ uint8_t countdown;
+
+ for(uint8_t i = 0; i < 10; ++i){
+ if(buffer[i]) *port |= _BV(index);
+ else *port &= ~_BV(index);
+
+ countdown = delay;
+ while(--countdown) asm("nop");
+ }
+}
+
+static void _serial_tx_char(char c, volatile uint8_t* port, uint8_t index){
+ uint8_t buffer[10];
+ uint8_t i = 0;
+
+ buffer[i++] = 0; // START
+ for (int idx = 0; idx < 8; ++idx)
+ buffer[i++] = (((uint8_t)(c) & ((uint8_t)1<<((idx)))) ? 0x01 : 0x00); // Endianness: 7-
+ buffer[i++] = 1; // STOP
+
+ _serial_tx_send(buffer, port, index);
+}
+
+void serial_tx_P(const char* message, volatile uint8_t* port, uint8_t index, bool newline){
+ char c = pgm_read_byte(message);
+ if(c == '\0') return;
+
+ do{
+ _serial_tx_char(c, port, index);
+ c = pgm_read_byte(++message);
+ } while(c != '\0');
+
+ if(newline){
+ _serial_tx_char('\r', port, index);
+ _serial_tx_char('\n', port, index);
+ }
+
+ *port |= _BV(index);
+}
+
+void serial_tx(const char* message, volatile uint8_t* port, uint8_t index, bool newline){
+ if (message[0] == '\0')
+ return;
+
+ do
+ {
+ _serial_tx_char(*message, port, index);
+ } while (*(++message) != '\0');
+
+ if (newline){
+ _serial_tx_char('\r', port, index);
+ _serial_tx_char('\n', port, index);
+ }
+
+ *port |= _BV(index);
+}
+
+void serial_tx_byte(uint8_t byte, volatile uint8_t* port, uint8_t index, bool newline){
+ char ch[4];
+ ch[0] = '0' + (byte / 100);
+ ch[1] = '0' + ((byte % 100) / 10);
+ ch[2] = '0' + (byte % 10);
+ ch[3] = '\0';
+ serial_tx(ch, port, index, newline);
+}
+
+void serial_tx_hex(uint8_t byte, volatile uint8_t* port, uint8_t index, bool newline){
+ char ch[3];
+ uint8_t _byte = byte >> 4;
+ if (_byte < 10)
+ ch[0] = '0' + _byte;
+ else
+ ch[0] = 'A' + (_byte - 10);
+ byte &= 0x0F;
+ if (byte < 10)
+ ch[1] = '0' + byte;
+ else
+ ch[1] = 'A' + (byte - 10);
+ ch[2] = '\0';
+ serial_tx(ch, port, index, newline);
+}
+
+char serial_rx_char(volatile uint8_t* port, uint8_t index){
+ char c = 0;
+ const uint8_t delay = BAUD_115200_DELAY;
+ uint8_t countdown;
+
+ //Wait for character to appear, 0 will act as start marker
+ while(*port & _BV(index));
+
+ //With start marker there, wait for next bit
+ countdown = delay;
+ while(--countdown) asm("nop");
+
+ for(uint8_t i = 0; i < 8; ++i){
+ if(*port & _BV(index)) c &= (uint8_t)(1 << i);
+
+ countdown = delay;
+ while(--countdown) asm("nop");
+ }
+
+ return c;
+}
+
+//Assume ready (probably risky)
+char serial_rx_char_nowait(volatile uint8_t* port, uint8_t index){
+ char c = 0;
+ const uint8_t delay = BAUD_115200_DELAY;
+ uint8_t countdown;
+
+ //Wait for start marker to pass
+ countdown = delay;
+ while(--countdown) asm("nop");
+
+ for(uint8_t i = 0; i < 8; ++i){
+ if(*port & _BV(index)) c &= (uint8_t)(1 << i);
+
+ countdown = delay;
+ while(--countdown) asm("nop");
+ }
+
+ return c;
+}
diff --git a/firmware/octoclock/lib/state.c b/firmware/octoclock/lib/state.c
new file mode 100644
index 000000000..2adab6fc4
--- /dev/null
+++ b/firmware/octoclock/lib/state.c
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2014 Ettus Research LLC
+ *
+ * This program 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.
+ *
+ * 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. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <avr/interrupt.h>
+#include <avr/io.h>
+
+#include <debug.h>
+#include <octoclock.h>
+#include <clkdist.h>
+#include <state.h>
+
+void led(LEDs which, int turn_it_on) {
+
+ // selects the proper bit
+ uint8_t LED = 0x20 << which;
+
+ if(turn_it_on)
+ PORTC |= LED;
+ else
+ PORTC &= ~LED;
+}
+
+void LEDs_Off(void){
+ led(Top,false);
+ led(Middle,false);
+ led(Bottom,false);
+}
+
+void force_internal(void){
+ led(Top,true);
+ led(Middle,false);
+ led(Bottom,true);
+
+ setup_TI_CDCE18005(Primary_GPS);
+
+ // Set PPS to Primary (1) n.b.: "1" in general means "Internal" for all
+ // such signals
+ PORTA |= (1<<PA6);
+}
+
+void force_external(void){
+ led(Top, false);
+ led(Middle, true);
+ led(Bottom, true);
+
+ setup_TI_CDCE18005(Secondary_Ext);
+
+ // Set PPS to External
+ PORTA &= ~(1<<PA6);
+}
+
+void prefer_internal(void){
+ // if internal is NOT OK, then force external
+ if(global_gps_present)
+ force_internal();
+ else if(global_ext_ref_is_present)
+ force_external();
+ else
+ LEDs_Off();
+}
+
+void prefer_external(void){
+ // if external is NOT OK, then force internal
+ if(global_ext_ref_is_present)
+ force_external();
+ else if(global_gps_present)
+ force_internal();
+ else
+ LEDs_Off();
+}
+
+bool is_ext_ref_present(void){
+ volatile uint8_t prev = (PINE & (1<<DDE7));
+ volatile uint8_t now;
+
+ for(uint16_t i = 1; i < 512; i++){
+ now = (PINE & (1<<DDE7));
+ if(prev != now) return true;
+ }
+
+ return false;
+}
+
+bool is_gps_present(void){
+ return (PIND & (1<<DDD4));
+}
+
+ref_t which_ref(void){
+ if(!global_gps_present && !global_ext_ref_is_present) global_which_ref = NO_REF;
+ else if(global_gps_present && !global_ext_ref_is_present) global_which_ref = INTERNAL;
+ else if(!global_gps_present && global_ext_ref_is_present) global_which_ref = EXTERNAL;
+ else global_which_ref = (get_switch_pos() == UP) ? INTERNAL : EXTERNAL;
+
+ return global_which_ref;
+}
+
+void check_what_is_present(void){
+ global_ext_ref_is_present = is_ext_ref_present();
+ global_gps_present = is_gps_present();
+}
+
+switch_pos_t get_switch_pos(void){
+ uint8_t portC = PINC;
+
+ // UP is prefer internal,
+ // DOWN is prefer external
+ return (portC & (1<<DDC1)) ? DOWN : UP;
+}
diff --git a/firmware/octoclock/lib/udp_handlers.c b/firmware/octoclock/lib/udp_handlers.c
new file mode 100644
index 000000000..daf2a7612
--- /dev/null
+++ b/firmware/octoclock/lib/udp_handlers.c
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2014 Ettus Research LLC
+ *
+ * This program 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.
+ *
+ * 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. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+
+#include <avr/eeprom.h>
+#include <avr/io.h>
+#include <avr/wdt.h>
+
+#include <octoclock.h>
+#include <gpsdo.h>
+#include <network.h>
+#include <state.h>
+#include <net/udp_handlers.h>
+
+void handle_udp_ctrl_packet(
+ struct socket_address src, struct socket_address dst,
+ unsigned char *payload, int payload_len
+){
+ const octoclock_packet_t *pkt_in = (octoclock_packet_t*)payload;
+ octoclock_packet_t pkt_out;
+ pkt_out.proto_ver = OCTOCLOCK_FW_COMPAT_NUM;
+ pkt_out.sequence = pkt_in->sequence;
+
+ if(pkt_in->proto_ver == OCTOCLOCK_FW_COMPAT_NUM){
+ switch(pkt_in->code){
+ case OCTOCLOCK_QUERY_CMD:
+ pkt_out.code = OCTOCLOCK_QUERY_ACK;
+ pkt_out.len = 0;
+ break;
+
+ case SEND_EEPROM_CMD:
+ pkt_out.code = SEND_EEPROM_ACK;
+ pkt_out.len = sizeof(octoclock_fw_eeprom_t);
+
+ octoclock_fw_eeprom_t *eeprom_info = (octoclock_fw_eeprom_t*)pkt_out.data;
+
+ //Read values from EEPROM into packet
+ eeprom_read_block(eeprom_info, (void*)0, sizeof(octoclock_fw_eeprom_t));
+
+ //If EEPROM network fields are not fully populated, copy defaults
+ if(eeprom_read_byte((uint8_t*)OCTOCLOCK_EEPROM_IP_ADDR) == 0xFF ||
+ eeprom_read_byte((uint8_t*)OCTOCLOCK_EEPROM_DR_ADDR) == 0xFF ||
+ eeprom_read_byte((uint8_t*)OCTOCLOCK_EEPROM_MAC_ADDR) == 0xFF){
+
+ memcpy(eeprom_info->mac_addr, default_mac, 6);
+ eeprom_info->ip_addr = default_ip;
+ eeprom_info->dr_addr = default_dr;
+ eeprom_info->netmask = default_netmask;
+ }
+
+ //Check if strings or revision is empty
+ if(eeprom_info->serial[0] == 0xFF) memset(eeprom_info->serial, 0, 10);
+ if(eeprom_info->name[0] == 0xFF) memset(eeprom_info->name, 0, 10);
+ if(eeprom_info->revision == 0xFF) eeprom_info->revision = 0;
+ break;
+
+ case BURN_EEPROM_CMD:{
+ //Confirm length of data
+ if(pkt_in->len != sizeof(octoclock_fw_eeprom_t)){
+ pkt_out.code = BURN_EEPROM_FAILURE_ACK;
+ break;
+ }
+
+ /*
+ * In all cases, a full octoclock_fw_eeprom_t is written to lower the overall
+ * number of writes due to this EEPROM's smaller amount of safe writes.
+ * It is up to the host to make sure that the values that should be
+ * preserved are present in the octoclock_fw_eeprom_t struct.
+ */
+ const octoclock_fw_eeprom_t *eeprom_pkt = (octoclock_fw_eeprom_t*)pkt_in->data;
+ pkt_out.len = 0;
+
+ //Write EEPROM data from packet
+ eeprom_write_block(eeprom_pkt, (void*)0, sizeof(octoclock_fw_eeprom_t));
+
+ //Read back and compare to packet to confirm successful write
+ uint8_t eeprom_contents[sizeof(octoclock_fw_eeprom_t)];
+ eeprom_read_block(eeprom_contents, (void*)0, sizeof(octoclock_fw_eeprom_t));
+ uint8_t n = memcmp(eeprom_contents, eeprom_pkt, sizeof(octoclock_fw_eeprom_t));
+ pkt_out.code = n ? BURN_EEPROM_FAILURE_ACK
+ : BURN_EEPROM_SUCCESS_ACK;
+ break;
+ }
+
+ case SEND_STATE_CMD:
+ pkt_out.code = SEND_STATE_ACK;
+ pkt_out.len = sizeof(octoclock_state_t);
+
+ //Populate octoclock_state_t fields
+ octoclock_state_t *state = (octoclock_state_t*)pkt_out.data;
+ state->external_detected = (is_ext_ref_present()) ? 1 : 0;
+ state->gps_detected = (PIND & _BV(DDD4)) ? 1 : 0;
+ state->which_ref = (uint8_t)which_ref();
+ state->switch_pos = (uint8_t)get_switch_pos();
+ break;
+
+ case RESET_CMD:
+ pkt_out.code = RESET_ACK;
+ send_udp_pkt(OCTOCLOCK_UDP_CTRL_PORT, src, (void*)&pkt_out, sizeof(octoclock_packet_t));
+ wdt_enable(WDTO_30MS);
+ while(1);
+ return;
+
+ default:
+ return;
+ }
+
+ send_udp_pkt(OCTOCLOCK_UDP_CTRL_PORT, src, (void*)&pkt_out, sizeof(octoclock_packet_t));
+ }
+}
+
+void handle_udp_gpsdo_packet(
+ struct socket_address src, struct socket_address dst,
+ unsigned char *payload, int payload_len
+){
+ const octoclock_packet_t *pkt_in = (octoclock_packet_t*)payload;
+ octoclock_packet_t pkt_out;
+ pkt_out.proto_ver = OCTOCLOCK_FW_COMPAT_NUM;
+ pkt_out.sequence = pkt_in->sequence;
+
+ if(pkt_in->proto_ver == OCTOCLOCK_FW_COMPAT_NUM){
+ switch(pkt_in->code){
+ case HOST_SEND_TO_GPSDO_CMD:
+ send_gpsdo_cmd((char*)pkt_in->data, pkt_in->len);
+ pkt_out.code = HOST_SEND_TO_GPSDO_ACK;
+ pkt_out.len = 0;
+ break;
+
+ case SEND_POOLSIZE_CMD:
+ pkt_out.code = SEND_POOLSIZE_ACK;
+ pkt_out.len = 0;
+ pkt_out.poolsize = POOLSIZE;
+ break;
+
+ case SEND_CACHE_STATE_CMD:
+ pkt_out.code = SEND_CACHE_STATE_ACK;
+ pkt_out.state = gpsdo_state;
+ break;
+
+ case SEND_GPSDO_CACHE_CMD:
+ pkt_out.code = SEND_GPSDO_CACHE_ACK;
+ pkt_out.state = gpsdo_state;
+ memcpy(pkt_out.data, gpsdo_buf, POOLSIZE);
+ break;
+
+ default:
+ return;
+ }
+
+ send_udp_pkt(OCTOCLOCK_UDP_GPSDO_PORT, src, (void*)&pkt_out, sizeof(octoclock_packet_t));
+ }
+}
diff --git a/firmware/octoclock/lib/usart.c b/firmware/octoclock/lib/usart.c
new file mode 100644
index 000000000..3620ac5e9
--- /dev/null
+++ b/firmware/octoclock/lib/usart.c
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2014 Ettus Research LLC
+ *
+ * This program 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.
+ *
+ * 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. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <octoclock.h>
+#include <usart.h>
+
+#include <util/delay.h>
+#include <avr/io.h>
+
+void usart_init(void){
+ UCSR1B = (1 << TXEN) | (1 << RXEN) | (1 << RXCIE); //Turn on TX/RX circuitry, enable RX interrupts
+ UCSR1C = (3 << UCSZ0); //Use 8-bit character sizes, 1 stop bit, no parity
+ UBRR1H = (uint8_t)(BAUD_PRESCALE >> 8);
+ UBRR1L = (uint8_t)BAUD_PRESCALE;
+}
+
+char usart_getc(void){
+ while((UCSR1A & (1 << RXC)) == 0);
+
+ return UDR1;
+}
+
+char usart_getc_noblock(void){
+ return ((UCSR1A & (1 << RXC))) ? UDR1 : -1;
+}
+
+void usart_putc(char ch){
+ while((UCSR1A & (1 << UDRE1)) == 0);
+
+ UDR1 = ch;
+}
+
+void usart_putc_nowait(char ch){
+ if((UCSR1A & (1 << UDRE1)) != 0) UDR1 = ch;
+}