面试真题 | 奥比中光[20241215]

@[toc]

1、自我介绍

2、pwm输入捕获定时器怎么工作的

在嵌入式系统中,PWM(脉冲宽度调制)输入捕获定时器是一种重要的外设,它能够测量外部PWM信号的频率、占空比等参数。以下是PWM输入捕获定时器的工作原理的详细解释:

PWM输入捕获定时器的工作原理

  1. 基本结构

    • 每个高级定时器和通用定时器都拥有输入捕获功能,这些定时器包含输入捕获通道,这些通道可以配置为PWM输入模式(PWMI)。
    • 在PWM输入模式下,输入信号与两个捕获通道连接。这两个通道分别捕获PWM信号的上升沿和下降沿,从而测量PWM的周期和占空比。
  2. 捕获过程

    • 当外部PWM信号的边沿(如上升沿或下降沿)触发定时器的捕获通道时,定时器的当前计数值(CNT)会被捕获并存储到捕获比较寄存器(CCR)中。
    • 对于PWM信号的周期测量,可以选择一个捕获通道在PWM信号的上升沿捕获计数值,然后选择另一个捕获通道在下一个上升沿捕获计数值。两个计数值之间的差值即代表PWM信号的周期。
    • 对于占空比的测量,可以在一个PWM周期内,分别捕获上升沿和下降沿的计数值。上升沿到下降沿的计数值代表高电平周期,而整个周期的计数值代表PWM信号的周期。占空比即为高电平周期与周期的比值。
  3. 定时器配置

    • 为了正确捕获PWM信号,需要对定时器进行配置。包括选择时钟源、设置预分频器、配置捕获通道等。
    • 时钟源的选择决定了定时器的计数频率。预分频器可以对时钟源进行分频,从而降低计数频率。
    • 捕获通道的配置包括选择捕获边沿(上升沿、下降沿或两者都捕获)、配置滤波器等。
  4. 中断与事件

    • 当捕获事件发生时,定时器可以产生中断或事件标志。这允许软件在捕获到PWM信号的边沿时执行特定的操作。
    • 通过配置中断优先级和中断服务程序,可以实现对捕获事件的及时处理。

面试官可能追问的深入问题

  1. 如何选择合适的预分频器和计数周期以测量特定频率范围的PWM信号?

    • 这个问题涉及到定时器的配置和PWM信号的特性。需要根据待测PWM信号的频率范围、定时器的时钟频率以及所需的测量精度来选择合适的预分频器和计数周期。
  2. 在测量PWM信号的占空比时,如何确保测量的准确性?

    • 这个问题涉及到捕获通道的配置和信号的稳定性。需要确保捕获通道能够准确捕获PWM信号的上升沿和下降沿,并且需要考虑到信号噪声和抖动对测量的影响。可以采取滤波措施来减少噪声干扰,并选择合适的捕获边沿来确保测量的准确性。
  3. 在使用PWM输入捕获定时器时,如何避免溢出和错误捕获?

    • 这个问题涉及到定时器的计数范围和捕获通道的灵敏度。需要确保定时器的计数范围足够大,以容纳待测PWM信号的周期。同时,需要合理配置捕获通道的灵敏度和滤波器,以避免因信号抖动或噪声而引起的错误捕获。此外,还需要注意定时器的溢出问题,确保在测量过程中不会发生溢出。
  4. 能否结合具体的硬件平台(如STM32)来描述PWM输入捕获定时器的配置和使用方法?

    • 这个问题要求将理论知识与实际应用相结合。可以结合具体的硬件平台(如STM32)来描述PWM输入捕获定时器的配置和使用方法。包括如何配置定时器的时钟源、预分频器、捕获通道等,以及如何编写中断服务程序来处理捕获事件。
  5. 在使用PWM输入捕获定时器时,如何考虑功耗和实时性要求?

    • 这个问题涉及到嵌入式系统的整体设计。在使用PWM输入捕获定时器时,需要考虑功耗和实时性要求。可以通过选择合适的时钟源和预分频器来降低功耗,并通过优化中断服务程序和任务调度来满足实时性要求。同时,还需要考虑系统的整体功耗预算和实时性要求,以确保设计的合理性和可行性。

3、虚拟地址和物理地址

