TA的每日心情 | 开心 2022-12-27 15:07 |
|---|
签到天数: 1 天 [LV.1]初来乍到
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
每个C程序都有一个main()函数。当然,有可能编写一个成功的程序,其中唯一的功能是main()。我的猜测是已经做过很多次了,确实在某些简单的应用程序中不需要其他功能。* S, u. _) ^. E7 h! p S4 z
' ?1 r& I' s! m( v9 k但是,功能的广泛使用表明编写代码的人是经验丰富的固件开发人员。为什么?因为函数使我们能够更快地编写更好的代码,从而减少工作量并减少错误。对于那些花费大量时间编写固件的人来说,这些优势是不容忽视的。即使我们起初因为似乎需要更多工作而拒绝使用功能,但经验逐渐告诉我们,好处远大于成本。5 O. X) `% Z( @( t. p4 `6 V) x
# G2 G7 |7 v' a6 v2 b) ^! K) c
什么是功能?, _& t1 H% W( o/ V6 M! ^2 e
2 o3 [" I6 X9 ZAC功能是一组指令,这些指令一起工作以实现特定类型的处理器活动。在许多情况下,某个功能会执行一项特定任务,例如从SPI缓冲区中检索数据,配置计时器以使其产生指定的延迟,或者从存储器中读取值并将其加载到DAC寄存器中。, B& Z" ?3 U6 m3 W5 M0 c
但是,当然没有法律说明某个功能只能执行一项任务。您可能会发现拥有一个更新三个不相关状态机的函数很方便,或者您可以编写一个通过UART发送一个字节,然后检查状态位直到接收到一个字节,然后将接收到的字节的值合并到一个函数中的函数。数学计算。
6 r$ H e( S9 K) o6 }! n4 y M
% ~5 I/ y# ?2 h6 `! }- \- x3 v7 A" R9 Y) j' `, S- s8 c
我喜欢函数的一件事是,它们使流程图和代码之间可以进行相当直接的转换。% B4 E p8 y# h' [
功能的“组成部分”函数由名称,输入参数列表,实现所需功能的代码语句以及返回类型组成。1 r2 o8 F* n3 u* V* a) [! F. _
以下代码段为您提供了一个示例。
5 ?# n: \4 r% W9 A/ {6 X% G# z' i6 D
4 `8 S) F1 l( A! c
" u! T7 k: b, e [, ^6 `4 T5 v
6 _$ M( D& U; @% _5 |- char Convert_to_Lowercase(char UppercaseLetter)
- {
- if(UppercaseLetter < 65 || UppercaseLetter > 90)
- return 0x00;
- else
- return (UppercaseLetter + 32);
- }
1 j9 P+ O; Y x9 Q6 a! x( n- k" } U
0 R" n$ |) f- ?/ y: W7 g
) ~' W* U4 ]9 ]/ E7 e复制代码
; F* k# ?3 a; B; c& N0 b# e B! S$ t" t" i9 F* f
) _- y' ]! M9 f/ `- q8 ^
我喜欢使我的函数名称具有很强的描述性。这使代码更具可读性,并有助于您保持思想井井有条。& x1 s$ V5 x# @: o- x
# k) b6 Q6 q' K, d+ H C这些说明放在大括号中;函数定义的这一部分称为函数的主体。“ return”关键字用于退出函数并标识哪些数据应传递到代码的先前执行部分。& c- n/ ]' j B" Q7 C/ g! a
返回类型位于函数名称之前,用于标识将要返回的信息的数据类型。具有仅执行任务而无需返回数据的功能是完全可以接受的。在这种情况下,您将使用关键字“ void”代替数据类型。+ d" ~# T9 o& @! C2 P% n- f
) O: y4 \" `7 L( _& R: x将数据传递给函数/ M- n1 |4 s) t& ]0 H0 b
输入参数(也称为自变量)用括号括起来并放在函数名称之后。AC函数可以有多个参数,在这种情况下,它们用逗号分隔。每个参数必须带有数据类型。- U# E# m+ f; A. p0 E
在嵌入式应用程序中,通常不需要使用参数。我可以想到两个原因。
& e: x3 d/ I0 q3 _* T' Z5 S0 B) T首先,嵌入式固件经常直接与设备的硬件交互,因此功能可以从配置寄存器,通信寄存器或端口引脚中获取所需的信息。
$ C I% A5 c. a+ {0 W5 `其次,为微控制器编写的简单C程序可以使用全局变量,即整个程序中存在的变量,并且可以由任何函数访问。据我了解,不鼓励在应用程序编程中使用全局变量,或者甚至“定罪”将是合适的词。但是我认为许多固件项目,尤其是完全由一个程序员编写的固件项目,都可以从全局变量的简单性中受益。
6 R- F- E0 ^9 ^0 R# d. R3 Z定义不带参数的函数时,可以将括号留空或插入关键字“ void”。从理论上讲,“空”方法比空括号要好,但是在嵌入式开发的背景下(尤其是考虑到现代编译器的智能程度),我不知道它到底有多重要。
7 L- p' K E) W2 w* s" H: ~: R) |* N9 {. p
演练" B4 g+ W8 B- J5 t$ }( }- r
让我们简单地检查一下上面显示的函数定义。9 l7 j; i# f2 T: [9 U) t
- 函数名称Convert_to_Lowercase清楚地表明了该函数的用途:它接受与大写ASCII字母相对应的八位值,并返回与该字母的小写版本相对应的八位值。
- 有一个输入参数。它具有char数据类型,并使用描述性标识符。
- 与输入参数一样,返回值是ASCII字符,因此返回类型为char。
- 如果输入值超出了与大写ASCII字母相对应的范围,则该函数返回0x00,表示错误。否则,它将32与输入值相加并返回总和。如果您不熟悉ASCII值,下表将帮助您了解为什么我使用数字65、90和32。
- r+ W S( B2 q$ s4 u8 U( j 7 S8 C3 T$ h" O' m. @/ q% o
+ K b- _% e i7 _1 M( [* l3 W
* W" A9 A. }7 Q
硬件功能
9 C$ x( `) C1 f) W+ ?: t* g正如处理器的数据存储器不直接支持附加到C 变量的详细信息一样,处理器的代码存储器比C函数要简单得多。代码存储器是很长的存储位置序列,没有以任何有用的方式对其进行分类或组织。唯一标识代码存储器中特定位置的是地址。" H Y5 p. A( g, ~' z# x3 W2 `
因此,AC函数是一种精心设计且对程序员友好的方法,用于将代码块放置在内存中并将处理器定向到需要执行的块。如果您使用汇编语言,那么您将熟悉代码执行的底层现实:每条指令都有一个地址。我们使用文本标签表示给定的地址,如果我们希望处理器在该地址执行指令,我们告诉它跳转到标签。
/ V0 q; ]- x# oC函数是对汇编语言中使用的基本子例程的重大改进,但它们在本质上没有什么不同。当您调用或“调用”一个函数时,处理器的程序计数器将接收与该函数关联的第一条机器语言指令的地址。
) a2 X X' d9 R2 V' C
- v/ i9 J6 N6 H1 Y7 i$ V/ V调用堆栈3 t' u; m) j9 A& s& Z& c4 m
内存的一部分称为调用堆栈,用于存储函数执行后处理器应返回到的代码存储地址。堆栈还为局部变量(即在调用函数时创建并仅在函数内使用的变量)提供了存储位置。
/ t0 t, r9 s- s7 p) X
- c4 ]5 p U- c
6 H# c5 \# h/ o4 k% G: P; @1 B调用堆栈的示例。请注意,该图没有指出每个项目需要多少字节。" p B2 F8 T) W0 F6 r1 p9 @5 K
调用堆栈可以用作存储作为输入参数传递给函数的数据的地方,但是据我了解,编译器将在可能的情况下为此目的而不是内存使用寄存器(因为寄存器更快)。
$ ]2 g6 B+ [% }; n
* R+ ~" d& X( ~9 ^% e- c' t0 b& y. o结论
" K% s8 G* _- c我希望本文能为您提供有关C语言程序和处理器实际硬件中函数的结构和行为的良好概述。在下一篇文章中,我们将继续讨论C函数。. q5 o. w: L# t- p
4 ~4 E; U9 ?; ~% W* m& r# Z. J |
|