Simulink仿真入门到精通(十九) 总结回顾&自我练习
从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转换器,因此损失了部分精度。
![](https://img2018.cnblogs.com/i-beta/1752446/202002/1752446-20200213105017184-918917197.png)
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