I wrote some haskell code to toggle a pin on the raspberry pi depending on an interrupt I get from another pin on the raspberry pi. I simply do not know how to toggle the state of the pin without knowing the previous toggle state. The program itself is very simple.
import Control.Concurrent
import Data.IORef
import HasberryPi
main = do wiringPiSetup
pinMode 0 output
pinMode 7 input
pullUpDnControl 7 pull_down
wiringPiISR 7 edge_both onoff
threadDelay (15*(10^6))
onoff s = do a <- readIORef s -- This is wrong
digitalWrite 0 (if b then pinhigh else pinlow) -- This is wrong
So basically what happens here is pin 7 is registered as an interrupt. The interrupt is triggered whenever pin 7 goes from high to low or low to high. And whenever the interrupt is triggered it calls the onoff
function that toggles the state of pin 0.
The main
function is correct. Its the onoff
function that is the problem. The wanted behavior of the onoff
function is to make pin 0 high when the pin it is low and toggle the pin low when it is high. But to do that I need to store the previous state of the pin in the previous call to onoff
.
I tried the state monad. But the problem is that the state monad passes state around based on an initial state value. But in subsequent calls to onoff
it seems impossible to change the initial state value. I thought about IORef and it seems no different. It just seems like it's doing what state is doing.. but only inside the IO.
I can clearly see that I am sorely missing the ability of storing state in a Global variable. And I'm happy that I'm not able to do it because I know there is some other idiomatic way of achieving the same goal.
Any help in the right direction is very much appreciated.
Cheers and Regards.
The State
monad is really an abstraction over the idea of passing state around in an extra parameter to your functions - it's still pure, it just gives you a lot of syntactic help. IORef
, on the other hand, is an actual update of an actual mutable value, which is why it has to live inside the IO
monad. This is generally considered undesirable unless required for performance reasons, as you lose all the promises you get with pure code about laziness and order of execution and concurrency.
Using State
and IO
together is achieved by use of the StateT monad transformer, which might be thought of as wrapping the State monad around the IO monad. There are some examples on the Haskell wiki: http://www.haskell.org/haskellwiki/Simple_StateT_use which shows how you maintain state while using I/O, and how to use lift
to get IO monad functions to run inside StateT
.
Here's a small example. I'm not sure if it's idiomatic Haskell but it should be enough to get you on the right track. Rather than actually toggling a pin (I don't have a Raspberry Pi to test against) it just prints the state. They're both IO () though so it should match.
Your real state would presumably be a record/list/array of pins. You'd then pass in an index to togglePin and it would have a type like
togglePin :: Int -> PStateT
Anyway - here's the example, it compiles and runs fine here.
import Control.Monad.State
-- Presumably you've got something like this defined in a library
data Pin = PinHigh | PinLow
deriving (Eq,Show)
-- A simple state would be
-- type PState = State Pin
-- We want to wrap our state around IO () so need a transformer
type PStateT = StateT Pin IO ()
-- Simple print function
printPinState :: String -> Pin -> IO ()
printPinState msg pin = putStrLn $ msg ++ (show pin)
-- Toggles the state, real function would set the pin's level too rather than
-- just print it's new state
togglePin :: PStateT
togglePin = do
curr_p <- get
lift $ printPinState "toggle before: " curr_p
let new_p = if curr_p == PinHigh then PinLow else PinHigh
lift $ printPinState "toggle after: " new_p
put new_p
return ()
-- Initialise our state, then run our toggle function using the state
-- as its environment.
main = do
let env = PinLow
printPinState "main before: " env
(_, env') <- runStateT (togglePin) env
printPinState "main after: " env'
-- And again for luck...
(_, env'') <- runStateT (togglePin) env'
return ()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With