一、前言:
有关闭环位置控制,详见[STM32+HAL]DengFOC移植之闭环位置控制
二、Keil填写代码:
1、AS5600读取角位移
#include "AS5600.h"
#include "math.h"
#include "FOC2.h"
float angle_prev=0; // 最后一次调用 getSensorAngle() 的输出结果,用于得到完整的圈数和速度
long angle_prev_ts=0; // 上次调用 getAngle 的时间戳
float vel_angle_prev=0; // 最后一次调用 getVelocity 时的角度
long vel_angle_prev_ts=0; // 最后速度计算时间戳
int32_t full_rotations=0; // 总圈数计数
int32_t vel_full_rotations=0; // 用于速度计算的先前完整旋转圈数
/* IIC读多字节 */
void AS5600_Read_Reg(uint16_t reg, uint8_t* buf, uint8_t len)
{
HAL_I2C_Mem_Read(&hi2c1, AS5600_ADDRESS, reg, I2C_MEMADD_SIZE_8BIT, buf, len, 100);
}
/* 得到弧度制的角度,范围在0-6.28 */
float GetAngle_Without_Track(void)
{
float angle_d; // GetAngle_Without_Track()的返回值
int16_t in_angle;
uint8_t temp[DATA_SIZE]={0};
AS5600_Read_Reg( Angle_Hight_Register_Addr, temp, DATA_SIZE);
in_angle = ((int16_t)temp[0] <<8) | (temp[1]);
angle_d = (float)in_angle * (2.0f*PI) / 4096;
//angle_d为弧度制,范围在0-6.28
return angle_d;
}
/* 得到弧度制的带圈数角度 */
float GetAngle(void)
{
float angle_cd; // GetAngle()的返回值
float val = GetAngle_Without_Track();
float d_angle = val - angle_prev;
//计算旋转的总圈数
//通过判断角度变化是否大于80%的一圈(0.8f*6.28318530718f)来判断是否发生了溢出,如果发生了,则将full_rotations增加1(如果d_angle小于0)或减少1(如果d_angle大于0)。
if(fabs(d_angle) > (0.8f*2.0f*PI) ) full_rotations += ( d_angle > 0 ) ? -1 : 1;
angle_prev = val;
angle_cd = full_rotations * (2.0f*PI) + angle_prev;
return angle_cd;
}
/* 初始化AS5600 */
void AS5600_Init(void) {
vel_angle_prev = GetAngle_Without_Track();
vel_angle_prev_ts = micros();
HAL_Delay(1);
GetAngle_Without_Track();
angle_prev = GetAngle_Without_Track();
angle_prev_ts = micros();
}
/* 更新当前角度值 */
void AS5600_Update(void) {
float val = GetAngle_Without_Track();
angle_prev_ts = micros();
float d_angle = val - angle_prev; //前一次循环的角度-当前的角度
// 圈数检测
if(fabs(d_angle) > (0.8f*_2PI) ) full_rotations += ( d_angle > 0 ) ? -1 : 1; //判断是否大于80%的360度;判断转向
angle_prev = val;
}
/* 上一次的度数 */
float AS5600_GetMechanicalAngle(void) {
return angle_prev;
}
/*
角度输出函数
圈数转化为角度的弧度值,再加上未转满一圈的度数
*/
float AS5600_GetAngle(void){
printf("%.2f\r\n",(float)full_rotations * _2PI + angle_prev);
return (float)full_rotations * _2PI + angle_prev;
}
/* 获取编码器速度 */
float AS5600_GetVelocity(void) {
// 计算采样时间
float Ts = (angle_prev_ts - vel_angle_prev_ts)*1e-6;
// 快速修复奇怪的情况(微溢出)
if(Ts <= 0) Ts = 1e-3f;
// 速度计算
float vel = ( (float)(full_rotations - vel_full_rotations)*_2PI + (angle_prev - vel_angle_prev) ) / Ts;
// 保存变量以待将来使用
vel_angle_prev = angle_prev;
vel_full_rotations = full_rotations;
vel_angle_prev_ts = angle_prev_ts;
printf("%.2f\r\n",vel);
return vel;
}
2、pid.c
#include "pid.h"
#include "FOC2.h"
void PIDController_Init(PIDController* pid, float P, float I, float D, float ramp, float limit)
{
pid->P = P;
pid->I = I;
pid->D = D;
pid->output_ramp = ramp; // PID控制器加速度限幅
pid->limit = limit; // PID控制器输出限幅
pid->error_prev = 0.0f;
pid->output_prev = 0.0f;
pid->integral_prev = 0.0f;
pid->timestamp_prev = micros(); // 假设时间戳以某种形式初始化
}
/*
PID控制器函数
输入结构体及偏差值,输出相应的电压
*/
float PIDController_Operator(PIDController* pid, float error)
{
// 计算两次循环中间的间隔时间
unsigned long timestamp_now = micros();
float Ts = (timestamp_now - pid->timestamp_prev) * 1e-6f;
if(Ts <= 0 || Ts > 0.5f) Ts = 1e-3f;
// P环
float proportional = pid->P * error;
// Tustin 散点积分(I环)
float integral = pid->integral_prev + pid->I*Ts*0.5f*(error + pid->error_prev);
integral = _constrain(integral, -pid->limit, pid->limit);
// D环(微分环节)
float derivative = pid->D*(error - pid->error_prev)/Ts;
// 将P,I,D三环的计算值加起来
float output = proportional + integral + derivative;
output = _constrain(output, -pid->limit, pid->limit);
if(pid->output_ramp > 0){
// 对PID的变化速率进行限制
float output_rate = (output - pid->output_prev)/Ts;
if (output_rate > pid->output_ramp)
output = pid->output_prev + pid->output_ramp*Ts;
else if (output_rate < - pid->output_ramp)
output = pid->output_prev - pid->output_ramp*Ts;
}
// 保存值(为了下一次循环)
pid->integral_prev = integral;
pid->output_prev = output;
pid->error_prev = error;
pid->timestamp_prev = timestamp_now;
return output;
}
/*
// 使用示例
int main() {
PIDController pid;
PIDController_Init(&pid, 1.0f, 0.0f, 0.0f, 0.1f, 100.0f);
float output = PIDController_Update(&pid, 0.5f);
printf("PID output: %f\n", output);
return 0;
}
*/
3、简易低通滤波
#include "lowpass_filter.h"
#include <stdint.h>
void LowPassFilter_Init(LowPassFilter *filter, float time_constant) {
filter->Tf = time_constant;
filter->y_prev = 0.0f;
filter->timestamp_prev = micros();
}
float LowPassFilter_process(LowPassFilter *filter, float x) {
unsigned long timestamp = micros();
float dt = (float)(timestamp - filter->timestamp_prev) * 1e-6f; //计算两次循环的时间间隔并转换为秒
if (dt < 0.0f) dt = 1e-3f; //处理因为micros跳变引起的时间间隔跳变
else if (dt > 0.3f) { //处理时间间隔大于0.3s的情况
filter->y_prev = x;
filter->timestamp_prev = timestamp;
return x;
}
float alpha = filter->Tf / (filter->Tf + dt);
float y = alpha * filter->y_prev + (1.0f - alpha) * x;
filter->y_prev = y;
filter->timestamp_prev = timestamp;
return y;
}
4、main.c
/* USER CODE BEGIN PV */
float motor_target=2; //目标速度
float angle_target=2; //目标角度
extern int PP,DIR;
/* USER CODE END PV */
/* USER CODE BEGIN 2 */
printf("Hello World\r\n");
HAL_Delay(500);
DFOC_Vbus(12.6); // 输入电压12.6V
DFOC_alignSensor(PP,DIR); // 设置转轴数和方向
HAL_Delay(100);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
DFOC_M0_set_Force_Angle(angle_target); //设置位置
// DFOC_M0_SetVelocity(motor_target); //设置速度
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}