arduino-copilot 1.0.1 → 1.1.1
raw patch · 9 files changed
+545/−137 lines, 9 filesdep +containersdep +copilot-languagePVP ok
version bump matches the API change (PVP)
Dependencies added: containers, copilot-language
API changes (from Hackage documentation)
- Copilot.Arduino: data GPIO
- Copilot.Arduino: type Stream t = Stream t
- Copilot.Arduino.Internals: GPIO :: Int16 -> GPIO
- Copilot.Arduino.Internals: newtype GPIO
- Copilot.Arduino.Internals: sketchFramework :: Framework -> [String] -> [CFragment]
- Copilot.Arduino.Internals: type CFragment = String
+ Copilot.Arduino: AnalogInput :: PinCapabilities
+ Copilot.Arduino: DigitalIO :: PinCapabilities
+ Copilot.Arduino: PWM :: PinCapabilities
+ Copilot.Arduino: data Pin t
+ Copilot.Arduino: data PinCapabilities
+ Copilot.Arduino: data Stream a
+ Copilot.Arduino: infixr 4 ||
+ Copilot.Arduino: pullup :: Pin t -> Sketch ()
+ Copilot.Arduino: pwm :: IsPWMPin t => Pin t -> Output DutyCycle
+ Copilot.Arduino: readvoltage :: IsAnalogInputPin t => Pin t -> Input Voltage
+ Copilot.Arduino: readvoltage' :: IsAnalogInputPin t => Pin t -> [Int16] -> Input Voltage
+ Copilot.Arduino: type DutyCycle = Word8
+ Copilot.Arduino: type Voltage = Int16
+ Copilot.Arduino.Internals: AnalogInput :: PinCapabilities
+ Copilot.Arduino.Internals: CLine :: String -> CLine
+ Copilot.Arduino.Internals: DigitalIO :: PinCapabilities
+ Copilot.Arduino.Internals: InputMode :: PinMode
+ Copilot.Arduino.Internals: InputPullupMode :: PinMode
+ Copilot.Arduino.Internals: OutputMode :: PinMode
+ Copilot.Arduino.Internals: PWM :: PinCapabilities
+ Copilot.Arduino.Internals: Pin :: PinId -> Pin t
+ Copilot.Arduino.Internals: PinId :: Int16 -> PinId
+ Copilot.Arduino.Internals: [fromCLine] :: CLine -> String
+ Copilot.Arduino.Internals: [inputPinmode] :: InputSource t -> Map PinId PinMode
+ Copilot.Arduino.Internals: [outputPinmode] :: Output t -> Map PinId PinMode
+ Copilot.Arduino.Internals: [pinmodes] :: Framework -> Map PinId (Set PinMode)
+ Copilot.Arduino.Internals: data PinCapabilities
+ Copilot.Arduino.Internals: data PinMode
+ Copilot.Arduino.Internals: instance GHC.Classes.Eq Copilot.Arduino.Internals.PinCapabilities
+ Copilot.Arduino.Internals: instance GHC.Classes.Eq Copilot.Arduino.Internals.PinId
+ Copilot.Arduino.Internals: instance GHC.Classes.Eq Copilot.Arduino.Internals.PinMode
+ Copilot.Arduino.Internals: instance GHC.Classes.Ord Copilot.Arduino.Internals.PinCapabilities
+ Copilot.Arduino.Internals: instance GHC.Classes.Ord Copilot.Arduino.Internals.PinId
+ Copilot.Arduino.Internals: instance GHC.Classes.Ord Copilot.Arduino.Internals.PinMode
+ Copilot.Arduino.Internals: instance GHC.Show.Show Copilot.Arduino.Internals.PinCapabilities
+ Copilot.Arduino.Internals: instance GHC.Show.Show Copilot.Arduino.Internals.PinId
+ Copilot.Arduino.Internals: instance GHC.Show.Show Copilot.Arduino.Internals.PinMode
+ Copilot.Arduino.Internals: instance forall k (t :: k). GHC.Classes.Eq (Copilot.Arduino.Internals.Pin t)
+ Copilot.Arduino.Internals: instance forall k (t :: k). GHC.Classes.Ord (Copilot.Arduino.Internals.Pin t)
+ Copilot.Arduino.Internals: instance forall k (t :: k). GHC.Show.Show (Copilot.Arduino.Internals.Pin t)
+ Copilot.Arduino.Internals: newtype CLine
+ Copilot.Arduino.Internals: newtype Pin t
+ Copilot.Arduino.Internals: newtype PinId
+ Copilot.Arduino.Internals: type family SameCapability (a :: PinCapabilities) (b :: PinCapabilities) :: Bool
+ Copilot.Arduino.Library.Serial: BIN :: Base
+ Copilot.Arduino.Library.Serial: DEC :: Base
+ Copilot.Arduino.Library.Serial: HEX :: Base
+ Copilot.Arduino.Library.Serial: OCT :: Base
+ Copilot.Arduino.Library.Serial: baud :: Int -> Sketch ()
+ Copilot.Arduino.Library.Serial: char :: Char -> FormatOutput
+ Copilot.Arduino.Library.Serial: class FormatableType t f
+ Copilot.Arduino.Library.Serial: class ShowableType t
+ Copilot.Arduino.Library.Serial: data Base
+ Copilot.Arduino.Library.Serial: input :: Input Int8
+ Copilot.Arduino.Library.Serial: input' :: [Int8] -> Input Int8
+ Copilot.Arduino.Library.Serial: instance Copilot.Arduino.Library.Serial.FormatableType GHC.Types.Float GHC.Types.Int
+ Copilot.Arduino.Library.Serial: instance Copilot.Arduino.Library.Serial.ShowableType GHC.Int.Int16
+ Copilot.Arduino.Library.Serial: instance Copilot.Arduino.Library.Serial.ShowableType GHC.Int.Int32
+ Copilot.Arduino.Library.Serial: instance Copilot.Arduino.Library.Serial.ShowableType GHC.Int.Int64
+ Copilot.Arduino.Library.Serial: instance Copilot.Arduino.Library.Serial.ShowableType GHC.Int.Int8
+ Copilot.Arduino.Library.Serial: instance Copilot.Arduino.Library.Serial.ShowableType GHC.Types.Bool
+ Copilot.Arduino.Library.Serial: instance Copilot.Arduino.Library.Serial.ShowableType GHC.Types.Double
+ Copilot.Arduino.Library.Serial: instance Copilot.Arduino.Library.Serial.ShowableType GHC.Types.Float
+ Copilot.Arduino.Library.Serial: instance Copilot.Arduino.Library.Serial.ShowableType GHC.Word.Word16
+ Copilot.Arduino.Library.Serial: instance Copilot.Arduino.Library.Serial.ShowableType GHC.Word.Word32
+ Copilot.Arduino.Library.Serial: instance Copilot.Arduino.Library.Serial.ShowableType GHC.Word.Word64
+ Copilot.Arduino.Library.Serial: instance Copilot.Arduino.Library.Serial.ShowableType GHC.Word.Word8
+ Copilot.Arduino.Library.Serial: instance GHC.Real.Integral t => Copilot.Arduino.Library.Serial.FormatableType t Copilot.Arduino.Library.Serial.Base
+ Copilot.Arduino.Library.Serial: instance GHC.Show.Show Copilot.Arduino.Library.Serial.Base
+ Copilot.Arduino.Library.Serial: noInput :: Int8
+ Copilot.Arduino.Library.Serial: output :: Stream Bool -> [FormatOutput] -> Sketch ()
+ Copilot.Arduino.Library.Serial: show :: forall t. (ShowableType t, Typed t) => Stream t -> FormatOutput
+ Copilot.Arduino.Library.Serial: showFormatted :: forall t f. (ShowableType t, Typed t, FormatableType t f) => Stream t -> f -> FormatOutput
+ Copilot.Arduino.Library.Serial: str :: String -> FormatOutput
+ Copilot.Arduino.Nano: a0 :: Pin '[ 'AnalogInput, 'DigitalIO]
+ Copilot.Arduino.Nano: a1 :: Pin '[ 'AnalogInput, 'DigitalIO]
+ Copilot.Arduino.Nano: a2 :: Pin '[ 'AnalogInput, 'DigitalIO]
+ Copilot.Arduino.Nano: a3 :: Pin '[ 'AnalogInput, 'DigitalIO]
+ Copilot.Arduino.Nano: a4 :: Pin '[ 'AnalogInput, 'DigitalIO]
+ Copilot.Arduino.Nano: a5 :: Pin '[ 'AnalogInput, 'DigitalIO]
+ Copilot.Arduino.Nano: a6 :: Pin '[ 'AnalogInput]
+ Copilot.Arduino.Nano: a7 :: Pin '[ 'AnalogInput]
+ Copilot.Arduino.Uno: a0 :: Pin '[ 'AnalogInput, 'DigitalIO]
+ Copilot.Arduino.Uno: a1 :: Pin '[ 'AnalogInput, 'DigitalIO]
+ Copilot.Arduino.Uno: a2 :: Pin '[ 'AnalogInput, 'DigitalIO]
+ Copilot.Arduino.Uno: a3 :: Pin '[ 'AnalogInput, 'DigitalIO]
+ Copilot.Arduino.Uno: a4 :: Pin '[ 'AnalogInput, 'DigitalIO]
+ Copilot.Arduino.Uno: a5 :: Pin '[ 'AnalogInput, 'DigitalIO]
- Copilot.Arduino: infixr 1 =<<
+ Copilot.Arduino: infixr 1 ++
- Copilot.Arduino: readfrom :: GPIO -> Input Bool
+ Copilot.Arduino: readfrom :: IsDigitalIOPin t => Pin t -> Input Bool
- Copilot.Arduino: readfrom' :: GPIO -> [Bool] -> Input Bool
+ Copilot.Arduino: readfrom' :: IsDigitalIOPin t => Pin t -> [Bool] -> Input Bool
- Copilot.Arduino: writeto :: GPIO -> Output Bool
+ Copilot.Arduino: writeto :: IsDigitalIOPin t => Pin t -> Output Bool
- Copilot.Arduino.Internals: Framework :: [CFragment] -> [CFragment] -> [CFragment] -> Framework
+ Copilot.Arduino.Internals: Framework :: [CLine] -> [CLine] -> Map PinId (Set PinMode) -> [CLine] -> Framework
- Copilot.Arduino.Internals: InputSource :: [CFragment] -> [CFragment] -> [CFragment] -> Stream t -> InputSource t
+ Copilot.Arduino.Internals: InputSource :: [CLine] -> [CLine] -> Map PinId PinMode -> [CLine] -> Stream t -> InputSource t
- Copilot.Arduino.Internals: Output :: [CFragment] -> Stream Bool -> (Stream Bool -> Behavior t) -> Output t
+ Copilot.Arduino.Internals: Output :: [CLine] -> Map PinId PinMode -> Stream Bool -> (Stream Bool -> Behavior t) -> Output t
- Copilot.Arduino.Internals: [defineVar] :: InputSource t -> [CFragment]
+ Copilot.Arduino.Internals: [defineVar] :: InputSource t -> [CLine]
- Copilot.Arduino.Internals: [defines] :: Framework -> [CFragment]
+ Copilot.Arduino.Internals: [defines] :: Framework -> [CLine]
- Copilot.Arduino.Internals: [loops] :: Framework -> [CFragment]
+ Copilot.Arduino.Internals: [loops] :: Framework -> [CLine]
- Copilot.Arduino.Internals: [readInput] :: InputSource t -> [CFragment]
+ Copilot.Arduino.Internals: [readInput] :: InputSource t -> [CLine]
- Copilot.Arduino.Internals: [setupInput] :: InputSource t -> [CFragment]
+ Copilot.Arduino.Internals: [setupInput] :: InputSource t -> [CLine]
- Copilot.Arduino.Internals: [setupOutput] :: Output t -> [CFragment]
+ Copilot.Arduino.Internals: [setupOutput] :: Output t -> [CLine]
- Copilot.Arduino.Internals: [setups] :: Framework -> [CFragment]
+ Copilot.Arduino.Internals: [setups] :: Framework -> [CLine]
- Copilot.Arduino.Nano: pin10 :: GPIO
+ Copilot.Arduino.Nano: pin10 :: Pin '[ 'DigitalIO, 'PWM]
- Copilot.Arduino.Nano: pin11 :: GPIO
+ Copilot.Arduino.Nano: pin11 :: Pin '[ 'DigitalIO, 'PWM]
- Copilot.Arduino.Nano: pin12 :: GPIO
+ Copilot.Arduino.Nano: pin12 :: Pin '[ 'DigitalIO]
- Copilot.Arduino.Nano: pin13 :: GPIO
+ Copilot.Arduino.Nano: pin13 :: Pin '[ 'DigitalIO]
- Copilot.Arduino.Nano: pin2 :: GPIO
+ Copilot.Arduino.Nano: pin2 :: Pin '[ 'DigitalIO]
- Copilot.Arduino.Nano: pin3 :: GPIO
+ Copilot.Arduino.Nano: pin3 :: Pin '[ 'DigitalIO, 'PWM]
- Copilot.Arduino.Nano: pin4 :: GPIO
+ Copilot.Arduino.Nano: pin4 :: Pin '[ 'DigitalIO]
- Copilot.Arduino.Nano: pin5 :: GPIO
+ Copilot.Arduino.Nano: pin5 :: Pin '[ 'DigitalIO, 'PWM]
- Copilot.Arduino.Nano: pin6 :: GPIO
+ Copilot.Arduino.Nano: pin6 :: Pin '[ 'DigitalIO, 'PWM]
- Copilot.Arduino.Nano: pin7 :: GPIO
+ Copilot.Arduino.Nano: pin7 :: Pin '[ 'DigitalIO]
- Copilot.Arduino.Nano: pin8 :: GPIO
+ Copilot.Arduino.Nano: pin8 :: Pin '[ 'DigitalIO]
- Copilot.Arduino.Nano: pin9 :: GPIO
+ Copilot.Arduino.Nano: pin9 :: Pin '[ 'DigitalIO, 'PWM]
- Copilot.Arduino.Uno: pin10 :: GPIO
+ Copilot.Arduino.Uno: pin10 :: Pin '[ 'DigitalIO, 'PWM]
- Copilot.Arduino.Uno: pin11 :: GPIO
+ Copilot.Arduino.Uno: pin11 :: Pin '[ 'DigitalIO, 'PWM]
- Copilot.Arduino.Uno: pin12 :: GPIO
+ Copilot.Arduino.Uno: pin12 :: Pin '[ 'DigitalIO]
- Copilot.Arduino.Uno: pin13 :: GPIO
+ Copilot.Arduino.Uno: pin13 :: Pin '[ 'DigitalIO]
- Copilot.Arduino.Uno: pin2 :: GPIO
+ Copilot.Arduino.Uno: pin2 :: Pin '[ 'DigitalIO]
- Copilot.Arduino.Uno: pin3 :: GPIO
+ Copilot.Arduino.Uno: pin3 :: Pin '[ 'DigitalIO, 'PWM]
- Copilot.Arduino.Uno: pin4 :: GPIO
+ Copilot.Arduino.Uno: pin4 :: Pin '[ 'DigitalIO]
- Copilot.Arduino.Uno: pin5 :: GPIO
+ Copilot.Arduino.Uno: pin5 :: Pin '[ 'DigitalIO, 'PWM]
- Copilot.Arduino.Uno: pin6 :: GPIO
+ Copilot.Arduino.Uno: pin6 :: Pin '[ 'DigitalIO, 'PWM]
- Copilot.Arduino.Uno: pin7 :: GPIO
+ Copilot.Arduino.Uno: pin7 :: Pin '[ 'DigitalIO]
- Copilot.Arduino.Uno: pin8 :: GPIO
+ Copilot.Arduino.Uno: pin8 :: Pin '[ 'DigitalIO]
- Copilot.Arduino.Uno: pin9 :: GPIO
+ Copilot.Arduino.Uno: pin9 :: Pin '[ 'DigitalIO, 'PWM]
Files
- CHANGELOG +15/−0
- TODO +11/−2
- arduino-copilot.cabal +6/−3
- src/Copilot/Arduino.hs +107/−34
- src/Copilot/Arduino/Internals.hs +77/−50
- src/Copilot/Arduino/Library/Serial.hs +192/−0
- src/Copilot/Arduino/Main.hs +70/−2
- src/Copilot/Arduino/Nano.hs +37/−24
- src/Copilot/Arduino/Uno.hs +30/−22
CHANGELOG view
@@ -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)
TODO view
@@ -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
arduino-copilot.cabal view
@@ -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
src/Copilot/Arduino.hs view
@@ -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
src/Copilot/Arduino/Internals.hs view
@@ -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 <> ["}"]
+ src/Copilot/Arduino/Library/Serial.hs view
@@ -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+
src/Copilot/Arduino/Main.hs view
@@ -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 ++ ");"
src/Copilot/Arduino/Nano.hs view
@@ -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)
src/Copilot/Arduino/Uno.hs view
@@ -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)