Tool/software:
Estoy tratando de implementar un controlador PID en mi Tiva C, al cual le estoy enviando parámetros desde LabVIEW para el control de un sistema; los datos que envío en un frame separado por "/" son modo manual o automático (1 auto, 2 manuales), un valor PWM en porcentaje que escalo a un valor entre 50 y 70 en mi Tiva C, las ganancias kp, ki y kd del controlador PID, el número de posición actual (valor entero de 0 a 99) y por último envío el valor de referencia. El procesamiento del frame desde LabVIEW al Tiva C es el siguiente: El frame inicia con "C" y solo se procesa si inicia con "C", de igual forma el frame termina con "E" y cada uno de los datos enviados son separados por "/", luego se separan los datos tomando en cuenta este caracter y posteriormente se convierten al tipo de dato correspondiente y se almacenan en su respectiva variable. Estoy generando un timeout de 10 ms para sincronizar la comunicación con LabVIEW, lo cual funciona. Cada vez que recibe datos y esta en modo manual o automático, el Tiva C retorna un frame a LabVIEW para mostrar el porcentaje de PWM y el valor medido, el formato es B100M25.67E (el frame inicia con "B", la "M" es el delimitador y termina con "E"). Estoy teniendo un problema en el algoritmo PID, debido a que el valor vel[0] (salida del controlador PID) está siendo sobrescrito por alguna otra variable en modo automático (variable AuMa=1), pero aún no encuentro qué línea está sobrescribiendo el valor, y por lo tanto no puedo controlar mi sistema. Agradecería mucho si alguien me pudiera ayudar a encontrar mi(s) error(es).
Adjunto el código que estoy utilizando en mi TivaC:
#include <stdint.h> // Define tipos de datos enteros de tamaño fijo (uint8_t, uint16_t, etc.)
#include <stdbool.h> // Permite el uso del tipo de dato bool (true/false)
#include <math.h> // Use math operators
#include "inc/hw_gpio.h" // Definiciones de hardware para los puertos GPIO
#include "inc/hw_ints.h" // Definiciones de hardware para las interrupciones
#include "inc/hw_memmap.h" // Mapa de memoria del microcontrolador
#include "inc/hw_types.h" // Definiciones de tipos de datos específicos del hardware
#include "driverlib/gpio.h" // Funciones para controlar los pines GPIO
#include "driverlib/pin_map.h" // Asignación de pines a funciones específicas (UART, PWM, etc.)
#include "driverlib/sysctl.h" // Configuración del sistema (relojes, modos de energía, etc.)
#include "driverlib/timer.h" // Control y configuración de timers
#include "driverlib/uart.h" // Funciones para controlar los periféricos UART
#include "driverlib/debug.h" // Funciones de depuración
#include "driverlib/pwm.h" // Funciones para el control de PWM
#include "driverlib/adc.h" // Definiciones para utilizar el ADC
#include "driverlib/interrupt.h" // Manejo de interrupciones
#include <stdlib.h> // Librería estándar para funciones generales (atoi, atof, etc.)
#include <string.h> // Funciones de manipulación de cadenas de caracteres
#define PWM_FREQUENCY 50 // Frecuencia del PWM
uint32_t ADC0_Value; // Variable para guardar datos leídos del ADC0
volatile uint32_t Sensor_Value; // Variable used to save the measured value
float Sensor_Pos; // Señal medida del sensor de posición
float rang; // Variable de rango de control
uint16_t aang; // Ángulo leído
uint8_t Pos; // Guarda el número de posición actual
uint8_t AuMa; // Almacena modo manual y automático (1 si es auto, 2 si es manual)
float Vel; // Recibe la velocidad en modo manual
uint8_t PWM; // Guarda y muestra el valor de PWM en porcentaje
float err[3]; // Señal de error actual, error anterior y error anterior al anterior
float vel[2]; // Velocidad calculada por el controlador
float a0, a1, a2; // Variables que acompañan al controlador PID
float kp, ki, kd; // Ganancias del controlador PID
float Set_P; // Setpoint recibido
float Adjust; // Palabra de ajuste de velocidad del motor
volátil bool bandera_Timeout = 0; // Bandera que se activa ocurre cuando un timeout
volatile bool dato_Recibido = 0; // Indica que se ha recibido un dato por UART
volatile char char_Recibido; // Recibe cada dato que llega a la trama
char trama_Recibida[80] = {0}; // Almacena temporalmente cada elemento de la trama
char buff_Trama[9][15]; // Espacio para almacenar 8 elementos de máximo 15 caracteres cada uno
uint8_t indice_Trama = 0; // Contador del índice de la trama principal
uint8_t cont_Delimitador = 0; // Contador de los delimitadores de la trama
bool trama_Lista = 0; // Bandera para indicar que los datos fueron procesados y extraídos de la trama
bool inicio_Valido = 0; // Bandera para validar si la trama empieza con 'C'
vacío UART0IntHandler (vacío); // Rutina de interrupción del UART0
void Timer0IntHandler(void); // Rutina de interrupción del TIMER0
char UART0_ReadCharTimeout(void); // Función para leer un carácter con tiempo de espera
void PeriphConfiguration(void); // Función de configuración de perfiéricos
int main(void) {
float Filtro; // Variable para filtrar el ruido de la señal
volatile uint32_t Load; // Palabra de carga para configurar el ancho de pulso
volatile uint32_t PWMClock; // Señal de reloj del PWM
vel[0] = 50.0;
velocidad[1] = 0,0;
errar[0] = 0,0;
errar[1] = 0,0;
errar[2] = 0,0;
SysCtlClockSet(SYSCTL_SYSDIV_5 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN | SYSCTL_XTAL_16MHZ); // Frecuencia del sistema de 40 MHz
Configuración periférica(); // Función de configuración de periféricos
RelojPWM = SysCtlClockGet() / 64; // Obtener la frecuencia del sistema y la divide por 64 para obtener la frecuencia del reloj del PWM (625 kHz)
Load = (PWMClock / PWM_FREQUENCY); // Calcular el valor de carga para el período del PWM en función de la frecuencia deseada
PWMGenConfigure(PWM1_BASE, PWM_GEN_0, PWM_GEN_MODE_DOWN); // Configurar el generador PWM en modo descendente
PWMGenPeriodSet(PWM1_BASE, PWM_GEN_0, Load); // Establecer el período del generador PWM
PWMPulseWidthSet(PWM1_BASE, PWM_OUT_0, 50 * Load / 1000); // Configura el ancho de pulso del PWM en función del valor de ajuste
PWMOutputState(PWM1_BASE, PWM_OUT_0_BIT, 1); // Habilitar la salida de PWM en el canal 0
PWMGenEnable(PWM1_BASE, PWM_GEN_0); // Habilitar el generador de PWM
mientras (1) {
ADCIntClear(ADC0_BASE, 1); // Limpiar la bandera de interrupción del secuenciador 1 del ADC0
ADCProcessorTrigger(ADC0_BASE, 1); // Iniciar el proceso del ADC
while (!ADCIntStatus(ADC0_BASE, 1, 0)) { // Espera que la conversión sea completada
}
ADCSequenceDataGet(ADC0_BASE, 1, &ADC0_Value); // Guardar los datos del ADC0
Sensor_Value = ADC0_Value; // Obtener el valor del ADC0 antes de convertirlo a grados
Sensor_Pos = (float)(Sensor_Value); // Convertir a flotante el valor del ADC0 */
Filtro = 0.05 * Sensor_Pos + (1.0 - 0.05) * Filtro; // Cálculo del filtro media movil exponencial
rang = (0.06937 * Filtro) + 0.3524 - 0.6; // Ecuación característica del sensor (obtención del rango de 0° a 95°)
if(rang <= 0) rang = 0; // Evitar desbordamiento
if(rang >= 95) ring = 95; // Evitar desbordamiento
aang = (uint16_t)(rang * 100); // Ángulo que se envía por UART
datos char = UART0_ReadCharTimeout(); // Espera un caracter con el timeout
if(dato_Recibido && datos != -1) { // Verificar si se recibió un dato válido antes de enviarlo por UART; se asegura de que dato_recibido sea verdadero y que datos no sea -1 (indicador de timeout)
if (AuMa == 1) { // Pregunta si se activó el modo automático
err[0] = Set_P - rang; // Cálculo del error actual
a0 = kp + (ki * 0.01) + (kd / 0.01); // Coeficiente a0, utilizado en la ecuación del control PID
a1 = -(2.0 * kd / 0.01) - kp; // Coeficiente a1, parte de la ecuación en diferencias
a2 = kd / 0.01; // Coeficiente a2, correspondiente al término más antiguo en la ecuación del PID
vel[0] = (a0 * err[0]) + (a1 * err[1]) + (a2 * err[2]) + vel[1]; // Algoritmo de control PID; señal corregida por la acción de control
si(vel[0] > 60.4) vel[0] = 60.4; // Pregunta si el cálculo de la velocidad es mayor al permitido y establece el máximo si es así
if(vel[0] < 56.5) vel[0] = 56.5; // Pregunta si el cálculo de la velocidad es menor al permitido y establece el mínimo si es así
PWMPulseWidthSet(PWM1_BASE, PWM_OUT_0, vel[0] * Carga/1000); // Escribir el valor calculado por el controlador a la salida de PWM
PWM = 5 * ((int)(vel[0]) - 50); // Escalar el valor de la palabra de ajuste a porcentaje de PWM
err[2] = err[1]; // Se hace un corrimiento del error anterior
err[1] = err[0]; // Se hace el corrimiento del error actual al anterior
vel[1] = vel[0]; // Se hace el corrimiento de la salida actual a la anterior
UARTCharPut(UART0_BASE, 'B'); // Enviar una B para indicar el inicio de la trama
UARTCharPut(UART0_BASE, ((PWM / 100) % 10) + '0'); // Obtener y enviar las centenas del porcentaje
UARTCharPut(UART0_BASE, ((PWM / 10) % 10) + '0'); // Obtener y enviar las decenas del porcentaje
UARTCharPut(UART0_BASE, (PWM % 10) + '0'); // Obtener y enviar las unidades del porcentaje
UARTCharPut(UART0_BASE, 'M'); // Enviar una M para separar la trama
UARTCharPut(UART0_BASE, ((aang / 1000) % 10) + '0'); // Obtener y enviar las unidades de millar de la posición angular
UARTCharPut(UART0_BASE, ((aang / 100) % 10) + '0'); // Obtener y enviar las centenas de la posición angular
UARTCharPut(UART0_BASE, '.'); // Enviar un . para separar la parte entera del decimal
UARTCharPut(UART0_BASE, ((aang / 10) % 10) + '0'); // Obtener y enviar las decenas de la posición angular
UARTCharPut(UART0_BASE, (aang % 10) + '0'); // Obtener y enviar las unidades de la posición angular
UARTCharPut(UART0_BASE, 'E'); // Enviar una E para indicar el fin de la trama
UARTCharPut(UART0_BASE, '\r'); // Retorno de carro
UARTCharPut(UART0_BASE, '\n'); // Salto de línea
SysCtlDelay(133333);
}
else if(AuMa == 2) { // Pregunta si se activó el modo manual
Ajustar = ((Vel / 100.0) * 20.0) + 50.0; // Escalar el valor recibido de PWM en porcentaje al valor de la palabra de ajuste
PWMPulseWidthSet(PWM1_BASE, PWM_OUT_0, Ajustar * Load / 1000); // Escribir el valor recibido por UART a la salida de PWM
UARTCharPut(UART0_BASE, 'B'); // Enviar una B para indicar el inicio de la trama
UARTCharPut(UART0_BASE, (((int)Vel / 100) % 10) + '0'); // Obtener y enviar las centenas del porcentaje
UARTCharPut(UART0_BASE, (((int)Vel / 10) % 10) + '0'); // Obtener y enviar las decenas del porcentaje
UARTCharPut(UART0_BASE, ((int)Vel % 10) + '0'); // Obtener y enviar las unidades del porcentaje
UARTCharPut(UART0_BASE, 'M'); // Enviar una M para separar la trama
UARTCharPut(UART0_BASE, ((aang / 1000) % 10) + '0'); // Obtener y enviar las unidades de millar de la posición angular
UARTCharPut(UART0_BASE, ((aang / 100) % 10) + '0'); // Obtener y enviar las centenas de la posición angular
UARTCharPut(UART0_BASE, '.'); // Enviar un . para separar la parte entera del decimal
UARTCharPut(UART0_BASE, ((aang / 10) % 10) + '0'); // Obtener y enviar las decenas de la posición angular
UARTCharPut(UART0_BASE, (aang % 10) + '0'); // Obtener y enviar las unidades de la posición angular
UARTCharPut(UART0_BASE, 'E'); // Enviar una E para indicar el fin de la trama
UARTCharPut(UART0_BASE, '\r'); // Retorno de carro
UARTCharPut(UART0_BASE, '\n'); // Salto de línea
}
dato_Recibido = 0; // Reinicia la bandera de recepción de datos
}
}
}
void UART0IntHandler(void) { // Rutina de interrupción del UART0
uint32_t ui32Estado_Int; // Variable para obtener el estado de la interrupción
ui32Estado_Int = UARTIntStatus(UART0_BASE, 1); // Obtener el estado de la interrupción del UART0
UARTIntClear(UART0_BASE, ui32Estado_Int); // Limpia la interrupción del UART0
if (UARTCharsAvail(UART0_BASE)) { // Pregunta si se recibieron datos
char_Recibido = UARTCharGetNonBlocking(UART0_BASE); // Guarda los datos recibidos
dato_Recibido = 1; // Activa la bandera de datos recibidos
TimerDisable(TIMER0_BASE, TIMER_A); // Desactiva el TIMER0 una vez se recibe el dato
if (char_Recibido == 'C' || inicio_Valido == 1) inicio_Valido = 1; // Pregunta si llegó una C en primer lugar al puerto serial; activar bandera de validación de inicio de trama
if (inicio_Valido) { // La trama es válida solo si inicia con 'C'
switch(char_Recibido) { // Se opera con la variable que recibe los caracteres en el puerto serial
case '/': // Caso 1: cuando el caracter leído es una /
trama_Recibida[indice_Trama] = '\0'; // Agregar un terminador nulo para reconocer el fin de un subtrama
strcpy(buff_Trama[cont_Delimitador], trama_Recibida); // Guardar en uno de los elementos de buff_Trama el valor actual de la trama recibida
cont_Delimitador++; // Incrementar el índice cada vez que se recibe una /
indice_Trama = 0; // El índice de la trama vuelve a 0, lo que significa que separó una subtrama
break;
case 'E': // Caso 2: cuando el personaje leído es una E
indice_Trama = 0; // El índice de la trama vuelve a 0, lo que significa que separó la última subtrama
cont_Delimitador = 0; // El índice del vector bidimensional vuelve a 0, lo que significa que se separó la trama recibida por UART
trama_Lista = 1; // Se activa la bandera que indica que se pueden procesar los datos separados
break;
predeterminado:
trama_Recibida[indice_Trama++] = char_Recibido; // Almacenará los caracteres recibidos en la trama e incrementará el índice para guardar el carácter en una nueva posición
break;
}
if (trama_Lista == 1) { // Verificar el inicio del mensaje y que haya finalizado el envío de la trama
AuMa = atoi(buff_Trama[1]); // Convierte a entero el valor de la subtrama 1; almacena en AuMa (1 si es automático, 2 si es manual)
Vel = atof(buff_Trama[2]); // Convierte a entero el valor de la subtrama 2; se guarda el valor de la velocidad en porcentaje para el modo manual
kp = atof(buff_Trama[3]); // Convierte a flotante el valor de la subtrama 3; ganancia proporcional
ki = atof(buff_Trama[4]); // Convierte a flotante el valor de la subtrama 4; ganancia integral
kd = atof(buff_Trama[5]); // Convierte a flotante el valor de la subtrama 5; ganancia derivada
Pos = atoi(buff_Trama[6]); // Convierte a entero el valor del número de posición actual
Set_P = atof(buff_Trama[7]); // Convierte a flotante el valor de la subtrama 6; valor del punto de ajuste
trama_Lista = 0; // Restablecer bandera para volver a procesar
inicio_Valido = 0; // Restablecer bandera para volver a recibir datos por procesar
}
}
}
}
void Timer0IntHandler(void) { // Rutina de interrupción del TIMER0
TimerIntClear(TIMER0_BASE, TIMER_TIMA_TIMEOUT); // Limpia la interrupción del TIMER0
bandera_Timeout = 1; // Se activa la bandera cuando ocurre el timeout
}
char UART0_ReadCharTimeout(void) { // Función para leer un carácter con tiempo de espera de 10 ms
bandera_Timeout = 0; // Reinicia la bandera del timeout
dato_Recibido = 0; // Reinicia la bandera de recepción de datos
TimerEnable(TIMER0_BASE, TIMER_A); // Habilita el TIMER0
while (!dato_Recibido && !bandera_Timeout); // Espera hasta que se reciba un dato o ocurra un timeout
return char_Recibido; // Devuelve el dato recibido
}
void PeriphConfiguration(void) {
SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0); // Habilitar UART0
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA); // Habilitar el puerto A
GPIOPinConfigure(GPIO_PA0_U0RX); // Configurar PA0 como RX
GPIOPinConfigure(GPIO_PA1_U0TX); // Configurar PA1 como TX
GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1); // Configurar pinos UART
UARTConfigSetExpClk(UART0_BASE, SysCtlClockGet(), 115200, // Velocidad de transferencia de 115200 baudios
(UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE | UART_CONFIG_PAR_NONE)); // Paquetes de 8 bits, un bit de parada, sin paridad
UARTIntRegister(UART0_BASE, UART0IntHandler); // Registra la función de interrupción
IntEnable(INT_UART0); // Habilitar interrupción de UART0
UARTIntEnable(UART0_BASE, UART_INT_RX | UART_INT_RT); // Habilitar interrupciones de recepción y timeout
SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0); // Habilitar el TIMER0
TimerConfigure(TIMER0_BASE, TIMER_CFG_ONE_SHOT); // Configura el temporizador en modo de disparo en solitario
TimerLoadSet(TIMER0_BASE, TIMER_A, (SysCtlClockGet() / 100) - 1); // Configura la carga del temporizador para generar un tiempo de espera de 10 ms
TimerIntRegister(TIMER0_BASE, TIMER_A, Timer0IntHandler); // Registra la función de interrupción del TIMER0
IntEnable(INT_TIMER0A); // Habilitar la interrupción del TIMER0
TimerIntEnable(TIMER0_BASE, TIMER_TIMA_TIMEOUT); // Habilita la interrupción por tiempo de espera
SysCtlPWMClockSet(SYSCTL_PWMDIV_64); // Configurar el reloj del módulo PWM con un divisor de 64
SysCtlPeripheralEnable(SYSCTL_PERIPH_PWM1); // Habilitar el PWM1
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD); // Habilitar el puerto D
GPIOPinTypePWM(GPIO_PORTD_BASE, GPIO_PIN_0); // PD0 como salida tipo PWM
GPIOPinConfigure(GPIO_PD0_M1PWM0); // Asigna la función alternativa de PWM al pin PD0
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOE); // Habilitar el puerto E
GPIOPinTypeADC(GPIO_PORTE_BASE, GPIO_PIN_2); // PE2 como entrada analógica
SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0); // Habilitar el ADC0
ADCSequenceConfigure(ADC0_BASE, 1, ADC_TRIGGER_PROCESSOR, 0); // Configurar el ADC0, el secuenciador 1 y la prioridad más alta
ADCSequenceStepConfigure(ADC0_BASE, 1, 0, ADC_CTL_CH1); // Para el ADC0, configura el paso 0 del secuenciador 1 para leer el canal 1
ADCSequenceStepConfigure(ADC0_BASE, 1, 1, ADC_CTL_CH1); // Para el ADC0, configura el paso 1 del secuenciador 1 para leer el canal 1
ADCSequenceStepConfigure(ADC0_BASE, 1, 2, ADC_CTL_CH1); // Para el ADC0, configura el paso 2 del secuenciador 1 para leer el canal 1
ADCSequenceStepConfigure(ADC0_BASE, 1, 3, ADC_CTL_CH1 | ADC_CTL_IE | ADC_CTL_END); // Para el ADC0, configura el paso 3 del secuenciador 1 para leer el canal 1; habilitar la interrupción y marcar el fin de la secuencia
ADCSequenceEnable(ADC0_BASE, 1); // Habilitar el secuenciador
}