岗位背景:海康威视是全球安防巨头,机器人业务涵盖 AGV/AMR 和巡检机器人。嵌入式软件工程师是其核心岗位,重点考察 RTOS、Linux驱动、STM32、传感器驱动、通信协议(CAN/I2C/SPI/UART) 等方向。以下 10 道题均来自真实面经和笔试真题,覆盖基础原理、底层驱动、RTOS、通信协议和场景应用五大维度。

【基础原理类】

第1题:Cortex-M 系列 MCU 上电启动流程是怎样的?从复位到 main() 函数之间发生了什么?

考察点: 嵌入式底层原理、启动文件理解

高频指数: ⭐⭐⭐⭐⭐

答案解析:

这是海康嵌入式一面出现频率极高的问题。Cortex-M 内核上电复位后,硬件自动完成以下动作,不需要像 ARM7/ARM9 那样靠汇编代码初始化:

第一步:硬件自动完成(无需软件干预)

  • 从 0x00000000 地址读取 初始 MSP(主堆栈指针) 值

  • 从 0x00000004 地址读取 Reset_Handler 入口地址

  • 硬件自动将 MSP 设置为栈顶地址,然后跳转到 Reset_Handler

第二步:Reset_Handler 执行(启动文件 startup_stm32xxxx.s 中)

  • 调用 SystemInit():配置系统时钟、PLL、Flash 预取等

  • 搬运 .data 段:将 Flash 中已初始化的全局/静态变量复制到 RAM 中

  • 清零 .bss 段:将未初始化的全局/静态变量所在 RAM 区域清零

  • 调用 main():跳转到 C 世界

面试官追问: 如果让你检查一个 MCU 是否成功启动了,你会怎么做?

答: 可以点一个 LED,或者在 SystemInit 里面配置一个 GPIO 输出高电平,用示波器看波形;更标准的是看晶振是否起振、CLKOUT 引脚能否测到系统时钟。

海康一面真题原文:"在 Cortex-M 单片机中,从上电复位到最终进入 main 函数执行,整个启动流程涉及哪些关键步骤以及每一步的作用是什么?"

第2题:const、volatile、static 这三个关键字在嵌入式开发中分别有什么用?能否举一个实际的例子?

考察点: C 语言底层功底、嵌入式场景理解

高频指数: ⭐⭐⭐⭐⭐

答案解析:

海康笔试和面试都反复出现这三个关键字,而且特别喜欢让你结合嵌入式场景来回答。

① const(只读修饰)

  • 修饰变量:告诉编译器这个变量不允许被程序修改

  • 典型场景:存放在 Flash 中的查表数据,比如 const uint16_t sine_table[256] = {...};

  • 修饰函数参数:void write_reg(const uint8_t *data) 表示不修改 data 指向的内容

② volatile(可被意外修改)

  • 告诉编译器这个变量可能被"程序之外"的因素改变,禁止编译器优化掉读写操作

  • 三种典型应用场景:

- 硬件寄存器:volatile uint32_t reg = (volatile uint32_t )0x40020000; — 每次读都从真实地址读,每次写都真实写出去

- 中断中修改的全局变量:volatile uint8_t g_irq_flag; — 防止编译器把 flag 优化到寄存器

- RTOS 中多任务共享的变量


// 经典错误示范(如果没有 volatile)
uint8_t flag = 0;
void ISR(void) { flag = 1; }
void main() {
    while (!flag);  // 编译器可能优化成 while(1),永远出不来
}

③ static(作用域/生命周期控制)

  • 修饰局部变量:延长生命周期到整个程序运行期,但作用域不变

- 典型:void counter() { static int count = 0; count++; } — 每次调用 count 都是累加的

  • 修饰全局变量/函数:限定在当前文件内可见,防止命名冲突

- 海康做 AGV 项目时多个.c文件协作,用 static 防止全局变量被外部非法访问

面试官经典追问: const 和 volatile 能同时用吗?

答: 能!比如只读硬件寄存器:const volatile uint32_t status_reg = (const volatile uint32_t )0x40010000;。const 表示程序不能修改它,volatile 表示它可能被硬件改变。这在 STM32 状态寄存器读取中非常常见。


