arduino-copilot 1.3.0 → 1.4.0
raw patch · 23 files changed
+1037/−62 lines, 23 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Copilot.Arduino: type Voltage = Int16
- Copilot.Arduino.Internals: instance Copilot.Arduino.Internals.IsAnalogInputPin t => Copilot.Arduino.Internals.Input (Copilot.Arduino.Internals.Pin t) Copilot.Arduino.Internals.Voltage
- Copilot.Arduino.Internals: type Voltage = Int16
- Copilot.Arduino.Library.Serial: class ShowableType t
- Copilot.Arduino.Library.Serial.Device: class ShowableType t
- Copilot.Arduino.Library.Serial.Device: instance Copilot.Arduino.Library.Serial.Device.ShowableType GHC.Int.Int16
- Copilot.Arduino.Library.Serial.Device: instance Copilot.Arduino.Library.Serial.Device.ShowableType GHC.Int.Int32
- Copilot.Arduino.Library.Serial.Device: instance Copilot.Arduino.Library.Serial.Device.ShowableType GHC.Int.Int64
- Copilot.Arduino.Library.Serial.Device: instance Copilot.Arduino.Library.Serial.Device.ShowableType GHC.Int.Int8
- Copilot.Arduino.Library.Serial.Device: instance Copilot.Arduino.Library.Serial.Device.ShowableType GHC.Types.Bool
- Copilot.Arduino.Library.Serial.Device: instance Copilot.Arduino.Library.Serial.Device.ShowableType GHC.Types.Double
- Copilot.Arduino.Library.Serial.Device: instance Copilot.Arduino.Library.Serial.Device.ShowableType GHC.Types.Float
- Copilot.Arduino.Library.Serial.Device: instance Copilot.Arduino.Library.Serial.Device.ShowableType GHC.Word.Word16
- Copilot.Arduino.Library.Serial.Device: instance Copilot.Arduino.Library.Serial.Device.ShowableType GHC.Word.Word32
- Copilot.Arduino.Library.Serial.Device: instance Copilot.Arduino.Library.Serial.Device.ShowableType GHC.Word.Word64
- Copilot.Arduino.Library.Serial.Device: instance Copilot.Arduino.Library.Serial.Device.ShowableType GHC.Word.Word8
- Copilot.Arduino.Library.Serial.Device: showCType :: ShowableType t => Proxy t -> String
- Copilot.Arduino.Library.Serial.XBee: class ShowableType t
+ Copilot.Arduino: data ClockMicros
+ Copilot.Arduino: data ClockMillis
+ Copilot.Arduino: frequency :: Integer -> Behavior Bool
+ Copilot.Arduino: instance Copilot.Arduino.Internals.Input Copilot.Arduino.ClockMicros GHC.Word.Word32
+ Copilot.Arduino: instance Copilot.Arduino.Internals.Input Copilot.Arduino.ClockMillis GHC.Word.Word32
+ Copilot.Arduino: micros :: ClockMicros
+ Copilot.Arduino: millis :: ClockMillis
+ Copilot.Arduino: type ADC = Int16
+ Copilot.Arduino.Internals: UniqueIds :: [Integer] -> UniqueIds
+ Copilot.Arduino.Internals: [earlySetups] :: Framework -> [CLine]
+ Copilot.Arduino.Internals: class ShowCType t
+ Copilot.Arduino.Internals: evalSketch :: Sketch a -> (Maybe Spec, Framework)
+ Copilot.Arduino.Internals: getUniqueId :: Sketch Integer
+ Copilot.Arduino.Internals: instance Control.Monad.State.Class.MonadState Copilot.Arduino.Internals.UniqueIds Copilot.Arduino.Internals.Sketch
+ Copilot.Arduino.Internals: instance Copilot.Arduino.Internals.IsAnalogInputPin t => Copilot.Arduino.Internals.Input (Copilot.Arduino.Internals.Pin t) Copilot.Arduino.Internals.ADC
+ Copilot.Arduino.Internals: instance Copilot.Arduino.Internals.ShowCType GHC.Int.Int16
+ Copilot.Arduino.Internals: instance Copilot.Arduino.Internals.ShowCType GHC.Int.Int32
+ Copilot.Arduino.Internals: instance Copilot.Arduino.Internals.ShowCType GHC.Int.Int64
+ Copilot.Arduino.Internals: instance Copilot.Arduino.Internals.ShowCType GHC.Int.Int8
+ Copilot.Arduino.Internals: instance Copilot.Arduino.Internals.ShowCType GHC.Types.Bool
+ Copilot.Arduino.Internals: instance Copilot.Arduino.Internals.ShowCType GHC.Types.Double
+ Copilot.Arduino.Internals: instance Copilot.Arduino.Internals.ShowCType GHC.Types.Float
+ Copilot.Arduino.Internals: instance Copilot.Arduino.Internals.ShowCType GHC.Word.Word16
+ Copilot.Arduino.Internals: instance Copilot.Arduino.Internals.ShowCType GHC.Word.Word32
+ Copilot.Arduino.Internals: instance Copilot.Arduino.Internals.ShowCType GHC.Word.Word64
+ Copilot.Arduino.Internals: instance Copilot.Arduino.Internals.ShowCType GHC.Word.Word8
+ Copilot.Arduino.Internals: newtype UniqueIds
+ Copilot.Arduino.Internals: showCType :: ShowCType t => Proxy t -> String
+ Copilot.Arduino.Internals: type ADC = Int16
+ Copilot.Arduino.Library.EEPROMex: EndAddress :: Word16 -> EndAddress
+ Copilot.Arduino.Library.EEPROMex: RangeWrites :: (Behavior Bool -> Behavior RangeIndex) -> Behavior t -> RangeWrites t
+ Copilot.Arduino.Library.EEPROMex: StartAddress :: Word16 -> StartAddress
+ Copilot.Arduino.Library.EEPROMex: alloc :: forall t. EEPROMable t => Sketch (Behavior t, Location t)
+ Copilot.Arduino.Library.EEPROMex: alloc' :: forall t. EEPROMable t => t -> Sketch (Behavior t, Location t)
+ Copilot.Arduino.Library.EEPROMex: allocRange :: forall t. EEPROMable t => Word16 -> Sketch (Range t)
+ Copilot.Arduino.Library.EEPROMex: class (ShowCType t, Typed t) => EEPROMable t
+ Copilot.Arduino.Library.EEPROMex: data Location t
+ Copilot.Arduino.Library.EEPROMex: data Range t
+ Copilot.Arduino.Library.EEPROMex: data RangeWrites t
+ Copilot.Arduino.Library.EEPROMex: instance Copilot.Arduino.Library.EEPROMex.EEPROMable GHC.Int.Int16
+ Copilot.Arduino.Library.EEPROMex: instance Copilot.Arduino.Library.EEPROMex.EEPROMable GHC.Int.Int32
+ Copilot.Arduino.Library.EEPROMex: instance Copilot.Arduino.Library.EEPROMex.EEPROMable GHC.Int.Int8
+ Copilot.Arduino.Library.EEPROMex: instance Copilot.Arduino.Library.EEPROMex.EEPROMable GHC.Types.Bool
+ Copilot.Arduino.Library.EEPROMex: instance Copilot.Arduino.Library.EEPROMex.EEPROMable GHC.Types.Double
+ Copilot.Arduino.Library.EEPROMex: instance Copilot.Arduino.Library.EEPROMex.EEPROMable GHC.Types.Float
+ Copilot.Arduino.Library.EEPROMex: instance Copilot.Arduino.Library.EEPROMex.EEPROMable GHC.Word.Word16
+ Copilot.Arduino.Library.EEPROMex: instance Copilot.Arduino.Library.EEPROMex.EEPROMable GHC.Word.Word32
+ Copilot.Arduino.Library.EEPROMex: instance Copilot.Arduino.Library.EEPROMex.EEPROMable GHC.Word.Word8
+ Copilot.Arduino.Library.EEPROMex: instance Copilot.Arduino.Library.EEPROMex.EEPROMable t => Copilot.Arduino.Internals.IsBehavior (Copilot.Arduino.Library.EEPROMex.RangeWrites t)
+ Copilot.Arduino.Library.EEPROMex: instance Copilot.Arduino.Library.EEPROMex.EEPROMable t => Copilot.Arduino.Internals.Output (Copilot.Arduino.Library.EEPROMex.Location t) (Copilot.Arduino.Internals.Event () (Copilot.Language.Stream.Stream t))
+ Copilot.Arduino.Library.EEPROMex: instance Copilot.Arduino.Library.EEPROMex.EEPROMable t => Copilot.Arduino.Internals.Output (Copilot.Arduino.Library.EEPROMex.Range t) (Copilot.Arduino.Internals.Event () (Copilot.Arduino.Library.EEPROMex.RangeWrites t))
+ Copilot.Arduino.Library.EEPROMex: instance Copilot.Arduino.Library.EEPROMex.EEPROMable t => Copilot.Arduino.Internals.Output (Copilot.Arduino.Library.EEPROMex.Range t) (Copilot.Arduino.Library.EEPROMex.RangeWrites t)
+ Copilot.Arduino.Library.EEPROMex: instance GHC.Classes.Eq Copilot.Arduino.Library.EEPROMex.EndAddress
+ Copilot.Arduino.Library.EEPROMex: instance GHC.Classes.Eq Copilot.Arduino.Library.EEPROMex.StartAddress
+ Copilot.Arduino.Library.EEPROMex: instance GHC.Classes.Ord Copilot.Arduino.Library.EEPROMex.EndAddress
+ Copilot.Arduino.Library.EEPROMex: instance GHC.Classes.Ord Copilot.Arduino.Library.EEPROMex.StartAddress
+ Copilot.Arduino.Library.EEPROMex: instance GHC.Enum.Bounded Copilot.Arduino.Library.EEPROMex.EndAddress
+ Copilot.Arduino.Library.EEPROMex: instance GHC.Enum.Bounded Copilot.Arduino.Library.EEPROMex.StartAddress
+ Copilot.Arduino.Library.EEPROMex: instance GHC.Enum.Enum Copilot.Arduino.Library.EEPROMex.EndAddress
+ Copilot.Arduino.Library.EEPROMex: instance GHC.Enum.Enum Copilot.Arduino.Library.EEPROMex.StartAddress
+ Copilot.Arduino.Library.EEPROMex: instance GHC.Num.Num Copilot.Arduino.Library.EEPROMex.EndAddress
+ Copilot.Arduino.Library.EEPROMex: instance GHC.Num.Num Copilot.Arduino.Library.EEPROMex.StartAddress
+ Copilot.Arduino.Library.EEPROMex: instance GHC.Read.Read Copilot.Arduino.Library.EEPROMex.EndAddress
+ Copilot.Arduino.Library.EEPROMex: instance GHC.Read.Read Copilot.Arduino.Library.EEPROMex.StartAddress
+ Copilot.Arduino.Library.EEPROMex: instance GHC.Real.Integral Copilot.Arduino.Library.EEPROMex.EndAddress
+ Copilot.Arduino.Library.EEPROMex: instance GHC.Real.Integral Copilot.Arduino.Library.EEPROMex.StartAddress
+ Copilot.Arduino.Library.EEPROMex: instance GHC.Real.Real Copilot.Arduino.Library.EEPROMex.EndAddress
+ Copilot.Arduino.Library.EEPROMex: instance GHC.Real.Real Copilot.Arduino.Library.EEPROMex.StartAddress
+ Copilot.Arduino.Library.EEPROMex: instance GHC.Show.Show Copilot.Arduino.Library.EEPROMex.EndAddress
+ Copilot.Arduino.Library.EEPROMex: instance GHC.Show.Show Copilot.Arduino.Library.EEPROMex.StartAddress
+ Copilot.Arduino.Library.EEPROMex: maxAllowedWrites :: Word16 -> Sketch ()
+ Copilot.Arduino.Library.EEPROMex: memPool :: StartAddress -> EndAddress -> Sketch ()
+ Copilot.Arduino.Library.EEPROMex: newtype EndAddress
+ Copilot.Arduino.Library.EEPROMex: newtype StartAddress
+ Copilot.Arduino.Library.EEPROMex: sweepRange :: RangeIndex -> Behavior t -> RangeWrites t
+ Copilot.Arduino.Library.EEPROMex: sweepRange' :: RangeIndex -> Behavior Bool -> Behavior RangeIndex
+ Copilot.Arduino.Library.EEPROMex: type RangeIndex = Word16
+ Copilot.Arduino.Library.Serial: FlashString :: String -> FlashString
+ Copilot.Arduino.Library.Serial: class OutputString t
+ Copilot.Arduino.Library.Serial: newtype FlashString
+ Copilot.Arduino.Library.Serial.Device: FlashString :: String -> FlashString
+ Copilot.Arduino.Library.Serial.Device: class OutputString t
+ Copilot.Arduino.Library.Serial.Device: instance Copilot.Arduino.Library.Serial.Device.OutputString Copilot.Arduino.Library.Serial.Device.FlashString
+ Copilot.Arduino.Library.Serial.Device: instance Copilot.Arduino.Library.Serial.Device.OutputString GHC.Base.String
+ Copilot.Arduino.Library.Serial.Device: newtype FlashString
+ Copilot.Arduino.Library.Serial.Device: quoteString :: String -> String
+ Copilot.Arduino.Library.Serial.XBee: FlashString :: String -> FlashString
+ Copilot.Arduino.Library.Serial.XBee: class OutputString t
+ Copilot.Arduino.Library.Serial.XBee: newtype FlashString
+ Copilot.Arduino.Nano: sizeOfEEPROM :: Word16
+ Copilot.Arduino.Uno: sizeOfEEPROM :: Word16
- Copilot.Arduino: MicroSeconds :: Stream Word16 -> MicroSeconds
+ Copilot.Arduino: MicroSeconds :: Stream Word32 -> MicroSeconds
- Copilot.Arduino: MilliSeconds :: Stream Word16 -> MilliSeconds
+ Copilot.Arduino: MilliSeconds :: Stream Word32 -> MilliSeconds
- Copilot.Arduino.Internals: Framework :: [CLine] -> [CLine] -> Map PinId (Set PinMode) -> [CLine] -> Framework
+ Copilot.Arduino.Internals: Framework :: [CLine] -> [CLine] -> [CLine] -> Map PinId (Set PinMode) -> [CLine] -> Framework
- Copilot.Arduino.Internals: Sketch :: Writer [(Spec, Framework)] t -> Sketch t
+ Copilot.Arduino.Internals: Sketch :: WriterT [(Spec, Framework)] (State UniqueIds) t -> Sketch t
- Copilot.Arduino.Library.Serial: show :: forall t. (ShowableType t, Typed t) => Stream t -> FormatOutput
+ Copilot.Arduino.Library.Serial: show :: forall t. (ShowCType 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: showFormatted :: forall t f. (ShowCType t, Typed t, FormatableType t f) => Stream t -> f -> FormatOutput
- Copilot.Arduino.Library.Serial: str :: String -> FormatOutput
+ Copilot.Arduino.Library.Serial: str :: OutputString t => t -> FormatOutput
- Copilot.Arduino.Library.Serial.Device: show :: forall t. (ShowableType t, Typed t) => Stream t -> FormatOutput
+ Copilot.Arduino.Library.Serial.Device: show :: forall t. (ShowCType t, Typed t) => Stream t -> FormatOutput
- Copilot.Arduino.Library.Serial.Device: showFormatted :: forall t f. (ShowableType t, Typed t, FormatableType t f) => Stream t -> f -> FormatOutput
+ Copilot.Arduino.Library.Serial.Device: showFormatted :: forall t f. (ShowCType t, Typed t, FormatableType t f) => Stream t -> f -> FormatOutput
- Copilot.Arduino.Library.Serial.Device: str :: String -> FormatOutput
+ Copilot.Arduino.Library.Serial.Device: str :: OutputString t => t -> FormatOutput
- Copilot.Arduino.Library.Serial.XBee: show :: forall t. (ShowableType t, Typed t) => Stream t -> FormatOutput
+ Copilot.Arduino.Library.Serial.XBee: show :: forall t. (ShowCType t, Typed t) => Stream t -> FormatOutput
- Copilot.Arduino.Library.Serial.XBee: showFormatted :: forall t f. (ShowableType t, Typed t, FormatableType t f) => Stream t -> f -> FormatOutput
+ Copilot.Arduino.Library.Serial.XBee: showFormatted :: forall t f. (ShowCType t, Typed t, FormatableType t f) => Stream t -> f -> FormatOutput
- Copilot.Arduino.Library.Serial.XBee: str :: String -> FormatOutput
+ Copilot.Arduino.Library.Serial.XBee: str :: OutputString t => t -> FormatOutput
Files
- CHANGELOG +13/−0
- README +12/−0
- TODO +18/−1
- arduino-copilot.cabal +10/−1
- examples/button/demo.hs +2/−2
- examples/eeprom/Makefile +129/−0
- examples/eeprom/README +25/−0
- examples/eeprom/demo.hs +78/−0
- examples/eeprom/pre-build-hook.sh +2/−0
- examples/eepromrange/Makefile +129/−0
- examples/eepromrange/README +26/−0
- examples/eepromrange/demo.hs +40/−0
- examples/eepromrange/pre-build-hook.sh +2/−0
- examples/serialport/demo.hs +7/−4
- src/Copilot/Arduino.hs +66/−10
- src/Copilot/Arduino/Internals.hs +56/−6
- src/Copilot/Arduino/Library/EEPROMex.hs +368/−0
- src/Copilot/Arduino/Library/Serial.hs +3/−2
- src/Copilot/Arduino/Library/Serial/Device.hs +26/−25
- src/Copilot/Arduino/Library/Serial/XBee.hs +3/−2
- src/Copilot/Arduino/Main.hs +12/−9
- src/Copilot/Arduino/Nano.hs +6/−0
- src/Copilot/Arduino/Uno.hs +4/−0
CHANGELOG view
@@ -1,3 +1,16 @@+arduino-copilot (1.4.0) unstable; urgency=medium++ * New Copilot.Arduino.Library.EEPROMex module.+ * Serial: Added FlashString, to display strings directly from flash+ without copying into ram.+ * Added millis and micros.+ * MicroSeconds and MilliSeconds types changed to contain a Word32 not a+ Word16.+ * Renamed Voltage to ADC+ * Added frequency.++ -- Joey Hess <id@joeyh.name> Sat, 01 Feb 2020 16:30:43 -0400+ arduino-copilot (1.3.0) unstable; urgency=medium * New polymorphic "input" action that can be used to read digital and
README view
@@ -12,3 +12,15 @@ This and other examples are included in the examples/ directory, each with their own README explaining how to build and use them.++## Contributing++Contributions are welcome, including adding support for more+Arduino C libraries, provided that the C library is reasonably commonly+used, and is licensed with a free software license.++Contributors of such libraries will be expected to maintain them,+otherwise they may get removed if they are blocking changes to+arduino-copilot or are buggy.++Any contribution should have well documented functions and types.
TODO view
@@ -1,3 +1,4 @@+ * Currently all Inputs run each time through the loop, which could be a problem when an input is slow. It would be good if the @: operator could also rate limit when Inputs occur.@@ -60,12 +61,23 @@ three never runs, because both combinators must branch on the same Stream Bool. +* The way serial output works is somewhat unsatisfying, because+ it does not take a Stream FormatOutput. So the Copilot DSL can't be used+ to adjust the output format as the arduino runs; it's fixed at compile+ time. While Serial.byte does operate on a Stream, it would be hard to do+ eg, an interactive program using it.++ It might be possible, using Copilot's support for C structs, to allow+ Stream FormatOutput to be accepted by Copilot. However, this issue+ seems like it would probably get in the way of actually constructing such+ a stream: https://github.com/Copilot-Language/copilot/issues/36+ * 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.-* Voltage is a raw ADC value; add a stream function to convert that+* ADC is 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. * pulses@@ -73,3 +85,8 @@ * Wire library * test.hs is not wired into the cabal file because it depends on runghc having the library available+* EEPROMex supports writing individial bits of a byte, but the haskell+ library does not expose it. What would be a good haskell interface to+ that, a Stream (Array 8 Bool)?+* reading from EEPROMex ranges+* Factor out Range from EEPROMex, and use it to also implement RAM ranges.
arduino-copilot.cabal view
@@ -1,5 +1,5 @@ Name: arduino-copilot-Version: 1.3.0+Version: 1.4.0 Cabal-Version: >= 1.8 License: BSD3 Maintainer: Joey Hess <id@joeyh.name>@@ -45,6 +45,14 @@ examples/serialport/README examples/serialport/demo.hs examples/serialport/pre-build-hook.sh+ examples/eeprom/Makefile+ examples/eeprom/README+ examples/eeprom/demo.hs+ examples/eeprom/pre-build-hook.sh+ examples/eepromrange/Makefile+ examples/eepromrange/README+ examples/eepromrange/demo.hs+ examples/eepromrange/pre-build-hook.sh test.hs Library@@ -58,6 +66,7 @@ Copilot.Arduino.Library.Serial Copilot.Arduino.Library.Serial.Device Copilot.Arduino.Library.Serial.XBee+ Copilot.Arduino.Library.EEPROMex Other-Modules: Copilot.Arduino.Main Build-Depends:
examples/button/demo.hs view
@@ -2,10 +2,10 @@ import Copilot.Arduino.Uno -longer_and_longer :: Stream Word16+longer_and_longer :: Stream Word32 longer_and_longer = counter true $ counter true false `mod` 64 == 0 -counter :: Stream Bool -> Stream Bool -> Stream Word16+counter :: Stream Bool -> Stream Bool -> Stream Word32 counter inc reset = cnt where cnt = if reset
+ examples/eeprom/Makefile view
@@ -0,0 +1,129 @@+# Arduino Make file. Refer to https://github.com/sudar/Arduino-Makefile+BOARD_TAG = uno+ARDUINO_LIBS = EEPROMEx+CPPFLAGS = -D_EEPROMEX_DEBUG+include /usr/share/arduino/Arduino.mk++# --- leonardo (or pro micro w/leo bootloader)+#BOARD_TAG = leonardo+#MONITOR_PORT = /dev/ttyACM0+#include /usr/share/arduino/Arduino.mk++# --- mega2560 ide 1.0+#BOARD_TAG = mega2560+#ARDUINO_PORT = /dev/ttyACM0+#include /usr/share/arduino/Arduino.mk++# --- mega2560 ide 1.6+#BOARD_TAG = mega+#BOARD_SUB = atmega2560+#MONITOR_PORT = /dev/ttyACM0+#ARDUINO_DIR = /where/you/installed/arduino-1.6.5+#include /usr/share/arduino/Arduino.mk++# --- nano ide 1.0+#BOARD_TAG = nano328+#MONITOR_PORT = /dev/ttyUSB0+#include /usr/share/arduino/Arduino.mk++# --- nano ide 1.6+#BOARD_TAG = nano+#BOARD_SUB = atmega328+#ARDUINO_DIR = /where/you/installed/arduino-1.6.5+#include /usr/share/arduino/Arduino.mk++# --- pro mini+#BOARD_TAG = pro5v328+#MONITOR_PORT = /dev/ttyUSB0+#include /usr/share/arduino/Arduino.mk++# --- sparkfun pro micro+#BOARD_TAG = promicro16+#ALTERNATE_CORE = promicro+#BOARDS_TXT = $(HOME)/arduino/hardware/promicro/boards.txt+#BOOTLOADER_PARENT = $(HOME)/arduino/hardware/promicro/bootloaders+#BOOTLOADER_PATH = caterina+#BOOTLOADER_FILE = Caterina-promicro16.hex+#ISP_PROG = usbasp+#AVRDUDE_OPTS = -v+#include /usr/share/arduino/Arduino.mk++# --- chipkit+#BOARD_TAG = mega_pic32+#MPIDE_DIR = /where/you/installed/mpide-0023-linux64-20130817-test+#include /usr/share/arduino/chipKIT.mk++# --- pinoccio+#BOARD_TAG = pinoccio256+#ALTERNATE_CORE = pinoccio+#BOOTLOADER_PARENT = $(HOME)/arduino/hardware/pinoccio/bootloaders+#BOOTLOADER_PATH = STK500RFR2/release_0.51+#BOOTLOADER_FILE = boot_pinoccio.hex+#CFLAGS_STD = -std=gnu99+#CXXFLAGS_STD = -std=gnu++11+#include /usr/share/arduino/Arduino.mk++# --- fio+#BOARD_TAG = fio+#include /usr/share/arduino/Arduino.mk++# --- atmega-ng ide 1.6+#BOARD_TAG = atmegang+#BOARD_SUB = atmega168+#MONITOR_PORT = /dev/ttyACM0+#ARDUINO_DIR = /where/you/installed/arduino-1.6.5+#include /usr/share/arduino/Arduino.mk++# --- arduino-tiny ide 1.0+#ISP_PROG = usbasp+#BOARD_TAG = attiny85at8+#ALTERNATE_CORE = tiny+#ARDUINO_VAR_PATH = $(HOME)/arduino/hardware/tiny/cores/tiny+#ARDUINO_CORE_PATH = $(HOME)/arduino/hardware/tiny/cores/tiny+#AVRDUDE_OPTS = -v+#include /usr/share/arduino/Arduino.mk++# --- arduino-tiny ide 1.6+#ISP_PROG = usbasp+#BOARD_TAG = attiny85at8+#ALTERNATE_CORE = tiny+#ARDUINO_DIR = /where/you/installed/arduino-1.6.5+#include /usr/share/arduino/Arduino.mk++# --- damellis attiny ide 1.0+#ISP_PROG = usbasp+#BOARD_TAG = attiny85+#ALTERNATE_CORE = attiny-master+#AVRDUDE_OPTS = -v+#include /usr/share/arduino/Arduino.mk++# --- damellis attiny ide 1.6+#ISP_PROG = usbasp+#BOARD_TAG = attiny+#BOARD_SUB = attiny85+#ALTERNATE_CORE = attiny+#F_CPU = 16000000L+#ARDUINO_DIR = /where/you/installed/arduino-1.6.5+#include /usr/share/arduino/Arduino.mk++# --- teensy3+#BOARD_TAG = teensy31+#ARDUINO_DIR = /where/you/installed/the/patched/teensy/arduino-1.0.6+#include /usr/share/arduino/Teensy.mk++# --- mighty 1284p+#BOARD_TAG = mighty_opt+#BOARDS_TXT = $(HOME)/arduino/hardware/mighty-1284p/boards.txt+#BOOTLOADER_PARENT = $(HOME)/arduino/hardware/mighty-1284p/bootloaders+#BOOTLOADER_PATH = optiboot+#BOOTLOADER_FILE = optiboot_atmega1284p.hex+#ISP_PROG = usbasp+#AVRDUDE_OPTS = -v+#include /usr/share/arduino/Arduino.mk++# --- atmega328p on breadboard+#BOARD_TAG = atmega328bb+#ISP_PROG = usbasp+#AVRDUDE_OPTS = -v+#BOARDS_TXT = $(HOME)/arduino/hardware/breadboard/boards.txt+#include /usr/share/arduino/Arduino.mk
+ examples/eeprom/README view
@@ -0,0 +1,25 @@+This is a demo program using arduino-copilot, showing how to use EEPROMex.++This needs the Arduino-Makefile to be installed. It relies on it+to set CPPFLAGS=-D_EEPROMEX_DEBUG when building the C code.+<https://github.com/sudar/Arduino-Makefile>++The path in the `Makefile` to `Arduino.mk` may need to be adjusted (the+default is the location where on a Debian system, `apt-get install+arduino-mk` will install it).++You will also need to install the EEPROMex library to someplace where+the Arduino toolchain can find it.+<https://github.com/thijse/Arduino-EEPROMEx/>++First, run this to build and upload a sketch that zeros out some values of+the EEPROM.++ ZERO=y runghc demo.hs+ ZERO=y make upload++Then, you can build and upload the sketch that actually does something+interesting.++ runghc demo.hs+ make upload
+ examples/eeprom/demo.hs view
@@ -0,0 +1,78 @@+{-# LANGUAGE RebindableSyntax #-}++import Copilot.Arduino.Uno+import qualified Copilot.Arduino.Library.EEPROMex as EEPROM+import qualified Copilot.Arduino.Library.Serial as Serial+import System.Posix.Env+import qualified Prelude++main :: IO ()+main = do+ z <- getEnvDefault "ZERO" ""+ arduino $ case z of+ "y" -> mainZero+ _ -> mainReal++-- Zero out the EEPROM, in preparation for mainReal.+mainZero :: Sketch ()+mainZero = do+ EEPROM.maxAllowedWrites 10++ (_, eepromloc) <- EEPROM.alloc' (20 :: ADC)+ eepromloc =: constant (0 :: ADC) @: firstIteration++ (_, uptimeloc) <- EEPROM.alloc' (0 :: Word32)+ uptimeloc =: constant (0 :: Word32) @: firstIteration++ Serial.baud 9600+ Serial.device =: [ Serial.str "EEPROM zeroed" ] @: firstIteration++mainReal :: Sketch ()+mainReal = do+ -- Store the maximum value read from a1 in the EEPROM.+ -- There are only 1024 possible values, so this can only possibly+ -- write new values that many times, so no delay is needed in this+ -- program. Just in case though, limit the total number of writes:+ EEPROM.maxAllowedWrites 2048+ (bootval, eepromloc) <- EEPROM.alloc' (20 :: ADC)+ currval <- input' a1 [10, 11, 25, 100, 99, 0, 0, 0, 0, 0]+ let maxval = if currval > bootval then currval else bootval+ -- Note that using maxBound here means it will never write+ -- on the first iteration, even if a1 is then larger than bootval.+ -- Any better way to write this that avoids the problem?+ let newmax = currval > ([maxBound] ++ maxval)+ eepromloc =: currval @: newmax++ -- Store the maximum MilliSeconds this program has been left running+ -- in the EEPROM too.+ (bootuptime, uptimeloc) <- EEPROM.alloc' (0 :: Word32)+ uptime <- input' millis (dummyclockinput [1, 1, 1, 2, 2, 2, 3, 3, 3])+ -- micros overflows after ~70 minutes; detect that and use it to+ -- trigger a periodic write of the uptime to EEPROM.+ hourlyish <- microsOverFlowed+ let maxuptime = if uptime > bootuptime then uptime else bootuptime+ uptimeloc =: maxuptime @: hourlyish+ + Serial.baud 9600+ Serial.device =:+ [ Serial.str "max a1 seen: "+ -- This is not quite right, because maxval resets to+ -- bootval when this is displayed at hourlyish.+ -- Interpreting shows the problem:+ -- -- (3) (20,3)+ -- How to fix this?+ , Serial.show maxval+ , Serial.str " max uptime: "+ , Serial.show maxuptime+ , Serial.char '\n'+ ]+ @: (firstIteration || newmax || hourlyish)++microsOverFlowed :: Sketch (Behavior Bool)+microsOverFlowed = do+ t <- input' micros (dummyclockinput [1, 2, 3])+ let oldt = [0] ++ t+ return (t < oldt)++dummyclockinput :: [Word32] -> [Word32]+dummyclockinput = Prelude.cycle
+ examples/eeprom/pre-build-hook.sh view
@@ -0,0 +1,2 @@+#!/bin/sh+exec runghc demo.hs
+ examples/eepromrange/Makefile view
@@ -0,0 +1,129 @@+# Arduino Make file. Refer to https://github.com/sudar/Arduino-Makefile+BOARD_TAG = uno+ARDUINO_LIBS = EEPROMEx+CPPFLAGS = -D_EEPROMEX_DEBUG+include /usr/share/arduino/Arduino.mk++# --- leonardo (or pro micro w/leo bootloader)+#BOARD_TAG = leonardo+#MONITOR_PORT = /dev/ttyACM0+#include /usr/share/arduino/Arduino.mk++# --- mega2560 ide 1.0+#BOARD_TAG = mega2560+#ARDUINO_PORT = /dev/ttyACM0+#include /usr/share/arduino/Arduino.mk++# --- mega2560 ide 1.6+#BOARD_TAG = mega+#BOARD_SUB = atmega2560+#MONITOR_PORT = /dev/ttyACM0+#ARDUINO_DIR = /where/you/installed/arduino-1.6.5+#include /usr/share/arduino/Arduino.mk++# --- nano ide 1.0+#BOARD_TAG = nano328+#MONITOR_PORT = /dev/ttyUSB0+#include /usr/share/arduino/Arduino.mk++# --- nano ide 1.6+#BOARD_TAG = nano+#BOARD_SUB = atmega328+#ARDUINO_DIR = /where/you/installed/arduino-1.6.5+#include /usr/share/arduino/Arduino.mk++# --- pro mini+#BOARD_TAG = pro5v328+#MONITOR_PORT = /dev/ttyUSB0+#include /usr/share/arduino/Arduino.mk++# --- sparkfun pro micro+#BOARD_TAG = promicro16+#ALTERNATE_CORE = promicro+#BOARDS_TXT = $(HOME)/arduino/hardware/promicro/boards.txt+#BOOTLOADER_PARENT = $(HOME)/arduino/hardware/promicro/bootloaders+#BOOTLOADER_PATH = caterina+#BOOTLOADER_FILE = Caterina-promicro16.hex+#ISP_PROG = usbasp+#AVRDUDE_OPTS = -v+#include /usr/share/arduino/Arduino.mk++# --- chipkit+#BOARD_TAG = mega_pic32+#MPIDE_DIR = /where/you/installed/mpide-0023-linux64-20130817-test+#include /usr/share/arduino/chipKIT.mk++# --- pinoccio+#BOARD_TAG = pinoccio256+#ALTERNATE_CORE = pinoccio+#BOOTLOADER_PARENT = $(HOME)/arduino/hardware/pinoccio/bootloaders+#BOOTLOADER_PATH = STK500RFR2/release_0.51+#BOOTLOADER_FILE = boot_pinoccio.hex+#CFLAGS_STD = -std=gnu99+#CXXFLAGS_STD = -std=gnu++11+#include /usr/share/arduino/Arduino.mk++# --- fio+#BOARD_TAG = fio+#include /usr/share/arduino/Arduino.mk++# --- atmega-ng ide 1.6+#BOARD_TAG = atmegang+#BOARD_SUB = atmega168+#MONITOR_PORT = /dev/ttyACM0+#ARDUINO_DIR = /where/you/installed/arduino-1.6.5+#include /usr/share/arduino/Arduino.mk++# --- arduino-tiny ide 1.0+#ISP_PROG = usbasp+#BOARD_TAG = attiny85at8+#ALTERNATE_CORE = tiny+#ARDUINO_VAR_PATH = $(HOME)/arduino/hardware/tiny/cores/tiny+#ARDUINO_CORE_PATH = $(HOME)/arduino/hardware/tiny/cores/tiny+#AVRDUDE_OPTS = -v+#include /usr/share/arduino/Arduino.mk++# --- arduino-tiny ide 1.6+#ISP_PROG = usbasp+#BOARD_TAG = attiny85at8+#ALTERNATE_CORE = tiny+#ARDUINO_DIR = /where/you/installed/arduino-1.6.5+#include /usr/share/arduino/Arduino.mk++# --- damellis attiny ide 1.0+#ISP_PROG = usbasp+#BOARD_TAG = attiny85+#ALTERNATE_CORE = attiny-master+#AVRDUDE_OPTS = -v+#include /usr/share/arduino/Arduino.mk++# --- damellis attiny ide 1.6+#ISP_PROG = usbasp+#BOARD_TAG = attiny+#BOARD_SUB = attiny85+#ALTERNATE_CORE = attiny+#F_CPU = 16000000L+#ARDUINO_DIR = /where/you/installed/arduino-1.6.5+#include /usr/share/arduino/Arduino.mk++# --- teensy3+#BOARD_TAG = teensy31+#ARDUINO_DIR = /where/you/installed/the/patched/teensy/arduino-1.0.6+#include /usr/share/arduino/Teensy.mk++# --- mighty 1284p+#BOARD_TAG = mighty_opt+#BOARDS_TXT = $(HOME)/arduino/hardware/mighty-1284p/boards.txt+#BOOTLOADER_PARENT = $(HOME)/arduino/hardware/mighty-1284p/bootloaders+#BOOTLOADER_PATH = optiboot+#BOOTLOADER_FILE = optiboot_atmega1284p.hex+#ISP_PROG = usbasp+#AVRDUDE_OPTS = -v+#include /usr/share/arduino/Arduino.mk++# --- atmega328p on breadboard+#BOARD_TAG = atmega328bb+#ISP_PROG = usbasp+#AVRDUDE_OPTS = -v+#BOARDS_TXT = $(HOME)/arduino/hardware/breadboard/boards.txt+#include /usr/share/arduino/Arduino.mk
+ examples/eepromrange/README view
@@ -0,0 +1,26 @@+This is a demo program using arduino-copilot, showing how to use EEPROMex+with ranges.++This needs the Arduino-Makefile to be installed. It relies on it+to set CPPFLAGS=-D_EEPROMEX_DEBUG when building the C code.+<https://github.com/sudar/Arduino-Makefile>++The path in the `Makefile` to `Arduino.mk` may need to be adjusted (the+default is the location where on a Debian system, `apt-get install+arduino-mk` will install it).++You will also need to install the EEPROMex library to someplace where+the Arduino toolchain can find it.+<https://github.com/thijse/Arduino-EEPROMEx/>++First, run this to build and upload a sketch that zeros out the entire+EEPROM.++ ZERO=y runghc demo.hs+ ZERO=y make upload++Then, you can build and upload the sketch that actually does something+interesting.++ runghc demo.hs+ make upload
+ examples/eepromrange/demo.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE RebindableSyntax #-}++import Copilot.Arduino.Uno+import qualified Copilot.Arduino.Library.EEPROMex as EEPROM+import qualified Copilot.Arduino.Library.Serial as Serial+import System.Posix.Env++-- Change this to use a smaller part of the EEPROM.+sz :: Word16+sz = sizeOfEEPROM++main :: IO ()+main = do+ z <- getEnvDefault "ZERO" ""+ arduino $ case z of+ "y" -> mainZero+ _ -> mainReal++-- Zero out the EEPROM, in preparation for mainReal.+mainZero :: Sketch ()+mainZero = do+ EEPROM.maxAllowedWrites sz++ range <- EEPROM.allocRange sz :: Sketch (EEPROM.Range Word8)+ range =: EEPROM.sweepRange 0 (constant (0 :: Word8))+ + Serial.baud 9600+ Serial.device =: [ Serial.str "EEPROM zeroing started" ]+ @: firstIteration++mainReal :: Sketch ()+mainReal = do+ EEPROM.maxAllowedWrites (sz * 10)+ range <- EEPROM.allocRange sz :: Sketch (EEPROM.Range ADC)+ v <- input' a1 ([10, 20..] :: [ADC])+ range =: EEPROM.sweepRange 0 v @: frequency 3+ led =: frequency 3+ Serial.device =: [ Serial.show v, Serial.char '\n']+ delay =: MilliSeconds (constant 10000)+ Serial.baud 9600
+ examples/eepromrange/pre-build-hook.sh view
@@ -0,0 +1,2 @@+#!/bin/sh+exec runghc demo.hs
examples/serialport/demo.hs view
@@ -16,11 +16,11 @@ useXBee :: Bool useXBee = True -userIsTyping :: Stream Int8 -> Stream Bool+userIsTyping :: Behavior Int8 -> Behavior Bool userIsTyping userinput = userinput /= constant Serial.noInput getSerial :: Input o Int8 => Bool -> o -> Sketch (Behavior Int8)-getSerial True i = input i+getSerial True i = input' i (repeat Serial.noInput) getSerial False _ = return (constant Serial.noInput) main :: IO ()@@ -28,14 +28,17 @@ when useSerial $ Serial.baud 9600 when useXBee $ XBee.configure pin2 pin3 (XBee.Baud 9600) - -- Measure the voltage of an analog input, and display it to the+ -- Measure the an analog input, and display it to the -- serial port. This is a handy demo because with nothing connected -- to a0, just moving your hand close to the arduino will probably -- cause some noise to be read.- n <- input a0 :: Sketch (Behavior Voltage)+ n <- input' a0 [1, 50, 11, 99, 120] :: Sketch (Behavior ADC)+ c <- input' millis [20, 60, 80, 100] :: Sketch (Behavior Word32) let msg = [ Serial.str "analog input:" , Serial.showFormatted n Serial.HEX+ , Serial.str " millis:"+ , Serial.show c , Serial.char '\n' ] when useSerial $ Serial.device =: msg
src/Copilot/Arduino.hs view
@@ -9,6 +9,8 @@ {-# LANGUAGE RebindableSyntax #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TypeApplications #-} module Copilot.Arduino ( -- * Arduino sketch generation@@ -25,7 +27,8 @@ input, input', pullup,- Voltage,+ millis,+ micros, -- * Outputs -- -- | Only a few common outputs are included in this module.@@ -36,11 +39,16 @@ (=:), pwm, delay,+ -- * Other types+ ADC, MilliSeconds(..), MicroSeconds(..),+ ClockMillis,+ ClockMicros, -- * Utilities blinking, firstIteration,+ frequency, sketchSpec, -- * Copilot DSL --@@ -62,6 +70,8 @@ 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 @@ -82,11 +92,19 @@ 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 Word16)+data MilliSeconds = MilliSeconds (Stream Word32) -- | A stream of microseconds.-data MicroSeconds = MicroSeconds (Stream Word16)+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.@@ -104,7 +122,46 @@ 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 @@ -114,10 +171,11 @@ -- > led =: buttonpressed -- -- Some pins support multiple types of reads, for example pin a0--- supports a digital read (`Bool`), and an analog read (`Voltage`).--- In such cases you may need to specify the type of data to read:+-- 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 Voltage)+-- > v <- input a0 :: Sketch (Behavior ADC) input :: Input o t => o -> Sketch (Behavior t) input o = input' o [] @@ -154,6 +212,4 @@ -- This can be useful to intergrate with other libraries -- such as copilot-theorem. sketchSpec :: Sketch a -> Spec-sketchSpec (Sketch s) = sequence_ is- where- (is, _fs) = unzip (execWriter s)+sketchSpec = fromMaybe (return ()) . fst . evalSketch
src/Copilot/Arduino/Internals.hs view
@@ -15,9 +15,12 @@ import Language.Copilot import Control.Monad.Writer+import Control.Monad.State.Strict+import Data.Functor.Identity import qualified Data.Map as M import qualified Data.Set as S import Data.Type.Bool+import Data.Proxy import GHC.TypeLits -- | A value that changes over time.@@ -50,8 +53,14 @@ -- -- While it is a monad, a Sketch's outputs are not updated in any -- particular order, because Copilot does not guarantee any order.-newtype Sketch t = Sketch (Writer [(Spec, Framework)] t)- deriving (Monad, Applicative, Functor, MonadWriter [(Spec, Framework)])+newtype Sketch t = Sketch (WriterT [(Spec, Framework)] (State UniqueIds) t)+ deriving + ( Monad+ , Applicative+ , Functor+ , MonadWriter [(Spec, Framework)]+ , MonadState UniqueIds+ ) instance Monoid (Sketch ()) where mempty = Sketch (return ())@@ -59,12 +68,36 @@ instance Semigroup (Sketch t) where (Sketch a) <> (Sketch b) = Sketch (a >> b) +newtype UniqueIds = UniqueIds [Integer]++evalSketch :: Sketch a -> (Maybe Spec, Framework)+evalSketch (Sketch s) = (spec, mconcat fs)+ where+ (is, fs) = unzip $ + runIdentity $ evalStateT (execWriterT s) (UniqueIds [1..])+ -- Copilot will throw an ugly error if given a spec that does+ -- nothing at all, so return Nothing to avoid that.+ spec = if null is+ then Nothing+ else Just (sequence_ is)++getUniqueId :: Sketch Integer+getUniqueId = do+ v <- get+ case v of+ UniqueIds (u:us) -> do+ put (UniqueIds us)+ return u+ UniqueIds [] -> error "somehow UniqueIds ran out"+ -- | The framework of an Arduino sketch. data Framework = Framework { defines :: [CLine] -- ^ Things that come before the C code generated by Copilot. , setups :: [CLine] -- ^ Things to do at setup, not including configuring pins.+ , earlySetups :: [CLine]+ -- ^ Things to do at setup, before the setups. , pinmodes :: M.Map PinId (S.Set PinMode) -- ^ How pins are used. , loops :: [CLine]@@ -78,12 +111,13 @@ a <> b = Framework { defines = defines a <> defines b , setups = setups a <> setups b+ , earlySetups = earlySetups a <> earlySetups 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+ mempty = Framework mempty mempty mempty mempty mempty -- | Copilot only supports calling a trigger with a given name once -- per Spec; the generated C code will fail to build if the same name is@@ -117,6 +151,7 @@ f = Framework { defines = defineVar i , setups = setupInput i+ , earlySetups = mempty , pinmodes = M.map S.singleton (inputPinmode i) , loops = readInput i }@@ -262,10 +297,10 @@ | null interpretvalues = Nothing | otherwise = Just interpretvalues --- | Voltage read from an Arduino's ADC. Ranges from 0-1023.-type Voltage = Int16+-- | Value read from an Arduino's ADC. Ranges from 0-1023.+type ADC = Int16 -instance IsAnalogInputPin t => Input (Pin t) Voltage where+instance IsAnalogInputPin t => Input (Pin t) ADC where input' (Pin (PinId n)) interpretvalues = mkInput $ InputSource { defineVar = [CLine $ "int " <> varname <> ";"] , setupInput = []@@ -278,4 +313,19 @@ interpretvalues' | null interpretvalues = Nothing | otherwise = Just interpretvalues++class ShowCType t where+ showCType :: Proxy t -> String++instance ShowCType Bool where showCType _ = "bool"+instance ShowCType Int8 where showCType _ = "int8_t"+instance ShowCType Int16 where showCType _ = "int16_t"+instance ShowCType Int32 where showCType _ = "int32_t"+instance ShowCType Int64 where showCType _ = "int64_t"+instance ShowCType Word8 where showCType _ = "uint8_t"+instance ShowCType Word16 where showCType _ = "uint16_t"+instance ShowCType Word32 where showCType _ = "uint32_t"+instance ShowCType Word64 where showCType _ = "uint64_t"+instance ShowCType Float where showCType _ = "float"+instance ShowCType Double where showCType _ = "double"
+ src/Copilot/Arduino/Library/EEPROMex.hs view
@@ -0,0 +1,368 @@+-- | EEPROMex library for arduino-copilot.+--+-- This module is designed to be imported qualified.+--+-- This is an interface to the C EEPROMex library, which will need to be+-- installed in your Arduino development environment.+-- https://playground.arduino.cc/Code/EEPROMex/++{-# LANGUAGE RebindableSyntax #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE TypeFamilies #-}++module Copilot.Arduino.Library.EEPROMex (+ -- * Configuration+ maxAllowedWrites,+ memPool,+ StartAddress(..),+ EndAddress(..),+ EEPROMable,+ -- * Single values+ alloc,+ alloc',+ Location,+ -- * Ranges+ Range,+ RangeIndex,+ allocRange,+ sweepRange,+ sweepRange',+ RangeWrites(..),+) where++import Copilot.Arduino+import Copilot.Arduino.Internals+import Control.Monad.Writer+import Data.Proxy++-- | Set the maximum number of writes to EEPROM that can be made while+-- the Arduino is running. When too many writes have been made,+-- it will no longer write to the EEPROM, and will send warning+-- messages to the serial port.+--+-- This is a strongly encouraged safety measure to use, because the+-- Arduino's EEPROM can only endure around 100,000 writes, and+-- a Sketch that's constantly writing to EEPROM, without a well chosen +-- `:@` rate limit or `delay`, could damage your hardware in just a few+-- minutes.+--+-- Note that this module uses EEPROMex's update facility for all writes+-- to EEPROM. That avoids writing a byte when its current value is+-- the same as what needed to be written. That prevents excessive wear+-- in some cases, but you should still use maxAllowedWrites too.+--+-- For this to work, CPPFLAGS needs to include "-D_EEPROMEX_DEBUG" when the+-- EEPROMex C library gets built. When you use this, it generates C+-- code that makes sure that is enabled.+maxAllowedWrites :: Word16 -> Sketch ()+maxAllowedWrites n = tell [(return (), f)]+ where+ f = mempty+ { earlySetups = + [ CLine "#ifdef _EEPROMEX_DEBUG"+ , CLine $ "EEPROM.setMaxAllowedWrites(" <> show n <> ");"+ , CLine "#else"+ , CLine "#error \"maxAllowedWrites cannot be checked because _EEPROMEX_DEBUG is not set.\""+ , CLine "#endif"+ ]+ , defines = [ includeCLine ]+ }++-- | The address of the first byte of the EEPROM that will be allocated+-- by `alloc`. The default is to start allocation from 0. +--+-- Picking a different StartAddress can avoid overwriting the +-- part of the EEPROM that is used by some other program.+newtype StartAddress = StartAddress Word16+ deriving (Num, Eq, Ord, Enum, Show, Read, Bounded, Integral, Real)++-- | The address of the last byte of the EEPROM that can be allocated+-- by `alloc`. The default is to allow allocating 512 bytes.+--+-- Modules for particular boards, such as Copilot.Arduino.Library.Uno,+-- each define a sizeOfEEPROM value, so to use the entire EEPROM, use:+--+-- > EndAddress sizeOfEEPROM+newtype EndAddress = EndAddress Word16+ deriving (Num, Eq, Ord, Enum, Show, Read, Bounded, Integral, Real)++-- | Configure the EEPROM memory pool that the program can use.+--+-- > EEPROM.memPool 0 (EndAddress sizeOfEEPROM)+memPool :: StartAddress -> EndAddress -> Sketch ()+memPool (StartAddress start) (EndAddress end) = tell [(return (), f)]+ where+ f = mempty+ -- setMemPool() has to come before any+ -- getAddress(), so do it in earlySetups.+ { earlySetups = + [ CLine $ "EEPROM.setMemPool("+ <> show start+ <> ", "+ <> show end+ <> ");"+ ]+ , defines = [ includeCLine ]+ }++-- | Allocates a location in the EEPROM. +--+-- Two things are returned; first a `Behavior` which contains a value+-- read from the EEPROM at boot; and secondly a `Location` that can be+-- written to later on.+--+-- Here's an example of using it, to remember the maximum value read from+-- a1, persistently across power cycles.+--+-- > EEPROM.maxAllowedWrites 100+-- > (bootval, eepromloc) <- EEPROM.alloc+-- > currval <- input a1 :: Sketch (Behavior ADC)+-- > let maxval = [maxBound] ++ if currval > bootval then currval else bootval+-- > eepromloc =: currval @: (currval > maxval)+-- > delay =: MilliSeconds (constant 10000)+--+-- Of course, the EEPROM could already contain any value at the allocated+-- location to start with (either some default value or something written+-- by another program). So you'll need to first boot up a sketch that zeros+-- it out before using the sketch above. Here's a simple sketch that zeros+-- two values:+--+-- > EEPROM.maxAllowedWrites 100+-- > (_, eepromloc1) <- EEPROM.alloc+-- > (_, eepromloc2) <- EEPROM.alloc+-- > eepromloc1 =: constant (0 :: ADC) :@ firstIteration+-- > eepromloc2 =: constant (0 :: Word16) :@ firstIteration+-- +-- `alloc` can be used as many times as you want, storing multiple values+-- in the EEPROM, up to the limit of the size of the EEPROM.+-- (Which is not statically checked.)+--+-- Do note that the EEPROM layout is controlled by the order of calls to+-- `alloc`, so take care when reordering or deleting calls.+alloc :: forall t. (EEPROMable t) => Sketch (Behavior t, Location t)+alloc = alloc' (factoryValue (Proxy @t))++-- | Same as `alloc'`, but with a value which will be used+-- as the EEPROM's boot value when interpreting the Sketch,+-- instead of the default of acting as if all bits of the EEPROM are+-- set.+alloc' :: forall t. (EEPROMable t) => t -> Sketch (Behavior t, Location t)+alloc' interpretval = do+ i <- getUniqueId+ let addrvarname = "eeprom_address" <> show i+ let bootvarname = "eeprom_boot_val" <> show i+ let writername = "eeprom_write" <> show i+ let proxy = Proxy @t+ bootval <- mkInput $ InputSource+ { defineVar = + [ includeCLine+ , CLine $ "int " <> addrvarname <> ";"+ , CLine $ showCType proxy <> " " <> bootvarname <> ";"+ , CLine $ "void " <> writername+ <> "(" <> showCType proxy <> " value) {"+ , CLine $ " EEPROM." <> writeValue proxy+ <> "(" <> addrvarname <> ", value);"+ , CLine "}"+ ]+ , setupInput =+ [ CLine $ addrvarname <> + " = EEPROM.getAddress(sizeof(" + <> showCType proxy <> "));"+ , CLine $ bootvarname <>+ " = EEPROM."+ <> readValue proxy <> "(" <> addrvarname <> ");"+ ]+ , readInput = []+ , inputStream = extern bootvarname (Just (repeat interpretval))+ , inputPinmode = mempty+ }+ return (bootval, Location writername)++data Location t = Location String++instance EEPROMable t => Output (Location t) (Event () (Stream t)) where+ Location writername =: (Event v c) =+ tell [(trigger writername c [arg v], mempty)]++-- | A range of values in the EEPROM.+data Range t = Range+ { rangeStart :: Location (Range t)+ , rangeSize :: Word16 -- ^ number of `t` values in the Range+ }++-- | Allocates a Range in the EEPROM, which stores the specified number of+-- items of some type.+--+-- This is an example of using a range of the EEPROM as a ring buffer,+-- to store the last 100 values read from a1.+--+-- > EEPROM.maxAllowedWrites 1000+-- > range <- EEPROM.allocRange 100 :: Sketch (EEPROM.Range ADC)+-- > currval <- input a1 :: Sketch (Behavior ADC)+-- > range =: EEPROM.sweepRange 0 currval+-- > delay =: MilliSeconds (constant 10000)+--+-- `allocRange` can be used as many times as you want, and combined with+-- uses of `alloc`, up to the size of the EEPROM. +-- (Which is not statically checked.)+--+-- Do note that the EEPROM layout is controlled by the order of calls to+-- `allocRange` and `alloc`, so take care when reordering or deleting calls.+allocRange :: forall t. (EEPROMable t) => Word16 -> Sketch (Range t)+allocRange sz = do+ i <- getUniqueId+ let startaddrvarname = "eeprom_range_address" <> show i+ let writername = "eeprom_range_write" <> show i+ let proxy = Proxy @t+ let f = Framework+ { defines = + [ includeCLine+ , CLine $ "int " <> startaddrvarname <> ";"+ , CLine $ "void " <> writername+ <> "(" <> showCType proxy <> " value"+ <> ", " <> showCType (Proxy @Word16) <> " offset"+ <> ") {"+ , CLine $ " EEPROM." <> writeValue proxy+ <> "(" <> startaddrvarname + <> " + offset*sizeof(" + <> showCType proxy+ <> ")"+ <> ", value);"+ , CLine "}"+ ]+ , setups = + [ CLine $ startaddrvarname <> " = EEPROM.getAddress"+ <> "(sizeof(" <> showCType proxy <> ")" + <> " * " <> show sz+ <> ");"+ ]+ , earlySetups = []+ , pinmodes = mempty+ , loops = mempty+ }+ tell [(return (), f)]+ return (Range (Location writername) sz)++-- | Description of writes made to a Range.+--+-- This can be turned into an `Event` by using `@:` with it, and that's+-- what the Behavior Bool is needed for. Consider this example,+-- that ignores the Behavior Bool, and just uses a counter for the+-- `RangeIndex`:+--+-- > range =: EEPROM.RangeWrites (\_ -> counter) (constant 0) @: blinking+--+-- The use of `@:` `blinking` makes an `Event` that only occurs on every other+-- iteration of the Sketch. But, the counter increases on every+-- iteration of the Sketch. So that will only write to every other value+-- in the `Range`.+--+-- The Behavior Bool is only True when an event occurs, and so+-- it can be used to avoid incrementing the counter otherwise. See+--`sweepRange'` for an example.+data RangeWrites t = RangeWrites+ (Behavior Bool -> Behavior RangeIndex) + (Behavior t)++-- | An index into a Range. 0 is the first value in the Range.+--+-- Indexes larger than the size of the Range will not overflow it,+-- instead they loop back to the start of the Range.+type RangeIndex = Word16++instance EEPROMable t => Output (Range t) (RangeWrites t) where+ (=:) = writeRange true++instance EEPROMable t => Output (Range t) (Event () (RangeWrites t)) where+ range =: Event ws c = + writeRange c range ws++instance EEPROMable t => IsBehavior (RangeWrites t) where+ (@:) = Event++type instance BehaviorToEvent (RangeWrites t) = Event () (RangeWrites t)++writeRange :: EEPROMable t => Behavior Bool -> Range t -> RangeWrites t -> Sketch()+writeRange c range (RangeWrites idx v) = + tell [(trigger writername c [arg idx', arg v], mempty)]+ where+ Location writername = rangeStart range+ idx' = idx c `mod` constant (rangeSize range)++-- | Treat the range as a ring buffer, and starting with the specified+-- `RangeIndex`, sweep over the range writing values from the+-- `Behavior`.+sweepRange :: RangeIndex -> Behavior t -> RangeWrites t+sweepRange start = RangeWrites (sweepRange' start)++-- | This is actually just a simple counter that increments on each write+-- to the range. That's sufficient, because writes that overflow the+-- end of the range wrap back to the start.+sweepRange' :: RangeIndex -> Behavior Bool -> Behavior RangeIndex+sweepRange' start c = cnt+ where+ cnt = [start] ++ rest+ rest = if c then cnt + 1 else cnt++class (ShowCType t, Typed t) => EEPROMable t where+ readValue :: Proxy t -> String+ writeValue :: Proxy t -> String+ factoryValue :: Proxy t -> t+ -- ^ The EEPROM comes from the factory with all bits set,+ -- this follows suite. Eg, maxBound for unsigned ints,+ -- minBound for signed ints since the sign bit being set+ -- makes it negative, and NaN for floats.++-- | This instance is not efficient; a whole byte is read/written+-- rather than a single bit.+instance EEPROMable Bool where+ readValue _ = "readByte"+ writeValue _ = "updateByte"+ factoryValue _ = True++instance EEPROMable Int8 where+ readValue _ = "readByte"+ writeValue _ = "updateByte"+ factoryValue _ = minBound++instance EEPROMable Int16 where+ readValue _ = "readInt"+ writeValue _ = "updateInt"+ factoryValue _= minBound++instance EEPROMable Int32 where+ readValue _ = "readLong"+ writeValue _ = "updateLong"+ factoryValue _= minBound++instance EEPROMable Word8 where+ readValue _ = "readByte"+ writeValue _ = "updateByte"+ factoryValue _ = maxBound++instance EEPROMable Word16 where+ readValue _ = "readInt"+ writeValue _ = "updateInt"+ factoryValue _ = maxBound++instance EEPROMable Word32 where+ readValue _ = "readLong"+ writeValue _ = "updateLong"+ factoryValue _ = maxBound++instance EEPROMable Float where+ readValue _ = "readFloat"+ writeValue _ = "updateFloat"+ factoryValue _ = 0/0 -- NaN++instance EEPROMable Double where+ readValue _ = "readDouble"+ writeValue _ = "updateDOuble"+ factoryValue _ = 0/0 -- NaN++includeCLine :: CLine+includeCLine = CLine "#include <EEPROMex.h>"
src/Copilot/Arduino/Library/Serial.hs view
@@ -10,13 +10,14 @@ device, char, str,+ FlashString(..), show, showFormatted, byte, noInput, SerialDevice, FormatOutput,- ShowableType,+ OutputString, FormatableType, Base(..), ) where@@ -40,7 +41,7 @@ -- that describes the serial output. Note that you can only do this once -- in a Sketch. ----- > main = arduino $ doa+-- > main = arduino $ do -- > Serial.baud 9600 -- > b <- input pin4 -- > Serial.device =:
src/Copilot/Arduino/Library/Serial/Device.hs view
@@ -75,7 +75,7 @@ , readInput = [CLine $ varname <> " = " <> devname <> ".read();"] , inputStream = extern varname interpretvalues' }- varname = "arduino_serial_" <> devname <> "_read"+ varname = "input_" <> devname interpretvalues' | null interpretvalues = Nothing | otherwise = Just interpretvalues@@ -94,7 +94,7 @@ go = trigger triggername c (mapMaybe formatArg l) f = mempty { defines = printer } - triggername = "arduino_serial_" <> devname <> "_output"+ triggername = "output_" <> devname printer = concat [ [CLine $ "void " <> triggername <> "("@@ -120,7 +120,7 @@ , formatCLine :: SerialDeviceName -> String -> CLine } --- | Use this to output a Char+-- | Use this to output a Char. char :: Char -> FormatOutput char c = FormatOutput Nothing Nothing (\(SerialDeviceName devname) _ ->@@ -131,21 +131,37 @@ esc '\n' = "\\n" esc c' = [c'] --- | Use this to output a String-str :: String -> FormatOutput-str s = FormatOutput Nothing Nothing- (\(SerialDeviceName devname) _ ->- CLine $ devname <> ".print(\"" <> concatMap esc s <> "\");")+quoteString :: String -> String+quoteString s = '"' : concatMap esc s <> "\"" where esc '"' = "\"" esc '\\' = "\\" esc '\n' = "\\\n" esc c = [c] +class OutputString t where+ -- | Use this to output a `String` or `FlashString`+ str :: t -> FormatOutput++instance OutputString String where+ str s = FormatOutput Nothing Nothing $ \(SerialDeviceName devname) _ ->+ CLine $ devname <> ".print(" <> quoteString s <> ");"++-- | Normally a String will be copied into ram before it is output.+-- A FlashString will be output directly from flash memory.+--+-- Using this with `str` will reduce the amount of memory used by your+-- program, but will likely slightly increase the size of the program.+newtype FlashString = FlashString String++instance OutputString FlashString where+ str (FlashString s) = FormatOutput Nothing Nothing $ \(SerialDeviceName devname) _ ->+ CLine $ devname <> ".print(F(" <> quoteString s <> "));"+ -- | 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 :: forall t. (ShowCType t, Typed t) => Stream t -> FormatOutput show s = FormatOutput (Just (arg s)) (Just (showCType (Proxy @t)))@@ -171,7 +187,7 @@ -- -- > Serial.showFormatted (constant (78 :: Int8)) Serial.HEX -- "4E" showFormatted- :: forall t f. (ShowableType t, Typed t, FormatableType t f)+ :: forall t f. (ShowCType t, Typed t, FormatableType t f) => Stream t -> f -> FormatOutput@@ -182,21 +198,6 @@ CLine $ devname <> ".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
src/Copilot/Arduino/Library/Serial/XBee.hs view
@@ -11,13 +11,14 @@ device, char, str,+ FlashString(..), show, showFormatted, byte, noInput, SerialDevice, FormatOutput,- ShowableType,+ OutputString, FormatableType, Base(..), ) where@@ -48,7 +49,7 @@ -- that describes the serial output. Note that you can only do this once -- in a Sketch. ----- > main = arduino $ doa+-- > main = arduino $ do -- > XBee.configure pin2 pin3 (XBee.Baud 9600) -- > b <- input pin4 -- > XBee.device =:
src/Copilot/Arduino/Main.hs view
@@ -5,10 +5,11 @@ import Language.Copilot (Spec, interpret, reify) import Copilot.Compile.C99 (compile) import Copilot.Arduino.Internals+import System.IO import System.Directory import System.Posix.Temp (mkdtemp) import System.FilePath-import Control.Monad.Writer+import System.Exit import Data.List (isInfixOf) import Data.Maybe import qualified Data.Map as M@@ -41,23 +42,25 @@ -- > (100) (13,false) -- > (100) (13,true) arduino :: Sketch () -> IO ()-arduino (Sketch s) = go =<< execParser opts+arduino s = go =<< execParser opts where opts = info (parseCmdLine <**> helper) ( fullDesc <> progDesc "Run this program with no options to generate an Arduino sketch." ) - go o = case interpretSteps o of- Just n -> interpret n spec- Nothing -> writeIno spec f+ go o = case (mspec, interpretSteps o) of+ (Nothing, _) -> do+ hPutStrLn stderr "This Sketch does not do anything."+ exitFailure+ (Just spec, Just n) -> interpret n spec+ (Just spec, Nothing) -> writeIno spec f' - (is, fs) = unzip (execWriter s)- spec = sequence_ is+ (mspec, f) = evalSketch s -- Strict evaluation because this may throw errors, -- and we want to throw them before starting compilation.- !f = finalizeFramework (mconcat fs)+ !f' = finalizeFramework f data CmdLine = CmdLine { interpretSteps :: Maybe Integer@@ -111,7 +114,7 @@ , [ "void setup()" ]- , codeblock $ map fromCLine (setups f)+ , codeblock $ map fromCLine (earlySetups f <> setups f) , [blank] , [ "void loop()"
src/Copilot/Arduino/Nano.hs view
@@ -25,6 +25,7 @@ , a5 , a6 , a7+ , sizeOfEEPROM ) where import Copilot.Arduino@@ -58,3 +59,8 @@ a6, a7 :: Pin '[ 'AnalogInput ] a6 = Pin (PinId 20) a7 = Pin (PinId 21)++-- | Different versions of Arduino Nano have different EEPROM sizes.+-- This is a safe value that will work on all versions.+sizeOfEEPROM :: Word16+sizeOfEEPROM = 512
src/Copilot/Arduino/Uno.hs view
@@ -23,6 +23,7 @@ , a3 , a4 , a5+ , sizeOfEEPROM ) where import Copilot.Arduino@@ -51,3 +52,6 @@ a3 = Pin (PinId 17) a4 = Pin (PinId 18) a5 = Pin (PinId 19)++sizeOfEEPROM :: Word16+sizeOfEEPROM = 512