diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,18 @@
+arduino-copilot (1.1.1) unstable; urgency=medium
+
+  * New Copilot.Arduino.Library.Serial module.
+  * Added the analog pins for Uno and Nano, and support using
+    them for digital IO, when the hardware allows that to be done.
+  * Support reading voltage from analog pins.
+  * Support PWM.
+  * Added a way to enable pullup resistors for input pins.
+  * Detect if a program tries to use the same pin for both input and
+    output. This is not currently supported, so it will refuse to build the
+    program.
+  * Copilot.Arduino.Internals API changed.
+
+ -- Joey Hess <id@joeyh.name>  Sun, 26 Jan 2020 20:37:26 -0400
+
 arduino-copilot (1.0.1) unstable; urgency=medium
 
   * Move example from cabal description (where it did not render correctly)
diff --git a/TODO b/TODO
--- a/TODO
+++ b/TODO
@@ -1,2 +1,11 @@
-* analog pins
-* serial console support (involves arrays of bytes)
+* analogReference()
+  Takes one of a set of defines, which vary depending on board,
+  so how to pass that through Copilot? Could write a C
+  switch statement, on an int, and have the boade modules define
+  effectively enums.
+* readvoltage gets a raw ADC value; add a stream function to convert that
+  to a floating point actual voltage, given also a stream that contains
+  the reference voltage.
+* delayMicroseconds
+* random numbers
+* Wire library
diff --git a/arduino-copilot.cabal b/arduino-copilot.cabal
--- a/arduino-copilot.cabal
+++ b/arduino-copilot.cabal
@@ -1,5 +1,5 @@
 Name: arduino-copilot
-Version: 1.0.1
+Version: 1.1.1
 Cabal-Version: >= 1.8
 License: BSD3
 Maintainer: Joey Hess <id@joeyh.name>
@@ -50,18 +50,21 @@
     Copilot.Arduino.Nano
     Copilot.Arduino.Uno
     Copilot.Arduino.Internals
+    Copilot.Arduino.Library.Serial
   Other-Modules:
     Copilot.Arduino.Main
   Build-Depends:
     base (>= 4.5 && < 5),
     copilot (== 3.1.*),
     copilot-c99 (== 3.1.*),
+    copilot-language (== 3.1.*),
     filepath,
     directory,
     mtl,
     unix,
-    optparse-applicative (>= 0.14.1)
+    optparse-applicative (>= 0.14.1),
+    containers
 
 source-repository head
   type: git
-  location: git://git.joeyh.name/haskell-arduino-copilot.git
+  location: git://git.joeyh.name/arduino-copilot.git
diff --git a/src/Copilot/Arduino.hs b/src/Copilot/Arduino.hs
--- a/src/Copilot/Arduino.hs
+++ b/src/Copilot/Arduino.hs
@@ -1,9 +1,15 @@
 -- | Programming the Arduino with Copilot.
 --
 -- This module should work on any model of Arduino.