第3题:什么是结构体内存对齐?为什么要对齐?在 STM32 上不同对齐方式会有什么影响?

考察点: C 语言内存布局、嵌入式硬件理解

高频指数: ⭐⭐⭐⭐

答案解析:

这个问题海康笔试经常出,给一个 struct 让你算 sizeof。

为什么需要对齐?

  • CPU 访问对齐数据是一次总线周期,访问非对齐数据可能需要多次读取

  • Cortex-M3/M4 支持非对齐访问但有性能损失,某些外设 DMA 要求数据必须对齐

对齐规则:

  • 每个成员的起始地址必须是其自身大小的整数倍(比如 int32_t 起始地址能被4整除)

  • 整个结构体的大小必须是最宽成员大小的整数倍

  • 编译器会插入 padding 字节


typedef struct {
    uint8_t  a;    // offset 0,占1字节
    // padding 3字节 ← 为了让 b 在4字节对齐地址上
    uint32_t b;    // offset 4,占4字节
    uint16_t c;    // offset 8,占2字节
    // padding 2字节 ← 为了让 struct 整体大小是4的倍数
} __attribute__((packed)) SensorData; 
// sizeof(SensorData) = 12

// 如果不用 packed,优化写法:
typedef struct {
    uint32_t b;    // offset 0
    uint16_t c;    // offset 4
    uint8_t  a;    // offset 6
    // padding 1字节
} SensorDataOpt;   // sizeof = 8,省了4字节!

海康场景: AGV 机器人的 CAN 报文数据结构定义,如果对齐不当,在多个 MCU 间通过 CAN 总线传递结构体指针时,可能因为 padding 不一致导致数据解析错误。

使用 #pragma pack(1) 或 __attribute__((packed)) 的场景:

  • 通信协议中需要严格按字节排列的数据包

  • 写入 Flash/EEPROM 的结构体

  • 代价: packed 后的成员访问可能不是原子的,多任务环境要注意


【底层驱动类】

第4题:用 STM32 的 HAL 库写一个 I2C 读写传感器的驱动,讲讲需要注意哪些坑?

考察点: 传感器驱动开发能力、I2C 协议细节

高频指数: ⭐⭐⭐⭐

答案解析:

海康机器人业务涉及大量传感器(温湿度、IMU、激光雷达、TOF 等),I2C 驱动是基本功。

I2C 基础回顾:

  • 两条线:SCL(时钟线)+ SDA(数据线)

  • 起始信号:SCL 高电平时 SDA 从高→低

  • 停止信号:SCL 高电平时 SDA 从低→高

  • 每次传输 8 位数据 + 1 位 ACK/NACK

  • 7 位地址模式最常用


// STM32 HAL 库读取 AHT20 温湿度传感器示例
#define AHT20_ADDR    0x38

void AHT20_ReadData(float *temp, float *humi) {
    uint8_t cmd[3] = {0xAC, 0x33, 0x00};  // 触发测量指令
    uint8_t buf[6] = {0};
    
    // 发送测量指令
    HAL_I2C_Master_Transmit(&hi2c1, AHT20_ADDR << 1, cmd, 3, 100);
    HAL_Delay(80);  // 等待传感器采样完成,至少75ms
    
    // 读取6字节数据
    HAL_I2C_Master_Receive(&hi2c1, (AHT20_ADDR << 1) | 0x01, buf, 6, 100);
    
    // 解析数据
    uint32_t raw_humi = ((uint32_t)buf[1] << 12) | ((uint32_t)buf[2] << 4) | (buf[3] >> 4);
    uint32_t raw_temp = ((uint32_t)(buf[3] & 0x0F) << 16) | ((uint32_t)buf[4] << 8) | buf[5];
    *humi = raw_humi * 100.0f / (1 << 20);
    *temp = raw_temp * 200.0f / (1 << 20) - 50;
}

