Data Aquisition Box, preferably with ~100kHz@16bit Signal sampling

The place to share and discuss your Ultibo projects.
_Tropo
Posts: 16
Joined: Tue Mar 20, 2018 1:01 pm
Location: Germany

Re: Data Aquisition Box, preferably with ~100kHz@16bit Signal sampling

Postby _Tropo » Wed Jul 11, 2018 3:31 pm

Here's my slightly cleaned up version of my little ADS8320 Unit:

Code: Select all

unit ADS8320;

//{$mode objfpc}
{$mode delphi} {Default to Delphi compatible syntax}
{$H+}
{$inline on}   {Allow use of Inline procedures}

interface

uses
  Classes,
  SysUtils,
  GlobalConst,
  GlobalConfig,

  //RPi3:
  BCM2710,
  Platform{$IFNDEF CONSOLE_EARLY_INIT},PlatformRPi3{$ENDIF},
  BCM2837,

  //RPi2:
  //BCM2709,
  //Platform{$IFNDEF CONSOLE_EARLY_INIT},PlatformRPi2{$ENDIF},
  //BCM2836,

  Devices,
  SPI;

type

  TSPIBytes = array[0..2] of Byte;
  PSPIBytes =  ^TSPIBytes;


function ADC_startDevice(var SampleBytes:PSPIBytes): PSPIDevice;
function ADC_getSample(ADCDevice:PSPIDevice;SampleBytes:PSPIBytes):Word;
function ADS8320SPI0WriteRead(SPI:PSPIDevice;ChipSelect:Word;Source,Dest:Pointer;Size,Flags:LongWord;var Count:LongWord):LongWord;
function BCM2710SPI0ReadWritePoll(SPI:PBCM2710SPI0Device):LongWord;

implementation

function ADC_startDevice(var SampleBytes:PSPIBytes): PSPIDevice;
var
   ADCDevice:PSPIDevice;
begin
  // alloce memory for the buffer for the return bytes of the ADC
  SampleBytes:=AllocMem(SizeOf(TSPIBytes));

  //Locate the SPI device (Adjust for boards other than Pi3)
  ADCDevice := SPIDeviceFindByDescription(BCM2710_SPI0_DESCRIPTION);
  if ADCDevice = nil then
    Exit;

  //Configure SPI Chip Select 0
  if SPIDeviceSetClockRate(ADCDevice ,SPI_CS_0, 2400000) <> ERROR_SUCCESS then
    Exit;

  //Start the SPI device
  if SPIDeviceStart(ADCDevice, SPI_MODE_3WIRE, 2400000, SPI_CLOCK_PHASE_HIGH, SPI_CLOCK_POLARITY_HIGH) <> ERROR_SUCCESS then
    Exit;

  Result:=ADCDevice;
end;

function ADC_getSample(ADCDevice:PSPIDevice;SampleBytes:PSPIBytes):Word;
var
  n_Bytes     : LongWord;
  ADS_WR_Return: LongWord;
  SourceBytes:PSPIBytes;
begin
  n_Bytes:=0;
  SourceBytes:=AllocMem(SizeOf(TSPIBytes));

  //Read 3 bytes from the SPI device
  ADS_WR_Return:=ADS8320SPI0WriteRead(ADCDevice ,SPI_CS_0, SourceBytes, SampleBytes, 3, SPI_TRANSFER_NONE, n_Bytes);
  if ADS_WR_Return <> ERROR_SUCCESS then
   begin
    Result:= ADS_WR_Return;
   end
  else
   begin
    Result:= (((SampleBytes[0] and 3) << 14) or (SampleBytes[1] << 6) or (SampleBytes[2] and (63)));
   end;
end;


function ADS8320SPI0WriteRead(SPI:PSPIDevice;ChipSelect:Word;Source,Dest:Pointer;Size,Flags:LongWord;var Count:LongWord):LongWord;
var
 Control:LongWord;
