LWDAQ Function Generator (A3050)

© 2024 Nathan Sayer, Open Source Instruments Inc.
© 2024-2025 Kevan Hashemi, Open Source Instruments Inc.


Contents

Description
Versions
Set-up
TclTk Interface
Network Interface
Operation
Design
Modifications
Development

Description

[19-FEB-25] The Function Generator (A3050) is a Power-over-Ethernet (PoE), two-channel, programmable signal generator. The latest version provides sinusoidal output from 1 mHz to 2 MHz with an output dynamic range of ±10 V for high-impedance loads and ±5 V for 50-Ω loads. We connect the generator to our local area network (LAN) with a PoE switch. It then acts as a TCPIP (Transmission Control Protocol and Internet Protocol) server with its own address and listening port. The function generator contains two signal sources, each of with its own 8-KByte waveform memory, 13-Bit waveform length, 32-Bit clock divisor, 8-Bit DAC (Digital to Analog Converter), variable low-pass filter, variable attenuator, power amplifier, and 50-ω output.


Figure: Function Generator (A3050C). Green lights from left to right: −12V, +3.3V, +5V, +12V. White: Channel Two Active. Channel Two Output. Hardware reset switch, configuration reset switch. White: Channel One Active. Channel One Output. Red: hardware reset. Amber: configuration reset, data strobe, ethernet link.

To communicate with the function generator, we open a socket to its listening port. Once the socket is established, we read and write bytes to the socket using a binary messaging protocol. These messages perform simple actions such as writing to or reading from a location in the function generator's memory. When shipped, the A3050 is configured with address 10.0.0.37 and listens on port 90. We can change the address and listening port of the function generator by uploading a new TCPIP configuration to its non-volatile memory. We can return the default address and port by performing a reconfiguration reset.

Versions

[04-NOV-24] The following versions of the Function Generator (A3050) are defined.

Version Range
(High Z)
Range
(50 Ω)
Minimum
Frequency
Corner
Frequency
Comment
A3050A ±9.5 V ±4.8 V 1 mHz 2 MHz Prototype
A3050B ±9.5 V ±4.8 V 1 mHz 1 MHz Discontinued
A3050C ±10.0 V ±5.0 V 1 mHz 2 MHz Active
Table: Versions of the Function Generator (A3050).

For each version, we give the minimum frequency, as dictated by the length of the clock divider in the generator's firmware, and the maximum frequency, as defined by a 3 dB drop in sinusoidal output amplitude compared to the low-frequency amplitude.

Specification

[21-FEB-25] The dynamic range of the function generator outputs is ±10 V for a high-impedance load, or ±5 V for a 50-Ω load. The plot below shows how the output voltage varies with the signal value. Each signal value is an eight-bit number that drives the channel's digital to analog converter.


Figure: Output Voltage vs. Signal Value. The signal value varies from 0 to 255, with 128 producing a voltage close to 0 V on the output.

Each of the two signal outputs of the A3050C is equipped with its own 8-KByte waveform memory. Given that each point in the waveform is itself a single byte that will control the output voltage of one of our eight-bit digital to analog converters (DACs), we see that we can define waveforms with up to 8192 points. Our source code of our LWFG package shows how we can use these 8192 points to generate useful waveforms like a sinusoidal sweep, in which we record in the waveform memory the entire sweep, and then instruct the function generator to repeat the sweep at a certain period, and run through the points in the sweep at a certain frequency.

Set-up

[19-FEB-25] On the back side of the function generator is an RJ-45 socket with a 100-Base-T PoE interface. Plug the function generator into a PoE (Power over Ethernet) switch with a network cable. The cable can be shielded or unshielded, but it must be CAT-5 or above to support 100-Base-T communication. When you first set up the function generator, press the RESET and CONFIG buttons on the front panel. Release the RESET button, but keep holding the CONFIG button for three seconds. The function generator will reset its non-voltile TCPIP configuration to the factory default. Its IP address will be 10.0.0.37 and it will listen on port 90.

If you want to change the IP address or listening port number, you can open a socket to the function generator and use the config_write instruction to upload a new TCIP configuration string. But we recommend you perform your first re-configuration with our Configurator Tool. Download our LWDAQ Software as a zip archive from ouur LWDAQ Download page, or clone the LWDAQ GitHub repository. Follow the instructions in the Configurator section of the LWDAQ Manual to update the IP address. We recommend you do not change the listening port number.

