Active drive-train damping in FAST

Hello all,

I’m new at using FAST and so far very pleased with the structure and results.
The turbine modell I want to build is originally modelled in GH Bladed, which leads to a problem with the drive-train damper: in the current modell an active drive-train damper modules the generator torque in order to damp drive-train oscillation. This is done given a transfer function within Bladed and not part of the DLL-type controller.
My guess is, that it is - in principle - possible to implement such an “additional” damping torque controller in FAST, but what would be the most simple and stable solution? Hint, for several reasons I can’t integrate the drive-train damper within the DLL.
Did anyone solve a similar problem?

Kind regards,
Sönke Neumann

Dear Soenke,

Regardless of the control action, there are 5 ways to implement active controls in FAST:

  1. Select from one of the built-in routines (not available for active drive-train damping)

  2. Fortran subroutine:

  • Separate routines for each controller (i.e.: separate routines for blade pitch, generator torque, nacelle yaw, & brake)
  • Requires recompile with each change to controller source code
  • Sample variable-speed torque controller based on table look-up provided with FAST archive
  • Sample PID blade-pitch controller provided with FAST archive
  1. Bladed-style dynamic link library (DLL):
  • DLL compiled separately from FAST (Mixed languages possible – Can be Fortran, C, etc.)
  • DLL is a master controller (i.e.: pitch, torque, yaw, & brake controlled with same DLL)
  • Sample NREL 5-MW baseline controllers provided with FAST archive
  1. MATLAB/Simulink:
  • FAST implemented asS-Function block
  • Controls implemented in block-diagram form
  • SimPowerSystems toolbox for detailed electrical drive
  1. LabVIEW*:
  • FAST implemented asDLL callable byLabVIEW
  • Hardware-in-the-loop (HIL) possible

I’m not sure why you say that you can’t integrate the active drain-train damper within a DLL, but the other options provide the same functionality in a different form.

*Available in FAST v7, but not yet in v8

Best regards,

Dear Jason,

first of all, thanks for the quick answer.

The point is, I have a DLL master controller “as-is”, which structure I can’t change. Thus, I would need somehow two controllers, like 3) + 2), where the DLL controls the generator torque and a secondary function adds a damping modulation to the torque.
The seperation of master controller and drive-train damping controller is rooted in the underlying process.

Kind regards

Dear Soenke,

It sounds like the active drivetrain damper and the master generator torque are independent control loops, whose torque gets summed. The easiest approach, then, is probably to write a DLL that includes (1) the active drivetrain damper, (2) calls the original DLL, and (3) sums the torque from (1) and (2). That is, FAST will call you’re new DLL, which itself will call the original DLL.

Best regards,

Dear Jason,

that would be a solution. I wasn’t sure if this kind of “wrapper” is possible with the structure of the DLL function. But I’ll start with the sample functions and give feedback if it works.

Kind regards,
Sönke

Hi, Sönke.

My only concern about this approach is that the routine in the Bladed DLL must be called DISCON, and FAST is currently hard-coded to read a routine called DISCON. There are ways to get around this, but you should be aware that it may need to be addressed.

Hi all,

here a short C example of a running wrapper. It aim’s to add further (damping) variables to the output of an existing DISCON.dll.

[code]/*

  • compile with: gcc -shared -o DISCON_wrapper.dll DISCON_wrapper.c
    */

#include <stdio.h>
#include <string.h>
#include <windows.h>

/* header */
char DLLpath[MAX_PATH], DLLbase[MAX_PATH]; // full file path and folder
char CtrlPath[MAX_PATH], OutfilePath[MAX_PATH];
char Message[257];

typedef void (*DisconFunc)(float *avrSwap, int *aviFail, char *accInfile, char *avcOutname, char *avcMsg);
DisconFunc _DisconFunc;
HINSTANCE hInstLibrary;

// file I/O
FILE *fp, *ip;

EXTERN_C IMAGE_DOS_HEADER __ImageBase;

/* entry-point function /
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved )
{
BOOL ret=FALSE;
switch(ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
printf(“Using DISCONwrapper…\n”);
/
aquire path of original DISCON.dll */
GetModuleFileName((HINSTANCE)&__ImageBase,DLLpath,sizeof(DLLpath)); // get path of current DLL
char Dir[MAX_PATH]; // temporary path variable
_splitpath( DLLpath, DLLbase, Dir, NULL, NULL );
strcat(DLLbase,Dir);

        // set path of real controller
        strcpy(CtrlPath, DLLbase);
        strcpy(Dir, DLLbase); // generate input file path
        strcat(Dir, "DISCON_wrapper.inp");
        ip=fopen(Dir, "r");     // OR char CtrlName[MAX_PATH];
        fscanf(ip,"%s",Dir);    // read DISCON.dll name as string
        fclose(ip);
        strcat(CtrlPath,Dir);

        // load dll of real controller
        hInstLibrary = LoadLibrary(CtrlPath);

        //Set message to blank

// memset(Message,’ ',257);

        if (hInstLibrary)
        {
            // load real controller
            printf("[DISCONwrapper] Loading controller %s...\n", Dir);
            // get pointer to DISCON function within the DLL
            _DisconFunc = (DisconFunc)GetProcAddress(hInstLibrary, "DISCON");

            // for debugging
    //        strcpy(Message, DLLbase);
            ret = TRUE;
        }
        else
        {
            char msg[MAX_PATH];
            strcpy(msg,"[DISCONwrapper] Loading DLL from path failed: ");
            strcat(msg, CtrlPath );
            strcat(msg, "\n" );
            printf(msg);

// aviFail[0] = -1;
ret = FALSE;
}

        // prepare files
        strcpy(OutfilePath,DLLbase);
        strcat(OutfilePath,"avrSwap.txt");
        fp=fopen(OutfilePath, "w");


        //Return strings

// memcpy(avcMsg,Message,MIN(256,NINT(avrSwap[48])));

        break;

// case DLL_THREAD_ATTACH:
// printf(“DLL_THREAD_ATTACH”);
// break;

    case DLL_PROCESS_DETACH:
        printf("[DISCONwrapper] Finished run.\n");
        FreeLibrary(hInstLibrary);
        fclose(fp);
        break;
}

return ret;
}

//Main DLL routine
void __declspec(dllexport) __cdecl DISCON(float *avrSwap, int *aviFail,
char *accInfile, char *avcOutname, char avcMsg)
{
// get current working dir for DISCON.IN — not needed for FAST
// char
cwd;
// cwd = _getcwd( NULL, 0 );
// // add full path to accInfile
// strcat(cwd,“\DISCON.IN”);
// strcpy(accInfile,cwd);

/* 12:= demanded power
 * 13:= shaft power
 * 14:= electrical output
 * 18:= Demanded generator speed above rated (rad/s)
 * 19:= Measured generator speed (rad/s))
 * 21:= Demanded generator torque (Nm)
 * 22:= Measured generator torque (Nm) (GenTrq_prev)
 *
 * OUTPUTS
 * 46:= Demanded generator torque (Nm)
 */

/* add calculations for drive-train damper here 
 * computedPower = [...]
 */

avrSwap[14] = computedPower;

_DisconFunc(avrSwap, aviFail, accInfile, avcOutname, avcMsg);
return;

}
[/code]

There are some changes of the file pathes, but in principial the original DISCON.dll location is read from the DISON_wrapper.inp input file, loaded at the initiation of the wrapper and then all inputs and outputs are handed over.

Regards, Sönke