4. 从dSPACE原型到嵌入式目标处理器的代码移植
任务定义
将dSPACE/Simulink环境中验证通过的控制算法模型,移植为可在嵌入式目标处理器(以STM32F407为例)上独立运行的C代码。核心目标:完成从模型到嵌入式C代码的映射转换,确保控制周期、I/O接口与原型系统一致,并能在目标硬件上编译运行。
移植前准备
原型系统基于dSPACE DS1104配合MATLAB/Simulink搭建,控制对象为一阶惯性环节(模拟被控对象),采样周期T=1ms。原型模型中包含:ADC输入模块(读取模拟电压)、PID控制器(离散化)、DAC输出模块(输出电压)。移植目标为STM32F407VET6开发板,使用其内置ADC1(12位)与DAC1(12位),主频168MHz,定时器TIM6产生1ms中断作为控制节拍。
代码实现
/* main.c - STM32F407 PID控制移植示例 */
#include "stm32f4xx.h"
#include <math.h>
/* 控制参数(与dSPACE原型一致) */
#define KP 2.5f
#define KI 0.8f
#define KD 0.1f
#define TS 0.001f /* 采样周期1ms */
/* 全局变量 */
static float err_prev = 0.0f;
static float integral = 0.0f;
static volatile uint16_t adc_val = 0;
static volatile uint16_t dac_val = 0;
/* 参考输入(由DAC输出模拟给定) */
#define REF_VALUE 2048 /* 12位DAC中间值,对应1.65V */
/* PID计算函数 */
float PID_Calculate(float setpoint, float measurement)
{
float err = setpoint - measurement;
float output;
/* 积分分离:误差过大时停止积分 */
if (fabs(err) < 100.0f) {
integral += KI * err * TS;
}
/* 限幅积分 */
if (integral > 3000.0f) integral = 3000.0f;
if (integral < -3000.0f) integral = -3000.0f;
output = KP * err + integral + KD * (err - err_prev) / TS;
err_prev = err;
/* 输出限幅到12位DAC范围 */
if (output > 4095.0f) output = 4095.0f;
if (output < 0.0f) output = 0.0f;
return output;
}
/* TIM6中断服务函数 - 1ms控制周期 */
void TIM6_DAC_IRQHandler(void)
{
if (TIM6->SR & TIM_SR_UIF) {
TIM6->SR = ~TIM_SR_UIF;
/* 读取ADC值(实际测量值) */
ADC1->CR2 |= ADC_CR2_SWSTART;
while (!(ADC1->SR & ADC_SR_EOC));
adc_val = ADC1->DR & 0x0FFF;
/* 执行PID计算 */
float control = PID_Calculate((float)REF_VALUE, (float)adc_val);
dac_val = (uint16_t)control;
/* 输出到DAC */
DAC->DHR12R1 = dac_val;
}
}
/* 系统初始化 */
void SystemInit(void)
{
/* 时钟配置:168MHz主频 */
RCC->CR |= RCC_CR_HSEON;
while (!(RCC->CR & RCC_CR_HSERDY));
RCC->CFGR |= RCC_CFGR_PLLSRC_HSE;
RCC->CFGR |= RCC_CFGR_PLLM(8) | RCC_CFGR_PLLN(336) |
RCC_CFGR_PLLP(2) | RCC_CFGR_PLLQ(7);
RCC->CR |= RCC_CR_PLLON;
while (!(RCC->CR & RCC_CR_PLLRDY));
RCC->CFGR |= RCC_CFGR_SW_PLL;
while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
/* 使能外设时钟 */
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
RCC->APB1ENR |= RCC_APB1ENR_TIM6EN | RCC_APB1ENR_DACEN;
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
/* GPIO配置:PA0-ADC输入,PA4-DAC输出 */
GPIOA->MODER |= GPIO_MODER_MODER0 | GPIO_MODER_MODER4;
GPIOA->PUPDR |= GPIO_PUPDR_PUPD0_0; /* PA0上拉 */
/* ADC1配置:12位,通道0 */
ADC1->CR2 = ADC_CR2_ADON | ADC_CR2_SWSTART;
ADC1->SQR3 = 0; /* 通道0 */
/* DAC配置:通道1使能 */
DAC->CR |= DAC_CR_EN1;
/* TIM6配置:1ms中断 */
TIM6->PSC = 168 - 1; /* 168MHz/168 = 1MHz */
TIM6->ARR = 1000 - 1; /* 1MHz/1000 = 1kHz */
TIM6->DIER |= TIM_DIER_UIE;
NVIC_EnableIRQ(TIM6_DAC_IRQn);
TIM6->CR1 |= TIM_CR1_CEN;
}
int main(void)
{
SystemInit();
while (1) {
/* 主循环空闲,控制由中断驱动 */
__WFI();
}
}
移植要点说明
上述代码将dSPACE原型中的离散PID控制器直接映射为C语言实现。采样周期由TIM6定时器中断保证1ms精度,与原型系统一致。ADC1通道0读取被控对象反馈电压,DAC1通道1输出控制量。PID参数(KP、KI、KD)与原型模型中的参数完全一致,积分分离和输出限幅逻辑也保持相同策略。
验证方法
编译验证:使用arm-none-eabi-gcc工具链编译,命令如下:
arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 \
-mfloat-abi=hard -DSTM32F407xx -I./Inc \
-c main.c -o main.o
arm-none-eabi-ld -T stm32f407.ld main.o -o control.elf
arm-none-eabi-objcopy -O binary control.elf control.bin
硬件验证:将control.bin通过ST-Link烧录至STM32F407开发板。用信号发生器在PA0引脚输入0~3.3V正弦波(频率1Hz),用示波器观察PA4引脚输出波形。判定标准:输出波形应为输入波形的反相放大(因PID负反馈),且无明显滞后(相位差小于采样周期对应的0.36°)。
判定标准
当示波器显示PA4输出波形与PA0输入波形呈反相关系(相位差180°±0.36°),且幅值满足PID控制规律(稳态时输出稳定在参考值附近),即判定移植成功。若输出波形出现明显失真或振荡,需检查PID参数是否与原型一致、采样周期是否准确为1ms。