zephyr-copilot-1.0.0: src/Copilot/Zephyr/Internals.hs
-- | You should not need to import this module unless you're adding support
-- for a specific board supported by Zephyr, or a Zephyr library.
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE UndecidableInstances #-}
module Copilot.Zephyr.Internals (
module Copilot.Zephyr.Internals,
module X
) where
import Sketch.FRP.Copilot as X
import Sketch.FRP.Copilot.Types as X
import Sketch.FRP.Copilot.Internals as X
import Language.Copilot
import Control.Monad.Writer
import qualified Data.Map as M
import qualified Data.Set as S
import Data.Char (toLower, toUpper)
-- | A sketch, implemented using Copilot.
--
-- It's best to think of the `Sketch` as a description of the state of the
-- board at any point in time.
--
-- Under the hood, the `Sketch` is run in a loop. On each iteration, it first
-- reads inputs and then updates outputs as needed.
--
-- While it is a monad, a Sketch's outputs are not updated in any
-- particular order, because Copilot does not guarantee any order.
type Sketch = GenSketch Zephyr
-- | The framework of a sketch.
type Framework = GenFramework Zephyr
-- | A pin on the board.
--
-- For definitions of specific pins, load a module which provides the pins
-- of a particular board.
--
-- A type-level list indicates how a Pin can be used, so the haskell
-- compiler will detect impossible uses of pins.
newtype Pin t = Pin Zephyr
deriving (Show, Eq, Ord)
-- | Indicates that you're programming a board with Zephyr.
-- The similar library arduino-copilot allows programming
-- Arduinos in a very similar style to this one.
data Zephyr = Zephyr GPIOAlias GPIOAddress
deriving (Show, Eq, Ord)
instance Context Zephyr
newtype GPIOAlias = GPIOAlias String
deriving (Show, Eq, Ord)
data GPIOAddress
= GPIOAddress String
-- ^ Eg "porta 17"
| GPIOAddressBuiltIn
-- ^ Use when Zephyr defines the GPIO address for a GPIOAlias.
deriving (Show, Eq, Ord)
instance IsDigitalIOPin t => Output Zephyr (Pin t) (Event () (Stream Bool)) where
(Pin p@(Zephyr (GPIOAlias n) _)) =: (Event b c) = do
(f, triggername) <- defineTriggerAlias pinsetfunc basef
tell [(go triggername, const f)]
where
go triggername tl =
let c' = addTriggerLimit tl c
in trigger triggername c' [arg b]
basef = (emptyFramework @Zephyr)
{ pinmodes = M.singleton p (S.singleton OutputMode)
, defines = (\v -> [CChunk v])
[ CLine $ "static inline int "
<> pinsetfunc
<> "(int value) {"
, CLine $ " return gpio_pin_set"
<> "(" <> pinDevVar n
<> ", " <> pinDevDef n
<> ", value);"
, CLine "}"
]
}
pinsetfunc = "gpio_pin_set_" <> map toLower n
pinDevVar :: String -> String
pinDevVar n = "pin_dev_" <> map toLower n
pinDevDef :: String -> String
pinDevDef n = "PIN_DEV_" <> map toUpper n
pinDevNode :: String -> String
pinDevNode n = pinDevDef n <> "_NODE"
-- FIXME for zephyr
instance IsPWMPin t => Output Zephyr (Pin t) (Event 'PWM (Stream Word8)) where
(Pin (Zephyr (GPIOAlias n) _)) =: (Event v c) = do
(f, triggername) <- defineTriggerAlias' ("pin_" <> n) "analogWrite" mempty
tell [(go triggername, const f)]
where
go triggername tl =
let c' = addTriggerLimit tl c
in trigger triggername c' [arg v]
-- analogWrite does not need any pinmodes set up
instance IsDigitalIOPin t => Input Zephyr (Pin t) Bool where
input' (Pin p@(Zephyr (GPIOAlias n) _)) interpretvalues = mkInput $ InputSource
{ defineVar = mkCChunk
[ CLine $ "bool " <> varname <> ";"
, CLine $ "static const struct gpio_dt_spec " <> specname
<> " = GPIO_DT_SPEC_GET_OR(" <> nodename <> ", gpios, {0});"
]
, setupInput = mempty
, inputPinmode = M.singleton p InputMode
, readInput = mkCChunk
[CLine $ varname <> " = gpio_pin_get_dt(&" <> specname <> ");"]
, inputStream = extern varname interpretvalues'
}
where
varname = "zephyr_digital_pin_input_" <> n
specname = "zephyr_digital_pin_dt_spec_" <> n
nodename = pinDevNode n
interpretvalues'
| null interpretvalues = Nothing
| otherwise = Just interpretvalues
-- | Value read from an ADC. Ranges from 0-1023.
type ADC = Int16
-- FIXME for zephyr
instance IsAnalogInputPin t => Input Zephyr (Pin t) ADC where
input' (Pin (Zephyr (GPIOAlias n) _)) interpretvalues = mkInput $ InputSource
{ defineVar = mkCChunk [CLine $ "int " <> varname <> ";"]
, setupInput = mempty
, inputPinmode = mempty
, readInput = mkCChunk
[CLine $ varname <> " = analogRead(" <> show n <> ");"]
, inputStream = extern varname interpretvalues'
}
where
varname = "zephyr_analog_pin_input_" <> show n
interpretvalues'
| null interpretvalues = Nothing
| otherwise = Just interpretvalues
instance Output Zephyr Delay MilliSeconds where
Delay =: (MilliSeconds n) = do
(f, triggername) <- defineTriggerAlias "k_msleep" mempty
tell [(go triggername, \_ -> f)]
where
go triggername tl =
let c = getTriggerLimit tl
in trigger triggername c [arg n]
instance Output Zephyr Delay MicroSeconds where
Delay =: (MicroSeconds n) = do
(f, triggername) <- defineTriggerAlias "k_usleep" mempty
tell [(go triggername, \_ -> f)]
where
go triggername tl =
let c = getTriggerLimit tl
in trigger triggername c [arg n]