Once you have the IP address and port set to your liking, the function generator is set up and ready to use. You can configure it right away with the The SCT_Check.tcl tool in the LWDAQ Tools/More menu, or you can open a socket to the function generator with your own code, and start to read and write to its control registers. Do not worry about damaging the function generator with any kind of read or write: no such damage is possible. You can always restore the functiong generator by performing another configuration reset.

When you open a socket to to the A3050, make sure the socket is configured for binary byte transfer, without any newline characters or UTF-8 encoding restrictions. Once open, use the function generator's messaging protocol to read and write to locations in the function generator's memory map. Use our memory map tables to find the locations you need to write to.

Network Interface

[21-FEB-25] The A3050 function generator is an example an LWDAQ Server. We communicate with this server using the LWDAQ-TCPIP message protocol.


Figure: The LWDAQ-TCPIP Message Format. Messages use big-endian format, meaning the most significant byte of any multi-byte variable comes first.

We open a socket to the function generator, using its IP address and listening port. We make sure the socket is configured for binary communication. We do not want the socket to restrict the communication to ASCII or UTF=8 characters. Nor do we want it to add a line break every time we flush our messages out of the socket. Once we have opened a socket, we can start to send messages. Each message has the format given above. The message begins with a start byte (0xA5). After that comes a four-byte message identifier and a four-byte content length. The content length can be zero, but if it is non-zero, we follow with that exact number of bytes. The message ends with an end byte (0x5A). We can transmit as many messages as we like. The code below shows how we can read the function generator's software version number.

When we have sent all the messages we want to send, we must take care how we terminate our communication. The protocol requires that we end our transmission with an EOT (End of Transmission, 0x04) byte. When the function generator receives this byte, it must immediately closes the socket, even if it has not finished executing the instructions it has received during the transmission. The last message we send must be a byte read, such as a version number read, and the client must wait until it receives the returned byte before writing the EOT byte and closing the socket for itself. The server will not execute the byte read until all the preceeding messages are processed, so we close the socket when the instructions are complete, but no sooner.

# Open a socket to the function generator at local address 192.168.1.14. We will
# connect to the standard LWDAQ server port, which is port 90.
set sock [socket 192.168.1.14 90]

# Configure the socket for binary data transfer. No newline characters will be
# added to data transmitted, nor will we wait for newline characters when we
# read.
fconfigure $sock -translation binary

# Compose the version read measage out of binary bytes, each of which we specify
# with an escape sequence. Write the message to the socket output buffer.
puts -nonewline $sock "\xA5\x00\x00\x00\x00\x00\x00\x00\x00\x5A"

# Transmit the message by flushing the output buffer.
flush $sock

# Read fourteen bytes from the socket, which will be the prefix byte, four
# message identifier, bytes, four content length bytes, four software version
# bytes, and a suffix byte.
set message [read $sock 14]

# Scan these bytes for the prefix, identifier, length, version, and suffix.
# Write these values to the terminal.
binary scan $message H2IIIH2 prefix id len version suffix
puts "$prefix $id $len $version $suffix" 

# Send the end of transmission byte to the server: we want it to close the 
# socket right away.
puts -nonewline $sock "\x04"

# Close our side of the socket.
close $sock

The messages serve functions such as byte_write, stream_write, and version_read, each of which has its own numerical identifier. For a definitive presentation of the message types and their formats, we refer you to the messaging protocol specification. We summarize the message identifiers, names, and functionas in the table below.

Message
Identifier
NameFunction
0version_readread relay software version
1byte_readread from controller location and return result
2byte_writewrite to controller location
3stream_readread repeatedly from controller location
4data_returnmessage contains block of data
5byte_pollpoll a controller byte until it equals specified value
6loginsend password to relay to attain higher access privilege
7config_readread relay configuration file
8config_writere-write relay configuration file
9mac_readread relay MAC address
10stream_deletewrite repeatedly to controller location
11echoreceive and return the message contents
12stream_writewrite a block of data to a controller location
13rebootreboot the relay
Table: Message Identifiers Defined by the LWDAQ-TCPIP Message Protocol.

In the Tcl code below, we arrange to sets the attenuator register for channel number two (CH2) to 0x0F, which provides maximum attenuition of the raw signal before output.

