從2019年12月27到2020年2月12日,學習了Simulink仿真及代碼生成技術入門到精通,歷時17天。
學習的比較粗糙,有一些地方還沒理解透徹,全書梳理總結:
- Simulink的基礎模塊已基本掌握,對不熟悉的模塊可以借助幫助文檔了解其功能;
- Simulink信號基本掌握,了解了各種信號的外觀及意義的不同;
- 對Simulink子系統的認識有待深入,對原子子系統需要進一步熟悉;
- 對仿真過程及參數配置有所了解,對Debugger的應用不太熟悉;
- 對回調函數有所認識,能夠簡單應用到參數預加載等場合;
- 關於M語言對Simulink的操作有待進一步熟悉;
- Simulink流控制有待進一步熟悉;
- S函數掌握得不好;
- 對模塊的封裝有所認識,能夠應用到子系統外觀繪制等場合;
- 自定義庫、自定義環境估計暫且用不到;
- 對代碼生成前的配置有了一定的認識,對各種優化代碼生成的技巧需要進一步練習;
- 對TLC語言有初步的入門。
在學習過程中一直采用打字記錄的方法,其間一周由QQ拼音統計的結果如下,好處在於在仔細地輸入過程中對書中內容有了更多的思考時間,對參數配置都過了一遍,而不是走馬觀花,以后用到之時即使記不起具體內容,應該也會記得我在哪里曾經輸入過,方便回來查找。壞處在於花費了較多時間,且博客照搬書中文字,缺少自己的思考和創新,在以后的學習中,應該簡要記錄重點內容,加之自己的思考和描述。
全書共19章,最后一章由於沒有下載到相應的庫和工具,暫且放過。其他1~18章鏈接如下。在打字過程中難免疏漏,存在一些錯誤,希望日后查閱時能夠逐步改正,如果這些博客有幸為其他小伙伴所瀏覽,也歡迎評論指出錯誤之處。
Simulink仿真入門到精通(一) Simulink界面介紹
Simulink仿真入門到精通(四) Simulink子系統
Simulink仿真入門到精通(五) Simulink模型的仿真
Simulink仿真入門到精通(六) Simulink模型保存為圖片
Simulink仿真入門到精通(七) Simulink的回調函數
Simulink仿真入門到精通(八) M語言對Simulink模型的自動化操作及配置
Simulink仿真入門到精通(九) Simulink的流控制
Simulink仿真入門到精通(十二) Publish發布M文件
Simulink仿真入門到精通(十三) Simulink創建自定義庫
Simulink仿真入門到精通(十四) Simulink自定義環境
Simulink仿真入門到精通(十五) Simulink在流程工業中的仿真應用
Simulink仿真入門到精通(十六) Simulink基於模型設計的工業應用概述
Simulink仿真入門到精通(十七) Simulink代碼生成技術詳解
學完本書,決定完成一個代碼生成的練習,以低通濾波器為例,以C51做載體,原因在於C51比較簡單,資源較少,我比較熟悉,另外C51可以借助Keil、Proteus等工具方便地進行仿真,驗證其正確性。
下面將介紹我的練習過程。
注:MATLAB版本2018a,Keil V5.24,Proteus8.6。
1. Simulink模型
1.1 模型外觀
說明:
- 為了配合C51的數據類型,輸入輸出均設置為uint16,故需要進行數據類型轉換;
- LPF(Low Pass Filter)內部結構及輸出表達式如圖所示;
- 增益參數設為g,並在Model Properties→Callbacks→PreLoadFcn中設置回調函數為g=0.05;以便與在生成的代碼中對增益進行修改;
- 在Mask Editor的Icon drawing commands中添加一下繪圖命令,繪制低通濾波器的大致形狀,使模塊美觀。
num=1; den=[1,1]; ts=tf(num,den); P=bodeoptions; [mag,~,w]=bode(ts,P); w=reshape(w,1,length(w)); mag=reshape(mag,1,length(w)); dB=20*log10(mag); w=log10(w); plot(w,dB);
傳遞函數G(s)=1/(s+1),使用bode獲取幅頻特性並繪圖。
補:本想選用DSP System Toolbox→Filtering→Filter Implementations中的Analog Filter Design作為低通濾波器。但此模塊階數(Order)較高時,計算比較復雜,可能導致C51的RAM空間不足,此處只是為了驗證代碼生成的過程,故選用比較簡單的模型。
1.2 模型配置
Solver Type:Fixed-step
Solver:discrete(no continuous states)
生成嵌入式代碼必須采用固定步長,由於沒有連續環節,故可選用離散解算器。
Device:Intel
Device type:8051 Compatible
注:MATLAB2018a中沒有此選項,但可以在低版本MATLAB中建立模型,選擇此項,再將模型在2018a中打開即可。
System target file:ert.tlc
Language:C
Generate code only:√
Default parameter behavior:Tunable
Create code generation report:√
Open report automatically:√
生成報告並自動打開。
Generate an example main program:□
自己編寫main主函數,不需生成示例。
其他參數保持默認。
1.3 模型仿真
在Simulink中建立如下模型。
輸入信號由正弦信號和正態隨機數疊加構成。
仿真時長30s,步長0.01s,得到仿真結果如下圖所示。
可以看到,模型具有較好的濾波效果。
1.4 代碼生成
配置好模型參數后,選定好工作目錄,按下Ctrl+B,啟動代碼生成。
得到Report如下圖。
在LPF_data中可以看到g的定義,在此處可進行修改。
LPF_step函數為核心函數。
void LPF_step(void) { real_T rtb_Add1; real_T tmp; /* Sum: '<S1>/Add1' incorporates: * DataTypeConversion: '<Root>/Data Type Conversion' * Gain: '<S1>/Gain' * Inport: '<Root>/In1' * Sum: '<S1>/Add' * UnitDelay: '<S1>/Unit Delay' */ rtb_Add1 = ((real_T)LPF_U.In1 - LPF_DW.UnitDelay_DSTATE) * LPF_P.g + LPF_DW.UnitDelay_DSTATE; /* DataTypeConversion: '<Root>/Data Type Conversion1' */ tmp = floor(rtb_Add1); if (rtIsNaN(tmp) || rtIsInf(tmp)) { tmp = 0.0; } else { tmp = fmod(tmp, 65536.0); } /* Outport: '<Root>/Out1' incorporates: * DataTypeConversion: '<Root>/Data Type Conversion1' */ LPF_Y.Out1 = tmp < 0.0 ? (uint16_T)-(int16_T)(uint16_T)-tmp : (uint16_T)tmp; /* Update for UnitDelay: '<S1>/Unit Delay' */ LPF_DW.UnitDelay_DSTATE = rtb_Add1; }
2. Proteus硬件連接
2.1 帶噪聲的正弦信號發生器
由於Proteus中沒有直接產生白噪聲的元件,在這里采用基於EPROM的波形發生方式。首先采用外部函數將白噪聲數據寫入txt文件,在轉換為二進制數據加載到27256中,得到噪聲信號。
比如我采用R語言生成這些數據,R代碼如下。
#生成波形數據部分 noise<-rnorm(500,mean=36*3.5,sd=36) noise<-round(noise) noise_H<-as.character(as.hexmode(noise)) #寫入文件部分 write.table(noise_H,file='C:/Users/lenovo/Desktop/noise_H.txt', row.names =FALSE,col.names =FALSE, quote =FALSE)
由於正態分布在3倍標准差范圍內的概率已經達到99.74%,因此此處選3.5足夠。得到數據文件如下:
利用從網上下載的轉換工具進行轉換:(感謝這位博主)
https://download.csdn.net/download/mouseleoz/10905646
即得到可加載的bin文件。
555定時器產生周期性信號,74LS161構成16進制計數器,噪聲數據即在27256的D0到D7端口輸出。
2.2 A/D轉換
將上面的輸入信號封裝為子電路圖,總電路圖如下。
利用11通道12位串行A/D轉換芯片TLC2543進行A/D轉換,TLC2543引腳說明如下。
2.3 D/A轉換
利用DAC0832進行D/A轉化,由於0832是8位D/A轉換器,因此損失了部分精度。

