Add function generator example/test

This commit is contained in:
Alex Spataru 2024-11-13 18:59:45 -05:00
parent 4303e1a5e3
commit cc8609cd94
5 changed files with 508 additions and 0 deletions

4
.gitignore vendored
View File

@ -5,6 +5,10 @@
.LSOverride
.cache
# Binary builds
udp_function_generator
udp_function_generator.exe
# Build & IDEs
*.kdev*
build/*

View File

@ -0,0 +1,165 @@
# UDP Function Generator Example
## Overview
This project demonstrates how to use the **UDP Function Generator** program to generate and transmit multiple real-time waveforms (sine, triangle, sawtooth, and square) over a UDP network. The program is designed to feed CSV-formatted data to **Serial Studio**, allowing users to visualize the generated waveforms in real-time.
With **Serial Studio**, you can use the **Quick Plot** feature to easily visualize data transmitted via the UDP socket. This provides an intuitive way to test and analyze waveform generation.
![Serial Studio with UDP Function Generator](doc/screenshot.png)
**Compatibility**: This program runs on any system with support for **POSIX sockets** and is compatible with **Serial Studio** for visualization.
### What is a Function Generator?
A **function generator** creates electrical waveforms that can be used for testing circuits, analyzing systems, and generating real-time signals for processing. This program simulates such a generator but transmits its output over a UDP socket instead of generating physical signals.
The waveforms can be used for:
- Testing UDP-based communication.
- Stress-testing Serial Studio to find bugs.
- Visualizing signal behavior in applications.
- Learning and experimenting with waveform generation and signal processing.
## Program Features
- **Waveform Types**: Generate sine, triangular, sawtooth, and square waves.
- **Customizable Settings**:
- Number of waveforms to generate.
- Frequency, phase, and type of each waveform.
- Adjustable transmission interval.
- **Verbose Output**: Print real-time data to the console (optional).
- **Aliasing Protection**: Warns if the frequency is too high to ensure smooth waveform reconstruction.
## Getting Started
### Requirements
- GCC or any compatible C compiler.
- A system with POSIX support for UDP sockets (Linux, macOS, or Windows with WSL).
- [**Serial Studio**](https://serial-studio.github.io/) for real-time visualization.
### 1. Compile the Program
To compile the program, use the following command:
```bash
gcc -o udp_function_generator udp_function_generator.c -lm
```
You can also simply type:
```bash
make udp_function_generator
```
### 2. Run the Program
Use the following command to execute the program:
```bash
./udp_function_generator [-p port] [-i interval] [-n num_functions] [-v]
```
#### Command-Line Options:
- `-p <port>`: UDP port (default: `9000`).
- `-i <interval>`: Transmission interval in milliseconds (default: `1.0 ms`).
- `-n <num_functions>`: Number of waveforms to generate (default: `1`).
- `-v`: Enable verbose output (prints generated data to the console).
#### Example:
```bash
./udp_function_generator -p 9000 -i 5 -n 3 -v
```
### 3. Visualize Data in Serial Studio
To visualize the transmitted data:
1. **Download and Install Serial Studio**:
Visit the [official website](https://serial-studio.github.io/) to download and install the software.
2. **Configure Serial Studio**:
- Set the **I/O Interface** to `Network Socket`.
- Select `UDP` as the **Socket Type**.
- Set the **Host** to `localhost`.
- Configure both the **Local** and **Remote** ports to match the program's `-p` option (default: `9000`).
3. **Enable Quick Plot**:
- In Serial Studio, click on the **Quick Plot** checkbox in the **Setup** pane.
- This feature plots numerical values transmitted via UDP in real time.
![Serial Studio Quick Plot](doc/quick_plot.png)
4. **Run the Program**:
Execute the `udp_function_generator` program. Waveforms will be displayed in Serial Studio's real-time plot.
## Step-by-Step Guide
### Waveform Configuration
When you run the program, it prompts you to configure the waveforms:
1. Enter the **type of waveform** (`sine`, `triangle`, `saw`, or `square`).
2. Specify the **frequency** in Hertz.
3. Enter the **phase** in radians.
The program validates your input and warns about aliasing or distortion if the frequency is too high relative to the sampling rate.
### Data Transmission
The program formats the waveform data into a comma-separated string and transmits it via UDP at the specified interval. You can view this data in Serial Studio or analyze it using any UDP-compatible client.
### Troubleshooting
- **No Waveforms in Serial Studio**: Ensure that the UDP port matches between the program and Serial Studio, and that the **host** is set to `localhost`.
- **Distorted Waveforms**: Reduce the frequency of the waveforms if they approach the Nyquist limit. The program issues warnings for frequencies near this threshold.
- **No Data Output**: Ensure the program is running and the network configuration is correct.
## Examples
### Example 1: Single Sine Wave
Command:
```bash
./udp_function_generator -p 9000 -i 1 -n 1 -v
```
Configuration:
- Waveform Type: `sine`
- Frequency: `10 Hz`
- Phase: `0 radians`
### Example 2: Multiple Waveforms
Command:
```bash
./udp_function_generator -p 8000 -i 5 -n 3 -v
```
Configuration:
1. Waveform 1: `triangle`, `5 Hz`, `0 radians`.
2. Waveform 2: `saw`, `20 Hz`, `1.5 radians`.
3. Waveform 3: `square`, `50 Hz`, `0 radians`.
Visualization:
- Serial Studio will display all three waveforms in real time, with a sampling interval of 5 ms.
### Example 3: High-Frequency Warning
If the frequency exceeds 80% of the Nyquist rate, the program displays a warning:
```plaintext
Warning: Frequency 450.00 Hz approaches the Nyquist rate (500.00 Hz).
Consider reducing it below 400.00 Hz to ensure smooth waveform reconstruction.
```
This ensures a smooth visualization of waveforms.
## Enjoy Your Testing!
For more advanced use cases, refer to the source code and explore the customizable options. You're welcome to make a PR with an improved version of this code.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@ -0,0 +1,321 @@
///
/// @file udp_function_generator.c
/// @author Alex Spataru
/// @brief A UDP-based waveform generator for sine, triangular, sawtooth, and
/// square waves.
///
/// This program generates waveform data and sends it via UDP to a specified
/// port. Users can configure the number of functions, their forms, frequencies,
/// and phases.
///
/// Features:
/// - Generate multiple waveforms of different types: sine, triangular,
/// sawtooth, and square.
/// - Configurable send interval and UDP port.
/// - Option to print generated data to the console.
/// - Frequency validation to warn about potential aliasing.
///
/// Usage:
/// - Compile: `gcc -o udp_function_generator udp_function_generator.c -lm`
/// - Run: `./udp_function_generator [-p port] [-i interval] [-n num_functions]`
///
/// Options:
/// - `-p <port>`: UDP port (default: 9000)
/// - `-i <interval>`: Send interval in milliseconds (default: 1.0 ms)
/// - `-n <num_functions>`: Number of waveforms (default: 1)
/// - `-v`: Enable verbose output
///
/// Example: `./udp_function_generator -p 9000 -i 5 -n 3 -v`
///
/// Press Ctrl+C to terminate the program.
///
#include <time.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define MAX_BUFFER_SIZE 128
#define TWO_PI 6.28318530718
#define DEFAULT_UDP_PORT 9000
#define DEFAULT_NUM_FUNCTIONS 1
#define DEFAULT_SEND_INTERVAL_MS 1.0
/**
* @brief Sleep for a specified number of milliseconds.
*
* This function uses `nanosleep` to pause the program for the specified
* duration in milliseconds, including fractional values.
*
* @param milliseconds Time in milliseconds to sleep.
*/
void sleep_ms(double milliseconds)
{
struct timespec ts;
ts.tv_sec = (time_t)(milliseconds / 1000);
ts.tv_nsec = (long)((milliseconds - (ts.tv_sec * 1000)) * 1e6);
nanosleep(&ts, NULL);
}
/**
* @brief Validate the waveform type.
*
* Checks whether the provided waveform type is one of the supported
* types: "sine", "triangle", "saw", or "square".
*
* @param wave_type The type of waveform to validate.
* @return 1 if the waveform type is valid, 0 otherwise.
*/
int validate_wave_type(const char *wave_type)
{
return (strcmp(wave_type, "sine") == 0 || strcmp(wave_type, "triangle") == 0
|| strcmp(wave_type, "saw") == 0 || strcmp(wave_type, "square") == 0);
}
/**
* @brief Calculate waveform values based on type, frequency, and phase.
*
* Generates a value for the specified waveform type at the given
* frequency and phase. The output value is scaled to a 0-5V range.
*
* @param wave_type The type of waveform (sine, triangle, sawtooth, square).
* @param frequency Frequency of the waveform (not used directly here).
* @param phase Current phase of the waveform in radians.
* @return Generated waveform value in the 0-5V range.
*/
float generate_wave_value(const char *wave_type, float frequency, float phase)
{
if (strcmp(wave_type, "sine") == 0)
return (sinf(phase) + 1.0) * 2.5;
else if (strcmp(wave_type, "triangle") == 0)
return fabsf(fmodf(phase / TWO_PI, 1.0f) * 2.0f - 1.0f) * 5.0f;
else if (strcmp(wave_type, "saw") == 0)
return fmodf(phase / TWO_PI, 1.0f) * 5.0f;
else if (strcmp(wave_type, "square") == 0)
return (sinf(phase) >= 0 ? 5.0f : 0.0f);
return 0.0f;
}
/**
* @brief Print a quick tutorial on program usage.
*
* This function explains how to use the program, including available options,
* and gives an example of a typical command.
*/
void print_tutorial()
{
// clang-format off
printf("UDP Function Generator Tutorial:\n");
printf("- Generate multiple waveforms (sine, triangle, saw, square).\n");
printf("- Specify number of functions, waveform type, frequency, and phase.\n");
printf("- Options:\n");
printf(" -p <port> : UDP port (default: 9000)\n");
printf(" -i <interval> : Send interval in milliseconds (default: 1.0 ms)\n");
printf(" -n <num_functions> : Number of waveforms to generate (default: 1)\n");
printf(" -v : Enable verbose output\n\n");
printf("Example: ./udp_function_generator -p 9000 -i 5 -n 3 -v\n\n");
// clang-format on
}
/**
* @brief Validate frequencies for aliasing issues and warn the user.
*
* This function checks whether the specified frequency is too high for
* the given sampling interval. If the frequency exceeds the Nyquist rate
* or approaches it too closely (e.g., > 80%), it warns the user about
* potential waveform distortion.
*
* @param frequency Frequency of the waveform in Hz.
* @param send_interval_ms Time between data points in milliseconds.
*/
void validate_frequency(float frequency, double send_interval_ms)
{
double nyquist_rate = 1.0 / (2.0 * (send_interval_ms / 1000.0));
double safe_rate = 0.8 * nyquist_rate;
if (frequency >= nyquist_rate)
{
printf("Warning: Frequency %.2f Hz equals or exceeds the Nyquist rate "
"(%.2f Hz). "
"Waveform will be severely distorted.\n",
frequency, nyquist_rate);
}
else if (frequency > safe_rate)
{
printf("Warning: Frequency %.2f Hz approaches the Nyquist rate (%.2f Hz). "
"Consider reducing it below %.2f Hz to ensure smooth waveform "
"reconstruction.\n",
frequency, nyquist_rate, safe_rate);
}
}
/**
* @brief Main program entry point.
*
* This function parses command-line arguments, collects user input for
* waveform details, validates the inputs, and generates waveforms to
* send via UDP.
*
* @param argc Argument count.
* @param argv Argument vector.
* @return Exit status of the program.
*/
int main(int argc, char *argv[])
{
int udp_port = DEFAULT_UDP_PORT;
int num_functions = DEFAULT_NUM_FUNCTIONS;
double send_interval_ms = DEFAULT_SEND_INTERVAL_MS;
// Parse command-line arguments
int opt;
int verbose = 0;
while ((opt = getopt(argc, argv, "p:i:n:v")) != -1)
{
switch (opt)
{
case 'p':
udp_port = atoi(optarg);
break;
case 'i':
send_interval_ms = atof(optarg);
break;
case 'n':
num_functions = atoi(optarg);
if (num_functions < 1)
{
fprintf(stderr, "Number of functions must be at least 1\n");
return 1;
}
break;
case 'v':
verbose = 1;
break;
default:
fprintf(stderr,
"Usage: %s [-p port] [-i interval] [-n num_functions]\n",
argv[0]);
return 1;
}
}
print_tutorial();
printf("Program started with the following options:\n");
printf("- UDP Port: %d\n", udp_port);
printf("- Tx Interval (ms): %.3f\n", send_interval_ms);
printf("- Number of waveforms: %d\n\n", num_functions);
// Collect waveform details from the user
char wave_types[num_functions][16];
float frequencies[num_functions];
float phases[num_functions];
for (int i = 0; i < num_functions; i++)
{
while (1)
{
printf("Enter details for waveform %d:\n", i + 1);
printf("- Wave type (sine, triangle, saw, square): ");
scanf("%s", wave_types[i]);
if (validate_wave_type(wave_types[i]))
break;
else
{
printf("Error: Invalid waveform type. Please enter 'sine', 'triangle', "
"'saw', or 'square'.\n");
}
}
printf("- Frequency (Hz): ");
scanf("%f", &frequencies[i]);
printf("- Phase (radians): ");
scanf("%f", &phases[i]);
validate_frequency(frequencies[i], send_interval_ms);
printf("\n");
}
// Create UDP socket
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("Socket creation failed");
return 1;
}
// Set up destination address
struct sockaddr_in dest_addr;
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(udp_port);
dest_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
// Calculate phase increments for each waveform
double phase_increment[num_functions];
for (int i = 0; i < num_functions; i++)
phase_increment[i] = TWO_PI * frequencies[i] * (send_interval_ms / 1000.0);
// Print run status
printf("The program is now generating real-time functions...\n\n");
printf("To visualize the data in Serial Studio:\n");
printf(" 1. Set the I/O interface to \"Network Socket\".\n");
printf(" 2. Enable \"Quick Plot\" operation mode.\n");
printf(" 3. Select \"UDP\" as the Socket Type.\n");
printf(" 4. Set the Host to \"localhost\".\n");
printf(" 5. Configure both the Local and Remote ports to %d.\n\n", udp_port);
printf("Enjoy your testing experience! :)\n\n");
// Main loop: Generate and send waveforms
while (1)
{
float values[num_functions];
for (int i = 0; i < num_functions; i++)
{
values[i] = generate_wave_value(wave_types[i], frequencies[i], phases[i]);
phases[i] += phase_increment[i];
if (phases[i] > TWO_PI)
phases[i] -= TWO_PI;
}
// Format data as a comma-separated string
char buffer[MAX_BUFFER_SIZE];
int offset = 0;
for (int i = 0; i < num_functions; i++)
{
offset += snprintf(buffer + offset, sizeof(buffer) - offset, "%.2f",
values[i]);
if (i < num_functions - 1)
offset += snprintf(buffer + offset, sizeof(buffer) - offset, ",");
}
snprintf(buffer + offset, sizeof(buffer) - offset, "\n");
// Send data via UDP
// clang-format off
ssize_t sent_bytes = sendto(sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
// clang-format on
if (sent_bytes < 0)
{
perror("Send failed");
close(sockfd);
return 1;
}
// Optionally print the generated data
if (verbose)
printf("Sent data: %s", buffer);
// Sleep for the specified interval
sleep_ms(send_interval_ms);
}
close(sockfd);
return 0;
}

