packages feed

hArduino 0.3 → 0.4

raw patch · 24 files changed

+1352/−587 lines, 24 filesdep +timedep −processdep ~serialportPVP ok

version bump matches the API change (PVP)

Dependencies added: time

Dependencies removed: process

Dependency ranges changed: serialport

API changes (from Hackage documentation)

- System.Hardware.Arduino.LCD: Hitachi44780 :: Pin -> Pin -> Pin -> Pin -> Pin -> Pin -> Int -> Int -> Bool -> LCDController
- System.Hardware.Arduino.LCD: data LCDController
- System.Hardware.Arduino.LCD: data LCDSymbol
- System.Hardware.Arduino.LCD: dotMode5x10 :: LCDController -> Bool
- System.Hardware.Arduino.LCD: lcdAutoScrollOff :: LCD -> Arduino ()
- System.Hardware.Arduino.LCD: lcdAutoScrollOn :: LCD -> Arduino ()
- System.Hardware.Arduino.LCD: lcdBlinkOff :: LCD -> Arduino ()
- System.Hardware.Arduino.LCD: lcdBlinkOn :: LCD -> Arduino ()
- System.Hardware.Arduino.LCD: lcdClear :: LCD -> Arduino ()
- System.Hardware.Arduino.LCD: lcdCols :: LCDController -> Int
- System.Hardware.Arduino.LCD: lcdCreateSymbol :: LCD -> [String] -> Arduino LCDSymbol
- System.Hardware.Arduino.LCD: lcdCursorOff :: LCD -> Arduino ()
- System.Hardware.Arduino.LCD: lcdCursorOn :: LCD -> Arduino ()
- System.Hardware.Arduino.LCD: lcdD4 :: LCDController -> Pin
- System.Hardware.Arduino.LCD: lcdD5 :: LCDController -> Pin
- System.Hardware.Arduino.LCD: lcdD6 :: LCDController -> Pin
- System.Hardware.Arduino.LCD: lcdD7 :: LCDController -> Pin
- System.Hardware.Arduino.LCD: lcdDisplayOff :: LCD -> Arduino ()
- System.Hardware.Arduino.LCD: lcdDisplayOn :: LCD -> Arduino ()
- System.Hardware.Arduino.LCD: lcdEN :: LCDController -> Pin
- System.Hardware.Arduino.LCD: lcdFlash :: LCD -> Int -> Int -> Arduino ()
- System.Hardware.Arduino.LCD: lcdHome :: LCD -> Arduino ()
- System.Hardware.Arduino.LCD: lcdInternalSymbol :: Word8 -> LCDSymbol
- System.Hardware.Arduino.LCD: lcdLeftToRight :: LCD -> Arduino ()
- System.Hardware.Arduino.LCD: lcdRS :: LCDController -> Pin
- System.Hardware.Arduino.LCD: lcdRegister :: LCDController -> Arduino LCD
- System.Hardware.Arduino.LCD: lcdRightToLeft :: LCD -> Arduino ()
- System.Hardware.Arduino.LCD: lcdRows :: LCDController -> Int
- System.Hardware.Arduino.LCD: lcdScrollDisplayLeft :: LCD -> Arduino ()
- System.Hardware.Arduino.LCD: lcdScrollDisplayRight :: LCD -> Arduino ()
- System.Hardware.Arduino.LCD: lcdSetCursor :: LCD -> (Int, Int) -> Arduino ()
- System.Hardware.Arduino.LCD: lcdWrite :: LCD -> String -> Arduino ()
- System.Hardware.Arduino.LCD: lcdWriteSymbol :: LCD -> LCDSymbol -> Arduino ()
+ System.Hardware.Arduino: analog :: Word8 -> Pin
+ System.Hardware.Arduino: digital :: Word8 -> Pin
+ System.Hardware.Arduino: pulse :: Pin -> Bool -> Int -> Maybe Int -> Arduino (Maybe Int)
+ System.Hardware.Arduino: pulseIn_hostTiming :: Pin -> Bool -> Maybe Int -> Arduino (Maybe Int)
+ System.Hardware.Arduino: pulseOut_hostTiming :: Pin -> Bool -> Int -> Int -> Arduino ()
+ System.Hardware.Arduino: time :: Arduino a -> Arduino (Int, a)
+ System.Hardware.Arduino: timeOut :: Int -> Arduino a -> Arduino (Maybe a)
+ System.Hardware.Arduino.Parts.LCD: Hitachi44780 :: Pin -> Pin -> Pin -> Pin -> Pin -> Pin -> Int -> Int -> Bool -> LCDController
+ System.Hardware.Arduino.Parts.LCD: data LCDController
+ System.Hardware.Arduino.Parts.LCD: data LCDSymbol
+ System.Hardware.Arduino.Parts.LCD: dotMode5x10 :: LCDController -> Bool
+ System.Hardware.Arduino.Parts.LCD: lcdAutoScrollOff :: LCD -> Arduino ()
+ System.Hardware.Arduino.Parts.LCD: lcdAutoScrollOn :: LCD -> Arduino ()
+ System.Hardware.Arduino.Parts.LCD: lcdBlinkOff :: LCD -> Arduino ()
+ System.Hardware.Arduino.Parts.LCD: lcdBlinkOn :: LCD -> Arduino ()
+ System.Hardware.Arduino.Parts.LCD: lcdClear :: LCD -> Arduino ()
+ System.Hardware.Arduino.Parts.LCD: lcdCols :: LCDController -> Int
+ System.Hardware.Arduino.Parts.LCD: lcdCreateSymbol :: LCD -> [String] -> Arduino LCDSymbol
+ System.Hardware.Arduino.Parts.LCD: lcdCursorOff :: LCD -> Arduino ()
+ System.Hardware.Arduino.Parts.LCD: lcdCursorOn :: LCD -> Arduino ()
+ System.Hardware.Arduino.Parts.LCD: lcdD4 :: LCDController -> Pin
+ System.Hardware.Arduino.Parts.LCD: lcdD5 :: LCDController -> Pin
+ System.Hardware.Arduino.Parts.LCD: lcdD6 :: LCDController -> Pin
+ System.Hardware.Arduino.Parts.LCD: lcdD7 :: LCDController -> Pin
+ System.Hardware.Arduino.Parts.LCD: lcdDisplayOff :: LCD -> Arduino ()
+ System.Hardware.Arduino.Parts.LCD: lcdDisplayOn :: LCD -> Arduino ()
+ System.Hardware.Arduino.Parts.LCD: lcdEN :: LCDController -> Pin
+ System.Hardware.Arduino.Parts.LCD: lcdFlash :: LCD -> Int -> Int -> Arduino ()
+ System.Hardware.Arduino.Parts.LCD: lcdHome :: LCD -> Arduino ()
+ System.Hardware.Arduino.Parts.LCD: lcdInternalSymbol :: Word8 -> LCDSymbol
+ System.Hardware.Arduino.Parts.LCD: lcdLeftToRight :: LCD -> Arduino ()
+ System.Hardware.Arduino.Parts.LCD: lcdRS :: LCDController -> Pin
+ System.Hardware.Arduino.Parts.LCD: lcdRegister :: LCDController -> Arduino LCD
+ System.Hardware.Arduino.Parts.LCD: lcdRightToLeft :: LCD -> Arduino ()
+ System.Hardware.Arduino.Parts.LCD: lcdRows :: LCDController -> Int
+ System.Hardware.Arduino.Parts.LCD: lcdScrollDisplayLeft :: LCD -> Arduino ()
+ System.Hardware.Arduino.Parts.LCD: lcdScrollDisplayRight :: LCD -> Arduino ()
+ System.Hardware.Arduino.Parts.LCD: lcdSetCursor :: LCD -> (Int, Int) -> Arduino ()
+ System.Hardware.Arduino.Parts.LCD: lcdWrite :: LCD -> String -> Arduino ()
+ System.Hardware.Arduino.Parts.LCD: lcdWriteSymbol :: LCD -> LCDSymbol -> Arduino ()
+ System.Hardware.Arduino.Parts.Servo: attach :: Pin -> Maybe Int -> Maybe Int -> Arduino Servo
+ System.Hardware.Arduino.Parts.Servo: data Servo
+ System.Hardware.Arduino.Parts.Servo: setAngle :: Servo -> Int -> Arduino ()
+ System.Hardware.Arduino.Parts.SevenSegmentCodes: char2SS :: Char -> Maybe Word8
+ System.Hardware.Arduino.Parts.ShiftRegisters: SR_74HC595 :: Pin -> Pin -> Pin -> Pin -> Pin -> Maybe [Pin] -> SR_74HC595
+ System.Hardware.Arduino.Parts.ShiftRegisters: class ShiftRegister a
+ System.Hardware.Arduino.Parts.ShiftRegisters: clear :: ShiftRegister a => a -> Arduino ()
+ System.Hardware.Arduino.Parts.ShiftRegisters: data SR_74HC595
+ System.Hardware.Arduino.Parts.ShiftRegisters: dataSheet :: ShiftRegister a => a -> String
+ System.Hardware.Arduino.Parts.ShiftRegisters: disable :: ShiftRegister a => a -> Arduino ()
+ System.Hardware.Arduino.Parts.ShiftRegisters: enable :: ShiftRegister a => a -> Arduino ()
+ System.Hardware.Arduino.Parts.ShiftRegisters: initialize :: ShiftRegister a => a -> Arduino ()
+ System.Hardware.Arduino.Parts.ShiftRegisters: instance ShiftRegister SR_74HC595
+ System.Hardware.Arduino.Parts.ShiftRegisters: mbBits :: SR_74HC595 -> Maybe [Pin]
+ System.Hardware.Arduino.Parts.ShiftRegisters: nClear :: SR_74HC595 -> Pin
+ System.Hardware.Arduino.Parts.ShiftRegisters: nEnable :: SR_74HC595 -> Pin
+ System.Hardware.Arduino.Parts.ShiftRegisters: name :: ShiftRegister a => a -> String
+ System.Hardware.Arduino.Parts.ShiftRegisters: push :: ShiftRegister a => a -> Bool -> Arduino ()
+ System.Hardware.Arduino.Parts.ShiftRegisters: rClock :: SR_74HC595 -> Pin
+ System.Hardware.Arduino.Parts.ShiftRegisters: read :: ShiftRegister a => a -> Arduino [Bool]
+ System.Hardware.Arduino.Parts.ShiftRegisters: sClock :: SR_74HC595 -> Pin
+ System.Hardware.Arduino.Parts.ShiftRegisters: serial :: SR_74HC595 -> Pin
+ System.Hardware.Arduino.Parts.ShiftRegisters: size :: ShiftRegister a => a -> Int
+ System.Hardware.Arduino.Parts.ShiftRegisters: store :: ShiftRegister a => a -> Arduino ()
+ System.Hardware.Arduino.SamplePrograms.Distance: distance :: IO ()
+ System.Hardware.Arduino.SamplePrograms.Distance: microSecondsToCentimeters :: Int -> Float
+ System.Hardware.Arduino.SamplePrograms.PulseIn: pulseDemo :: IO ()
+ System.Hardware.Arduino.SamplePrograms.PulseOut: pulseDemo :: IO ()
+ System.Hardware.Arduino.SamplePrograms.Servo: servo :: IO ()
+ System.Hardware.Arduino.SamplePrograms.Servo: servoAnalog :: IO ()
+ System.Hardware.Arduino.SamplePrograms.SevenSegment: sevenSegment :: IO ()
+ System.Hardware.Arduino.SamplePrograms.SevenSegment: sr :: SR_74HC595

Files