# Open a socket to the function generator and configure.
set sock [socket 192.168.1.14 90]
fconfigure $sock -translation binary

# Four byte writes to the data address locations set the data address to
# 0x00008017, which is the data address location of the CH2 attenuator register.
puts -nonewline $sock "\xA5\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x18\x00\x5A"
puts -nonewline $sock "\xA5\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x19\x00\x5A"
puts -nonewline $sock "\xA5\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x1A\x80\x5A"
puts -nonewline $sock "\xA5\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x1B\x17\x5A"

# A single byte write to the data portal sets the attenuator.
puts -nonewline $sock "\xA5\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x3F\x0F\x5A"

# Transmit a software version read and wait for reply. When we receive the reply, we
# sent the EOT byte and close the socket.
puts -nonewline $sock "\xA5\x00\x00\x00\x00\x00\x00\x00\x00\x5A"
flush $sock
set message [read $sock 14]
puts -nonewline $sock "\x04"
close $sock

The above code would be simplified by the introduction of dedicated routines for writing and reading bytes, which we will demonstrate in our TclTk interface section.

Register Map

[21-FEB-25] The A3050 presents itself as a 64-byte (6-bit) control space and a 4-GByte (32-bit) data space. The control space is accessible throught the LWDAQ-TCPIP byte_read and byte_write instructions. The byte_write instruction specifies the control address and a byte to write. The byte_read instruction specifies a control address to read, then returns the byte read from that control address.

Control Space Address (Hex)Contents
00Hardware identifier
12Hardware version
13Firmware version
18Data Address, Byte 3
19Data Address, Byte 2
1AData Address, Byte 1
1BData Address, Byte 0
3FData portal
Table: Function Generator Controller Space Adress Map.

All of the registers necessary for controlling the function generator are located in the data space. To gain access to the function generator's data space, we write the 32-bit data space address to the four data address registers using four byte_write instrcutions. We can then write to or read from the specified data address location using a byte_write or byte_read to the data portal address. After any byte_write or byte_read the data address increments automatically, so that we can immediately write to or read from the next location in the data space with another byte_write or byte_read. If we want to write a block of data to the data space, however, we can use the stream_write instruction, which takes as its content a block of bytes to be written consecutively to the data portal.

Data Address (Hex)Contents
0000...1FFFWaveform Memory, Channel 1
4000...5FFFWaveform Memory, Channel 2
8000Divisor, Channel 1, Byte 3
8001Divisor, Channel 1, Byte 2
8002Divisor, Channel 1, Byte 1
8003Divisor, Channel 1, Byte 0
8004Waveform Length, Channel 1, Byte 1
8005Waveform Length, Channel 1, Byte 0
8006Filter Control Register, Channel 1
8007Attenuator Control Register, Channel 1
8010Divisor, Channel 2, Byte 3
8011Divisor, Channel 2, Byte 2
8012Divisor, Channel 2, Byte 1
8013Divisor, Channel 2, Byte 0
8014Waveform Length, Channel 2, Byte 1
8015Waveform Length, Channel 2, Byte 0
8016Filter Control Register, Channel 2
8017Attenuator Control Register, Channel 2
Table: Function Generator Data Adress Space Map.

Time constants specified by various binary, hexadecimal, and decimal values written to a filter control register are given below.

BinaryHexDecimalResistanceCapacitanceTime Constant
00000001010151 Ω250 pF (parasitic)12.5 ns
00010001111751 Ω1 nF51 ns
000100101218110 Ω1 nF110 ns
000101001420270 Ω1 nF270 ns
000110001824560 Ω1 nF560 ns
00100001213351 Ω22 nF1.12 μs
001000102234110 Ω22 nF2.42 μs
001001002436270 Ω22 nF5.94 μs
001010002840560 Ω22 nF12.3 μs
01000001416551 Ω500 nF25.5 μs
010000104266110 Ω500 nF55 μs
010001004468270 Ω500 nF135 μs
010010004872560 Ω500 nF280 μs
100000018112951 Ω20 μF1.02 ms
1000001082130110 Ω20 μF2.2 ms
1000010084132270 Ω20 μF5.4 ms
1000100088136560 Ω20 μF11.2 ms
Table: Filter Register Values and Their Time Constants

