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

The place to share and discuss your Ultibo projects.
Gavinmc42
Posts: 1387
Joined: Sun Jun 05, 2016 12:38 pm
Location: Brisbane, Australia

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

Postby Gavinmc42 » Fri Apr 20, 2018 7:55 am

Ultibo does not use much memory, so you can have 100MB+ for buffer.
Or even two buffers, write one to SD while other is filling.

Been playing with Gentoo Aarch64 and just learnt an important lesson, SD cards/USB sticks are not all equal.
Some USB sticks can be quite fast too, not most of mine :oops:

Ultibo might be good to use for making memory benchtests.
A recent post about HD speed highlighted it again.

Another one
viewtopic.php?f=16&t=715&hilit=memory+speed
How fast is the SD card and how can it go faster?

Can Ultibo be used for 1-10MHz Data Acquisition?
_Tropo
Posts: 48
Joined: Tue Mar 20, 2018 1:01 pm
Location: Germany

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

Postby _Tropo » Wed Jun 27, 2018 3:31 pm

Hey there,

long time past since i last posted (again). So i spent a lot of time to learn how to run my ADC chips and got those working so that i can take samples. Now i'm trying to implement that into my Ultibo DAQ Script. Here is my little Unit to take samples from an ADS8320 (max of 100kHz SPS, 2,4MHz SPI frequency):

Code: Select all

unit ADS8320;

{$mode objfpc}{$H+}

interface

uses
  Classes,
  SysUtils,
  GlobalConst,
  //BCM2710,      {RPi3}
  BCM2709,      {RPi2}
  SPI;

type

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

const
  N_BYTES     = 3;

function ADC_getSample(SampleBytes : PSPIBytes):Word;

implementation
function ADC_getSample(SampleBytes : PSPIBytes):Word;
var
  ADCDevice   : PSPIDevice;

