-- | Programming the Arduino with Copilot, in functional reactive style. -- -- This module should work on any model of Arduino. -- 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 MultiParamTypeClasses #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE TypeApplications #-} module Copilot.Arduino ( -- * Arduino sketch generation arduino, Sketch, Pin, -- * Functional reactive programming Behavior, TypedBehavior, Event, (@:), -- * Inputs Input, input, input', pullup, millis, micros, -- * Outputs -- -- | Only a few common outputs are included in this module. -- Import a module such as Copilot.Arduino.Uno for `Pin` -- definitions etc. Output, led, (=:), pwm, delay, -- * Other types ADC, MilliSeconds(..), MicroSeconds(..), ClockMillis, ClockMicros, -- * Utilities blinking, firstIteration, frequency, sketchSpec, -- * Copilot DSL -- -- | The Copilot.Language module is re-exported here, including -- a version of the Prelude modified for it. You should enable -- the RebindableSyntax language extension in your program -- to use the Copilot DSL. -- -- > {-# LANGUAGE RebindableSyntax #-} -- -- For documentation on using the Copilot DSL, see -- <https://copilot-language.github.io/> Stream, module X, ) where import Language.Copilot as X hiding (Stream) import Language.Copilot (Stream) import Copilot.Arduino.Internals import Copilot.Arduino.Main import Control.Monad.Writer import Data.Proxy import Data.Maybe import qualified Data.Map as M import qualified Data.Set as S -- | Use this to make a LED blink on and off. -- -- On each iteration of the `Sketch`, this changes to the opposite of its -- previous value. -- -- This is implemented using Copilot's `clk`, so to get other blinking -- behaviors, just pick different numbers, or use Copilot `Stream` -- combinators. -- -- > blinking = clk (period 2) (phase 1) blinking :: Behavior Bool blinking = clk (period (2 :: Integer)) (phase (1 :: Integer)) -- | True on the first iteration of the `Sketch`, and False thereafter. firstIteration :: Behavior Bool firstIteration = [True]++false -- | Use this to make an event occur 1 time out of n. -- -- This is implemented using Copilot's `clk`: -- -- > frequency = clk (period n) (phase 1) frequency :: Integer -> Behavior Bool frequency n = clk (period n) (phase 1) -- | A stream of milliseconds. data MilliSeconds = MilliSeconds (Stream Word32) -- | A stream of microseconds. data MicroSeconds = MicroSeconds (Stream Word32) -- | 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 := MilliSeconds (constant 100) delay :: Delay delay = Delay data Delay = Delay instance Output Delay MilliSeconds where Delay =: (MilliSeconds n) = tell [(trigger "delay" true [arg n], mempty)] instance Output Delay MicroSeconds where Delay =: (MicroSeconds n) = tell [(trigger "delayMicroseconds" true [arg n], mempty)] -- | Number of MillisSeconds since the Arduino booted. -- -- > n <- input millis -- -- The value wraps back to zero after approximately 50 days. millis :: ClockMillis millis = ClockMillis -- | Number of MicroSeconds since the Arduino booted. -- -- > n <- input micros -- -- The value wraps back to zero after approximately 70 minutes. micros :: ClockMicros micros = ClockMicros data ClockMillis = ClockMillis data ClockMicros = ClockMicros instance Input ClockMillis Word32 where input' ClockMillis = inputClock "millis" instance Input ClockMicros Word32 where input' ClockMicros = inputClock "micros" inputClock :: [Char] -> [Word32] -> Sketch (Behavior Word32) inputClock src interpretvalues = mkInput $ InputSource { setupInput = [] , defineVar = [CLine $ showCType (Proxy @Word32) <> " " <> varname <>";"] , inputPinmode = mempty , readInput = [CLine $ varname <> " = " <> src <> "();"] , inputStream = extern varname interpretvalues' } where varname = "clock_" <> src interpretvalues' | null interpretvalues = Nothing | otherwise = Just interpretvalues -- | Use this to read a value from a component of the Arduino. -- -- For example, to read a digital value from pin12 and turn on the -- led when the pin is high: -- -- > buttonpressed <- input pin12 -- > led =: buttonpressed -- -- Some pins support multiple types of reads, for example pin a0 -- supports a digital read (`Bool`), and an analog to digital converter -- read (`ADC`). In such cases you may need to specify the type of -- data to read: -- -- > v <- input a0 :: Sketch (Behavior ADC) input :: Input o t => o -> Sketch (Behavior t) input o = input' o [] -- | Normally when a digital value is read from a `Pin`, 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 pin12 pullup :: Pin t -> Sketch () pullup (Pin p) = tell [(return (), f)] where f = mempty { pinmodes = M.singleton p (S.singleton InputPullupMode) } -- | Use this to do PWM output to a pin. -- -- > pin3 =: pwm (constant 128) -- -- Each Word8 of the Behavior describes a PWM square wave. -- 0 is always off and 255 is always on. pwm :: Behavior Word8 -> TypedBehavior 'PWM Word8 pwm = TypedBehavior -- | The on-board LED. led :: Pin '[ 'DigitalIO ] led = Pin (PinId 13) -- | Extracts a copilot `Spec` from a `Sketch`. -- -- This can be useful to intergrate with other libraries -- such as copilot-theorem. sketchSpec :: Sketch a -> Spec sketchSpec = fromMaybe (return ()) . fst . evalSketch