diff --git a/.gitignore b/.gitignore index d603eca1..5665131d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,10 @@ .LSOverride .cache +# Binary builds +udp_function_generator +udp_function_generator.exe + # Build & IDEs *.kdev* build/* diff --git a/examples/FunctionGenerator/README.md b/examples/FunctionGenerator/README.md new file mode 100644 index 00000000..c3e7ee68 --- /dev/null +++ b/examples/FunctionGenerator/README.md @@ -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 `: UDP port (default: `9000`). +- `-i `: Transmission interval in milliseconds (default: `1.0 ms`). +- `-n `: 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. diff --git a/examples/FunctionGenerator/doc/screenshot.png b/examples/FunctionGenerator/doc/screenshot.png new file mode 100644 index 00000000..f50aa2d2 Binary files /dev/null and b/examples/FunctionGenerator/doc/screenshot.png differ diff --git a/examples/FunctionGenerator/udp_function_generator.c b/examples/FunctionGenerator/udp_function_generator.c new file mode 100644 index 00000000..f1895812 --- /dev/null +++ b/examples/FunctionGenerator/udp_function_generator.c @@ -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 `: UDP port (default: 9000) +/// - `-i `: Send interval in milliseconds (default: 1.0 ms) +/// - `-n `: 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 +#include +#include +#include +#include +#include +#include + +#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 : UDP port (default: 9000)\n"); + printf(" -i : Send interval in milliseconds (default: 1.0 ms)\n"); + printf(" -n : 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; +} diff --git a/examples/README.md b/examples/README.md index 145ee38e..3f9d1e9b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -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: