8 releases
0.1.10 | Jul 12, 2021 |
---|---|
0.1.9 | Jan 12, 2021 |
#390 in Math
280KB
7.5K
SLoC
signal-gen-cjds66-lib
Control the GH-CJDS66 60MHz Signal Generator
https://gitlab.com/defcronyke/signal-gen-cjds66
Copyright © 2020-2021 Jeremy Carter jeremy@jeremycarter.ca
MIT License
By using this software, you agree to the LICENSE TERMS outlined in the file titled LICENSE.md contained in the top-level directory of this project. If you don't agree to the LICENSE TERMS, you aren't allowed to use this software.
Purpose:
This is an unofficial project which fully implements the official communication spec for the DDS Signal Generator and Counter device known as the "Koolertron Upgraded 60MHz DDS Signal Generator Counter, High Precision Dual-channel Arbitrary Waveform Function Generator Frequency Meter 200MSa/s (60MHz) Model: GH-CJDS66-FU" (less a few spec errata, which you can read about in a commit message here: 713b026a4e10807d23f7436d26649dcc4c584019)
Device and USB Interface info:
Manufacturer page with info on where to buy it:
https://www.koolertron.com/koolertron-upgraded-60mhz-dds-signal-generator-counterhigh-precision-dualchannel-arbitrary-waveform-function-generator-frequency-meter-200msas-60mhz-p-867.html
Linux lsusb
output:
ID 1a86:7523 QinHeng Electronics CH340 serial converter
Basic Usage Code Example:
Getting Started:
-
This library is published as a Rust crate on crates.io, so that you can use it in your own projects easily:
https://crates.io/crates/signal-gen-cjds66-lib -
The crate's auto-generated documentation can be viewed on docs.rs here:
https://docs.rs/signal-gen-cjds66-lib -
You can also download the latest development version docs here if you want the docs for the master branch (development version) of the crate. This version also includes the docs for the associated command line program:
signal-gen-cjds66 docs (Development Version) -
To use this crate, put the following lines in your Rust project's
Cargo.toml
file (but substitute the newest version number listed at the crates.io link above so you're using the latest version):
[dependencies]
signal-gen-cjds66-lib = { version = "0.1" }
- IMPORTANT: Note that this crate uses three-part version numbers,
such as
0.1.7
, but it's better to leave off the third part of the version number in yourCargo.toml
file, because that way you can more easily and automatically update to the latest0.1.x
versions, without having to update that third number by hand. It would be annoying otherwise, since that third number may need to change frequently.
Code Example - Print the device's model number and serial number:
examples/basic-usage.rs
/*! A basic usage example for the library.
Control one signal generator device on
Linux or Windows. Print the device's
model number and serial number.
*/
extern crate signal_gen_cjds66_lib;
extern crate clap;
use signal_gen_cjds66_lib::command::*;
use signal_gen_cjds66_lib::error;
use signal_gen_cjds66_lib::error::From;
use signal_gen_cjds66_lib::serial::*;
use clap::ErrorKind;
/// The main entrypoint.
fn main() {
/* Call the main logic function, and save the
result, which will either be Ok(0) on success,
or some type of error.
*/
let res = real_main();
/* Exit the program, returning an appropriate
exit code to the parent shell or execution
environment. This is important so whoever
ran this program (a bash or batch script maybe)
can check if anything went wrong while the
program was running, and react accordingly.
*/
std::process::exit(
/* If there was an error, get the proper
exit code for it, to return on exit.
*/
error::handle_exit(res)
.map_or_else(
|e| e.code, // Get the numeric error exit code (non-zero).
|code| code, // Or, get the success exit code (0).
)
);
}
/** The main logic area. Rust is a bit weird so it's
better in this case to have this function separate
from main().
*/
fn real_main() -> Result<i32, error::Error> {
// An error variable, to specify if an error happened.
let mut err: Option<error::Error> = None;
/* Choose the serial device location depending
on which operating system you're using.
HINT: You can put more than one device location
in each of these arrays if you'd like to control
multiple devices in sequence.
NOTE: The values listed below are the default values
used by the first connected serial device on my
Linux and Windows systems. If you get a connection
error when you run this code, make sure your signal
generator is plugged into your computer and powered
on.
If you still have a connection error, you'll probably
need to find the correct path for your system and
change the values below so they are pointing at the
correct device.
*/
let devices =
if cfg!(unix) { // If running on Linux (or UNIX).
vec!["/dev/ttyUSB0".to_string()]
} else if cfg!(windows) { // If running on Windows.
vec!["COM3".to_string()]
} else { // If unsure of the operating system.
vec!["/dev/ttyUSB0".to_string()]
};
/* Iterate over each device path you configured above,
and perform operations on each device sequentially.
This example is only connecting to one device, but you
can add more above if you want.
*/
for device in &devices {
/* A new error varialbe (an alias really), borrowing a mutable
reference to our error variable from above, and storing it in
an immutable binding.
*/
let err = &mut err;
/* Establish a serial connection to the signal generator device,
with all the correct communication options configured. The
second parameter to the `SerialPortType::new()` function should
always be false unless we are making a fake connection for
testing purposes, which is currently only useful for the crate's
automated test suite.
*/
println!("\nOpening communication link with device: {}\n", device);
let opened = SerialPortType::new(device, false).map_or_else(
// If opening the device failed, return an error.
|e| {
Err(error::Error::with_description(&format!("(device: {}): {}: make sure the device is connected and turned on, or try specifying a different device path with -d /path/to/device", device, e), clap::ErrorKind::Io))
},
/* If the device was opened successfully, go on to run some
commands which operate on the device.
*/
|mut port| {
// A new error variable, just for this inner scope.
let mut err: Option<error::Error> = None;
/* Get the model and serial numbers from the device
(verbose version). The second parameter on the
`get_model_and_serial()` function enables
"verbose output mode" when you pass in a number
higher than 0.
Note that this function, and all the other ones
which communicate with the signal generator, are
defined in the `command` module, and are imported
into the global scope in this file by this line
near the top of the file:
`use signal_gen_cjds66_lib::command::*;`
*/
println!("\nGetting the device's model number and serial number, with verbose output mode enabled...\n");
get_model_and_serial(&mut port, 1)
.map_err(
|e| {
// If there was a problem, report the error.
err = Some(error::Error::from_clap_error(e));
println!("{}", err.as_ref().unwrap());
}
)
.unwrap();
/* Get model and serial number from the device
(non verbose version). Notice the second
parameter to the `get_model_and_serial()`
function below is 0 this time, which turns off
"verbose output mode".
NOTE: Every function in the `command` module
accepts the same type of "verbose" parameter,
although it's not always the second parameter,
rather, it's usually the last one.
*/
println!("\nGetting the device's model number and serial number, with verbose output mode disabled...\n");
get_model_and_serial(&mut port, 0)
.map_err(
|e| {
// If there was a problem, report the error.
err = Some(error::Error::from_clap_error(e));
println!("{}", err.as_ref().unwrap());
}
)
.unwrap();
println!(""); // Line break for nicer output.
/* Return Ok(0) on success, or an error if there were
any.
*/
err.map_or_else(|| { Ok(0) }, |v| { Err(v) })
},
);
/* If we can't connect to a certain device, skip it and continue
with any remaining devices we haven't tried yet.
*/
if opened.is_err() {
*err = opened.map_or_else(
|e| {
// If there was a problem, report the error.
if e.kind == ErrorKind::Io {
println!("{}", e);
}
Some(e)
},
|_val| None,
);
continue;
}
}
/* If there was a problem during any of the above operations,
return the error to the parent function.
*/
if err.is_some() {
Err(err.unwrap())
} else { // Otherwise, return Ok(0) to indicate success.
Ok(0)
}
}
NOTE: The above example can be found in the file signal-gen-cjds66-lib/examples/basic-usage.rs, and you can try running it if you want, by issuing the following command from within this signal-gen-cjds66-lib/ directory:
cargo run --release --example basic-usage
Dependencies
~5MB
~92K SLoC