[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.
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.
[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 |
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.
[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.
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.
[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.
[21-FEB-25] The A3050 function generator is an example an LWDAQ Server. We communicate with this server using the LWDAQ-TCPIP message protocol.
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 | Name | Function |
0 | version_read | read relay software version |
1 | byte_read | read from controller location and return result |
2 | byte_write | write to controller location |
3 | stream_read | read repeatedly from controller location |
4 | data_return | message contains block of data |
5 | byte_poll | poll a controller byte until it equals specified value |
6 | login | send password to relay to attain higher access privilege |
7 | config_read | read relay configuration file |
8 | config_write | re-write relay configuration file |
9 | mac_read | read relay MAC address |
10 | stream_delete | write repeatedly to controller location |
11 | echo | receive and return the message contents |
12 | stream_write | write a block of data to a controller location |
13 | reboot | reboot the relay |
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.
[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 |
---|---|
00 | Hardware identifier |
12 | Hardware version |
13 | Firmware version |
18 | Data Address, Byte 3 |
19 | Data Address, Byte 2 |
1A | Data Address, Byte 1 |
1B | Data Address, Byte 0 |
3F | Data portal |
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...1FFF | Waveform Memory, Channel 1 |
4000...5FFF | Waveform Memory, Channel 2 |
8000 | Divisor, Channel 1, Byte 3 |
8001 | Divisor, Channel 1, Byte 2 |
8002 | Divisor, Channel 1, Byte 1 |
8003 | Divisor, Channel 1, Byte 0 |
8004 | Waveform Length, Channel 1, Byte 1 |
8005 | Waveform Length, Channel 1, Byte 0 |
8006 | Filter Control Register, Channel 1 |
8007 | Attenuator Control Register, Channel 1 |
8010 | Divisor, Channel 2, Byte 3 |
8011 | Divisor, Channel 2, Byte 2 |
8012 | Divisor, Channel 2, Byte 1 |
8013 | Divisor, Channel 2, Byte 0 |
8014 | Waveform Length, Channel 2, Byte 1 |
8015 | Waveform Length, Channel 2, Byte 0 |
8016 | Filter Control Register, Channel 2 |
8017 | Attenuator Control Register, Channel 2 |
Time constants specified by various binary, hexadecimal, and decimal values written to a filter control register are given below.
Binary | Hex | Decimal | Resistance | Capacitance | Time Constant |
---|---|---|---|---|---|
00000001 | 01 | 01 | 51 Ω | 250 pF (parasitic) | 12.5 ns |
00010001 | 11 | 17 | 51 Ω | 1 nF | 51 ns |
00010010 | 12 | 18 | 110 Ω | 1 nF | 110 ns |
00010100 | 14 | 20 | 270 Ω | 1 nF | 270 ns |
00011000 | 18 | 24 | 560 Ω | 1 nF | 560 ns |
00100001 | 21 | 33 | 51 Ω | 22 nF | 1.12 μs |
00100010 | 22 | 34 | 110 Ω | 22 nF | 2.42 μs |
00100100 | 24 | 36 | 270 Ω | 22 nF | 5.94 μs |
00101000 | 28 | 40 | 560 Ω | 22 nF | 12.3 μs |
01000001 | 41 | 65 | 51 Ω | 500 nF | 25.5 μs |
01000010 | 42 | 66 | 110 Ω | 500 nF | 55 μs |
01000100 | 44 | 68 | 270 Ω | 500 nF | 135 μs |
01001000 | 48 | 72 | 560 Ω | 500 nF | 280 μs |
10000001 | 81 | 129 | 51 Ω | 20 μF | 1.02 ms |
10000010 | 82 | 130 | 110 Ω | 20 μF | 2.2 ms |
10000100 | 84 | 132 | 270 Ω | 20 μF | 5.4 ms |
10001000 | 88 | 136 | 560 Ω | 20 μF | 11.2 ms |
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.
Binary | Hex | Decimal | Attenuition |
---|---|---|---|
0000 | 00 | 0 | 1.04 |
0001 | 01 | 1 | 0.320 |
0011 | 03 | 3 | 0.116 |
0111 | 07 | 7 | 0.0588 |
1111 | 0f | 15 | 0.0360 |
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.
Parameter | Reset Value |
---|---|
Output Sample Value | 0x80 |
Clock Divisor | 0x00000000 |
Waveform Length | 0x0000 |
Filter Control Register | 0x01 |
Attenuation Control Register | 0x00 |
[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 Ω.
[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.
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.
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.
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.
[21-NOV-24] All our designs are Open Source, protected by the GPL 3.0 license.
S3050C_1: Schematic for A3050C, Page 1.[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.
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.
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.
[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.
[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.