diff --git a/blink-cmake/CMakeLists.txt b/blink-cmake/CMakeLists.txt
index c1b419ecadfbe489e047d0bd67967046287cbfcd..ce98a30d869122cdd276b657652f0276c48bc37d 100644
--- a/blink-cmake/CMakeLists.txt
+++ b/blink-cmake/CMakeLists.txt
@@ -1,33 +1,19 @@
 cmake_minimum_required(VERSION 3.13) # 3.13 is required for target_link_options
 project(HelloSAMD51 C)
 
-set(CMAKE_SYSTEM_NAME Generic)
-set(CMAKE_SYSTEM_PROCESSOR arm)
-set(CMAKE_CROSSCOMPILING 1)
-set(CMAKE_C_COMPILER "arm-none-eabi-gcc")
-set(CMAKE_CXX_COMPILER "arm-none-eabi-g++")
-set(CMAKE_AR "arm-none-eabi-ar")
-set(CMAKE_RANLIB "arm-none-eabi-ranlib")
-set(CMAKE_C_FLAGS "")
-set(CMAKE_C_LINK_FLAGS "")
-
-# search for programs in the build host directories
-SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
-# for libraries and headers in the target directories
-SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
-SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
-SET(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
-
-set(MCU_DEFINE "__SAMD51J19A__")
-
+# Default to optimizing for size. If you want to build with debug symbols, run cmake again with
+# -DCMAKE_BUILD_TYPE=Debug as an argument.
 if(NOT CMAKE_BUILD_TYPE)
-    set(CMAKE_BUILD_TYPE "Release")
+    set(CMAKE_BUILD_TYPE "MinSizeRel")
 endif()
 message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
 
+# Pull in common compiler flags and package the ASF into a library.
+include(cmake/toolchain_arm-none-eabi.cmake)
 add_subdirectory(samd51)
 
+# Define an executable, and link it against ASF and the C math library.
 add_executable(hello_world
     main.c
 )
-target_link_libraries(hello_world asf)
+target_link_libraries(hello_world asf m)
diff --git a/blink-cmake/cmake/toolchain_arm-none-eabi.cmake b/blink-cmake/cmake/toolchain_arm-none-eabi.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..f90d387ae4c8db456f0e8afba8c617ead667d190
--- /dev/null
+++ b/blink-cmake/cmake/toolchain_arm-none-eabi.cmake
@@ -0,0 +1,50 @@
+#---------------------------------------------------------------------------------------------------
+# This file tells cmake how to build for samd51 with arm-none-eabi-gcc.
+
+# These commands tell cmake that we're cross compiling to ARM with arm-none-eabi.
+set(CMAKE_SYSTEM_NAME Generic)
+set(CMAKE_SYSTEM_PROCESSOR arm)
+set(CMAKE_CROSSCOMPILING 1)
+set(CMAKE_C_COMPILER "arm-none-eabi-gcc")
+set(CMAKE_CXX_COMPILER "arm-none-eabi-g++")
+set(CMAKE_AR "arm-none-eabi-ar")
+set(CMAKE_RANLIB "arm-none-eabi-ranlib")
+
+# Clear default compiler and linker flags.
+set(CMAKE_C_FLAGS "")
+set(CMAKE_C_LINK_FLAGS "")
+
+# This should eventually live somewhere else, since we'll be using J19s and J20s.
+set(MCU_DEFINE "__SAMD51J19A__")
+
+# Search for programs in the build host directories.
+SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+# Search for libraries and headers in the target directories.
+SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+SET(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
+
+
+# This creates an interface library that holds common compiler and link flags. An interface library
+# is one that has no actual targets to build; it's a virtual library that we can use to manage
+# dependencies.
+add_library(shared_settings INTERFACE)
+
+# These flags are derived from Adafruit projects. They might not all be necessary.
+list(APPEND common_flags
+    -mthumb
+    -mabi=aapcs-linux
+    -mcpu=cortex-m4
+    -mfpu=fpv4-sp-d16
+    -mfloat-abi=softfp
+    -mlong-calls
+    -DSAMD51
+)
+
+target_compile_options(shared_settings INTERFACE
+    ${common_flags}
+    -D${MCU_DEFINE}
+    -ffunction-sections
+    -Wall
+)
+target_link_options(shared_settings INTERFACE ${common_flags})
diff --git a/blink-cmake/samd51/CMakeLists.txt b/blink-cmake/samd51/CMakeLists.txt
index 19b6bd59a2cc38e3ed6fccc56c03b307a152045f..58f071ac26130267d8fc860f8b7af7624f76b503 100644
--- a/blink-cmake/samd51/CMakeLists.txt
+++ b/blink-cmake/samd51/CMakeLists.txt
@@ -1,5 +1,5 @@
 #---------------------------------------------------------------------------------------------------
-# This file creates an asf library.
+# This file packages ASF code into a library.
 
 # By default, cmake would make a static library and package the object files into an archive. This
 # doesn't work since the linker then throws away the startup code, since it's not called by
@@ -10,44 +10,18 @@ add_library(asf OBJECT
     startup/system_samd51.c
 )
 
+# Pull in the shared compiler flags from the toolchain file. (Aren't interface libraries nice?)
+target_link_libraries(asf shared_settings)
+
+# Every target that links against asf needs to know where the ASF headers are.
 target_include_directories(asf PUBLIC
     ${CMAKE_CURRENT_SOURCE_DIR}/CMSIS/Include
     ${CMAKE_CURRENT_SOURCE_DIR}/include
 )
 
-target_link_libraries(asf m)
-
+# Use the custom linker script provided with ASF.
 target_link_options(asf PUBLIC
-    -mthumb
-    -mabi=aapcs-linux
-    -mlong-calls
-    -mcpu=cortex-m4
-    -mfpu=fpv4-sp-d16
-    -mfloat-abi=softfp
-    -DSAMD51
     -T${CMAKE_CURRENT_SOURCE_DIR}/startup/samd51j19a_flash.ld
-    -L${CMAKE_CURRENT_SOURCE_DIR}/startup
-    #-Wl,--start-group
-    #-Wl,--end-group
-    #-Wl,-Map="main.map"
     --specs=nano.specs
     -Wl,--gc-sections
 )
-
-target_compile_options(asf PUBLIC
-    -D${MCU_DEFINE}
-    -x c
-    -DDEBUG
-    -Os
-    -ffunction-sections
-    #-g3
-    -Wall
-    -std=gnu99
-    -mthumb # use T32 instruction set instead of A32 (don't know if this matters)
-    -mabi=aapcs-linux
-    -mlong-calls # changes how functions are called
-    -mcpu=cortex-m4
-    -mfpu=fpv4-sp-d16
-    -mfloat-abi=softfp # this flag specifies whether to use software or hardware float operations
-    -DSAMD51
-)