├── cmake ├── modules │ ├── Platform │ │ ├── Arduino.cmake │ │ └── ArduinoPaths.cmake │ ├── ArduinoProcessing.cmake │ └── FindArduino.cmake └── toolchains │ └── Arduino.cmake ├── .gitignore ├── CMakeLists.txt ├── rakefile ├── LICENSE └── src └── main.cpp /cmake/modules/Platform/Arduino.cmake: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | *.swp 3 | tags 4 | libtags 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # CMake Configuration 2 | 3 | cmake_minimum_required(VERSION 2.8) 4 | 5 | set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules) 6 | set(CMAKE_TOOLCHAIN_FILE ${CMAKE_SOURCE_DIR}/cmake/toolchains/Arduino.cmake) 7 | 8 | find_package(Arduino 1.0 REQUIRED) 9 | 10 | # Project Configuration 11 | 12 | project(ntp C CXX) 13 | set(FIRMWARE_NAME ntp) 14 | 15 | set(${FIRMWARE_NAME}_BOARD atmega328) 16 | set(${FIRMWARE_NAME}_SRCS src/main.cpp) 17 | set(${FIRMWARE_NAME}_PORT /dev/ttyUSB0) 18 | 19 | generate_arduino_firmware(${FIRMWARE_NAME}) 20 | -------------------------------------------------------------------------------- /cmake/modules/Platform/ArduinoPaths.cmake: -------------------------------------------------------------------------------- 1 | if(UNIX) 2 | include(Platform/UnixPaths) 3 | if(APPLE) 4 | list(APPEND CMAKE_SYSTEM_PREFIX_PATH ~/Applications 5 | /Applications 6 | /Developer/Applications 7 | /sw # Fink 8 | /opt/local) # MacPorts 9 | endif() 10 | elseif(WIN32) 11 | include(Platform/WindowsPaths) 12 | endif() 13 | 14 | if(ARDUINO_SDK_PATH) 15 | if(WIN32) 16 | list(APPEND CMAKE_SYSTEM_PREFIX_PATH ${ARDUINO_SDK_PATH}/hardware/tools/avr/bin) 17 | list(APPEND CMAKE_SYSTEM_PREFIX_PATH ${ARDUINO_SDK_PATH}/hardware/tools/avr/utils/bin) 18 | elseif(APPLE) 19 | list(APPEND CMAKE_SYSTEM_PREFIX_PATH ${ARDUINO_SDK_PATH}/hardware/tools/avr/bin) 20 | endif() 21 | endif() 22 | -------------------------------------------------------------------------------- /rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/clean' 2 | 3 | CLEAN.include('build/', 'tags') 4 | CLOBBER.include('libtags') 5 | PROJECT = 'ntp' 6 | MAKEFILE = 'build/Makefile' 7 | OUTPUT = "build/#{PROJECT}.elf" 8 | 9 | task :default => [:tags, :upload] 10 | 11 | task :rebuild => [:clean, :tags, :upload] 12 | 13 | task :make => OUTPUT 14 | 15 | task :upload => OUTPUT do 16 | cd 'build' do 17 | sh "make #{PROJECT}-upload" 18 | end 19 | end 20 | 21 | task :configure => MAKEFILE 22 | 23 | file MAKEFILE do 24 | mkdir 'build' 25 | cd 'build' do 26 | sh 'cmake ..' 27 | end 28 | end 29 | 30 | file OUTPUT => MAKEFILE do 31 | cd 'build' do 32 | sh "make #{PROJECT}" 33 | end 34 | end 35 | 36 | file 'libtags' do 37 | sh 'ctags -f libtags --tag-relative --extra=+fq -R --exclude=numpy /usr/include/' 38 | sh 'ctags -f libtags --tag-relative --extra=+fq -R -a /usr/lib/avr/include/' 39 | sh 'ctags -f libtags --tag-relative --extra=+fq -R -a ~/arduino-1.0/hardware/arduino/cores/arduino/' 40 | sh 'ctags -f libtags --tag-relative --extra=+fq -R -a ~/arduino-1.0/libraries/' 41 | end 42 | 43 | task :tags => 'libtags' do 44 | sh 'ctags -f tags --tag-relative --extra=+fq -R ./src/' 45 | end 46 | 47 | task :fix_baud do 48 | sh 'stty -F /dev/ttyUSB0 57600' 49 | end 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Jacob Stanley 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Perth ICFP 2013 Team nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /cmake/modules/ArduinoProcessing.cmake: -------------------------------------------------------------------------------- 1 | # 1. Concatenate all PDE files 2 | # 2. Write #include "WProgram.h" 3 | # 3. Write prototypes 4 | # 4. Write original sources 5 | # 6 | # 7 | # Prefix Writer 8 | # 1. Scrub comments 9 | # 2. Optionally subsitute Unicode 10 | # 3. Find imports 11 | # 4. Find prototypes 12 | # 13 | # Find prototypes 14 | # 1. Strip comments, quotes, preprocessor directives 15 | # 2. Collapse braches 16 | # 3. Regex 17 | 18 | 19 | set(SINGLE_QUOTES_REGEX "('.')") 20 | set(DOUBLE_QUOTES_REGEX "(\"([^\"\\\\]|\\\\.)*\")") 21 | set(SINGLE_COMMENT_REGEX "([ ]*//[^\n]*)") 22 | set(MULTI_COMMENT_REGEX "(/[*][^/]*[*]/)") 23 | set(PREPROC_REGEX "([ ]*#(\\\\[\n]|[^\n])*)") 24 | 25 | #"[\w\[\]\*]+\s+[&\[\]\*\w\s]+\([&,\[\]\*\w\s]*\)(?=\s*\{)" 26 | set(PROTOTPYE_REGEX "([a-zA-Z0-9]+[ ]*)*[a-zA-Z0-9]+[ ]*\([^{]*\)[ ]*{") 27 | 28 | function(READ_SKETCHES VAR_NAME ) 29 | set(SKETCH_SOURCE) 30 | foreach(SKETCH ${ARGN}) 31 | if(EXISTS ${SKETCH}) 32 | message(STATUS "${SKETCH}") 33 | file(READ ${SKETCH} SKETCH_CONTENTS) 34 | set(SKETCH_SOURCE "${SKETCH_SOURCE}\n${SKETCH_CONTENTS}") 35 | else() 36 | message(FATAL_ERROR "Sketch file does not exist: ${SKETCH}") 37 | endif() 38 | endforeach() 39 | set(${VAR_NAME} "${SKETCH_SOURCE}" PARENT_SCOPE) 40 | endfunction() 41 | 42 | function(STRIP_SOURCES VAR_NAME SOURCES) 43 | string(REGEX REPLACE "${SINGLE_QUOTES_REGEX}|${DOUBLE_QUOTES_REGEX}|${SINGLE_COMMENT_REGEX}|${MULTI_COMMENT_REGEX}|${PREPROC_REGEX}" 44 | "" 45 | SOURCES 46 | "${SOURCES}") 47 | set(${VAR_NAME} "${SOURCES}" PARENT_SCOPE) 48 | endfunction() 49 | 50 | function(COLLAPSE_BRACES VAR_NAME SOURCES) 51 | set(PARSED_SOURCES) 52 | string(LENGTH "${SOURCES}" SOURCES_LENGTH) 53 | math(EXPR SOURCES_LENGTH "${SOURCES_LENGTH}-1") 54 | 55 | set(NESTING 0) 56 | set(START 0) 57 | foreach(INDEX RANGE ${SOURCES_LENGTH}) 58 | string(SUBSTRING "${SOURCES}" ${INDEX} 1 CURRENT_CHAR) 59 | #message("${CURRENT_CHAR}") 60 | if(CURRENT_CHAR STREQUAL "{") 61 | if(NESTING EQUAL 0) 62 | math(EXPR SUBLENGTH "${INDEX}-${START} +1") 63 | string(SUBSTRING "${SOURCES}" ${START} ${SUBLENGTH} CURRENT_CHUNK) 64 | set(PARSED_SOURCES "${PARSED_SOURCES}${CURRENT_CHUNK}") 65 | #message("INDEX: ${INDEX} START: ${START} LENGTH: ${SUBLENGTH}") 66 | endif() 67 | math(EXPR NESTING "${NESTING}+1") 68 | elseif(CURRENT_CHAR STREQUAL "}") 69 | math(EXPR NESTING "${NESTING}-1") 70 | if(NESTING EQUAL 0) 71 | set(START ${INDEX}) 72 | endif() 73 | endif() 74 | endforeach() 75 | 76 | math(EXPR SUBLENGTH "${SOURCES_LENGTH}-${START} +1") 77 | string(SUBSTRING "${SOURCES}" ${START} ${SUBLENGTH} CURRENT_CHUNK) 78 | set(PARSED_SOURCES "${PARSED_SOURCES}${CURRENT_CHUNK}") 79 | 80 | set(${VAR_NAME} "${PARSED_SOURCES}" PARENT_SCOPE) 81 | endfunction() 82 | 83 | function(extract_prototypes VAR_NAME SOURCES) 84 | string(REGEX MATCHALL "${PROTOTPYE_REGEX}" 85 | SOURCES 86 | "${SOURCES}") 87 | set(${VAR_NAME} "${SOURCES}" PARENT_SCOPE) 88 | endfunction() 89 | 90 | read_sketches(SKETCH_SOURCE ${FILES}) 91 | strip_sources(SKETCH_SOURCE "${SKETCH_SOURCE}") 92 | collapse_braces(SKETCH_SOURCE "${SKETCH_SOURCE}") 93 | extract_prototypes(SKETCH_SOURCE "${SKETCH_SOURCE}") 94 | 95 | 96 | 97 | 98 | message("===============") 99 | foreach(ENTRY ${SKETCH_SOURCE}) 100 | message("START]]]${ENTRY}[[[END") 101 | endforeach() 102 | message("===============") 103 | #message("${SKETCH_SOURCE}") 104 | -------------------------------------------------------------------------------- /cmake/toolchains/Arduino.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_SYSTEM_NAME Arduino) 2 | 3 | set(CMAKE_C_COMPILER avr-gcc) 4 | set(CMAKE_CXX_COMPILER avr-g++) 5 | 6 | #=============================================================================# 7 | # C Flags # 8 | #=============================================================================# 9 | if (NOT DEFINED ARDUINO_C_FLAGS) 10 | set(ARDUINO_C_FLAGS "-ffunction-sections -fdata-sections") 11 | endif() 12 | set(CMAKE_C_FLAGS "-g -Os ${ARDUINO_C_FLAGS}" CACHE STRING "") 13 | set(CMAKE_C_FLAGS_DEBUG "-g ${ARDUINO_C_FLAGS}" CACHE STRING "") 14 | set(CMAKE_C_FLAGS_MINSIZEREL "-Os -DNDEBUG ${ARDUINO_C_FLAGS}" CACHE STRING "") 15 | set(CMAKE_C_FLAGS_RELEASE "-Os -DNDEBUG -w ${ARDUINO_C_FLAGS}" CACHE STRING "") 16 | set(CMAKE_C_FLAGS_RELWITHDEBINFO "-Os -g -w ${ARDUINO_C_FLAGS}" CACHE STRING "") 17 | 18 | #=============================================================================# 19 | # C++ Flags # 20 | #=============================================================================# 21 | if (NOT DEFINED ARDUINO_CXX_FLAGS) 22 | set(ARDUINO_CXX_FLAGS "${ARDUINO_C_FLAGS} -fno-exceptions") 23 | endif() 24 | set(CMAKE_CXX_FLAGS "-g -Os ${ARDUINO_CXX_FLAGS}" CACHE STRING "") 25 | set(CMAKE_CXX_FLAGS_DEBUG "-g ${ARDUINO_CXX_FLAGS}" CACHE STRING "") 26 | set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG ${ARDUINO_CXX_FLAGS}" CACHE STRING "") 27 | set(CMAKE_CXX_FLAGS_RELEASE "-Os -DNDEBUG ${ARDUINO_CXX_FLAGS}" CACHE STRING "") 28 | set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-Os -g ${ARDUINO_CXX_FLAGS}" CACHE STRING "") 29 | 30 | #=============================================================================# 31 | # Executable Linker Flags # 32 | #=============================================================================# 33 | if (NOT DEFINED ARDUINO_LINKER_FLAGS) 34 | set(ARDUINO_LINKER_FLAGS "-Wl,--gc-sections") 35 | endif() 36 | set(CMAKE_EXE_LINKER_FLAGS "${ARDUINO_LINKER_FLAGS}" CACHE STRING "") 37 | set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${ARDUINO_LINKER_FLAGS}" CACHE STRING "") 38 | set(CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "${ARDUINO_LINKER_FLAGS}" CACHE STRING "") 39 | set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${ARDUINO_LINKER_FLAGS}" CACHE STRING "") 40 | set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${ARDUINO_LINKER_FLAGS}" CACHE STRING "") 41 | 42 | #=============================================================================# 43 | # Shared Lbrary Linker Flags # 44 | #=============================================================================# 45 | set(CMAKE_SHARED_LINKER_FLAGS "" CACHE STRING "") 46 | set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "" CACHE STRING "") 47 | set(CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL "" CACHE STRING "") 48 | set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "" CACHE STRING "") 49 | set(CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO "" CACHE STRING "") 50 | 51 | set(CMAKE_MODULE_LINKER_FLAGS "" CACHE STRING "") 52 | set(CMAKE_MODULE_LINKER_FLAGS_DEBUG "" CACHE STRING "") 53 | set(CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL "" CACHE STRING "") 54 | set(CMAKE_MODULE_LINKER_FLAGS_RELEASE "" CACHE STRING "") 55 | set(CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO "" CACHE STRING "") 56 | 57 | 58 | 59 | 60 | set(ARDUINO_PATHS) 61 | foreach(VERSION 22 1) 62 | list(APPEND ARDUINO_PATHS arduino-00${VERSION}) 63 | endforeach() 64 | 65 | #list(APPEND ARDUINO_PATHS arduino) 66 | 67 | find_path(ARDUINO_SDK_PATH 68 | NAMES lib/version.txt 69 | PATH_SUFFIXES share/arduino 70 | Arduino.app/Contents/Resources/Java/ 71 | ${ARDUINO_PATHS} 72 | DOC "Arduino Development Kit path.") 73 | 74 | include(Platform/ArduinoPaths) 75 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #define __STDC_LIMIT_MACROS 2 | #include 3 | 4 | // Fix for PROGMEM warning. 5 | #include 6 | #undef PROGMEM 7 | #define PROGMEM __attribute__((section(".progmem.data"))) 8 | #undef PSTR 9 | #define PSTR(s) (__extension__({static prog_char __c[] PROGMEM = (s); &__c[0];})) 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | // 'off_t' is defined in stdio.h which we don't use on Arduino. 17 | // We don't really need this but it's useful to document the purpose of 18 | // a variable with a type. 19 | typedef size_t off_t; 20 | 21 | //////////////////////////////////////////////////////////////////////// 22 | // GPS 23 | 24 | #define GPS_RATE_1HZ F("$PMTK220,1000*1F") 25 | #define GPS_RATE_5HZ F("$PMTK220,200*2C") 26 | #define GPS_RATE_10HZ F("$PMTK220,100*2F") 27 | 28 | // NOTE: You must set SoftwareSerial.h _SS_MAX_RX_BUFF 64 to 128 in 29 | // order to receive more than one NMEA string. 30 | #define GPS_OUTPUT_GGA F("$PMTK314,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0*29") 31 | #define GPS_OUTPUT_RMCGGA F("$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0*28") 32 | #define GPS_OUTPUT_RMCGGAGSA F("$PMTK314,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0*29") 33 | #define GPS_OUTPUT_RMCGSA F("$PMTK314,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0*28") 34 | #define GPS_OUTPUT_RMC F("$PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29") 35 | #define GPS_OUTPUT_ALLDATA F("$PMTK314,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0*29") 36 | 37 | #define GPS_BAUD_4800 F("$PMTK251,4800*14") 38 | #define GPS_BAUD_9600 F("$PMTK251,9600*17") 39 | #define GPS_BAUD_19200 F("$PMTK251,19200*22") 40 | #define GPS_BAUD_38400 F("$PMTK251,38400*27") 41 | #define GPS_BAUD_57600 F("$PMTK251,57600*2C") 42 | #define GPS_BAUD_115200 F("$PMTK251,115200*1F") 43 | 44 | #define GPS_RX 8 45 | #define GPS_TX 9 46 | 47 | static TinyGPS gps; 48 | static SoftwareSerial gps_serial(GPS_RX, GPS_TX); 49 | 50 | static void gps_setup() 51 | { 52 | // Ensure GPS is using baud rate of 9600. 53 | gps_serial.begin(9600); 54 | gps_serial.println(GPS_BAUD_9600); 55 | gps_serial.begin(19200); 56 | gps_serial.println(GPS_BAUD_9600); 57 | gps_serial.begin(38400); 58 | gps_serial.println(GPS_BAUD_9600); 59 | gps_serial.begin(57600); 60 | gps_serial.println(GPS_BAUD_9600); 61 | gps_serial.begin(115200); 62 | gps_serial.println(GPS_BAUD_9600); 63 | 64 | // Set our baud rate to 9600. 65 | gps_serial.begin(9600); 66 | 67 | // Set 1Hz update rate. 68 | gps_serial.println(GPS_RATE_1HZ); 69 | 70 | // Set the output sentence to RMC. RMC is the only sentence 71 | // available on this GPS receiver which has the date as well 72 | // as the time in the message. 73 | gps_serial.println(GPS_OUTPUT_RMC); 74 | } 75 | 76 | //////////////////////////////////////////////////////////////////////// 77 | // PPS 78 | 79 | // ATmega 168 and 328: 80 | // - Pin 2 = Interrupt 0 81 | // - Pin 3 = Interrupt 1 82 | 83 | #define LED_PIN A4 84 | #define PPS_PIN 3 85 | #define PPS_INT 1 86 | 87 | static volatile uint32_t g_pps_time; 88 | static volatile bool g_waiting_for_fix; 89 | 90 | static void pps_interrupt() 91 | { 92 | g_pps_time = micros(); 93 | g_waiting_for_fix = true; 94 | } 95 | 96 | static void pps_setup() 97 | { 98 | g_pps_time = micros(); 99 | 100 | pinMode(LED_PIN, OUTPUT); 101 | digitalWrite(LED_PIN, LOW); 102 | 103 | pinMode(PPS_PIN, INPUT); 104 | digitalWrite(PPS_PIN, HIGH); 105 | attachInterrupt(PPS_INT, pps_interrupt, RISING); 106 | } 107 | 108 | static void pps_loop() 109 | { 110 | uint32_t pps_state = micros() - g_pps_time < 100000 ? HIGH : LOW; 111 | digitalWrite(LED_PIN, pps_state); 112 | } 113 | 114 | //////////////////////////////////////////////////////////////////////// 115 | // Web Server Utils 116 | 117 | extern int __bss_end; 118 | extern void *__brkval; 119 | 120 | static int32_t get_free_memory() 121 | { 122 | int32_t free_memory; 123 | 124 | if(__brkval) 125 | free_memory = ((int32_t)&free_memory) - ((int32_t)__brkval); 126 | else 127 | free_memory = ((int32_t)&free_memory) - ((int32_t)&__bss_end); 128 | 129 | return free_memory; 130 | } 131 | 132 | static char* print_dms(float degrees, char* buffer) 133 | { 134 | float fValue = fabs(degrees); 135 | int deg = (int) fValue; 136 | float remainder = fValue-deg; 137 | fValue = remainder*60.0; 138 | int mins = (int) fValue; 139 | remainder = fValue - mins; 140 | fValue = remainder * 100.0; 141 | int secs = (int) fValue; 142 | remainder = fValue- secs; 143 | fValue = remainder *1000; 144 | int ptSec = (int) fValue; 145 | 146 | sprintf(buffer, "%d:%d:%d.%d", deg, mins, secs, ptSec); 147 | 148 | return buffer; 149 | } 150 | 151 | static char* print_ms_time(long ms, char* buffer) 152 | { 153 | long t = ms / 1000; 154 | word h = t / 3600; 155 | byte m = (t / 60) % 60; 156 | byte s = t % 60; 157 | byte ss = (ms % 1000)/100; 158 | 159 | sprintf(buffer, "%d%d:%0d%d:%d%d.%d", 160 | h/10, h%10, m/10, m%10, s/10, s%10, ss); 161 | 162 | return buffer; 163 | } 164 | 165 | static char* print_gps_time(char *buffer) 166 | { 167 | int year; 168 | byte month, day, hour, minute, second, hundredths; 169 | 170 | gps.crack_datetime( 171 | &year, &month, &day, 172 | &hour, &minute, &second, &hundredths); 173 | 174 | sprintf(buffer, "%d-%02d-%02d %02d:%02d:%02d.%02d", 175 | year, month, day, 176 | hour, minute, second, hundredths); 177 | } 178 | 179 | //////////////////////////////////////////////////////////////////////// 180 | // Web Server 181 | 182 | static void write_status_page(BufferFiller &response) 183 | { 184 | char buffer[32]; 185 | 186 | response.emit_p(PSTR( 187 | "HTTP/1.0 200 OK\r\n" 188 | "Content-Type: text/html\r\n" 189 | "Pragma: no-cache\r\n" 190 | "\r\n" 191 | "" 192 | "Jystic NTP Server v0.1")); 193 | 194 | print_gps_time(buffer); 195 | response.emit_p(PSTR("

