PID控制器的數字實現及C語法講解

来源:http://www.cnblogs.com/cjq0301/archive/2016/02/10/5184808.html
-Advertisement-
Play Games

PID控制器的數字實現及C語法講解 概述 為方便學習與交流,根據自己的理解與經驗寫了這份教程,有錯誤之處請各位讀者予以指出,具體包含以下三部分內容: (1) PID數字化的推導過程(實質:微積分的近似計算); (2) 程式風格介紹(程式風格來源於TI官方案例); (3) C有關語法簡述(語法會結合實


PID控制器的數字實現及C語法講解

  • 概述  

  為方便學習與交流,根據自己的理解與經驗寫了這份教程,有錯誤之處請各位讀者予以指出,具體包含以下三部分內容:

  (1)  PID數字化的推導過程(實質:微積分的近似計算);

  (2)  程式風格介紹(程式風格來源於TI官方案例);

  (3)  C有關語法簡述(語法會結合實例進行講解)。


  • PID控制器的數字化

  PID控制器是工業過程式控制制中廣泛採用的一種控制器,其中,P、I、D分別為比例(Proportion)、積分(Integral)、微分(Differential)的簡寫;將偏差的比例、積分和微分通過線性組合構成控制量,用該控制量對受控對象進行控制,稱為PID演算法

  為了用軟體實現PID演算法,需將PID控制器離散化。

  1. 整體思路

        

  2. 方框圖

  PID控制器的方框圖如圖所示:

           

  3. 拉氏域的表達式

  根據方框圖,可寫出PID控制器對應的傳遞函數:

              

         其中,Kp為比例繫數,ki為積分繫數,Kd為微分繫數

 

  4. 時域的表達式

  在分析時,通常藉助於拉氏空間,例如判斷系統的穩定性與相對穩定性;而現在我們關心的是時域里的問題,因此對上式進行拉普拉斯逆變換,得到時域里的表達式:

             

       其對應的結構框圖如圖所示:

              

  5. 差分方程

  該時域里的表達式不便於編程處理,因此需對該式進行離散化處理,從而得到可編程實現的差分方程,分析過程如下:

  (說明:PID離散化的實質為微積分的離散化(數值化處理),由於這個推導過程很多教材上都有介紹,因而略去推導過程,只給出最終表達式,程式的演算法就是基於此表達式而寫的

  數字PID控制器的增量式演算法:

      

      其中,T為步長,即採樣周期(由微控制器的定時器確定

  u(kT)=u(k),便得到PID控制器增量式演算法的差分方程:

      

  這樣就可編程實現了或許有人會問,為什麼差分方程就可編程實現呢?這是因為解差分方程的一般解法就是迭代法,而迭代法只需初值跟通項公式,這在電腦編程中很容易實現 

  為使編程方便,可引入中間變數,定義如下:

             

  則,PID控制器增量式演算法的差分方程變為:

      

  說明:

   (1)在PID增量式演算法中只需對輸出u(t)作限幅處理

   (2)當微分繫數 Kd=0 時,PID控制器就成了PI控制器在編寫PID程式時預設使其為PI調節器

       當積分繫數 Ki=0 時,PID控制器就成了PD控制器。

 


  •  基於微控制器的演算法實現

  我寫的數字PID程式如圖所示(在最後的附件部分),有兩套代碼,一套是直接函數調用C/C++通用,另一套是使用函數指針進行函數調用僅適用於C,現從兩個方面對該程式做講解:

(一)程式風格

  程式採用了模塊化編程的思想,這樣做的目的增強代碼的可移植性及程式的可讀性

  程式被拆分成三個模塊:

  一個是PID的頭文件’PID.h’:主要是定義演算法實現有關的數據類型;

  一個是PID的源文件’PID.c’:主要是定義演算法實現的函數;

  一個是主函數文件’amain.c’:PID程式的使用方法,即在主程式中做相應的初始化工作,在中斷服務程式中進行PID的計算。

說明:讀這個程式時可能有點困難,不過這屬情理之中的事,畢竟剛接觸這種風格的童鞋不太能理解這種風格的產生(為什麼這麼做)及用意(這麼做的好處);我的建議是:在理解演算法的原理後,根據自己的編程風格嘗試著寫一下,然後再跟這套程式對比著來理解,推敲一下別人為什麼要這麼做;當熟悉了整個流程後,你才能體會這種程式風格的優勢,再將這種編程風格慢慢轉化為自己的編程風格。

(二)程式中涉及的C語法講解

  這裡,我只講述為什麼要採用這些語法以及採用這些語法所帶來的好處,至於細枝末節的問題,就請各位童鞋自行查閱有關資料,順帶給大家推薦一本不錯的C語言教材:C Primer Plus,畢竟學習的興趣濃度跟書籍的編排也有關。

  1. 條件編譯指令

  第一處:#ifndef PID_H語句

  使用該語句的目的是避免造成把重覆定義語句(如,結構體類型定義)添加到工程中,而使得編譯出錯

  說明:其實也可不用#ifndef語句,因為每個定義的變數都具有特定的物理含義,不會造成重覆定義現象。

  第二處:#if (PID_DEBUG) 語句

  使用該語句的目的實現功能切換註意了:是在校正PID參數後手動切換,通過改變巨集定義語句#define PID_DEBUG 1中的巨集體實現),具體請看程式清單。

  2. 結構體及結構體指針

  使用結構體類型的好處:可為實現某一功能的各變數進行“打包”處理

  使用結構體指針的好處:通過傳址調用,對方便對結構體變數本身進行操作

  3. typedef數據類型定義

  使用typedef數據類型定義的好處是方便跨平臺進行代碼移植操作;但由於教材的緣故,造成很多童鞋都停留在錶面層次上的理解(typedef 數據類型 別名),因而此處作重點講解。

       我的理解:任何一個typedef聲明中的標識符不再是一個變數,而是代表一個數據類型,其表示的數據類型為正常變數聲明(去掉typedef)的那個標識符的數據類型。


  理解起來可能有點困難,現結合實例來講解:

       [例1]

typedef int Myint;

  分析:

  第一步:整體分析

  該語句表示定義了一個數據類型Myint(這裡Myint為數據類型標識符);

  (至於其具體表示什麼類型,請看下步分析)

  第二步:正常變數聲明(去掉typedef)

  int Myint;

  其所表示的類型為變數Myint(這裡Myint為變數標識符)的數據類型,即整型類型。

  應用:

  Myint a; //聲明整型變數a

       [例2]    

typedef struct
{
    //省略成員
}PID;

  分析:

  第一步:整體分析

  該語句表示定義了一個數據類型PID(這裡PID為類型標識符)

  (至於其具體表示什麼類型,請看下步分析)

  第二步:正常變數聲明(去掉typedef)

    struct
    {
        //省略成員
    }PID;    

  其所表示的類型為結構體變數PID(這裡PID為變數標識符)的數據類型,即結構體類型,且其具有的成員同結構體變數PID。

  應用:

    PID ASR; //定義結構體變數ASR

       [例3]

typedef void (*PFun)(int );

  分析:

  第一步:整體分析

  該語句表示定義了一個數據類型PFun(這裡PFun為類型標識符)

  (至於其具體表示什麼類型,請看下步分析)

  第二步:正常變數聲明(去掉typedef)

    void (*PFun)(int );

  其所表示的類型為函數指針PFun(這裡PFun為變數標識符)的數據類型,即函數指針類型,且指針所指向的函數類型:形參為整型,無返回值的一類函數。

  應用:

  PFun  pf; //定義函數指針pf

  說明:typedef的用法與巨集定義#define的用法類似,但又有區別,體現在以下兩點:

  (a)  typedef是對數據類型的定義,而#define是對數值的定義;

  (b)  typedef由編譯器解釋,而#define由預處理器執行。

  4. 空形參函數和形參帶(void)函數

  這是在C/C++中相當容易混淆的地方,因此這裡重點介紹一下,若是這個知識點沒搞懂,那麼這個程式你就無法看懂為什麼會如此定義函數指針及利用函數指針來進行函數調用。

  void本身就是一種數據類型(空類型),把void作為形參時,表示這個函數不需要參數。

  在C++中,空形參表與新參為void是等價的,這是C++中明確規定的;但在C中則是兩回事:C中的空形參表僅表示函數的形參個數和類型不確定,並非沒有參數,這會暫時掛起編譯器的類型檢查機制,從而造成類型安全隱患,所以在C中欲表示函數無形參時,最好用void,此時編譯器將進行函數參數類型驗證。

  [例]

void pid_calc(int); //函數聲明
void (*calc_1)(int); //函數指針聲明
void (*calc_2)(); //函數指針聲明

void main()
{
    //將函數的入口地址賦給函數指針
    calc_1=pid_calc; //C編譯通過;C++編譯通過
    calc_2=pid_calc; //C編譯通過;C++編譯失敗
}

  5. 函數指針及其函數調用

  函數調用,除了直接調用”函數名(實參)”這種語法外,還可通過函數指針來實現,兩者並無區別,但為了代碼的緊湊性及美觀性,建議大家使用函數指針來進行函數調用。

  在我放出的兩套代碼中,一套是直接函數調用C/C++通用),另一套是使用函數指針進行函數調用僅適用於C),大家可體會這兩種用法的區別。

  6. 數據類型轉換

  C語言中的數據類型分為自動類型轉換與強制類型轉換

  (1) 自動類型轉換(由編譯器完成)

  自動轉換的適用場合及其轉換規則,請讀者查閱有關資料

  (2) 強制類型轉換通過類型轉換運算實現

  在本程式中,即可對函數名`pid_calc`(函數名代表對應函數的入口地址)使用強制類型轉換(轉換為函數指針類型),也可不用,我都調試驗證過;現把程式截取出來,方便大家理解:

void pid_calc(PID *p); //函數聲明
void (*calc)(); //函數指針:指向PID計算函數

void main()
{
    //將函數的入口地址賦給指針變數
    calc=(void (*)(unsigned long))pid_calc; //編譯通過(強制類型轉換)
    calc=pid_calc; //編譯通過
}

  7. 代碼換行問題

  為了代碼的美觀調試方便,需涉及到代碼換行問題

  在本程式的巨集定義語句中使用了”\”,這是巨集定義中連接上下行的連接符,表示該巨集定義還未結束。

//定義PID控制器的初始值
#define PID_DEFAULTS {0,0, \
                      0,0,0, \
                      0.0002, \
                      0,0,0, \
                      0,0,0, \
                      0,0,0,0, \
                      (void (*)(unsigned long))pid_calc}

 


附件一:直接函數調用C/C++通用

PID.h文件

//===================================================
//PID.h文件
//===================================================
#ifndef PID_H
#define PID_H

//定義PID計算用到的結構體類型
typedef struct
{
    float Ref; //輸入:系統待調節量的給定值
    float Fdb; //輸入:系統待調節量的反饋值
    
    //PID控制器部分
    float Kp; //參數:比例繫數
    float Ki; //參數:積分繫數
    float Kd; //參數:微分繫數
    
    float T; //參數:離散化系統的採樣周期
    
    float a0; //變數:a0
    float a1; //變數: a1
    float a2; //變數: a2
    
    float Err; //變數:當前的偏差e(k)
    float Err_1; //歷史:前一步的偏差e(k-1)
    float Err_2;    //歷史:前前一步的偏差e(k-2)
    
    float Out; //輸出:PID控制器的輸出u(k)
    float Out_1;    //歷史:PID控制器前一步的輸出u(k-1)
    float OutMax; //參數:PID控制器的最大輸出
    float OutMin; //參數:PID控制器的最小輸出
    
}PID;

//定義PID控制器的初始值
#define PID_DEFAULTS {0,0, \
                      0,0,0, \
                      0.0002, \
                      0,0,0, \
                      0,0,0, \
                      0,0,0,0}

