[Simulink] MBD开发嵌入式软件的C语言基础

本文转载并改编自董淑成 基于模型的设计 微信公众号,侵删,原文链接 https://mp.weixin.qq.com/s/c4G907PBnyHMI_uMrCmSlw

工作过程中,随着使用Simulink建模的深入,越发觉得C语言的重要性,尤其是将自动生成的代码与底层代码做集成的时,如果不了解C语言,在开发过程中很不方便。

|-引言 —— C语言中.h文件的作用

当前技术背景下,工程化的项目已经没有小到一个文件就可以搞定的了,但凡有点规模的项目,基本上都是模块化开发的。模块化开发的时候,对应每个模块,通常会写两个文件,一个.c,另一个.h,比如module1.c,module1.h
.c文件通常用于定义变量和实现函数,而.h文件,除了定义一些公用的宏和类型之外,还会有下面这样的代码:

      extern uint8 var1;
      extern uint8 var2;
      extern void myfunction(void);

这里 var1、var2是全局变量,而myfunction()是全局函数。

.h文件的主要作用是接口,不同模块之间的接口
举个例子:在module2模块中如果要用到module1里面定义的全局变量或者全局函数,只要在module2.h文件中加这样一样代码:#include “module1.h”。负责module1模块的工程师,如果想调用module2模块提供的全局函数,也只要看一下module2.h即可。

所以老胡经常跟用户强调不要去读自动生成的代码,如果一定要读,也只要读.h文件即可。

我们在.h文件中经常会看到这样一段代码:

#ifndef _MODULE1_H_
#define _MODULE1_H_

然后在.h文件的最末尾有:

#endif

这样的代码在.c文件中没有,请问,这又是什么意思?下面给出答案

|-条件编译的作用

举个例子:如果一个项目有10个模块,module1为其中的一个模块,其他模块在用到module1中定义的全局变量或者全局函数的时候,通常会在自己模块的.h文件中加这样一行代码#include “module1.h”

问题来了:如果10个模块中的9个模块都有这样一行代码,在预编译的时候,就会重复加载很多次module1.h文件。怎么解决这个问题?

可以通过宏_MODULE1_H_来确认是否已经加载过module1.h里的代码,第一次加载的时候,发现没有定义过宏_MODULE1_H_,也就是#ifndef _MODULE1_H_为真,于是定义这个宏,并加载后面的代码,在第二次再遇到#include “module1.h”的时候,已经有_MODULE1_H_这个宏了,#ifndef _MODULE1_H_后面的内容就不再被考虑,直接到#endif。

条件编译,顾名思义,也就是条件成立的时候给后面的内容做编译,条件不成立,则直接跳过。

|-从C到hex/exe

编译,通常我们会说把C编译成hex文件。其实,这种说法是不准确的,当然,你也可以认为这里的编译是广义上的编译,也就是我们经常在各种编译工具中的IDE界面下看到的Build,没错,Build更准确一些(Build是否应该翻译成“构建”?)。
从C到hex的整个Build过程会经历编译链接两个阶段。

编译

编译,就是把C代码转换成目标代码,通常扩展名为.obj或者.o。
目标代码不是最终的二进制码,目标代码中没有代码和变量的地址信息,编译过程中不会提示你某个变量没有定义或者某个变量被多次定义,只要你合理的使用extern关键字。

链接

地址信息是在链接(Link)过程中加上去的,链接器会把编译好的目标代码根据链接文件,把代码和数据分配到合适的存储区域。

|-extern关键字

编译阶段

这个关键字是给编译器看的,编译器遇到这个关键字,就知道这个关键字修饰的那个全局变量或者全局函数不在本文件中定义,而是在其他文件中有定义,所以编译器在编译本文件的时候不会因为没有定义这个全局变量或者全局函数而提示“未定义”之类的错误。

链接阶段

当然,如果到了链接阶段,链接器找遍了所有的目标文件,都找不到你所谓的extern变量的话,那个时候,链接器就会提示undefined reference 了。

|-static变量和全局变量

C语言中static变量和全局变量一样,编译、链接之后会分配固定的RAM地址,不同的是,static变量的作用域仅限于文件级别,你没法让module1中定义的staitc变量被module2模块使用,自然,加了static关键字的变量,也不会在module1.h文件中做extern声明。

另外,static除了可以修饰变量之外,还可以修饰函数。

换言之,static和extern两个关键字不能同时出现。

|- #pragma的作用

翻开C标准,你会发现,#pragma在标准里并没有明确的定义,而是预留给编译器扩展使用,通常,大多数编译器使用如下格式使用:

#pragma mysection begin
Uint8 var1;
Uint16 var2;
#pragma mysection end

需要强调的是这种格式并非标准化的格式,不同的编译器之间可能有所不同,使用前需要查看编译器手册,使用正确的格式

上面的4行代码是什么作用呢?中间两行显然是定义了两个变量var1和var2,前后的两行#pragma是把这两个变量放到了mysection区域,而这个mysection的地址范围是在link文件中定义的。
当然,也可以使用@将变量分配到某一地址下,不过这种做法在Simulink模型中实现起来非常麻烦,不建议使用。

|-宏定义 #define

关于#define,我们在代码中可能会看到这样一些使用方式

	#define MY_MACRO
    #define  K      30
    #define MAX(a,b)  (a>b)?a:b

先说第一种,就是定义了MY_MACRO,MY_MACRO就是一个宏,这个宏就像一个标记,后续代码可以判断是否有这个宏,有的话怎么样,没有的话又怎么样,.h的文件最开始的那段,就是这种用法。
第二种,就是把K定义为30,后续代码中凡是遇到K的地方,编译器编译的时候,就直接替换成30。
第三种,相当于用宏的方式定义了一个函数,跟普通的函数定义不同,在宏定义函数的时候无需定义形参的数据类型,而普通函数的形参是需要定义数据类型的,为什么呢?

在函数中,形参和实参是两个不同的变量,都有自己的作用域,调用时要把实参的值传递给形参;而在带参数的宏中,只是符号的替换,不存在值传递的问题,所以不需要定义形参数据类型

|- 定点数

定点数就是用整数来表达小数,计算机里并没有定点数这种东西,所以我们在定义数据类型的时候,Simulink模型中可能会使用fixdt,而到C代码中,只能看到uint16,uint32等等。定点数中的小数点,是我们假设的,我们会在整数的某个位置假设有一个小数点,那么小数点左边的是整数部分,小数点右边的是小数部分,当然这些计算机并不知道,它依然当成整数运算处理,所以对于编程者来说,需要在运算前后做一些移位对齐小数点的工作,如果你用Simulink模型开发软件,移位对齐工作就不需要你去折腾了,代码生成的时候会被考虑进去。

结个尾

原文作者老胡给MBD开发者的建议是 simulink、stateflow要学,S函数和TLC不要碰,之前不理解,现在很有道理。
举个例子: waijung早期推出基于STM32的MBD开发平台,就是利用一大堆TLC实现底层驱动。之前还觉得有用,现在发现只需要使用STM32CubeMX做底层配置,再加一点自己需要的底层代码,Simulink推出的C Caller完美解决上述问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值