La presente investigación surge de la necesidad de modernizar y optimizar la plataforma Open-CoRoCo mediante el desarrollo de su versión 2.0, incorporando tecnologías de procesamiento embebido basadas en FPGA. El eje central del proyecto es la implementación del algoritmo de Control Directo por Torque (DTC), una técnica avanzada de control de motores que permite gestionar de manera precisa y rápida tanto el par electromagnético como el flujo magnético del estator.
Para ello, se ha diseñado un sistema en el que una FPGA iCE40-HX8K actúa como unidad principal de cálculo, comunicándose directamente con una placa STM32F407VG-Discovery, la cual cumple funciones de adquisición de señales y control del actuador. En este esquema, el microcontrolador captura las señales instantáneas de corriente ( y ) y voltaje de bus ( ) provenientes de un motor síncrono de imanes permanentes (PMSM), y las transmite hacia la FPGA para su procesamiento. A partir de esta información, se calculan en hardware el vector de flujo magnético del estator () y el torque electromagnético (), con el fin de generar los vectores de control del inversor de fuente de voltaje (VSI)
Finalmente, los resultados obtenidos contribuirán al desarrollo de un sistema de control de impedancia destinado al robot humanoide del Laboratorio de Investigación en Robots Autónomos y Sistemas Cognitivos (ARCOS-Lab) de la Universidad de Costa Rica, fortaleciendo así la base tecnológica de plataformas robóticas nacionales.
El objetivo principal del DTC es estimar valores instantáneos de par y flujo magnético, basándose en la corriente y el voltaje del motor. Los vectores de par y flujo se controlan de forma directa e independiente,
seleccionando el vector de voltaje del inversor adecuado que mantenga los errores de par y flujo dentro de los límites del comparador de histéresis.
Para estimar los valores de par y flujo del motor, se obtienen las señales instantáneas de corriente () y voltaje del bus de corriente continua () del motor de inducción de corriente alterna (ACIM). Estas señales analógicas se convierten en valores digitales mediante un convertidor analógico a digital (ADC). Las señales de corriente y voltaje, así como el estado actual del vector del inversor de fuente de voltaje (VSI) , se transforman de un sistema de referencia trifásico a uno bifásico . La idea final pretende calcular los valores de flujo electromagnético y torque electromagnético para para re calcular los nuevos valores del vector del inversor .
![dtc_bdiagram.png).[@square_root]](/teaching/proyectos/2025i/fpga_stm32_verilog/dtc_bdiagram.png)
Los primeros dos bloques del diagrama son el Conversor al plano () y el Estimador T/F. Ambos bloque incluyen los siguientes cálculos 8, 7: Las corrientes en ():
Las tensiones en () se obtienen como:
Componentes de flujo magnético para y :
Donde:
-- Periodo de muestreo [segundos]
-- Resistencia del estator [ohmios]
-- Valor del componente de flujo anterior o inicial [Wb]
La magnitud del flujo magnético del estator es entonces:
El torque electromagnético vendría como:
Para poder reorientar el vector de flujo , primero es necesario determinar su posición. Para ello, la trayectoria circular del vector de flujo se divide en seis sectores simétricos. La posición de este ayudará luego a determinar el vector de pulso de voltaje () para el vector del inversor de fuente de voltaje (VSI).