//條件編譯的判別條件
#define PID_DEBUG 1                     
    
//函數聲明
void pid_calc(PID *p);

#endif

//===================================================
//End of File
//===================================================

 

PID.c文件 

//===================================================
//PID.c文件
//===================================================
#include "PID.h"
//===================函數定義========================
/****************************************************
*說    明:
*    (1)PID控制器預設為PI調節器
*    (2)使用了條件編譯進行功能切換:節省計算時間
*        在校正PID參數時,使用巨集定義將PID_DEBUG設為1;
*        當參數校正完成後,使用巨集定義將PID_DEBUG設為0,同時,在初始化時
*    直接為p->a0、p->a1、p->a2賦值
****************************************************/
void pid_calc(PID *p)
{
    //使用條件編譯進行功能切換
    #if (PID_DEBUG)
    float a0,a1,a2;
    //計算中間變數a0、a1、a2
    a0=p->Kp+p->Ki*p->T+p->Kd/p->T;
    a1=p->Kp+2*p->Kd/p->T;
    a2=p->Kd/p->T;
    //計算PID控制器的輸出
    p->Out=p->Out_1+a0*p->Err-a1*p->Err_1+a2*p->Err_2;
    #else
    //計算PID控制器的輸出
    p->Out=p->Out_1+p->a0*p->Err-p->a1*p->Err_1+p->a2*p->Err_2;
    #endif
    
    //輸出限幅
    if(p->Out>p->OutMax)
        p->Out=p->OutMax;
    if(p->Out<p->OutMin)
        p->Out=p->OutMin;
    
    //為下步計算做準備
    p->Out_1=p->Out;
    p->Err_2=p->Err_1;
    p->Err_1=p->Err;
    
}

