mirror of
https://github.com/Serial-Studio/Serial-Studio.git
synced 2025-01-15 05:22:53 +08:00
Add function generator example/test
This commit is contained in:
parent
4303e1a5e3
commit
cc8609cd94
4
.gitignore
vendored
4
.gitignore
vendored
@ -5,6 +5,10 @@
|
||||
.LSOverride
|
||||
.cache
|
||||
|
||||
# Binary builds
|
||||
udp_function_generator
|
||||
udp_function_generator.exe
|
||||
|
||||
# Build & IDEs
|
||||
*.kdev*
|
||||
build/*
|
||||
|
165
examples/FunctionGenerator/README.md
Normal file
165
examples/FunctionGenerator/README.md
Normal 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.
|
BIN
examples/FunctionGenerator/doc/screenshot.png
Normal file
BIN
examples/FunctionGenerator/doc/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 MiB |
321
examples/FunctionGenerator/udp_function_generator.c
Normal file
321
examples/FunctionGenerator/udp_function_generator.c
Normal 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;
|
||||
}
|
@ -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:
|
||||
|
Loading…
x
Reference in New Issue
Block a user