• Autor de la entrada:
  • Tiempo de lectura:26 minutos de lectura

Controlar una cadena JTAG mediante con FTDI e interfaz MPSSE hace posible crear aplicaciones a medida. La librería FTCJTAG proporciona la capa de abstracción necesaria.

Librería FTCJTAG de FTDI

FTDI desarrolló librerías para implementar los protocolos SPI, I2C y JTAG mediante la interfaz MPSSE. En su página web se pueden encontrar ejemplos de uso con varios lenguajes de programación, así como el código fuente de las mismas: https://ftdichip.com/software-examples/mpsse-projects/.

Fin de soporte de la librería FTCJTAG
Aviso FTDI sobre fin de soporte de la librería FTCJTAG

Por alguna razón FTDI dejó de dar soporte a la librería que implementa el protocolo JTAG. Lo cual implica que además de no corregir problemas de funcionalidad o seguridad, utilizar dicha librería estará restringido a los circuitos integrados que estaban a la venta en ese momento.

FTCJTAG D2XX Windows CDM Driver Architecture
Arquitectura de aplicación JTAG en Windows

Migrando librería FTCJTAG a Visual Studio 2022

Aprovechando que la licencia con la que FTDI distribuye el código de esta librería permite la modificación y redistribución, se realiza una revisión que es explicada a continuación.

FTDI ofrece el código de las librerías en un proyecto de Visual Studio 2008, el cual está pensado para generar una librería dinámica (.dll), algo apropiado para entornos Windows y su integración con otros lenguajes de programación. El primer objetivo será migrar este proyecto a Visual Studio 2022.

Importar el código fuente de la librería FTCJTAG

Tras descargar el código fuente de la librería FTCJTAG el primer paso será abrir la solución original con Visual Studio 2022. Este paso es automático y no debe originar ningún problema.

Actualizar librería FTD2XX

Cuando la librería FTCJTAG perdió soporte dejó de actualizarse su dependencia principal: la librería FTD2XX. Antes de continuar se debe actualizar tanto la cabecera ftd2xx.h como los archivos .lib asociados. Para ello se acude a la página D2XX Drivers, donde se descargan los archivos correspondientes a la arquitectura sobre la que corra el proyecto. En este caso se descargan los correspondientes a Windows (Desktop)-x64.

Última versión de drivers FTDI
Última versión de drivers FTDI a la edición.

Entre los archivos descargados se encontrará el ftd2xx.h, que debe sustituir al antiguo FTD2XX.H.

Para compilar es necesario el archivo ftd2xx.lib, que se encontrará en los archivos descargados con el ftd2xx.h. Es importante que se elija un .lib apropiado para la máquina de destino, los que se encuentran en las carpetas amd64 no funcionarán en sistemas de 32 bit.

Una vez se tienen los archivos, ir a Propiedades y en Vinculador->Entrada->Dependencias adicionales dar la ruta relativa a los mismos dependiendo de cada configuración.

NOTA: La configuración para x64 se tratará en el futuro.

Actualización de la librería ftd2xx en el linker
Actualización de la librería ftd2xx en el linker

Funciones no seguras: strcpy, sprintf, strupr

El uso de funciones donde se accede a un número indeterminado de direcciones de memoria requiere un cuidado especial. La librería <string.h> provee un gran número de herramientas útiles a la vez que inseguras, al confiar en que las cadenas de caracteres a las que acceden contendrán el carácter nulo ‘\0’ o ‘0x00’ al final de ellas. Para solucionar esto, se hará uso de las versiones seguras que provee Microsoft, a las que ha añadido el sufijo ‘_s’ al nombre y requieren que se indique el número máximo de caracteres a los que debe acceder.

Funciones no seguras en FT2232hMpsseJtag.cpp

En este fichero existen varias funciones con la sintaxis de función segura (sufijo _s) pero implementación erronea, se desconoce cual es la causa de esto, quizá la versión que permite descargar FTDI es el comienzo de una actualización que nunca llegó.