View File

@ -35,6 +35,24 @@ This directory contains various examples demonstrating how to use Serial Studio
- **README.md**: Comprehensive setup instructions for GPS configuration, including Serial Studio setup.
- **Screenshots**: `project-setup.png` and `screenshot.png` for guidance on map visualization in Serial Studio.
### 5. UDP Function Generator
- **Description**: This example generates real-time waveforms (sine, triangle, sawtooth, and square) and transmits them over an UDP socket locally. It is designed to generate data that can be visualized in **Serial Studio**, where you can observe and analyze the generated signals in real-time. The program is versatile and can also be used to stress-test Serial Studio's performance under continuous, high-frequency data streams.
- **Contents**:
- **udp_function_generator.c**: The main C program that generates waveforms and sends them via UDP.
- **README.md**: Detailed setup and usage instructions for configuring and running the program with Serial Studio.
- **Screenshots**: Includes `serial-studio-setup.png` for configuration and `waveform-visualization.png` showcasing real-time waveform plots in Serial Studio.
- **Key Features**:
- Generates multiple waveform types: sine, triangle, sawtooth, and square.
- Configurable waveform properties: frequency, phase, and transmission interval.
- Sends waveform data over UDP, making it ideal for network-based signal processing.
- Option to print generated data for debugging and analysis.
- Warns about high frequencies that may cause aliasing or distortion.
:warning: Using sub-millisecond intervals is likely to overload Serial Studio's event system, potentially causing crashes and/or hangs. If you encounter this issue, consider running Serial Studio with a debugger and sharing your findings to help improve and address this limitation in future releases. Your feedback is invaluable in making Serial Studio more robust!
## Getting Started
To use these examples: