nuttx/libs/libdsp/lib_observer_b16.c
2023-10-07 21:25:56 +08:00

758 lines
21 KiB
C

/****************************************************************************
* libs/libdsp/lib_observer_b16.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <dspb16.h>
#include <string.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Squared */
#define SQ_B16(x) (b16mulb16((x), (x)))
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: motor_sobserver_init_b16
*
* Description:
* Initialize motor speed observer
*
* Input Parameters:
* observer - pointer to the speed observer data
* so - pointer to the speed specific observer data
* per - observer execution period
*
* Returned Value:
* None
*
****************************************************************************/
void motor_sobserver_init_b16(FAR struct motor_sobserver_b16_s *observer,
FAR void *so, b16_t per)
{
LIBDSP_DEBUGASSERT(observer != NULL);
LIBDSP_DEBUGASSERT(so != NULL);
LIBDSP_DEBUGASSERT(per > 0);
/* Reset observer data */
memset(observer, 0, sizeof(struct motor_sobserver_b16_s));
/* Set observer period */
observer->per = per;
/* Connect speed estimation observer data */
observer->so = so;
}
/****************************************************************************
* Name: motor_aobserver_init_b16
*
* Description:
* Initialize motor angle observer
*
* Input Parameters:
* observer - pointer to the angle observer data
* ao - pointer to the angle specific observer data
* per - observer execution period
*
* Returned Value:
* None
*
****************************************************************************/
void motor_aobserver_init_b16(FAR struct motor_aobserver_b16_s *observer,
FAR void *ao, b16_t per)
{
LIBDSP_DEBUGASSERT(observer != NULL);
LIBDSP_DEBUGASSERT(ao != NULL);
LIBDSP_DEBUGASSERT(per > 0);
/* Reset observer data */
memset(observer, 0, sizeof(struct motor_aobserver_b16_s));
/* Set observer period */
observer->per = per;
/* Connect angle estimation observer data */
observer->ao = ao;
}
/****************************************************************************
* Name: motor_aobserver_smo_init_b16
*
* Description:
* Initialize motor sliding mode observer.
*
* Input Parameters:
* smo - pointer to the sliding mode observer private data
* kslide - SMO gain
* err_max - linear region upper limit
*
* Returned Value:
* None
*
****************************************************************************/
void motor_aobserver_smo_init_b16(FAR struct motor_aobserver_smo_b16_s *smo,
b16_t kslide, b16_t err_max)
{
LIBDSP_DEBUGASSERT(smo != NULL);
LIBDSP_DEBUGASSERT(kslide > 0);
LIBDSP_DEBUGASSERT(err_max > 0);
/* Reset structure */
memset(smo, 0, sizeof(struct motor_aobserver_smo_b16_s));
/* Initialize structure */
smo->k_slide = kslide;
smo->err_max = err_max;
/* Store inverted err_max to avoid division */
smo->one_by_err_max = b16divb16(b16ONE, err_max);
}
/****************************************************************************
* Name: motor_aobserver_smo_b16
*
* Description:
* One step of the SMO observer.
* REFERENCE: http://ww1.microchip.com/downloads/en/AppNotes/01078B.pdf
*
* Below some theoretical backgrounds about SMO.
*
* The digitalized motor model can be represent as:
*
* d(i_s.)/dt = (-R/L)*i_s. + (1/L)*(v_s - e_s. - z)
*
* We compare estimated current (i_s.) with measured current (i_s):
*
* err = i_s. - i_s
*
* and get correction factor (z):
*
* sign = sign(err)
* z = sign*K_SLIDE
*
* Once the digitalized model is compensated, we estimate BEMF (e_s.) by
* filtering z:
*
* e_s. = low_pass(z)
*
* The estimated BEMF is filtered once again and used to approximate the
* motor angle:
*
* e_filtered_s. = low_pass(e_s.)
* theta = arctan(-e_alpha/e_beta)
*
* The estimated theta is phase-shifted due to low pass filtration, so we
* need some phase compensation. More details below.
*
* where:
* v_s - phase input voltage vector
* i_s. - estimated phase current vector
* i_s - phase current vector
* e_s. - estimated phase BEMF vector
* R - motor winding resistance
* L - motor winding inductance
* z - output correction factor voltage
*
* Input Parameters:
* o - (in/out) pointer to the angle observer data
* i_ab - (in) inverter alpha-beta current
* v_ab - (in) inverter alpha-beta voltage
* phy - (in) pointer to the motor physical parameters
* dir - (in) rotation direction (1.0 for CCW, -1.0 for CW)
* NOTE: (mechanical dir) = -(electrical dir)
* speed - (in) electrical speed
* TODO: pass rotation direction with speed sign
*
* Returned Value:
* None
*
****************************************************************************/
void motor_aobserver_smo_b16(FAR struct motor_aobserver_b16_s *o,
FAR ab_frame_b16_t *i_ab, FAR ab_frame_b16_t *v_ab,
FAR struct motor_phy_params_b16_s *phy, b16_t dir,
b16_t speed)
{
LIBDSP_DEBUGASSERT(o != NULL);
LIBDSP_DEBUGASSERT(i_ab != NULL);
LIBDSP_DEBUGASSERT(v_ab != NULL);
LIBDSP_DEBUGASSERT(phy != NULL);
FAR struct motor_aobserver_smo_b16_s *smo =
(FAR struct motor_aobserver_smo_b16_s *)o->ao;
FAR ab_frame_b16_t *emf = &smo->emf;
FAR ab_frame_b16_t *emf_f = &smo->emf_f;
FAR ab_frame_b16_t *z = &smo->z;
FAR ab_frame_b16_t *i_est = &smo->i_est;
FAR ab_frame_b16_t *v_err = &smo->v_err;
FAR ab_frame_b16_t *i_err = &smo->i_err;
FAR ab_frame_b16_t *sign = &smo->sign;
b16_t i_err_a_abs = 0;
b16_t i_err_b_abs = 0;
b16_t angle = 0;
b16_t filter = 0;
LIBDSP_DEBUGASSERT(smo != NULL);
/* REVISIT: observer works only when IQ current is high enough
* Lower IQ current -> lower K_SLIDE
*/
/* Calculate observer gains */
smo->F = (b16ONE - b16mulb16(b16mulb16(o->per, phy->res),
phy->one_by_ind));
smo->G = b16mulb16(o->per, phy->one_by_ind);
/* Saturate F gain */
if (smo->F < 0)
{
smo->F = 0;
}
/* Saturate G gain */
if (smo->G > ftob16(0.999f))
{
smo->G = ftob16(0.999f);
}
/* Configure low pass filters
*
* We tune low-pass filters to achieve cutoff frequency equal to
* input signal frequency. This gives us constant phase shift between
* input and output signals equals to:
*
* phi = -arctan(f_in/f_c) = -arctan(1) = -45deg = -PI/4
*
* Input signal frequency is equal to the frequency of the motor currents,
* which give us:
*
* f_c = omega_e/(2*PI)
* omega_m = omega_e/pole_pairs
* f_c = omega_m*pole_pairs/(2*PI)
*
* filter = T * (2*PI) * f_c
* filter = T * omega_m * pole_pairs
*
* T - [s] period at which the digital filter is being
* calculated
* f_in - [Hz] input frequency of the filter
* f_c - [Hz] cutoff frequency of the filter
* omega_m - [rad/s] mechanical angular velocity
* omega_e - [rad/s] electrical angular velocity
* pole_pairs - pole pairs
*
*/
filter = b16mulb16(b16mulb16(o->per, speed), itob16(phy->p));
/* Limit SMO filters
* REVISIT: lowest filter limit should depend on minimum speed:
* filter = T * (2*PI) * f_c = T * omega0
*
*/
if (filter >= b16ONE)
{
filter = ftob16(0.99f);
}
else if (filter < ftob16(0.005f))
{
filter = ftob16(0.005f);
}
smo->emf_lp_filter1 = filter;
smo->emf_lp_filter2 = smo->emf_lp_filter1;
/* Get voltage error: v_err = v_ab - emf */
v_err->a = v_ab->a - emf->a;
v_err->b = v_ab->b - emf->b;
/* Estimate stator current */
i_est->a = (b16mulb16(smo->F, i_est->a) +
b16mulb16(smo->G, (v_err->a - z->a)));
i_est->b = (b16mulb16(smo->F, i_est->b) +
b16mulb16(smo->G, (v_err->b - z->b)));
/* Get motor current error */
i_err->a = i_ab->a - i_est->a;
i_err->b = i_ab->b - i_est->b;
/* Slide-mode controller */
sign->a = b16sign(i_err->a);
sign->b = b16sign(i_err->b);
/* Get current error absolute value - just multiply value with its sign */
i_err_a_abs = b16mulb16(i_err->a, sign->a);
i_err_b_abs = b16mulb16(i_err->b, sign->b);
/* Calculate new output correction factor voltage */
if (i_err_a_abs < smo->err_max)
{
/* Enter linear region if error is small enough */
z->a = b16mulb16(b16mulb16(i_err->a, smo->k_slide),
smo->one_by_err_max);
}
else
{
/* Non-linear region */
z->a = b16mulb16(sign->a, smo->k_slide);
}
if (i_err_b_abs < smo->err_max)
{
/* Enter linear region if error is small enough */
z->b = b16mulb16(b16mulb16(i_err->b, smo->k_slide),
smo->one_by_err_max);
}
else
{
/* Non-linear region */
z->b = b16mulb16(sign->b, smo->k_slide);
}
/* Filter z to obtain estimated emf */
LP_FILTER_B16(emf->a, z->a, smo->emf_lp_filter1);
LP_FILTER_B16(emf->b, z->b, smo->emf_lp_filter1);
/* Filter emf one more time before angle stimation */
LP_FILTER_B16(emf_f->a, emf->a, smo->emf_lp_filter2);
LP_FILTER_B16(emf_f->b, emf->b, smo->emf_lp_filter2);
/* Estimate phase angle according to:
* emf_a = -|emf| * sin(th)
* emf_b = |emf| * cos(th)
* th = atan2(-emf_a, emf->b)
*
* NOTE: bottleneck but we can't do much more to optimise this
*/
angle = fast_atan2_b16(-emf->a, emf->b);
/* Angle compensation.
* Due to low pass filtering we have some delay in estimated phase angle.
*
* Adaptive filters introduced above cause -PI/4 phase shift for each
* filter. We use 2 times filtering which give us constant -PI/2 (-90deg)
* phase shift.
*/
angle = angle + b16mulb16(dir, b16HALFPI);
/* Normalize angle to range <0, 2PI> */
angle_norm_2pi_b16(&angle, 0, b16TWOPI);
/* Store estimated angle in observer data */
o->angle = angle;
}
/****************************************************************************
* Name: motor_sobserver_div_init_b16
*
* Description:
* Initialize DIV speed observer
*
* Input Parameters:
* so - (in/out) pointer to the DIV speed observer data
* sample - (in) number of angle samples
* filter - (in) low-pass filter for final omega
* per - (in) speed observer execution period
*
* Returned Value:
* None
*
****************************************************************************/
void motor_sobserver_div_init_b16(FAR struct motor_sobserver_div_b16_s *so,
uint8_t samples, b16_t filter, b16_t per)
{
LIBDSP_DEBUGASSERT(so != NULL);
LIBDSP_DEBUGASSERT(samples > 0);
LIBDSP_DEBUGASSERT(filter > 0);
/* Reset observer data */
memset(so, 0, sizeof(struct motor_sobserver_div_b16_s));
/* Store number of samples for DIV observer */
so->samples = samples;
/* Store low-pass filter for DIV observer speed */
so->filter = filter;
/* Store inverted sampling period */
so->one_by_dt = b16divb16(b16ONE, b16muli(per, so->samples));
}
/****************************************************************************
* Name: motor_sobserver_div_b16
*
* Description:
* Estimate motor speed based on motor angle difference (electrical
* or mechanical)
*
* Input Parameters:
* o - (in/out) pointer to the speed observer data
* angle - (in) angle normalized to <0.0, 2PI>
* dir - (in) rotation direction. Valid values:
* DIR_CW (1.0f) or DIR_CCW(-1.0f)
*
****************************************************************************/
void motor_sobserver_div_b16(FAR struct motor_sobserver_b16_s *o,
b16_t angle)
{
LIBDSP_DEBUGASSERT(o != NULL);
LIBDSP_DEBUGASSERT(angle >= 0 && angle <= b16TWOPI);
FAR struct motor_sobserver_div_b16_s *so =
(FAR struct motor_sobserver_div_b16_s *)o->so;
volatile b16_t omega = 0;
LIBDSP_DEBUGASSERT(so != NULL);
/* Normalize angle to range <-PI, PI> */
angle_norm_2pi_b16(&angle, -b16PI, b16PI);
/* Get angle diff */
so->angle_diff = angle - so->angle_prev;
/* Normalize angle to range <-PI, PI> */
angle_norm_2pi_b16(&so->angle_diff, -b16PI, b16PI);
/* Accumulate angle only if sample is valid */
so->angle_acc += so->angle_diff;
/* Increase counter */
so->cntr += 1;
/* Accumulate angle until we get configured number of samples */
if (so->cntr >= so->samples)
{
/* Estimate omega using accumulated angle samples.
* In this case use simple estimation:
*
* omega = delta_theta/delta_time
* speed_now = low_pass(omega)
*
*/
omega = b16mulb16(so->angle_acc, so->one_by_dt);
/* Store filtered omega.
*
* REVISIT: cut-off frequency for this filter should be
* (probably) set according to minimum supported omega:
*
* filter = T * (2*PI) * f_c = T * omega0
*
* where:
* omega0 - minimum angular speed
* T - speed estimation period (samples*per)
*/
LP_FILTER_B16(o->speed, omega, so->filter);
/* Reset samples counter and accumulated angle */
so->cntr = 0;
so->angle_acc = 0;
}
/* Store current angle as previous angle */
so->angle_prev = angle;
}
/****************************************************************************
* Name: motor_aobserver_nfo_init_b16
*
* Description:
* Initialize motor nolinear fluxlink observer.
*
* Input Parameters:
* nfo - pointer to the nolinear fluxlink observer private data
*
* Returned Value:
* None
*
****************************************************************************/
void motor_aobserver_nfo_init_b16(FAR struct motor_aobserver_nfo_b16_s *nfo)
{
LIBDSP_DEBUGASSERT(nfo != NULL);
/* Reset structure */
memset(nfo, 0, sizeof(struct motor_aobserver_nfo_b16_s));
}
/****************************************************************************
* Name: motor_aobserver_nfo_b16
*
* Description:
* nolinear fluxlink observer.
* REFERENCE: http://cas.ensmp.fr/~praly/Telechargement/Journaux/
* 2010-IEEE_TPEL-Lee-Hong-Nam-Ortega-Praly-Astolfi.pdf
*
* Input Parameters:
* o - (in/out) pointer to the angle observer data
* i_ab - (in) inverter alpha-beta current
* v_ab - (in) inverter alpha-beta voltage
* phy - (in) pointer to the motor physical parameters
* gain - (in) dynamic observer gain
*
* Returned Value:
* None
*
****************************************************************************/
void motor_aobserver_nfo_b16(FAR struct motor_aobserver_b16_s *o,
FAR ab_frame_b16_t *i_ab, FAR ab_frame_b16_t *v_ab,
FAR struct motor_phy_params_b16_s *phy, b16_t gain)
{
FAR struct motor_aobserver_nfo_b16_s *nfo =
(FAR struct motor_aobserver_nfo_b16_s *)o->ao;
b16_t angle;
b16_t err;
b16_t x1_dot;
b16_t x2_dot;
b16_t l_ia = 0;
b16_t l_ib = 0;
b16_t r_ia = 0;
b16_t r_ib = 0;
LIBDSP_DEBUGASSERT(nfo != NULL);
l_ia = b16mulb16(b16mulb16((b16ONE + b16HALF), phy->ind), i_ab->a);
l_ib = b16mulb16(b16mulb16((b16ONE + b16HALF), phy->ind), i_ab->b);
r_ia = b16mulb16(b16mulb16((b16ONE + b16HALF), phy->res), i_ab->a);
r_ib = b16mulb16(b16mulb16((b16ONE + b16HALF), phy->res), i_ab->b);
err = SQ_B16(phy->flux_link) - (SQ_B16(nfo->x1 - l_ia) +
SQ_B16(nfo->x2 - l_ib));
/* Forcing this term to stay negative helps convergence according to
* http://cas.ensmp.fr/Publications/Publications/Papers/
* ObserverPermanentMagnet.pdf and
* https://arxiv.org/pdf/1905.00833.pdf
*/
if (err > 0)
{
err = 0;
}
x1_dot = -r_ia + v_ab->a + b16mulb16(b16mulb16(gain, (nfo->x1 - l_ia)),
err);
x2_dot = -r_ib + v_ab->b + b16mulb16(b16mulb16(gain, (nfo->x2 - l_ib)),
err);
nfo->x1 += b16mulb16(x1_dot, o->per);
nfo->x2 += b16mulb16(x2_dot, o->per);
/* Prevent the magnitude from getting too low
* as that makes the angle very unstable.
*/
if (vector2d_mag_b16(nfo->x1, nfo->x2) <
(b16mulb16(phy->flux_link, b16HALF)))
{
nfo->x1 = b16mulb16((b16ONE + b16ONETENTH), nfo->x1);
nfo->x2 = b16mulb16((b16ONE + b16ONETENTH), nfo->x2);
}
angle = fast_atan2_b16(nfo->x2 - l_ib, nfo->x1 - l_ia);
/* Normalize angle to range <0, 2PI> */
angle_norm_2pi_b16(&angle, 0, b16TWOPI);
/* Store estimated angle in observer data */
o->angle = angle;
}
/****************************************************************************
* Name: motor_sobserver_pll_init_b16
*
* Description:
* Initialize PLL speed observer
*
* Input Parameters:
* so - (in/out) pointer to the PLL speed observer data
* pll_kp - (in) pll proportional gain
* pll_ki - (in) pll integral gain
*
* Returned Value:
* None
*
****************************************************************************/
void motor_sobserver_pll_init_b16(FAR struct motor_sobserver_pll_b16_s *so,
b16_t pll_kp, b16_t pll_ki)
{
LIBDSP_DEBUGASSERT(so != NULL);
LIBDSP_DEBUGASSERT(pll_kp > 0);
LIBDSP_DEBUGASSERT(pll_ki > 0);
/* Reset observer data */
memset(so, 0, sizeof(struct motor_sobserver_pll_b16_s));
/* Store kp for PLL observer */
so->pll_kp = pll_kp;
/* Store ki for PLL observer speed */
so->pll_ki = pll_ki;
}
/****************************************************************************
* Name: motor_sobserver_pll_b16
*
* Description:
* Estimate motor electrical speed based on motor electrical angle
* difference.
*
* Input Parameters:
* o - (in/out) pointer to the speed observer data
* angle - (in) electrical angle normalized to <0.0, 2PI>
*
****************************************************************************/
void motor_sobserver_pll_b16(FAR struct motor_sobserver_b16_s *o,
b16_t angle)
{
FAR struct motor_sobserver_pll_b16_s *so =
(FAR struct motor_sobserver_pll_b16_s *)o->so;
b16_t delta_theta = 0;
LIBDSP_DEBUGASSERT(so != NULL);
/* Normalize angle to range <-PI, PI> */
angle_norm_2pi_b16(&angle, -b16PI, -b16PI);
delta_theta = angle - so->pll_phase;
/* Normalize angle to range <-PI, PI> */
angle_norm_2pi_b16(&delta_theta, -b16PI, -b16PI);
so->pll_phase += b16mulb16((o->speed + b16mulb16(so->pll_kp, delta_theta)),
o->per);
/* Normalize angle to range <-PI, PI> */
angle_norm_2pi_b16(&so->pll_phase, -b16PI, -b16PI);
o->speed += b16mulb16(b16mulb16(so->pll_ki, delta_theta), o->per);
}
/****************************************************************************
* Name: motor_sobserver_speed_get_b16
*
* Description:
* Get the estmiated motor speed from the observer
*
* Input Parameters:
* o - (in/out) pointer to the speed observer data
*
* Returned Value:
* Return estimated motor speed from observer
*
****************************************************************************/
b16_t motor_sobserver_speed_get_b16(FAR struct motor_sobserver_b16_s *o)
{
LIBDSP_DEBUGASSERT(o != NULL);
return o->speed;
}
/****************************************************************************
* Name: motor_aobserver_angle_get_b16
*
* Description:
* Get the estmiated motor electrical angle from the observer
*
* Input Parameters:
* o - (in/out) pointer to the angle observer data
*
* Returned Value:
* Return estimated motor electrical angle from observer
*
****************************************************************************/
b16_t motor_aobserver_angle_get_b16(FAR struct motor_aobserver_b16_s *o)
{
LIBDSP_DEBUGASSERT(o != NULL);
return o->angle;
}