begin
 {}
 {Setup Result}
 Count:=0;
 Result:=ERROR_INVALID_PARAMETER;

 {Check Buffers}
 if (Source = nil) and (Dest = nil) then Exit;


 {Check SPI}
 if SPI = nil then Exit;

 {$IF DEFINED(BCM2710_DEBUG) or DEFINED(SPI_DEBUG)}
 if SPI_LOG_ENABLED then SPILogDebug(SPI,'BCM2710: SPI0 Write Read (ChipSelect=' + SPIChipSelectToString(ChipSelect) + ' Size=' + IntToStr(Size) + ')');
 {$ENDIF}

 {Check Size}
 if Size > BCM2710_SPI0_MAX_SIZE then Exit;

 {Check Chip Select}
 if (ChipSelect <> SPI_CS_NONE) and (ChipSelect > SPI_CS_2) then Exit;

 {Update Statistics}
 Inc(SPI.TransferCount);

 {Write from Source / Read to Dest}
 if Size > 0 then
  begin
   {Setup Data}
   PBCM2710SPI0Device(SPI).Source:=Source;
   PBCM2710SPI0Device(SPI).Dest:=Dest;
   PBCM2710SPI0Device(SPI).Count:=0;
   PBCM2710SPI0Device(SPI).SourceRemain:=Size;
   PBCM2710SPI0Device(SPI).DestRemain:=Size;

   {Memory Barrier}
   DataMemoryBarrier; {Before the First Write}

   {Get Control and Status}
   Control:=PBCM2837SPI0Registers(PBCM2710SPI0Device(SPI).Address).CS and not(BCM2837_SPI0_CS_CS_MASK);

   {Set Mode}
   if (SPI.SPIMode = SPI_MODE_3WIRE) and (Dest <> nil) then
    begin
     Control:=Control or BCM2837_SPI0_CS_REN;
    end
   else
    begin
     Control:=Control and not(BCM2837_SPI0_CS_REN);
    end;

   {Set Chip Select}
   if ChipSelect = SPI_CS_NONE then
    begin
     Control:=Control or (BCM2837_SPI0_CS_CS_MASK); {Select the reserved value}
    end
   else
    begin
     Control:=Control or (ChipSelect and BCM2837_SPI0_CS_CS_MASK);
    end;

   {Check Clock Rate}
   if (ChipSelect = SPI_CS_NONE) or (SPI.ChipSelects[ChipSelect].ClockRate = 0) then
    begin
     {Set Clock Divider}
     PBCM2837SPI0Registers(PBCM2710SPI0Device(SPI).Address).CLK:=(SPI.Divider and BCM2837_SPI0_CLK_CDIV);
    end
   else
    begin
     {Set Clock Divider}
     PBCM2837SPI0Registers(PBCM2710SPI0Device(SPI).Address).CLK:=(SPI.ChipSelects[ChipSelect].Divider and BCM2837_SPI0_CLK_CDIV);
    end;

   {Update Data}
   PBCM2710SPI0Device(SPI).Mode:=BCM2710_SPI0_MODE_IRQ;

   {Note: Cannot fill FIFO when TA bit is not set, interrupt handler will fill on first IRQ}

   {Set Control (Active/Interrupt/Clear)}
   Control:=Control and not(BCM2837_SPI0_CS_INTR or BCM2837_SPI0_CS_INTD);
   Control:=Control or (BCM2837_SPI0_CS_TA or BCM2837_SPI0_CS_CLEAR_RX or BCM2837_SPI0_CS_CLEAR_TX);

   {Set Control and Status}
   PBCM2837SPI0Registers(PBCM2710SPI0Device(SPI).Address).CS:=Control;

   {Memory Barrier, not necessary, since we will be running on a dedicated thread anyway?}
   DataMemoryBarrier; {After the Last Read}

   {Wait for Completion}
   if BCM2710SPI0ReadWritePoll(PBCM2710SPI0Device(SPI)) = ERROR_SUCCESS then

    begin
     {Get Count}
     Count:=PBCM2710SPI0Device(SPI).Count;

     {Check Count}
     if Count < Size then
      begin

       if SPI_LOG_ENABLED then SPILogError(SPI,'BCM2710: Write failure or timeout');

       {Update Statistics}
       Inc(SPI.TransferErrors);
      end;
    end
   else
    begin
     if SPI_LOG_ENABLED then SPILogError(SPI,'BCM2710: Wait failure on write');

     Result:=ERROR_OPERATION_FAILED;
    end;

   {Reset Data}
   PBCM2710SPI0Device(SPI).Source:=nil;
   PBCM2710SPI0Device(SPI).Dest:=nil;
   PBCM2710SPI0Device(SPI).Count:=0;
   PBCM2710SPI0Device(SPI).SourceRemain:=0;
   PBCM2710SPI0Device(SPI).DestRemain:=0;
  end;

 {$IF DEFINED(BCM2710_DEBUG) or DEFINED(SPI_DEBUG)}
 if SPI_LOG_ENABLED then SPILogDebug(SPI,'BCM2710:  Return Count=' + IntToStr(Count));
 {$ENDIF}

 {Return Result}
 if (Size = Count) then Result:=ERROR_SUCCESS;
end;

function BCM2710SPI0ReadWritePoll(SPI:PBCM2710SPI0Device):LongWord;
var
 Control:LongWord;