The attenuition registers allow us to generate a signal much larger than we need, then attenuate it just before output so as to reduce the quantization noise we see on the final output. The table below gives the attenuition values we obtain by measurement of our first five function generators.

BinaryHexDecimalAttenuition
00000001.04
00010110.320
00110330.116
01110770.0588
11110f150.0360
Table: Attenuition Register Values and Their Amplitude Multipliers.

Upon reset, which we can invoke with the reset button or by unplugging and re-plugging the PoE cable, the registers return to the following values.

ParameterReset Value
Output Sample Value0x80
Clock Divisor0x00000000
Waveform Length0x0000
Filter Control Register0x01
Attenuation Control Register0x00
Table: Register Reset Values.

TclTk Interface

[21-FEB-25] Our LWDAQ Software provides routines that perform LWDAQ-TCPIP messaging. Clone the LWDAQ Repository and you can run the program right away, on Linux, MacOS, or Windows. The LWDAQ software performs all its TCPIP communication with Tcl. Its graphical user interface is provided by Tk. Even if you do not want to use LWDAQ to communicate with the function generator, you will find Tcl routines to communicate with generic LWDAQ servers, and specifically with the function generator, in its source code.

The Driver.tcl file contains routines for communicating with LWDAQ servers. All LWDAQ commands are listed and described in the LWDAQ Command Reference. We write to a byte in control space with LWDAQ_byte_write. We read with LWDAQ_byte_read. Writing to the data space is a two-step process. We first use LWDAQ_set_data_addr to write four bytes to the data address, which resides in the control space. We then use LWDAQ_stream_write to write a block of data to the data portal, which is a single byte in control space. The LWFG package does not use any byte read or write routines, but instead relies upon higher-level routines defined in Driver.tcl that make it easy for us to work in the function generator's data space. The code below, taken from package LWFG V1.4, demonstrates the use of these routines with the function generator.

# Open a socket to the function generator.
set sock [LWDAQ_socket_open $ip]

# Write the waveform values to the waveform memory.
LWDAQ_set_data_addr $sock $LWFG(ch$ch_num\_ram)
LWDAQ_stream_write $sock $LWFG(data_portal) [binary format c* $values]

# Set the filter configuration register.
LWDAQ_set_data_addr $sock $LWFG(ch$ch_num\_rc)
LWDAQ_stream_write $sock $LWFG(data_portal) [binary format c [expr $filter]]

# Set the attenuator configuration register.
LWDAQ_set_data_addr $sock $LWFG(ch$ch_num\_att)
LWDAQ_stream_write $sock $LWFG(data_portal) [binary format c [expr $attenuator]]

# Set the clock divisor.
LWDAQ_set_data_addr $sock $LWFG(ch$ch_num\_div)
LWDAQ_stream_write $sock $LWFG(data_portal) [binary format I [expr $divisor - 1]]

# Set the waveform length.
LWDAQ_set_data_addr $sock $LWFG(ch$ch_num\_len)
LWDAQ_stream_write $sock $LWFG(data_portal) [binary format S [expr $num_pts - 1]]

# Wait for the controller to be done with configuration.
set id [LWDAQ_hardware_id $sock]

The final call to LWDAQ_hardware_id is a way to wait for the function generator to finish all the writes to the data space before we close the socket. If we close the socket too soon, the function generator will abandon its work.

[19-FEB-25] A Tcl interface for the Function Generator (A3050) is provided by the LWFG.tcl (Long-Wire Function Generator) package, which you will find in the LWDAQ package directory. Load the LWFG package into your own LWDAQ process with "package require LWFG". The LWFG package provides routines to configure your function generator to produce sine, square, and triangle waves. The LWFG_configure routine takes six parameters.

LWFG_configure ip ch_num waveform frequency v_lo v_hi

Here, ip is the IP address of the function generator's TCPIP server. The LWFG assumes that the function generator is listening on port 90. We have channels CH1 and CH2, which we identify with 1 and 2. The two voltages v_lo and v_hi are the voltages of the minimum and maximum points in the waveform respectively. For the waveform we can specify "sine", "square", or "triangle". The SCT_Check.tcl tool in the LWDAQ Tools/More menu provides an example graphical user interface to the A3050 function generator. We can enter sine, square, or triangle for the waveform type, but also "sweep", which will generate a sinusoidal sweep waveform. The tool has us specify peak-to-peak amplitude when terminated by 50 Ω.

