本篇文章记录我在智能车竞赛中,对 Infineon_TC264 这款芯片的底层库函数的学习分析。通过深入地对其库函数进行分析,C语言深入的知识得以再次在编程中呈现和运用。故觉得很有必要在此进行记录一下。
目录
编辑 一、代码段
1、枚举类型
2、结构体
3、看门狗
4、inline关键字
为什么使用inline
inline的限制和注意事项
5、ADC分辨率
6、__attribute__
7、GCC
8、枚举型数组
(1)枚举型数组的定义和初始化
(2)枚举型数组的用法
(3)示例:使用枚举型数组处理LED状态
一、代码段
1、枚举类型
typedef enum //定义了一个名为 IfxCpu_Id 的枚举类型。用户自定义数据类型,增强器可读性
{IfxCpu_Id_0 = 0, /**< \brief CPU 0 ,在枚举类型中,如果没有指定值,则第一个枚举成员默认为0,之后的每一个美剧成员的值依次增加*/IfxCpu_Id_1 = 1, /**< \brief CPU 1 */IfxCpu_Id_none /**< \brief None of the CPU */
} IfxCpu_Id;
这段代码是用C语言编写的,它定义了一个名为
IfxCpu_Id
的枚举类型。枚举(Enumeration)是一种用户定义的数据类型,它允许程序员为整数类型分配更具有可读性的名称,使代码更加清晰易懂。在这个特定的例子中,
IfxCpu_Id
枚举被用来表示不同的 CPU 标识符。让我们逐个看看每个枚举成员及其含义:
IfxCpu_Id_0 = 0
:这个成员代表 CPU 0。它被显式地赋值为 0。在枚举中,如果没有指定值,第一个枚举成员默认值是 0,之后的每个枚举成员的值依次递增。
IfxCpu_Id_1 = 1
:这个成员代表 CPU 1。它被显式地赋值为 1。这里的赋值其实是可选的,因为按照默认规则,如果IfxCpu_Id_0
被赋值为 0,IfxCpu_Id_1
将自动被赋值为 1。但在这里显式指定可以增强代码的可读性和明确性。
IfxCpu_Id_none
:这个成员表示没有任何 CPU 被选中或者指定的操作不涉及特定的 CPU。注意到这个成员没有被显式地赋予一个特定的值。在枚举中,如果前一个成员的值是 1,那么IfxCpu_Id_none
将自动被赋值为 2。这种自动赋值机制使得在枚举中添加新成员变得非常灵活,无需手动调整其他成员的值。
2、结构体
void get_clock (void)
{// 获取时钟频率,便于查看当前系统运行频率g_AppCpu0.info.pllFreq = IfxScuCcu_getPllFrequency();g_AppCpu0.info.cpuFreq = IfxScuCcu_getCpuFrequency(IfxCpu_getCoreIndex());g_AppCpu0.info.sysFreq = IfxScuCcu_getSpbFrequency();g_AppCpu0.info.stmFreq = IfxStm_getFrequency(&MODULE_STM0);
}
代码片段中,
g_AppCpu0.info
是一个全局变量,这里的.info
指示它是一个结构体或者是一个包含结构体的更大结构体的成员。这个结构体至少包含了以下几个成员:
pllFreq
:用于存储PLL(Phase-Locked Loop,锁相环)频率的成员。这个成员的数据类型应该能够存储频率值,通常是一个整数或浮点数。
cpuFreq
:用于存储CPU频率的成员。同样,其数据类型应该是能够表示频率的整数或浮点数。
sysFreq
:用于存储系统外设总线(SPB,System Peripheral Bus)频率的成员。其数据类型很可能与pllFreq
和cpuFreq
相同。
stmFreq
:用于存储系统定时管理器(STM,System Timer Module)频率的成员。与其他频率成员类似,其数据类型应该能够存储频率值。结构体通常用于将逻辑上相关的数据组织在一起,便于管理和访问。在这个例子中,
g_AppCpu0.info
结构体显然用于存储与系统频率相关的信息。这样的设计使得程序的其他部分可以方便地访问这些信息,而不必关心这些信息是如何获取的。虽然代码片段没有直接提供这个结构体的定义,但我们可以根据上下文推断出它的大致结构。一个可能的定义如下(使用C语言):
typedef struct {float pllFreq; // PLL频率float cpuFreq; // CPU频率float sysFreq; // 系统外设总线频率float stmFreq; // 系统定时管理器频率 } SystemInfo;typedef struct {SystemInfo info; // 系统信息// 可能还有其他成员 } AppCpu;AppCpu g_AppCpu0; // 全局变量
3、看门狗
void IfxScuWdt_disableCpuWatchdog(uint16 password)
{/* Select CPU Watchdog based on Core Id */uint32 coreId = (uint32)IfxCpu_getCoreIndex();Ifx_SCU_WDTCPU *wdt = &MODULE_SCU.WDTCPU[coreId];IfxScuWdt_clearCpuEndinitInline(wdt, password);wdt->CON1.B.DR = 1; //Set DR bit in Config_1 registerIfxScuWdt_setCpuEndinitInline(wdt, password);
}void IfxScuWdt_disableSafetyWatchdog(uint16 password)
{IfxScuWdt_clearSafetyEndinitInline(password);SCU_WDTS_CON1.B.DR = 1; //Set DR bit in Config_1 registerIfxScuWdt_setSafetyEndinitInline(password);
}void IfxScuWdt_enableCpuWatchdog(uint16 password)
{/* Select CPU Watchdog based on Core Id */uint32 coreId = (uint32)IfxCpu_getCoreIndex();Ifx_SCU_WDTCPU *wdt = &MODULE_SCU.WDTCPU[coreId];IfxScuWdt_clearCpuEndinitInline(wdt, password);wdt->CON1.B.DR = 0; //Clear DR bit in Config_1 registerIfxScuWdt_setCpuEndinitInline(wdt, password);
}void IfxScuWdt_enableSafetyWatchdog(uint16 password)
{IfxScuWdt_clearSafetyEndinitInline(password);SCU_WDTS_CON1.B.DR = 0; //Clear DR bit in Config_1 registerIfxScuWdt_setSafetyEndinitInline(password);
}
看门狗(Watchdog Timer,简称WDT)是一种在计算机硬件或软件中常见的机制,用于检测和恢复系统挂起(例如,由于软件错误导致的无限循环)的情况。它的基本工作原理是计时器持续倒计时,软件必须定期重置(或“喂狗”)这个计时器,以防止它达到零。如果因为程序错误或其他原因导致软件未能在规定时间内重置看门狗,计时器将到达零,看门狗便会采取措施,通常是重置系统,以恢复正常操作。
看门狗的用途
系统恢复:看门狗最主要的功能是在系统运行出现异常时自动进行系统重启,尝试恢复系统正常运行。这对于需要高可靠性的嵌入式系统尤其重要,例如医疗设备、工业控制系统等。
错误检测:看门狗可以帮助检测系统中的错误状态。一些高级的看门狗定时器还可以提供错误日志或错误位置的记录功能,帮助开发人员定位问题。
防止系统卡死:在没有看门狗的情况下,系统可能因为软件缺陷、硬件故障或外部干扰而卡死。看门狗确保了即使在这种情况下,系统也能够自我恢复。
增强系统安全性:在一些安全敏感的应用中,看门狗可以防止恶意软件长时间控制系统。通过强制系统定期重启,可以减少恶意软件造成持久性损害的机会。
看门狗的类型
硬件看门狗:硬件看门狗是由微控制器或专用看门狗芯片实现的,独立于主程序运行。它们通常更可靠,因为即使主程序完全崩溃,硬件看门狗仍然可以重置系统。
软件看门狗:软件看门狗是在主程序中实现的,通常通过操作系统提供的服务来实现。虽然软件看门狗的灵活性更高,但它们依赖于操作系统的稳定性,如果操作系统崩溃,软件看门狗可能就无法正常工作。
结论
看门狗是提高系统可靠性和安全性的重要工具,尤其在嵌入式系统设计中扮演着关键角色。通过确保系统能够在遇到无法从软件层面恢复的错误时自动重启,看门狗帮助确保系统能够尽可能长时间地正常运行。
这些函数是用于控制Infineon微控制器上CPU看门狗定时器和安全看门狗定时器的启用和禁用的。它们通过修改特定的寄存器来实现对看门狗定时器的控制。下面是对每个函数的详细分析:
IfxScuWdt_disableCpuWatchdog(uint16 password)
此函数的目的是禁用CPU看门狗定时器。它首先获取当前CPU核心的索引,然后根据这个索引选择相应的CPU看门狗定时器实例。接着,使用提供的密码暂时解除
Endinit
保护(这是一种防止关键系统设置被意外或恶意更改的保护机制),修改CON1
寄存器的DR
位为1(这会禁用看门狗定时器),最后重新设置Endinit
保护。
IfxScuWdt_disableSafetyWatchdog(uint16 password)
此函数禁用安全看门狗定时器。与CPU看门狗定时器不同,安全看门狗通常用于整个系统的安全监控。该函数使用提供的密码清除
Endinit
保护,设置SCU_WDTS_CON1
寄存器的DR
位为1以禁用安全看门狗定时器,然后重新设置Endinit
保护。
IfxScuWdt_enableCpuWatchdog(uint16 password)
此函数启用CPU看门狗定时器。它的工作流程与禁用函数类似,但是在修改
CON1
寄存器时,将DR
位清零,这会启用看门狗定时器。
IfxScuWdt_enableSafetyWatchdog(uint16 password)
此函数启用安全看门狗定时器。它清除
Endinit
保护,将SCU_WDTS_CON1
寄存器的DR
位清零以启用看门狗定时器,然后重新设置Endinit
保护。密码和
Endinit
保护所有这些函数都使用了一个名为
password
的参数来处理Endinit
保护。Endinit
保护是Infineon微控制器提供的一种机制,旨在保护关键寄存器不被意外或恶意更改。要修改受保护的寄存器,首先需要使用正确的密码清除Endinit
保护,完成修改后再重新设置保护。这增加了系统的安全性。结构体访问
这些函数通过结构体和位字段访问和修改寄存器。例如,
wdt->CON1.B.DR
语句访问了CON1
寄存器的B
位字段中的DR
位。这种方式使得代码更易于阅读和维护,同时也隐藏了底层硬件的复杂性。总之,这些函数提供了对Infineon微控制器上看门狗定时器的基本控制,允许开发者根据需要启用或禁用这些重要的系统监视工具。
4、inline关键字
inline void ADC_Init(void)
{adc_init(ADC0_CH7_A7, ADC_12BIT); // 初始化对应 ADC 通道为对应精度
}
inline
是C和C++语言中的一个关键字,用于建议编译器对特定的函数进行内联展开,而不是按照常规的函数调用机制进行调用。当一个函数被声明为inline
时,编译器会尝试将该函数的所有调用替换为函数本体的副本。这意味着在每个调用点,编译器会插入整个函数的代码,从而避免了函数调用的开销(如参数传递、栈帧的创建和销毁等)。为什么使用
inline
性能提升:内联函数可以减少函数调用的开销,对于小型且频繁调用的函数,这可能会带来性能上的提升。
避免函数调用开销:由于没有函数调用的额外开销,使用
inline
可能会使得程序运行更快。
inline
的限制和注意事项
编译器的自由度:值得注意的是,
inline
只是一个建议,最终是否将函数内联,以及如何内联,取决于编译器的优化策略。现代编译器通常会对代码进行深入分析,并自动决定哪些函数适合内联,即使它们没有被显式地声明为inline
。代码膨胀:过度使用内联可能会导致代码大小增加,因为函数体的副本会在每个调用点插入。这可能会导致指令缓存命中率降低,从而影响性能,特别是在嵌入式系统或内存受限的环境中。
递归和大函数:递归函数或较大的函数通常不适合内联,因为这可能会导致栈溢出或代码膨胀。
5、ADC分辨率
typedef enum // 枚举ADC通道
{// ADC0可选引脚ADC0_CH0_A0 = 0*16 + 0,ADC0_CH1_A1,ADC0_CH2_A2,ADC0_CH3_A3,ADC0_CH4_A4,ADC0_CH5_A5,ADC0_CH6_A6,ADC0_CH7_A7,ADC0_CH8_A8,ADC0_CH10_A10 = 0*16 + 10,ADC0_CH11_A11,ADC0_CH12_A12,ADC0_CH13_A13,// ADC1可选引脚ADC1_CH0_A16 = 1*16 + 0,ADC1_CH1_A17 = 1*16 + 1,ADC1_CH4_A20 = 1*16 + 4,ADC1_CH5_A21 = 1*16 + 5,ADC1_CH8_A24 = 1*16 + 8,ADC1_CH9_A25 = 1*16 + 9,// ADC2可选引脚ADC2_CH3_A35 = 2*16 + 3,ADC2_CH4_A36,ADC2_CH5_A37,ADC2_CH6_A38,ADC2_CH7_A39,ADC2_CH10_A44 = 2*16 + 10,ADC2_CH11_A45,ADC2_CH12_A46,ADC2_CH13_A47,ADC2_CH14_A48,ADC2_CH15_A49,
}adc_channel_enum;// 此枚举定义不允许用户修改
typedef enum // 枚举ADC通道
{ADC_8BIT, // 8位分辨率ADC_10BIT, // 10位分辨率ADC_12BIT, // 12位分辨率
}adc_resolution_enum;
在电子和信号处理领域中,分辨率是指系统能够区分或表示的最小单位或细节级别。对于模数转换器(ADC)来说,分辨率通常是指数字信号中能够区分的最小幅度或最小变化量。
具体来说,在ADC中,分辨率通常以位数来表示,比如8位、10位或12位等。这些位数表示了ADC输出的数字信号可以代表的离散级别数量。例如:
- 8位ADC:具有(2^8 = 256)个不同的离散级别,即能够将模拟输入信号转换为0到255之间的数字。
- 10位ADC:具有(2^{10} = 1024)个不同的离散级别,即能够将模拟输入信号转换为0到1023之间的数字。
- 12位ADC:具有(2^{12} = 4096)个不同的离散级别,即能够将模拟输入信号转换为0到4095之间的数字。
更高的分辨率意味着ADC能够更准确地表示输入信号的细微变化,因为它可以区分更多的离散级别。通常情况下,更高的分辨率也意味着更高的精度,但同时也会带来更高的数据传输和处理成本。
因此,当谈论ADC的分辨率时,我们通常指的是ADC能够将模拟输入信号转换为数字形式时的精确度和能力,以及数字输出的位数和表示范围。
6、__attribute__
#define IFX_ALIGN(n) __attribute__ ((__align(n)))
__attribute__
是 GCC (GNU Compiler Collection) 提供的一种功能强大的语言扩展,它允许程序员为函数、变量以及类型声明指定特殊的属性。这些属性可以用来控制编译器如何优化代码、如何对齐内存,或者提供关于代码特性的附加信息,从而提高程序的性能或可靠性。上面的代码是一个宏定义,用于简化
__attribute__((__align(n)))
属性的使用。这个属性指示编译器按照指定的字节对齐变量或数据结构。n
应该是2的幂,并且是目标处理器支持的对齐方式。
如果希望某个结构体在内存中按照32字节对齐,可以这样做 :
struct IFX_ALIGN(32) my_struct {int a;char b;// 其他成员 };
这里的
IFX_ALIGN(32)
宏会展开为__attribute__((__align(32)))
,告诉编译器这个结构体的实例需要按照32字节对齐。这种对齐通常用于提高内存访问的效率,尤其是在需要频繁访问大量数据的高性能计算中非常有用。需要注意的是,
__attribute__
是GCC特有的扩展,不是标准C或C++的一部分。因此,如果您的代码需要在不同的编译器上移植,可能需要考虑使用条件编译或其他机制来确保兼容性。
7、GCC
GCC,全称GNU Compiler Collection(GNU编译器套件),是一套由GNU项目开发的编程语言编译器。它是自由软件基金会(Free Software Foundation, FSF)的关键项目之一,最初由理查德·斯托曼(Richard Stallman)在1985年发起,旨在为GNU操作系统提供一个完全自由的编译器。GCC最初仅支持C语言编译,但随着时间的推移,它已经扩展到支持多种编程语言,包括C++、Objective-C、Fortran、Ada、Go和D等。
GCC是跨平台的,可以在多种操作系统上运行,包括Linux、macOS、Windows等,同时支持多种处理器架构。它使用GPL(GNU通用公共许可证)发布,意味着任何人都可以自由地使用、修改和分发GCC,只要他们遵守GPL的条款。
GCC的特点和优势包括:
跨平台和多语言支持:GCC可以在多种操作系统上编译多种编程语言,使其成为开发跨平台应用的强大工具。
优化能力:GCC提供了广泛的优化选项,允许开发者针对不同的应用场景和硬件配置调整代码以提高性能。
可移植性:GCC生成的代码具有良好的可移植性,能够在不同的硬件和操作系统环境中运行。
开源和自由:作为自由软件,GCC的源代码可供任何人查看和修改,这促进了其不断的改进和发展。
广泛的应用:GCC被广泛应用于学术界、工业界和开源社区,是许多操作系统和嵌入式系统开发的首选编译器。
强大的社区支持:由于GCC是开源项目,它拥有一个活跃的社区,社区成员贡献代码、修复bug并提供支持。
8、枚举型数组
枚举(Enumeration)是一种在C语言中用于定义常量的用户自定义数据类型,它使得代码更加易读易潜。枚举型数组是指其元素类型为枚举类型的数组。这种数组可以有效地用于表示一系列固定选项或状态的集合,使得代码的逻辑更清晰,维护更简便。
(1)枚举型数组的定义和初始化
首先,定义一个枚举类型:
typedef enum {RED,GREEN,BLUE,YELLOW
} Color;
然后,你可以定义一个这种枚举类型的数组,并进行初始化
Color colorArray[4] = {RED, GREEN, BLUE, YELLOW};
这里,
colorArray
是一个包含4个元素的数组,每个元素都是Color
枚举类型,分别被初始化为RED
、GREEN
、BLUE
和YELLOW
。
(2)枚举型数组的用法
状态机: 枚举型数组经常用于实现状态机,其中数组的每个元素代表一个可能的状态。
配置选项: 如果你有一组固定的配置选项,可以使用枚举型数组来存储当前配置。
映射值: 在需要将整数值映射到特定的标签或名称时,可以使用枚举型数组。例如,你可以根据枚举值索引数组来获取对应的字符串表示。
(3)示例:使用枚举型数组处理LED状态
假设你正在编写一个程序来控制不同颜色的LED灯,你可以使用枚举型数组来简化这个过程。
首先,定义一个枚举来表示LED的状态:
typedef enum {LED_OFF,LED_ON
} LEDState;
然后,如果你有多个LED灯,可以定义一个枚举型数组来存储每个LED的状态:
LEDState leds[4] = {LED_OFF, LED_OFF, LED_OFF, LED_OFF};
接下来,你可以根据需要更改数组中特定LED的状态:
// 打开第一个LED
leds[0] = LED_ON;// 关闭第三个LED
leds[2] = LED_OFF;
通过使用枚举型数组,你的代码更加清晰和易于理解,同时也减少了错误的可能性。
总之,枚举型数组是处理一组固定选项或状态的强大工具,它提高了代码的可读性和可维护性。
注:上面的代码之所以可以直接设定LED_ON和LED_OF,是因为美剧类型的变量在没有赋初值的时候,默认是从 0开始进行递增的。