UTC $S

"), buffer); 196 | 197 | response.emit_p(PSTR("Running $S\n"), 198 | print_ms_time(millis(), buffer)); 199 | 200 | response.emit_p(PSTR("Free mem $D\n"), 201 | get_free_memory() ); 202 | 203 | uint32_t time_since_pps = micros() - g_pps_time; 204 | response.emit_p(PSTR("Since PPS $S\n"), 205 | print_ms_time(time_since_pps/1000, buffer)); 206 | } 207 | 208 | // NOTE: The request and response share the same underlying buffer, so 209 | // NOTE: make sure you don't access the request after you start writing 210 | // NOTE: the response. 211 | static word process_http_request(const char *request, BufferFiller &response) 212 | { 213 | //size_t r = strchr(request, '\r') - request; 214 | //Serial.write((const uint8_t*)request, r); 215 | //Serial.println(); 216 | 217 | write_status_page(response); 218 | } 219 | 220 | static void http_loop(word pos) 221 | { 222 | const char *request = (const char *)ether.buffer + pos; 223 | BufferFiller response = ether.tcpOffset(); 224 | 225 | // Process the request and build the response. 226 | process_http_request(request, response); 227 | 228 | // Send the response to the client. 229 | ether.httpServerReply(response.position()); 230 | } 231 | 232 | //////////////////////////////////////////////////////////////////////// 233 | // Ethernet Utils 234 | 235 | #define g_packet ether.buffer 236 | 237 | inline static uint8_t read_uint8(off_t offset) 238 | { 239 | return g_packet[offset]; 240 | } 241 | 242 | static uint16_t read_uint16(off_t offset) 243 | { 244 | return ((uint16_t)g_packet[offset + 0] << 8) 245 | | ((uint16_t)g_packet[offset + 1]); 246 | } 247 | 248 | static uint32_t read_uint32(off_t offset) 249 | { 250 | return ((uint32_t)g_packet[offset + 0] << 24) 251 | | ((uint32_t)g_packet[offset + 1] << 16) 252 | | ((uint32_t)g_packet[offset + 2] << 8) 253 | | ((uint32_t)g_packet[offset + 3] ); 254 | } 255 | 256 | static void write_uint32(off_t offset, uint32_t value) 257 | { 258 | g_packet[offset + 0] = (value >> 24) & 0xFF; 259 | g_packet[offset + 1] = (value >> 16) & 0xFF; 260 | g_packet[offset + 2] = (value >> 8) & 0xFF; 261 | g_packet[offset + 3] = (value ) & 0xFF; 262 | } 263 | 264 | static bool packet_is_udp(size_t len) 265 | { 266 | return len >= 42 267 | && g_packet[ETH_TYPE_H_P] == ETHTYPE_IP_H_V 268 | && g_packet[ETH_TYPE_L_P] == ETHTYPE_IP_L_V 269 | && g_packet[IP_HEADER_LEN_VER_P] == 0x45 270 | && g_packet[IP_PROTO_P] == IP_PROTO_UDP_V; 271 | } 272 | 273 | static bool udp_dst_port_is(uint8_t port) 274 | { 275 | return g_packet[UDP_DST_PORT_H_P] == 0 276 | && g_packet[UDP_DST_PORT_L_P] == port; 277 | } 278 | 279 | //////////////////////////////////////////////////////////////////////// 280 | // NTP Time Utils 281 | 282 | #define NTP_EPOCH0 1900UL 283 | #define NTP_FRAC_MAX UINT32_MAX 284 | 285 | #define SECS_PER_MIN (60UL) 286 | #define SECS_PER_HOUR (3600UL) 287 | #define SECS_PER_DAY (SECS_PER_HOUR * 24UL) 288 | #define SECS_PER_YEAR (SECS_PER_DAY * 365UL) 289 | 290 | const uint16_t g_month_days[] = {0,31,59,90,120,151,181,212,243,273,304,334}; 291 | 292 | // The 64-bit NTP timestamp. 293 | typedef struct { 294 | uint32_t secs; // Seconds since 1 Jan 1900 UTC 295 | uint32_t frac; // Fractions of a second, 1 unit == 1/UINT32_MAX secs 296 | } timestamp_t; 297 | 298 | inline static bool is_leap_year(uint16_t year) 299 | { 300 | return (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0); 301 | } 302 | 303 | static timestamp_t timestamp( 304 | uint16_t year, 305 | uint8_t month, 306 | uint8_t day, 307 | uint8_t hour, 308 | uint8_t minute, 309 | uint8_t second, 310 | uint8_t hundredths) 311 | { 312 | timestamp_t t; 313 | 314 | t.secs = SECS_PER_YEAR * (year - NTP_EPOCH0) 315 | + SECS_PER_DAY * g_month_days[month-1] 316 | + SECS_PER_DAY * (day - 1) 317 | + SECS_PER_HOUR * hour 318 | + SECS_PER_MIN * minute 319 | + second; 320 | 321 | // add extra day for each leap year since epoch 0 322 | for (uint16_t y = NTP_EPOCH0; y < year; y++) 323 | { 324 | if (is_leap_year(y)) 325 | { 326 | t.secs += SECS_PER_DAY; 327 | } 328 | } 329 | 330 | // add extra day if this year is a leap year and we 331 | // have passed February 29 332 | if (is_leap_year(year) && month > 2) 333 | { 334 | t.secs += SECS_PER_DAY; 335 | } 336 | 337 | t.frac = hundredths * (NTP_FRAC_MAX/100); 338 | 339 | return t; 340 | } 341 | 342 | //////////////////////////////////////////////////////////////////////// 343 | // NTP Server 344 | 345 | #define NTP_PORT 123 346 | #define NTP_DATA_LEN 48 347 | 348 | #define NTP_DATA_P (UDP_DATA_P + 0) 349 | #define NTP_FLAGS_P (NTP_DATA_P + 0) 350 | #define NTP_STRATUM_P (NTP_DATA_P + 1) 351 | #define NTP_POLL_P (NTP_DATA_P + 2) 352 | #define NTP_PRECISION_P (NTP_DATA_P + 3) 353 | #define NTP_ROOT_DELAY_P (NTP_DATA_P + 4) 354 | #define NTP_ROOT_DISPERSION_P (NTP_DATA_P + 8) 355 | #define NTP_REFERENCE_ID_P (NTP_DATA_P + 12) 356 | #define NTP_REFERENCE_TIME_P (NTP_DATA_P + 16) 357 | #define NTP_TIME1_P (NTP_DATA_P + 24) 358 | #define NTP_TIME2_P (NTP_DATA_P + 32) 359 | #define NTP_TIME3_P (NTP_DATA_P + 40) 360 | 361 | static void read_flags(uint8_t *leap_indicator, uint8_t *version, uint8_t *mode) 362 | { 363 | uint8_t flags = read_uint8(NTP_FLAGS_P); 364 | 365 | *leap_indicator = flags >> 6; 366 | *version = (flags & 0x38) >> 3; 367 | *mode = (flags & 0x7); 368 | } 369 | 370 | static timestamp_t last_fix_time() 371 | { 372 | int year; 373 | byte month, day, hour, minute, second, hundredths; 374 | 375 | gps.crack_datetime( 376 | &year, &month, &day, 377 | &hour, &minute, &second, &hundredths); 378 | 379 | return timestamp( 380 | year, month, day, 381 | hour, minute, second, hundredths); 382 | } 383 | 384 | static timestamp_t micros_to_timestamp(uint32_t micros) 385 | { 386 | uint64_t us_since_pps = micros - g_pps_time; 387 | uint64_t frac_since_pps = (NTP_FRAC_MAX * us_since_pps) / 1000000; 388 | 389 | timestamp_t time = last_fix_time(); 390 | 391 | if (g_waiting_for_fix) 392 | { 393 | // pps is for the next fix 394 | time.secs++; 395 | } 396 | 397 | uint64_t frac = time.frac + frac_since_pps; 398 | 399 | while (frac > NTP_FRAC_MAX) 400 | { 401 | time.secs++; 402 | frac -= NTP_FRAC_MAX; 403 | } 404 | 405 | time.frac += frac; 406 | 407 | return time; 408 | } 409 | 410 | static volatile uint32_t g_receive_time; 411 | 412 | static timestamp_t last_receive_time() 413 | { 414 | return micros_to_timestamp(g_receive_time); 415 | } 416 | 417 | static timestamp_t current_time() 418 | { 419 | return micros_to_timestamp(micros()); 420 | } 421 | 422 | static timestamp_t read_timestamp(off_t offset) 423 | { 424 | timestamp_t t; 425 | t.secs = read_uint32(offset+0); 426 | t.frac = read_uint32(offset+4); 427 | return t; 428 | } 429 | 430 | static void write_timestamp(off_t offset, timestamp_t time) 431 | { 432 | write_uint32(offset+0, time.secs); 433 | write_uint32(offset+4, time.frac); 434 | } 435 | 436 | // Check RFC5905 for further details about the format. 437 | const uint8_t ntp_header[] PROGMEM = { 438 | 0x24, // No warning / Version 3 / Server (packed bitfield) 439 | 1, // Stratum 1 server (connected to GPS) 440 | 3, // Polling interval 441 | -20, // Precision in log2 seconds (2^-20 is about 1us) 442 | 0,0,0,0, // Delay to reference clock (we have PPS, so effectively zero) 443 | 0,0,0,1, // Jitter of reference clock (the PPS is rated to +/- 50ns) 444 | 'G','P','S',0, // Reference ID - we are using a GPS receiver 445 | }; 446 | 447 | static void ntp_loop(uint32_t rxtime) 448 | { 449 | if (!udp_dst_port_is(NTP_PORT)) return; 450 | 451 | uint16_t len = read_uint16(UDP_LEN_H_P); 452 | 453 | if (len < UDP_HEADER_LEN + NTP_DATA_LEN) return; 454 | 455 | timestamp_t time1 = read_timestamp(NTP_TIME3_P); 456 | timestamp_t time2 = micros_to_timestamp(rxtime); 457 | 458 | memcpy_P(g_packet + NTP_DATA_P, ntp_header, sizeof ntp_header); 459 | 460 | timestamp_t ref_time = last_fix_time(); 461 | 462 | write_timestamp(NTP_REFERENCE_TIME_P, ref_time); 463 | write_timestamp(NTP_TIME1_P, time1); 464 | write_timestamp(NTP_TIME2_P, time2); 465 | 466 | timestamp_t time3 = current_time(); 467 | write_timestamp(NTP_TIME3_P, time3); 468 | 469 | ether.makeUdpReply( 470 | (char *)(g_packet + NTP_DATA_P), NTP_DATA_LEN, NTP_PORT); 471 | 472 | Serial.print(F("NTP request from ")); 473 | uint8_t *dst_ip = &g_packet[IP_DST_P]; 474 | ether.printIp("", dst_ip); 475 | } 476 | 477 | //////////////////////////////////////////////////////////////////////// 478 | // Ethernet 479 | 480 | // MAC address must be unique on the LAN. 481 | static uint8_t mac_address[] = { 0x74,0x69,0x69,0x2D,0x30,0x32 }; 482 | static uint8_t ip_address[] = { 1,1,1,100 }; 483 | 484 | #define ETHERNET_BUFFER_SIZE 550 485 | uint8_t Ethernet::buffer[ETHERNET_BUFFER_SIZE]; 486 | 487 | #define ETHERNET_CS_PIN 10 488 | 489 | #define ETHERNET_INT 0 // Pin 2 (on ATmega 168 and 328) 490 | 491 | static void packet_received_interrupt() 492 | { 493 | g_receive_time = micros(); 494 | } 495 | 496 | static void ethernet_setup() 497 | { 498 | if (!ether.begin(ETHERNET_BUFFER_SIZE, mac_address, ETHERNET_CS_PIN)) 499 | Serial.println(F("Failed to access Ethernet controller")); 500 | 501 | ether.staticSetup(ip_address); 502 | 503 | attachInterrupt(ETHERNET_INT, packet_received_interrupt, FALLING); 504 | } 505 | 506 | static void ethernet_loop() 507 | { 508 | bool rxtime_valid = true; 509 | 510 | for (;;) 511 | { 512 | uint8_t pktcnt = ether.packetCount(); 513 | 514 | if (pktcnt == 0) 515 | { 516 | // all packets processed, do 517 | // housekeeping then return 518 | ether.packetLoop(0); 519 | return; 520 | } 521 | 522 | uint32_t rxtime = g_receive_time; 523 | uint16_t len = ether.packetReceive(); 524 | 525 | if (rxtime_valid && packet_is_udp(len)) 526 | { 527 | ntp_loop(rxtime); 528 | } 529 | 530 | uint16_t pos = ether.packetLoop(len); 531 | 532 | // If we received a TCP packet then 'pos' is the index 533 | // where the packet's data is stored in 'ether.buffer'. 534 | if (pos != 0) 535 | { 536 | // Assume that it's an HTTP request. 537 | http_loop(pos); 538 | } 539 | 540 | // The packet received interrupt only fires once per group 541 | // of packets, so the time is only valid for the first packet 542 | // after the 'pktcnt' is reset to zero. 543 | rxtime_valid = false; 544 | } 545 | } 546 | 547 | //////////////////////////////////////////////////////////////////////// 548 | // Main 549 | 550 | // Baud rate for the UART serial port used for diagnostics. 551 | #define UART_BAUD_RATE 57600 552 | 553 | void setup() 554 | { 555 | Serial.begin(UART_BAUD_RATE); 556 | Serial.println(F("Jystic NTP Server v0.1")); 557 | 558 | pps_setup(); 559 | gps_setup(); 560 | ethernet_setup(); 561 | } 562 | 563 | static void gps_loop() 564 | { 565 | while (gps_serial.available()) 566 | { 567 | char c = gps_serial.read(); 568 | //Serial.write(c); 569 | if (gps.encode(c)) 570 | { 571 | g_waiting_for_fix = false; 572 | } 573 | } 574 | } 575 | 576 | void loop() 577 | { 578 | pps_loop(); 579 | gps_loop(); 580 | ethernet_loop(); 581 | } 582 | 583 | // vim: ft=arduino 584 | -------------------------------------------------------------------------------- /cmake/modules/FindArduino.cmake: -------------------------------------------------------------------------------- 1 | # - Generate firmware and libraries for Arduino Devices 2 | # generate_arduino_firmware(TARGET_NAME) 3 | # TARGET_NAME - Name of target 4 | # Creates a Arduino firmware target. 5 | # 6 | # The target options can be configured by setting options of 7 | # the following format: 8 | # ${TARGET_NAME}${SUFFIX} 9 | # The following suffixes are availabe: 10 | # _SRCS # Sources 11 | # _HDRS # Headers 12 | # _SKETCHES # Arduino sketch files 13 | # _LIBS # Libraries to linked in 14 | # _BOARD # Board name (such as uno, mega2560, ...) 15 | # _PORT # Serial port, for upload and serial targets [OPTIONAL] 16 | # _AFLAGS # Override global Avrdude flags for target 17 | # _SERIAL # Serial command for serial target [OPTIONAL] 18 | # _NO_AUTOLIBS # Disables Arduino library detection 19 | # Here is a short example for a target named test: 20 | # set(test_SRCS test.cpp) 21 | # set(test_HDRS test.h) 22 | # set(test_BOARD uno) 23 | # 24 | # generate_arduino_firmware(test) 25 | # 26 | # 27 | # generate_arduino_library(TARGET_NAME) 28 | # TARGET_NAME - Name of target 29 | # Creates a Arduino firmware target. 30 | # 31 | # The target options can be configured by setting options of 32 | # the following format: 33 | # ${TARGET_NAME}${SUFFIX} 34 | # The following suffixes are availabe: 35 | # 36 | # _SRCS # Sources 37 | # _HDRS # Headers 38 | # _LIBS # Libraries to linked in 39 | # _BOARD # Board name (such as uno, mega2560, ...) 40 | # _NO_AUTOLIBS # Disables Arduino library detection 41 | # 42 | # Here is a short example for a target named test: 43 | # set(test_SRCS test.cpp) 44 | # set(test_HDRS test.h) 45 | # set(test_BOARD uno) 46 | # 47 | # generate_arduino_library(test) 48 | 49 | file(GLOB SDK_PATHS /usr/share/arduino* $ENV{HOME}/arduino*) 50 | 51 | find_path(ARDUINO_SDK_PATH 52 | NAMES lib/version.txt hardware libraries 53 | PATH_SUFFIXES share/arduino 54 | HINTS ${SDK_PATHS} 55 | DOC "Arduino Development Kit path.") 56 | 57 | # load_board_settings() 58 | # 59 | # Load the Arduino SDK board settings from the boards.txt file. 60 | # 61 | function(LOAD_BOARD_SETTINGS) 62 | load_arduino_style_settings(ARDUINO_BOARDS "${ARDUINO_BOARDS_PATH}") 63 | endfunction() 64 | 65 | function(LOAD_PROGRAMMERS_SETTINGS) 66 | load_arduino_style_settings(ARDUINO_PROGRAMMERS "${ARDUINO_PROGRAMMERS_PATH}") 67 | endfunction() 68 | 69 | # print_board_list() 70 | # 71 | # Print list of detected Arduino Boards. 72 | function(PRINT_BOARD_LIST) 73 | message(STATUS "Supported Arduino Boards:") 74 | print_list(ARDUINO_BOARDS) 75 | message(STATUS "") 76 | endfunction() 77 | 78 | # print_programmer_list() 79 | # 80 | # Print list of detected Programmers. 81 | function(PRINT_PROGRAMMER_LIST) 82 | message(STATUS "Supported Programmers:") 83 | print_list(ARDUINO_PROGRAMMERS) 84 | message(STATUS "") 85 | endfunction() 86 | 87 | # print_programmer_settings(PROGRAMMER) 88 | # 89 | # PROGRAMMER - programmer id 90 | # 91 | # Print the detected Programmer settings. 92 | # 93 | function(PRINT_PROGRAMMER_SETTINGS PROGRAMMER) 94 | if(${PROGRAMMER}.SETTINGS) 95 | message(STATUS "Programmer ${PROGRAMMER} Settings:") 96 | print_settings(${PROGRAMMER}) 97 | endif() 98 | endfunction() 99 | 100 | # print_board_settings(ARDUINO_BOARD) 101 | # 102 | # ARDUINO_BOARD - Board id 103 | # 104 | # Print the detected Arduino board settings. 105 | # 106 | function(PRINT_BOARD_SETTINGS ARDUINO_BOARD) 107 | if(${ARDUINO_BOARD}.SETTINGS) 108 | message(STATUS "Arduino ${ARDUINO_BOARD} Board:") 109 | print_settings(${ARDUINO_BOARD}) 110 | endif() 111 | endfunction() 112 | 113 | 114 | 115 | # generate_arduino_library(TARGET_NAME) 116 | # 117 | # see documentation at top 118 | function(GENERATE_ARDUINO_LIBRARY TARGET_NAME) 119 | load_generator_settings(${TARGET_NAME} INPUT _SRCS # Sources 120 | _HDRS # Headers 121 | _LIBS # Libraries to linked in 122 | _BOARD) # Board name (such as uno, mega2560, ...) 123 | set(INPUT_AUTOLIBS True) 124 | if(DEFINED ${TARGET_NAME}_NO_AUTOLIBS AND ${TARGET_NAME}_NO_AUTOLIBS) 125 | set(INPUT_AUTOLIBS False) 126 | endif() 127 | 128 | message(STATUS "Generating ${TARGET_NAME}") 129 | 130 | set(ALL_LIBS) 131 | set(ALL_SRCS ${INPUT_SRCS} ${INPUT_HDRS}) 132 | 133 | setup_arduino_compiler(${INPUT_BOARD}) 134 | setup_arduino_core(CORE_LIB ${INPUT_BOARD}) 135 | 136 | if(INPUT_AUTOLIBS) 137 | setup_arduino_libraries(ALL_LIBS ${INPUT_BOARD} "${ALL_SRCS}") 138 | endif() 139 | 140 | list(APPEND ALL_LIBS ${CORE_LIB} ${INPUT_LIBS}) 141 | 142 | add_library(${TARGET_NAME} ${ALL_SRCS}) 143 | target_link_libraries(${TARGET_NAME} ${ALL_LIBS}) 144 | endfunction() 145 | 146 | # generate_arduino_firmware(TARGET_NAME) 147 | # 148 | # see documentation at top 149 | function(GENERATE_ARDUINO_FIRMWARE TARGET_NAME) 150 | load_generator_settings(${TARGET_NAME} INPUT _SRCS # Sources 151 | _HDRS # Headers 152 | _LIBS # Libraries to linked in 153 | _BOARD # Board name (such as uno, mega2560, ...) 154 | _PORT # Serial port, for upload and serial targets 155 | _AFLAGS # Override global Avrdude flags for target 156 | _SKETCHES # Arduino sketch files 157 | _SERIAL) # Serial command for serial target 158 | 159 | set(INPUT_AUTOLIBS True) 160 | if(DEFINED ${TARGET_NAME}_NO_AUTOLIBS AND ${TARGET_NAME}_NO_AUTOLIBS) 161 | set(INPUT_AUTOLIBS False) 162 | endif() 163 | 164 | message(STATUS "Generating ${TARGET_NAME}") 165 | 166 | set(ALL_LIBS) 167 | set(ALL_SRCS ${INPUT_SRCS} ${INPUT_HDRS}) 168 | 169 | setup_arduino_compiler(${INPUT_BOARD}) 170 | setup_arduino_core(CORE_LIB ${INPUT_BOARD}) 171 | 172 | #setup_arduino_sketch(SKETCH_SRCS ${INPUT_SKETCHES}) 173 | 174 | if(INPUT_AUTOLIBS) 175 | setup_arduino_libraries(ALL_LIBS ${INPUT_BOARD} "${ALL_SRCS}") 176 | endif() 177 | 178 | 179 | list(APPEND ALL_LIBS ${CORE_LIB} ${INPUT_LIBS}) 180 | 181 | setup_arduino_target(${TARGET_NAME} "${ALL_SRCS}" "${ALL_LIBS}") 182 | 183 | if(INPUT_PORT) 184 | setup_arduino_upload(${INPUT_BOARD} ${TARGET_NAME} ${INPUT_PORT}) 185 | endif() 186 | 187 | if(INPUT_SERIAL) 188 | setup_serial_target(${TARGET_NAME} "${INPUT_SERIAL}") 189 | endif() 190 | endfunction() 191 | 192 | 193 | # load_generator_settings(TARGET_NAME PREFIX [SUFFIX_1 SUFFIX_2 .. SUFFIX_N]) 194 | # 195 | # TARGET_NAME - The base name of the user settings 196 | # PREFIX - The prefix name used for generator settings 197 | # SUFFIX_XX - List of suffixes to load 198 | # 199 | # Loads a list of user settings into the generators scope. User settings have 200 | # the following syntax: 201 | # 202 | # ${BASE_NAME}${SUFFIX} 203 | # 204 | # The BASE_NAME is the target name and the suffix is a specific generator settings. 205 | # 206 | # For every user setting found a generator setting is created of the follwoing fromat: 207 | # 208 | # ${PREFIX}${SUFFIX} 209 | # 210 | # The purpose of loading the settings into the generator is to not modify user settings 211 | # and to have a generic naming of the settings within the generator. 212 | # 213 | function(LOAD_GENERATOR_SETTINGS TARGET_NAME PREFIX) 214 | foreach(GEN_SUFFIX ${ARGN}) 215 | if(${TARGET_NAME}${GEN_SUFFIX}) 216 | set(${PREFIX}${GEN_SUFFIX} ${${TARGET_NAME}${GEN_SUFFIX}} PARENT_SCOPE) 217 | endif() 218 | endforeach() 219 | endfunction() 220 | 221 | # setup_arduino_compiler(BOARD_ID) 222 | # 223 | # BOARD_ID - The board id name 224 | # 225 | # Configures the the build settings for the specified Arduino Board. 226 | # 227 | macro(setup_arduino_compiler BOARD_ID) 228 | set(BOARD_CORE ${${BOARD_ID}.build.core}) 229 | set(PIN_HEADER ${${BOARD_ID}.build.variant}) 230 | if(BOARD_CORE) 231 | if(ARDUINO_SDK_VERSION MATCHES "([0-9]+)[.]([0-9]+)") 232 | string(REPLACE "." "" ARDUINO_VERSION_DEFINE "${ARDUINO_SDK_VERSION}") # Normalize version (remove all periods) 233 | set(ARDUINO_VERSION_DEFINE "${CMAKE_MATCH_1}") 234 | if(CMAKE_MATCH_2 GREATER 10) 235 | set(ARDUINO_VERSION_DEFINE "${ARDUINO_VERSION_DEFINE}${CMAKE_MATCH_2}") 236 | else() 237 | set(ARDUINO_VERSION_DEFINE "${ARDUINO_VERSION_DEFINE}0${CMAKE_MATCH_2}") 238 | endif() 239 | else() 240 | set(ARDUINO_VERSION_DEFINE "00${ARDUINO_SDK_VERSION}") 241 | endif() 242 | set(BOARD_CORE_PATH ${ARDUINO_CORES_PATH}/${BOARD_CORE}) 243 | include_directories(${ARDUINO_VARIANTS_PATH}/${PIN_HEADER}) 244 | include_directories(${BOARD_CORE_PATH}) 245 | include_directories(${ARDUINO_LIBRARIES_PATH}) 246 | add_definitions(-DF_CPU=${${BOARD_ID}.build.f_cpu} 247 | -DARDUINO=${ARDUINO_VERSION_DEFINE} 248 | -mmcu=${${BOARD_ID}.build.mcu} 249 | ) 250 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -mmcu=${${BOARD_ID}.build.mcu}" PARENT_SCOPE) 251 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -mmcu=${${BOARD_ID}.build.mcu}" PARENT_SCOPE) 252 | set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -mmcu=${${BOARD_ID}.build.mcu}" PARENT_SCOPE) 253 | else() 254 | message(FATAL_ERROR "Invalid Arduino board ID (${BOARD_ID}), aborting.") 255 | endif() 256 | endmacro() 257 | 258 | # setup_arduino_core(VAR_NAME BOARD_ID) 259 | # 260 | # VAR_NAME - Variable name that will hold the generated library name 261 | # BOARD_ID - Arduino board id 262 | # 263 | # Creates the Arduino Core library for the specified board, 264 | # each board gets it's own version of the library. 265 | # 266 | function(setup_arduino_core VAR_NAME BOARD_ID) 267 | set(CORE_LIB_NAME ${BOARD_ID}_CORE) 268 | set(BOARD_CORE ${${BOARD_ID}.build.core}) 269 | if(BOARD_CORE AND NOT TARGET ${CORE_LIB_NAME}) 270 | set(BOARD_CORE_PATH ${ARDUINO_CORES_PATH}/${BOARD_CORE}) 271 | find_sources(CORE_SRCS ${BOARD_CORE_PATH} True) 272 | # Debian/Ubuntu fix 273 | list(REMOVE_ITEM CORE_SRCS "${BOARD_CORE_PATH}/main.cxx") 274 | add_library(${CORE_LIB_NAME} ${CORE_SRCS}) 275 | set(${VAR_NAME} ${CORE_LIB_NAME} PARENT_SCOPE) 276 | endif() 277 | endfunction() 278 | 279 | # find_arduino_libraries(VAR_NAME SRCS) 280 | # 281 | # VAR_NAME - Variable name which will hold the results 282 | # SRCS - Sources that will be analized 283 | # 284 | # returns a list of paths to libraries found. 285 | # 286 | # Finds all Arduino type libraries included in sources. Available libraries 287 | # are ${ARDUINO_SDK_PATH}/libraries and ${CMAKE_CURRENT_SOURCE_DIR}. 288 | # 289 | # A Arduino library is a folder that has the same name as the include header. 290 | # For example, if we have a include "#include " then the following 291 | # directory structure is considered a Arduino library: 292 | # 293 | # LibraryName/ 294 | # |- LibraryName.h 295 | # `- LibraryName.c 296 | # 297 | # If such a directory is found then all sources within that directory are considred 298 | # to be part of that Arduino library. 299 | # 300 | function(find_arduino_libraries VAR_NAME SRCS) 301 | set(ARDUINO_LIBS ) 302 | foreach(SRC ${SRCS}) 303 | file(STRINGS ${SRC} SRC_CONTENTS) 304 | foreach(SRC_LINE ${SRC_CONTENTS}) 305 | if("${SRC_LINE}" MATCHES "^ *#include *[<\"](.*)[>\"]") 306 | get_filename_component(INCLUDE_NAME ${CMAKE_MATCH_1} NAME_WE) 307 | get_property(LIBRARY_SEARCH_PATH 308 | DIRECTORY # Property Scope 309 | PROPERTY LINK_DIRECTORIES) 310 | foreach(LIB_SEARCH_PATH ${LIBRARY_SEARCH_PATH} ${ARDUINO_LIBRARIES_PATH} ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/libraries) 311 | if(EXISTS ${LIB_SEARCH_PATH}/${INCLUDE_NAME}/${CMAKE_MATCH_1}) 312 | list(APPEND ARDUINO_LIBS ${LIB_SEARCH_PATH}/${INCLUDE_NAME}) 313 | break() 314 | endif() 315 | endforeach() 316 | endif() 317 | endforeach() 318 | endforeach() 319 | if(ARDUINO_LIBS) 320 | list(REMOVE_DUPLICATES ARDUINO_LIBS) 321 | endif() 322 | set(${VAR_NAME} ${ARDUINO_LIBS} PARENT_SCOPE) 323 | endfunction() 324 | 325 | # setup_arduino_library(VAR_NAME BOARD_ID LIB_PATH) 326 | # 327 | # VAR_NAME - Vairable wich will hold the generated library names 328 | # BOARD_ID - Board name 329 | # LIB_PATH - path of the library 330 | # 331 | # Creates an Arduino library, with all it's library dependencies. 332 | # 333 | # ${LIB_NAME}_RECURSE controls if the library will recurse 334 | # when looking for source files. 335 | # 336 | 337 | # For known libraries can list recurse here 338 | set(Wire_RECURSE True) 339 | set(Ethernet_RECURSE True) 340 | set(SD_RECURSE True) 341 | function(setup_arduino_library VAR_NAME BOARD_ID LIB_PATH) 342 | set(LIB_TARGETS) 343 | 344 | get_filename_component(LIB_NAME ${LIB_PATH} NAME) 345 | set(TARGET_LIB_NAME ${BOARD_ID}_${LIB_NAME}) 346 | if(NOT TARGET ${TARGET_LIB_NAME}) 347 | string(REGEX REPLACE ".*/" "" LIB_SHORT_NAME ${LIB_NAME}) 348 | 349 | # Detect if recursion is needed 350 | if (NOT DEFINED ${LIB_SHORT_NAME}_RECURSE) 351 | set(${LIB_SHORT_NAME}_RECURSE False) 352 | endif() 353 | 354 | find_sources(LIB_SRCS ${LIB_PATH} ${${LIB_SHORT_NAME}_RECURSE}) 355 | if(LIB_SRCS) 356 | 357 | message(STATUS "Generating Arduino ${LIB_NAME} library") 358 | include_directories(${LIB_PATH} ${LIB_PATH}/utility) 359 | add_library(${TARGET_LIB_NAME} STATIC ${LIB_SRCS}) 360 | 361 | find_arduino_libraries(LIB_DEPS "${LIB_SRCS}") 362 | foreach(LIB_DEP ${LIB_DEPS}) 363 | setup_arduino_library(DEP_LIB_SRCS ${BOARD_ID} ${LIB_DEP}) 364 | list(APPEND LIB_TARGETS ${DEP_LIB_SRCS}) 365 | endforeach() 366 | 367 | target_link_libraries(${TARGET_LIB_NAME} ${BOARD_ID}_CORE ${LIB_TARGETS}) 368 | list(APPEND LIB_TARGETS ${TARGET_LIB_NAME}) 369 | endif() 370 | else() 371 | # Target already exists, skiping creating 372 | include_directories(${LIB_PATH} ${LIB_PATH}/utility) 373 | list(APPEND LIB_TARGETS ${TARGET_LIB_NAME}) 374 | endif() 375 | if(LIB_TARGETS) 376 | list(REMOVE_DUPLICATES LIB_TARGETS) 377 | endif() 378 | set(${VAR_NAME} ${LIB_TARGETS} PARENT_SCOPE) 379 | endfunction() 380 | 381 | # setup_arduino_libraries(VAR_NAME BOARD_ID SRCS) 382 | # 383 | # VAR_NAME - Vairable wich will hold the generated library names 384 | # BOARD_ID - Board ID 385 | # SRCS - source files 386 | # 387 | # Finds and creates all dependency libraries based on sources. 388 | # 389 | function(setup_arduino_libraries VAR_NAME BOARD_ID SRCS) 390 | set(LIB_TARGETS) 391 | find_arduino_libraries(TARGET_LIBS "${SRCS}") 392 | foreach(TARGET_LIB ${TARGET_LIBS}) 393 | setup_arduino_library(LIB_DEPS ${BOARD_ID} ${TARGET_LIB}) # Create static library instead of returning sources 394 | list(APPEND LIB_TARGETS ${LIB_DEPS}) 395 | endforeach() 396 | set(${VAR_NAME} ${LIB_TARGETS} PARENT_SCOPE) 397 | endfunction() 398 | 399 | 400 | # setup_arduino_target(TARGET_NAME ALL_SRCS ALL_LIBS) 401 | # 402 | # TARGET_NAME - Target name 403 | # ALL_SRCS - All sources 404 | # ALL_LIBS - All libraries 405 | # 406 | # Creates an Arduino firmware target. 407 | # 408 | function(setup_arduino_target TARGET_NAME ALL_SRCS ALL_LIBS) 409 | add_executable(${TARGET_NAME} ${ALL_SRCS}) 410 | target_link_libraries(${TARGET_NAME} ${ALL_LIBS}) 411 | set_target_properties(${TARGET_NAME} PROPERTIES SUFFIX ".elf") 412 | 413 | set(TARGET_PATH ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}) 414 | add_custom_command(TARGET ${TARGET_NAME} POST_BUILD 415 | COMMAND ${CMAKE_OBJCOPY} 416 | ARGS ${ARDUINO_OBJCOPY_EEP_FLAGS} 417 | ${TARGET_PATH}.elf 418 | ${TARGET_PATH}.eep 419 | VERBATIM) 420 | add_custom_command(TARGET ${TARGET_NAME} POST_BUILD 421 | COMMAND ${CMAKE_OBJCOPY} 422 | ARGS ${ARDUINO_OBJCOPY_HEX_FLAGS} 423 | ${TARGET_PATH}.elf 424 | ${TARGET_PATH}.hex 425 | VERBATIM) 426 | endfunction() 427 | 428 | # setup_arduino_upload(BOARD_ID TARGET_NAME PORT) 429 | # 430 | # BOARD_ID - Arduino board id 431 | # TARGET_NAME - Target name 432 | # PORT - Serial port for upload 433 | # 434 | # Create an upload target (${TARGET_NAME}-upload) for the specified Arduino target. 435 | # 436 | function(setup_arduino_upload BOARD_ID TARGET_NAME PORT) 437 | # setup_arduino_bootloader_upload() 438 | setup_arduino_bootloader_upload(${TARGET_NAME} ${BOARD_ID} ${PORT}) 439 | 440 | # Add programmer support if defined 441 | if(${TARGET_NAME}_PROGRAMMER AND ${${TARGET_NAME}_PROGRAMMER}.protocol) 442 | setup_arduino_programmer_burn(${TARGET_NAME} ${BOARD_ID} ${${TARGET_NAME}_PROGRAMMER} ${PORT}) 443 | setup_arduino_bootloader_burn(${TARGET_NAME} ${BOARD_ID} ${${TARGET_NAME}_PROGRAMMER} ${PORT}) 444 | endif() 445 | endfunction() 446 | 447 | 448 | # setup_arduino_bootloader_upload(TARGET_NAME BOARD_ID PORT) 449 | # 450 | # TARGET_NAME - target name 451 | # BOARD_ID - board id 452 | # PORT - serial port 453 | # 454 | # Set up target for upload firmware via the bootloader. 455 | # 456 | # The target for uploading the firmware is ${TARGET_NAME}-upload . 457 | # 458 | function(setup_arduino_bootloader_upload TARGET_NAME BOARD_ID PORT) 459 | set(UPLOAD_TARGET ${TARGET_NAME}-upload) 460 | set(AVRDUDE_ARGS) 461 | 462 | setup_arduino_bootloader_args(${BOARD_ID} ${TARGET_NAME} ${PORT} AVRDUDE_ARGS) 463 | 464 | if(NOT AVRDUDE_ARGS) 465 | message("Could not generate default avrdude bootloader args, aborting!") 466 | return() 467 | endif() 468 | 469 | list(APPEND AVRDUDE_ARGS "-Uflash:w:${TARGET_NAME}.hex") 470 | add_custom_target(${UPLOAD_TARGET} 471 | ${ARDUINO_AVRDUDE_PROGRAM} 472 | ${AVRDUDE_ARGS} 473 | DEPENDS ${TARGET_NAME}) 474 | endfunction() 475 | 476 | # setup_arduino_programmer_burn(TARGET_NAME BOARD_ID PROGRAMMER) 477 | # 478 | # TARGET_NAME - name of target to burn 479 | # BOARD_ID - board id 480 | # PROGRAMMER - programmer id 481 | # 482 | # Sets up target for burning firmware via a programmer. 483 | # 484 | # The target for burning the firmware is ${TARGET_NAME}-burn . 485 | # 486 | function(setup_arduino_programmer_burn TARGET_NAME BOARD_ID PROGRAMMER) 487 | set(PROGRAMMER_TARGET ${TARGET_NAME}-burn) 488 | 489 | set(AVRDUDE_ARGS) 490 | 491 | setup_arduino_programmer_args(${BOARD_ID} ${PROGRAMMER} ${TARGET_NAME} ${PORT} AVRDUDE_ARGS) 492 | 493 | if(NOT AVRDUDE_ARGS) 494 | message("Could not generate default avrdude programmer args, aborting!") 495 | return() 496 | endif() 497 | 498 | list(APPEND AVRDUDE_ARGS "-Uflash:w:${TARGET_NAME}.hex") 499 | 500 | add_custom_target(${PROGRAMMER_TARGET} 501 | ${ARDUINO_AVRDUDE_PROGRAM} 502 | ${AVRDUDE_ARGS} 503 | DEPENDS ${TARGET_NAME}) 504 | endfunction() 505 | 506 | # setup_arduino_bootloader_burn(TARGET_NAME BOARD_ID PROGRAMMER) 507 | # 508 | # TARGET_NAME - name of target to burn 509 | # BOARD_ID - board id 510 | # PROGRAMMER - programmer id 511 | # 512 | # Create a target for burning a bootloader via a programmer. 513 | # 514 | # The target for burning the bootloader is ${TARGET_NAME}-burn-bootloader 515 | # 516 | function(setup_arduino_bootloader_burn TARGET_NAME BOARD_ID PROGRAMMER PORT) 517 | set(BOOTLOADER_TARGET ${TARGET_NAME}-burn-bootloader) 518 | 519 | set(AVRDUDE_ARGS) 520 | 521 | setup_arduino_programmer_args(${BOARD_ID} ${PROGRAMMER} ${TARGET_NAME} ${PORT} AVRDUDE_ARGS) 522 | 523 | if(NOT AVRDUDE_ARGS) 524 | message("Could not generate default avrdude programmer args, aborting!") 525 | return() 526 | endif() 527 | 528 | if(NOT ${BOARD_ID}.bootloader.unlock_bits) 529 | message("Missing ${BOARD_ID}.bootloader.unlock_bits, not creating bootloader burn target ${BOOTLOADER_TARGET}.") 530 | return() 531 | endif() 532 | if(NOT ${BOARD_ID}.bootloader.high_fuses) 533 | message("Missing ${BOARD_ID}.bootloader.high_fuses, not creating bootloader burn target ${BOOTLOADER_TARGET}.") 534 | return() 535 | endif() 536 | if(NOT ${BOARD_ID}.bootloader.low_fuses) 537 | message("Missing ${BOARD_ID}.bootloader.low_fuses, not creating bootloader burn target ${BOOTLOADER_TARGET}.") 538 | return() 539 | endif() 540 | if(NOT ${BOARD_ID}.bootloader.path) 541 | message("Missing ${BOARD_ID}.bootloader.path, not creating bootloader burn target ${BOOTLOADER_TARGET}.") 542 | return() 543 | endif() 544 | if(NOT ${BOARD_ID}.bootloader.file) 545 | message("Missing ${BOARD_ID}.bootloader.file, not creating bootloader burn target ${BOOTLOADER_TARGET}.") 546 | return() 547 | endif() 548 | 549 | if(NOT EXISTS "${ARDUINO_BOOTLOADERS_PATH}/${${BOARD_ID}.bootloader.path}/${${BOARD_ID}.bootloader.file}") 550 | message("${ARDUINO_BOOTLOADERS_PATH}/${${BOARD_ID}.bootloader.path}/${${BOARD_ID}.bootloader.file}") 551 | message("Missing bootloader image, not creating bootloader burn target ${BOOTLOADER_TARGET}.") 552 | return() 553 | endif() 554 | 555 | # Erase the chip 556 | list(APPEND AVRDUDE_ARGS "-e") 557 | 558 | # Set unlock bits and fuses (because chip is going to be erased) 559 | list(APPEND AVRDUDE_ARGS "-Ulock:w:${${BOARD_ID}.bootloader.unlock_bits}:m") 560 | if(${BOARD_ID}.bootloader.extended_fuses) 561 | list(APPEND AVRDUDE_ARGS "-Uefuse:w:${${BOARD_ID}.bootloader.extended_fuses}:m") 562 | endif() 563 | list(APPEND AVRDUDE_ARGS "-Uhfuse:w:${${BOARD_ID}.bootloader.high_fuses}:m") 564 | list(APPEND AVRDUDE_ARGS "-Ulfuse:w:${${BOARD_ID}.bootloader.low_fuses}:m") 565 | 566 | # Set bootloader image 567 | list(APPEND AVRDUDE_ARGS "-Uflash:w:${${BOARD_ID}.bootloader.file}:i") 568 | 569 | # Set lockbits 570 | list(APPEND AVRDUDE_ARGS "-Ulock:w:${${BOARD_ID}.bootloader.lock_bits}:m") 571 | 572 | # Create burn bootloader target 573 | add_custom_target(${BOOTLOADER_TARGET} 574 | ${ARDUINO_AVRDUDE_PROGRAM} 575 | ${AVRDUDE_ARGS} 576 | WORKING_DIRECTORY ${ARDUINO_BOOTLOADERS_PATH}/${${BOARD_ID}.bootloader.path} 577 | DEPENDS ${TARGET_NAME}) 578 | endfunction() 579 | 580 | # setup_arduino_programmer_args(PROGRAMMER OUTPUT_VAR) 581 | # 582 | # PROGRAMMER - programmer id 583 | # TARGET_NAME - target name 584 | # OUTPUT_VAR - name of output variable for result 585 | # 586 | # Sets up default avrdude settings for burning firmware via a programmer. 587 | function(setup_arduino_programmer_args BOARD_ID PROGRAMMER TARGET_NAME PORT OUTPUT_VAR) 588 | set(AVRDUDE_ARGS ${${OUTPUT_VAR}}) 589 | 590 | set(AVRDUDE_FLAGS ${ARDUINO_AVRDUDE_FLAGS}) 591 | if(DEFINED ${TARGET_NAME}_AFLAGS) 592 | set(AVRDUDE_FLAGS ${${TARGET_NAME}_AFLAGS}) 593 | endif() 594 | 595 | list(APPEND AVRDUDE_ARGS "-C${ARDUINO_AVRDUDE_CONFIG_PATH}") 596 | 597 | #TODO: Check mandatory settings before continuing 598 | if(NOT ${PROGRAMMER}.protocol) 599 | message(FATAL_ERROR "Missing ${PROGRAMMER}.protocol, aborting!") 600 | endif() 601 | 602 | list(APPEND AVRDUDE_ARGS "-c${${PROGRAMMER}.protocol}") # Set programmer 603 | 604 | if(${PROGRAMMER}.communication STREQUAL "usb") 605 | list(APPEND AVRDUDE_ARGS "-Pusb") # Set USB as port 606 | elseif(${PROGRAMMER}.communication STREQUAL "serial") 607 | list(APPEND AVRDUDE_ARGS "-P${PORT}") # Set port 608 | if(${PROGRAMMER}.speed) 609 | list(APPEND AVRDUDE_ARGS "-b${${PROGRAMMER}.speed}") # Set baud rate 610 | endif() 611 | endif() 612 | 613 | if(${PROGRAMMER}.force) 614 | list(APPEND AVRDUDE_ARGS "-F") # Set force 615 | endif() 616 | 617 | if(${PROGRAMMER}.delay) 618 | list(APPEND AVRDUDE_ARGS "-i${${PROGRAMMER}.delay}") # Set delay 619 | endif() 620 | 621 | list(APPEND AVRDUDE_ARGS "-p${${BOARD_ID}.build.mcu}") # MCU Type 622 | 623 | list(APPEND AVRDUDE_ARGS ${AVRDUDE_FLAGS}) 624 | 625 | set(${OUTPUT_VAR} ${AVRDUDE_ARGS} PARENT_SCOPE) 626 | endfunction() 627 | 628 | # setup_arduino_bootloader_args(BOARD_ID TARGET_NAME PORT OUTPUT_VAR) 629 | # 630 | # BOARD_ID - board id 631 | # TARGET_NAME - target name 632 | # PORT - serial port 633 | # OUTPUT_VAR - name of output variable for result 634 | # 635 | # Sets up default avrdude settings for uploading firmware via the bootloader. 636 | function(setup_arduino_bootloader_args BOARD_ID TARGET_NAME PORT OUTPUT_VAR) 637 | set(AVRDUDE_ARGS ${${OUTPUT_VAR}}) 638 | 639 | set(AVRDUDE_FLAGS ${ARDUINO_AVRDUDE_FLAGS}) 640 | if(DEFINED ${TARGET_NAME}_AFLAGS) 641 | set(AVRDUDE_FLAGS ${${TARGET_NAME}_AFLAGS}) 642 | endif() 643 | 644 | list(APPEND AVRDUDE_ARGS "-C${ARDUINO_AVRDUDE_CONFIG_PATH}") # avrdude config 645 | 646 | list(APPEND AVRDUDE_ARGS "-p${${BOARD_ID}.build.mcu}") # MCU Type 647 | 648 | # Programmer 649 | if(${BOARD_ID}.upload.protocol STREQUAL "stk500") 650 | list(APPEND AVRDUDE_ARGS "-cstk500v1") 651 | else() 652 | list(APPEND AVRDUDE_ARGS "-c${${BOARD_ID}.upload.protocol}") 653 | endif() 654 | 655 | list(APPEND AVRDUDE_ARGS "-b${${BOARD_ID}.upload.speed}") # Baud rate 656 | 657 | list(APPEND AVRDUDE_ARGS "-P${PORT}") # Serial port 658 | 659 | list(APPEND AVRDUDE_ARGS "-D") # Dont erase 660 | 661 | list(APPEND AVRDUDE_ARGS ${AVRDUDE_FLAGS}) 662 | 663 | set(${OUTPUT_VAR} ${AVRDUDE_ARGS} PARENT_SCOPE) 664 | endfunction() 665 | 666 | # find_sources(VAR_NAME LIB_PATH RECURSE) 667 | # 668 | # VAR_NAME - Variable name that will hold the detected sources 669 | # LIB_PATH - The base path 670 | # RECURSE - Whether or not to recurse 671 | # 672 | # Finds all C/C++ sources located at the specified path. 673 | # 674 | function(find_sources VAR_NAME LIB_PATH RECURSE) 675 | set(FILE_SEARCH_LIST 676 | ${LIB_PATH}/*.cpp 677 | ${LIB_PATH}/*.c 678 | ${LIB_PATH}/*.cc 679 | ${LIB_PATH}/*.cxx 680 | ${LIB_PATH}/*.h 681 | ${LIB_PATH}/*.hh 682 | ${LIB_PATH}/*.hxx) 683 | 684 | if(RECURSE) 685 | file(GLOB_RECURSE LIB_FILES ${FILE_SEARCH_LIST}) 686 | else() 687 | file(GLOB LIB_FILES ${FILE_SEARCH_LIST}) 688 | endif() 689 | 690 | if(LIB_FILES) 691 | set(${VAR_NAME} ${LIB_FILES} PARENT_SCOPE) 692 | endif() 693 | endfunction() 694 | 695 | # setup_serial_target(TARGET_NAME CMD) 696 | # 697 | # TARGET_NAME - Target name 698 | # CMD - Serial terminal command 699 | # 700 | # Creates a target (${TARGET_NAME}-serial) for launching the serial termnial. 701 | # 702 | function(setup_serial_target TARGET_NAME CMD) 703 | string(CONFIGURE "${CMD}" FULL_CMD @ONLY) 704 | add_custom_target(${TARGET_NAME}-serial 705 | ${FULL_CMD}) 706 | endfunction() 707 | 708 | 709 | # detect_arduino_version(VAR_NAME) 710 | # 711 | # VAR_NAME - Variable name where the detected version will be saved 712 | # 713 | # Detects the Arduino SDK Version based on the revisions.txt file. 714 | # 715 | function(detect_arduino_version VAR_NAME) 716 | if(ARDUINO_VERSION_PATH) 717 | file(READ ${ARDUINO_VERSION_PATH} ARD_VERSION) 718 | if("${ARD_VERSION}" MATCHES " *[0]+([0-9]+)") 719 | set(${VAR_NAME} ${CMAKE_MATCH_1} PARENT_SCOPE) 720 | elseif("${ARD_VERSION}" MATCHES "[ ]*([0-9]+[.][0-9]+)") 721 | set(${VAR_NAME} ${CMAKE_MATCH_1} PARENT_SCOPE) 722 | endif() 723 | endif() 724 | endfunction() 725 | 726 | 727 | function(convert_arduino_sketch VAR_NAME SRCS) 728 | endfunction() 729 | 730 | # load_arduino_style_settings(SETTINGS_LIST SETTINGS_PATH) 731 | # 732 | # SETTINGS_LIST - Variable name of settings list 733 | # SETTINGS_PATH - File path of settings file to load. 734 | # 735 | # Load a Arduino style settings file into the cache. 736 | # 737 | # Examples of this type of settings file is the boards.txt and 738 | # programmers.txt files located in ${ARDUINO_SDK}/hardware/arduino. 739 | # 740 | # Settings have to following format: 741 | # 742 | # entry.setting[.subsetting] = value 743 | # 744 | # where [.subsetting] is optional 745 | # 746 | # For example, the following settings: 747 | # 748 | # uno.name=Arduino Uno 749 | # uno.upload.protocol=stk500 750 | # uno.upload.maximum_size=32256 751 | # uno.build.mcu=atmega328p 752 | # uno.build.core=arduino 753 | # 754 | # will generate the follwoing equivalent CMake variables: 755 | # 756 | # set(uno.name "Arduino Uno") 757 | # set(uno.upload.protocol "stk500") 758 | # set(uno.upload.maximum_size "32256") 759 | # set(uno.build.mcu "atmega328p") 760 | # set(uno.build.core "arduino") 761 | # 762 | # set(uno.SETTINGS name upload build) # List of settings for uno 763 | # set(uno.upload.SUBSETTINGS protocol maximum_size) # List of sub-settings for uno.upload 764 | # set(uno.build.SUBSETTINGS mcu core) # List of sub-settings for uno.build 765 | # 766 | # The ${ENTRY_NAME}.SETTINGS variable lists all settings for the entry, while 767 | # ${ENTRY_NAME}.SUBSETTINGS variables lists all settings for a sub-setting of 768 | # a entry setting pair. 769 | # 770 | # These variables are generated in order to be able to programatically traverse 771 | # all settings (for a example see print_board_settings() function). 772 | # 773 | function(LOAD_ARDUINO_STYLE_SETTINGS SETTINGS_LIST SETTINGS_PATH) 774 | 775 | if(NOT ${SETTINGS_LIST} AND EXISTS ${SETTINGS_PATH}) 776 | file(STRINGS ${SETTINGS_PATH} FILE_ENTRIES) # Settings file split into lines 777 | 778 | foreach(FILE_ENTRY ${FILE_ENTRIES}) 779 | if("${FILE_ENTRY}" MATCHES "^[^#]+=.*") 780 | string(REGEX MATCH "^[^=]+" SETTING_NAME ${FILE_ENTRY}) 781 | string(REGEX MATCH "[^=]+$" SETTING_VALUE ${FILE_ENTRY}) 782 | string(REPLACE "." ";" ENTRY_NAME_TOKENS ${SETTING_NAME}) 783 | string(STRIP "${SETTING_VALUE}" SETTING_VALUE) 784 | 785 | list(LENGTH ENTRY_NAME_TOKENS ENTRY_NAME_TOKENS_LEN) 786 | 787 | 788 | # Add entry to settings list if it does not exist 789 | list(GET ENTRY_NAME_TOKENS 0 ENTRY_NAME) 790 | list(FIND ${SETTINGS_LIST} ${ENTRY_NAME} ENTRY_NAME_INDEX) 791 | if(ENTRY_NAME_INDEX LESS 0) 792 | # Add entry to main list 793 | list(APPEND ${SETTINGS_LIST} ${ENTRY_NAME}) 794 | endif() 795 | 796 | # Add entry setting to entry settings list if it does not exist 797 | set(ENTRY_SETTING_LIST ${ENTRY_NAME}.SETTINGS) 798 | list(GET ENTRY_NAME_TOKENS 1 ENTRY_SETTING) 799 | list(FIND ${ENTRY_SETTING_LIST} ${ENTRY_SETTING} ENTRY_SETTING_INDEX) 800 | if(ENTRY_SETTING_INDEX LESS 0) 801 | # Add setting to entry 802 | list(APPEND ${ENTRY_SETTING_LIST} ${ENTRY_SETTING}) 803 | set(${ENTRY_SETTING_LIST} ${${ENTRY_SETTING_LIST}} 804 | CACHE INTERNAL "Arduino ${ENTRY_NAME} Board settings list") 805 | endif() 806 | 807 | set(FULL_SETTING_NAME ${ENTRY_NAME}.${ENTRY_SETTING}) 808 | 809 | # Add entry sub-setting to entry sub-settings list if it does not exists 810 | if(ENTRY_NAME_TOKENS_LEN GREATER 2) 811 | set(ENTRY_SUBSETTING_LIST ${ENTRY_NAME}.${ENTRY_SETTING}.SUBSETTINGS) 812 | list(GET ENTRY_NAME_TOKENS 2 ENTRY_SUBSETTING) 813 | list(FIND ${ENTRY_SUBSETTING_LIST} ${ENTRY_SUBSETTING} ENTRY_SUBSETTING_INDEX) 814 | if(ENTRY_SUBSETTING_INDEX LESS 0) 815 | list(APPEND ${ENTRY_SUBSETTING_LIST} ${ENTRY_SUBSETTING}) 816 | set(${ENTRY_SUBSETTING_LIST} ${${ENTRY_SUBSETTING_LIST}} 817 | CACHE INTERNAL "Arduino ${ENTRY_NAME} Board sub-settings list") 818 | endif() 819 | set(FULL_SETTING_NAME ${FULL_SETTING_NAME}.${ENTRY_SUBSETTING}) 820 | endif() 821 | 822 | # Save setting value 823 | set(${FULL_SETTING_NAME} ${SETTING_VALUE} 824 | CACHE INTERNAL "Arduino ${ENTRY_NAME} Board setting") 825 | 826 | 827 | endif() 828 | endforeach() 829 | set(${SETTINGS_LIST} ${${SETTINGS_LIST}} 830 | CACHE STRING "List of detected Arduino Board configurations") 831 | mark_as_advanced(${SETTINGS_LIST}) 832 | endif() 833 | endfunction() 834 | 835 | # print_settings(ENTRY_NAME) 836 | # 837 | # ENTRY_NAME - name of entry 838 | # 839 | # Print the entry settings (see load_arduino_syle_settings()). 840 | # 841 | function(PRINT_SETTINGS ENTRY_NAME) 842 | if(${ENTRY_NAME}.SETTINGS) 843 | 844 | foreach(ENTRY_SETTING ${${ENTRY_NAME}.SETTINGS}) 845 | if(${ENTRY_NAME}.${ENTRY_SETTING}) 846 | message(STATUS " ${ENTRY_NAME}.${ENTRY_SETTING}=${${ENTRY_NAME}.${ENTRY_SETTING}}") 847 | endif() 848 | if(${ENTRY_NAME}.${ENTRY_SETTING}.SUBSETTINGS) 849 | foreach(ENTRY_SUBSETTING ${${ENTRY_NAME}.${ENTRY_SETTING}.SUBSETTINGS}) 850 | if(${ENTRY_NAME}.${ENTRY_SETTING}.${ENTRY_SUBSETTING}) 851 | message(STATUS " ${ENTRY_NAME}.${ENTRY_SETTING}.${ENTRY_SUBSETTING}=${${ENTRY_NAME}.${ENTRY_SETTING}.${ENTRY_SUBSETTING}}") 852 | endif() 853 | endforeach() 854 | endif() 855 | message(STATUS "") 856 | endforeach() 857 | endif() 858 | endfunction() 859 | 860 | # print_list(SETTINGS_LIST) 861 | # 862 | # SETTINGS_LIST - Variables name of settings list 863 | # 864 | # Print list settings and names (see load_arduino_syle_settings()). 865 | function(PRINT_LIST SETTINGS_LIST) 866 | if(${SETTINGS_LIST}) 867 | set(MAX_LENGTH 0) 868 | foreach(ENTRY_NAME ${${SETTINGS_LIST}}) 869 | string(LENGTH "${ENTRY_NAME}" CURRENT_LENGTH) 870 | if(CURRENT_LENGTH GREATER MAX_LENGTH) 871 | set(MAX_LENGTH ${CURRENT_LENGTH}) 872 | endif() 873 | endforeach() 874 | foreach(ENTRY_NAME ${${SETTINGS_LIST}}) 875 | string(LENGTH "${ENTRY_NAME}" CURRENT_LENGTH) 876 | math(EXPR PADDING_LENGTH "${MAX_LENGTH}-${CURRENT_LENGTH}") 877 | set(PADDING "") 878 | foreach(X RANGE ${PADDING_LENGTH}) 879 | set(PADDING "${PADDING} ") 880 | endforeach() 881 | message(STATUS " ${PADDING}${ENTRY_NAME}: ${${ENTRY_NAME}.name}") 882 | endforeach() 883 | endif() 884 | endfunction() 885 | 886 | 887 | # Setting up Arduino enviroment settings 888 | if(NOT ARDUINO_FOUND) 889 | find_file(ARDUINO_CORES_PATH 890 | NAMES cores 891 | PATHS ${ARDUINO_SDK_PATH} 892 | PATH_SUFFIXES hardware/arduino) 893 | 894 | find_file(ARDUINO_VARIANTS_PATH 895 | NAMES variants 896 | PATHS ${ARDUINO_SDK_PATH} 897 | PATH_SUFFIXES hardware/arduino) 898 | 899 | find_file(ARDUINO_BOOTLOADERS_PATH 900 | NAMES bootloaders 901 | PATHS ${ARDUINO_SDK_PATH} 902 | PATH_SUFFIXES hardware/arduino) 903 | 904 | find_file(ARDUINO_LIBRARIES_PATH 905 | NAMES libraries 906 | PATHS ${ARDUINO_SDK_PATH}) 907 | 908 | find_file(ARDUINO_BOARDS_PATH 909 | NAMES boards.txt 910 | PATHS ${ARDUINO_SDK_PATH} 911 | PATH_SUFFIXES hardware/arduino) 912 | 913 | find_file(ARDUINO_PROGRAMMERS_PATH 914 | NAMES programmers.txt 915 | PATHS ${ARDUINO_SDK_PATH} 916 | PATH_SUFFIXES hardware/arduino) 917 | 918 | find_file(ARDUINO_REVISIONS_PATH 919 | NAMES revisions.txt 920 | PATHS ${ARDUINO_SDK_PATH}) 921 | 922 | find_file(ARDUINO_VERSION_PATH 923 | NAMES lib/version.txt 924 | PATHS ${ARDUINO_SDK_PATH}) 925 | 926 | find_program(ARDUINO_AVRDUDE_PROGRAM 927 | NAMES avrdude 928 | PATHS ${ARDUINO_SDK_PATH} 929 | PATH_SUFFIXES hardware/tools) 930 | 931 | find_program(ARDUINO_AVRDUDE_CONFIG_PATH 932 | NAMES avrdude.conf 933 | PATHS ${ARDUINO_SDK_PATH} /etc/avrdude 934 | PATH_SUFFIXES hardware/tools 935 | hardware/tools/avr/etc) 936 | 937 | set(ARDUINO_OBJCOPY_EEP_FLAGS -O ihex -j .eeprom --set-section-flags=.eeprom=alloc,load --no-change-warnings --change-section-lma .eeprom=0 938 | CACHE STRING "") 939 | set(ARDUINO_OBJCOPY_HEX_FLAGS -O ihex -R .eeprom 940 | CACHE STRING "") 941 | set(ARDUINO_AVRDUDE_FLAGS -V 942 | CACHE STRING "Arvdude global flag list.") 943 | 944 | if(ARDUINO_SDK_PATH) 945 | detect_arduino_version(ARDUINO_SDK_VERSION) 946 | set(ARDUINO_SDK_VERSION ${ARDUINO_SDK_VERSION} CACHE STRING "Arduino SDK Version") 947 | endif(ARDUINO_SDK_PATH) 948 | 949 | include(FindPackageHandleStandardArgs) 950 | find_package_handle_standard_args(Arduino 951 | REQUIRED_VARS ARDUINO_SDK_PATH 952 | ARDUINO_SDK_VERSION 953 | VERSION_VAR ARDUINO_SDK_VERSION) 954 | 955 | 956 | mark_as_advanced(ARDUINO_CORES_PATH 957 | ARDUINO_VARIANTS_PATH 958 | ARDUINO_BOOTLOADERS_PATH 959 | ARDUINO_SDK_VERSION 960 | ARDUINO_LIBRARIES_PATH 961 | ARDUINO_BOARDS_PATH 962 | ARDUINO_PROGRAMMERS_PATH 963 | ARDUINO_REVISIONS_PATH 964 | ARDUINO_VERSION_PATH 965 | ARDUINO_AVRDUDE_FLAGS 966 | ARDUINO_AVRDUDE_PROGRAM 967 | ARDUINO_AVRDUDE_CONFIG_PATH 968 | ARDUINO_OBJCOPY_EEP_FLAGS 969 | ARDUINO_OBJCOPY_HEX_FLAGS) 970 | 971 | load_board_settings() 972 | load_programmers_settings() 973 | 974 | print_board_list() 975 | print_programmer_list() 976 | 977 | set(ARDUINO_FOUND True CACHE INTERNAL "Arduino Found") 978 | endif() 979 | 980 | --------------------------------------------------------------------------------