diff --git a/System/Hardware/BusPirate/I2C.hs b/System/Hardware/BusPirate/I2C.hs
--- a/System/Hardware/BusPirate/I2C.hs
+++ b/System/Hardware/BusPirate/I2C.hs
@@ -1,7 +1,7 @@
 {-# LANGUAGE OverloadedStrings, GeneralizedNewtypeDeriving #-}
 
 module System.Hardware.BusPirate.I2C
-  ( -- * Primitive operations
+  ( -- * Bus operations
     I2cM
   , i2cMode
   , startBit
@@ -9,7 +9,6 @@
   , readByte
   , ackBit
   , nackBit
-  , AckNack(..)
   , bulkWrite
   , writeRead
     -- * Configuration
@@ -17,12 +16,23 @@
   , setConfig
   , I2cSpeed(..)
   , setSpeed
+    -- * Device addresses
+  , I2CAddress
+  , from7Bit
+  , from8Bit
+  , readAddr
+  , writeAddr
+    -- * Register interface
+  , writeReg
+  , readReg
+  , readReg'
   ) where
 
 import Control.Applicative
-import Control.Monad (replicateM, when)
+import Control.Monad (replicateM, when, void)
 import Control.Monad.IO.Class
 import Control.Monad.Trans.Either
+import Data.Bits
 import Data.Word
 import Data.List (intercalate)
 
@@ -50,9 +60,6 @@
 readByte :: I2cM Word8
 readByte = I2cM $ putByte 0x4 >> getByte
 
-data AckNack = Ack | Nack
-             deriving (Show, Eq, Ord, Enum, Bounded)
-
 -- | Send an ACK 
 ackBit :: I2cM ()
 ackBit = I2cM $ command 0x6
@@ -61,6 +68,9 @@
 nackBit :: I2cM ()
 nackBit = I2cM $ command 0x7
 
+data AckNack = Ack | Nack
+             deriving (Show, Eq, Ord, Enum, Bounded)
+
 -- | Write some bytes
 bulkWrite :: ByteString -> I2cM ()
 bulkWrite d 
@@ -120,3 +130,38 @@
     case status of
       0x00 -> fail "writeRead: Failed"
       0x01 -> get recv
+
+-- | An I2C address (shifted 7-bit)
+newtype I2CAddress = I2cAddr Word8
+
+-- | An I2C address from a unshifted 7-bit address
+from7Bit :: Word8 -> I2CAddress
+from7Bit = I2cAddr . (`shiftL` 1)
+
+-- | An I2C address from a shifted 8-bit address (masking out the read/write bit)
+from8Bit :: Word8 -> I2CAddress
+from8Bit = I2cAddr . (`clearBit` 0)
+
+readAddr :: I2CAddress -> Word8
+readAddr (I2cAddr n) = n + 1
+
+writeAddr :: I2CAddress -> Word8
+writeAddr (I2cAddr n) = n
+
+type Register = Word8
+
+-- | Perform a read of the given length starting at the given register
+readReg' :: I2CAddress -> Word8 -> Int -> I2cM BS.ByteString
+readReg' addr reg length = do
+    startBit
+    bulkWrite $ BS.pack [writeAddr addr, reg]
+    writeRead (BS.singleton $ readAddr addr) length
+
+-- | Read the given register
+readReg :: I2CAddress -> Word8 -> I2cM Word8
+readReg addr reg = BS.head <$> readReg' addr reg 1
+
+-- | Perform a write to the given register
+writeReg :: I2CAddress -> Word8 -> Word8 -> I2cM ()
+writeReg addr reg value = do
+    void $ writeRead (BS.pack [writeAddr addr, reg, value]) 0
diff --git a/bus-pirate.cabal b/bus-pirate.cabal
--- a/bus-pirate.cabal
+++ b/bus-pirate.cabal
@@ -1,5 +1,5 @@
 name:                bus-pirate
-version:             0.2
+version:             0.4
 synopsis:            Haskell interface to the Bus Pirate binary interface
 homepage:            http://www.github.com/bgamari/bus-pirate
 license:             BSD3