El ángulo se puede calcular a partir de las componentes de flujo () de la siguiente manera:
Sin embargo, implementar la ecuación en una FPGA es complejo y requiere mucho tiempo. En cambio, es posible determinar el sector donde se ubica el vector de flujo a partir de los signos de las componentes de flujo en
la siguiente tabla:
![Sector del vector espacial de flujo del estator[@Tahar_Lamchich]](/teaching/proyectos/2025i/fpga_stm32_verilog/space_vectors_sector.png)
Por otro lado, los valores estimados de flujo magnético y par
electromagnético se comparan con sus respectivos valores de referencia.
A partir de esta comparación, se obtienen los errores de flujo
() y de par (), los cuales se envían a controladores por
histéresis. El controlador de histéresis utilizado para el flujo es de
dos niveles, mientras que el del par es de tres niveles. Medir estas
configuraciones de los errores permiten mantener dentro de rangos de
operación al motor según el valor de referencia por medio de los valores
discretos que escogen vector de pulso de voltaje.
Valores discretos del flujo 2:
Se necesita un decremento si:
Se necesita un incremento si:
Valores discretos para el torque 2:
Se necesita un incremento si.
Se necesita un decremento si.
Se mantiene igual si:
Las variables digitalizadas del flujo (), el par () y el sector en el que se encuentra el flujo del estator determinan qué vector de tensión debe aplicarse, según la tabla de conmutación del inversor.
![Tabla de switches óptima del vector de pulso de voltaje[@Tahar_Lamchich]](/teaching/proyectos/2025i/fpga_stm32_verilog/switching_tables.png)
De esta manera, la tabla de selección genera las señales de control del vector de pulso de voltaje () que activan los interruptores de potencia del inversor, permitiendo generar seis vectores activos posibles. Incrementando el vector de flujo con (v2, v3 o v4), decrementándolo con (v1, v5 o v6) y dos vectores nulos constantes con (v0 y v7).
La idea estos valores que se vean aplicados a un puente inversor trifásico que se ve de la siguiente forma:
![Puente inversor trifásico.[@img_puente]](/teaching/proyectos/2025i/fpga_stm32_verilog/puente_inversor_trifasico.png)
Para la implementación del Control de Impedancia es necesario la implementación de un controlador de Torque. El Control por Impedancia se rige por la siguiente ecuación:

Las entradas y salidas se definen bajo el siguiente esquema:


Los pines de DATAx son instanciados como bidireccionales para el envío y recibimiento de datos entre ambos dispositivos. Por otro lado, se usan pines del SPI2 para aumentar las variables de control (CTRLx) y de selección (SELx). En el código se definen como:
module Direct_torque_control (
input ice_CLK,
output LED2, output LED3, output LED4, output LED5,
output LED6, output LED7, output LED8, output LED9,
inout wire DATA0, // pin L1
inout wire DATA1, // pin J3
inout wire DATA2, // pin L2
inout wire DATA3, // pin K3
inout wire DATA4, // pin J4
inout wire DATA5, // pin L3
inout wire DATA6, // pin L4
inout wire DATA7, // pin K4
input wire CTRL0, // Pin J5 CTRL0 :: STM32 w = 1, w = 0.
input wire CTRL1, // Pin K5 CTRL1 :: STM32 data ready to read or write = 1, not = 0.
input wire CTRL2, // Pin J7 CTRL2 :: FPGA w = 0, w = 1.
output reg CTRL3, // Pin E11 CTRL3 (SPI2_SCK) :: FPGA data ready to be read = 1, not = 0.
input wire CTRL4, // Pin D11 CTRL4 (SPI2_MISO) :: pin numero negativo.
// pin numero negativo: supone que algun valor superara el valor 128 y no distingue entre
// numero negativo o muy grande para 8 bits de datos.
// Selector de variables externas
input wire SEL0, // pin H7 SEL0
input wire SEL1, // pin K7 SEL1
input wire SEL2, // pin J8 SEL2
input wire SEL3 // pin D9 SPI2_CS
);
El siguiente bloque de código muestra el uso de un bloque simple de buffers triestado para la simulación. Este se activa cuando las señales de CTRL3; que indica que ya hay datos listos desde la FPGA y CTRL1; que indica que el STM32 está listo para leer. De otra forma los mantiene en alta impedancia.
// Control de pines para la salida (fpga_d_rd = 1 & stm_d_rd = 1) SIMULACION
assign DATA0 = (CTRL3&CTRL1) ? data_out[0] : 1'bz;
assign DATA1 = (CTRL3&CTRL1) ? data_out[1] : 1'bz;
assign DATA2 = (CTRL3&CTRL1) ? data_out[2] : 1'bz;
assign DATA3 = (CTRL3&CTRL1) ? data_out[3] : 1'bz;
assign DATA4 = (CTRL3&CTRL1) ? data_out[4] : 1'bz;
assign DATA5 = (CTRL3&CTRL1) ? data_out[5] : 1'bz;
assign DATA6 = (CTRL3&CTRL1) ? data_out[6] : 1'bz;
assign DATA7 = (CTRL3&CTRL1) ? data_out[7] : 1'bz;
Para la síntesis se utiliza la instanciación del buffer triestado con el bloque primitivo SB_IO que se ve de la siguiente forma:
//Bloque buffer triestado para los pines DATAx. SINTESIS
SB_IO #(
.PIN_TYPE(6'b101001),
.PULLUP(1'b0)
) io_data0 (.PACKAGE_PIN(DATA0), .D_OUT_0(dataout_0), .D_IN_0(datain_0), .OUTPUT_ENABLE(oe));
SB_IO #(
.PIN_TYPE(6'b101001),
.PULLUP(1'b0)
... ...
El estado inicial o IDLE permite la inicialización de las constantes internas y permite el arranque a lectura de datos activando las señales CTRL0 y desactivando CTRL2.
Estado_inicial: begin
var_cntr = 0;
if (CTRL0 && !CTRL2) begin //STM32 w = 1 | FPGA w = 0
e_siguiente = Estado_lectura;
end else begin
end
end
En el estado de lectura se define el var_select = {SEL3, SEL2, SEL1, SEL0}; que indica cuál variable y qué parte de esta se va a enviar, ya sea parte entera o parte decimal. Por otro lado, sólo se leen los datos cuando se cumple que la señal de control CTRL1 = 1, que indica que el STM32 está listo para escribir.
Estado_lectura: begin
var_select = {SEL3, SEL2, SEL1, SEL0};
//CTRL0 == 1 && CTRL1 == 1 && CTRL2 == 0
if(((CTRL0 && CTRL1) && ~(CTRL2))) begin //STM32 w = 1 && data_ready = 1 && FPGA w = 0
case (var_select)
4'b0000: begin i_a[15:8] = {datain_7, datain_6, datain_5, datain_4, data_in_3, datain_2, datain_1, datain_0}; var_cntr[0] = 1; if(CTRL4 == 1) var_sgned[0] = 1'b1; else begin end end // parte entera i_a
4'b0001: begin i_a[7:0] = {datain_7, datain_6, datain_5, datain_4, data_in_3, datain_2, datain_1, datain_0}; var_cntr[1] = 1; end// parte decimal i_a
4'b0010: begin i_b[15:8] = {datain_7, datain_6, datain_5, datain_4, data_in_3, datain_2, datain_1, datain_0}; var_cntr[2] = 1; if(CTRL4 == 1) var_sgned[1] = 1'b1; else begin end end// parte entera i_b
4'b0011: begin i_b[7:0] = {datain_7, datain_6, datain_5, datain_4, data_in_3, datain_2, datain_1, datain_0}; var_cntr[3] = 1; end// parte decimal i_b
... ... ...
if (var_cntr == 10'h3FF && !CTRL1) begin // data_ready = 0
e_siguiente = E_Estimador_FyT;
end else begin
end
Finalmente la variable var_cntr indica el conteo de variables necesarias para el cálculo, que una vez estén todas, inicia el proceso de cálculo.
En el estado de salida se trabaja de la misma manera que en el de lectura, solo que ahora las señales de control deben cambiar a CTRL0 = 0, CTRL1 = 1, CTRL2 = 1 para leer los datos cuando la FPGA indica que hay datos nuevos que es con la señal CTRL3 = 1.
Estado_salida: begin
CTRL3 = 1; // avisa cuando hay datos listos de salida
var_select = {SEL3, SEL2, SEL1, SEL0};
//CTRL0 == 0 && CTRL1 == 1 && CTRL2 == 1
if((~(CTRL0) && CTRL1 && CTRL2)) begin //STM32 w = 0 && data_w/ry = 1 && FPGA w = 1
case (var_select)
4'b0000: begin data_out[2:0] = vector_S; var_cntr[0] = 1;end
4'b0001: begin data_out[7:0] = i_beta [15:8]; var_cntr[1] = 1;end
4'b0010: begin data_out[7:0] = i_beta [7:0]; var_cntr[2] = 1;end
4'b0011: begin data_out[7:0] = i_alpha [7:0]; var_cntr[3] = 1;end
4'b0100: begin data_out[7:0] = i_alpha [15:8]; var_cntr[4] = 1;end
... ... ...
if (var_cntr == 12'h207 && !CTRL1) begin
e_siguiente = Estado_inicial;
end else begin
end
Para el cálculo de la magnitud del flujo magnético del estator es necesario realizar la operación de raíz cuadrada. El problema es que para un lenguaje de descripción de hardware como los es Verilog, no existe soporte para poder realizar este cálculo. Es por esto que para la ecuación [7] es necesario implementar el algoritmo de cálculo de raíz cuadrada de Rafael R. et al. 7. Para realizar el cálculo la raíz cuadrada se parte de la ecuación de la raíz como se muestra en la siguiente ecuación:
Si se elevan al cuadrado ambos lados de la ecuación, resulta lo siguiente:
Ahora, se pasa la variable al lado izquierdo de la ecuación resultando:
Para calcular la raíz cuadrada de se asigna un número a la variable el cual va cambiando un bit en cada iteración hasta que el resultado de la resta es muy cercano a cero; el valor final de será el resultado de la raíz cuadrada. El siguiente diagrama muestra el paso a paso a seguir para obtener el resultado correcto.
![Diagrama de flujo para el calculo de la raíz cuadrada. [@square_root]](/teaching/proyectos/2025i/fpga_stm32_verilog/fsqroot.png)
Para poder funcionar el programa se deben instalar algunas librerías importantes como lo son yosys, arachne-pnr y fpga-icestorm.
sudo apt-get install yosys
sudo apt-get install arachne-pnr
sudo apt-get install fpga-icestorm
Utilizamos la herramienta de Yosys, que lo que hace es cambiar automáticamente la versión del código conductual a la versión estructural (con compuertas) especificando el módulo top. Se corre el yosys para generar el .blif:
yosys -p "synth_ice40 -blif Direct_torque_control32bits.blif"
Archivo fuente: Direct_torque_control32bits.v
Se realiza el place & route con el archivo .blif, es decir, genera ya la descripción interna del hardware de las pistas y registros a utilizar de la FPGA que va usar nuestro programa. Esta descripción genera un .txt:
arachne-pnr -d 8k -P ct256 -p restriction.pcf Direct_torque_control32bits.blif -o Direct_torque_control32bits.txt
A partir del .txt se genera el binario que se va a "flashear" a la FPGA:
icepack Direct_torque_control32bits.txt Direct_torque_control32bits.bin
iceprog Direct_torque_control32bits.bin
Utilizar el Makefile:
make compile # genera hasta el .bin
make flash # flashea a la FPGA
libopencm3-plus-examples/examples/stm32/f4/stm32f4discovery/DTC\_demo
Los pasos para descargar la librería y ponerla a funcionar son:
git clone git@gitlab.com:arcoslab/libopencm3-plus-examples.git
cd libopencm3-plus-examples
git submodule init
git submodule update
//Para inicializarla
cd libopencm3
make -j`nproc`
cd ..
cd libopencm3-plus
make -j`nproc`
cd ..
make -jnproc y para flashearlo make V=1 flash.sudo minicom -s. Para ver un tutorial de como configurar el minicom ver libopencm3.



Primeramente se puede observar como en los estados de entrada E_lectura y salida E_salida se utilizan las señales de CTRLx y SELx para leer los valores y enviarlos por medio de la señal data_bus_in que representa los pines de entrada y salida de DATAx.
La interpretación de estos valores, como se mencionó anteriormente es pasar la representación hexadecimal a decimal y dividir por o 256. Esto dará los resultados correctos. Por ejemplo en E_resultados las señal Phi_s = 00E2, esto en decimal es 226 que al dividir por o 256 da como resultado 0.882812 y así con todos los valores de resultados de ecuaciones.
Se puede observar que el retardo máximo suponiendo que el algoritmo de raíz cuadrada agota todos los bits para verificar el valor correcto es de alrededor de 500 ns.

Los valores que se imprimen como "Valor Hexadecimal" son los valores que se deberían de estar enviando directamente al FPGA. Esto como forma para verificar que la traducción de decimal a hexadecimal con punto flotante Q8.8 se está haciendo de manera correcta. Esto recordando que los pines de datos son solo 8, haciendo que las palabras se tengan que dividir por la mitad en parte entera y decimal.
La correcta selección y validación teórica de los algoritmos permitió una implementación
funcional y optimizada en la FPGA iCE40-HX8K. El acuerdo con el profesor tutor sobre los
algoritmos a implementar, seguido de un estudio detallado de DTC en y control por
histéresis, permitió establecer una base sólida para su codificación en Verilog, garantizando
la fidelidad respecto a la teoría de control.
Se logró una implementación efectiva del sistema de control DTC, incluyendo los cálculos
de variables clave como , y las componentes y , validada mediante
herramientas como GTKWave. La simulación de las señales permitió verificar los resultados esperados,
y se cumplió así uno de los principales objetivos técnicos del proyecto.
Las optimizaciones realizadas fueron claves para adaptar el diseño a las limitaciones de
recursos de la FPGA, sin comprometer la precisión crítica del cálculo en variables
fundamentales. Se logró reducir el uso de logic cells (LCs) mediante técnicas como el cambio
de Q16.16 a Q8.8, simplificación de condiciones y reducción de tamaño de registros, cumpliendo
así los requerimientos de síntesis del dispositivo.
Por otro lado, no se logran realizar las pruebas fisicas entre ambos dispositivos. Aunque
si se desarrollon ambas aplicaciones, algoritmo de DTC para la FPGA y tester para el STM32.
Un fallo en el diseño de la plataforma OpenCorocoV2 respecto al mapeo de pines y sus
paquetes para diferentes FPGAs evitó que el programa funcione de forma exitosa en la FPGA
y por tanto no se pudiese comunicar con el STM32.
Incorporar pruebas automáticas o bancos de prueba (testbenches) más extensos para cubrir casos límite del algoritmo DTC. Aunque se usaron señales de prueba en GTKWave, ampliar la cobertura de simulación mejoraría la robustez de la implementación.
Considerar el uso de herramientas de síntesis más avanzadas o FPGAs de mayor capacidad si se desean implementar versiones extendidas del algoritmo o integrar múltiples estrategias de control. Esto brindaría mayor flexibilidad sin necesidad de comprometer precisión o estructura de diseño.
[1] Marcelo F. Castoldi y Manoel L. Aguiar. “Simulation of DTC Strategy in VHDL Code for Induction
Motor Control”. En: Universidade de São Paulo / Dpt. of Electrical Eng, São Carlos, Brazil. IEEE ISIE
2006, July 9-12, 2006, Montréal, Québec, Canada (2006).
[2] Chafik Ed-dahmani Hassane Mahmoudi Marouane Elazzaoui. “Direct Torque Control of Permanent Magnet Synchronous Motors in MATLAB/SIMULINK”. En: 2nd International Conference on
Electrical and Information Technologies ICEIT2016 Power Electronic and Control Laboratory,
Department of Electrical Engineering Mohammadia School of Engineers, Mohammed V University,
Rabat, Morocco. (2024).
[3] Kenneth Hernández González. “Análisis de las ventajas y desventajas de diferentes implementaciones del control directo de torque para motores BLAC”. En: ARCOS-Lab Ciudad Universitaria
Rodrigo Facio, Facultad de Ingeniería, Escuela de Ingeniería Eléctrica (2022).
[4] Moulay Tahar Lamchich. “Torque Control”. En: IntechOpen National and University Library in
Zagreb (2019). doi:10.5772/636.
[5] Brian Morera Madriz. “Desarrollo de una articulación por control por impedancia para una plataforma omnidireccional del robot humanoide”. En: ARCOS-Lab Ciudad Universitaria Rodrigo
Facio, Facultad de Ingeniería, Escuela de Ingeniería Eléctrica (2019).
url: https://cloud.arcoslab.org/apps/files/?dir=/General/Literature/Thesis/ARCOS-Lab/2019&fileid=85441.
[6] Rafael Rodríguez, Roberto Augusto Gómez, Juvenal Rodríguez. “Fast Square Root Calculation for
DTC Magnetic Flux Estimator”. En: IEEE LATIN AMERICA TRANSACTIONS, VOL. 12, NO. 2 (2024).
doi:10.5772/636.
[7] Rafael Rodríguez-Ponce et al. “DTC-FPGA Drive for Induction Motors”. En: www.intechopen.com
Published: 18 November (2015). url: https://www.intechopen.com/chapters/48713.
[8] Lattice Semiconductors. “iCE40-HX8K Breakout Board”. En: Lattice Semiconductors (2025).
url: https://www.latticesemi.com/Products/DevelopmentBoardsAndKits/iCE40HX8KBreakoutBoard.aspx.
[9] STMicroelectronics. “STM32F407VG - Arm Cortex-M4 core”. En: STMicroelectronics (2025).
url: https://www.st.com/en/microcontrollers-microprocessors/stm32f407vg.html#documentation.
[10] STMicroelectronics. “STM32F4DISCOVERY - Discovery User Manual”. En: STMicroelectronics (2025).
url: https://www.st.com/en/evaluation-tools/stm32f4discovery.html.
[11] FPGALattice Wiki. “Tool chain for Lattice iCE40 FPGAs”. En: wiki.debian.org (2020).
url: https://wiki.debian.org/FPGA/Lattice.