在嵌入式系统中,虚拟地址和物理地址是两个非常重要的概念,它们之间的关系和计算对于理解内存管理和系统架构至关重要。

  1. 虚拟地址与物理地址的定义

    • 虚拟地址:是程序运行时使用的地址,也称为逻辑地址或虚拟内存地址。它是由操作系统和硬件(如MMU,内存管理单元)共同管理,用于提供一个比实际物理内存大得多的地址空间,从而允许程序在比实际物理内存小的虚拟空间中运行。
    • 物理地址:是实际硬件内存单元的地址,也称为实地址或硬件地址。它是内存单元在物理内存中的确切位置。
  2. 它们之间的关系

    • 虚拟地址通过操作系统的内存管理机制映射到物理地址。这种映射允许操作系统在多个进程之间动态地分配和重定位内存,从而提高了内存使用的灵活性和安全性。
    • 映射过程中,操作系统会维护一个或多个映射表(如页表),这些表存储了虚拟地址到物理地址的转换信息。
  3. 计算

    • 在现代处理器中,虚拟地址到物理地址的转换通常是由硬件(如MMU)自动完成的。当处理器访问一个虚拟地址时,它会查找相应的映射表,将虚拟地址转换为物理地址,然后访问物理内存。
    • 转换过程可能涉及分页(paging)和分段(segmentation)等技术。分页将虚拟内存划分为固定大小的页(page),每个页映射到物理内存的一个或多个页框(page frame)。分段则将虚拟内存划分为不同大小的段(segment),每个段有自己的基地址和长度。
  4. 嵌入式系统中的应用

    • 在嵌入式系统中,由于资源有限,虚拟地址和物理地址的映射可能更加直接和简单。然而,随着嵌入式系统变得越来越复杂,一些高级功能(如多任务处理、内存保护等)也开始使用虚拟地址和物理地址的映射机制。
    • 嵌入式系统设计师需要仔细考虑内存布局和映射策略,以确保系统的性能和可靠性。

面试官追问

  1. 分页和分段的区别是什么?在嵌入式系统中,哪种机制更常用?

    • 分页和分段的主要区别在于它们划分虚拟内存的方式和映射的粒度。分页将虚拟内存划分为固定大小的页,而分段则划分为不同大小的段。在嵌入式系统中,由于资源有限和性能要求,分页机制可能更常用,因为它提供了更灵活和高效的内存管理。
  2. 在嵌入式系统中,如果虚拟地址空间超过了物理内存大小,会发生什么?操作系统如何处理这种情况?

    • 如果虚拟地址空间超过了物理内存大小,操作系统会使用虚拟内存技术(如分页和交换)来管理内存。当程序尝试访问一个不在物理内存中的虚拟地址时,操作系统会触发页面错误(page fault),并将相应的页面从磁盘(或其他存储介质)加载到物理内存中。如果物理内存已满,操作系统可能会选择将某个页面交换出去(即写入磁盘),以便为新的页面腾出空间。
  3. 在嵌入式系统中,如何确保虚拟地址到物理地址的映射是高效且可靠的?

    • 在嵌入式系统中,确保虚拟地址到物理地址的映射高效且可靠的关键在于优化映射表的存储和访问策略。例如,可以使用哈希表或快速查找算法来加速映射表的查找过程。此外,还需要对映射表进行定期的检查和维护,以确保其正确性和一致性。同时,嵌入式系统设计师还需要考虑如何平衡内存使用效率和系统性能之间的关系。

4、C语言的staic、const、extern、strlen、32位计算机结构体对齐sizeof

static

定义static关键字在C语言中有多种用途,主要包括:

  1. 修饰局部变量:延长局部变量的生命周期,使其在整个程序运行期间都存在,但作用域不变,仍只在定义它的代码块内有效。
  2. 修饰全局变量:限制全局变量的作用域,使其只在定义它的文件内可见,避免命名冲突。
  3. 修饰函数:限制函数的作用域,使其只在定义它的文件内可见。

示例

void foo() {
    static int count = 0; // 静态局部变量,只初始化一次
    count++;
    printf("%d\n", count);
}

const

定义const关键字用于定义常量,即一旦赋值后不能再被修改的值。它可以修饰变量、数组、指针等。

示例

const int MAX = 100; // 常量
const char *str = "Hello"; // 指向常量的指针,指针可变,指向的内容不可变
char *const ptr = "World"; // 常量指针,指针不可变,指向的内容可变(但这里指向的是字符串常量,实际上不可变)
const char arr[] = "Const Array"; // 常量数组,数组内容不可变

extern

定义extern关键字用于声明一个变量或函数是在别的文件中定义的,即告诉编译器该符号在其他地方已经定义,需要链接时使用。

示例

// 在file1.c中定义全局变量
int globalVar = 10;

// 在file2.c中声明并使用全局变量
extern int globalVar;
void printGlobalVar() {
    printf("%d\n", globalVar);
}

strlen

定义strlen是标准库函数,用于计算字符串的长度(不包括终止符\0)。

示例

#include <string.h>
#include <stdio.h>

int main() {
    char str[] = "Hello";
    printf("Length of string: %lu\n", strlen(str));
    return 0;
}

注意strlen计算的是字节数,不是字符数(对于多字节字符集需要注意)。

32位计算机结构体对齐sizeof

定义:在32位计算机上,结构体(struct)的对齐是为了提高内存访问效率,编译器会在结构体成员之间插入填充字节(padding),使得每个成员的内存地址都是其类型的倍数。

示例

#include <stdio.h>

struct Example {
    char a; // 1字节
    int b;  // 4字节,需要3字节填充
    short c; // 2字节,需要2字节填充以对齐到4字节边界(如果考虑结构体整体对齐)
};

int main() {
    printf("Size of struct Example: %lu\n", sizeof(struct Example));
    return 0;
}

