diff options
author | Nicholas Corgan <nick.corgan@ettus.com> | 2014-07-17 11:50:50 -0700 |
---|---|---|
committer | Nicholas Corgan <nick.corgan@ettus.com> | 2014-07-23 07:37:32 -0700 |
commit | a6e18604befdb6a954542f7722c8d55424065621 (patch) | |
tree | 22168e6f4c41c931e38ccd07ff8881b56c8cd88a /firmware/octoclock/lib | |
parent | 7423d1691fff3af08f8e42e3e09d8c8d9ec99fe8 (diff) | |
download | uhd-a6e18604befdb6a954542f7722c8d55424065621.tar.gz uhd-a6e18604befdb6a954542f7722c8d55424065621.tar.bz2 uhd-a6e18604befdb6a954542f7722c8d55424065621.zip |
OctoClock firmware upgrade, added host driver
* OctoClock can communicate with UHD over Ethernet
* Can read NMEA strings from GPSDO and send to host
* Added multi_usrp_clock class for clock devices
* uhd::device can now filter to return only USRP devices or clock devices
* New OctoClock bootloader can accept firmware download over Ethernet
* Added octoclock_burn_eeprom,octoclock_firmware_burner utilities
* Added test_clock_synch example to show clock API
Diffstat (limited to 'firmware/octoclock/lib')
-rw-r--r-- | firmware/octoclock/lib/CMakeLists.txt | 37 | ||||
-rw-r--r-- | firmware/octoclock/lib/arp_cache.c | 87 | ||||
-rw-r--r-- | firmware/octoclock/lib/arp_cache.h | 33 | ||||
-rw-r--r-- | firmware/octoclock/lib/clkdist.c | 182 | ||||
-rw-r--r-- | firmware/octoclock/lib/enc28j60.c | 322 | ||||
-rw-r--r-- | firmware/octoclock/lib/gpsdo.c | 37 | ||||
-rw-r--r-- | firmware/octoclock/lib/init.c | 175 | ||||
-rw-r--r-- | firmware/octoclock/lib/network.c | 405 | ||||
-rw-r--r-- | firmware/octoclock/lib/serial.c | 156 | ||||
-rw-r--r-- | firmware/octoclock/lib/state.c | 122 | ||||
-rw-r--r-- | firmware/octoclock/lib/udp_handlers.c | 167 | ||||
-rw-r--r-- | firmware/octoclock/lib/usart.c | 49 |
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(ð_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; +} |