Operation

[04-NOV-24] As an example waveform, consider a 10-Vpp, 1.25-MHz sine wave. We can do this by writing "128, 152, 176, 198, 218, 234, 245, 253, 255, 253, 245, 234, 218, 198, 176, 152, 128, 103, 79, 57, 37, 21, 10, 2, 0, 2, 10, 21, 37, 57, 79, 103" to the waveform memory. We write 31 to the waveform length and 0 to the divisor. We now have a sample clock of 40 MHz ÷ 1 = 40 MHz, and we have 32 samples to step through, so we produce the entire waveform at 1.25 MHz.


Figure: Sine wave, waveform length=31, filter=1, divisor=0.

The 1.25 MHz sine wave was fast enough that it didn't require a filter to smooth out. We now write 124 to the divisor register and our frequency drops to 10 kHz.


Figure: Sine wave, waveform length=31, RC=1, divisor=124.

We can see each sample as a step in the sinusoid. We write "34" to the filter control register. The resulting waveform has about 1 dB less amplitude but looks smoother, as a result of the low-pass filter we deploy with our filter register.


Figure: Sine wave, waveform length=31, filter=34, divisor=124.

The LWFG package picks the filter function automatically to suit the period of sinusoidal and triangle waves. It always disables the filter for square waves.

Design

[21-NOV-24] All our designs are Open Source, protected by the GPL 3.0 license.

S3050C_1: Schematic for A3050C, Page 1.
S3050C_2: Schematic for A3050C, Page 2.
S3050C_3: Schematic for A3050C, Page 3.
S3050C_4: Schematic for A3050C, Page 4.
S3050C_5: Schematic for A3050C, Page 5.
A305001D: KiCAD project for PCB used in A3050C
S3050X_1: Schematic for isolated DC-DC converter.
A305002B: PCB used in the A3050X.
P3050: Firmware for the A3050.

[04-NOV-24] The A3050 runs off an 80-MHz clock. We divide this clock by two, and then by a user-programmable signal divisor to generate the sample clock. We set the divisor by writing the divisor minus one to the thirty-two bit divisor register. We use the sample clock to increment the waveform memory address and so obtain the next sample value in our waveform. We connect the eight-bit memory output directly to an eight-bit resistor digital to analog converter (DAC). The DAC output varies from 0 V to 3.3 V, these being the logic power supplies. The A3050 circuit takes the DAC output and shifts it down by 1.65 V before amplifying until the range is slightly greater than ±10 V. After the final amplifier, we have a 50-Ω, 3 W resistor, which makes the A3050 outputs tolerant of continuous short-circuits.


Figure: A3050B LWDAQ Function Generator inside its enclosure with the lid removed, front view.

On the way through the amplifiers, the signal passes through a resistor-capacitor low-pass filter network that we can configure with one of the A3050 registers. The LWFG package sets the RC filters automatically so as to smooth out the sharp edges of our eight-bit steps. The signal also passes through a resistor-resistor attenuator network that we can control with another A3050 register. The LWFG package implements a calibration of these attenuators and selects attenuators automatically so that we can use the largest origin signal before a final attenuation, by which means we reduce quantization noise in the output.


Figure: A3050B LWDAQ Function Generator inside its enclosure with the lid removed, top view.

The signal itself will be made up of at least two sample values, and all these values will be written to the waveform memory during waveform configuration. The LWFG package always uses at least half of the 8-KByte memory available for each of the two A3050C channels. It does this by repeating the waveform as many times as necessary, and by allowing itself to repeat the waveform, combined with the clock divisor, we are able to generate frequencies with precision better than ±0.1%. After writing the waveform to memory, we specify the waveform length by writing the length minus one to the sixteen-bit waveform length register.

Modifications

[21-NOV-24] To create the A3050C out of the A3050B, we load 270 Ω for the attenuator series resistors R86 and R91. We load divider resistors 100 Ω, 27 Ω, 10 Ω, and 3.3 Ω for both channels. We load 2.2 kΩ for R46 and R58 to increase the output amplitude.

Development

[04-NOV-24] For a chronological account of the development and production of the A3050, see its Development page. That page provides an account of our work on discrete-component DC-DC converters, which we may deploy in later versions of the A3050, or in other Form C LWDAQ systems.