begin
 {}
 {Check SPI}
 if SPI = nil then Exit;

 {Update Statistics}
 //Inc(SPI.InterruptCount); //no interrupts enabled, shouldn't be necessary

 {Memory Barrier}
 DataMemoryBarrier; {Before the First Write}

 {Read FIFO}
 BCM2710SPI0ReadFIFO(SPI);
 Control:=PBCM2837SPI0Registers(SPI.Address).CS;

 {Write FIFO}
 BCM2710SPI0WriteFIFO(SPI);

 {Get Control and Status}
 Control:=PBCM2837SPI0Registers(SPI.Address).CS;

 {Check Done}
// if ((Control and BCM2837_SPI0_CS_DONE) <> 0) and (SPI.SourceRemain = 0) then
 if (SPI.SourceRemain = 0) then
  begin
   {Read remaining FIFO}
   BCM2710SPI0ReadFIFO(SPI);
   {Reset Control (Active/Interrupt/Deassert/DMA/Clear)}
   Control:=Control and not(BCM2837_SPI0_CS_INTR or BCM2837_SPI0_CS_INTD or BCM2837_SPI0_CS_ADCS or BCM2837_SPI0_CS_DMAEN or BCM2837_SPI0_CS_TA);
   //Control:=Control and not(BCM2837_SPI0_CS_ADCS or BCM2837_SPI0_CS_DMAEN or BCM2837_SPI0_CS_TA);
   Control:=Control or (BCM2837_SPI0_CS_CLEAR_RX or BCM2837_SPI0_CS_CLEAR_TX);

   {Set Control and Status}
   PBCM2837SPI0Registers(SPI.Address).CS:=Control;

   {Set Data Length}
   PBCM2837SPI0Registers(SPI.Address).DLEN:=0;

   {Signal Semaphore}
   //SemaphoreSignal(SPI.SPI.Wait);
  end;

 {Memory Barrier}
 DataMemoryBarrier; {After the Last Read}

 Result:=ERROR_SUCCESS; // This needs to have some actual use and not be ERROR_SUCCESS after every call
end;


And here a little main script that shows me it's not working as intended:

Code: Select all

program DAQ_development;

//{$mode objfpc}{$H+}
{$mode delphi} {Default to Delphi compatible syntax}
{$H+}          {Default to AnsiString}
{$inline on}   {Allow use of Inline procedures}
uses
  //BCM2709,      {RPi2}
  //BCM2836,      {RPi2}
  //BCM2710,      {RPi3}
  //BCM2837,      {RPi3}
  InitUnit,     {Include InitUnit to allow us to change the startup behaviour}
  RaspberryPi3, {Include RaspberryPi2 to make sure all standard functions are included}
  GlobalConst,
  GlobalConfig,
  GlobalTypes,
  Threads,
  Console,
  SysUtils,
  ThreadUnit,         {Include our thread unit which contains most of the example}
  //MeasureUnit,
  SampleBufferUnit,
  //DataFileHandlingUnit,
  Platform,
  FATFS,
  NTFS,
  FileSystem,  {Include the file system core and interfaces}
  DAQ_Globals,
  // Time
  Ultibo,   {The Ultibo unit provides some APIs for getting and setting timezones}
  DateUtils,
  SPI,
  ADS8320,
  Services; {The services unit includes the NTP client and will automatically include the network}

var
 LeftWindow:TWindowHandle;
 SampleBytes:PSPIBytes;
 ADCDevice:PSPIDevice;
 n_Bytes:LongWord;
 Sample,Sample2,Sample2a:Word;
 
begin
 {Create a console window to show what is happening}
 LeftWindow:=ConsoleWindowCreate(ConsoleDeviceGetDefault,CONSOLE_POSITION_LEFT,True);

 ADCDevice:=ADC_startDevice(SampleBytes);


 // This works:
 n_Bytes:=0;
 if SPIDeviceRead(ADCDevice ,SPI_CS_0, SampleBytes, 3, SPI_TRANSFER_NONE,n_Bytes) <> ERROR_SUCCESS then
    ConsoleWindowWriteLn(LeftWindow,'Failed to read ADC');
 Sample:=(((SampleBytes^[0] and 3) << 14) or (SampleBytes^[1] << 6) or (SampleBytes^[2] and (63)));

 //reset SampleBytes
 SampleBytes^[0]:=0;
 SampleBytes^[1]:=0;
 SampleBytes^[2]:=0;
  // This doesn't:
 Sleep(10);
 Sample2:=ADC_getSample(ADCDevice ,SampleBytes);
 Sample2a:=(((SampleBytes^[0] and 3) << 14) or (SampleBytes^[1] << 6) or (SampleBytes^[2] and (63)));


 ConsoleWindowWriteLn(LeftWindow,'Sample = '+IntToStr(Sample)+'   ,Sample2 = '+IntToStr(Sample2)+',   Sample2a = '+IntToStr(Sample2a));
 ConsoleWindowWriteLn(LeftWindow,'SampleByte_1 = '+  IntToStr(SampleBytes^[0]) +   '  SampleByte_2 = '+  IntToStr(SampleBytes^[1]) +'  SampleByte_3 = '+  IntToStr(SampleBytes^[2]));
 end.
 