实战中容易踩的坑:

  1. 地址左移问题: HAL 库的地址是 7 位原始地址 << 1,因为 STM32 I2C 外设需要 8 位地址(左移 1 位 + R/W 位)。新人容易忘记左移。

  2. 时钟延展(Clock Stretching): 某些从设备会拉低 SCL 表示还没准备好,如果 I2C 超时时间设太短会导致通讯失败。

  3. GPIO 配置: 硬件 I2C 必须配置为复用开漏输出,不要误配为推挽输出,否则会烧坏引脚。

  4. 上拉电阻: I2C 总线需要外部上拉电阻(一般 4.7kΩ),标准模式 100kHz,快速模式 400kHz。

  5. 软件模拟 I2C: 如果硬件 I2C 有问题或者引脚被占用,可以用 GPIO 模拟 I2C 时序,但注意严格按时序要求操作延时。

面试官可能追问: 硬件 I2C 和软件模拟 I2C 各有什么优缺点?

答: 硬件 I2C 不占 CPU,效率高,但引脚固定、调试麻烦(ST 的早期 I2C 硬件有 bug);软件模拟的引脚任意、移植性好,但会占用 CPU 时间。海康 AGV 项目中传感器较多,一般推荐硬件 I2C + DMA 方式。


第5题:STM32 的中断系统是怎样的?中断服务函数(ISR)里能做的事情有哪些限制?如何设计一个高效的中断处理方案?

考察点: 中断体系理解、嵌入式编程规范

高频指数: ⭐⭐⭐⭐

答案解析:

海康面经里多次出现中断相关的问题,而且会结合项目问。

Cortex-M 中断体系:

  • NVIC(嵌套向量中断控制器)统一管理中断

  • 可配置 0~4 位的抢占优先级和子优先级分组

  • 中断向量表存储在 Flash 开头(可通过 SCB->VTOR 重映射)

ISR 中的"十不"原则:

| ❌ 禁止操作 | ✅ 推荐做法 |

| 调用 printf/printf 类函数(重入不安全、耗时) | 置一个标志位,在主循环中处理 |

| malloc/free(非线程安全、延迟不确定) | 用静态分配的缓冲区 |

| 信号量/互斥量的阻塞等待(导致死锁) | 用 xSemaphoreGiveFromISR() 非阻塞版本 |

| 长时间的循环计算(阻塞其他中断) | 只做最低限度的处理 |

| 操作浮点数(硬件 FPU 进 ISR 压栈开销大) | 整数运算,必要时在主循环中处理 |

高效中断设计模式(海康 AGV 电机编码器中断典型场景):


// 顶半部(ISR):极简处理
void TIM_IRQHandler(void) {
    if (TIM_GetITStatus(TIM2, TIM_IT_Update)) {
        g_encoder_pulse++;            // 仅累加计数
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    }
}

// 底半部(主循环或 RTOS 任务中):集中处理
void MotorControlTask(void *param) {
    while (1) {
        uint32_t pulses = g_encoder_pulse;
        g_encoder_pulse = 0;          // 原子读取并清零
        float speed = pulses * CALIB_FACTOR / CONTROL_PERIOD_MS;
        PID_Update(&pid, speed, target_speed);
        vTaskDelay(pdMS_TO_TICKS(CONTROL_PERIOD_MS));
    }
}

追问: 中断嵌套是怎么回事?怎么配置抢占优先级?

答: NVIC 支持中断嵌套——高优先级中断可以打断正在执行的低优先级中断。通过 NVIC_PriorityGroupConfig() 设置优先级分组,比如 NVIC_PriorityGroup_2 表示 2 位抢占优先级 + 2 位子优先级,共 4 级抢占优先级。


第6题:如何用 STM32 的 GPIO 模拟 SPI 协议?SPI 的 4 种工作模式是怎么区分的?

考察点: SPI 协议理解、底层时序模拟能力

高频指数: ⭐⭐⭐

答案解析:

海康机器人项目中,SPI 常用于连接 LCD 屏幕、外部 Flash(W25Q64)、无线模块等。面试官常问 SPI 模式区分和 IO 模拟。

SPI 四线制: SCLK(时钟)、MOSI(主出从入)、MISO(主入从出)、CS(片选)

四种模式由时钟极性(CPOL)和时钟相位(CPHA)决定:

| 模式 | CPOL | CPHA | 说明 |

|---|---|---|---|

