----------------------------------------------------------------------------
-- |
-- Module : App.ADC
-- Copyright : (c) Marc Fontaine 2017
-- License : BSD3
--
-- Maintainer : Marc.Fontaine@gmx.de
-- Stability : experimental
-- Portability : GHC-only
--
-- Example for the analog digital converter.
-- The ADC of the STM32 works best with DMA transfers.
-- This example turns the STM32 into a small digital storage oscilloscope.
-- As this works with DMA transfers, one can sample with precise timings
-- and the block size and the sampling rate are not limited by the speed of
-- the Haskell code.
module App.ADC
where
import Control.Monad
import Control.Monad.IO.Class
import STM32.API
import STM32.DMA as DMA
import STM32.GPIO as GPIO
import STM32.ADC as ADC
import qualified Data.ByteString.Lazy as BSL (fromStrict)
import Data.Binary
import Data.Binary.Get
-- this is buggy the channels get mixed up from time to time
-- maybe DMA out of sync
adc3channel :: IO ()
adc3channel = runMI $ do
initMI
resetHalt
setDefaultClocks
peripheralClockOn GPIOA
GPIO.pinMode (GPIOA,Pin_1) InputAnalog
GPIO.pinMode (GPIOA,Pin_3) InputAnalog
GPIO.pinMode (GPIOA,Pin_5) InputAnalog
let overSampling :: Num x => x
overSampling = 8
bufferSize :: Num x => x
bufferSize = overSampling * 2 *3
dmaCount = overSampling *3
let dmaBuffer = 0x20001000
dmaConfig = DMA.Config {
_BufferSize = dmaCount
,_Direction = PeripheralSRC
,_MemoryBaseAddr = dmaBuffer
,_MemoryDataSize = HalfWord
,_MemoryInc = True
,DMA._Mode = Circular
,_PeripheralBaseAddr = regToAddr ADC1 DR
,_PeripheralDataSize = HalfWord
,_PeripheralInc = False
,_Priority = High
}
peripheralClockOn DMA1
DMA.deInit DMA1_Channel1
DMA.init DMA1_Channel1 dmaConfig
DMA.enable DMA1_Channel1
let adcConfig = ADC.Config {
ADC._Mode = Independent
,_ScanConvMode = True
,_ContinuousConvMode = True
,_ExternalTrigConv = ExternalTrigConv_None
,_DataAlign = AlignRight
,_NbrOfChannel = 3
}
peripheralClockOn ADC1
ADC.init ADC1 adcConfig
ADC.regularChannelConfig ADC1 Channel_1 1 SampleTime_71Cycles5
ADC.regularChannelConfig ADC1 Channel_3 2 SampleTime_71Cycles5
ADC.regularChannelConfig ADC1 Channel_5 3 SampleTime_71Cycles5
ADC.dmaCmd ADC1 True
ADC.cmd ADC1 True
-- todo : implement calibration
ADC.softwareStartConvCmd ADC1 True
forever $ do
buffer <- readMem8 dmaBuffer bufferSize
let
vals :: [(Word16,Word16,Word16)]
vals = runGet
( replicateM overSampling
((,,) <$> getWord16le <*> getWord16le <*> getWord16le)
)
(BSL.fromStrict buffer)
average sel = (fromIntegral $ sum $ map sel vals) * 100 `div` overSampling
w1 :: Int
w1 = average (\(x,_,_) -> x)
w2 :: Int
w2 = average (\(_,x,_) -> x)
w3 :: Int
w3 = average (\(_,_,x) -> x)
{-
when some input pin is connect to a poti while some
neighboring inputs are left floating
the floating ones do not "float" randomely
floating inputs are pulled by the poti
-}
print' (w1,w2,w3)
delay 100000
-- | Periodically sample a block of data and write it to a file.
-- In combination with a wave-form viewer that can detect file updates,
-- this works as a poor mans' digital storage oscilloscope.
sampleBlock :: FilePath -> IO ()
sampleBlock filename = runMI $ do
initMI
resetHalt
setDefaultClocks
peripheralClockOn GPIOA
GPIO.pinMode (GPIOA,Pin_1) InputAnalog
let samples :: Num x => x
samples = 1000
bufferSize :: Num x => x
bufferSize = samples *2
let dmaBuffer = 0x20001000
dmaConfig = DMA.Config {
_BufferSize = samples
,_Direction = PeripheralSRC
,_MemoryBaseAddr = dmaBuffer
,_MemoryDataSize = HalfWord
,_MemoryInc = True
,DMA._Mode = Circular
,_PeripheralBaseAddr = regToAddr ADC1 DR
,_PeripheralDataSize = HalfWord
,_PeripheralInc = False
,_Priority = High
}
peripheralClockOn DMA1
DMA.deInit DMA1_Channel1
DMA.init DMA1_Channel1 dmaConfig
DMA.enable DMA1_Channel1
let adcConfig = ADC.Config {
ADC._Mode = Independent
,_ScanConvMode = True
,_ContinuousConvMode = True
,_ExternalTrigConv = ExternalTrigConv_None
,_DataAlign = AlignRight
,_NbrOfChannel = 1
}
peripheralClockOn ADC1
ADC.init ADC1 adcConfig
ADC.regularChannelConfig ADC1 Channel_1 1 SampleTime_239Cycles5
-- ADC.regularChannelConfig ADC1 Channel_3 2 SampleTime_71Cycles5
-- ADC.regularChannelConfig ADC1 Channel_5 3 SampleTime_71Cycles5
ADC.dmaCmd ADC1 True
ADC.cmd ADC1 True
-- todo : implement calibration
ADC.softwareStartConvCmd ADC1 True
liftIO $ putStrLn "sampling"
delay 1000000
liftIO $ putStrLn "sampling OK"
buffer <- readMem8 dmaBuffer bufferSize
let
vals :: [(Int,Word16)]
vals = zip [0..] $ runGet (replicateM samples $
(getWord16le)
)
$ BSL.fromStrict buffer
out = concat $ map (\(idx,val) -> (show idx ++"," ++ show val ++ "\n")) vals
liftIO $ writeFile filename out