//===================================================
//End of File
//===================================================

 

amain.c主函數文件

//===================================================
//amain.c文件
//===================================================

//將用戶定義的頭文件包含進來
#include "PID.h"

//=============巨集定義=====================
#define T0 0.0002 //離散化採樣周期,單位s

//============全局變數========================
//定義PID控制器對應的結構體變數
PID ASR=PID_DEFAULTS; //速度PI調節器ASR

//定義PID控制器的參數及輸出限幅值
float SpeedKp=2,SpeedKi=1,SpeedLimit=10; //速度PI調節器ASR

//===============主程式=======================
void main()
{
    //初始化PID控制器
    ASR.Kp=SpeedKp;
    ASR.Ki=SpeedKi;
    ASR.T=T0;
    ASR.OutMax=SpeedLimit;
    ASR.OutMin=-SpeedLimit;
    
}

//============中斷服務程式====================
interrupt void T1UFINT_ISR(void)
{
    //轉速調節ASR
    ASR.Ref=2; //速度給定
    ASR.Fdb=1; //速度反饋
    ASR.Err=ASR.Ref-ASR.Fdb; //偏差
    pid_calc(&ASR); //PI計算(直接函數調用)
    
}

//===================================================
//End of File
//===================================================

 

附件二:使用函數指針進行函數調用僅適用於C

PID.h文件

//===================================================
//PID.h文件
//===================================================
#ifndef PID_H
#define PID_H

//定義PID計算用到的結構體類型
typedef struct
{
    float Ref; //輸入:系統待調節量的給定值
    float Fdb; //輸入:系統待調節量的反饋值
    
    //PID控制器部分
    float Kp; //參數:比例繫數
    float Ki; //參數:積分繫數
    float Kd; //參數:微分繫數
    
    float T; //參數:離散化系統的採樣周期
    
    float a0; //變數:a0
    float a1; //變數: a1
    float a2; //變數: a2
    
    float Err; //變數:當前的偏差e(k)
    float Err_1; //歷史:前一步的偏差e(k-1)
    float Err_2;    //歷史:前前一步的偏差e(k-2)
    
    float Out; //輸出:PID控制器的輸出u(k)
    float Out_1;    //歷史:PID控制器前一步的輸出u(k-1)
    float OutMax; //參數:PID控制器的最大輸出
    float OutMin; //參數:PID控制器的最小輸出
    
    void (*calc)(); //函數指針:指向PID計算函數
    
}PID;

//定義PID控制器的初始值
#define PID_DEFAULTS {0,0, \
                      0,0,0, \
                      0.0002, \
                      0,0,0, \
                      0,0,0, \
                      0,0,0,0, \
                      (void (*)(unsigned long))pid_calc} //加與不加強制類型轉換都沒影響

//條件編譯的判別條件
#define PID_DEBUG 1                     
    
//函數聲明
void pid_calc(PID *p);

#endif

//===================================================
//End of File
//===================================================

 

PID.c文件

//===================================================
//PID.c文件
//===================================================
#include "PID.h"
//===================函數定義========================
/****************************************************
*說    明:
*    (1)PID控制器預設為PI調節器
*    (2)使用了條件編譯進行功能切換:節省計算時間
*        在校正PID參數時,使用巨集定義將PID_DEBUG設為1;
*        當參數校正完成後,使用巨集定義將PID_DEBUG設為0,同時,在初始化時
*    直接為p->a0、p->a1、p->a2賦值
****************************************************/
void pid_calc(PID *p)
{
    //使用條件編譯進行功能切換
    #if (PID_DEBUG)
    float a0,a1,a2;
    //計算中間變數a0、a1、a2
    a0=p->Kp+p->Ki*p->T+p->Kd/p->T;
    a1=p->Kp+2*p->Kd/p->T;
    a2=p->Kd/p->T;
    //計算PID控制器的輸出
    p->Out=p->Out_1+a0*p->Err-a1*p->Err_1+a2*p->Err_2;
    #else
    //計算PID控制器的輸出
    p->Out=p->Out_1+p->a0*p->Err-p->a1*p->Err_1+p->a2*p->Err_2;
    #endif
    
    //輸出限幅
    if(p->Out>p->OutMax)
        p->Out=p->OutMax;
    if(p->Out<p->OutMin)
        p->Out=p->OutMin;
    
    //為下步計算做準備
    p->Out_1=p->Out;
    p->Err_2=p->Err_1;
    p->Err_1=p->Err;
    
}