| 模式0 | 0 | 0 | 空闲时SCLK=低,第一个跳变沿采样 |

| 模式1 | 0 | 1 | 空闲时SCLK=低,第二个跳变沿采样 |

| 模式2 | 1 | 0 | 空闲时SCLK=高,第一个跳变沿采样 |

| 模式3 | 1 | 1 | 空闲时SCLK=高,第二个跳变沿采样 |

简单记法: CPOL=0 空闲低、CPOL=1 空闲高;CPHA=0 第一个边沿采、CPHA=1 第二个边沿采。最常见的是 模式0 和 模式3。

GPIO 模拟 SPI(模式0):


#define SPI_CLK_HIGH()  HAL_GPIO_WritePin(SPI_CLK_GPIO_Port, SPI_CLK_Pin, GPIO_PIN_SET)
#define SPI_CLK_LOW()   HAL_GPIO_WritePin(SPI_CLK_GPIO_Port, SPI_CLK_Pin, GPIO_PIN_RESET)
#define SPI_MOSI_HIGH() HAL_GPIO_WritePin(SPI_MOSI_GPIO_Port, SPI_MOSI_Pin, GPIO_PIN_SET)
#define SPI_MOSI_LOW()  HAL_GPIO_WritePin(SPI_MOSI_GPIO_Port, SPI_MOSI_Pin, GPIO_PIN_RESET)
#define SPI_MISO_READ() HAL_GPIO_ReadPin(SPI_MISO_GPIO_Port, SPI_MISO_Pin)
#define SPI_CS_LOW()    HAL_GPIO_WritePin(SPI_CS_GPIO_Port, SPI_CS_Pin, GPIO_PIN_RESET)
#define SPI_CS_HIGH()   HAL_GPIO_WritePin(SPI_CS_GPIO_Port, SPI_CS_Pin, GPIO_PIN_SET)

uint8_t SPI_Soft_WriteRead(uint8_t tx_data) {
    uint8_t rx_data = 0;
    for (int i = 7; i >= 0; i--) {
        // MOSI 设置数据位(MSB first)
        if (tx_data & (1 << i)) SPI_MOSI_HIGH();
        else SPI_MOSI_LOW();
        
        SPI_CLK_HIGH();              // 模式0:第一个边沿(上升沿)采样
        // 小延迟(根据需要的频率调整)
        for (volatile int j = 0; j < delay; j++);
        
        if (SPI_MISO_READ()) rx_data |= (1 << i);  // 读取 MISO
        
        SPI_CLK_LOW();
        for (volatile int j = 0; j < delay; j++);
    }
    return rx_data;
}

关键点: IO 模拟 SPI 要注意时序延迟不可太短(确保从设备能跟上),CS 片选要在传输开始前拉低、结束后拉高。速率一般建议不要超过 1MHz(用 IO 模拟)。


【RTOS 类】

第7题:FreeRTOS 的任务调度机制是怎样的?在 Cortex-M3/M4 上,任务切换具体是怎么通过 PendSV 异常实现的?

考察点: RTOS 内核理解深度

高频指数: ⭐⭐⭐⭐⭐

答案解析:

海康一面频繁出现这个问题,回答深度直接决定面试官对你「到底有没有真正用过 RTOS」的判断。

FreeRTOS 调度策略:

  • 抢占式调度(默认): 系统总是执行就绪态中优先级最高的任务。每次 SysTick 中断或任务主动阻塞时触发调度

  • 时间片轮转: 同优先级任务之间按时间片轮流执行(configUSE_TIME_SLICING 使能)

  • 合作式调度: 任务主动让出 CPU(不常用)

Cortex-M 上的任务切换机制(核心!):

整个切换依赖三个异常:

  1. SysTick(系统滴答定时器): 周期性产生中断,在中断服务函数 xPortSysTickHandler() 中检查是否有更高优先级的任务需要执行

  2. PendSV(可挂起的系统调用异常): 这是真正的"调度执行者"

  3. SVC(系统服务调用): 用于从任务中主动触发调度(比如 taskYIELD())

PendSV 切换过程详解:

