------------------------------------------------------------------------------------------------- -- | -- 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