The most recent version of this page is a draft.This version (22 Mar 2017 22:57) was *approved* by rgetz.The Previously approved version (15 Mar 2017 19:50) is available.

**This is an old revision of the document!**

The AD9361 Filter Design Wizard is a small MATLAB App, which can be used to design transmitter and receiver FIR filters, which take into account the magnitude and phase response from other analog and digital stages in the filter chain. This tool provides not only a general purpose low pass filter designer, but also magnitude and phase equalization for other stages in the signal path.

With this wizard, users can perform the following tasks:

- Choose correct digital filters to use for receive and transmit.
- Design the programmable FIR filters, get the filter coefficients and save them in a .ftr file, which can be directly loaded into the hardware.
- Examine the independent response of each filter, and the composite response of all the filters, including both digital and analog filters.

Here is a brief introduction on why everyone needs to, and how to use this tool.

**PREREQUISITES**

In order to run the wizard, your MATLAB license needs to include the following components:

- MATLAB (R2015b or higher version is required)
- Signal Processing Toolbox
- DSP System Toolbox

In addition, in order to generate HDL, your MATLAB license needs to include the following component:

- Fixed-Point Designer

In order to get the wizard, please go to Analog Devices GitHub. Different releases of AD9361 Filter Design Wizard and their source files can be found here:

- For each release, the wizard is available as a MATLAB App installer (mlappinstall) or in archive form (zip or tarball).

For more information about install and running MATLAB apps, please refer to: http://www.mathworks.com/help/matlab/creating_guis/install-and-run-app.html

If using a checkout or unpacked archive, the application can be run in one of two ways:

- Right click “AD9361_Filter_Wizard.fig” and select “Open in GUIDE” to open the figure. Then type “Ctrl+T” to run the figure.
- Within the application directory run the command “AD9361_Filter_Wizard” from the MATLAB command line.

The Filter Design Wizard has been applied in the SimRF models of AD9361, provided by MathWorks as a hardware support package. **Download this version if to be used with the AD9361 SimRF model:**

To learn more about AD9361 modeling and to download the Tx and Rx models, the hardware support package can be found here:

Generally speaking, there are two ways you can use the design wizard:

- MATLAB App: A graphical user interface is created to facilitate the process of filter design. Users can easily define the input, observe the design performance, and specify the way they want to save the results. This is a more straightforward method to use the wizard.
- MATLAB function: The link to design functions can be found in the Download section. They are MATLAB functions, which users can launch from the MATLAB command window by properly defining the input parameters. Using this way, users have more control of the internal design process.

In this section, we are going to elaborate on the first option - MATLAB App.

After you launch the MATLAB App, there shows a drop-down list in “Device Settings”, which includes the default parameter profiles for several widely used LTE applications. You can move the highlight bar to the one you would like to start with.

This table is stored in github.

LTE Specification | `|` | AD9361 settings | ||||||||
---|---|---|---|---|---|---|---|---|---|---|

LTE Bandwidth Set ^{1)} | Occupied RF Bandwidth | Sample Rate | `|` | Sample Rate | F_{PASS} | F_{STOP} | A_{PASS} | A_{STOP} | ||

MHz | MHz | MSPS | `|` | MSPS | MHz | MHz | dB | dB | max Tx input | Tx input backoff (in dB) |

1.4 | 1.08 | 1.92 | `|` | 1.92 | 0.54 | 0.66 | 0.1 | 80 | ||

3 | 2.7 | 3.84 | `|` | 3.84 | 1.35 | 1.65 | 0.1 | 80 | ||

5 | 4.5 | 7.68 | `|` | 7.68 | 2.25 | 2.75 | 0.1 | 80 | 0.93667 | -0.56830 |

10 | 9 | 15.36 | `|` | 15.36 | 4.5 | 5.5 | 0.1 | 80 | 0.93665 | -0.56850 |

15 | 13.5 | 23.04 | `|` | 23.04 | 6.75 | 8.25 | 0.1 | 80 | 0.93813 | -0.55478 |

20 | 18 | 30.72 | `|` | 30.72 | 9 | 11 | 0.1 | 80 | 0.93813 | -0.55478 |

The LTE Release-8 physical layer specification actually supports 105 different bandwidth options (not just the 6 shown above). Occupied RF Bandwidth from between 1.08MHz to 19.8MHz with 180kHz steps complies with the spec, and while these filters can be designed (manually) they are not included as defaults.