...excuse all those unused units, and the slightly messy formating. It's from my developement script i used for lot's of testing...

Thanks so much for your continued support! ou guys are awesome!
User avatar
Ultibo
Site Admin
Posts: 1903
Joined: Sat Dec 19, 2015 3:49 am
Location: Australia

Re: Data Aquisition Box, preferably with ~100kHz@16bit Signal sampling

Postby Ultibo » Thu Jul 12, 2018 12:04 am

_Tropo wrote:Here's my slightly cleaned up version of my little ADS8320 Unit:

I haven't tested this yet but I think all that is missing is a loop to write/read the data.

The SPI device is running at 2.4MHz but the CPU is running at 1.2GHz (or more) so by the time the SPI device gets data delivered into the FIFO for you to read your function has already given up and returned.

I modified the BCM2710SPI0ReadWritePoll() function to include a loop while waiting and it looks like this:

Code: Select all

function BCM2710SPI0ReadWritePoll(SPI:PBCM2710SPI0Device):LongWord;
var
 Control:LongWord;
begin
 {}
 Result:=ERROR_INVALID_PARAMETER;
 
 {Check SPI}
 if SPI = nil then Exit;

 {Loop until Completion}
 while SPI.DestRemain > 0 do
  begin
   {Memory Barrier}
   DataMemoryBarrier; {Before the First Write}
 
   {Read FIFO}
   BCM2710SPI0ReadFIFO(SPI);
 
   {Write FIFO}
   BCM2710SPI0WriteFIFO(SPI);
 
   {Get Control and Status}
   Control:=PBCM2837SPI0Registers(SPI.Address).CS;
 
   {Check Done}
   if ((Control and BCM2837_SPI0_CS_DONE) <> 0) and (SPI.SourceRemain = 0) then
    begin
     {Read remaining FIFO}
     BCM2710SPI0ReadFIFO(SPI);
     
     {Reset Control (Active/Interrupt/Deassert/DMA/Clear)}
     Control:=Control and not(BCM2837_SPI0_CS_INTR or BCM2837_SPI0_CS_INTD or BCM2837_SPI0_CS_ADCS or BCM2837_SPI0_CS_DMAEN or BCM2837_SPI0_CS_TA);
     Control:=Control or (BCM2837_SPI0_CS_CLEAR_RX or BCM2837_SPI0_CS_CLEAR_TX);
 
     {Set Control and Status}
     PBCM2837SPI0Registers(SPI.Address).CS:=Control;
 
     {Set Data Length}
     PBCM2837SPI0Registers(SPI.Address).DLEN:=0;
    end;
 
   {Memory Barrier}
   DataMemoryBarrier; {After the Last Read}
  end;
 
 Result:=ERROR_SUCCESS;
end;

That should be close to working but as I said it hasn't been tested so I might have missed something.

I will still create my own test of using polling to do this just to make sure it works and we might even add a new flag to the SPI functions for SPI_TRANSFER_PIO and make a polling mode available, that won't change anything for you because you still need your own function to do it from a dedicated thread (the SPI functions all use locks).

There are probably a couple of other changes you could make to your code once you get it working but one I did notice that you should change is in ADC_getSample(). There is no need to allocate SourceBytes for the dummy data, just pass nil to the ADS8320SPI0WriteRead() function instead and it will handle it correctly. You also don't want to be allocating memory from a dedicated thread if you have scheduling disabled, again because of locks etc.

Let us know the result of this change.
Ultibo.org | Make something amazing
https://ultibo.org
_Tropo
Posts: 16
Joined: Tue Mar 20, 2018 1:01 pm
Location: Germany

Re: Data Aquisition Box, preferably with ~100kHz@16bit Signal sampling

Postby _Tropo » Thu Jul 12, 2018 11:58 am

Ultibo wrote:I haven't tested this yet but I think all that is missing is a loop to write/read the data.

The SPI device is running at 2.4MHz but the CPU is running at 1.2GHz (or more) so by the time the SPI device gets data delivered into the FIFO for you to read your function has already given up and returned.


Oh, yes of course, you're right! I forgot about the time difference between the SPI filling the RX buffer at 2.4 MHz and the CPU checking it so much faster. So just checking it once obviously cannot work! You're suggested change fixed my issue and my dedicated thread is now doing the sampling of the ADC with a rate 100kSPS and writing it to data files. Thanks so much.

Return to “Projects”

Who is online

Users browsing this forum: No registered users and 0 guests