--- See Arduino.Uno and Arduino.Nano for model-specific code.
+-- See Copilot.Arduino.Uno and Copilot.Arduino.Nano for model-specific code.
+--
+-- There are also libraries like Copilot.Arduino.Library.Serial to support
+-- additional hardware.
 
 {-# LANGUAGE RebindableSyntax #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE PolyKinds #-}
+{-# LANGUAGE TypeFamilies #-}
 
 module Copilot.Arduino (
 	-- * Arduino sketch generation
@@ -11,21 +17,35 @@
 	Sketch,
 	Input,
 	Output,
+	-- | A value that varies over time.
+	--
+	-- The Copilot DSL provides many operations on streams, for example
+	-- `Language.Copilot.&&` to combine two streams of Bools.
+	-- 
+	-- For documentation on using the Copilot DSL, see
+	-- <https://copilot-language.github.io/>
 	Stream,
-	GPIO,
+	Pin,
+	PinCapabilities(..),
 	-- * Combinators
 	(=:),
 	(@:),
 	-- * Inputs
 	readfrom,
 	readfrom',
+	pullup,
+	Voltage,
+	readvoltage,
+	readvoltage',
 	-- * Outputs
 	--
 	-- | Only outputs that work on all Arduino boards are included
-	-- here. Load a module such as Arduino.Uno for model-specific
-	-- outputs.
+	-- here. Import a module such as Copilot.Arduino.Uno for
+	-- model-specific outputs.
 	led,
 	writeto,
+	DutyCycle,
+	pwm,
 	MicroSeconds,
 	delay,
 	-- * Utilities
@@ -43,27 +63,20 @@
 	--
 	-- For documentation on using the Copilot DSL, see
 	-- <https://copilot-language.github.io/>
-	module Language.Copilot,
+	module X,
 ) where
 
-import qualified Language.Copilot
-import Language.Copilot hiding (Stream)
+import Language.Copilot as X hiding (Stream)
+import Language.Copilot (Stream)
 import Copilot.Arduino.Internals
 import Copilot.Arduino.Main
 import Control.Monad.Writer
-
--- | A value that varies over time.
---
--- The Copilot DSL provides many operations on streams, for example
--- `Language.Copilot.&&` to combine two streams of Bools.
--- 
--- For documentation on using the Copilot DSL, see
--- <https://copilot-language.github.io/>
-type Stream t = Language.Copilot.Stream t
+import qualified Data.Map as M
+import qualified Data.Set as S
 
 -- | Connect a `Stream` to an `Output`.
 --
--- > led := blinking
+-- > led =: blinking
 (=:) :: Output t -> Stream t -> Sketch ()
 o =: s = tell [(go, toFramework o)]
   where
@@ -109,50 +122,110 @@
 
 -- | Use this to add a delay between each iteration of the `Sketch`.
 -- A `Sketch` with no delay will run as fast as the hardware can run it.
+--
+-- > delay =: constant 100
 delay :: Output MicroSeconds
 delay = Output
 	{ setupOutput = []
 	, outputBehavior = \c n -> trigger "delay" c [arg n]
 	, outputCond = true
+	, outputPinmode = mempty
 	}
 
--- | Reading from a GPIO pin.
+-- | Reading a Bool from a `Pin`.
 --
 -- > do
 -- > 	buttonpressed <- readfrom pin12
--- > 	led := buttonpressed
-readfrom :: GPIO -> Input Bool
-readfrom n = readfrom' n []
+-- > 	led =: buttonpressed
+readfrom :: IsDigitalIOPin t => Pin t -> Input Bool
+readfrom p = readfrom' p []
 
 -- | The list is used as simulated input when interpreting the program.
-readfrom' :: GPIO -> [Bool] -> Input Bool
-readfrom' (GPIO n) interpretvalues = mkInput $ InputSource
-	{ defineVar = ["bool " <> varname]
-	, setupInput = ["pinMode(" <> show n <> ", INPUT)"]
-	, readInput = [varname <> " = digitalRead(" <> show n <> ")"]
+readfrom' :: IsDigitalIOPin t => Pin t -> [Bool] -> Input Bool
+readfrom' (Pin p@(PinId n)) interpretvalues = mkInput $ InputSource
+	{ defineVar = [CLine $ "bool " <> varname <> ";"]
+	, setupInput = []
+	, inputPinmode = M.singleton p InputMode
+	, readInput = [CLine $ varname <> " = digitalRead(" <> show n <> ");"]
 	, inputStream = extern varname interpretvalues'
 	}
   where
-	varname = "arduino_gpio_input" <> show n
+	varname = "arduino_digital_pin_input" <> show n
 	interpretvalues'
 		| null interpretvalues = Nothing
 		| otherwise = Just interpretvalues
 
--- | Writing to a GPIO pin.
-writeto :: GPIO -> Output Bool
-writeto  (GPIO n) = Output
-	{ setupOutput = ["pinMode(" <> sn <> ", OUTPUT)"]
+-- | Normally when a `Pin` is read with `readfrom`, it is configured
+-- without the internal pullup resistor being enabled. Use this to enable
+-- the pullup register for all reads from the `Pin`.
+--
+-- Bear in mind that enabling the pullup resistor inverts the value that
+-- will be read from the pin.
+--
+-- > pullup pin3
+pullup :: Pin t -> Sketch ()
+pullup (Pin p) = tell [(return (), f)]
+  where
+	f = mempty
+		{ pinmodes = M.singleton p (S.singleton InputPullupMode)
+		}
+
+-- | Voltage read from an Arduino's ADC. Ranges from 0-1023.
+type Voltage = Int16
+
+-- | Measuring the voltage of a `Pin`.
+readvoltage :: IsAnalogInputPin t => Pin t -> Input Voltage
+readvoltage p = readvoltage' p []
+
+readvoltage' :: IsAnalogInputPin t => Pin t -> [Int16] -> Input Voltage
+readvoltage' (Pin (PinId n)) interpretvalues = mkInput $ InputSource
+	{ defineVar = [CLine $ "int " <> varname <> ";"]
+	, setupInput = []
+	, inputPinmode = mempty
+	, readInput = [CLine $ varname <> " = analogRead(" <> show n <> ");"]
+	, inputStream = extern varname interpretvalues'
+	}
+  where
+	varname = "arduino_analog_pin_input" <> show n
+	interpretvalues'
+		| null interpretvalues = Nothing
+		| otherwise = Just interpretvalues
+
+-- | Writing to a pin.
+--
+-- > writeto pin3 =: blinking
+writeto :: IsDigitalIOPin t => Pin t -> Output Bool
+writeto (Pin p@(PinId n)) = Output
+	{ setupOutput = []
+	, outputPinmode = M.singleton p OutputMode
 	, outputBehavior = 
 		\c v -> trigger "digitalWrite" c [arg (constant n), arg v]
 	, outputCond = true
 	}
-  where
-	sn = show n
 
+-- | PWM output to a pin.
+--
+-- > pwm pin3 =: constant 128
+pwm :: IsPWMPin t => Pin t -> Output DutyCycle
+pwm (Pin (PinId n)) = Output
+	{ setupOutput = []
+	, outputPinmode = mempty -- analogWrite does not need pinMode
+	, outputBehavior =
+		\c v -> trigger "analogWrite" c [arg (constant n), arg v]
+	, outputCond = true
+	}
+
+-- | Describes a PWM square wave. Between 0 (always off) and 255 (always on).
+type DutyCycle = Word8
+
 -- | The on-board LED.
 led :: Output Bool
-led = writeto (GPIO 13)
+led = writeto p
+  where
+	p :: Pin '[ 'DigitalIO ]
+	p = Pin (PinId 13)
 
+-- | An 8 bit character.
 -- | Extracts a copilot `Spec` from a `Sketch`.
 --
 -- This can be useful to intergrate with other libraries 
diff --git a/src/Copilot/Arduino/Internals.hs b/src/Copilot/Arduino/Internals.hs
--- a/src/Copilot/Arduino/Internals.hs
+++ b/src/Copilot/Arduino/Internals.hs
@@ -1,14 +1,22 @@
 -- | You should not need to import this module unless you're adding support
 -- for a new model of Arduino, or an Arduino library.
 
-{-# LANGUAGE RebindableSyntax #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE PolyKinds #-}
+{-# LANGUAGE TypeOperators #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE UndecidableInstances #-}
 
 module Copilot.Arduino.Internals where
 
 import Language.Copilot
 import Control.Monad.Writer
+import qualified Data.Map as M
+import qualified Data.Set as S
+import Data.Type.Bool
+import GHC.TypeLits
 
 -- | An Arduino sketch, implemented using Copilot.
 --
@@ -30,26 +38,29 @@
 
 -- | The framework of an Arduino sketch.
 data Framework = Framework
-	{ defines :: [CFragment]
+	{ defines :: [CLine]
 	-- ^ Things that come before the C code generated by Copilot.
-	, setups :: [CFragment]
-	-- ^ Things to run in `setup`.
-	, loops :: [CFragment]
+	, setups :: [CLine]
+	-- ^ Things to do at setup, not including configuring pins.
+	, pinmodes :: M.Map PinId (S.Set PinMode)
+	-- ^ How pins are used.
+	, loops :: [CLine]
 	-- ^ Things to run in `loop`.
 	}
 
--- | A fragment of C code.
-type CFragment = String
+-- | A line of C code.
+newtype CLine = CLine { fromCLine :: String }
 
 instance Semigroup Framework where
 	a <> b = Framework
 		{ defines = defines a <> defines b
 		, setups = setups a <> setups b
+		, pinmodes = M.unionWith S.union (pinmodes a) (pinmodes b)
 		, loops = loops a  <> loops b
 		}
 
 instance Monoid Framework where
-	mempty = Framework mempty mempty mempty
+	mempty = Framework mempty mempty mempty mempty
 
 class ToFramework t where
 	toFramework :: t -> Framework
@@ -59,8 +70,9 @@
 -- | Somewhere that a Stream can be directed to, in order to control the
 -- Arduino.
 data Output t = Output
-	{ setupOutput :: [CFragment]
-	-- ^ How to set up the output.
+	{ setupOutput :: [CLine]
+	-- ^ How to set up the output, not including pin mode.
+	, outputPinmode :: M.Map PinId PinMode
 	, outputCond :: Stream Bool
 	, outputBehavior :: Stream Bool -> Behavior t
 	}
@@ -69,6 +81,7 @@
 	toFramework o = Framework
 		{ defines = mempty
 		, setups = setupOutput o
+		, pinmodes = M.map S.singleton (outputPinmode o)
 		, loops = mempty
 		}
 
@@ -78,12 +91,13 @@
 type Input t = Sketch (Stream t)
 
 data InputSource t = InputSource
-	{ defineVar :: [CFragment]
+	{ defineVar :: [CLine]
 	-- ^ Added to the `Framework`'s `defines`, this typically
 	-- defines a C variable.
-	, setupInput :: [CFragment]
-	-- ^ How to set up the input.
-	, readInput :: [CFragment]
+	, setupInput :: [CLine]
+	-- ^ How to set up the input, not including pin mode.
+	, inputPinmode :: M.Map PinId PinMode
+	, readInput :: [CLine]
 	-- ^ How to read a value from the input, this typically
 	-- reads a value into a C variable.
 	, inputStream :: Stream t
@@ -93,6 +107,7 @@
 	toFramework i = Framework
 		{ defines = defineVar i
 		, setups = setupInput i
+		, pinmodes = M.map S.singleton (inputPinmode i)
 		, loops = readInput i
 		}
 
@@ -101,45 +116,57 @@
 	tell [(return (), toFramework i)]
 	return (inputStream i)
 
--- | A GPIO pin
+-- | A pin on the Arduino board.
 --
--- For definitions of GPIO pins like `Copilot.Arduino.Uno.pin12`, 
+-- For definitions of pins like `Copilot.Arduino.Uno.pin12`, 
 -- load a module such as Copilot.Arduino.Uno, which provides the pins of a
 -- particular board.
-newtype GPIO = GPIO Int16
+--
+-- Some pins can only be used for digital IO, while others support
+-- analog input and/or digital IO. A type-level list of PinCapabilties
+-- indicates how a Pin can be used.
+newtype Pin t = Pin PinId
+	deriving (Show, Eq, Ord)
 
+newtype PinId = PinId Int16
+	deriving (Show, Eq, Ord)
+
+data PinCapabilities
+	= DigitalIO
+	| AnalogInput
+	| PWM
+	deriving (Show, Eq, Ord)
+
+type family IsDigitalIOPin t where
+	IsDigitalIOPin t = 
+		'True ~ If (HasPinCapability 'DigitalIO t)
+			('True)
+			(TypeError ('Text "This Pin does not support digital IO"))
+
+type family IsAnalogInputPin t where
+	IsAnalogInputPin t = 
+		'True ~ If (HasPinCapability 'AnalogInput t)
+			('True)
+			(TypeError ('Text "This Pin does not support analog input"))
+
+type family IsPWMPin t where
+	IsPWMPin t = 
+		'True ~ If (HasPinCapability 'PWM t)
+			('True)
+			(TypeError ('Text "This Pin does not support PWM"))
+
+type family HasPinCapability (c :: t) (list :: [t]) :: Bool where
+	HasPinCapability c '[] = 'False
+	HasPinCapability c (x ': xs) = SameCapability c x || HasPinCapability c xs
+
+type family SameCapability (a :: PinCapabilities) (b :: PinCapabilities) :: Bool where
+	SameCapability 'DigitalIO 'DigitalIO = 'True
+	SameCapability 'AnalogInput 'AnalogInput = 'True
+	SameCapability 'PWM 'PWM = 'True
+	SameCapability _ _ = 'False
+
+data PinMode = InputMode | InputPullupMode | OutputMode
+	deriving (Show, Eq, Ord)
+
 -- FIXME should be a newtype, but how to make a stream of a newtype?
 type MicroSeconds = Int16
-
--- | Makes an arduino sketch, using a Framework, and a list of lines of C
--- code generated by Copilot.
-sketchFramework :: Framework -> [String] -> [CFragment]
-sketchFramework f ccode = concat
-	[
-		[ "/* automatically generated, do not edit */"
-		, blank
-		, "#include <stdbool.h>"
-		, "#include <stdint.h>"
-		, blank
-		]
-	, map statement (defines f)
-	, [blank]
-	, ccode
-	, [blank]
-	,
-		[ "void setup()"
-		]
-	, codeblock $ map statement (setups f)
-	, [blank]
-	,
-		[ "void loop()"
-		]
-	, codeblock $ map statement $ (loops f) <>
-		[ "step()"
-		]
-	]
-  where
-	blank = ""
-	indent l = "  " <> l
-	statement d = d <> ";"
-	codeblock l = ["{"] <> map indent l <> ["}"]
diff --git a/src/Copilot/Arduino/Library/Serial.hs b/src/Copilot/Arduino/Library/Serial.hs
new file mode 100644
--- /dev/null
+++ b/src/Copilot/Arduino/Library/Serial.hs
@@ -0,0 +1,192 @@
+-- | Serial port library for arduino-copilot.
+--
+-- This module is designed to be imported qualified as Serial
+
+{-# LANGUAGE RebindableSyntax #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE PolyKinds #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeApplications #-}
+
+module Copilot.Arduino.Library.Serial (
+	baud,
+	output,
+	char,
+	str,
+	show,
+	showFormatted,
+	input,
+	input',
+	noInput,
+	ShowableType,
+	FormatableType,
+	Base(..),
+) where
+
+import Copilot.Arduino hiding (show)
+import Copilot.Arduino.Internals
+import Control.Monad.Writer
+import Copilot.Language.Stream (Arg)
+import Data.List
+import Data.Maybe
+import Data.Proxy
+import qualified Prelude
+
+-- | Configure the baud rate of the serial port.
+--
+-- This must be included in your sketch if it uses the serial port.
+baud :: Int -> Sketch ()
+baud n = tell [(return (), f)]
+  where
+	f = mempty
+		{ setups = [CLine $ "Serial.begin(" <> Prelude.show n <> ");"]
+		}
+
+-- | Output to the serial port.
+--
+-- Note that this can only be used once in a Sketch.
+--
+-- > main = arduino $ do
+-- > 	Serial.baud 9600
+-- > 	b <- readfrom pin3
+-- > 	n <- readvoltage a1
+-- > 	Serial.output true
+-- > 		[ Serial.str "pin3:"
+-- > 		, Serial.show b
+-- > 		, Serial.str " a1:"
+-- > 		, Serial.show n
+-- > 		, Serial.char '\n'
+-- > 		]
+output
+	:: Stream Bool
+	-- ^ This Stream controls when output is sent to the serial port.
+	-> [FormatOutput]
+	-> Sketch ()
+output c l = tell [(go, f)]
+  where
+	go = trigger "arduino_serial_output" c (mapMaybe formatArg l)
+	f = mempty { defines = printer }
+
+	printer = concat
+		[ [CLine $ "void arduino_serial_output("
+			<> intercalate ", " arglist <> ") {"]
+		, map (\(fmt, n) -> CLine ("  " <> fromCLine (fmt n)))
+			(zip (map formatCLine l) argnames)
+		, [CLine "}"]
+		]
+	
+	argnames = map (\n -> "arg" <> Prelude.show n) ([1..] :: [Integer])
+	arglist = mapMaybe mkarg (zip (map formatCType l) argnames)
+	mkarg (Just ctype, argname) = Just (ctype <> " " <> argname)
+	mkarg (Nothing, _) = Nothing
+
+data FormatOutput = FormatOutput
+	{ formatArg :: Maybe Arg
+	, formatCType :: Maybe String
+	, formatCLine :: String -> CLine
+	}
+
+-- | Use this to output a Char
+char :: Char -> FormatOutput
+char c = FormatOutput Nothing Nothing
+	(\_ -> CLine $ "Serial.print('" <> esc c <> "');")
+  where
+	esc '\'' = "\\\'"
+	esc '\\' = "\\\\"
+	esc '\n' = "\\n"
+	esc c' = [c']
+
+-- | Use this to output a String
+str :: String -> FormatOutput
+str s = FormatOutput Nothing Nothing
+	(\_ -> CLine $ "Serial.print(\"" <> concatMap esc s <> "\");")
+  where
+	esc '"' = "\""
+	esc '\\' = "\\"
+	esc c = [c]
+
+-- | Use this to show the current value of a Stream.
+--
+-- Numbers will be formatted in decimal. Bool is displayed as 0 and 1.
+show :: forall t. (ShowableType t, Typed t) => Stream t -> FormatOutput
+show s = FormatOutput
+	(Just (arg s))
+	(Just (showCType (Proxy @t)))
+	(\v -> CLine $ "Serial.print(" <> v <> ");")
+
+-- | Show the current value of a Stream with control over the formatting.
+--
+-- When used with a Float, provide the number of decimal places
+-- to show.
+--
+-- > Serial.show (constant (1.234 :: Float)) 2 -- "1.23"
+--
+-- When used with any Integral type, provide the `Base` to display it in
+--
+-- > Serial.show (constant (78 :: Int8)) Serial.HEX -- "4E"
+showFormatted :: forall t f. (ShowableType t, Typed t, FormatableType t f) => Stream t -> f -> FormatOutput
+showFormatted s f = FormatOutput
+	(Just (arg s))
+	(Just (showCType t))
+	(\v -> CLine $ "Serial.print(" <> v <> ", " <> formatter t f <> ");")
+  where
+	t = Proxy @t
+
+class ShowableType t where
+	showCType :: Proxy t -> String
+
+instance ShowableType Bool where showCType _ = "bool"
+instance ShowableType Int8 where showCType _ = "int8_t"
+instance ShowableType Int16 where showCType _ = "int16_t"
+instance ShowableType Int32 where showCType _ = "int32_t"
+instance ShowableType Int64 where showCType _ = "int64_t"
+instance ShowableType Word8 where showCType _ = "uint8_t"
+instance ShowableType Word16 where showCType _ = "uint16_t"
+instance ShowableType Word32 where showCType _ = "uint32_t"
+instance ShowableType Word64 where showCType _ = "uint64_t"
+instance ShowableType Float where showCType _ = "float"
+instance ShowableType Double where showCType _ = "double"
+
+class FormatableType t f where
+	formatter :: Proxy t -> f -> String
+
+instance FormatableType Float Int where
+	formatter _ precision = Prelude.show precision
+
+instance Integral t => FormatableType t Base where
+	formatter _ b = Prelude.show b
+
+data Base = BIN | OCT | DEC | HEX
+	deriving (Show)
+
+-- | Input from the serial port.
+--
+-- Reads one byte on each iteration of the sketch. When there is no
+-- serial input available, reads `noInput`.
+--
+-- > userinput <- Serial.input
+input :: Input Int8
+input = input' []
+
+-- | The list is used to simulate serial input when interpreting the program.
+input' :: [Int8] -> Input Int8
+input' interpretvalues = mkInput $ InputSource
+	{ defineVar = [CLine $ "int " <> varname <> ";"]
+	, setupInput = []
+	, inputPinmode = mempty
+	, readInput = [CLine $ varname <> " = Serial.read();"]
+	, inputStream = extern varname interpretvalues'
+	}
+  where
+	varname = "arduino_serial_read"
+	interpretvalues'
+		| null interpretvalues = Nothing
+		| otherwise = Just interpretvalues
+
+-- | Value that is read from serial port when there is no input available.
+noInput :: Int8
+noInput = -1
+
diff --git a/src/Copilot/Arduino/Main.hs b/src/Copilot/Arduino/Main.hs
--- a/src/Copilot/Arduino/Main.hs
+++ b/src/Copilot/Arduino/Main.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE BangPatterns #-}
+
 module Copilot.Arduino.Main (arduino) where
 
 import Language.Copilot (Spec, interpret, reify)
@@ -8,6 +10,9 @@
 import System.FilePath
 import Control.Monad.Writer
 import Data.List (isInfixOf)
+import Data.Maybe
+import qualified Data.Map as M
+import qualified Data.Set as S
 import Options.Applicative
 
 -- | Typically your Arduino program's main will be implemented using this.
@@ -45,10 +50,14 @@
 
 	go o = case interpretSteps o of
 		Just n -> interpret n spec
-		Nothing -> writeIno spec (mconcat fs)
+		Nothing -> writeIno spec f
 	
 	(is, fs) = unzip (execWriter s)
 	spec = sequence_ is
+	
+	-- Strict evaluation because this may throw errors,
+	-- and we want to throw them before starting compilation.
+	!f = finalizeFramework (mconcat fs)
 
 data CmdLine = CmdLine
 	{ interpretSteps :: Maybe Integer
@@ -80,5 +89,64 @@
 	d <- getCurrentDirectory
 	let dirbase = takeFileName d
 	writeFile (addExtension dirbase "ino") $ 
-		unlines $ sketchFramework framework c'
+		unlines $ map fromCLine $
+			sketchFramework framework (map CLine c')
 	removeDirectoryRecursive mytmpdir
+
+-- Makes an arduino sketch, using a Framework, and a list of lines of C
+-- code generated by Copilot.
+sketchFramework :: Framework -> [CLine] -> [CLine]
+sketchFramework f ccode = map CLine $ concat
+	[
+		[ "/* automatically generated, do not edit */"
+		, blank
+		, "#include <stdbool.h>"
+		, "#include <stdint.h>"
+		, blank
+		]
+	, map fromCLine (defines f)
+	, [blank]
+	, map fromCLine ccode
+	, [blank]
+	,
+		[ "void setup()"
+		]
+	, codeblock $ map fromCLine (setups f)
+	, [blank]
+	,
+		[ "void loop()"
+		]
+	, codeblock $ map fromCLine (loops f) <>
+		[ "step();"
+		]
+	]
+  where
+	blank = ""
+	indent l = "  " <> l
+	codeblock l = ["{"] <> map indent l <> ["}"]
+
+finalizeFramework :: Framework -> Framework
+finalizeFramework f = 
+	let pinsetups = mapMaybe setuppinmode (M.toList $ pinmodes f)
+	in f { setups = pinsetups <> setups f }
+  where
+	setuppinmode (PinId n, s)
+		| s == S.singleton OutputMode =
+			setmode n "OUTPUT"
+		| s == S.singleton InputMode =
+			setmode n "INPUT"
+		| s == S.singleton InputPullupMode =
+			setmode n "INPUT_PULLUP"
+		| s == S.fromList [InputMode, InputPullupMode] =
+			-- Enabling pullup is documented to make all
+			-- reads from that pin be with pullup.
+			setmode n "INPUT_PULLUP"
+		| S.null s = Nothing
+		| otherwise = error $
+			"The program uses pin " ++ show n ++
+			" in multiple ways in different places (" ++
+			unwords (map show (S.toList s)) ++ "). " ++
+			"This is not currently supported by arduino-copilot."
+	
+	setmode n v = Just $ CLine $
+		"pinMode(" <> show n <> ", " ++ v ++ ");"
diff --git a/src/Copilot/Arduino/Nano.hs b/src/Copilot/Arduino/Nano.hs
--- a/src/Copilot/Arduino/Nano.hs
+++ b/src/Copilot/Arduino/Nano.hs
@@ -1,9 +1,11 @@
 -- | Programming the Arduino Nano with Copilot.
 
+{-# LANGUAGE DataKinds #-}
+
 module Copilot.Arduino.Nano (
 	-- * General Arduino programming infrastructure
 	module Copilot.Arduino
-	-- * GPIO
+	-- * Pins
 	, pin2
 	, pin3
 	, pin4
@@ -16,16 +18,14 @@
 	, pin11
 	, pin12
 	, pin13
-	-- * Analog input
-	-- TODO!
-	--, a0
-	--, a1
-	--, a2
-	--, a3
-	--, a4
-	--, a5
-	--, a6
-	--, a7
+	, a0
+	, a1
+	, a2
+	, a3
+	, a4
+	, a5
+	, a6
+	, a7
 	-- * UART
 	-- Not currently supported!
 	--, uart
@@ -34,18 +34,31 @@
 import Copilot.Arduino
 import Copilot.Arduino.Internals
 
-pin2, pin3, pin4, pin5, pin6, pin7, pin8, pin9, pin10, pin11, pin12, pin13 :: GPIO
-pin2 = GPIO 2
-pin3 = GPIO 3
-pin4 = GPIO 4
-pin5 = GPIO 5
-pin6 = GPIO 6
-pin7 = GPIO 7
-pin8 = GPIO 8
-pin9 = GPIO 9
-pin10 = GPIO 10
-pin11 = GPIO 11
-pin12 = GPIO 12
+pin2, pin4, pin7, pin8, pin12, pin13 :: Pin '[ 'DigitalIO ]
+pin3, pin5, pin6, pin9, pin10, pin11 :: Pin '[ 'DigitalIO, 'PWM ]
+pin2 = Pin (PinId 2)
+pin3 = Pin (PinId 3)
+pin4 = Pin (PinId 4)
+pin5 = Pin (PinId 5)
+pin6 = Pin (PinId 6)
+pin7 = Pin (PinId 7)
+pin8 = Pin (PinId 8)
+pin9 = Pin (PinId 9)
+pin10 = Pin (PinId 10)
+pin11 = Pin (PinId 11)
+pin12 = Pin (PinId 12)
 -- | This pin is connected to the `led`
-pin13 = GPIO 13
+pin13 = Pin (PinId 13)
 
+a0, a1, a2, a3, a4, a5 :: Pin '[ 'AnalogInput, 'DigitalIO ]
+a0 = Pin (PinId 14)
+a1 = Pin (PinId 15)
+a2 = Pin (PinId 16)
+a3 = Pin (PinId 17)
+a4 = Pin (PinId 18)
+a5 = Pin (PinId 19)
+
+-- | Limited to analog input.
+a6, a7 :: Pin '[ 'AnalogInput ]
+a6 = Pin (PinId 20)
+a7 = Pin (PinId 21)
diff --git a/src/Copilot/Arduino/Uno.hs b/src/Copilot/Arduino/Uno.hs
--- a/src/Copilot/Arduino/Uno.hs
+++ b/src/Copilot/Arduino/Uno.hs
@@ -1,9 +1,11 @@
 -- | Programming the Arduino Uno with Copilot.
 
+{-# LANGUAGE DataKinds #-}
+
 module Copilot.Arduino.Uno (
 	-- * General Arduino programming infrastructure
 	module Copilot.Arduino
-	-- * GPIO
+	-- * Pins
 	, pin2
 	, pin3
 	, pin4
@@ -16,14 +18,12 @@
 	, pin11
 	, pin12
 	, pin13
-	-- * Analog input
-	-- TODO!
-	--, a0
-	--, a1
-	--, a2
-	--, a3
-	--, a4
-	--, a5
+	, a0
+	, a1
+	, a2
+	, a3
+	, a4
+	, a5
 	-- * UART
 	-- Not currently supported!
 	--, uart
@@ -32,18 +32,26 @@
 import Copilot.Arduino
 import Copilot.Arduino.Internals
 
-pin2, pin3, pin4, pin5, pin6, pin7, pin8, pin9, pin10, pin11, pin12, pin13 :: GPIO
-pin2 = GPIO 2
-pin3 = GPIO 3
-pin4 = GPIO 4
-pin5 = GPIO 5
-pin6 = GPIO 6
-pin7 = GPIO 7
-pin8 = GPIO 8
-pin9 = GPIO 9
-pin10 = GPIO 10
-pin11 = GPIO 11
-pin12 = GPIO 12
+pin2, pin4, pin7, pin8, pin12, pin13 :: Pin '[ 'DigitalIO ]
+pin3, pin5, pin6, pin9, pin10, pin11 :: Pin '[ 'DigitalIO, 'PWM ]
+pin2 = Pin (PinId 2)
+pin3 = Pin (PinId 3)
+pin4 = Pin (PinId 4)
+pin5 = Pin (PinId 5)
+pin6 = Pin (PinId 6)
+pin7 = Pin (PinId 7)
+pin8 = Pin (PinId 8)
+pin9 = Pin (PinId 9)
+pin10 = Pin (PinId 10)
+pin11 = Pin (PinId 11)
+pin12 = Pin (PinId 12)
 -- | This pin drives the `led`
-pin13 = GPIO 13
+pin13 = Pin (PinId 13)
 
+a0, a1, a2, a3, a4, a5 :: Pin '[ 'AnalogInput, 'DigitalIO ]
+a0 = Pin (PinId 14)
+a1 = Pin (PinId 15)
+a2 = Pin (PinId 16)
+a3 = Pin (PinId 17)
+a4 = Pin (PinId 18)
+a5 = Pin (PinId 19)