Código original
Código seguro
strcpy(lpDllVersionBuffer, DLL_VERSION_NUM);
strcpy_s(lpDllVersionBuffer, sizeof(DLL_VERSION_NUM), DLL_VERSION_NUM);
strcpy_s(szErrorMsg, EN_Common_Errors[StatusCode]);
strcpy_s(szErrorMsg, MAX_ERROR_MSG_SIZE, EN_Common_Errors[StatusCode]);
strcpy_s(szErrorMsg, EN_New_Errors[(StatusCode - FTC_FAILED_TO_COMPLETE_COMMAND)]);
strcpy_s(szErrorMsg, MAX_ERROR_MSG_SIZE, EN_New_Errors[(StatusCode - FTC_FAILED_TO_COMPLETE_COMMAND)]);
strcpy_s(szErrorMsg, EN_New_Errors[FTC_INVALID_LANGUAGE_CODE - FTC_FAILED_TO_COMPLETE_COMMAND]);
strcpy_s(szErrorMsg, MAX_ERROR_MSG_SIZE, EN_New_Errors[FTC_INVALID_LANGUAGE_CODE - FTC_FAILED_TO_COMPLETE_COMMAND]);
sprintf(szErrorMsg, "%s%d", EN_New_Errors[FTC_INVALID_STATUS_CODE - FTC_FAILED_TO_COMPLETE_COMMAND], StatusCode);
sprintf_s(szErrorMsg, MAX_ERROR_MSG_SIZE, "%s%d", EN_New_Errors[FTC_INVALID_STATUS_CODE - FTC_FAILED_TO_COMPLETE_COMMAND], StatusCode);
strcpy(lpErrorMessageBuffer, szErrorMsg);
strcpy_s(lpErrorMessageBuffer, dwBufferSize, szErrorMsg);

Funciones no seguras en FTC2232c.cpp

Código original
Código seguro
if ((pszStringSearch = strstr(strupr(devInfo.Description), DEVICE_CHANNEL_A)) != NULL)
_strupr_s(devInfo.Description, sizeof(((FT_DEVICE_LIST_INFO_NODE*)0)->Description));
if ((pszStringSearch = strstr(devInfo.Description, DEVICE_CHANNEL_A)) != NULL)
    
strcpy(lpDeviceName, szDeviceNameBuffer);
strcpy_s(lpDeviceName, dwBufferSize, szDeviceNameBuffer);   
strcpy(OpenedDevices[iDeviceCntr].szDeviceName, lpDeviceName);
  strcpy_s(OpenedDevices[iDeviceCntr].szDeviceName, MAX_NUM_DEVICE_NAME_CHARS, lpDeviceName);
strcpy(OpenedDevices[iDeviceCntr].szDeviceName, "");
strcpy_s(OpenedDevices[iDeviceCntr].szDeviceName, MAX_NUM_DEVICE_NAME_CHARS, "");

Funciones no seguras en FTC2232h.cpp

Código original
Código seguro
// Search for the first occurrence of the channel string ie ' A' or ' B'. The Description field contains the device name
if (((pszStringSearch = strstr(strupr(devInfo.Description), DEVICE_NAME_CHANNEL_A)) != NULL) ||
((pszStringSearch = strstr(strupr(devInfo.Description), DEVICE_NAME_CHANNEL_B)) != NULL))
// Search for the first occurrence of the channel string ie ' A' or ' B'. The Description field contains the device name
_strupr_s(devInfo.Description, sizeof(((FT_DEVICE_LIST_INFO_NODE*)0)->Description));
if (((pszStringSearch = strstr(devInfo.Description, DEVICE_NAME_CHANNEL_A)) != NULL) ||
((pszStringSearch = strstr(devInfo.Description, DEVICE_NAME_CHANNEL_B)) != NULL))
    
