-1

I'm working with a DAQ device that samples 32 analog channels and send it over udp using multicast. I'm saving all data using a Ubuntu linux laptop. For development purpose, I want to create a sine wave generator that emulates the DAQ device in localhost.

How should I generate data at constant rate and sampling frequency? Maybe I could handle data as audio and using some existent utility? Or write a scheduler that, given sample rate , packets size and number off channels, sends data over UDP. I'm working on Linux using C++ .

Stphane
  • 3,368
  • 5
  • 32
  • 47
Marco
  • 1
  • 3
  • if it's just development/testing why bother with C++? Python would probably be much less code. what UDP protocol is being used (i.e. what's the packet format), what sample rate are you targeting? please also include any code you've written – Sam Mason Oct 27 '19 at 17:18
  • I'm using c++ in order to achieve better performances and because I have many years of experience in coding c++ embedded applications. The DAQ device is outputting data in raw format, so 32 channels, 24 bit interleaved samples. The maximum sampling freq is 200khz. – Marco Oct 27 '19 at 17:46
  • ok, but what's the format of the UDP packets? "raw" doesn't mean anything me! the protocol would also define how packet loss and retries are handled. note that going above [50k packets per second](https://blog.cloudflare.com/how-to-receive-a-million-packets/) starts to get awkward, so the protocol should bundle multiple samples into a packet to send at higher speeds (e.g. 10 samples of 32 channels at 200khz = 960 bytes at 20k pps) – Sam Mason Oct 28 '19 at 14:26
  • Each UDP packet is made of a header (timestamp and a counter and some other flags) and a payload. The payload is 32ch x 3bytes x 15 samples = 1440 byes. So at 200k samples per second, the packet rate is 200000/15= 13333 packets per second. Packet loss and retries aren't handled. – Marco Oct 28 '19 at 20:23

1 Answers1

1

I'd just write a loop that goes around generating packets and sending them at the appropriate interval, e.g: given:

static constexpr int NUM_CHANNELS = 32;
static constexpr int NUM_SAMPLES_PER_PACKET = 15;

struct packet {
    uint64_t timestamp_ms;
    uint32_t packet_counter;

    int16_t data[NUM_CHANNELS * NUM_SAMPLES_PER_PACKET];
};

I've obviously made up the packet format as you didn't specify anything. you'd need to hack this around to match what's actually required. I'm also using 16 bit values here to make the data easier to work with, filling in 24bit samples would probably be easiest with an array of uint8_ts and some bitwise operations to pack values in.

next I define the function to implement the loop:

void
generate_samples(int sockfd)
{
    struct timespec scheduler;
    clock_gettime(CLOCK_MONOTONIC, &scheduler);

    struct packet pkt {};
    auto time = 0;

    while (time < 200000) {
        // fill data into packet
        for (auto sample = 0; sample < NUM_SAMPLES_PER_PACKET; ++sample) {
            double t = time++ * (2 * M_PI / 100);
            for (auto channel = 0; channel < NUM_CHANNELS; ++channel) {
                pkt.data[sample * NUM_CHANNELS + channel] = sin(t + 0.1 * channel) * (1<<15);
            }
        }

        // wait until an appropriate amount of time has passed before
        // sending out the next packet
        scheduler.tv_nsec += 75000;
        if(scheduler.tv_nsec >= 1000000000) {
            scheduler.tv_nsec -= 1000000000;
            scheduler.tv_sec++;
        }
        clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &scheduler, NULL);

        struct timespec now;
        clock_gettime(CLOCK_REALTIME, &now);

        // update book-keeping fields
        pkt.timestamp_ms = int64_t{now.tv_sec} * 1000000 + int64_t{now.tv_nsec} / 1000;
        pkt.packet_counter += 1;

        // actually send the packet
        send(sockfd, &pkt, sizeof(pkt), 0);
    }
}

the sockfd parameter is just a file handle from socket() that's been connect()ed to the relevant address so the send knows where to go. you'd probably want some error checking (e.g. around the clock_* and send calls) but this does something sensible for me.

Sam Mason
  • 15,216
  • 1
  • 41
  • 60
  • I expect you need to make lots of changes! just change it to `while(1)` so it keeps going forever rather than stopping after 1 second – Sam Mason Oct 30 '19 at 20:37
  • Hi, Thanks! Yes, I made some changes and called the function inside a while(true) loop. I added code to configure the sampling freq. So the code for the nanosleep became: scheduler.tv_nsec += (1/(samplFreq/15))*1000000000; – Marco Oct 30 '19 at 20:48