3. Keil工程
3.1 main函數
#include<reg52.h> #include "LPF.h" #include "LPF_private.h" #define uint unsigned int #define uchar unsigned char #define y P2 uint volt; uchar addr; sbit CLK=P1^7;//定義時鍾信號口 sbit DIN=P1^6;//定義2543數據寫入口 sbit DOUT=P1^5;//定義2543數據讀取口 sbit CS=P1^4;//定義2543片選信號口 sbit P2_5=P2^5; void read2543(uchar addr) { uint ad=0; uchar i; CLK=0; CS=0;//片選段,啟動2543 addr<<=4;//對地址位預處理 for(i=0;i<12;i++) //12個時鍾走完,完成一次讀取測量 { if(DOUT==1) ad=ad|0x01;//單片機讀取ad數據 DIN=addr&0x80;//2543讀取測量地址位 CLK=1; ;;;//很短的延時 CLK=0;//產生下降沿,產生時鍾信號 ;;; addr<<=1; ad<<=1;//將數據移位准備下一位的讀寫 } CS=1;//關2543 ad>>=1; volt=ad;//取走轉換結果 } void main() { addr=0; LPF_initialize(); TMOD=0x01; TH0=0xD8; TL0=0xF0; TR0=1; EA=1; ET0=1; while(1) { read2543(addr); } } void Timer0_ISR(void) interrupt 1 { TH0=0xD8; TL0=0xF0; LPF_U.In1=volt; LPF_step(); y=LPF_Y.Out1/16; }
在while(1)中不斷讀取A/D轉換結果,在定時器中固定步長執行LPF_step函數。定時器初值D8F0=55536=2^12-10000,故步長為0.01s。
由於讀取的轉換結果是12位數據,因此除以16轉換為8位,同時損失了精度。
3.2 編譯方法
將Simulink生成的.c和.h文件加入到工程下,啟動編譯。
編譯無誤即可得到hex文件,將其加載到單片機中,啟動仿真,得到示波器結果如下所示。
可以看到,該電路完成了濾波任務。
4. 模型評價總結
該實例僅是為了練習Simulink生成代碼的過程,實際效果並不理想,主要缺點有:
- 中間經過A/D和D/A轉換,損失了精度,得到的結果是離散的;
- 算法導致輸出比輸入有一定的相位滯后。
如圖,放大后即可看到其離散性,實際效果甚至不如一階RC濾波器(有源或無源)。
在這個過程中對Proteus的總線創建、標號添加及子電路圖繪制方法進行了回顧。
對C51的端口賦值方法進行了復習,#define y P2。
在建模過程中參考了以下網頁,對這些博主表示感謝。
Simulink生成代碼在C51中的用法:https://blog.csdn.net/weixin_41911709/article/details/90648818
TLC2543的用法:https://blog.csdn.net/nanfeibuyi/article/details/80564741
通過此練習,起碼簡單驗證了Simulink生成C代碼的可行性,其他學習有待以后深入。
總之,本書由淺入深,還是非常值得一讀,錯誤也比較少,即使偶爾有拼寫錯誤也不影響閱讀。
我的模型文件打包如下:
鏈接:https://pan.baidu.com/s/1iedYzuQbcezU4_-fW2s6tw
提取碼:k3fj