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