--------------------------------------------------------------------------------------------------- |-- 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 #-}{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}moduleSystem.Hardware.Arduino.Parts.LCD(-- * LCD types and registrationLCD ,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 )whereimportControl.Concurrent(modifyMVar,withMVar)importControl.Monad(when)importControl.Monad.State(gets,liftIO)importData.Bits(testBit,(.|.),(.&.),setBit,clearBit,shiftL,bit)importData.Char(ord,isSpace)importData.Maybe(fromMaybe)importData.Word(Word8)importqualifiedData.MapasMimportSystem.Hardware.Arduino.Data importSystem.Hardware.Arduino.Firmata importqualifiedSystem.Hardware.Arduino.Utils asUimportSystem.Exit(exitFailure)----------------------------------------------------------------------------------------- Low level interface, not available to the user----------------------------------------------------------------------------------------- | Commands understood by HitachidataCmd =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-wordgetCmdVal ::LCDController ->Cmd ->Word8getCmdVal :: LCDController -> Cmd -> Word8 getCmdVal Hitachi44780 {Int lcdRows :: LCDController -> Int lcdRows :: Int lcdRows ,Bool dotMode5x10 :: LCDController -> Bool dotMode5x10 :: Bool dotMode5x10 }=Cmd -> Word8 get wheremultiLine :: Word8 multiLine -- bit 3|Int lcdRows forall a. Ord a => a -> a -> Bool >Int 1=Word8 0x08::Word8|Bool True=Word8 0x00::Word8dotMode :: Word8 dotMode -- bit 2|Bool dotMode5x10 =Word8 0x04::Word8|Bool True=Word8 0x00::Word8displayFunction :: Word8 displayFunction =Word8 multiLine forall a. Bits a => a -> a -> a .|.Word8 dotMode get :: Cmd -> Word8 get Cmd LCD_INITIALIZE =Word8 0x33get Cmd LCD_INITIALIZE_END =Word8 0x32get Cmd LCD_FUNCTIONSET =Word8 0x20forall a. Bits a => a -> a -> a .|.Word8 displayFunction get (LCD_DISPLAYCONTROL Word8 w )=Word8 0x08forall a. Bits a => a -> a -> a .|.Word8 w get Cmd LCD_CLEARDISPLAY =Word8 0x01get (LCD_ENTRYMODESET Word8 w )=Word8 0x04forall a. Bits a => a -> a -> a .|.Word8 w get Cmd LCD_RETURNHOME =Word8 0x02get (LCD_SETDDRAMADDR Word8 w )=Word8 0x80forall a. Bits a => a -> a -> a .|.Word8 w get (LCD_CURSORSHIFT Word8 w )=Word8 0x10forall a. Bits a => a -> a -> a .|.Word8 0x08forall a. Bits a => a -> a -> a .|.Word8 w -- NB. LCD_DISPLAYMOVE (0x08) hard coded hereget (LCD_SETCGRAMADDR Word8 w )=Word8 0x40forall a. Bits a => a -> a -> a .|.Word8 w forall a. Bits a => a -> Int -> a `shiftL`Int 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 -> LCDController -> Arduino () initLCD LCD lcd c :: LCDController c @Hitachi44780 {Pin lcdRS :: LCDController -> Pin lcdRS :: Pin lcdRS ,Pin lcdEN :: LCDController -> Pin lcdEN :: Pin lcdEN ,Pin lcdD4 :: LCDController -> Pin lcdD4 :: Pin lcdD4 ,Pin lcdD5 :: LCDController -> Pin lcdD5 :: Pin lcdD5 ,Pin lcdD6 :: LCDController -> Pin lcdD6 :: Pin lcdD6 ,Pin lcdD7 :: LCDController -> Pin lcdD7 :: Pin lcdD7 }=doString -> Arduino () debug String "Starting the LCD initialization sequence"forall (t :: * -> *) (m :: * -> *) a b. (Foldable t, Monad m) => (a -> m b) -> t a -> m () mapM_(Pin -> PinMode -> Arduino () `setPinMode` PinMode OUTPUT )[Pin lcdRS ,Pin lcdEN ,Pin lcdD4 ,Pin lcdD5 ,Pin lcdD6 ,Pin lcdD7 ]-- Wait for 50ms, data-sheet says at least 40ms for 2.7V version, so be safeInt -> Arduino () delay Int 50LCDController -> Cmd -> Arduino () sendCmd LCDController c Cmd LCD_INITIALIZE Int -> Arduino () delay Int 5LCDController -> Cmd -> Arduino () sendCmd LCDController c Cmd LCD_INITIALIZE_END LCDController -> Cmd -> Arduino () sendCmd LCDController c Cmd LCD_FUNCTIONSET LCD -> Arduino () lcdCursorOff LCD lcd LCD -> Arduino () lcdBlinkOff LCD lcd LCD -> Arduino () lcdLeftToRight LCD lcd LCD -> Arduino () lcdAutoScrollOff LCD lcd LCD -> Arduino () lcdHome LCD lcd LCD -> Arduino () lcdClear LCD lcd LCD -> Arduino () lcdDisplayOn LCD lcd -- | Get the controller associated with the LCDgetController ::LCD ->Arduino LCDController getController :: LCD -> Arduino LCDController getController LCD lcd =doMVar BoardState bs <-forall s (m :: * -> *) a. MonadState s m => (s -> a) -> m a getsArduinoState -> MVar BoardState boardState String -> [String] -> IO () err <-forall s (m :: * -> *) a. MonadState s m => (s -> a) -> m a getsArduinoState -> String -> [String] -> IO () bailOut forall (m :: * -> *) a. MonadIO m => IO a -> m a liftIOforall a b. (a -> b) -> a -> b $forall a b. MVar a -> (a -> IO b) -> IO b withMVarMVar BoardState bs forall a b. (a -> b) -> a -> b $\BoardState bst ->caseLCD lcd forall k a. Ord k => k -> Map k a -> Maybe a `M.lookup`BoardState -> Map LCD LCDData lcds BoardState bst ofMaybe LCDData Nothing->doString -> [String] -> IO () err (String "hArduino: Cannot locate "forall a. [a] -> [a] -> [a] ++forall a. Show a => a -> String showLCD lcd )[]forall a. IO a exitFailureJustLCDData ld ->forall (m :: * -> *) a. Monad m => a -> m a returnforall a b. (a -> b) -> a -> b $LCDData -> LCDController lcdController LCDData ld -- | Send a command to the LCD controllersendCmd ::LCDController ->Cmd ->Arduino ()sendCmd :: LCDController -> Cmd -> Arduino () sendCmd LCDController c =Bool -> LCDController -> Word8 -> Arduino () transmit Bool FalseLCDController c forall b c a. (b -> c) -> (a -> b) -> a -> c .LCDController -> Cmd -> Word8 getCmdVal LCDController c -- | Send 4-bit data to the LCD controllersendData ::LCDController ->Word8->Arduino ()sendData :: LCDController -> Word8 -> Arduino () sendData LCDController lcd Word8 n =doString -> Arduino () debug forall a b. (a -> b) -> a -> b $String "Transmitting LCD data: "forall a. [a] -> [a] -> [a] ++Word8 -> String U.showByte Word8 n Bool -> LCDController -> Word8 -> Arduino () transmit Bool TrueLCDController lcd Word8 n -- | By controlling the enable-pin, indicate to the controller that-- the data is ready for it to process.pulseEnable ::LCDController ->Arduino ()pulseEnable :: LCDController -> Arduino () pulseEnable Hitachi44780 {Pin lcdEN :: Pin lcdEN :: LCDController -> Pin lcdEN }=doString -> Arduino () debug String "Sending LCD pulseEnable"Pin -> Bool -> Arduino () digitalWrite Pin lcdEN Bool FalseInt -> Arduino () delay Int 1Pin -> Bool -> Arduino () digitalWrite Pin lcdEN Bool TrueInt -> Arduino () delay Int 1Pin -> Bool -> Arduino () digitalWrite Pin lcdEN Bool FalseInt -> Arduino () delay Int 1-- | Transmit data down to the LCDtransmit ::Bool->LCDController ->Word8->Arduino ()transmit :: Bool -> LCDController -> Word8 -> Arduino () transmit Bool mode c :: LCDController c @Hitachi44780 {Pin lcdRS :: Pin lcdRS :: LCDController -> Pin lcdRS ,Pin lcdEN :: Pin lcdEN :: LCDController -> Pin lcdEN ,Pin lcdD4 :: Pin lcdD4 :: LCDController -> Pin lcdD4 ,Pin lcdD5 :: Pin lcdD5 :: LCDController -> Pin lcdD5 ,Pin lcdD6 :: Pin lcdD6 :: LCDController -> Pin lcdD6 ,Pin lcdD7 :: Pin lcdD7 :: LCDController -> Pin lcdD7 }Word8 val =doPin -> Bool -> Arduino () digitalWrite Pin lcdRS Bool mode Pin -> Bool -> Arduino () digitalWrite Pin lcdEN Bool Falselet[Bool b7 ,Bool b6 ,Bool b5 ,Bool b4 ,Bool b3 ,Bool b2 ,Bool b1 ,Bool b0 ]=[Word8 val forall a. Bits a => a -> Int -> Bool `testBit`Int i |Int i <-[Int 7,Int 6..Int 0]]-- Send down the first 4 bitsPin -> Bool -> Arduino () digitalWrite Pin lcdD4 Bool b4 Pin -> Bool -> Arduino () digitalWrite Pin lcdD5 Bool b5 Pin -> Bool -> Arduino () digitalWrite Pin lcdD6 Bool b6 Pin -> Bool -> Arduino () digitalWrite Pin lcdD7 Bool b7 LCDController -> Arduino () pulseEnable LCDController c -- Send down the remaining batchPin -> Bool -> Arduino () digitalWrite Pin lcdD4 Bool b0 Pin -> Bool -> Arduino () digitalWrite Pin lcdD5 Bool b1 Pin -> Bool -> Arduino () digitalWrite Pin lcdD6 Bool b2 Pin -> Bool -> Arduino () digitalWrite Pin lcdD7 Bool b3 LCDController -> Arduino () pulseEnable LCDController c -- | Helper function to simplify library programming, not exposed to the user.withLCD ::LCD ->String->(LCDController ->Arduino a )->Arduino a withLCD :: forall a. LCD -> String -> (LCDController -> Arduino a) -> Arduino a withLCD LCD lcd String what LCDController -> Arduino a action =doString -> Arduino () debug String what LCDController c <-LCD -> Arduino LCDController getController LCD lcd LCDController -> Arduino a action LCDController 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 :: LCDController -> Arduino LCD lcdRegister LCDController controller =doMVar BoardState bs <-forall s (m :: * -> *) a. MonadState s m => (s -> a) -> m a getsArduinoState -> MVar BoardState boardState LCD lcd <-forall (m :: * -> *) a. MonadIO m => IO a -> m a liftIOforall a b. (a -> b) -> a -> b $forall a b. MVar a -> (a -> IO (a, b)) -> IO b modifyMVarMVar BoardState bs forall a b. (a -> b) -> a -> b $\BoardState bst ->doletn :: Int n =forall k a. Map k a -> Int M.sizeforall a b. (a -> b) -> a -> b $BoardState -> Map LCD LCDData lcds BoardState bst ld :: LCDData ld =LCDData {lcdDisplayMode :: Word8 lcdDisplayMode =Word8 0,lcdDisplayControl :: Word8 lcdDisplayControl =Word8 0,lcdGlyphCount :: Word8 lcdGlyphCount =Word8 0,lcdController :: LCDController lcdController =LCDController controller }forall (m :: * -> *) a. Monad m => a -> m a return(BoardState bst {lcds :: Map LCD LCDData lcds =forall k a. Ord k => k -> a -> Map k a -> Map k a M.insert(Int -> LCD LCD Int n )LCDData ld (BoardState -> Map LCD LCDData lcds BoardState bst )},Int -> LCD LCD Int n )caseLCDController controller ofHitachi44780 {}->LCD -> LCDController -> Arduino () initLCD LCD lcd LCDController controller forall (m :: * -> *) a. Monad m => a -> m a returnLCD lcd -- | Write a string on the LCD at the current cursor positionlcdWrite ::LCD ->String->Arduino ()lcdWrite :: LCD -> String -> Arduino () lcdWrite LCD lcd String m =forall a. LCD -> String -> (LCDController -> Arduino a) -> Arduino a withLCD LCD lcd (String "Writing "forall a. [a] -> [a] -> [a] ++forall a. Show a => a -> String showString m forall a. [a] -> [a] -> [a] ++String " to LCD")forall a b. (a -> b) -> a -> b $\LCDController c ->forall (t :: * -> *) (m :: * -> *) a b. (Foldable t, Monad m) => (a -> m b) -> t a -> m () mapM_(LCDController -> Word8 -> Arduino () sendData LCDController c )[Word8] m' wherem' :: [Word8] m' =forall a b. (a -> b) -> [a] -> [b] map(\Char ch ->forall a b. (Integral a, Num b) => a -> b fromIntegral(Char -> Int ordChar ch )forall a. Bits a => a -> a -> a .&.Word8 0xFF)String m -- | Clear the LCDlcdClear ::LCD ->Arduino ()lcdClear :: LCD -> Arduino () lcdClear LCD lcd =forall a. LCD -> String -> (LCDController -> Arduino a) -> Arduino a withLCD LCD lcd String "Sending clearLCD"forall a b. (a -> b) -> a -> b $\LCDController c ->doLCDController -> Cmd -> Arduino () sendCmd LCDController c Cmd LCD_CLEARDISPLAY Int -> Arduino () delay Int 2-- give some time to make sure LCD is really cleared-- | Send the cursor to home positionlcdHome ::LCD ->Arduino ()lcdHome :: LCD -> Arduino () lcdHome LCD lcd =forall a. LCD -> String -> (LCDController -> Arduino a) -> Arduino a withLCD LCD lcd String "Sending the cursor home"forall a b. (a -> b) -> a -> b $\LCDController c ->doLCDController -> Cmd -> Arduino () sendCmd LCDController c Cmd LCD_RETURNHOME Int -> Arduino () delay Int 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 -> (Int, Int) -> Arduino () lcdSetCursor LCD lcd (Int givenCol ,Int givenRow )=forall a. LCD -> String -> (LCDController -> Arduino a) -> Arduino a withLCD LCD lcd (String "Sending the cursor to Row: "forall a. [a] -> [a] -> [a] ++forall a. Show a => a -> String showInt givenRow forall a. [a] -> [a] -> [a] ++String " Col: "forall a. [a] -> [a] -> [a] ++forall a. Show a => a -> String showInt givenCol )LCDController -> Arduino () set whereset :: LCDController -> Arduino () set c :: LCDController c @Hitachi44780 {Int lcdRows :: Int lcdRows :: LCDController -> Int lcdRows ,Int lcdCols :: LCDController -> Int lcdCols :: Int lcdCols }=LCDController -> Cmd -> Arduino () sendCmd LCDController c (Word8 -> Cmd LCD_SETDDRAMADDR Word8 offset )wherealign ::Int->Int->Word8align :: Int -> Int -> Word8 align Int i Int m |Int i forall a. Ord a => a -> a -> Bool <Int 0=Word8 0|Int i forall a. Ord a => a -> a -> Bool >=Int m =forall a b. (Integral a, Num b) => a -> b fromIntegralforall a b. (a -> b) -> a -> b $Int m forall a. Num a => a -> a -> a -Int 1|Bool True=forall a b. (Integral a, Num b) => a -> b fromIntegralInt i col :: Word8 col =Int -> Int -> Word8 align Int givenCol Int lcdCols row :: Word8 row =Int -> Int -> Word8 align Int givenRow Int lcdRows -- The magic row-offsets come from various web sources-- I don't follow the logic in these numbers, but it seems to workrowOffsets :: [(Word8, Word8)] rowOffsets =[(Word8 0,Word8 0),(Word8 1,Word8 0x40),(Word8 2,Word8 0x14),(Word8 3,Word8 0x54)]offset :: Word8 offset =Word8 col forall a. Num a => a -> a -> a +forall a. a -> Maybe a -> a fromMaybeWord8 0x54(Word8 row forall a b. Eq a => a -> [(a, b)] -> Maybe b `lookup`[(Word8, Word8)] 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 -> Arduino () lcdScrollDisplayLeft LCD lcd =forall a. LCD -> String -> (LCDController -> Arduino a) -> Arduino a withLCD LCD lcd String "Scrolling display to the left by 1"forall a b. (a -> b) -> a -> b $\LCDController c ->LCDController -> Cmd -> Arduino () sendCmd LCDController c (Word8 -> Cmd LCD_CURSORSHIFT Word8 lcdMoveLeft )wherelcdMoveLeft :: Word8 lcdMoveLeft =Word8 0x00-- | Scroll the display to the right by 1 characterlcdScrollDisplayRight ::LCD ->Arduino ()lcdScrollDisplayRight :: LCD -> Arduino () lcdScrollDisplayRight LCD lcd =forall a. LCD -> String -> (LCDController -> Arduino a) -> Arduino a withLCD LCD lcd String "Scrolling display to the right by 1"forall a b. (a -> b) -> a -> b $\LCDController c ->LCDController -> Cmd -> Arduino () sendCmd LCDController c (Word8 -> Cmd LCD_CURSORSHIFT Word8 lcdMoveRight )wherelcdMoveRight :: Word8 lcdMoveRight =Word8 0x04-- | Display characteristics helper, set the new control/mode and send-- appropriate commands if anything changedupdateDisplayData ::String->(Word8->Word8,Word8->Word8)->LCD ->Arduino ()updateDisplayData :: String -> (Word8 -> Word8, Word8 -> Word8) -> LCD -> Arduino () updateDisplayData String what (Word8 -> Word8 f ,Word8 -> Word8 g )LCD lcd =doString -> Arduino () debug String what MVar BoardState bs <-forall s (m :: * -> *) a. MonadState s m => (s -> a) -> m a getsArduinoState -> MVar BoardState boardState String -> [String] -> IO () err <-forall s (m :: * -> *) a. MonadState s m => (s -> a) -> m a getsArduinoState -> String -> [String] -> IO () bailOut (LCDData {lcdDisplayControl :: LCDData -> Word8 lcdDisplayControl =Word8 oldC ,lcdDisplayMode :: LCDData -> Word8 lcdDisplayMode =Word8 oldM },LCDData {lcdDisplayControl :: LCDData -> Word8 lcdDisplayControl =Word8 newC ,lcdDisplayMode :: LCDData -> Word8 lcdDisplayMode =Word8 newM ,lcdController :: LCDData -> LCDController lcdController =LCDController c })<-forall (m :: * -> *) a. MonadIO m => IO a -> m a liftIOforall a b. (a -> b) -> a -> b $forall a b. MVar a -> (a -> IO (a, b)) -> IO b modifyMVarMVar BoardState bs forall a b. (a -> b) -> a -> b $\BoardState bst ->caseLCD lcd forall k a. Ord k => k -> Map k a -> Maybe a `M.lookup`BoardState -> Map LCD LCDData lcds BoardState bst ofMaybe LCDData Nothing->doString -> [String] -> IO () err (String "hArduino: Cannot locate "forall a. [a] -> [a] -> [a] ++forall a. Show a => a -> String showLCD lcd )[]forall a. IO a exitFailureJustld :: LCDData ld @LCDData {Word8 lcdDisplayControl :: Word8 lcdDisplayControl :: LCDData -> Word8 lcdDisplayControl ,Word8 lcdDisplayMode :: Word8 lcdDisplayMode :: LCDData -> Word8 lcdDisplayMode }->doletld' :: LCDData ld' =LCDData ld {lcdDisplayControl :: Word8 lcdDisplayControl =Word8 -> Word8 f Word8 lcdDisplayControl ,lcdDisplayMode :: Word8 lcdDisplayMode =Word8 -> Word8 g Word8 lcdDisplayMode }forall (m :: * -> *) a. Monad m => a -> m a return(BoardState bst {lcds :: Map LCD LCDData lcds =forall k a. Ord k => k -> a -> Map k a -> Map k a M.insertLCD lcd LCDData ld' (BoardState -> Map LCD LCDData lcds BoardState bst )},(LCDData ld ,LCDData ld' ))forall (f :: * -> *). Applicative f => Bool -> f () -> f () when(Word8 oldC forall a. Eq a => a -> a -> Bool /=Word8 newC )forall a b. (a -> b) -> a -> b $LCDController -> Cmd -> Arduino () sendCmd LCDController c (Word8 -> Cmd LCD_DISPLAYCONTROL Word8 newC )forall (f :: * -> *). Applicative f => Bool -> f () -> f () when(Word8 oldM forall a. Eq a => a -> a -> Bool /=Word8 newM )forall a b. (a -> b) -> a -> b $LCDController -> Cmd -> Arduino () sendCmd LCDController c (Word8 -> Cmd LCD_ENTRYMODESET Word8 newM )-- | Update the display control wordupdateDisplayControl ::String->(Word8->Word8)->LCD ->Arduino ()updateDisplayControl :: String -> (Word8 -> Word8) -> LCD -> Arduino () updateDisplayControl String what Word8 -> Word8 f =String -> (Word8 -> Word8, Word8 -> Word8) -> LCD -> Arduino () updateDisplayData String what (Word8 -> Word8 f ,forall a. a -> a id)-- | Update the display mode wordupdateDisplayMode ::String->(Word8->Word8)->LCD ->Arduino ()updateDisplayMode :: String -> (Word8 -> Word8) -> LCD -> Arduino () updateDisplayMode String what Word8 -> Word8 g =String -> (Word8 -> Word8, Word8 -> Word8) -> LCD -> Arduino () updateDisplayData String what (forall a. a -> a id,Word8 -> Word8 g )-- | Various control masks for the Hitachi44780dataHitachi44780Mask =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 nomaskBit ::Hitachi44780Mask ->IntmaskBit :: Hitachi44780Mask -> Int maskBit Hitachi44780Mask LCD_BLINKON =Int 0maskBit Hitachi44780Mask LCD_CURSORON =Int 1maskBit Hitachi44780Mask LCD_DISPLAYON =Int 2maskBit Hitachi44780Mask LCD_ENTRYSHIFTINCREMENT =Int 0maskBit Hitachi44780Mask LCD_ENTRYLEFT =Int 1-- | Clear by the maskclearMask ::Hitachi44780Mask ->Word8->Word8clearMask :: Hitachi44780Mask -> Word8 -> Word8 clearMask Hitachi44780Mask m Word8 w =Word8 w forall a. Bits a => a -> Int -> a `clearBit`Hitachi44780Mask -> Int maskBit Hitachi44780Mask m -- | Set by the masksetMask ::Hitachi44780Mask ->Word8->Word8setMask :: Hitachi44780Mask -> Word8 -> Word8 setMask Hitachi44780Mask m Word8 w =Word8 w forall a. Bits a => a -> Int -> a `setBit`Hitachi44780Mask -> Int maskBit Hitachi44780Mask m -- | Do not blink the cursorlcdBlinkOff ::LCD ->Arduino ()lcdBlinkOff :: LCD -> Arduino () lcdBlinkOff =String -> (Word8 -> Word8) -> LCD -> Arduino () updateDisplayControl String "Turning blinking off"(Hitachi44780Mask -> Word8 -> Word8 clearMask Hitachi44780Mask LCD_BLINKON )-- | Blink the cursorlcdBlinkOn ::LCD ->Arduino ()lcdBlinkOn :: LCD -> Arduino () lcdBlinkOn =String -> (Word8 -> Word8) -> LCD -> Arduino () updateDisplayControl String "Turning blinking on"(Hitachi44780Mask -> Word8 -> Word8 setMask Hitachi44780Mask LCD_BLINKON )-- | Hide the cursor. Note that a blinking cursor cannot be hidden, you must first-- turn off blinking.lcdCursorOff ::LCD ->Arduino ()lcdCursorOff :: LCD -> Arduino () lcdCursorOff =String -> (Word8 -> Word8) -> LCD -> Arduino () updateDisplayControl String "Not showing the cursor"(Hitachi44780Mask -> Word8 -> Word8 clearMask Hitachi44780Mask LCD_CURSORON )-- | Show the cursorlcdCursorOn ::LCD ->Arduino ()lcdCursorOn :: LCD -> Arduino () lcdCursorOn =String -> (Word8 -> Word8) -> LCD -> Arduino () updateDisplayControl String "Showing the cursor"(Hitachi44780Mask -> Word8 -> Word8 setMask Hitachi44780Mask 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 :: LCD -> Arduino () lcdDisplayOff =String -> (Word8 -> Word8) -> LCD -> Arduino () updateDisplayControl String "Turning display off"(Hitachi44780Mask -> Word8 -> Word8 clearMask Hitachi44780Mask LCD_DISPLAYON )-- | Turn the display onlcdDisplayOn ::LCD ->Arduino ()lcdDisplayOn :: LCD -> Arduino () lcdDisplayOn =String -> (Word8 -> Word8) -> LCD -> Arduino () updateDisplayControl String "Turning display on"(Hitachi44780Mask -> Word8 -> Word8 setMask Hitachi44780Mask LCD_DISPLAYON )-- | Set writing direction: Left to RightlcdLeftToRight ::LCD ->Arduino ()lcdLeftToRight :: LCD -> Arduino () lcdLeftToRight =String -> (Word8 -> Word8) -> LCD -> Arduino () updateDisplayMode String "Setting left-to-right entry mode"(Hitachi44780Mask -> Word8 -> Word8 setMask Hitachi44780Mask LCD_ENTRYLEFT )-- | Set writing direction: Right to LeftlcdRightToLeft ::LCD ->Arduino ()lcdRightToLeft :: LCD -> Arduino () lcdRightToLeft =String -> (Word8 -> Word8) -> LCD -> Arduino () updateDisplayMode String "Setting right-to-left entry mode"(Hitachi44780Mask -> Word8 -> Word8 clearMask Hitachi44780Mask 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 :: LCD -> Arduino () lcdAutoScrollOn =String -> (Word8 -> Word8) -> LCD -> Arduino () updateDisplayMode String "Setting auto-scroll ON"(Hitachi44780Mask -> Word8 -> Word8 setMask Hitachi44780Mask 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 :: LCD -> Arduino () lcdAutoScrollOff =String -> (Word8 -> Word8) -> LCD -> Arduino () updateDisplayMode String "Setting auto-scroll OFF"(Hitachi44780Mask -> Word8 -> Word8 clearMask Hitachi44780Mask LCD_ENTRYSHIFTINCREMENT )-- | Flash contents of the LCD screenlcdFlash ::LCD ->Int-- ^ Flash count->Int-- ^ Delay amount (in milli-seconds)->Arduino ()lcdFlash :: LCD -> Int -> Int -> Arduino () lcdFlash LCD lcd Int n Int d =forall (t :: * -> *) (m :: * -> *) a. (Foldable t, Monad m) => t (m a) -> m () sequence_forall a b. (a -> b) -> a -> b $forall (t :: * -> *) a. Foldable t => t [a] -> [a] concatforall a b. (a -> b) -> a -> b $forall a. Int -> a -> [a] replicateInt n [LCD -> Arduino () lcdDisplayOff LCD lcd ,Int -> Arduino () delay Int d ,LCD -> Arduino () lcdDisplayOn LCD lcd ,Int -> Arduino () delay Int d ]-- | An abstract symbol type for user created symbolsnewtypeLCDSymbol =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 -> [String] -> Arduino LCDSymbol lcdCreateSymbol LCD lcd [String] glyph |forall (t :: * -> *) a. Foldable t => t a -> Int length[String] glyph forall a. Eq a => a -> a -> Bool /=Int 8Bool -> Bool -> Bool ||forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool any((forall a. Eq a => a -> a -> Bool /=Int 5)forall b c a. (b -> c) -> (a -> b) -> a -> c .forall (t :: * -> *) a. Foldable t => t a -> Int length)[String] glyph =forall a. String -> [String] -> Arduino a die String "hArduino: lcdCreateSymbol: Invalid glyph description: must be 8x5!"(String "Received:"forall a. a -> [a] -> [a] :[String] glyph )|Bool True=doMVar BoardState bs <-forall s (m :: * -> *) a. MonadState s m => (s -> a) -> m a getsArduinoState -> MVar BoardState boardState String -> [String] -> IO () err <-forall s (m :: * -> *) a. MonadState s m => (s -> a) -> m a getsArduinoState -> String -> [String] -> IO () bailOut (Word8 i ,LCDController c )<-forall (m :: * -> *) a. MonadIO m => IO a -> m a liftIOforall a b. (a -> b) -> a -> b $forall a b. MVar a -> (a -> IO (a, b)) -> IO b modifyMVarMVar BoardState bs forall a b. (a -> b) -> a -> b $\BoardState bst ->caseLCD lcd forall k a. Ord k => k -> Map k a -> Maybe a `M.lookup`BoardState -> Map LCD LCDData lcds BoardState bst ofMaybe LCDData Nothing->doString -> [String] -> IO () err (String "hArduino: Cannot locate "forall a. [a] -> [a] -> [a] ++forall a. Show a => a -> String showLCD lcd )[]forall a. IO a exitFailureJustld :: LCDData ld @LCDData {Word8 lcdGlyphCount :: Word8 lcdGlyphCount :: LCDData -> Word8 lcdGlyphCount ,LCDController lcdController :: LCDController lcdController :: LCDData -> LCDController lcdController }->doletld' :: LCDData ld' =LCDData ld {lcdGlyphCount :: Word8 lcdGlyphCount =Word8 lcdGlyphCount forall a. Num a => a -> a -> a +Word8 1}forall (m :: * -> *) a. Monad m => a -> m a return(BoardState bst {lcds :: Map LCD LCDData lcds =forall k a. Ord k => k -> a -> Map k a -> Map k a M.insertLCD lcd LCDData ld' (BoardState -> Map LCD LCDData lcds BoardState bst )},(Word8 lcdGlyphCount ,LCDController lcdController ))LCDController -> Cmd -> Arduino () sendCmd LCDController c (Word8 -> Cmd LCD_SETCGRAMADDR Word8 i )letcvt ::String->Word8cvt :: String -> Word8 cvt String s =forall (t :: * -> *) a b. Foldable t => (a -> b -> b) -> b -> t a -> b foldrforall a. Bits a => a -> a -> a (.|.)Word8 0[forall a. Bits a => Int -> a bitInt p |(Char ch ,Int p )<-forall a b. [a] -> [b] -> [(a, b)] zip(forall a. [a] -> [a] reverseString s )[Int 0..],Bool -> Bool not(Char -> Bool isSpaceChar ch )]forall (t :: * -> *) (m :: * -> *) a b. (Foldable t, Monad m) => (a -> m b) -> t a -> m () mapM_(LCDController -> Word8 -> Arduino () sendData LCDController c forall b c a. (b -> c) -> (a -> b) -> a -> c .String -> Word8 cvt )[String] glyph forall (m :: * -> *) a. Monad m => a -> m a returnforall a b. (a -> b) -> a -> b $Word8 -> LCDSymbol LCDSymbol Word8 i -- | Display a user created symbol on the LCD. (See 'lcdCreateSymbol' for details.)lcdWriteSymbol ::LCD ->LCDSymbol ->Arduino ()lcdWriteSymbol :: LCD -> LCDSymbol -> Arduino () lcdWriteSymbol LCD lcd (LCDSymbol Word8 i )=forall a. LCD -> String -> (LCDController -> Arduino a) -> Arduino a withLCD LCD lcd (String "Writing custom symbol "forall a. [a] -> [a] -> [a] ++forall a. Show a => a -> String showWord8 i forall a. [a] -> [a] -> [a] ++String " to LCD")forall a b. (a -> b) -> a -> b $\LCDController c ->LCDController -> Word8 -> Arduino () sendData LCDController c Word8 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 :: Word8 -> LCDSymbol lcdInternalSymbol =Word8 -> LCDSymbol LCDSymbol