In addition, you can also save your favorite parameter settings in this list, such as “foobar (Rx & Tx)” shown in the figure.

Assume that you choose the “LTE10 (Rx & Tx)” profile, after you click it, all the parameters are filled in automatically for you, as shown in the figure below. There are three categories of input parameters: magnitude specifications, frequency specifications, and AD936x clock settings. If you are satisfied with all the parameters, you can go ahead and click “Design Filter” to start the design.

As soon as the design process completes, you will see a magnitude plot displayed on the top half of the GUI, where the specified Fpass, Fstop, Apass and Astop are highlighted in the plot. The x-axis is from 0 to half of the data rate. Below it, on the right, you will see a “Filter Results” portion, where the actual Apass, Astop, the number of FIR taps and the pass band group delay variance are shown. From these numbers, you will get an idea whether the design meets the requirements quantitatively.

If you are interested in more details of the design performance, you can click the “FVTool” buttons left to “Filter Results” to launch the Filter Visualization Tool (fvtool) provided by The MathWorks. For your convenience, we provide this tool on two different frequency scales. One is from 0 Hz to half of the data rate, the other is from 0 Hz up to half of the converter rate.

If you are mainly interested in pass band, click the top button, it will open the following three figures:

- Magnitude response of half band filters and HB + designed FIR filter.

- Magnitude Response of the designed FIR only. Besides the magnitude response, you can use the toolbar on the upper left corner (as highlighted in square) to navigate to the other responses, including phase response, group delay response, impulse response, poles/zeros and etc. It will enable you to have a better understanding of the designed FIR.

- Overall group delay on pass band. For your convenience, the group delay variance has been calculated and indicated on the figure.

If you are interested in the whole frequency band, click the bottom button, it will open the following figure:

- Magnitude response of half band filters and HB + designed FIR filter. You can easily have a closer observation on certain portion of the magnitude response by using the “Zoom In/Out” functions on the toolbar (as highlighted in square).

For more information about the FVTool, please refer to: http://www.mathworks.com/help/signal/ref/fvtool.html

After the deeper analysis, if you are satisfied with the results and would like to save the designed FIR filter, there are several options you can choose from. These options are in the “Controls” portion on the upper left corner of the GUI.

- Save object and data to workspace: If you will use the designed filter chain with some other MATLAB functions or Simulink models, you can simply leave it in the workspace by clicking “Save to Workspace” button, as shown in the figure below. After click this button and exit the App, you will find a mfilt.cascade object named “AD9361_Tx_Filter_object” or “AD9361_Rx_Filter_object” depending on whether it is on Tx or Rx.

When you click “Save to Workspace”, besides the filter object, there is also a data structure saved to workspace, which will initialize the SimRF model of FMCOMMS2. The data structure is named “FMCOMMS2_TX_Model_init” or “FMCOMMS2_RX_Model_init”.

For more information about the SimRF model of FMCOMMS2, please refer to: http://www.mathworks.com/hardware-support/analog-devices-rf-transceivers.html

- Save coefficients to a ftr file: If you will use the designed FIR filter with the IIO Oscilloscope application
^{2)}, you can save the FIR coefficients by clicking “Coefficients to ftr File” button, as shown in the figure below.

You need to have designed both the Transmit and Receive filters before you can use the “Coefficients to ftr File” button. Otherwise, this button is grayed out.

After that, a window will pop up, asking you to specify the name and the location of the ftr file, as shown in the figure below.

If you plan to use the Filter Design Wizard with a zyqn-based platform, there are several options available that will facilitate this process. These options are in the “Target (Zynq Board)” portion of the GUI.

- Connect to the target: In the IP box, you should input the IP address of the target. In Linux system, it can be easily found by the “ifconfig” command. Then, click the “Connect to Target” button.

- Read clock settings: If a target is detected at the specified IP address, the “Read Clock Settings” button will show up, as shown in the picture below. If you want to overwrite the current clock settings with the ones belong to the target, you can click this button.

- Save FIR coefficients to the target: If an FIR filter is designed for the target, the FIR coefficients can be saved directly to the target by clicking the “Coefficients to Target” button, as shown in the picture below.