//===================================================
//End of File
//===================================================

 

amain.c主函數文件

//===================================================
//amain.c文件
//===================================================

//將用戶定義的頭文件包含進來
#include "PID.h"

//=============巨集定義=====================
#define T0 0.0002 //離散化採樣周期,單位s

//============全局變數========================
//定義PID控制器對應的結構體變數
PID ASR=PID_DEFAULTS; //速度PI調節器ASR

//定義PID控制器的參數及輸出限幅值
float SpeedKp=2,SpeedKi=1,SpeedLimit=10; //速度PI調節器ASR

//===============主程式=======================
void main()
{
    //初始化PID控制器
    ASR.Kp=SpeedKp;
    ASR.Ki=SpeedKi;
    ASR.T=T0;
    ASR.OutMax=SpeedLimit;
    ASR.OutMin=-SpeedLimit;
    
}

//============中斷服務程式====================
interrupt void T1UFINT_ISR(void)
{
    //轉速調節ASR
    ASR.Ref=2; //速度給定
    ASR.Fdb=1; //速度反饋
    ASR.Err=ASR.Ref-ASR.Fdb; //偏差
    ASR.calc(&ASR); //PI計算_調用PID計算函數
    
}

//===================================================
//End of File
//===================================================

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前一段時間們需要對一個List<Model>集合去重,情況是該集合中會出現多個Name屬性值相同的,但是其他屬性值不同的數據。 在這種情況下,需求要只保留其中一個就好。 我覺得遍歷和HashSet都不是我想要的,便採用了一下方式 定義Compare類,繼承IEqualityComparer介面 pu
  • 看了《CLR via C#》的17章委托後,為自己做一點淺顯的總結,也分享給需要的人。 .NET通過委托來提供一種回調函數機制,.NET委托提供了很多功能,例如確保回調方法是類型安全的(CLR重要目標)。委托好允許順序調用多個方法(委托鏈),並且支持調用靜態方法和實例方法。 委托的基本語法就不多說了
  • 分類:C#、Android、VS2015; 創建日期:2016-02-07 一、簡介 圖庫(也叫畫廊)是一個佈局小部件,用於在可水平滾動的列表中顯示每一副圖片,當前所選的圖片將置於視圖的中心。 註意:Android已經棄用了這個小部件,棄用的原因是用Galery實現的效率比較低,官方的建議是改為用H
  • 分類:C#、Android、VS2015; 創建日期:2016-02-07 一、簡介 1、CheckBox 覆選 【Checked】屬性:是否選中。 2、RadioButton 單選 【Checked】屬性:是否選中。 【RadioGroup】屬性:RadioButton的分組容器。註意必須將Rad
  • 分類:C#、Android、VS2015; 創建日期:2016-02-07 一、簡介 1、Button 常規按鈕。 2、TextView 文本視圖,其功能和WPF的TextBlock控制項類似,【工具箱】中提供的3個組件實際上是同一個TextView控制項用不同的屬性來區分的,這3個不同的屬性在【工具箱
  • 分類:C#、Android、VS2015; 創建日期:2016-02-06 這一章主要介紹Android簡單控制項的基本用法。本章源程式共有9個示例,這些示例都在同一個項目中。 項目名:ch05demos,項目模板:Blank App(Android) 運行主界面截圖如下: 點擊每行的示例項,即進入對
  • 分類:C#、Android、VS2015;創建日期:2016-02-06 開發人員可以用以下兩種方式聲明UI:一是通過.xml文件(不帶預覽界面)或者.axml文件(帶預覽界面)來描述;二是用C#代碼實現。 用.axml文件描述用戶界面(UI)時,設計器分為【設計】視圖和【源】視圖。這種方式的優點是
  • 問題背景:八皇後問題是一個以國際象棋為背景的問題:如何能夠在 8×8 的國際象棋棋盤上放置八個皇後,使得任何一個皇後都無法直接吃掉其他的皇後。為了達到這個目的, 任兩個皇後都不能處於同一條橫行、縱行或斜線上。 以下的代碼給出的解法應該是最容易理解的一種方法,本問題還可以用回溯法和遞歸解決,理論上效率
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...