// 伪代码:xPortPendSVHandler() 的核心逻辑
void PendSV_Handler(void) {
    // 步骤1:获取当前任务控制块
    pxCurrentTCB = (TCB_t *)pvTaskGetCurrentTCB();
    
    // 步骤2:保存当前任务的上下文
    // [硬件自动保存] R0-R3, R12, LR, PC, xPSR → 压入当前 PSP 指向的栈
    // [手动保存] R4-R11 → 由 PendSV 处理函数中的 push {r4-r11} 完成
    
    // 步骤3:将更新后的栈指针保存到 TCB
    pxCurrentTCB->pxTopOfStack = psp;
    
    // 步骤4:调用 vTaskSwitchContext() 选择下一个要运行的任务
    vTaskSwitchContext();  // 这会更新 pxCurrentTCB
    
    // 步骤5:恢复新任务的上下文
    psp = pxCurrentTCB->pxTopOfStack;
    // [手动恢复] pop {r4-r11}
    // [硬件自动恢复] R0-R3, R12, LR, PC, xPSR
}

面试官想要听到的关键点:

  • 硬件自动压栈和手动压栈的区分——这体现了对 Cortex-M 体系的理解

  • PendSV 被设置为最低优先级——保证 PendSV 在所有 ISR 完成后才执行,避免调度器和中断处理冲突

  • PSP(进程栈指针) 和 MSP(主栈指针) 的区分——任务用 PSP,异常处理用 MSP


第8题:FreeRTOS 中的优先级反转(Priority Inversion)是什么?互斥信号量如何通过优先级继承解决这个问题?

考察点: RTOS 同步机制理解、底层原理掌握

高频指数: ⭐⭐⭐⭐

答案解析:

海康机器人项目中多个任务共享资源(如共享 CAN 总线、共享 LCD 显示),优先级反转是必问的经典问题。

什么是优先级反转?

最简单的场景需要三个任务:高优先级任务 H、中优先级任务 M、低优先级任务 L。

  1. L 先获得了一个互斥锁(持有共享资源)

  2. H 来了,需要同一个互斥锁,被阻塞,让出 CPU

  3. 此时 L 恢复执行,但 M 抢占了 L(M 不需要这个锁)

  4. H 在等 L 释放锁,L 在等 M 让出 CPU → H 被 M 间接阻塞了

这就是优先级反转——高优先级任务 H 被一个更低优先级的中等任务 M 拖住,系统实时性崩溃。

FreeRTOS 的解决方案:优先级继承

互斥信号量(Mutex)和二值信号量(Binary Semaphore)不同——Mutex 实现了优先级继承:

  • 当高优先级任务 H 因互斥锁被 L 持有而阻塞时,L 的优先级临时提升到 H 的优先级

  • L 获得 CPU 控制权,快速执行完临界区并释放锁

  • L 的优先级恢复原状,H 拿到锁继续执行


// FreeRTOS 互斥信号量使用
SemaphoreHandle_t xMutex;

void TaskLow(void *pvParams) {
    while (1) {
        xSemaphoreTake(xMutex, portMAX_DELAY);  // L 拿到锁
        // 临界区代码(访问共享资源)
        xSemaphoreGive(xMutex);                 // 释放锁
    }
}

void TaskHigh(void *pvParams) {
    while (1) {
        xSemaphoreTake(xMutex, portMAX_DELAY);  // H 被阻塞时触发优先级继承
        // 使用共享资源
        xSemaphoreGive(xMutex);
    }
}

底层实现: FreeRTOS 的 Mutex 内部有一个 xMutexHolder 字段记录当前持有互斥锁的任务,当检查到有更高优先级的任务在等待时,临时提升持有者的优先级。

追问: 二值信号量和互斥信号量的区别?

| 区别 | 二值信号量 | 互斥信号量 |

| 优先级继承 | 没有 | 有 |

| 使用场景 | 同步(Task A 通知 Task B) | 互斥(共享资源保护) |

| 递归获取 | 不支持 | 支持(xSemaphoreCreateRecursiveMutex) |

| 中断中释放 | 可以用 xSemaphoreGiveFromISR | 不推荐,Mutex 涉及优先级信息更新 |


第9题:FreeRTOS 的临界区保护有哪几种方式?taskENTER_CRITICAL 和挂起调度器有什么区别?各自适用什么场景?