The functions introduced so far provide a basic infrastructure to design and observe the FIR filter. If you would like to have more control and functionality, you can turn on the “Advanced” option, as shown in the figure below, which provides you with several more advanced options.

- Phase Equalization: If you would like to have the FIR filter do phase equalization, you can turn on the “Phase Equalization” option, as shown in the figure below. The main purpose of the phase equalization is to reduce the pass band group delay *variance* brought by analog filters, digital filters and FIR filter, so that for signals at different frequencies, they will be delayed by an almost identical amount when going through the filter chain.

After you click “Design Filter”, the phase equalization part of the FIR design file is executed, and you will get an updated FIR filter design. Comparing the group delay variance in the Results portion, it is decreased from 16.6 ns to 1.52 ns with phase equalization. Also note that when the design process completes, there is an updated target delay number (this number is 0 before phase equalization) shown in the “Filter Options” portion.

Please note the phase equalization process may take a few minutes, depending on the performance of your PC, since it tries to find a best target delay which yields the minimum group delay variance.

- Astop (FIR): This is a new parameter in magnitude specifications. It specifies the attenuation of FIR (not the composite response), which corresponds to the “dBstop_FIR” input in the design file. This parameter is not needed most of the time, so you can leave it as 0. However, if you do want to play around with it, you can enter a number there. For more information about “dBstop_FIR”, please refer to “Some Notes About dBstop_FIR” at the end of this page.

- Fcutoff (Analog): This is a new parameter in frequency specifications. It specifies the cutoff frequency of the analog Butterworth filters. By default, this parameter is calculated for you by the App according to the Fpass and Fstop you entered, so you can leave it as it is. However, if you do want to play around with it, you can enter a number there.

- Use Internal FIR: Due to the constraint on power consumption, some users may not want to use the FIR filter on AD936x. Instead, they want to move the FIR filter implementation on FPGA or some other processors. The Filter Design Wizard can also accommodate this requirement. If you decide not to use the AD936x FIR, you can turn off the “Use Internal FIR” option, as shown in the figure below, and click “Design Filter”. In this case, there is no longer any constraint on the number of the FIR taps, so the design file conducts a minimum order design. Comparing the FIR Taps in the Results portion, it is decreased from 128 to 105 if the AD936x FIR is not used.