if (strlen(szDeviceNameBuffer) <= dwDeviceNameBufferSize)
{
 strcpy(lpDeviceName, szDeviceNameBuffer);

// Check for hi-speed device channel A or channel B
 if (((pszStringSearch = strstr(strupr(szDeviceNameBuffer), DEVICE_NAME_CHANNEL_A)) != NULL) || ((pszStringSearch = strstr(strupr(szDeviceNameBuffer), DEVICE_NAME_CHANNEL_B)) != NULL))
 {
  if (dwChannelBufferSize >= CHANNEL_STRING_MIN_BUFF_SIZE)
  {
    if ((pszStringSearch = strstr(strupr(szDeviceNameBuffer), DEVICE_NAME_CHANNEL_A)) != NULL)
      strcpy(lpChannel, CHANNEL_A);
    else
      strcpy(lpChannel, CHANNEL_B);
if (strlen(szDeviceNameBuffer) <= dwDeviceNameBufferSize)
{
 strcpy_s(lpDeviceName, dwDeviceNameBufferSize, szDeviceNameBuffer);

 // Check for hi-speed device channel A or channel B
 _strupr_s(szDeviceNameBuffer, sizeof(szDeviceNameBuffer));
 if (((pszStringSearch = strstr(szDeviceNameBuffer, DEVICE_NAME_CHANNEL_A)) != NULL) || 
((pszStringSearch = strstr(szDeviceNameBuffer, DEVICE_NAME_CHANNEL_B)) != NULL)){
   if (dwChannelBufferSize >= CHANNEL_STRING_MIN_BUFF_SIZE){
       _strupr_s(szDeviceNameBuffer, sizeof(szDeviceNameBuffer));
    if ((pszStringSearch = strstr(szDeviceNameBuffer, DEVICE_NAME_CHANNEL_A)) != NULL)
     strcpy_s(lpChannel, dwChannelBufferSize, CHANNEL_A);
    else
     strcpy_s(lpChannel, dwChannelBufferSize, CHANNEL_B);
if ((strcmp(strupr(lpChannel), CHANNEL_A) == 0) || (strcmp(strupr(lpChannel), CHANNEL_B) == 0))
_strupr_s(lpChannel, sizeof(CHANNEL_A));
if ((strcmp(lpChannel, CHANNEL_A) == 0) || (strcmp(lpChannel, CHANNEL_B) == 0))
    
strcpy(OpenedHiSpeedDevices[iDeviceCntr].szDeviceName, "");
strcpy_s(OpenedHiSpeedDevices[iDeviceCntr].szDeviceName, MAX_NUM_DEVICE_NAME_CHARS, "");

Variables sin inicializar

A lo largo del todo el código existen variables sin inicializar. Las más obvias son el caso de punteros sin inicializar a NULL.

Los string char pueden inicializarse a cero en la declaración de la siguiente forma: char string_name[STRING_SIZE] = {0};.

La menos obvia se encuentra en el constructor FT2232c::FT2232c(void){}. Donde OutputBuffer puede inicializarse mediante la llamada a: memset(OutputBuffer, 0, sizeof(OutputBuffer));.

Firma de la DLL

El proyecto original contempla la firma de la DLL en la configuración de Release. Quien requiera firmar la dll puede sustituir el certificado por el que crea conveniente, en este caso simplemente no se realiza. Para ello borrar la línea en:

Proyecto -> Propiedades -> Eventos de compilación -> Evento posterior a la compilación -> Línea de comandos/Descripción

Aunque en este punto ya puede obtenerse la DLL, es conveniente atender a varias cosas.

Warning C6262. Uso excesivo del stack, mover variables al heap

Varios métodos de la librería FTCJTAG declaran como variables locales buffers de tamaños que llegan a 128kB. El compilador coloca por defecto dichos buffer en el stack, haciendo que el programa pueda colapsar si el tamaño de este se agota, para evitarlo, es recomendable mover estos datos al heap. En este caso existen dos alternativas bastante sencillas:

  • Redefinir el tipo de dato de estos buffer a std::vector, algo muy apropiado en lenguaje C++ y cuya clase facilita métodos que permiten diversas operaciones sobre los datos.
  • Solicitar memoria al sistema operativo mediante las funciones C standard calloc() y malloc().

Si bien el esqueleto de la librería FTCJTAG esta construido en C++, no hace un uso específico del lenguaje en el resto de la librería. Por por coherencia se mantendrá la implementación en C, resolviendo este warning mediante la segunda opción.

En las funciones donde se declara una variable InputByteBuffer InputBuffer; será necesario sustituir dicha declaración por un puntero una variable de ese tipo, solicitar asignación de memoria y posteriormente eliminarla. El código quedaría de la siguiente manera:

PInputByteBuffer pInputBuffer = nullptr;

pInputBuffer = (InputByteBuffer*)malloc(sizeof(InputByteBuffer));
if (pInputBuffer == nullptr)
{
    return FTC_INSUFFICIENT_RESOURCES;
}

/* Uso del buffer según sea necesario */

free(pInputBuffer);
pInputBuffer = nullptr;

Resto de warnings o incidencias en librería FTCJTAG

  • Warning 6385: existen dos bucles que que recorren una lista creada por el driver ftd2xx. El número de iteraciones del bucle depende del resultado que devuelva el driver, pero el compilador piensa que la lista puede llenarse con un número menor de ítems al que dice el driver. Si esto ocurriera sería un error del driver, se da por hecho que el driver nunca se comportará de esta manera y se procede a omitir dichos warning de forma explícita de la siguiente forma:
#pragma warning (disable:6385)
devInfo = pDevInfoList[dwDeviceIndex];
#pragma warning (default:6385)
  • WIO_DEFINED: es declarado en un #define pero no existe macro que la consuma, por ello puede eliminarse.
  • Warning D9035: la opción ‘Gm’ está desusada y se quitará en próximas versiones. Este warning tiene que ver con la administración mínima de dependencias de Visual Studio, esta característica analiza qué partes del código es necesario recompilar y cuando utilizar compilados anteriores, una gestión que ahora se realiza de otra manera. Para desactivar la característica ir a Proyecto -> Propiedades -> C/C++ -> Generación de código -> Habilitar recompilación mínima y dejar en blanco dicha característica.
  • Warning LNK4075: se omite ‘/EDITANDCONTINUE’ debido a la especificación ‘/SAFESEH’. /EDITANDCONTINUE permite editar código en depuración sin tener que recompilar. Por ahora no se realizan tareas de depuración en esta librería, por lo que se desactiva esta opción en Proyecto -> Propiedades -> C/C++ -> General -> Formmato de la información de depuración -> Base de datos de programa (/Zi).
  • Warning C6278: ‘pDevInfoList‘ se eliminará con la eliminación escalar. No se llamará a los destructores. En los métodos FT2232c::FTC_GetNumDevices() y FT2232c::FTC_GetNumNotOpenedDevices() se crean listas para almacenar la información de los dispositivos conectados, listas que son eliminadas manualmente. El compilador nos advierte que esto no es necesario, sin embargo, se sustituirá delete pDevInfoList; por una implementación más apropiada: delete[] pDevInfoList;.
  • switch(…) sin default case en el método FT2232hMpsseJtag::MoveJTAGFromOneStateToAnother(). Basta añadirlo, realmente nunca se ejecutará pero mantendrá los mensajes del compilador en orden.
  switch (CurrentJtagState)
  {
    {(...)}
    default:
    	return 0;
  }
  • Distinto tipo de dato en marcador de formato y variable de sprintf en el método FT2232hMpsseJtag::JTAG_GetErrorCodeString() realizar el siguiente cambio
//Sustituir
sprintf_s(szErrorMsg, MAX_ERROR_MSG_SIZE, "%s%d", EN_New_Errors[FTC_INVALID_STATUS_CODE - FTC_FAILED_TO_COMPLETE_COMMAND], StatusCode);
//sustituir por
sprintf_s(szErrorMsg, MAX_ERROR_MSG_SIZE, "%s%ld", EN_New_Errors[FTC_INVALID_STATUS_CODE - FTC_FAILED_TO_COMPLETE_COMMAND], StatusCode);
  • Variables no utilizadas en los siguientes métodos, simplemente comentar la declaración de las mismas.
    • FT2232hMpsseJtag::GetDataFromExternalDevice()
      • dwNumBytesDeviceInputBuffer
    • FT2232hMpsseJtag::GenerateTCKClockPulses()
      • dwDataBufferIndex
    • FT2232hMpsseJtag::JTAG_AddWriteCommand()
      • dwNumCommandDataBytes
    • FT2232hMpsseJtag:JTAG_AddReadCommand()
      • dwNumTmsClocks
    • FT2232hMpsseJtag:JTAG_AddWriteReadCommand()
      • dwNumCommandDataBytes
      • dwNumTmsClocks

Trabajo futuro

El límite de esta librería siempre debería ser que exista driver y librería D2XX para el sistema donde se ejecute la aplicación. En el futuro se trabajará en los siguientes aspectos:

FTCJTAG by FaultyProject (GitHub)

Tanto el proyecto en Visual Studio 2022 como el código fuente se encuentra en el siguiente repositorio de GitHub.