考察点: RTOS 资源保护机制、实用经验

高频指数: ⭐⭐⭐

答案解析:

三种临界区保护方式:

① taskENTER_CRITICAL() / taskEXIT_CRITICAL()

  • 原理:关闭所有可屏蔽中断(除了 configMAX_SYSCALL_INTERRUPT_PRIORITY 以上的中断)

  • 特点:全局关中断,任何 ISR 都不会触发,调度器自然也不工作

  • 适用:保护极短的代码段(不超过几十条指令),比如对全局变量做原子读-改-写

  • 缺点:影响系统实时性,长时间关中断可能导致错过外部事件

void Motor_SetSpeed(int16_t speed) {
    taskENTER_CRITICAL();
    g_motor_pwm = speed;          // 短操作,关中断保护
    TIM_SetCompare1(TIM1, g_motor_pwm);
    taskEXIT_CRITICAL();
}

② vTaskSuspendAll() / xTaskResumeAll()

  • 原理:只挂起调度器,不关中断!ISR 仍然可以执行

  • 特点:中断仍能响应,但 ISR 中的 xSemaphoreGiveFromISR() 等操作不会触发立即调度

  • 适用:保护中等长度的代码段,比如遍历链表、批量数据处理

  • 优点:不影响中断响应延迟

void Process_Sensor_Data(void) {
    vTaskSuspendAll();  // 挂起调度器,但不关中断
    // 可以批量处理传感器数据,中断仍能响应
    for (int i = 0; i < SENSOR_COUNT; i++) {
        sensors[i].avg = (sensors[i].val + sensors[i].last_val) / 2;
    }
    xTaskResumeAll();   // 恢复调度器(在 ISR 中调用的 Give 此时才生效)
}

③ 互斥信号量

  • 适用:保护任意长度的代码段,任务可以阻塞等待

  • 注意:不能在 ISR 中使用 xSemaphoreTake()(阻塞操作)

对比总结:

| 方式 | 关中断? | 影响 ISR? | 代码段长度 | ISR 中可用? |

| taskENTER_CRITICAL | ✅ 是 | ✅ 影响 | 极短 | ✅ 有 FromISR 版本 |

| vTaskSuspendAll | ❌ 不关 | ❌ 不影响 | 中等 | ❌ 不行 |

| Mutex/Semaphore | ❌ 不关 | ❌ 不影响 | 任意长度 | ❌ 不能 Take |


【通信协议类】

第10题:CAN 总线在 AGV/AMR 机器人中怎么用?CAN 报文的数据帧格式是怎样的?如果两个节点同时发送数据,总线仲裁是怎么决定的?

考察点: CAN 总线理解、机器人通信场景

高频指数: ⭐⭐⭐⭐

答案解析:

海康机器人的 AGV/AMR 产品中,CAN 总线是底盘电机控制和传感器采集的核心通信方式。这道题在海康二面中出现频率很高。

CAN 在 AGV 中的应用场景:

  • 底盘电机控制:主控通过 CAN 向电机驱动器发送速度指令(如 ID=0x201 发送目标转速)

  • 编码器反馈:驱动器通过 CAN 上报当前位置和速度(如 ID=0x202)

  • 电池管理 BMS:通过 CAN 上报电量、电压、温度

  • 超声波/红外传感器阵列:通过 CAN 传输障碍物距离数据

CAN 标准数据帧格式(2.0A):

| SOF | 仲裁段(12bit) | 控制段(6bit) | 数据段(0-8 Byte) | CRC(15bit) | ACK(2bit) | EOF(7bit) |
|  1   |  11 ID + 1RTR  |  4DLC + 2r   |     0~8 Byte       |   15+1    |  1+1     |    7     |

  • SOF(Start of Frame): 1 位显性位,同步所有节点

  • 仲裁段: 11 位 ID(标准帧)+ 1 位 RTR(远程帧标志)

  • 控制段: 4 位 DLC(数据长度)+ 2 位保留

  • 数据段: 0~8 字节

  • ACK: 发送节点发隐性位,接收节点发显性位应答

  • EOF: 7 个隐性位