- Generate HDL: Following the previous step, if you decide to have the FIR filter implemented on FPGA, the design wizard can help you generate the HDL code. By clicking “Generate HDL”, the 'fdhdltool' function (http://www.mathworks.com/help/hdlfilter/fdhdltool.html) is called and the Generate HDL dialog box will pop up, as shown in the figure below. There are quite a few options you can choose concerning how you would like the HDL to be generated. In the end, by clicking “Generate”, the HDL will be generated for you.

The icons shown on the toolbar below provide a shortcut to some frequently used functions.

From left to right, the first four icons are related to filter parameter settings:

- New Filter: It will open the drop-down list for you.
- Open Filter Design Parameters: It will open a saved parameter setting and load it for you.
- Save Parameters to File: It has the similar function as “Coefficients to ftr File” button.
- Save Parameters to Workspace: It has the similar function as “Object to Workspace” button.

The next four icons work on the magnitude response plot shown in the GUI:

- Zoom In: Click the area of the axes where you want to zoom in, or drag the cursor to draw a box around the area you want to zoom in on.
- Zoom Out: Click the area of the axes where you want to zoom out, or drag the cursor to draw a box around the area you want to zoom out on.
- Pan: Interactively pan the view of a plot.
- Data Cursor: Enable the interactive data cursor mode.

In addition to MATLAB App, users can also employ the MATLAB functions to complete the filter design. What they need to do is to launch the MATLAB functions from the MATLAB command window by properly defining the input parameters in a MATLAB structure.

In MATLAB command window, the command is:

output = design_filter(input)

In order to use this method, you need to download the whole ad936x-filter-wizard repository from GitHub. The following are the two most important functions for this method:

Please note this method is suitable for those users who have a clear idea about the parameter settings. For those who are not sure about the parameters, the MATLAB App is a better way to start with.

According to the design requirements, the inputs and outputs of the MATLAB function are as following:

Input structure containing the following fields:

- Rdata = input/output sample data rate (in Hz)
- FIR = FIR interpolation/decimation factor
- PLL_mult = PLL multiplication
- Fpass = passband frequency (in Hz)
- Fstop = stopband frequency (in Hz)
- Apass = max ripple allowed in passband (in dB)
- Astop = min attenuation in stopband (in dB)
- FIRdBmin = min rejection that FIR is required to have (in dB)
- phEQ = Phase Equalization on (not -1)/off (-1)
- int_FIR = Use AD9361 FIR on (1)/off (0)
- wnom = analog cutoff frequency (in Hz)

Output structure containing the following fields:

- firtaps = fixed point FIR coefficients
- filter = system object for visualization (does not include analog filters)
- delay = actual delay used in phase equalization

According to AD9361 Filter Guide, the TX signal path is as following:

The digital and analog paths are separated by DAC. Before DAC, there are four digital filters. The first one (PROG TX FIR) is a programmable poly-phase FIR filter, which can interpolate by a factor of 1, 2, or 4, or it can be bypassed if not needed. The others (HB1, HB2, HB3 and INT3) are all digital filters with fixed coefficients, and they can be turned on or turned off. After DAC, there are two low-pass analog filters.

According to AD9361 Filter Guide, the RX signal path is as following:

The analog and digital paths are separated by ADC. Before ADC, there are two low-pass analog filters. After ADC, there are three digital filters with fixed coefficients (HB3/DEC3, HB2, HB1) followed by a programmable poly-phase FIR filter (PROG RX FIR). The FIR filter can be decimated by a factor of 1, 2, or 4, or it can be bypassed if not needed.

In this section, we present the results for LTE-5 transmit signal path by using the MATLAB function. The input parameters are defined in ad9361_settings.mat included in the repository.

Therefore, in MATLAB command window, the command is:

>> input = ad9361_settings.tx.LTE5; >> output = design_filter(input);

After this command is executed, in MATLAB, you will see the output structure saved in workspace. We can now observe the independent filter, as well as the composite response by specifying the stage of the object. For example,

TFIR = output.Hmd; Hm1 = output.Hm1;

If you are interested in the filter response of HB1, you can proceed to apply the fvtool on HB1,

hfvt1 = fvtool(Hm1,... 'FrequencyRange','Specify freq. vector', ... 'FrequencyVector',linspace(0,122.88e6/2,2048),'Fs',122.88e6,... 'ShowReference','off','Color','White'); set(hfvt1, 'Color', [1 1 1]); set(hfvt1.CurrentAxes, 'YLim', [-100 1]); legend('HB1');

and you will get:

In this section, we will talk about the key steps in AD9361 filter design. Referring to this section, you will have a better understanding of the MATLAB design files. Later on, if you would like to implement your own design algorithm, you can edit the design files to incorporate your changes.

The AD9361 filter design file can be found here:

Based on the structure of Tx and Rx filters, in the design process, we first need to determine which half band digital filters should be included. We then design the programmable FIR filter and get its coefficients. In the end, we complete the design and return the whole filter chain in an object. Since both Tx and Rx designs follow a similar workflow, the following steps take the Tx side for example.

For the analog part, there is a third-order Butterworth low-pass filter and a single-pole low-pass filter on the Tx side. Both of them can be easily defined by the MATLAB function *butter* ^{3)} .

[b1,a1] = butter(3,2*pi*wc,'s'); % 3rd order [b2,a2] = butter(1,2*pi*wreal,'s'); % 1st order

The digital filters with fixed coefficients can be easily defined by referring to the AD9361 filter guide. Since they are interpolation filters on transmit path, the coefficients declaration is followed by the mfilt.firinterp ^{4)} function.

Take HB1 for example, the full-scale range for this filter is 2^13, and it has an interpolation factor of 2, so its coefficients are scaled by 2^(-14).

% Define the filters with fixed coefficients hb1 = 2^(-14)*[-53 0 313 0 -1155 0 4989 8192 4989 0 -1155 0 313 0 -53]; Hm1 = mfilt.firinterp(2,hb1);

If your MATLAB license includes Fixed-Point Designer, the Hm1 object can be further defined in a fixed point format, which is a better representation of the real hardware:

set(Hm1,'arithmetic','fixed'); Hm1.InputWordLength = 16; Hm1.InputFracLength = 14; Hm1.FilterInternals = 'SpecifyPrecision'; Hm1.OutputWordLength = 16; Hm1.OutputFracLength = 14; Hm1.CoeffWordLength = 16;