begin

  n_Bytes:=N_BYTES;

  //Locate the SPI device (Adjust for boards other than Pi3)
  ADCDevice := SPIDeviceFindByDescription(BCM2709_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;

  //Read 3 bytes from the SPI device
  if SPIDeviceRead(ADCDevice ,SPI_CS_0, SampleBytes, 3, SPI_TRANSFER_NONE,n_Bytes) <> ERROR_SUCCESS then
    Exit;

  Result:= ((SampleBytes^[0] and 3) << 14) or (SampleBytes^[1] << 6) or (SampleBytes^[2] and (63))
end;

end.                                                                           


This works fine anywhere but in my dedicated thread that is supposed to take the samples. The reason is obviously that i disable scheduler interrupts on my dedicated thread to make sure i don't miss my time window to get a sample in case some interrupt interferes. I guess SPIDeviceRead and/or some other SPI function has to wait for gpio events to receive the returned bytes from SPI and because of that, we can't return to the function calling it in the dedicated thread due to the disabled scheduler interrupts.

The thing is that i don't really know what would be a smart way to fix this...
Should i reenable and then disable scheduler interrupts again whenever i want to get a sample from the adc? Does that give me potential issues when some interrupt interferes in the wrong moment?
Do i actually need to disable scheduler interrupts at all? Or is it unlikely to cause any issues anyway?
Is there a better way to do this than the way i am doing it above?

Thanks in advance for the help!
User avatar
Ultibo
Site Admin
Posts: 1974
Joined: Sat Dec 19, 2015 3:49 am
Location: Australia

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

Postby Ultibo » Thu Jun 28, 2018 12:30 am

_Tropo wrote:This works fine anywhere but in my dedicated thread that is supposed to take the samples. The reason is obviously that i disable scheduler interrupts on my dedicated thread to make sure i don't miss my time window to get a sample in case some interrupt interferes. I guess SPIDeviceRead and/or some other SPI function has to wait for gpio events to receive the returned bytes from SPI and because of that, we can't return to the function calling it in the dedicated thread due to the disabled scheduler interrupts.

...

Is there a better way to do this than the way i am doing it above?

You are correct that the reason this doesn't work is because of disabling scheduler interrupts in your dedicated thread, if you look at the implementation of SPIDeviceRead() which will actually be BCM2709SPI0WriteRead() for the Raspberry Pi 2/3 (BCM2709) then you can see it is interrupt driven so the calling thread sets up the transfer and then waits on a semaphore for the interrupt handler to complete the request.

The dedicated CPU example explains that waiting or sleeping when the scheduler is disabled won't work so if you want to have the timing precision of running without interrupts then you will need to create your own SPIDeviceRead() equivalent that uses polling of the SPI0 device instead.

The good thing is you can continue to use the other functions of the Ultibo SPI driver such as SPIDeviceSetClockRate() and SPIDeviceStart() to configure the device, you just need to implement your own SPIDeviceRead() which uses polling instead of interrupts.

Pretty much everything you need can be found in the BCM2709 unit, if you look at BCM2709SPI0WriteRead() and BCM2709SPI0InterruptHandler() your version would need to combine the functionality of both of these into one and not set the BCM2836_SPI0_CS_INTR or BCM2836_SPI0_CS_INTD flags in the control register.

Have a look at the functions above and come back with any questions, it should be entirely possible to make this work but it might take a bit of careful study to understand the correct sequence.
Ultibo.org | Make something amazing
https://ultibo.org
_Tropo
Posts: 48
Joined: Tue Mar 20, 2018 1:01 pm
Location: Germany

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

Postby _Tropo » Thu Jun 28, 2018 11:55 am

Ultibo wrote:Pretty much everything you need can be found in the BCM2709 unit, if you look at BCM2709SPI0WriteRead() and BCM2709SPI0InterruptHandler() your version would need to combine the functionality of both of these into one and not set the BCM2836_SPI0_CS_INTR or BCM2836_SPI0_CS_INTD flags in the control register.

Have a look at the functions above and come back with any questions, it should be entirely possible to make this work but it might take a bit of careful study to understand the correct sequence.


I started looking through the functions. First of all some more general question regarding pascal syntax:

In your SPI unit, the TSPIDevice...=function(...) : ...; in the type definition mean basically that you define a Type that is some function with a predefined set of arguments, but no actual function is assigned yet, right? So the PSPIDevice Pointer is just a pointer to a record with "empty shells" for the different methods. And on Initialization, those shells are filled with actual functions? I just couldn't find where/when those shells are filled
I am asking because i could not find where BCM2709SPI0InterruptHandler() comes into play within BCM2709SPI0WriteRead(). Well i kind off did, because BCM2709SPI0InterruptHandler() sets "SPI.Wait" which BCM2709SPI0WriteRead() waits for, but i couldn't find where BCM2709SPI0InterruptHandler() is called.

related to that: does BCM2709SPI0WriteRead() only handle one WriteRead event? So that it is called in some other function in a loop over the "Count" the argument of SPIDeviceRead() which then also calls BCM2709SPI0InterruptHandler()? Sorry if it is obvious, but i couldn't find it and i also could not see how BCM2709SPI0WriteRead() would work for more than one byte written/read since i couldn't find any loop. This function that does the looping should be the function that is assigned to TSPIDeviceRead (or TSPIDeviceWriteRead), right?

Can you point me to where that one is assigned, or if i got it all wrong?

But from what i understood so far, assuming I ignore SPI_TRANFSER_DMA for now, and only build my function without it, i would think that Line 2301 in the BCM2709 unit:

Code: Select all

...
if SemaphoreWait(SPI.Wait) = ERROR_SUCCESS then
  begin
  ...
...

is the line i need to work around? So that instead of using SemaphoreWait(SPI.Wait) i need to build a "while WaitSwitch" loop that constantly checks the state of a switch that i build in my Version of BCM2709SPI0InterruptHandler() (or in my combined function), which i set to False in the equivalent position of where "SemaphoreSignal(SPI.SPI.Wait);" is called in BCM2709SPI0InterruptHandler()?

Thanks so much for your great support!
User avatar
Ultibo
Site Admin
Posts: 1974
Joined: Sat Dec 19, 2015 3:49 am
Location: Australia

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

Postby Ultibo » Thu Jun 28, 2018 11:40 pm

First up let me say, there are quite a few questions here so it is possible I will miss something, if there is anything I didn't cover just point it out and I'll add some more details.

_Tropo wrote:In your SPI unit, the TSPIDevice...=function(...) : ...; in the type definition mean basically that you define a Type that is some function with a predefined set of arguments, but no actual function is assigned yet, right? So the PSPIDevice Pointer is just a pointer to a record with "empty shells" for the different methods. And on Initialization, those shells are filled with actual functions? I just couldn't find where/when those shells are filled

Yes, those type declarations are just like typedefs in C, they only define what the function will look like (parameters, return type etc) and not the function itself.

Inside the TSPIDevice record (struct) you see the actual function pointers which are set by the driver when it registers (much that same as the way it works in Linux), for the BCM2709 SPI0 that happens in BCM2709Init but for the purpose of what you need to do you can ignore that, there is no specific need to change that process (see below for more info).

_Tropo wrote:I am asking because i could not find where BCM2709SPI0InterruptHandler() comes into play within BCM2709SPI0WriteRead(). Well i kind off did, because BCM2709SPI0InterruptHandler() sets "SPI.Wait" which BCM2709SPI0WriteRead() waits for, but i couldn't find where BCM2709SPI0InterruptHandler() is called.

So BCM2709SPI0InterruptHandler() is an interrupt handler and is called by the interrupt dispatcher, that is why you don't see it in BCM2709SPI0WriteRead(), for reference it is registered by a call to RequestIRQ() in BCM2709SPI0Start() but again you can ignore that for your purpose as well.

_Tropo wrote:related to that: does BCM2709SPI0WriteRead() only handle one WriteRead event? So that it is called in some other function in a loop over the "Count" the argument of SPIDeviceRead() which then also calls BCM2709SPI0InterruptHandler()? Sorry if it is obvious, but i couldn't find it and i also could not see how BCM2709SPI0WriteRead() would work for more than one byte written/read since i couldn't find any loop. This function that does the looping should be the function that is assigned to TSPIDeviceRead (or TSPIDeviceWriteRead), right?

No, the BCM2709SPI0WriteRead() can write/read any size data up to BCM2709_SPI0_MAX_SIZE (64KB) in a single call. You don't see the loop because it is done in BCM2709SPI0InterruptHandler() (or actually in BCM2709SPI0ReadFIFO and BCM2709SPI0WriteFIFO) which are called from BCM2709SPI0InterruptHandler().

What you need to do is replace SemaphoreWait(SPI.Wait) with the loop from the interrupt handler.

The way to do it would be to create a new function in your ADS8320 unit called ADS8320SPI0WriteRead() or something like that with the exact same parameters as BCM2709SPI0WriteRead() and combine the relevant parts of BCM2709SPI0WriteRead() and BCM2709SPI0InterruptHandler() into it, then your ADC_getSample() get sample function would call ADS8320SPI0WriteRead() instead of calling SPIDeviceRead().

As a note you will also need to restructure ADC_getSample() so it doesn't call SPIDeviceSetClockRate() and SPIDeviceStart() each time because that is not required and will also cause problems from the dedicated thread, make a new function that does those things once only at the start.

So taking the BCM2709SPI0WriteRead() as a starting point you can ignore the section for DMA transfers and replace the SemaphoreWait(SPI.Wait) with a loop that writes to and reads from the FIFO (as shown in BCM2709SPI0ReadFIFO and BCM2709SPI0WriteFIFO), remembering that SPI is full duplex so you must write a byte for each byte your read (write dummy bytes if you only want to read data). When the number of bytes read (Count) equals the size of the transfer and the Control register contains BCM2836_SPI0_CS_DONE then your transfer is complete and you can break from the loop and return.

Let me know if you need more info on any of that.
Ultibo.org | Make something amazing
https://ultibo.org
_Tropo
Posts: 48
Joined: Tue Mar 20, 2018 1:01 pm
Location: Germany

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

Postby _Tropo » Fri Jun 29, 2018 8:07 am

Man, you're a blast! I'll start working on it and will probably have to get back to you with more questions as they pop up (...which they will)! Thanks so much!
_Tropo
Posts: 48
Joined: Tue Mar 20, 2018 1:01 pm
Location: Germany

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

Postby _Tropo » Tue Jul 03, 2018 3:19 pm

Hi,

I am trying to compile a first version of my ADS8320SPI0WriteRead() function. I am getting:
"
ADS8320.pas(106,10) Error. Illegal qualifier
ADS8320.pas(106,10) Hint: may be pointer dereference is missing
ADS8320.pas(106,10) Fatal: Syntax error, ")" expected but "identifier TRANSFERCOUNT" found
"
on the line:

Code: Select all

{Update Statistics}
 Inc(SPI.TransferCount);


my function declaration looks like this:

Code: Select all

function ADS8320SPI0WriteRead(SPI:PSPIDevice;ChipSelect:Word;Source,Dest:Pointer;Size,Flags:LongWord;var Count:LongWord):LongWord;


So it's the same arguments as in BCM2710SPI0WriteRead(). I am using the SPI Unit in the "uses" where the PSPIDevice type is defined. For testing, i also included all the uses from BCM2710, but that didn't change anything.

It's probably obvious but what am i doing wrong?

Thanks in advance for the continued help!
User avatar
Ultibo
Site Admin
Posts: 1974
Joined: Sat Dec 19, 2015 3:49 am
Location: Australia

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

Postby Ultibo » Tue Jul 03, 2018 11:25 pm

_Tropo wrote:It's probably obvious but what am i doing wrong?

If I'm reading it correctly you are just missing the mode setting at the top of the unit, in all of the Ultibo units we enable Delphi mode because it enables support for certain syntax options that are easier to read/write. By default FPC uses ObjFPC mode which is slightly stricter with syntax for pointers etc.

Try adding the following line near the top of your unit:

Code: Select all

{$mode delphi} {Default to Delphi compatible syntax}


Alternatively you could just change the pointer usage to include the explicit dereference like this:

Code: Select all

 {Update Statistics}
 Inc(SPI^.TransferCount);

but you'll probably find quite few instances to be changed.

One of those options should fix it.
Ultibo.org | Make something amazing
https://ultibo.org
_Tropo
Posts: 48
Joined: Tue Mar 20, 2018 1:01 pm
Location: Germany

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

Postby _Tropo » Tue Jul 10, 2018 4:09 pm

Ultibo wrote:Try adding the following line near the top of your unit:

Code: Select all

{$mode delphi} {Default to Delphi compatible syntax}



Yes, that fixed it! Thanks! But i came across another problem:

I think i now understand the rough concept of how things work with SPI. So from my understanding, just calling the functions in the interrupt handler in the same sequence should work for me too, right? i just need to leave out the

Code: Select all

SemaphoreSignal(SPI.SPI.Wait);
and than just call that function instead of SemaphoreWait(SPI.Wait) inside my altered BCM2710SPI0WriteRead() function. So with the first call of BCM2710SPI0ReadFIFO(); we read all bytes that might still be in SPI RX buffer (it is some kind of buffer, right?). This should not be the case, since we cleared it before in the altered BCM2710SPI0WriteRead() function by setting BCM2837_SPI0_CS_CLEAR_RX in the SPI register. Then we wright some bytes (dummy bytes in my case) to the TX Buffer by calling BCM2710SPI0WriteFIFO(); and wait for BCM2837_SPI0_CS_DONE to be set in the register. However, this does not happen for me. So the second call of BCM2710SPI0ReadFIFO() is not executed. For testing purpose, i removed the check for BCM2837_SPI0_CS_DONE and just check for (SPI.SourceRemain = 0). Then BCM2710SPI0ReadFIFO(SPI) will be called a second time. However, at that point BCM2837_SPI0_CS_RXD is still 0, so the RX Buffer contains no data, and BCM2710SPI0ReadFIFO() returns without doing anything. I thought that after wrighting 3 dummy bytes to the TX buffer, my ADC should respond by wrighting 3 bytes into the RX buffer. This does not seem to be the case however.

Any suggestions what might the issue?

Note that i did all the testing with this function before setting up my dedicated thread and calling it on that thread specifically. Could that be a reason?

Thanks for the support!
User avatar
Ultibo
Site Admin
Posts: 1974
Joined: Sat Dec 19, 2015 3:49 am
Location: Australia

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

Postby Ultibo » Wed Jul 11, 2018 10:53 am

Hi,

You seem to have the correct idea of how it should work, maybe you are just missing a small detail somewhere. If you want to post your outline of the new WriteRead function we can take a look and see if there is anything missing.

We don't seem to have any device available that we can use to read data over SPI but there are a couple of LCD screens that we can use to try the same concept when writing data, I'll see if I can produce something similar to what you are wanting to do and make sure it works.
Ultibo.org | Make something amazing
https://ultibo.org

Return to “Projects”

Who is online

Users browsing this forum: No registered users and 0 guests