总线仲裁机制(核心亮点!):

CAN 总线采用 CSMA/CR(载波监听多点接入/冲突解决)机制:

  1. 所有节点在发送前监听总线是否空闲

  2. 空闲时同时发送,每个位显性(0)覆盖隐性(1)

  3. ID 越小优先级越高——在仲裁段逐位比较,谁先发出显性位谁就赢得仲裁

  4. 输的节点自动转为接收模式,不会损坏数据

通俗解释: 就像一群人同时说话,谁的 ID "声音大"(显性位多)谁就能继续说,其他人自动闭嘴等下次机会。

STM32 CAN 配置示例(500kbps):

CAN_HandleTypeDef hcan;

void CAN_Init(void) {
    hcan.Instance = CAN1;
    hcan.Init.Prescaler = 6;                // 36MHz/(6*(1+11+4)) = 500Kbps
    hcan.Init.Mode = CAN_MODE_NORMAL;
    hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;
    hcan.Init.TimeSeg1 = CAN_BS1_11TQ;      // 时间段1
    hcan.Init.TimeSeg2 = CAN_BS2_4TQ;       // 时间段2
    hcan.Init.AutoBusOff = ENABLE;          // 自动恢复总线
    hcan.Init.AutoWakeUp = DISABLE;
    HAL_CAN_Init(&hcan);
    
    // 配置滤波器:只接收 ID=0x201 的数据
    CAN_FilterTypeDef filter;
    filter.FilterBank = 0;
    filter.FilterMode = CAN_FILTERMODE_IDMASK;
    filter.FilterScale = CAN_FILTERSCALE_32BIT;
    filter.FilterIdHigh = 0x0000;
    filter.FilterIdLow = 0x0000;
    filter.FilterMaskIdHigh = 0x0000;      // 不限制
    filter.FilterMaskIdLow = 0x0000;
    filter.FilterFIFOAssignment = CAN_RX_FIFO0;
    filter.FilterActivation = ENABLE;
    HAL_CAN_ConfigFilter(&hcan, &filter);
}

// 发送 CAN 报文
void CAN_SendMotorCmd(uint16_t speed) {
    CAN_TxHeaderTypeDef tx_header;
    uint8_t tx_data[8] = {0};
    
    tx_header.StdId = 0x201;               // 电机控制报文ID
    tx_header.ExtId = 0;
    tx_header.IDE = CAN_ID_STD;            // 标准帧
    tx_header.RTR = CAN_RTR_DATA;          // 数据帧
    tx_header.DLC = 8;                     // 8字节数据
    tx_header.TransmitGlobalTime = DISABLE;
    
    tx_data[0] = (speed >> 8) & 0xFF;      // 速度高字节
    tx_data[1] = speed & 0xFF;             // 速度低字节
    
    uint32_t tx_mailbox;
    HAL_CAN_AddTxMessage(&hcan, &tx_header, tx_data, &tx_mailbox);
}

面试官追问: AGV 上 CAN 总线波特率一般设置多少?

答: 工业 AGV 通常用 250kbps 或 500kbps。250kbps 抗干扰性强、通信距离远(适合大厂房);500kbps 数据量大(适合通信负载高的场景)。海康巡检机器人用 500kbps 比较常见。


写在最后

海康威视机器人嵌入式岗位面试的整体特点:

  1. 项目深挖: 面试官会非常仔细地询问你简历上项目的架构设计、模块分工、遇到的具体问题和解决方案。建议用 STAR 法则梳理项目。

  2. C 语言功底要求高: 指针、内存、关键字(const/volatile/static)几乎是必考,会出代码阅读题让你分析输出。

  3. RTOS 原理要扎实: 不能只停留在 API 调用层面,调度机制、任务切换、同步互斥的底层实现都要理解。

  4. 通信协议要吃透: CAN/I2C/SPI/UART 四种协议的时序、帧格式、应用场景要能说清楚。

  5. 场景化问题: 面试官喜欢问"你在 AGV 项目中如果用 xxx 方案,会有什么问题?"这种开放性问题,考察工程思维和解决问题的能力。

祝面试顺利,拿下海康 offer!🚀

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