CHANGES.md view
@@ -1,8 +1,38 @@ * Hackage: (http://hackage.haskell.org/package/hArduino) * GitHub:  (http://leventerkok.github.com/hArduino) -* Latest Hackage released version: 0.3+* Latest Hackage released version: 0.4 +### Version 0.4, 2013-03-05+ + * New hardware components supported:+     * Shift-registers+     * Seven-segment displays+     * Servo-motors+ * New examples:+    * PulseIn: Demonstrates the use of reading pulses+    * PulseOut: Demonstrates the use of sending pulses+    * Distance: Measure the distance using an HC-SC04 sensor+    * Seven-segment: Display characters on a seven-segment display+    * Servo: Control a servo board+ * New functions:+     * pulseIn_hostTiming/pulseOut_hostTiming: Send and receive pulses.+         * NB. These functions use host-timing: watch out for accuracy+     * pulse: Send and receive a digital pulse on a pin.+         * This function is more accurate than the pair above, as+	   it uses a custom Firmata command to measure the pulse.+	   However, you need a custom Firmata version to use this+	   function, as the standard version that ships with Arduino+	   as of March 2013 does not support this functionality yet.+     * time: Measure the time taken by an Arduino action+     * timeOut: Run an action only for the given-time-out+ * Other:+    * Bugfix: Remove spurious extra call to user program+    * Rework pin assignment logic, making use of analog/digital pins much more clearer.+    * Better exception handling+    * Remove threadDelay workaround on the Mac. NB. If you are running on OSX, then+      you need at least GHC 7.6.2!+ ### Version 0.3, 2013-02-10   * Library@@ -12,19 +42,22 @@     * Add support for LCDs (based on the Hitachi 44780 chip)     * Better handling for Ctrl-C interrupts  * Examples-    * Add counter: use push buttons to count up and down-    * Add analog-reading example-    * Add LCD controller example-    * Add wiring schematics for all sample programs+    * Counter: Use push buttons to count up and down+    * Analog: Reading analog values+    * LCD: Control an LCD, writing text/glyphs etc  ### Version 0.2, 2013-01-28 - * Rewrite the communication engine- * Digital input/output implementation- * Add switch example+ * Library+    * Rewrite the communication engine+    * Digital input/output implementation+ * Examples+    * Button: Detecting putton pushes  ### Version 0.1, 2013-01-14 - * Initial design- * Blink example operational- * Created home page at: http://leventerkok.github.com/hArduino + * Library+    * Initial design+    * Created home page at: http://leventerkok.github.com/hArduino + * Examples+    * Blink: Hello world!
System/Hardware/Arduino.hs view
@@ -28,19 +28,23 @@   -- ** Basic handshake with the board   , queryFirmware   -- ** Accessing pins-  , pin, Pin, PinMode(..), setPinMode+  , analog, digital, pin, Pin, PinMode(..), setPinMode   -- ** Digital I/O   -- *** Writing digital values   , digitalWrite   -- *** Reading digital values   , digitalRead,  pullUpResistor, waitFor, waitAny, waitAnyHigh, waitAnyLow+  -- *** Receiving and sending pulses, host timing+  , pulseIn_hostTiming, pulseOut_hostTiming+  -- ** Pulse trigger and measurement+  , pulse   -- ** Analog Communication   -- *** Setting up sampling interval   , setAnalogSamplingInterval   -- *** Reading analog values   , analogRead   -- * Misc utilities-  , delay+  , delay, time, timeOut  )  where 
System/Hardware/Arduino/Comm.hs view
@@ -18,8 +18,9 @@ import Control.Exception    (tryJust, AsyncException(UserInterrupt), handle, SomeException) import Control.Monad.State  (runStateT, gets, liftIO, modify) import Data.Bits            (testBit, (.&.))-import Data.List            (intercalate)+import Data.List            (intercalate, isInfixOf) import Data.Maybe           (listToMaybe)+import Data.Word            (Word8) import System.Posix.Signals (installHandler, keyboardSignal, Handler(Catch)) import System.Timeout       (timeout) import System.IO            (stderr, hPutStrLn)@@ -59,12 +60,14 @@                                        if initOK                                           then program                                           else error "Communication time-out (5s) expired."-                                       program            handle (\(e::SomeException) -> do cleanUp listenerTid-                                             hPutStrLn stderr $ "*** hArduino:ERROR: " ++ show e-                                                              ++ concatMap ("\n*** " ++) [ "Make sure your Arduino is connected to " ++ fp-                                                                                         , "And StandardFirmata is running on it!"-                                                                                         ]) $+                                             let selfErr = "*** hArduino" `isInfixOf` show e+                                             hPutStrLn stderr $ if selfErr+                                                                then dropWhile (== '\n') (show e)+                                                                else "*** hArduino:ERROR: " ++ show e+                                                                     ++ concatMap ("\n*** " ++) [ "Make sure your Arduino is connected to " ++ fp+                                                                                                , "And StandardFirmata is running on it!"+                                                                                                ]) $              S.withSerial fp S.defaultSerialSettings{S.commSpeed = S.CS57600} $ \port -> do                 let initBoardState = BoardState {                                          boardCapabilities    = BoardCapabilities M.empty@@ -147,10 +150,11 @@                            Right nonSysEx    -> unpackageNonSysEx getBytes nonSysEx                 case resp of                   Unimplemented{}      -> dbg $ "Ignoring the received response: " ++ show resp+                  -- NB. When Firmata sends back AnalogMessage, it uses the number in A0-A1-A2, etc., i.e., 0-1-2; which we+                  -- need to properly interpret in our own pin mapping schema, where analogs come after digitals.                   AnalogMessage mp l h -> modifyMVar_ bs $ \bst ->-                                           do -- the mp is indexed at 0; need to find the mapping-                                              let BoardCapabilities caps = boardCapabilities bst-                                                  mbP = listToMaybe [mappedPin | (mappedPin, (Just mp', _)) <- M.assocs caps, pinNo mp == mp']+                                           do let BoardCapabilities caps = boardCapabilities bst+                                                  mbP = listToMaybe [mappedPin | (mappedPin, PinCapabilities{analogPinNumber = Just mp'}) <- M.assocs caps, pinNo mp == mp']                                               case mbP of                                                 Nothing -> return bst -- Mapping hasn't happened yet                                                 Just p  -> do@@ -229,11 +233,12 @@                                         else do liftIO $ dbg $ "Skipping unexpected response: " ++ show resp                                                 wait            wait-       mapAnalog bs p c+       mapAnalog :: [Word8] -> IPin -> PinCapabilities -> PinCapabilities+       mapAnalog as p c           | i < rl && m /= 0x7f-          = (Just m, snd c)+          = c{analogPinNumber = Just m}           | True             -- out-of-bounds, or not analog; ignore           = c-         where rl = length bs+         where rl = length as                i  = fromIntegral (pinNo p)-               m  = bs !! i+               m  = as !! i
System/Hardware/Arduino/Data.hs view
@@ -17,11 +17,12 @@  import Control.Applicative        (Applicative) import Control.Concurrent         (Chan, MVar, modifyMVar, modifyMVar_, withMVar, ThreadId)+import Control.Monad              (when) import Control.Monad.State        (StateT, MonadIO, MonadState, gets, liftIO) import Data.Bits                  ((.&.), (.|.), setBit) import Data.List                  (intercalate)-import Data.Maybe                 (fromMaybe)-import Data.Word                  (Word8)+import Data.Maybe                 (fromMaybe, listToMaybe)+import Data.Word                  (Word8, Word32) import System.Hardware.Serialport (SerialPort)  import qualified Data.Map as M@@ -37,28 +38,58 @@ instance Show Port where   show p = "Port" ++ show (portNo p) --- | A pin on the Arduino-data Pin = Pin { pinNo :: Word8   -- ^ The pin number-               }-         deriving (Eq, Ord)+-- | A pin on the Arduino, as specified by the user via 'pin', 'digital', and 'analog' functions.+data Pin = DigitalPin {userPinNo :: Word8}+         | AnalogPin  {userPinNo :: Word8}+         | MixedPin   {userPinNo :: Word8}  instance Show Pin where-  show p | i < 10 = "Pin0" ++ show i-         | True   = "Pin"  ++ show i-   where i = pinNo p+  show (DigitalPin w) = "DPin" ++ show w+  show (AnalogPin  w) = "APin" ++ show w+  show (MixedPin   w) = "Pin"  ++ show w --- | Declare a pin on the board by its number.+-- | A pin on the Arduino, as viewed by the library; i.e., real-pin numbers+data IPin = InternalPin { pinNo :: Word8 }+          deriving (Eq, Ord)++instance Show IPin where+  show (InternalPin w) = "IPin" ++ show w++-- | Declare a pin by its index. For maximum portability, prefer 'digital'+-- and 'analog' functions, which will adjust pin indexes properly based on+-- which board the program is running on at run-time, as Arduino boards+-- differ in their pin numbers. This function is provided for cases where+-- a pin is used in mixed-mode, i.e., both for digital and analog purposes,+-- as Arduino does not really distinguish pin usage. In these cases, the+-- user has the proof obligation to make sure that the index used is supported+-- on the board with appropriate capabilities. pin :: Word8 -> Pin-pin = Pin+pin = MixedPin +-- | Declare an digital pin on the board. For instance, to refer to digital pin no 12+-- use 'digital' @12@.+digital :: Word8 -> Pin+digital = DigitalPin++-- | Declare an analog pin on the board. For instance, to refer to analog pin no 0+-- simply use 'analog' @0@.+--+-- Note that 'analog' @0@ on an Arduino UNO will be appropriately adjusted+-- internally to refer to pin 14, since UNO has 13 digital pins, while on an+-- Arduino MEGA, it will refer to internal pin 55, since MEGA has 54 digital pins;+-- and similarly for other boards depending on their capabilities.+-- (Also see the note on 'pin' for pin mappings.)+analog :: Word8 -> Pin+analog = AnalogPin+ -- | On the Arduino, pins are grouped into banks of 8. -- Given a pin, this function determines which port it belongs to-pinPort :: Pin -> Port+pinPort :: IPin -> Port pinPort p = Port (pinNo p `quot` 8)  -- | On the Arduino, pins are grouped into banks of 8. -- Given a pin, this function determines which index it belongs to in its port-pinPortIndex :: Pin -> Word8+pinPortIndex :: IPin -> Word8 pinPortIndex p = pinNo p `rem` 8  -- | The mode for a pin.@@ -72,15 +103,17 @@              deriving (Eq, Show, Enum)  -- | A request, as sent to Arduino-data Request = SystemReset                          -- ^ Send system reset-             | QueryFirmware                        -- ^ Query the Firmata version installed-             | CapabilityQuery                      -- ^ Query the capabilities of the board-             | AnalogMappingQuery                   -- ^ Query the mapping of analog pins-             | SetPinMode         Pin  PinMode      -- ^ Set the mode on a pin-             | DigitalReport      Port Bool         -- ^ Digital report values on port enable/disable-             | AnalogReport       Pin  Bool         -- ^ Analog report values on pin enable/disable-             | DigitalPortWrite   Port Word8 Word8  -- ^ Set the values on a port digitally-             | SamplingInterval   Word8 Word8       -- ^ Set the sampling interval+data Request = SystemReset                                -- ^ Send system reset+             | QueryFirmware                              -- ^ Query the Firmata version installed+             | CapabilityQuery                            -- ^ Query the capabilities of the board+             | AnalogMappingQuery                         -- ^ Query the mapping of analog pins+             | SetPinMode         IPin PinMode            -- ^ Set the mode on a pin+             | DigitalReport      Port Bool               -- ^ Digital report values on port enable/disable+             | AnalogReport       IPin Bool               -- ^ Analog report values on pin enable/disable+             | DigitalPortWrite   Port Word8 Word8        -- ^ Set the values on a port digitally+             | AnalogPinWrite     IPin  Word8 Word8       -- ^ Send an analog-write; used for servo control+             | SamplingInterval   Word8 Word8             -- ^ Set the sampling interval+             | Pulse              IPin Bool Word32 Word32 -- ^ Request for a pulse reading on a pin, value, duration, timeout              deriving Show  -- | A response, as returned from the Arduino@@ -88,7 +121,8 @@               | Capabilities BoardCapabilities       -- ^ Capabilities report               | AnalogMapping [Word8]                -- ^ Analog pin mappings               | DigitalMessage Port Word8 Word8      -- ^ Status of a port-              | AnalogMessage  Pin  Word8 Word8      -- ^ Status of an analog pin+              | AnalogMessage  IPin Word8 Word8      -- ^ Status of an analog pin+              | PulseResponse  IPin Word32           -- ^ Repsonse to a PulseInCommand               | Unimplemented (Maybe String) [Word8] -- ^ Represents messages currently unsupported  instance Show Response where@@ -97,6 +131,7 @@   show (AnalogMapping bs)      = "AnalogMapping: " ++ showByteList bs   show (DigitalMessage p l h)  = "DigitalMessage " ++ show p ++ " = " ++ showByte l ++ " " ++ showByte h   show (AnalogMessage  p l h)  = "AnalogMessage "  ++ show p ++ " = " ++ showByte l ++ " " ++ showByte h+  show (PulseResponse p v)     = "PulseResponse "  ++ show p ++ " = " ++ show v ++ " (microseconds)"   show (Unimplemented mbc bs)  = "Unimplemeneted " ++ fromMaybe "" mbc ++ " " ++ showByteList bs  -- | Resolution, as referred to in http://firmata.org/wiki/Protocol#Capability_Query@@ -104,17 +139,18 @@ type Resolution = Word8  -- | Capabilities of a pin-type PinCapabilities  = ( Maybe Word8               -- Analog pin number, if any-                        , [(PinMode, Resolution)]-                        )+data PinCapabilities  = PinCapabilities {+                          analogPinNumber :: Maybe Word8              -- ^ Analog pin number, if any+                        , allowedModes    :: [(PinMode, Resolution)]  -- ^ Allowed modes and resolutions+                        }  -- | What the board is capable of and current settings-newtype BoardCapabilities = BoardCapabilities (M.Map Pin PinCapabilities)+newtype BoardCapabilities = BoardCapabilities (M.Map IPin PinCapabilities)  instance Show BoardCapabilities where   show (BoardCapabilities m) = intercalate "\n" (map sh (M.toAscList m))-    where sh (p, (mbA, pc)) = show p ++ sep ++ unwords [show md | (md, _) <- pc]-             where sep = maybe ": " (\i -> "[A" ++ show i ++ "]: ") mbA+    where sh (p, PinCapabilities{analogPinNumber, allowedModes}) = show p ++ sep ++ unwords [show md | (md, _) <- allowedModes]+             where sep = maybe ": " (\i -> "[A" ++ show i ++ "]: ") analogPinNumber  -- | Data associated with a pin data PinData = PinData {@@ -153,12 +189,12 @@  -- | State of the board data BoardState = BoardState {-                    boardCapabilities    :: BoardCapabilities  -- ^ Capabilities of the board-                  , analogReportingPins  :: S.Set Pin          -- ^ Which analog pins are reporting-                  , digitalReportingPins :: S.Set Pin          -- ^ Which digital pins are reporting-                  , pinStates            :: M.Map Pin PinData  -- ^ For-each pin, store its data-                  , digitalWakeUpQueue   :: [MVar ()]          -- ^ Semaphore list to wake-up upon receiving a digital message-                  , lcds                 :: M.Map LCD LCDData  -- ^ LCD's attached to the board+                    boardCapabilities    :: BoardCapabilities   -- ^ Capabilities of the board+                  , analogReportingPins  :: S.Set IPin          -- ^ Which analog pins are reporting+                  , digitalReportingPins :: S.Set IPin          -- ^ Which digital pins are reporting+                  , pinStates            :: M.Map IPin PinData  -- ^ For-each pin, store its data+                  , digitalWakeUpQueue   :: [MVar ()]           -- ^ Semaphore list to wake-up upon receiving a digital message+                  , lcds                 :: M.Map LCD LCDData   -- ^ LCD's attached to the board                   }  -- | State of the computation@@ -188,15 +224,15 @@               liftIO $ f m ms  -- | Which modes does this pin support?-getPinModes :: Pin -> Arduino [PinMode]+getPinModes :: IPin -> Arduino [PinMode] getPinModes p = do   BoardCapabilities caps <- gets capabilities   case p `M.lookup` caps of-    Nothing      -> return []-    Just (_, ps) -> return (map fst ps)+    Nothing                            -> return []+    Just PinCapabilities{allowedModes} -> return $ map fst allowedModes  -- | Current state of the pin-getPinData :: Pin -> Arduino PinData+getPinData :: IPin -> Arduino PinData getPinData p = do   bs  <- gets boardState   err <- gets bailOut@@ -214,7 +250,7 @@ --   * Second msb: pins 7-13 on the port -- -- In particular, the result is suitable to be sent with a digital message-computePortData :: Pin -> Bool -> Arduino (Word8, Word8)+computePortData :: IPin -> Bool -> Arduino (Word8, Word8) computePortData curPin newValue = do   let curPort  = pinPort curPin   let curIndex = pinPortIndex curPin@@ -238,9 +274,9 @@     liftIO $ modifyMVar_ bs $ \bst -> return bst{digitalWakeUpQueue = semaphore : digitalWakeUpQueue bst}  -- | Firmata commands, see: http://firmata.org/wiki/Protocol#Message_Types-data FirmataCmd = ANALOG_MESSAGE      Pin  -- ^ @0xE0@ pin+data FirmataCmd = ANALOG_MESSAGE      IPin -- ^ @0xE0@ pin                 | DIGITAL_MESSAGE     Port -- ^ @0x90@ port-                | REPORT_ANALOG_PIN   Pin  -- ^ @0xC0@ pin+                | REPORT_ANALOG_PIN   IPin -- ^ @0xC0@ pin                 | REPORT_DIGITAL_PORT Port -- ^ @0xD0@ port                 | START_SYSEX              -- ^ @0xF0@                 | SET_PIN_MODE             -- ^ @0xF4@@@ -271,9 +307,9 @@                  | w == 0xF7              = Right END_SYSEX                  | w == 0xF9              = Right PROTOCOL_VERSION                  | w == 0xFF              = Right SYSTEM_RESET-                 | Just i <- extract 0xE0 = Right $ ANALOG_MESSAGE      (Pin i)+                 | Just i <- extract 0xE0 = Right $ ANALOG_MESSAGE      (InternalPin i)                  | Just i <- extract 0x90 = Right $ DIGITAL_MESSAGE     (Port i)-                 | Just i <- extract 0xC0 = Right $ REPORT_ANALOG_PIN   (Pin i)+                 | Just i <- extract 0xC0 = Right $ REPORT_ANALOG_PIN   (InternalPin i)                  | Just i <- extract 0xD0 = Right $ REPORT_DIGITAL_PORT (Port i)                  | True                   = Left w @@ -288,6 +324,7 @@               | EXTENDED_ANALOG         -- ^ @0x6F@  analog write (PWM, Servo, etc) to any pin               | SERVO_CONFIG            -- ^ @0x70@  set max angle, minPulse, maxPulse, freq               | STRING_DATA             -- ^ @0x71@  a string message with 14-bits per char+              | PULSE                   -- ^ @0x74@  Pulse, see: https://github.com/rwldrn/johnny-five/issues/18               | SHIFT_DATA              -- ^ @0x75@  shiftOut config/data message (34 bits)               | I2C_REQUEST             -- ^ @0x76@  I2C request messages from a host to an I/O board               | I2C_REPLY               -- ^ @0x77@  I2C reply messages from an I/O board to a host@@ -310,6 +347,7 @@ sysExCmdVal EXTENDED_ANALOG         = 0x6F sysExCmdVal SERVO_CONFIG            = 0x70 sysExCmdVal STRING_DATA             = 0x71+sysExCmdVal PULSE                   = 0x74 sysExCmdVal SHIFT_DATA              = 0x75 sysExCmdVal I2C_REQUEST             = 0x76 sysExCmdVal I2C_REPLY               = 0x77@@ -339,10 +377,11 @@ getSysExCommand 0x7A = Right SAMPLING_INTERVAL getSysExCommand 0x7E = Right SYSEX_NON_REALTIME getSysExCommand 0x7F = Right SYSEX_REALTIME+getSysExCommand 0x74 = Right PULSE getSysExCommand n    = Left n  -- | Keep track of pin-mode changes-registerPinMode :: Pin -> PinMode -> Arduino [Request]+registerPinMode :: IPin -> PinMode -> Arduino [Request] registerPinMode p m = do         -- first check that the requested mode is supported for this pin         BoardCapabilities caps <- gets capabilities@@ -350,10 +389,10 @@           Nothing              -> die ("Invalid access to unsupported pin: " ++ show p)                     ("Available pins are: " : ["  " ++ show k | (k, _) <- M.toAscList caps])-          Just (_, ms)-            | m `notElem` map fst ms+          Just PinCapabilities{allowedModes}+            | m `notElem` map fst allowedModes             -> die ("Invalid mode " ++ show m ++ " set for " ++ show p)-                   ["Supported modes for this pin are: " ++ unwords (if null ms then ["NONE"] else map show ms)]+                   ["Supported modes for this pin are: " ++ unwords (if null allowedModes then ["NONE"] else map show allowedModes)]           _ -> return ()         -- see if there was a mode already set for this pin         bs  <- gets boardState@@ -373,7 +412,7 @@                                   return $ remActs ++ addActs  -- | A mode was removed from this pin, update internal state and determine any necessary actions to remove it-getRemovalActions :: Pin -> PinMode -> Arduino [Request]+getRemovalActions :: IPin -> PinMode -> Arduino [Request] getRemovalActions p INPUT  = do -- This pin is no longer digital input         bs <- gets boardState         liftIO $ modifyMVar bs $ \bst -> do@@ -394,7 +433,7 @@  -- | Depending on a mode-set call, determine what further -- actions should be executed, such as enabling/disabling pin/port reporting-getModeActions :: Pin -> PinMode -> Arduino [Request]+getModeActions :: IPin -> PinMode -> Arduino [Request] getModeActions p INPUT  = do -- This pin is just configured for digital input         bs <- gets boardState         liftIO $ modifyMVar bs $ \bst -> do@@ -420,4 +459,39 @@                                    }                     return (bst', acts1 ++ acts2) getModeActions _ OUTPUT = return []+getModeActions _ SERVO  = return [] getModeActions p m      = die ("hArduino: getModeActions: TBD: Unsupported mode: " ++ show m) ["On pin " ++ show p]++-- | On the arduino, digital pin numbers are in 1-to-1 match with+-- the board pins. However, ANALOG pins come at an offset, determined by+-- the capabilities query. Users of the library refer to these pins+-- simply by their natural numbers, which makes for portable programs+-- between boards that have different number of digital pins. We adjust+-- for this shift here.+getInternalPin :: Pin -> Arduino IPin+getInternalPin (MixedPin p)   = return $ InternalPin p+getInternalPin (DigitalPin p) = return $ InternalPin p+getInternalPin (AnalogPin p)+  = do BoardCapabilities caps <- gets capabilities+       case listToMaybe [realPin | (realPin, PinCapabilities{analogPinNumber = Just n}) <- M.toAscList caps, p == n] of+         Nothing -> die ("hArduino: " ++ show p ++ " is not a valid analog-pin on this board.")+                        -- Try to be helpful in case they are trying to use a large value thinking it needs to be offset+                        ["Hint: To refer to analog pin number k, simply use 'pin k', not 'pin (k+noOfDigitalPins)'" | p > 13]+         Just rp -> return rp++-- | Similar to getInternalPin above, except also makes sure the pin is in a required mode.+convertAndCheckPin :: String -> Pin -> PinMode -> Arduino (IPin, PinData)+convertAndCheckPin what p' m = do+   p <- getInternalPin p'+   pd <- getPinData p+   let user = userPinNo p'+       board = pinNo p+       bInfo+         | user == board = ""+         | True          = " (On board " ++ show p ++ ")"+   when (pinMode pd /= m) $ die ("Invalid " ++ what ++ " call on pin " ++ show p' ++ bInfo)+                                [ "The current mode for this pin is: " ++ show (pinMode pd)+                                , "For " ++ what ++ ", it must be set to: " ++ show m+                                , "via a proper call to setPinMode"+                                ]+   return (p, pd)
System/Hardware/Arduino/Firmata.hs view
@@ -12,14 +12,21 @@ {-# LANGUAGE NamedFieldPuns #-} module System.Hardware.Arduino.Firmata where -import Control.Concurrent  (newEmptyMVar, readMVar)+import Control.Concurrent  (newEmptyMVar, readMVar, withMVar, modifyMVar_, threadDelay) import Control.Monad       (when, unless, void)+import Control.Monad.State (StateT(..), gets) import Control.Monad.Trans (liftIO)-import Data.Bits           ((.&.), shiftR)+import Data.Bits           ((.&.), shiftR, setBit)+import Data.Maybe          (fromMaybe)+import Data.Time           (getCurrentTime, utctDayTime)+import System.Timeout      (timeout) import Data.Word           (Word8) +import qualified Data.Map as M+ import System.Hardware.Arduino.Data import System.Hardware.Arduino.Comm+ import qualified System.Hardware.Arduino.Utils as U  -- | Retrieve the Firmata firmware version running on the Arduino. The first@@ -37,23 +44,39 @@ delay :: Int -> Arduino () delay = liftIO . U.delay +-- | Time a given action, result is measured in micro-seconds.+time :: Arduino a -> Arduino (Int, a)+time a = do start <- tick+            r     <- a+            end   <- r `seq` tick+            return (toMicroSeconds (end - start), r)+ where -- tick gets the current time in picoseconds+       tick = do t <- liftIO $ utctDayTime `fmap` getCurrentTime+                 let precision = 1000000000000 :: Integer+                 return . round . (fromIntegral precision *) . toRational $ t+       toMicroSeconds :: Integer -> Int+       toMicroSeconds t = fromIntegral $ t `quot` 1000000++-- | Time-out a given action. Time-out amount is in micro-seconds.+timeOut :: Int -> Arduino a -> Arduino (Maybe a)+timeOut to (Arduino (StateT f)) = Arduino (StateT (\st -> do+        mbRes <- timeout to (f st)+        case mbRes of+          Nothing       -> return (Nothing, st)+          Just (a, st') -> return (Just a,  st')))+ -- | Set the mode on a particular pin on the board setPinMode :: Pin -> PinMode -> Arduino ()-setPinMode p m = do+setPinMode p' m = do+   p <- getInternalPin p'    extras <- registerPinMode p m    send $ SetPinMode p m    mapM_ send extras  -- | Set or clear a digital pin on the board digitalWrite :: Pin -> Bool -> Arduino ()-digitalWrite p v = do-   -- first make sure we have this pin set as output-   pd <- getPinData p-   when (pinMode pd /= OUTPUT) $ die ("Invalid digitalWrite call on pin " ++ show p)-                                       [ "The current mode for this pin is: " ++ show (pinMode pd)-                                       , "For digitalWrite, it must be set to: " ++ show OUTPUT-                                       , "via a proper call to setPinMode"-                                       ]+digitalWrite p' v = do+   (p, pd) <- convertAndCheckPin "digitalWrite" p' OUTPUT    case pinValue pd of      Just (Left b) | b == v -> return () -- no change, nothing to do      _                      -> do (lsb, msb) <- computePortData p v@@ -61,14 +84,8 @@  -- | Turn on/off internal pull-up resistor on an input pin pullUpResistor :: Pin -> Bool -> Arduino ()-pullUpResistor p v = do-   -- first make sure we have this pin set as input-   pd <- getPinData p-   when (pinMode pd /= INPUT) $ die ("Invalid turnOnPullUpResistor call on pin " ++ show p)-                                      [ "The current mode for this pin is: " ++ show (pinMode pd)-                                      , "For turnOnPullUpResistor, it must be set to: " ++ show INPUT-                                      , "via a proper call to setPinMode"-                                      ]+pullUpResistor p' v = do+   (p, _) <- convertAndCheckPin "pullUpResistor" p' INPUT    (lsb, msb) <- computePortData p v    send $ DigitalPortWrite (pinPort p) lsb msb @@ -76,14 +93,8 @@ -- the current value immediately. See 'waitFor' for a version that waits for a change -- in the pin first. digitalRead :: Pin -> Arduino Bool-digitalRead p = do-   -- first make sure we have this pin set as input-   pd <- getPinData p-   when (pinMode pd /= INPUT) $ die ("Invalid digitalRead call on pin " ++ show p)-                                      [ "The current mode for this pin is: " ++ show (pinMode pd)-                                      , "For digitalWrite, it must be set to: " ++ show INPUT-                                      , "via a proper call to setPinMode"-                                      ]+digitalRead p' = do+   (_, pd) <- convertAndCheckPin "digitalRead" p' INPUT    return $ case pinValue pd of               Just (Left v) -> v               _             -> False -- no (correctly-typed) value reported yet, default to False@@ -138,22 +149,120 @@                     else return $ zip curVals newVals    wait +-- | Send down a pulse, and measure how long the pin reports a corresponding pulse, with a potential time-out. The call @pulse p v duration mbTimeOut@+-- does the following:+--+--   * Set the pin to value @v@ for @duration@ microseconds.+--+--   * Waits 2 microseconds+--+--   * Waits until pin @p@ has value @not v@.+--+--   * Returns, in micro-seconds, the duration the pin stayed @v@, counting from the 2 microsecond wait.+--+-- Time-out parameter is used as follows:+--+--    * If @mbTimeOut@ is @Nothing@, then 'pulse' will wait until the pin attains the value required and so long as it holds it.+--    Note that very-long time-out values are unlikely to be accurate.+-- +--    * If @mbTimeOut@ is @Just t@ then, 'pulse' will stop if the above procedure does not complete within the given micro-seconds.+--    In this case, the overall return value is @Nothing@.+--+-- NB. Both the time-out value and the return value are given in micro-seconds.+--+-- NB. As of March 2 2013; StandardFirmata that's distributed with the Arduino-App does /not/ support the Pulse-In command.+-- However, there is a patch to add this command; see: <http://github.com/rwldrn/johnny-five/issues/18> for details.+-- If you want to use hArduino's 'pulseIn' command, then you /have/ to install the above patch. Also see the function+-- 'pulseIn_hostOnly', which works with the distributed StandardFirmata: It implements a version that is not as+-- accurate in its timing, but might be sufficient if high precision is not required.+pulse :: Pin -> Bool -> Int -> Maybe Int -> Arduino (Maybe Int)+pulse p' v duration mbTo = do+        (p, _) <- convertAndCheckPin "pulse" p' INPUT+        let to = fromMaybe maxAllowed mbTo+            maxAllowed = 2147483647  -- works out to about 36 minutes; which is way beyond the accuracy provided by Arduino+            bad x = x < 0 || x > maxAllowed+        when (any bad [duration, to]) $ die ("Invalid duration/time-out values for pulse on pin " ++ show p)+                                            [ "Values should be between 0 and " ++ show maxAllowed+                                            , "Received: " ++ show (duration, to)+                                            ]+        send $ Pulse p v (fromIntegral duration) (fromIntegral to)+        r <- recv+        case r of+          PulseResponse pOut d | p == pOut -> case d of+                                                0 -> return  Nothing+                                                i -> return (Just (fromIntegral i))+          _                                -> die ("pulseIn: Got unexpected response for Pulse call on pin: " ++ show p') [show r]++-- | A /hostOnly/ version of pulse-out on a digital-pin. Use this function only for cases where the+-- precision required only matters for the host, not for the board. That is, due to the inherent+-- delays involved in Firmata communication, the timing will /not/ be accurate, and should not+-- be expected to work uniformly over different boards. Similar comments apply for 'pulseIn_hostTiming'+-- as well. See the function 'pulse' for a more accurate version.+pulseOut_hostTiming :: Pin  -- ^ Pin to send the pulse on+                  -> Bool -- ^ Pulse value+                  -> Int  -- ^ Time, in microseconds, to signal beginning of pulse; will send the opposite value for this amount+                  -> Int  -- ^ Pulse duration, measured in microseconds+                  -> Arduino ()+pulseOut_hostTiming p' pulseValue dBefore dAfter+  | dBefore < 0 || dAfter < 0+  = die ("pulseOut: Invalid delay amounts: " ++ show (dBefore, dAfter)) +        [ "Pre-delay and pulse-amounts must be non-negative."]+  | True+  = do (p, pd) <- convertAndCheckPin "pulseOut_hostTiming" p' OUTPUT+       let curPort  = pinPort p+           curIndex = pinPortIndex p+       bs  <- gets boardState+       (setMask, resetMask) <- liftIO $ withMVar bs $ \bst -> do+           let values = [(pinPortIndex sp, pinValue spd) | (sp, spd) <- M.assocs (pinStates bst), curPort == pinPort sp, pinMode pd `elem` [INPUT, OUTPUT]]+               getVal nv i+                | i == curIndex                              = nv+                | Just (Just (Left ov)) <- i `lookup` values = ov+                | True                                       = False+               mkMask val = let [b0, b1, b2, b3, b4, b5, b6, b7] = map (getVal val) [0 .. 7]+                                lsb = foldr (\(i, b) m -> if b then m `setBit` i     else m) 0 (zip [0..] [b0, b1, b2, b3, b4, b5, b6])+                                msb = foldr (\(i, b) m -> if b then m `setBit` (i-7) else m) 0 (zip [7..] [b7])+                            in (lsb, msb)+           return (mkMask pulseValue, mkMask (not pulseValue))+       let writeThrough (lsb, msb) = send $ DigitalPortWrite curPort lsb msb+       -- make sure masks are pre computed, and clear the line+       fst setMask `seq` snd setMask `seq` fst resetMask `seq` snd resetMask `seq` writeThrough resetMask+       -- Wait before starting the pulse+       liftIO $ threadDelay dBefore+       -- Send the pulse+       writeThrough setMask+       liftIO $ threadDelay dAfter+       -- Finish the pulse+       writeThrough resetMask+       -- Do a final internal update to reflect the final value of the line+       liftIO $ modifyMVar_ bs $ \bst -> return bst{pinStates = M.insert p PinData{pinMode = OUTPUT, pinValue = Just (Left (not pulseValue))}(pinStates bst)}+{-# ANN pulseOut_hostTiming "HLint: ignore Use camelCase" #-}++-- | A /hostOnly/ version of pulse-in on a digital-pin. Use this function only for cases where the+-- precision required only matters for the host, not for the board. That is, due to the inherent+-- delays involved in Firmata communication, the timing will /not/ be accurate, and should not+-- be expected to work uniformly over different boards. Similar comments apply for 'pulseOut_hostTiming'+-- as well. See the function 'pulse' for a more accurate version.+pulseIn_hostTiming :: Pin -> Bool -> Maybe Int -> Arduino (Maybe Int)+pulseIn_hostTiming p v mbTo = case mbTo of+                    Nothing -> Just `fmap` measure+                    Just to -> timeOut to measure+  where waitTill f = do curVal <- digitalRead p+                        unless (f curVal) $ waitTill f+        measure = do waitTill (== v)                  -- wait until pulse starts+                     (t, _) <- time $ waitTill (/= v) -- wait till pulse ends, measuring the time+                     return $ fromIntegral t+{-# ANN pulseIn_hostTiming "HLint: ignore Use camelCase" #-}+ -- | Read the value of a pin in analog mode; this is a non-blocking call, immediately -- returning the last sampled value. It returns @0@ if the voltage on the pin -- is 0V, and @1023@ if it is 5V, properly scaled. (See `setAnalogSamplingInterval` for -- sampling frequency.) analogRead :: Pin -> Arduino Int-analogRead p = do-   -- first make sure we have this pin set as analog-   pd <- getPinData p-   when (pinMode pd /= ANALOG) $ die ("Invalid analogRead call on pin " ++ show p)-                                     [ "The current mode for this pin is: " ++ show (pinMode pd)-                                     , "For analogRead, it must be set to: " ++ show ANALOG-                                     , "via a proper call to setPinMode"-                                     ]+analogRead p' = do+   (_, pd) <- convertAndCheckPin "analogRead" p' ANALOG    return $ case pinValue pd of               Just (Right v) -> v-              _              -> 0 -- no (correctly-typed) value reported yet, default to False+              _              -> 0 -- no (correctly-typed) value reported yet, default to 0  -- | Set the analog sampling interval, in milliseconds. Arduino uses a default of 19ms to sample analog and I2C -- signals, which is fine for many applications, but can be modified if needed. The argument
− System/Hardware/Arduino/LCD.hs
@@ -1,432 +0,0 @@----------------------------------------------------------------------------------------------------- |--- Module      :  System.Hardware.Arduino.LCD--- Copyright   :  (c) Levent Erkok--- License     :  BSD3--- Maintainer  :  erkokl@gmail.com--- Stability   :  experimental------ LCD (Liquid Crystal Display) parts supported by hArduino. The Haskell code--- below has partly been implemented following the Arduino LiquidCrystal project--- source code: <http://code.google.com/p/arduino/source/browse/trunk/libraries/LiquidCrystal/>------ The Hitachi44780 data sheet is at: <http://lcd-linux.sourceforge.net/pdfdocs/hd44780.pdf>------ For an example program using this library, see "System.Hardware.Arduino.SamplePrograms.LCD".----------------------------------------------------------------------------------------------------{-# LANGUAGE NamedFieldPuns #-}-module System.Hardware.Arduino.LCD(-  -- * LCD types and registration-  LCDController(..), lcdRegister-  -- * Writing text on the LCD-  , lcdClear, lcdWrite-  -- * Moving the cursor-  , lcdHome, lcdSetCursor-  -- * Scrolling-  , lcdAutoScrollOn, lcdAutoScrollOff-  , lcdScrollDisplayLeft, lcdScrollDisplayRight-  -- * Display properties-  , lcdLeftToRight, lcdRightToLeft-  , lcdBlinkOn, lcdBlinkOff-  , lcdCursorOn, lcdCursorOff-  , lcdDisplayOn, lcdDisplayOff-  -- * Accessing internal symbols,-  , LCDSymbol, lcdInternalSymbol, lcdWriteSymbol-  -- Creating custom symbols-  , lcdCreateSymbol-  -- * Misc helpers-  , lcdFlash-  )  where--import Control.Concurrent  (modifyMVar, withMVar)-import Control.Monad       (when)-import Control.Monad.State (gets, liftIO)-import Data.Bits           (testBit, (.|.), (.&.), setBit, clearBit, shiftL, bit)-import Data.Char           (ord, isSpace)-import Data.Maybe          (fromMaybe)-import Data.Word           (Word8)--import qualified Data.Map as M--import System.Hardware.Arduino.Data-import System.Hardware.Arduino.Firmata--import qualified System.Hardware.Arduino.Utils as U-------------------------------------------------------------------------------------------- Low level interface, not available to the user-------------------------------------------------------------------------------------------- | Commands understood by Hitachi-data Cmd = LCD_INITIALIZE-         | LCD_INITIALIZE_END-         | LCD_FUNCTIONSET-         | LCD_DISPLAYCONTROL Word8-         | LCD_CLEARDISPLAY-         | LCD_ENTRYMODESET   Word8-         | LCD_RETURNHOME-         | LCD_SETDDRAMADDR   Word8-         | LCD_CURSORSHIFT    Word8-         | LCD_SETCGRAMADDR   Word8---- | Convert a command to a data-word-getCmdVal :: LCDController -> Cmd -> Word8-getCmdVal Hitachi44780{lcdRows, dotMode5x10} = get-  where multiLine -- bit 3-          | lcdRows > 1 = 0x08 :: Word8-          | True        = 0x00 :: Word8-        dotMode   -- bit 2-          | dotMode5x10 = 0x04 :: Word8-          | True        = 0x00 :: Word8-        displayFunction = multiLine .|. dotMode-        get LCD_INITIALIZE         = 0x33-        get LCD_INITIALIZE_END     = 0x32-        get LCD_FUNCTIONSET        = 0x20 .|. displayFunction-        get (LCD_DISPLAYCONTROL w) = 0x08 .|. w-        get LCD_CLEARDISPLAY       = 0x01-        get (LCD_ENTRYMODESET w)   = 0x04 .|. w-        get LCD_RETURNHOME         = 0x02-        get (LCD_SETDDRAMADDR w)   = 0x80 .|. w-        get (LCD_CURSORSHIFT w)    = 0x10 .|. 0x08 .|. w   -- NB. LCD_DISPLAYMOVE (0x08) hard coded here-        get (LCD_SETCGRAMADDR w)   = 0x40 .|. w `shiftL` 3---- | Initialize the LCD. Follows the data sheet <http://lcd-linux.sourceforge.net/pdfdocs/hd44780.pdf>,--- page 46; figure 24.-initLCD :: LCD -> LCDController -> Arduino ()-initLCD lcd c@Hitachi44780{lcdRS, lcdEN, lcdD4, lcdD5, lcdD6, lcdD7} = do-    debug "Starting the LCD initialization sequence"-    mapM_ (`setPinMode` OUTPUT) [lcdRS, lcdEN, lcdD4, lcdD5, lcdD6, lcdD7]-    -- Wait for 50ms, data-sheet says at least 40ms for 2.7V version, so be safe-    delay 50-    sendCmd c LCD_INITIALIZE-    delay 5-    sendCmd c LCD_INITIALIZE_END-    sendCmd c LCD_FUNCTIONSET-    lcdCursorOff lcd-    lcdBlinkOff lcd-    lcdLeftToRight lcd-    lcdAutoScrollOff lcd-    lcdHome lcd-    lcdClear lcd-    lcdDisplayOn lcd---- | Get the controller associated with the LCD-getController :: LCD -> Arduino LCDController-getController lcd = do-  bs  <- gets boardState-  err <- gets bailOut-  liftIO $ withMVar bs $ \bst -> case lcd `M.lookup` lcds bst of-                                   Nothing -> err ("hArduino: Cannot locate " ++ show lcd) []-                                   Just ld -> return $ lcdController ld---- | Send a command to the LCD controller-sendCmd :: LCDController -> Cmd -> Arduino ()-sendCmd c = transmit False c . getCmdVal c---- | Send 4-bit data to the LCD controller-sendData :: LCDController -> Word8 -> Arduino ()-sendData lcd n = do debug $ "Transmitting LCD data: " ++ U.showByte n-                    transmit True lcd n---- | By controlling the enable-pin, indicate to the controller that--- the data is ready for it to process.-pulseEnable :: LCDController -> Arduino ()-pulseEnable Hitachi44780{lcdEN} = do-  debug "Sending LCD pulseEnable"-  digitalWrite lcdEN False-  delay 1-  digitalWrite lcdEN True-  delay 1-  digitalWrite lcdEN False-  delay 1---- | Transmit data down to the LCD-transmit :: Bool -> LCDController -> Word8 -> Arduino ()-transmit mode c@Hitachi44780{lcdRS, lcdEN, lcdD4, lcdD5, lcdD6, lcdD7} val = do-  digitalWrite lcdRS mode-  digitalWrite lcdEN False-  let [b7, b6, b5, b4, b3, b2, b1, b0] = [val `testBit` i | i <- [7, 6 .. 0]]-  -- Send down the first 4 bits-  digitalWrite lcdD4 b4-  digitalWrite lcdD5 b5-  digitalWrite lcdD6 b6-  digitalWrite lcdD7 b7-  pulseEnable c-  -- Send down the remaining batch-  digitalWrite lcdD4 b0-  digitalWrite lcdD5 b1-  digitalWrite lcdD6 b2-  digitalWrite lcdD7 b3-  pulseEnable c---- | Helper function to simplify library programming, not exposed to the user.-withLCD :: LCD -> String -> (LCDController -> Arduino a) -> Arduino a-withLCD lcd what action = do-        debug what-        c <- getController lcd-        action c-------------------------------------------------------------------------------------------- High level interface, exposed to the user-------------------------------------------------------------------------------------------- | Register an LCD controller. When registration is complete, the LCD will be initialized so that:------   * Set display ON (Use 'lcdDisplayOn' / 'lcdDisplayOff' to change.)------   * Set cursor OFF (Use 'lcdCursorOn' / 'lcdCursorOff' to change.)------   * Set blink OFF  (Use 'lcdBlinkOn' / 'lcdBlinkOff' to change.)------   * Clear display (Use 'lcdClear' to clear, 'lcdWrite' to display text.)------   * Set entry mode left to write (Use 'lcdLeftToRight' / 'lcdRightToLeft' to control.)------   * Set autoscrolling OFF (Use 'lcdAutoScrollOff' / 'lcdAutoScrollOn' to control.)------   * Put the cursor into home position (Use 'lcdSetCursor' or 'lcdHome' to move around.)-lcdRegister :: LCDController -> Arduino LCD-lcdRegister controller = do-  bs <- gets boardState-  lcd <- liftIO $ modifyMVar bs $ \bst -> do-                    let n = M.size $ lcds bst-                        ld = LCDData { lcdDisplayMode    = 0-                                     , lcdDisplayControl = 0-                                     , lcdGlyphCount     = 0-                                     , lcdController     = controller-                                     }-                    return (bst {lcds = M.insert (LCD n) ld (lcds bst)}, LCD n)-  case controller of-     Hitachi44780{} -> initLCD lcd controller-  return lcd---- | Write a string on the LCD at the current cursor position-lcdWrite :: LCD -> String -> Arduino ()-lcdWrite lcd m = withLCD lcd ("Writing " ++ show m ++ " to LCD") $ \c -> mapM_ (sendData c) m'-   where m' = map (\ch -> fromIntegral (ord ch) .&. 0xFF) m---- | Clear the LCD-lcdClear :: LCD -> Arduino ()-lcdClear lcd = withLCD lcd "Sending clearLCD" $ \c ->-                 do sendCmd c LCD_CLEARDISPLAY-                    delay 2 -- give some time to make sure LCD is really cleared---- | Send the cursor to home position-lcdHome :: LCD -> Arduino ()-lcdHome lcd = withLCD lcd "Sending the cursor home" $ \c ->-                do sendCmd c LCD_RETURNHOME-                   delay 2---- | Set the cursor location. The pair of arguments is the new column and row numbers--- respectively:------   * The first value is the column, the second is the row. (This is counter-intuitive, but---     is in line with what the standard Arduino programmers do, so we follow the same convention.)------   * Counting starts at 0 (both for column and row no)------   * If the new location is out-of-bounds of your LCD, we will put it the cursor to the closest---     possible location on the LCD.-lcdSetCursor :: LCD -> (Int, Int) -> Arduino ()-lcdSetCursor lcd (givenCol, givenRow) = withLCD lcd ("Sending the cursor to Row: " ++ show givenRow ++ " Col: " ++ show givenCol) set-  where set c@Hitachi44780{lcdRows, lcdCols} = sendCmd c (LCD_SETDDRAMADDR offset)-              where align :: Int -> Int -> Word8-                    align i m-                      | i < 0  = 0-                      | i >= m = fromIntegral $ m-1-                      | True   = fromIntegral i-                    col = align givenCol lcdCols-                    row = align givenRow lcdRows-                    -- The magic row-offsets come from various web sources-                    -- I don't follow the logic in these numbers, but it seems to work-                    rowOffsets = [(0, 0), (1, 0x40), (2, 0x14), (3, 0x54)]-                    offset = col + fromMaybe 0x54 (row `lookup` rowOffsets)---- | Scroll the display to the left by 1 character. Project idea: Using a tilt sensor, scroll the contents of the display--- left/right depending on the tilt. -lcdScrollDisplayLeft :: LCD -> Arduino ()-lcdScrollDisplayLeft lcd = withLCD lcd "Scrolling display to the left by 1" $ \c -> sendCmd c (LCD_CURSORSHIFT lcdMoveLeft)-  where lcdMoveLeft = 0x00---- | Scroll the display to the right by 1 character-lcdScrollDisplayRight :: LCD -> Arduino ()-lcdScrollDisplayRight lcd = withLCD lcd "Scrolling display to the right by 1" $ \c -> sendCmd c (LCD_CURSORSHIFT lcdMoveRight)-  where lcdMoveRight = 0x04---- | Display characteristics helper, set the new control/mode and send--- appropriate commands if anything changed-updateDisplayData :: String -> (Word8 -> Word8, Word8 -> Word8) -> LCD -> Arduino ()-updateDisplayData what (f, g) lcd = do-   debug what-   bs  <- gets boardState-   err <- gets bailOut-   (  LCDData {lcdDisplayControl = oldC, lcdDisplayMode = oldM}-    , LCDData {lcdDisplayControl = newC, lcdDisplayMode = newM, lcdController = c})-        <- liftIO $ modifyMVar bs $ \bst ->-                       case lcd `M.lookup` lcds bst of-                         Nothing -> err ("hArduino: Cannot locate " ++ show lcd) []-                         Just ld@LCDData{lcdDisplayControl, lcdDisplayMode}-                            -> do let ld' = ld { lcdDisplayControl = f lcdDisplayControl-                                               , lcdDisplayMode    = g lcdDisplayMode-                                               }-                                  return (bst{lcds = M.insert lcd ld' (lcds bst)}, (ld, ld'))-   when (oldC /= newC) $ sendCmd c (LCD_DISPLAYCONTROL newC)-   when (oldM /= newM) $ sendCmd c (LCD_ENTRYMODESET   newM)---- | Update the display control word-updateDisplayControl :: String -> (Word8 -> Word8) -> LCD -> Arduino ()-updateDisplayControl what f = updateDisplayData what (f, id)---- | Update the display mode word-updateDisplayMode :: String -> (Word8 -> Word8) -> LCD -> Arduino ()-updateDisplayMode what g = updateDisplayData what (id, g)---- | Various control masks for the Hitachi44780-data Hitachi44780Mask = LCD_BLINKON              -- ^ bit @0@ Controls whether cursor blinks-                      | LCD_CURSORON             -- ^ bit @1@ Controls whether cursor is on-                      | LCD_DISPLAYON            -- ^ bit @2@ Controls whether display is on-                      | LCD_ENTRYSHIFTINCREMENT  -- ^ bit @0@ Controls left/right scroll-                      | LCD_ENTRYLEFT            -- ^ bit @1@ Controls left/right entry mode---- | Convert the mask value to the bit no-maskBit :: Hitachi44780Mask -> Int-maskBit LCD_BLINKON             = 0-maskBit LCD_CURSORON            = 1-maskBit LCD_DISPLAYON           = 2-maskBit LCD_ENTRYSHIFTINCREMENT = 0-maskBit LCD_ENTRYLEFT           = 1---- | Clear by the mask-clearMask :: Hitachi44780Mask -> Word8 -> Word8-clearMask m w = w `clearBit` maskBit m---- | Set by the mask-setMask :: Hitachi44780Mask -> Word8 -> Word8-setMask m w = w `setBit` maskBit m---- | Do not blink the cursor-lcdBlinkOff :: LCD -> Arduino ()-lcdBlinkOff = updateDisplayControl "Turning blinking off" (clearMask LCD_BLINKON)---- | Blink the cursor-lcdBlinkOn :: LCD -> Arduino ()-lcdBlinkOn = updateDisplayControl "Turning blinking on" (setMask LCD_BLINKON)---- | Hide the cursor. Note that a blinking cursor cannot be hidden, you must first--- turn off blinking.-lcdCursorOff :: LCD -> Arduino ()-lcdCursorOff = updateDisplayControl "Not showing the cursor" (clearMask LCD_CURSORON)---- | Show the cursor-lcdCursorOn :: LCD -> Arduino ()-lcdCursorOn = updateDisplayControl "Showing the cursor" (setMask LCD_CURSORON)---- | Turn the display off. Note that turning the display off does not mean you are--- powering it down. It simply means that the characters will not be shown until--- you turn it back on using 'lcdDisplayOn'. (Also, the contents will /not/ be--- forgotten when you call this function.) Therefore, this function is useful--- for temporarily hiding the display contents.-lcdDisplayOff :: LCD -> Arduino ()-lcdDisplayOff = updateDisplayControl "Turning display off" (clearMask LCD_DISPLAYON)---- | Turn the display on-lcdDisplayOn :: LCD -> Arduino ()-lcdDisplayOn = updateDisplayControl "Turning display on" (setMask LCD_DISPLAYON)---- | Set writing direction: Left to Right-lcdLeftToRight :: LCD -> Arduino ()-lcdLeftToRight = updateDisplayMode "Setting left-to-right entry mode" (setMask LCD_ENTRYLEFT)---- | Set writing direction: Right to Left-lcdRightToLeft :: LCD -> Arduino ()-lcdRightToLeft = updateDisplayMode "Setting right-to-left entry mode" (clearMask LCD_ENTRYLEFT)---- | Turn on auto-scrolling. In the context of the Hitachi44780 controller, this means that--- each time a letter is added, all the text is moved one space to the left. This can be--- confusing at first: It does /not/ mean that your strings will continuously scroll:--- It just means that if you write a string whose length exceeds the column-count--- of your LCD, then you'll see the tail-end of it. (Of course, this will create a scrolling--- effect as the string is being printed character by character.)------ Having said that, it is easy to program a scrolling string program: Simply write your string--- by calling 'lcdWrite', and then use the 'lcdScrollDisplayLeft' and 'lcdScrollDisplayRight' functions--- with appropriate delays to simulate the scrolling.-lcdAutoScrollOn :: LCD -> Arduino ()-lcdAutoScrollOn = updateDisplayMode "Setting auto-scroll ON" (setMask LCD_ENTRYSHIFTINCREMENT)---- | Turn off auto-scrolling. See the comments for 'lcdAutoScrollOn' for details. When turned--- off (which is the default), you will /not/ see the characters at the end of your strings that--- do not fit into the display.-lcdAutoScrollOff :: LCD -> Arduino ()-lcdAutoScrollOff = updateDisplayMode "Setting auto-scroll OFF" (clearMask LCD_ENTRYSHIFTINCREMENT)---- | Flash contents of the LCD screen-lcdFlash :: LCD-         -> Int  -- ^ Flash count-         -> Int  -- ^ Delay amount (in milli-seconds)-         -> Arduino ()-lcdFlash lcd n d = sequence_ $ concat $ replicate n [lcdDisplayOff lcd, delay d, lcdDisplayOn lcd, delay d]---- | An abstract symbol type for user created symbols-newtype LCDSymbol = LCDSymbol Word8---- | Create a custom symbol for later display. Note that controllers--- have limited capability for such symbols, typically storing no more--- than 8. The behavior is undefined if you create more symbols than your--- LCD can handle.------ The input is a simple description of the glyph, as a list of precisely 8--- strings, each of which must have 5 characters. Any space character is--- interpreted as a empty pixel, any non-space is a full pixel, corresponding--- to the pixel in the 5x8 characters we have on the LCD.  For instance, here's--- a happy-face glyph you can use:------ >--- >   [ "     "--- >   , "@   @"--- >   , "     "--- >   , "     "--- >   , "@   @"--- >   , " @@@ "--- >   , "     "--- >   , "     "--- >   ]--- >-lcdCreateSymbol :: LCD -> [String] -> Arduino LCDSymbol-lcdCreateSymbol lcd glyph-  | length glyph /= 8 || any (/= 5) (map length glyph)-  = die "hArduino: lcdCreateSymbol: Invalid glyph description: must be 8x5!" ("Received:" : glyph)-  | True-  = do bs  <- gets boardState-       err <- gets bailOut-       (i, c) <- liftIO $ modifyMVar bs $ \bst ->-                    case lcd `M.lookup` lcds bst of-                      Nothing -> err ("hArduino: Cannot locate " ++ show lcd) []-                      Just ld@LCDData{lcdGlyphCount, lcdController}-                              -> do let ld' = ld { lcdGlyphCount = lcdGlyphCount + 1 }-                                    return (bst{lcds = M.insert lcd ld' (lcds bst)}, (lcdGlyphCount, lcdController))-       sendCmd c (LCD_SETCGRAMADDR i)-       let cvt :: String -> Word8-           cvt s = foldr (.|.) 0 [bit p | (ch, p) <- zip (reverse s) [0..], not (isSpace ch)]-       mapM_ (sendData c . cvt) glyph-       return $ LCDSymbol i---- | Display a user created symbol on the LCD. (See 'lcdCreateSymbol' for details.)-lcdWriteSymbol :: LCD -> LCDSymbol -> Arduino ()-lcdWriteSymbol lcd (LCDSymbol i) = withLCD lcd ("Writing custom symbol " ++ show i ++ " to LCD") $ \c -> sendData c i---- | Access an internally stored symbol, one that is not available via its ASCII equivalent. See--- the Hitachi datasheet for possible values: <http://lcd-linux.sourceforge.net/pdfdocs/hd44780.pdf>, Table 4 on page 17.------ For instance, to access the symbol right-arrow:------   * Locate it in the above table: Right-arrow is at the second-to-last row, 7th character from left.------   * Check the upper/higher bits as specified in the table: For Right-arrow, upper bits are @0111@ and the---     lower bits are @1110@; which gives us the code @01111110@, or @0x7E@.------   * So, right-arrow can be accessed by symbol code 'lcdInternalSymbol' @0x7E@, which will give us a 'LCDSymbol' value---   that can be passed to the 'lcdWriteSymbol' function. The code would look like this: @lcdWriteSymbol lcd (lcdInternalSymbol 0x7E)@.-lcdInternalSymbol :: Word8 -> LCDSymbol-lcdInternalSymbol = LCDSymbol
+ System/Hardware/Arduino/Parts.hs view
@@ -0,0 +1,27 @@+-------------------------------------------------------------------------------+-- |+-- Module      :  System.Hardware.Arduino.Parts+-- Copyright   :  (c) Levent Erkok+-- License     :  BSD3+-- Maintainer  :  erkokl@gmail.com+-- Stability   :  experimental+--+-- Models of various Hardware components+-------------------------------------------------------------------------------+module System.Hardware.Arduino.Parts(+      -- * Liquid Crystal Displays+      module System.Hardware.Arduino.Parts.LCD+      -- * Seven-Segment Conversion Codes+   ,  module System.Hardware.Arduino.Parts.SevenSegmentCodes+      -- * Shift-registers+   ,  module System.Hardware.Arduino.Parts.ShiftRegisters+      -- * Servo-motors+   ,  module System.Hardware.Arduino.Parts.Servo+   ) where++import System.Hardware.Arduino.Parts.LCD+import System.Hardware.Arduino.Parts.SevenSegmentCodes+import System.Hardware.Arduino.Parts.ShiftRegisters+import System.Hardware.Arduino.Parts.Servo++{-# ANN module "HLint: ignore Use import/export shortcut" #-}
+ System/Hardware/Arduino/Parts/LCD.hs view
@@ -0,0 +1,432 @@+-------------------------------------------------------------------------------------------------+-- |+-- Module      :  System.Hardware.Arduino.Parts.LCD+-- Copyright   :  (c) Levent Erkok+-- License     :  BSD3+-- Maintainer  :  erkokl@gmail.com+-- Stability   :  experimental+--+-- LCD (Liquid Crystal Display) parts supported by hArduino. The Haskell code+-- below has partly been implemented following the Arduino LiquidCrystal project+-- source code: <http://code.google.com/p/arduino/source/browse/trunk/libraries/LiquidCrystal/>+--+-- The Hitachi44780 data sheet is at: <http://lcd-linux.sourceforge.net/pdfdocs/hd44780.pdf>+--+-- For an example program using this library, see "System.Hardware.Arduino.SamplePrograms.LCD".+-------------------------------------------------------------------------------------------------++{-# LANGUAGE NamedFieldPuns #-}+module System.Hardware.Arduino.Parts.LCD(+  -- * LCD types and registration+  LCDController(..), lcdRegister+  -- * Writing text on the LCD+  , lcdClear, lcdWrite+  -- * Moving the cursor+  , lcdHome, lcdSetCursor+  -- * Scrolling+  , lcdAutoScrollOn, lcdAutoScrollOff+  , lcdScrollDisplayLeft, lcdScrollDisplayRight+  -- * Display properties+  , lcdLeftToRight, lcdRightToLeft+  , lcdBlinkOn, lcdBlinkOff+  , lcdCursorOn, lcdCursorOff+  , lcdDisplayOn, lcdDisplayOff+  -- * Accessing internal symbols,+  , LCDSymbol, lcdInternalSymbol, lcdWriteSymbol+  -- Creating custom symbols+  , lcdCreateSymbol+  -- * Misc helpers+  , lcdFlash+  )  where++import Control.Concurrent  (modifyMVar, withMVar)+import Control.Monad       (when)+import Control.Monad.State (gets, liftIO)+import Data.Bits           (testBit, (.|.), (.&.), setBit, clearBit, shiftL, bit)+import Data.Char           (ord, isSpace)+import Data.Maybe          (fromMaybe)+import Data.Word           (Word8)++import qualified Data.Map as M++import System.Hardware.Arduino.Data+import System.Hardware.Arduino.Firmata++import qualified System.Hardware.Arduino.Utils as U++---------------------------------------------------------------------------------------+-- Low level interface, not available to the user+---------------------------------------------------------------------------------------++-- | Commands understood by Hitachi+data Cmd = LCD_INITIALIZE+         | LCD_INITIALIZE_END+         | LCD_FUNCTIONSET+         | LCD_DISPLAYCONTROL Word8+         | LCD_CLEARDISPLAY+         | LCD_ENTRYMODESET   Word8+         | LCD_RETURNHOME+         | LCD_SETDDRAMADDR   Word8+         | LCD_CURSORSHIFT    Word8+         | LCD_SETCGRAMADDR   Word8++-- | Convert a command to a data-word+getCmdVal :: LCDController -> Cmd -> Word8+getCmdVal Hitachi44780{lcdRows, dotMode5x10} = get+  where multiLine -- bit 3+          | lcdRows > 1 = 0x08 :: Word8+          | True        = 0x00 :: Word8+        dotMode   -- bit 2+          | dotMode5x10 = 0x04 :: Word8+          | True        = 0x00 :: Word8+        displayFunction = multiLine .|. dotMode+        get LCD_INITIALIZE         = 0x33+        get LCD_INITIALIZE_END     = 0x32+        get LCD_FUNCTIONSET        = 0x20 .|. displayFunction+        get (LCD_DISPLAYCONTROL w) = 0x08 .|. w+        get LCD_CLEARDISPLAY       = 0x01+        get (LCD_ENTRYMODESET w)   = 0x04 .|. w+        get LCD_RETURNHOME         = 0x02+        get (LCD_SETDDRAMADDR w)   = 0x80 .|. w+        get (LCD_CURSORSHIFT w)    = 0x10 .|. 0x08 .|. w   -- NB. LCD_DISPLAYMOVE (0x08) hard coded here+        get (LCD_SETCGRAMADDR w)   = 0x40 .|. w `shiftL` 3++-- | Initialize the LCD. Follows the data sheet <http://lcd-linux.sourceforge.net/pdfdocs/hd44780.pdf>,+-- page 46; figure 24.+initLCD :: LCD -> LCDController -> Arduino ()+initLCD lcd c@Hitachi44780{lcdRS, lcdEN, lcdD4, lcdD5, lcdD6, lcdD7} = do+    debug "Starting the LCD initialization sequence"+    mapM_ (`setPinMode` OUTPUT) [lcdRS, lcdEN, lcdD4, lcdD5, lcdD6, lcdD7]+    -- Wait for 50ms, data-sheet says at least 40ms for 2.7V version, so be safe+    delay 50+    sendCmd c LCD_INITIALIZE+    delay 5+    sendCmd c LCD_INITIALIZE_END+    sendCmd c LCD_FUNCTIONSET+    lcdCursorOff lcd+    lcdBlinkOff lcd+    lcdLeftToRight lcd+    lcdAutoScrollOff lcd+    lcdHome lcd+    lcdClear lcd+    lcdDisplayOn lcd++-- | Get the controller associated with the LCD+getController :: LCD -> Arduino LCDController+getController lcd = do+  bs  <- gets boardState+  err <- gets bailOut+  liftIO $ withMVar bs $ \bst -> case lcd `M.lookup` lcds bst of+                                   Nothing -> err ("hArduino: Cannot locate " ++ show lcd) []+                                   Just ld -> return $ lcdController ld++-- | Send a command to the LCD controller+sendCmd :: LCDController -> Cmd -> Arduino ()+sendCmd c = transmit False c . getCmdVal c++-- | Send 4-bit data to the LCD controller+sendData :: LCDController -> Word8 -> Arduino ()+sendData lcd n = do debug $ "Transmitting LCD data: " ++ U.showByte n+                    transmit True lcd n++-- | By controlling the enable-pin, indicate to the controller that+-- the data is ready for it to process.+pulseEnable :: LCDController -> Arduino ()+pulseEnable Hitachi44780{lcdEN} = do+  debug "Sending LCD pulseEnable"+  digitalWrite lcdEN False+  delay 1+  digitalWrite lcdEN True+  delay 1+  digitalWrite lcdEN False+  delay 1++-- | Transmit data down to the LCD+transmit :: Bool -> LCDController -> Word8 -> Arduino ()+transmit mode c@Hitachi44780{lcdRS, lcdEN, lcdD4, lcdD5, lcdD6, lcdD7} val = do+  digitalWrite lcdRS mode+  digitalWrite lcdEN False+  let [b7, b6, b5, b4, b3, b2, b1, b0] = [val `testBit` i | i <- [7, 6 .. 0]]+  -- Send down the first 4 bits+  digitalWrite lcdD4 b4+  digitalWrite lcdD5 b5+  digitalWrite lcdD6 b6+  digitalWrite lcdD7 b7+  pulseEnable c+  -- Send down the remaining batch+  digitalWrite lcdD4 b0+  digitalWrite lcdD5 b1+  digitalWrite lcdD6 b2+  digitalWrite lcdD7 b3+  pulseEnable c++-- | Helper function to simplify library programming, not exposed to the user.+withLCD :: LCD -> String -> (LCDController -> Arduino a) -> Arduino a+withLCD lcd what action = do+        debug what+        c <- getController lcd+        action c++---------------------------------------------------------------------------------------+-- High level interface, exposed to the user+---------------------------------------------------------------------------------------++-- | Register an LCD controller. When registration is complete, the LCD will be initialized so that:+--+--   * Set display ON (Use 'lcdDisplayOn' / 'lcdDisplayOff' to change.)+--+--   * Set cursor OFF (Use 'lcdCursorOn' / 'lcdCursorOff' to change.)+--+--   * Set blink OFF  (Use 'lcdBlinkOn' / 'lcdBlinkOff' to change.)+--+--   * Clear display (Use 'lcdClear' to clear, 'lcdWrite' to display text.)+--+--   * Set entry mode left to write (Use 'lcdLeftToRight' / 'lcdRightToLeft' to control.)+--+--   * Set autoscrolling OFF (Use 'lcdAutoScrollOff' / 'lcdAutoScrollOn' to control.)+--+--   * Put the cursor into home position (Use 'lcdSetCursor' or 'lcdHome' to move around.)+lcdRegister :: LCDController -> Arduino LCD+lcdRegister controller = do+  bs <- gets boardState+  lcd <- liftIO $ modifyMVar bs $ \bst -> do+                    let n = M.size $ lcds bst+                        ld = LCDData { lcdDisplayMode    = 0+                                     , lcdDisplayControl = 0+                                     , lcdGlyphCount     = 0+                                     , lcdController     = controller+                                     }+                    return (bst {lcds = M.insert (LCD n) ld (lcds bst)}, LCD n)+  case controller of+     Hitachi44780{} -> initLCD lcd controller+  return lcd++-- | Write a string on the LCD at the current cursor position+lcdWrite :: LCD -> String -> Arduino ()+lcdWrite lcd m = withLCD lcd ("Writing " ++ show m ++ " to LCD") $ \c -> mapM_ (sendData c) m'+   where m' = map (\ch -> fromIntegral (ord ch) .&. 0xFF) m++-- | Clear the LCD+lcdClear :: LCD -> Arduino ()+lcdClear lcd = withLCD lcd "Sending clearLCD" $ \c ->+                 do sendCmd c LCD_CLEARDISPLAY+                    delay 2 -- give some time to make sure LCD is really cleared++-- | Send the cursor to home position+lcdHome :: LCD -> Arduino ()+lcdHome lcd = withLCD lcd "Sending the cursor home" $ \c ->+                do sendCmd c LCD_RETURNHOME+                   delay 2++-- | Set the cursor location. The pair of arguments is the new column and row numbers+-- respectively:+--+--   * The first value is the column, the second is the row. (This is counter-intuitive, but+--     is in line with what the standard Arduino programmers do, so we follow the same convention.)+--+--   * Counting starts at 0 (both for column and row no)+--+--   * If the new location is out-of-bounds of your LCD, we will put it the cursor to the closest+--     possible location on the LCD.+lcdSetCursor :: LCD -> (Int, Int) -> Arduino ()+lcdSetCursor lcd (givenCol, givenRow) = withLCD lcd ("Sending the cursor to Row: " ++ show givenRow ++ " Col: " ++ show givenCol) set+  where set c@Hitachi44780{lcdRows, lcdCols} = sendCmd c (LCD_SETDDRAMADDR offset)+              where align :: Int -> Int -> Word8+                    align i m+                      | i < 0  = 0+                      | i >= m = fromIntegral $ m-1+                      | True   = fromIntegral i+                    col = align givenCol lcdCols+                    row = align givenRow lcdRows+                    -- The magic row-offsets come from various web sources+                    -- I don't follow the logic in these numbers, but it seems to work+                    rowOffsets = [(0, 0), (1, 0x40), (2, 0x14), (3, 0x54)]+                    offset = col + fromMaybe 0x54 (row `lookup` rowOffsets)++-- | Scroll the display to the left by 1 character. Project idea: Using a tilt sensor, scroll the contents of the display+-- left/right depending on the tilt. +lcdScrollDisplayLeft :: LCD -> Arduino ()+lcdScrollDisplayLeft lcd = withLCD lcd "Scrolling display to the left by 1" $ \c -> sendCmd c (LCD_CURSORSHIFT lcdMoveLeft)+  where lcdMoveLeft = 0x00++-- | Scroll the display to the right by 1 character+lcdScrollDisplayRight :: LCD -> Arduino ()+lcdScrollDisplayRight lcd = withLCD lcd "Scrolling display to the right by 1" $ \c -> sendCmd c (LCD_CURSORSHIFT lcdMoveRight)+  where lcdMoveRight = 0x04++-- | Display characteristics helper, set the new control/mode and send+-- appropriate commands if anything changed+updateDisplayData :: String -> (Word8 -> Word8, Word8 -> Word8) -> LCD -> Arduino ()+updateDisplayData what (f, g) lcd = do+   debug what+   bs  <- gets boardState+   err <- gets bailOut+   (  LCDData {lcdDisplayControl = oldC, lcdDisplayMode = oldM}+    , LCDData {lcdDisplayControl = newC, lcdDisplayMode = newM, lcdController = c})+        <- liftIO $ modifyMVar bs $ \bst ->+                       case lcd `M.lookup` lcds bst of+                         Nothing -> err ("hArduino: Cannot locate " ++ show lcd) []+                         Just ld@LCDData{lcdDisplayControl, lcdDisplayMode}+                            -> do let ld' = ld { lcdDisplayControl = f lcdDisplayControl+                                               , lcdDisplayMode    = g lcdDisplayMode+                                               }+                                  return (bst{lcds = M.insert lcd ld' (lcds bst)}, (ld, ld'))+   when (oldC /= newC) $ sendCmd c (LCD_DISPLAYCONTROL newC)+   when (oldM /= newM) $ sendCmd c (LCD_ENTRYMODESET   newM)++-- | Update the display control word+updateDisplayControl :: String -> (Word8 -> Word8) -> LCD -> Arduino ()+updateDisplayControl what f = updateDisplayData what (f, id)++-- | Update the display mode word+updateDisplayMode :: String -> (Word8 -> Word8) -> LCD -> Arduino ()+updateDisplayMode what g = updateDisplayData what (id, g)++-- | Various control masks for the Hitachi44780+data Hitachi44780Mask = LCD_BLINKON              -- ^ bit @0@ Controls whether cursor blinks+                      | LCD_CURSORON             -- ^ bit @1@ Controls whether cursor is on+                      | LCD_DISPLAYON            -- ^ bit @2@ Controls whether display is on+                      | LCD_ENTRYSHIFTINCREMENT  -- ^ bit @0@ Controls left/right scroll+                      | LCD_ENTRYLEFT            -- ^ bit @1@ Controls left/right entry mode++-- | Convert the mask value to the bit no+maskBit :: Hitachi44780Mask -> Int+maskBit LCD_BLINKON             = 0+maskBit LCD_CURSORON            = 1+maskBit LCD_DISPLAYON           = 2+maskBit LCD_ENTRYSHIFTINCREMENT = 0+maskBit LCD_ENTRYLEFT           = 1++-- | Clear by the mask+clearMask :: Hitachi44780Mask -> Word8 -> Word8+clearMask m w = w `clearBit` maskBit m++-- | Set by the mask+setMask :: Hitachi44780Mask -> Word8 -> Word8+setMask m w = w `setBit` maskBit m++-- | Do not blink the cursor+lcdBlinkOff :: LCD -> Arduino ()+lcdBlinkOff = updateDisplayControl "Turning blinking off" (clearMask LCD_BLINKON)++-- | Blink the cursor+lcdBlinkOn :: LCD -> Arduino ()+lcdBlinkOn = updateDisplayControl "Turning blinking on" (setMask LCD_BLINKON)++-- | Hide the cursor. Note that a blinking cursor cannot be hidden, you must first+-- turn off blinking.+lcdCursorOff :: LCD -> Arduino ()+lcdCursorOff = updateDisplayControl "Not showing the cursor" (clearMask LCD_CURSORON)++-- | Show the cursor+lcdCursorOn :: LCD -> Arduino ()+lcdCursorOn = updateDisplayControl "Showing the cursor" (setMask LCD_CURSORON)++-- | Turn the display off. Note that turning the display off does not mean you are+-- powering it down. It simply means that the characters will not be shown until+-- you turn it back on using 'lcdDisplayOn'. (Also, the contents will /not/ be+-- forgotten when you call this function.) Therefore, this function is useful+-- for temporarily hiding the display contents.+lcdDisplayOff :: LCD -> Arduino ()+lcdDisplayOff = updateDisplayControl "Turning display off" (clearMask LCD_DISPLAYON)++-- | Turn the display on+lcdDisplayOn :: LCD -> Arduino ()+lcdDisplayOn = updateDisplayControl "Turning display on" (setMask LCD_DISPLAYON)++-- | Set writing direction: Left to Right+lcdLeftToRight :: LCD -> Arduino ()+lcdLeftToRight = updateDisplayMode "Setting left-to-right entry mode" (setMask LCD_ENTRYLEFT)++-- | Set writing direction: Right to Left+lcdRightToLeft :: LCD -> Arduino ()+lcdRightToLeft = updateDisplayMode "Setting right-to-left entry mode" (clearMask LCD_ENTRYLEFT)++-- | Turn on auto-scrolling. In the context of the Hitachi44780 controller, this means that+-- each time a letter is added, all the text is moved one space to the left. This can be+-- confusing at first: It does /not/ mean that your strings will continuously scroll:+-- It just means that if you write a string whose length exceeds the column-count+-- of your LCD, then you'll see the tail-end of it. (Of course, this will create a scrolling+-- effect as the string is being printed character by character.)+--+-- Having said that, it is easy to program a scrolling string program: Simply write your string+-- by calling 'lcdWrite', and then use the 'lcdScrollDisplayLeft' and 'lcdScrollDisplayRight' functions+-- with appropriate delays to simulate the scrolling.+lcdAutoScrollOn :: LCD -> Arduino ()+lcdAutoScrollOn = updateDisplayMode "Setting auto-scroll ON" (setMask LCD_ENTRYSHIFTINCREMENT)++-- | Turn off auto-scrolling. See the comments for 'lcdAutoScrollOn' for details. When turned+-- off (which is the default), you will /not/ see the characters at the end of your strings that+-- do not fit into the display.+lcdAutoScrollOff :: LCD -> Arduino ()+lcdAutoScrollOff = updateDisplayMode "Setting auto-scroll OFF" (clearMask LCD_ENTRYSHIFTINCREMENT)++-- | Flash contents of the LCD screen+lcdFlash :: LCD+         -> Int  -- ^ Flash count+         -> Int  -- ^ Delay amount (in milli-seconds)+         -> Arduino ()+lcdFlash lcd n d = sequence_ $ concat $ replicate n [lcdDisplayOff lcd, delay d, lcdDisplayOn lcd, delay d]++-- | An abstract symbol type for user created symbols+newtype LCDSymbol = LCDSymbol Word8++-- | Create a custom symbol for later display. Note that controllers+-- have limited capability for such symbols, typically storing no more+-- than 8. The behavior is undefined if you create more symbols than your+-- LCD can handle.+--+-- The input is a simple description of the glyph, as a list of precisely 8+-- strings, each of which must have 5 characters. Any space character is+-- interpreted as a empty pixel, any non-space is a full pixel, corresponding+-- to the pixel in the 5x8 characters we have on the LCD.  For instance, here's+-- a happy-face glyph you can use:+--+-- >+-- >   [ "     "+-- >   , "@   @"+-- >   , "     "+-- >   , "     "+-- >   , "@   @"+-- >   , " @@@ "+-- >   , "     "+-- >   , "     "+-- >   ]+-- >+lcdCreateSymbol :: LCD -> [String] -> Arduino LCDSymbol+lcdCreateSymbol lcd glyph+  | length glyph /= 8 || any (/= 5) (map length glyph)+  = die "hArduino: lcdCreateSymbol: Invalid glyph description: must be 8x5!" ("Received:" : glyph)+  | True+  = do bs  <- gets boardState+       err <- gets bailOut+       (i, c) <- liftIO $ modifyMVar bs $ \bst ->+                    case lcd `M.lookup` lcds bst of+                      Nothing -> err ("hArduino: Cannot locate " ++ show lcd) []+                      Just ld@LCDData{lcdGlyphCount, lcdController}+                              -> do let ld' = ld { lcdGlyphCount = lcdGlyphCount + 1 }+                                    return (bst{lcds = M.insert lcd ld' (lcds bst)}, (lcdGlyphCount, lcdController))+       sendCmd c (LCD_SETCGRAMADDR i)+       let cvt :: String -> Word8+           cvt s = foldr (.|.) 0 [bit p | (ch, p) <- zip (reverse s) [0..], not (isSpace ch)]+       mapM_ (sendData c . cvt) glyph+       return $ LCDSymbol i++-- | Display a user created symbol on the LCD. (See 'lcdCreateSymbol' for details.)+lcdWriteSymbol :: LCD -> LCDSymbol -> Arduino ()+lcdWriteSymbol lcd (LCDSymbol i) = withLCD lcd ("Writing custom symbol " ++ show i ++ " to LCD") $ \c -> sendData c i++-- | Access an internally stored symbol, one that is not available via its ASCII equivalent. See+-- the Hitachi datasheet for possible values: <http://lcd-linux.sourceforge.net/pdfdocs/hd44780.pdf>, Table 4 on page 17.+--+-- For instance, to access the symbol right-arrow:+--+--   * Locate it in the above table: Right-arrow is at the second-to-last row, 7th character from left.+--+--   * Check the upper/higher bits as specified in the table: For Right-arrow, upper bits are @0111@ and the+--     lower bits are @1110@; which gives us the code @01111110@, or @0x7E@.+--+--   * So, right-arrow can be accessed by symbol code 'lcdInternalSymbol' @0x7E@, which will give us a 'LCDSymbol' value+--   that can be passed to the 'lcdWriteSymbol' function. The code would look like this: @lcdWriteSymbol lcd (lcdInternalSymbol 0x7E)@.+lcdInternalSymbol :: Word8 -> LCDSymbol+lcdInternalSymbol = LCDSymbol
+ System/Hardware/Arduino/Parts/Servo.hs view
@@ -0,0 +1,85 @@+-------------------------------------------------------------------------------------------------+-- |+-- Module      :  System.Hardware.Arduino.Parts.Servo+-- Copyright   :  (c) Levent Erkok+-- License     :  BSD3+-- Maintainer  :  erkokl@gmail.com+-- Stability   :  experimental+--+-- Abstractions for servo motors. See "System.Hardware.Arduino.SamplePrograms.Servo" for+-- example uses.+-------------------------------------------------------------------------------------------------++{-# LANGUAGE NamedFieldPuns #-}+module System.Hardware.Arduino.Parts.Servo(+   -- * Attaching a servo motor on a pin+     Servo, attach+   -- * Setting servo position+   , setAngle+   ) where++import Control.Monad (when)+import Data.Bits     (shiftR, (.&.))+import Data.Maybe    (fromMaybe)++import System.Hardware.Arduino+import System.Hardware.Arduino.Comm+import System.Hardware.Arduino.Data++-- | A servo motor. Note that this type is abstract, use 'attach' to+-- create an instance.+data Servo = Servo { servoPin :: IPin  -- ^ The internal-pin that controls the servo+                   , minPulse :: Int   -- ^ Pulse-width (microseconds) for the minumum (0-degree) angle.+                   , maxPulse :: Int   -- ^ Pulse-width (microseconds) for the maximum (typically 180-degree) angle.+                   }++-- | Create a servo motor instance. The default values for the min/max angle pulse-widths, while typical,+-- may need to be adjusted based on the specs of the actual servo motor. Check the data-sheet for your+-- servo to find the proper values. The default values of @544@ and @2400@ microseconds are typical, so you might+-- want to start by passing 'Nothing' for both parameters and adjusting as necessary.+attach :: Pin            -- ^ Pin controlling the servo. Should be a pin that supports SERVO mode.+       -> Maybe Int      -- ^ Pulse-width (in microseconds) for the minumum 0-degree angle. Default: @544@.+       -> Maybe Int      -- ^ Pulse-width (in microseconds) for the maximum, typically 180-degree, angle. Default: @2400@.+       -> Arduino Servo+attach p mbMin mbMax+  | Just m <- mbMin, m < 0+  = die "Servo.attach: minimum pulse width must be positive" ["Received: " ++ show m]+  | Just m <- mbMax, m < 0+  = die "Servo.attach: maximum pulse width must be positive" ["Received: " ++ show m]+  | True+  = do let minPulse = fromMaybe 544  mbMin+           maxPulse = fromMaybe 2400 mbMax+       debug $ "Attaching servo on pin: " ++ show p ++ " with parameters: " ++ show (minPulse, maxPulse)+       when (minPulse >= maxPulse) $ die "Servo.attach: min pulse duration must be less than max pulse duration"+                                         [ "Received min-pulse: " ++ show minPulse+                                         , "Received max-pulse: " ++ show maxPulse+                                         ]+       setPinMode p SERVO+       (ip, _) <- convertAndCheckPin "Servo.attach" p SERVO+       return Servo { servoPin = ip+                    , minPulse = fromMaybe 544  mbMin+                    , maxPulse = fromMaybe 2400 mbMax+                    }++-- | Set the angle of the servo. The argument should be a number between 0 and 180,+-- indicating the desired angle setting in degrees.+setAngle :: Servo -> Int -> Arduino ()+setAngle Servo{servoPin, minPulse, maxPulse} angle+  | angle < 0 || angle > 180+  = die "Servo.setAngle: angle must be between 0 and 180." ["Received: " ++ show angle]+  | True+  = do let duration = minPulse + ((maxPulse - minPulse) * angle) `div` 180+       debug $ "Setting servo on pin: " ++ show servoPin ++ " " ++ show angle ++ " degrees, via a pulse of " ++ show duration ++ " microseconds."+       -- In arduino, the most we can send is 16383; not that a servo should need such a large value, but+       -- just in case+       when (duration >= 16383) $ die "Servo.setAngle angle setting: out-of-range."+                                      [ "Servo pin         : " ++ show servoPin+                                      , "Angle required    : " ++ show angle+                                      , "Min pulse duration: " ++ show minPulse+                                      , "Max pulse duration: " ++ show maxPulse+                                      , "Duration needed   : " ++ show duration+                                      , "Exceeds max value : 16383"+                                      ]+       let msb = fromIntegral $ (duration `shiftR` 7) .&. 0x7f+           lsb = fromIntegral $ duration .&. 0x7f+       send $ AnalogPinWrite servoPin lsb msb
+ System/Hardware/Arduino/Parts/SevenSegmentCodes.hs view
@@ -0,0 +1,32 @@+-------------------------------------------------------------------------------------------------+-- |+-- Module      :  System.Hardware.Arduino.Parts.SevenSegmentCodes+-- Copyright   :  (c) Levent Erkok+-- License     :  BSD3+-- Maintainer  :  erkokl@gmail.com+-- Stability   :  experimental+--+-- Character to 7-segment display conversion.+-------------------------------------------------------------------------------------------------++module System.Hardware.Arduino.Parts.SevenSegmentCodes(char2SS) where++import Data.Word (Word8)++-- | Convert a character to a bit-pattern, suitable for display on a seven-segment display. +-- Note that most characters are just not representable in a 7-segment display, in which+-- case we map it to 'Nothing'. However, some substitutions are done, for instance '(' is+-- displayed the same as '['.+--+-- The return value is a 'Word8', although only 7-bits are used; the least significant bit will+-- always be 0. With the traditional coding, the bits correspond to segments ABCDEFG0, i.e.,+-- most-significant-bit will be for segment A, next for segment B, and so on.+char2SS :: Char -> Maybe Word8+char2SS = (`lookup` tbl)+ where tbl = [ ('"',  0x44), ('\'', 0x40), ('(',  0x9C), (')',  0xF0), ('-',  0x02), ('0',  0xFC), ('1',  0x60), ('2',  0xDA), ('3',  0xF2), ('4',  0x66), ('5',  0xB6)+             , ('6',  0xBE), ('7',  0xE0), ('8',  0xFE), ('9',  0xF6), ('=',  0x12), ('?',  0xCA), ('A',  0xEE), ('B',  0x3E), ('C',  0x9C), ('D',  0x7A), ('E',  0x9E)+             , ('F',  0x8E), ('G',  0xBC), ('H',  0x6E), ('I',  0x60), ('J',  0x78), ('L',  0x1C), ('N',  0x2A), ('O',  0xFC), ('P',  0xCE), ('R',  0x0A), ('S',  0xB6)+             , ('T',  0x1E), ('U',  0x7C), ('Y',  0x76), ('[',  0x9C), (']',  0xF0), ('_',  0x10), ('a',  0xFA), ('b',  0x3E), ('c',  0x1A), ('d',  0x7A), ('e',  0xDE)+             , ('f',  0x8E), ('g',  0xBC), ('h',  0x2E), ('i',  0x20), ('j',  0x78), ('l',  0x1C), ('n',  0x2A), ('o',  0x3A), ('p',  0xCE), ('q',  0xE7), ('r',  0x0A)+             , ('s',  0xB6), ('t',  0x1E), ('u',  0x38), ('y',  0x76), (' ',  0x00)+             ]
+ System/Hardware/Arduino/Parts/ShiftRegisters.hs view
@@ -0,0 +1,92 @@+-------------------------------------------------------------------------------------------------+-- |+-- Module      :  System.Hardware.Arduino.Parts.ShiftRegisters+-- Copyright   :  (c) Levent Erkok+-- License     :  BSD3+-- Maintainer  :  erkokl@gmail.com+-- Stability   :  experimental+--+-- Abstractions for shift-register IC parts.+-------------------------------------------------------------------------------------------------++{-# LANGUAGE NamedFieldPuns #-}+module System.Hardware.Arduino.Parts.ShiftRegisters(+     -- * Shift register abstraction+     ShiftRegister(..)+     -- * Supported shift-registers+     -- ** Texas Instruments 7HC595+   , SR_74HC595(..)+   ) where++import Data.Foldable (forM_)++import System.Hardware.Arduino+import System.Hardware.Arduino.Data (die)++-- | A shift-register class as supported by the hArduino library.+class ShiftRegister a where+  -- | Capacity+  size :: a -> Int+  -- | Display name+  name :: a -> String+  -- | Data sheet (typically a URL)+  dataSheet :: a -> String+  -- | Initialize the shift-register+  initialize :: a -> Arduino ()+  -- | Disable the output, putting it into high-impedance state+  disable :: a -> Arduino ()+  -- | Enable the output, getting it out of the high-impedance state+  enable :: a -> Arduino ()+  -- | Clear the contents+  clear  :: a -> Arduino ()+  -- | Push a single bit down the shift-register+  push   :: a -> Bool -> Arduino ()+  -- | Store the pushed-in values in the storage register+  store  :: a -> Arduino ()+  -- | Read the current value stored+  read :: a -> Arduino [Bool]++-- | The Texas-Instruments 74HC595 8-bit shift register with 3-state+-- outputs. Data sheet: <http://www.ti.com/lit/ds/symlink/sn74hc595.pdf>.+--+-- This is a versatile 8-bit shift-register with separate serial and register+-- clocks, allowing shifting to be done while the output remains untouched. We+-- model all control pins provided. Note that the enable and clear lines are+-- negated.+data SR_74HC595 = SR_74HC595 {+               serial  :: Pin         -- ^ Chip Pin: 14: Serial input+             , nEnable :: Pin         -- ^ Chip Pin: 13: Negated output-enable+             , rClock  :: Pin         -- ^ Chip Pin: 12: Register clock, positive triggered+             , sClock  :: Pin         -- ^ Chip Pin: 11: Serial clock, positive triggered+             , nClear  :: Pin         -- ^ Chip Pin: 10: Negated clear-data+             , mbBits  :: Maybe [Pin] -- ^ Chip Pins: 15, 1-7, and 8: Sequence of output bits, connect only if reading is necessary+             }++instance ShiftRegister SR_74HC595 where+  size      _ = 8+  name      _ = "TI SR_74HC595"+  dataSheet _ = "http://www.ti.com/lit/ds/symlink/sn74hc595.pdf"+  initialize sr@SR_74HC595{nEnable, serial, rClock, sClock, nClear, mbBits} =+        do mapM_ (`setPinMode` OUTPUT) [nEnable, nClear, serial, rClock, sClock]+           clear sr+           enable sr+           forM_ mbBits (mapM_ (`setPinMode` INPUT))+  disable SR_74HC595{nEnable} = digitalWrite nEnable True+  enable  SR_74HC595{nEnable} = digitalWrite nEnable False+  clear SR_74HC595{nClear}    = do digitalWrite nClear False+                                   digitalWrite nClear True+  push  SR_74HC595{serial, sClock} b = fallingEdge sClock $ digitalWrite serial b+  store SR_74HC595{rClock}           = fallingEdge rClock (return ())+  read  sr@SR_74HC595{mbBits} = case mbBits of+                                Nothing  -> die (name sr ++ ": Not configured for bit-reading")+                                                [ "Datasheet: " ++ dataSheet sr+                                                , "Make sure to set the `bits' field when configuring"+                                                ]+                                Just pins -> mapM digitalRead pins++-- | Execute action, followed by a simulated falling edge on the given clock+fallingEdge :: Pin -> Arduino a -> Arduino a+fallingEdge clk action = do r <- action+                            digitalWrite clk True+                            digitalWrite clk False+                            return r
System/Hardware/Arduino/Protocol.hs view
@@ -41,7 +41,9 @@ package (DigitalReport p b)      = nonSysEx (REPORT_DIGITAL_PORT p) [if b then 1 else 0] package (SetPinMode p m)         = nonSysEx SET_PIN_MODE            [fromIntegral (pinNo p), fromIntegral (fromEnum m)] package (DigitalPortWrite p l m) = nonSysEx (DIGITAL_MESSAGE p)     [l, m]+package (AnalogPinWrite p l m)   = nonSysEx (ANALOG_MESSAGE p)      [l, m] package (SamplingInterval l m)   = sysEx    SAMPLING_INTERVAL       [l, m]+package (Pulse p b dur to)       = sysEx    PULSE                   ([fromIntegral (pinNo p), if b then 1 else 0] ++ concatMap toArduinoBytes (word2Bytes dur ++ word2Bytes to))  -- | Unpackage a SysEx response unpackageSysEx :: [Word8] -> Response@@ -52,12 +54,14 @@       (REPORT_FIRMWARE, majV : minV : rest) -> Firmware majV minV (getString rest)       (CAPABILITY_RESPONSE, bs)             -> Capabilities (getCapabilities bs)       (ANALOG_MAPPING_RESPONSE, bs)         -> AnalogMapping bs+      (PULSE, xs) | length xs == 10         -> let [p, a, b, c, d] = fromArduinoBytes xs in PulseResponse (InternalPin p) (bytes2Words (a, b, c, d))       _                                     -> Unimplemented (Just (show cmd)) args   | True   = Unimplemented Nothing (cmdWord : args)  getCapabilities :: [Word8] -> BoardCapabilities-getCapabilities bs = BoardCapabilities $ M.fromList $ zipWith (\p c -> (p, (Nothing, c))) (map pin [0..]) (map pinCaps (chunk bs))+getCapabilities bs = BoardCapabilities $ M.fromList $ zipWith (\p c -> (p, PinCapabilities{analogPinNumber = Nothing, allowedModes = c}))+                                                              (map InternalPin [(0::Word8)..]) (map pinCaps (chunk bs))   where chunk xs = case break (== 0x7f) xs of                      ([], [])         -> []                      (cur, 0x7f:rest) -> cur : chunk rest
System/Hardware/Arduino/SamplePrograms/Analog.hs view
@@ -32,8 +32,8 @@                cur <- analogRead pot                liftIO $ print cur                go cur-  where led = pin 13-        pot = pin 17 -- NB. Analog-3 is pin 17 on the UNO+  where led = digital 13+        pot = analog 3         go cur = do digitalWrite led True                     delay cur                     digitalWrite led False
System/Hardware/Arduino/SamplePrograms/Blink.hs view
@@ -25,7 +25,7 @@ --  <<http://github.com/LeventErkok/hArduino/raw/master/System/Hardware/Arduino/SamplePrograms/Schematics/Blink.png>> blink :: IO () blink = withArduino False "/dev/cu.usbmodemfd131" $ do-           let led = pin 13+           let led = digital 13            setPinMode led OUTPUT            forever $ do digitalWrite led True                         delay 1000
System/Hardware/Arduino/SamplePrograms/Button.hs view
@@ -31,8 +31,8 @@             setPinMode led OUTPUT             setPinMode pb  INPUT             go =<< digitalRead pb- where pb   = pin 2-       led  = pin 13+ where pb   = digital 2+       led  = digital 13        go s = do liftIO $ putStrLn $ "Button is currently " ++ if s then "ON" else "OFF"                  digitalWrite led s                  go =<< waitFor pb
System/Hardware/Arduino/SamplePrograms/Counter.hs view
@@ -31,9 +31,9 @@             setPinMode bUp   INPUT             setPinMode bDown INPUT             update (0::Int)- where bUp   = pin 4-       bDown = pin 2-       led   = pin 13+ where bUp   = digital 4+       bDown = digital 2+       led   = digital 13        update curVal = do                 liftIO $ print curVal                 digitalWrite led (curVal == 0)
+ System/Hardware/Arduino/SamplePrograms/Distance.hs view
@@ -0,0 +1,64 @@+-------------------------------------------------------------------------------+-- |+-- Module      :  System.Hardware.Arduino.SamplePrograms.Distance+-- Copyright   :  (c) Levent Erkok+-- License     :  BSD3+-- Maintainer  :  erkokl@gmail.com+-- Stability   :  experimental+--+-- Measuring distance using a HC-SR04 sensor. (Data sheet: <http://www.micropik.com/PDF/HCSR04.pdf>.)+--+-- NB. As of March 2 2013; StandardFirmata that's distributed with the Arduino-App does /not/ support the high+-- accuracy pulse-in command, which is needed for this sketch.  However, there is a patch to add this+-- command; see: <http://github.com/rwldrn/johnny-five/issues/18> for details on how to install it. You /should/+-- have this patched version of Firmata running on your board for this sketch to function properly.+--+-- Accuracy: Keep in mind that measurements on a platform like Arduino is always subject to+-- various errors. Relying on this program for precise distance measurements would be a mistake.+-- The results here should be accurate to within about half-a-centimeter, provided you stay+-- within the range of HC-SR04, which is between 2 to 400 cm. For anything more precise than+-- this, you'll need to use a much more sensitive sensor.+-------------------------------------------------------------------------------++module System.Hardware.Arduino.SamplePrograms.Distance where++import Control.Monad       (forever)+import Control.Monad.Trans (liftIO)+import Numeric             (showGFloat)++import System.Hardware.Arduino++-- | Sound travels 343.2 meters per seconds (<http://en.wikipedia.org/wiki/Speed_of_sound>).+-- The echo time is round-trip, from the sensor to the object and back. Thus, if echo is high+-- for @d@ microseconds, then the distance in centimeters is:+--+--    @+--        d * 10^-6 * 343.2 * 10^2 / 2+--      = 1.716e-2 * d+--    @+microSecondsToCentimeters :: Int -> Float+microSecondsToCentimeters d = 1.716e-2 * fromIntegral d++-- | Measure and display the distance continuously, as reported by an HC-SR04 sensor.+--+-- Wiring: Simply connect VCC and GND of HC-SR04 to Arduino as usual. The @Echo@ line on the sensor is connected+-- to Arduino pin 2. The @Trig@ line is connected on the board to the @Echo@ line, i.e., they both connect to the+-- same pin on the Arduino. We also have a led on pin 13 that we will light-up+-- if the distance detected is less than 2 centimeters, indicating an impending crash!+--+--  <<http://github.com/LeventErkok/hArduino/raw/master/System/Hardware/Arduino/SamplePrograms/Schematics/Distance.png>>+distance :: IO ()+distance = withArduino False "/dev/cu.usbmodemfd131" $ do+             setPinMode sensor INPUT+             setPinMode led    OUTPUT+             update+ where sensor = digital 2+       led    = digital 13+       measure = do mbd <- pulse sensor True 10 Nothing+                    case mbd of+                      Nothing -> liftIO $ putStrLn "Distance: No measurement received."+                      Just d  -> do let c = microSecondsToCentimeters d+                                    liftIO $ putStrLn $ "Distance: " ++ showGFloat (Just 2) c " centimeters."+                                    digitalWrite led (c < 2)+       update = forever $ do measure+                             delay 1000
System/Hardware/Arduino/SamplePrograms/LCD.hs view
@@ -16,7 +16,7 @@ import Numeric             (showHex)  import System.Hardware.Arduino-import System.Hardware.Arduino.LCD+import System.Hardware.Arduino.Parts.LCD  -- | Connections for a basic hitachi controller. -- See <http://en.wikipedia.org/wiki/Hitachi_HD44780_LCD_controller> for@@ -49,14 +49,14 @@ -- --  <<http://github.com/LeventErkok/hArduino/raw/master/System/Hardware/Arduino/SamplePrograms/Schematics/LCD.png>> hitachi :: LCDController--- Connections:                   ARDUINO     Hitachi   Description---------------------------------  -------    ---------  -----------------hitachi = Hitachi44780 { lcdRS   = pin 12  --     4      Register-select-                       , lcdEN   = pin 11  --     6      Enable-                       , lcdD4   = pin  5  --    11      Data 4-                       , lcdD5   = pin  4  --    12      Data 5-                       , lcdD6   = pin  3  --    13      Data 6-                       , lcdD7   = pin  2  --    14      Data 7+-- Connections:                    ARDUINO        Hitachi   Description+--------------------------------   -----------    ---------  ----------------+hitachi = Hitachi44780 { lcdRS   = digital 12  --     4      Register-select+                       , lcdEN   = digital 11  --     6      Enable+                       , lcdD4   = digital  5  --    11      Data 4+                       , lcdD5   = digital  4  --    12      Data 5+                       , lcdD6   = digital  3  --    13      Data 6+                       , lcdD7   = digital  2  --    14      Data 7                        -- Other config variables for the display                        , lcdRows     = 2    -- 2 rows                        , lcdCols     = 16    -- of 16 columns
+ System/Hardware/Arduino/SamplePrograms/PulseIn.hs view
@@ -0,0 +1,40 @@+-------------------------------------------------------------------------------+-- |+-- Module      :  System.Hardware.Arduino.SamplePrograms.PulseIn+-- Copyright   :  (c) Levent Erkok+-- License     :  BSD3+-- Maintainer  :  erkokl@gmail.com+-- Stability   :  experimental+--+-- Computes the time a button is held pressed, demonstrating the use of+-- the 'pulseIn_hostOnly' function with a time-out.+-------------------------------------------------------------------------------++module System.Hardware.Arduino.SamplePrograms.PulseIn where++import Control.Monad       (forever)+import Control.Monad.Trans (liftIO)++import System.Hardware.Arduino++-- | Computes the amount of time a push-button is connected to+-- input pin 2 on the Arduino. We will wait for at most 5 seconds,+-- as a further demonstration of the time-out facility. Note that the+-- timing is done on the host side, so this measurement is inherently+-- inaccurate.+--+-- The wiring is straightforward: Simply put a push-button between+-- digital input 2 and +5V, guarded by a 10K resistor:+--+--  <<http://github.com/LeventErkok/hArduino/raw/master/System/Hardware/Arduino/SamplePrograms/Schematics/Pulse.png>>+pulseDemo :: IO ()+pulseDemo = withArduino False "/dev/cu.usbmodemfd131" $ do+               setPinMode pb INPUT+               go+ where pb = digital 2+       go = forever $ do+              liftIO $ putStr "Ready, push-and-hold for less than 5 seconds: "+              mbDur <- pulseIn_hostTiming pb True (Just 5000000)+              liftIO $ putStrLn $ case mbDur of+                Nothing -> "Time out!"+                Just d  -> "Button stayed high for: " ++ show d ++ " micro-seconds"
+ System/Hardware/Arduino/SamplePrograms/PulseOut.hs view
@@ -0,0 +1,36 @@+-------------------------------------------------------------------------------+-- |+-- Module      :  System.Hardware.Arduino.SamplePrograms.PulseOut+-- Copyright   :  (c) Levent Erkok+-- License     :  BSD3+-- Maintainer  :  erkokl@gmail.com+-- Stability   :  experimental+--+-- Demonstrates the use of pulseOut+-------------------------------------------------------------------------------++module System.Hardware.Arduino.SamplePrograms.PulseOut where++import Control.Monad       (forever)+import Control.Monad.Trans (liftIO)++import System.Hardware.Arduino++-- | Send pulses on a led as requested by the user. Note that the timing is computed+-- on the host side, thus the duration of the pulse is subject to some error due to+-- the Firmata communication overhead.+--+-- Wiring: Simply a led on pin 13:+--+--  <<http://github.com/LeventErkok/hArduino/raw/master/System/Hardware/Arduino/SamplePrograms/Schematics/Blink.png>>+pulseDemo :: IO ()+pulseDemo = withArduino False "/dev/cu.usbmodemfd131" $ do+              setPinMode led  OUTPUT+              digitalWrite led False+              forever trigger+ where led  = digital 13+       trigger = do liftIO $ putStr "Pulse duration? (microseconds) "+                    d <- liftIO getLine+                    case reads d of+                     [(v, "")] -> pulseOut_hostTiming led True 0 v+                     _         -> liftIO $ putStrLn "Please enter a number."
+ System/Hardware/Arduino/SamplePrograms/Servo.hs view
@@ -0,0 +1,68 @@+-------------------------------------------------------------------------------+-- |+-- Module      :  System.Hardware.Arduino.SamplePrograms.Servo+-- Copyright   :  (c) Levent Erkok+-- License     :  BSD3+-- Maintainer  :  erkokl@gmail.com+-- Stability   :  experimental+--+-- Demonstrates basic Servo motor control+-------------------------------------------------------------------------------++module System.Hardware.Arduino.SamplePrograms.Servo where++import Control.Monad       (forever)+import Control.Monad.Trans (liftIO)+import Data.Char           (toLower)++import System.Hardware.Arduino+import System.Hardware.Arduino.Parts.Servo++-- | Control a servo, by executing user requests of blade movement.  We allow 3 user commands:+--+--    * @l@ to swipe from angle-0 to 180;+--+--    * @r@ to swipe from angle-180 to 0;+--+--    * Or any number between @0@ to @180@, which puts the servo to the desired position.+--+-- Almost any servo motor would work with this example, though you should make sure to adjust min/max pulse durations+-- in the 'attach' command to match the datasheet of the servo you have. In this example, we have used the HS-55 feather+-- servo (<http://www.servocity.com/html/hs-55_sub-micro.html>), which has the values 600 to 2400 micro-seconds.+--+-- To connect the servo to the Arduino, simply connect the VCC (red) and the GND (black) appropriately, and the signal line (white)+-- to any SERVO capable pin, in this example we're using pin number 9:+--+--  <<http://github.com/LeventErkok/hArduino/raw/master/System/Hardware/Arduino/SamplePrograms/Schematics/Servo.png>>+servo :: IO ()+servo = withArduino False "/dev/cu.usbmodemfd131" $ do+            s <- attach (digital 9) (Just 600) (Just 2400)+            forever (demo s)+ where demo s = do liftIO $ putStr "Enter l, r or the desired servo angle: "+                   a <- liftIO getLine+                   case (map toLower a, reads a) of+                    ("l", _) -> mapM_ move [0 .. 180]+                    ("r", _) -> mapM_ move [180, 179 .. 0]+                    (_,  [(v, "")]) | 0 <= v && v <= 180+                             -> setAngle s v+                    _        -> liftIO $ putStrLn "Invalid entry."+         where move a = setAngle s a >> delay 100++-- | Control a servo, as guided by the input read from a potentiometer. The set-up is similar to the 'servo' example+-- above, except instead of querying the user for the angle, we use the readings from a potentiometer connected to+-- analog input number 2. We used a 10 KOhm potentiometer, but other pots would work just as well too:+--+--  <<http://github.com/LeventErkok/hArduino/raw/master/System/Hardware/Arduino/SamplePrograms/Schematics/ServoAnalog.png>>+servoAnalog :: IO ()+servoAnalog = withArduino False "/dev/cu.usbmodemfd131" $ do+                 s <- attach (digital 9) (Just 600) (Just 2400)+                 setPinMode pot ANALOG+                 liftIO $ putStrLn "Adjust the potentiometer to control the servo!"+                 forever (demo s)+ where pot = analog 2+       demo s = do v <- analogRead pot+                   setAngle s (cvt v)+                   delay 100+       -- Analog input will be from 0 to 1023; convert it to+       -- angles, mapping 1023 to 0-degrees, and 0 to 180+       cvt i = ((1023-i) * 180) `div` 1023
+ System/Hardware/Arduino/SamplePrograms/SevenSegment.hs view
@@ -0,0 +1,71 @@+-------------------------------------------------------------------------------+-- |+-- Module      :  System.Hardware.Arduino.SamplePrograms.SevenSegment+-- Copyright   :  (c) Levent Erkok+-- License     :  BSD3+-- Maintainer  :  erkokl@gmail.com+-- Stability   :  experimental+--+-- Control a single seven-segment display, echoing user's key presses+-- on it verbatim. We use a shift-register to reduce the number of+-- pins we need on the Arduino to control the display.+-------------------------------------------------------------------------------++module System.Hardware.Arduino.SamplePrograms.SevenSegment  where++import Control.Monad       (forever)+import Control.Monad.Trans (liftIO)+import Data.Bits           (testBit)+import Data.Word           (Word8)+import System.IO           (hSetBuffering, stdin, BufferMode(NoBuffering))++import System.Hardware.Arduino+import System.Hardware.Arduino.Parts.ShiftRegisters+import System.Hardware.Arduino.Parts.SevenSegmentCodes++-- | Connections for the Texas Instruments 74HC595 shift-register. Datasheet: <http://www.ti.com/lit/ds/symlink/sn74hc595.pdf>.+-- In our circuit, we merely use pins 8 thru 12 on the Arduino to control the 'serial', 'enable', 'rClock', 'sClock', and 'nClear'+-- lines, respectively. Since we do not need to read the output of the shift-register, we leave the 'bits' field unconnected.+sr :: SR_74HC595+sr = SR_74HC595 { serial  = digital 8+                , nEnable = digital 9+                , rClock  = digital 10+                , sClock  = digital 11+                , nClear  = digital 12+                , mbBits  = Nothing+                }++-- | Seven-segment display demo. For each key-press, we display an equivalent pattern+-- on the connected 7-segment-display. Note that most characters are not-mappable, so+-- we use approximations if available. We use a shift-register to reduce the pin+-- requirements on the Arduino, setting the bits serially.+--+-- Parts:+--+--   * The seven-segment digit we use is a common-cathode single-digit display, such as+--     TDSG5150 (<http://www.vishay.com/docs/83126/83126.pdf>), or Microvity's IS121,+--     but almost any such digit would do. Just pay attention to the line-connections,+--     and do not forget the limiting resistors: 220 ohm's should do nicely.+--+--   * The shift-register is Texas-Instruments 74HC595: <http://www.ti.com/lit/ds/symlink/sn74hc595.pdf>.+--     Make sure to connect the register output lines to the seven-segment displays with the corresponding+--     letters. That is, shift-registers @Q_A@ (Chip-pin 15) should connect to segment @A@; @Q_B@ (Chip-pin 1)+--     to segment @B@, and so on. We do not use the shift-register @Q_H'@ (Chip-pin 9) in this design.+--+--  <<http://github.com/LeventErkok/hArduino/raw/master/System/Hardware/Arduino/SamplePrograms/Schematics/SevenSegment.png>>+sevenSegment :: IO ()+sevenSegment = withArduino False "/dev/cu.usbmodemfd131" $ do+                  initialize sr+                  liftIO $ do hSetBuffering stdin NoBuffering+                              putStrLn "Seven-Segment-Display demo."+                              putStrLn "For each key-press, we will try to display it as a 7-segment character."+                              putStrLn "If there is no good mapping (which is common), we'll just display a dot."+                              putStrLn ""+                              putStrLn "Press-keys to be shown on the display, Ctrl-C to quit.."+                  forever repl+ where pushWord w = do mapM_ (push sr) [w `testBit` i | i <- [0..7]]+                       store sr+       repl = do c <- liftIO getChar+                 case char2SS c of+                   Just w  -> pushWord w+                   Nothing -> pushWord (0x01::Word8) -- the dot, which also nicely covers the '.'
System/Hardware/Arduino/Utils.hs view
@@ -11,25 +11,17 @@ module System.Hardware.Arduino.Utils where  import Control.Concurrent (threadDelay)-import Control.Monad      (void)-import Data.Bits          ((.|.), shiftL)+import Data.Bits          ((.|.), shiftL, (.&.), shiftR) import Data.Char          (isAlphaNum, isAscii, isSpace, chr) import Data.IORef         (newIORef, readIORef, writeIORef) import Data.List          (intercalate)-import Data.Word          (Word8)-import System.Process     (system)-import System.Info        (os)-import Numeric            (showHex, showIntAtBase, showFFloat)+import Data.Word          (Word8, Word32)+import Data.Time          (getCurrentTime, utctDayTime)+import Numeric            (showHex, showIntAtBase)  -- | Delay (wait) for the given number of milli-seconds--- NB. The 'threadDelay' function is broken on Mac, see <http://hackage.haskell.org/trac/ghc/ticket/7299>--- Until there's a new GHC release that fixes this issue, we temporarily use system/sleep on Mac. delay :: Int -> IO ()-delay n-  | os == "darwin"-  = void $ system $ "sleep " ++ showFFloat (Just 2) (fromIntegral n / (1000 :: Double)) ""-  | True-  = threadDelay (n*1000)+delay n = threadDelay (n*1000)  -- | A simple printer that can keep track of sequence numbers. Used for debugging purposes. mkDebugPrinter :: Bool -> IO (String -> IO ())@@ -38,7 +30,10 @@         cnt <- newIORef (1::Int)         let f s = do i <- readIORef cnt                      writeIORef cnt (i+1)-                     putStrLn $ "[" ++ show i ++ "] hArduino: " ++ s+                     tick <- utctDayTime `fmap` getCurrentTime+                     let precision = 1000000 :: Integer+                         micro = round . (fromIntegral precision *) . toRational $ tick+                     putStrLn $ "[" ++ show i ++ ":" ++ show (micro :: Integer) ++ "] hArduino: " ++ s         return f  -- | Show a byte in a visible format.@@ -59,7 +54,28 @@  -- | Turn a lo/hi encoded Arduino string constant into a Haskell string getString :: [Word8] -> String-getString []         = ""-getString [a]        = [chr (fromIntegral a)]  -- shouldn't happen-getString (l:h:rest) = c : getString rest-  where c = chr $ fromIntegral $ h `shiftL` 8 .|. l+getString = map (chr . fromIntegral) . fromArduinoBytes++-- | Turn a lo/hi encoded Arduino sequence into a bunch of words, again weird+-- encoding.+fromArduinoBytes :: [Word8] -> [Word8]+fromArduinoBytes []         = []+fromArduinoBytes [x]        = [x]  -- shouldn't really happen+fromArduinoBytes (l:h:rest) = c : fromArduinoBytes rest+  where c = h `shiftL` 7 .|. l -- first seven bit comes from l; then extra stuff is in h++-- | Turn a normal byte into a lo/hi Arduino byte. If you think this encoding+-- is just plain weird, you're not alone. (I suspect it has something to do+-- with error-correcting low-level serial communication of the past.)+toArduinoBytes :: Word8 -> [Word8]+toArduinoBytes w = [lo, hi]+  where lo =  w             .&. 0x7F   -- first seven bits+        hi = (w `shiftR` 7) .&. 0x7F   -- one extra high-bit++-- | Convert a word to it's bytes, as would be required by Arduino comms+word2Bytes :: Word32 -> [Word8]+word2Bytes i = map fromIntegral [(i `shiftR` 24) .&. 0xFF, (i `shiftR` 16) .&. 0xFF, (i `shiftR`  8) .&. 0xFF, i .&. 0xFF]++-- | Inverse conversion for word2Bytes+bytes2Words :: (Word8, Word8, Word8, Word8) -> Word32+bytes2Words (a, b, c, d) = fromIntegral a `shiftL` 24 .|. fromIntegral b `shiftL` 16 .|. fromIntegral c `shiftL` 8 .|. fromIntegral d
hArduino.cabal view
@@ -1,5 +1,5 @@ Name:          hArduino-Version:       0.3+Version:       0.4 Category:      Hardware Synopsis:      Control your Arduino board from Haskell. Description:   Control Arduino from Haskell, using the Firmata protocol.@@ -33,18 +33,23 @@ Library   default-language: Haskell2010   ghc-options     : -Wall-  Build-depends   : base  >= 4 && < 5, serialport, bytestring-                  , mtl , unix, containers-                  -- NB. process is only needed for "sleep", since-                  -- threaddelay is broken on Mac. See: http://hackage.haskell.org/trac/ghc/ticket/7299-                  , process+  Build-depends   : base  >= 4 && < 5, serialport >= 0.4.5, bytestring, mtl, unix, containers, time   Exposed-modules : System.Hardware.Arduino-                  , System.Hardware.Arduino.LCD+                  , System.Hardware.Arduino.Parts+                  , System.Hardware.Arduino.Parts.LCD+                  , System.Hardware.Arduino.Parts.ShiftRegisters+                  , System.Hardware.Arduino.Parts.SevenSegmentCodes+                  , System.Hardware.Arduino.Parts.Servo                   , System.Hardware.Arduino.SamplePrograms.Analog                   , System.Hardware.Arduino.SamplePrograms.Blink                   , System.Hardware.Arduino.SamplePrograms.Button                   , System.Hardware.Arduino.SamplePrograms.Counter+                  , System.Hardware.Arduino.SamplePrograms.Distance                   , System.Hardware.Arduino.SamplePrograms.LCD+                  , System.Hardware.Arduino.SamplePrograms.PulseIn+                  , System.Hardware.Arduino.SamplePrograms.PulseOut+                  , System.Hardware.Arduino.SamplePrograms.SevenSegment+                  , System.Hardware.Arduino.SamplePrograms.Servo   Other-modules   : System.Hardware.Arduino.Comm                   , System.Hardware.Arduino.Data                   , System.Hardware.Arduino.Firmata