Since there are 4 digital half-band filters on the TX signal path, there are a finite number of interpolations they can provide. Therefore, the digital half-band filters are picked up according to the overall interpolation factor required by the user.

Ideally, when the whole filter chain is completed, it will have flat response of magnitude 1 on passband, and magnitude 0 on stopband, call it . Since in the previous step, we have already picked up the digital filters, we can get the filter response without TFIR, call it . Therefore, the required response of TFIR is:

.

On passband, the required response *rg* and the weight *w* is:

rg1 = freqz(Filter1,omega,Fdac).*analogresp('Tx',omega,Fdac,b1,a1,b2,a2); rg2 = exp(-1i*2*pi*omega*delay); rg = rg2./rg1; w = abs(rg1)/(dBinv(dBripple/2)-1);

For the analog filters, unlike digital filter, there is no “cascade” function to combine them and quickly calculate the composite response, so we made a helper function *analogresp* to calculate the overall response of the two analog filters and the converter. On the Tx side, the DAC is represented by a *sinc* function. While on the Rx side, the ADC is represented by a *sinc*^3 function.

https://github.com/analogdevicesinc/ad936x-filter-wizard/blob/master/analogresp.m

On stopband, the required response *rg* = 0 and the weight *w* is:

wg1 = abs(freqz(Filter1,omega(Gpass+2:end),Fdac).*analogresp('Tx',omega(Gpass+2:end),Fdac,b1,a1,b2,a2)); wg2 = (sqrt(FIR_interp)*wg1)/(dBinv(-dBstop)); wg3 = dBinv(dBstop_FIR); wg = max(wg2,wg3);

One other constraint about TFIR is the number of filter taps. In the AD9361 Filter Guide, it says “the number of taps is configurable between a minimum of 16 taps and a maximum of 128 taps in groups of 16”. Therefore, the following piece of code calculates the tap number *N*:

% Determine the number of taps for TFIR switch tfirint case 1 Nmax = 64; case 2 Nmax = 128; case 4 Nmax = 128; end N = min(16*floor((Fdac*DAC_mult)/(2*Fin)),Nmax);

Given the tap number *N*, the required response on passband and stopband (*A1* and *A2*), as well as the corresponding weights (*W1* and *W2*), we can now use fdesign.arbmag ^{5)}
function to design the TFIR filter. In the following piece, *B*=2, which means there are two bands in the design.

d = fdesign.arbmag('N,B,F,A',N-1,B,F1,A1,F2,A2); Hd = design(d,'equiripple','B1Weights',W1,'B2Weights',W2,'SystemObject',true);

The design of the TFIR is saved in the system object *Hmd*. In order to get the 16-bit filter coefficients, the following line is used:

tfirtaps = Hmd.Numerator.*(2^16);

fvtool opens FVTool and displays the magnitude response of the digital filter defined with the system object. Using FVTool you can display the phase response, group delay, impulse response, step response, pole-zero plot, and coefficients of the filter.

For example, the following piece of code use fvtool to display the TFIR filter we just designed in the previous step. *Hmd* is the corresponding system object:

fvtool(... Hmd,... 'FrequencyRange','Specify freq. vector', ... 'FrequencyVector',linspace(0,clkTFIR/2,2048),'Fs',... clkTFIR, ... 'ShowReference','off','Color','White','Legend','off');

The “dBstop_FIR” variable insures a ceiling where no matter how much rejection comes from external filters, the FIR filter is required to have a minimum rejection. To understand the reason for this, imagine that at some frequency we need 60dB of rejection and we have an external filter that gives us 75dB of rejection. If the FIR filter gave us 15dB of gain at that frequency, we would be meet the frequency response. However having gain in a stop band would cause the filter to resonate strongly at that frequency which would result in time domain problems such as very large coefficients and over-ranging of signals at that frequency. “dBstop_FIR” limits this concern.

Picking up a proper dBstop_FIR value is a very important step in designing the filter. Since it determines the weight values on stopband, different dBstop_FIR will result in very different filter responses. It is suggested to try different dBstop_FIR values and observe the time-domain coefficients (it is desired to have smooth coefficients) and frequency-domain responses (passband ripple & stopband attenuation) until you pick up a the one which shows the best combination of everything.