在32位系统上,假设结构体整体对齐为4字节,上述结构体的大小可能是8字节(1 + 3填充 + 4 + 2 + 2填充)。

追问

  1. 关于static

    • 如果在一个函数内部定义了一个static变量,并且这个函数被多次调用,这个变量的值会如何变化?
    • static全局变量和extern全局变量在链接时的区别是什么?
  2. 关于const

    • const指针和指向const的指针有什么区别?
    • 如果将const变量赋值给非const变量,会发生什么?
  3. 关于extern

    • 如果在多个文件中都声明了同一个extern全局变量,但定义的值不同,链接时会发生什么?
    • extern "C"在C++中有什么作用?
  4. 关于strlen

    • strlen函数的时间复杂度是多少?
    • 如果传入strlen的指针指向的内存区域没有以\0结尾,会发生什么?
  5. 关于结构体对齐

    • 如何通过编译器指令(如#pragma pack)改变结构体的对齐方式?
    • 结构体对齐对性能的影响主要体现在哪些方面?

5、C++的多态、重载

多态(Polymorphism)

定义:多态性是面向对象编程(OOP)中的一个核心概念,它允许不同类的对象通过相同的接口调用不同的方法。在C++中,多态性通常通过继承和虚函数来实现。

实现方式

  1. 基于继承的多态:通过基类指针或引用来调用派生类中的重写方法。

    class Base {
    public:
        virtual void show() {
            cout << "Base class show function" << endl;
        }
    };
    
    class Derived : public Base {
    public:
        void show() override {
            cout << "Derived class show function" << endl;
        }
    };
    
    int main() {
        Base* basePtr;
        Derived derivedObj;
        basePtr = &derivedObj;
        basePtr->show(); // 调用的是Derived类的show方法
        return 0;
    }
    
  2. 基于函数重载和模板的多态(虽然这不是传统意义上的多态,但在某些情况下可以模拟多态行为):

    • 函数重载:允许在同一个作用域内定义多个同名函数,但这些函数的参数列表必须不同。

      void print(int i) {
          cout << "Integer: " << i << endl;
      }
      
      void print(double d) {
          cout << "Double: " << d << endl;
      }
      
    • 模板:允许编写与类型无关的代码,编译器在编译时会自动根据实参类型生成相应的函数或类。

      template <typename T>
      void print(T t) {
          cout << t << endl;
      }
      

注意:多态性要求基类中的函数必须是虚函数(使用virtual关键字),且派生类中对该函数进行了重写(使用override关键字,C++11及以后)。

重载(Overloading)

定义:函数重载是指在同一作用域内,可以声明多个具有相同名称但参数列表不同的函数。参数列表的不同可以是参数的类型、数量或顺序。

示例

class MathUtils {
public:
    int add(int a, int b) {
        return a + b;
    }

    double add(double a, double b) {
        return a + b;
    }

    int add(int a, int b, int c) {
        return a + b + c;
    }
};

在这个例子中,MathUtils类有三个名为add的函数,但它们的参数列表不同,因此可以共存。

面试官追问

  1. 关于多态

    • 虚函数表(vtable)是如何实现的?它是如何支持多态性的?
      • 答案:虚函数表是一个指向函数指针数组的指针,每个类都有一个自己的虚函数表。当通过基类指针或引用调用虚函数时,编译器会查找该指针所指向的虚函数表,并根据对象实际的类型找到正确的函数指针进行调用。
    • 虚析构函数的作用是什么?为什么需要它?
      • 答案:虚析构函数确保当通过基类指针删除派生类对象时,能够调用到派生类的析构函数,从而正确释放资源。如果没有虚析构函数,可能会导致资源泄漏或未定义行为。
  2. 关于重载

    • 函数重载是如何实现的?编译器如何区分同名函数?
      • 答案:函数重载是通过函数名称修饰(name mangling)来实现的。编译器会根据函数的参数列表生成一个唯一的函数标识符,从而区分同名但参数不同的函数。
    • 构造函数可以被重载吗?如果可以,有什么注意事项?
      • 答案:是的,构造函数可以被重载。每个构造函数都必须有独特的参数列表。在重载构造函数时,需要确保每个构造函数都能正确地初始化对象的状态,并避免重复或冲突的代码。

6、堆和栈

在嵌入式面试中,堆和栈是内存管理方面的核心概念,理解它们的区

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

ARM/Linux嵌入式真题 文章被收录于专栏

让实战与真题助你offer满天飞!!! 每周更新!!! 励志做最全ARM/Linux嵌入式面试必考必会的题库。 励志讲清每一个知识点,找到每个问题最好的答案。 让你学懂,掌握,融会贯通。 因为技术知识工作中也会用到,所以踏实学习哦!!!

全部评论

相关推荐

11-26 02:19
已编辑
门头沟学院 Java
美团 后端开发 n*15.5+5w签字费
点赞 评论 收藏
分享
评论
点赞
收藏
分享
牛客网
牛客企业服务