前言:定點是個好東西,就是編碼有點難受。但是找到了一些定點編程的方法后,我們還是能理解它,掌握它,運用它。畢竟浮點的單片機很貴,定點的單片機很便宜。作者上手第一個單片機就是支持浮點的,編碼的思想一直都是浮點,管你3721直接干就完了。但是最近因為需要做一些定點的功能開發,就被迫去學習一些關于定點的實現了。
定點計算的幾個基本點:
-
IQ格式的定點相乘等于Q相加,除法就是減少
-
相同IQ格式的定點可以直接加減
-
可以利用移位來實現數字放大或縮小
定點的目是將浮點數字放大X倍來抵消整數計算時丟失的數字誤差,比如0.0001f轉為定點數字就是直接將浮點數字乘以2^X這個數字,如果IQ轉換數字足夠大,就可以減弱整數數值計算的誤差。根據IEE754標準的單精度浮點數,小數點后的浮點數字編碼最大利用到24位,因此理論上使用IQ24的定點數(#define _IQ24(A) (long) ((A) * 16777216.0L))就可以等于浮點的小數點誤差。TI的IQMATH庫函數默認使用IQ24的格式就是因為這個原因。
利用這一點可以使用定點計算來獲得比單精度浮點更高的算法,比如高階的濾波器設計中,時常因為采樣頻率非常高導致離散化之后離散傳遞函數中多項式中的系數很小,因為浮點數量化誤差導致的濾波器誤差。筆者之前就有遇到一個帶通濾波器因為量化誤差的問題,導致一直不能很好地運行的問題,后面是拆分成多個濾波器組合后才解決。今天了解到定點數字計算的精度還能超越浮點時,后面應付這類問題就多了一個方法。
下面嘗試使用一個PI的定點實現方法來學習定點編程
實現:
typedef struct PIF_CTRL_LAW_DATA_IQ_TAG{
Uint16 coeff_init_flag;
_iq error_1;
_iq Integrator_output_1;
_iq Integrator_output;
_iq Integrator_gain;
_iq ts;
_iq kp;
_iq ki; /* 1/ti */
_iq pi_out;
_iq output;
_iq max_out;
_iq min_out;
Uint16 integrator_sign;
// LPF
_iq lpf_a_coeff;
_iq _1_lpf_a_coeff;
_iq lpf_out_last;
_iq lpf_out;
}PIF_CTRL_IQ_DATA_DEF;
static inline _iq piF_IQ_func( _iq error,
PIF_CTRL_IQ_DATA_DEF *p,
float32 kp,
float32 ti,
float32 lpc_fc,
float32 ts,
float32 max,
float32 min)
{
if(1u == p->coeff_init_flag) //判斷控制系統初始化
{
if(p->integrator_sign) //抗飽和積分,當輸出飽和時停止累積誤差
{
p->Integrator_output = (_IQmpy(p->ts, (error + p->error_1))) + p->Integrator_output_1;
}
else
{
p->Integrator_output = p->Integrator_output_1;
}
//更新參數
p->Integrator_output_1 = p->Integrator_output;
p->error_1 = error;
p->Integrator_gain = (_IQ10mpy(p->ki, p->Integrator_output)); //在積分增益初始時只左移了10位,這里進行IQ10*IQ24后,為了達到IQ24的精度,其實還需要左移14位,但是兩個IQ24計算完成后最要右移24位,所以左移14-右移24,那就是只需要右移10即可完成計算
p->output = _IQmpy(p->kp, error) + p->Integrator_gain;
p->pi_out = p->output;
//限制幅度
if(p->pi_out > p->max_out) {p->pi_out = p->max_out;}
if(p->pi_out < p->min_out) {p->pi_out = p->min_out;}
//飽和判斷
p->integrator_sign = (p->pi_out == p->output)? 1u : 0u;
// LPF
p->lpf_out = _IQmpy(p->_1_lpf_a_coeff, p->lpf_out_last) + _IQmpy(p->lpf_a_coeff, p->pi_out);
p->lpf_out_last = p->lpf_out;
}
else
{
p->error_1 = 0;
p->Integrator_output = 0;
p->Integrator_output_1 = 0;
p->Integrator_gain = 0;
p->integrator_sign = 1u;
p->output = 0;
p->pi_out = 0;
p->max_out = _IQ(max);
p->min_out = _IQ(min);
//初始化參數
p->ts = _IQ(ts * 0.5f);
p->kp = _IQ(kp);
//由于積分增益可能很大,為了在32位數字量化不溢出,僅使用IQ10來進行轉換,可以接受丟失精度
p->ki = _IQ10(kp/ti); // L SHIFT 10BIT
p->lpf_out = 0;
p->lpf_out_last = 0;
//低通濾波器參數計算
float32 lpf_rc_tao = 1.0f / (lpc_fc * 2.0f * M_PI);
/* 1ORDER LPF a = Ts/(Ts + 1/(2*pi*fc)) */
float32 lpf_a_coeff_f = ts / (ts + lpf_rc_tao);
float32 _1_lpf_a_coeff_f = 1.0f - lpf_a_coeff_f;
p->lpf_a_coeff = _IQ(lpf_a_coeff_f);
p->_1_lpf_a_coeff = _IQ(_1_lpf_a_coeff_f);
p->coeff_init_flag = 1u;
}
return(p->lpf_out);
}
本人能力有限,研究定點也才剛開始,如有錯誤懇請幫忙指正,謝謝。