Generally speaking, dBstop_FIR plays a more important role in narrow bandwidth filter design than in wide bandwidth filter design. It can even be omitted when designing a filter with wide bandwidth.

For users wishing to deploy or embed this filter designer into their own C/C++ source two options are available. First a GPL licensed C version of the `internal_filter_designer`

function is provided as part of the lib9361-iio library. This includes all the necessary source to generate filter coefficients. Alternatively, if you do not want to utilize a GPL license with your eventual code or product the MATLAB source, which has been enhanced for code generation, can be found in the codegen-support branch of ad936x-filter-wizard. A secondary designer function called `internal_filter_designer_cg`

which supports code generation is provided in this branch. This function will only provide filter coefficients and will numerically match outputs of the non-codegen designer except when the AD9361 FIR is turned off. However, this mode is still supported and will generate filters with flat magnitude responses.

Required MathWorks Toolboxes and Products:

The MATLAB source with code generation support is provided in the codegen-support branch of ad936x-filter-wizard, which can be found here:

To make sure the code is functional and your local compilers are set up correctly, automated testing has been added to this repository. To run these tests first make sure you are in the root of the downloaded codegen-support branch and run:

addpath(genpath('test')); runTests

If you have MATLAB configured correctly you should observe the following output after the tests complete:

Name Passed Failed Incomplete Duration Details _____________________________________________ ______ ______ __________ ________ ____________ 'FilterDesignerTests/testRXMEX' true false false 160.63 [1x1 struct] 'FilterDesignerTests/testRXEQMEX' true false false 14.787 [1x1 struct] 'FilterDesignerTests/testRXNonStandardFIRMEX' true false false 18.38 [1x1 struct] 'FilterDesignerTests/testRXDLL' true false false 15.854 [1x1 struct] 'FilterDesignerTests/testRXNonStandardFIRDLL' true false false 33.401 [1x1 struct] 'FilterDesignerTests/testTXMEX' true false false 7.6336 [1x1 struct] 'FilterDesignerTests/testTXEQMEX' true false false 8.5157 [1x1 struct] 'FilterDesignerTests/testTXNonStandardFIRMEX' true false false 16.03 [1x1 struct] 'FilterDesignerTests/testTXDLL' true false false 8.2624 [1x1 struct] 'FilterDesignerTests/testTXNonStandardFIRDLL' true false false 26.961 [1x1 struct]

These tests evaluate the numerical output of the generated designer with respect to the original designer. Usage of these tests can be helpful if modification of the MATLAB source is required. Note that it is possible to generate C/C++ source without the necessary compilers, but the generate code cannot be tested with the provided infrastructure.

For convenience, an example script called `generate_internal_designer.m`

is provided in the repository to generate C/C++ source and optionally compile a DLL. This script utilizes the `coder.Config`

class to configure MATLAB Coder. For additional configuration options not used in the script consult coder.CodeConfig. MATLAB Coder can be used to generate source code only, compile the generate code into a library, or compile external code with the newly generated code.

In the provided script, which we show a portion of here, a `coder.config`

object is created and configured to generate C code with the majority of the code output to a single file. We disable binary compilation and turn off OpenMP calls which are enabled by default. Since MATLAB Coder requires knowledge of input types they are provided as the `args`

input to the codegen call.

cfg = coder.config('dll'); cfg.TargetLang = 'C'; cfg.FilePartitionMethod = 'SingleFile'; cfg.GenCodeOnly = true; outputLIBName = 'libinternal_filter_designer'; result = codegen('-config','cfg',functionName,'-O ','disable:openmp','-args', args,'-o',outputLIBName);

Once this script completes the generated code will be available in the directory `codegen\dll\internal_filter_designer`

. Example code is provided in the `cpp`

folder of the root directory to demonstrate calls to the generated code. Due to MATLAB Coder styling before directly using the desired function a call to the initializer and terminator functions may be required as:

// Initialize the application. internal_design_filter_cg_initialize(); // Invoke the entry-point functions. main_internal_design_filter_cg(); // Terminate the application. internal_design_filter_cg_terminate();

If you have any questions about these scripts/tools, please ask on the EngineerZone. Help & Support.

/srv/wiki.analog.com/data/pages/resources/eval/user-guides/ad-fmcomms2-ebz/software/filters.txt · Last modified: 23 Mar 2017 13:39 by tfcollins