当前位置:首页>Linux>嵌入式Linux机器人开发:线程条件变量与环形缓冲区的实战场景.

嵌入式Linux机器人开发:线程条件变量与环形缓冲区的实战场景.

  • 2026-09-22 12:57:10
嵌入式Linux机器人开发:线程条件变量与环形缓冲区的实战场景.

从单片机思维到多线程RTOS,ROS机器人底层通信的卡死场景复现。

🤖 问题现场:当机器人"卡住"的那一刻

调试现场实录:

机器人:正在过弯道...激光雷达:每秒产生 5000 个点云数据IMU:每毫秒产生 6 轴数据电机控制器:需要每 5ms 接收一次控制指令突发!机器人撞墙了!串口日志:[ERROR] 电机控制超时!50ms未收到指令![ERROR] 激光雷达数据丢失!环形缓冲区溢出![INFO] 开始原地转圈...

问题根源:常见的while(1)轮询方式,在多传感器环境下彻底崩溃!

第一部分:机器人传感器数据采集(条件变量实战)

1.1 激光雷达数据采集系统

#include<pthread.h>#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h>#include<sys/time.h>#include<fcntl.h>#include<termios.h>#define LIDAR_POINTS_PER_SCAN 360   // 360度激光雷达,每度一个点#define LIDAR_DATA_QUEUE_SIZE 10      // 缓存10帧点云#define LIDAR_BAUDRATE B230400        // 激光雷达串口波特率#define IMU_SAMPLE_RATE 1000          // IMU 1KHz采样#define MOTOR_CONTROL_INTERVAL 5000    // 5ms控制周期(微秒)// 激光雷达点云数据结构typedefstruct {uint32_t timestamp;         // 时间戳(微妙级)uint16_t distance[LIDAR_POINTS_PER_SCAN];  // 距离数据(mm)uint8_t intensity[LIDAR_POINTS_PER_SCAN];  // 反射强度uint8_t quality[LIDAR_POINTS_PER_SCAN];    // 数据质量} lidar_frame_t;// IMU数据结构typedefstruct {uint32_t timestamp;float accel[3];    // 加速度计 x,y,z (m/s²)float gyro[3];     // 陀螺仪 x,y,z (rad/s)float mag[3];      // 磁力计 x,y,z (uT)float temperature; // 温度 (°C)} imu_data_t;// 多传感器同步队列typedefstruct {lidar_frame_t lidar_frames[LIDAR_DATA_QUEUE_SIZE];int lidar_head;int lidar_tail;int lidar_count;imu_data_t imu_buffer[IMU_SAMPLE_RATE * 2];  // 2秒IMU数据缓存int imu_write_pos;int imu_read_pos;pthread_mutex_t mutex;pthread_cond_t lidar_data_ready;   // 激光雷达数据就绪pthread_cond_t imu_data_ready;      // IMU数据就绪pthread_cond_t buffer_not_full;     // 缓冲区未满volatileint running;} robot_sensor_t;robot_sensor_t g_sensors = {    .lidar_head = 0,    .lidar_tail = 0,    .lidar_count = 0,    .imu_write_pos = 0,    .imu_read_pos = 0,    .mutex = PTHREAD_MUTEX_INITIALIZER,    .lidar_data_ready = PTHREAD_COND_INITIALIZER,    .imu_data_ready = PTHREAD_COND_INITIALIZER,    .buffer_not_full = PTHREAD_COND_INITIALIZER,    .running = 1};// 激光雷达数据采集线程(高优先级)void* lidar_acquisition_thread(void* arg){int lidar_fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY);if (lidar_fd < 0) {        perror("Failed to open lidar");returnNULL;    }// 配置串口structtermiosoptions;    tcgetattr(lidar_fd, &options);    cfsetispeed(&options, LIDAR_BAUDRATE);    cfsetospeed(&options, LIDAR_BAUDRATE);    options.c_cflag |= (CLOCAL | CREAD);    options.c_cflag &= ~CSIZE;    options.c_cflag |= CS8;    options.c_cflag &= ~PARENB;    options.c_iflag &= ~(IXON | IXOFF | IXANY);    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);    options.c_oflag &= ~OPOST;    tcsetattr(lidar_fd, TCSANOW, &options);lidar_frame_t frame;structtimevaltv;while (g_sensors.running) {// 读取一帧激光雷达数据(通常需要接收几百字节)uint8_t header[4];int ret = read(lidar_fd, header, 4);if (ret != 4) continue;if (header[0] == 0xAA && header[1] == 0x55) {  // 帧头校验int frame_len = header[2] * 256 + header[3];// 分配缓冲区读取完整帧uint8_t *buffer = malloc(frame_len);            ret = read(lidar_fd, buffer, frame_len);if (ret == frame_len) {// 解析激光雷达数据                gettimeofday(&tv, NULL);                frame.timestamp = tv.tv_sec * 1000000 + tv.tv_usec;for (int i = 0; i < LIDAR_POINTS_PER_SCAN; i++) {                    frame.distance[i] = buffer[i*3] | (buffer[i*3+1] << 8);                    frame.intensity[i] = buffer[i*3+2];                }                pthread_mutex_lock(&g_sensors.mutex);// 等待缓冲区有空位(背压机制)structtimespectimeout;                clock_gettime(CLOCK_REALTIME, &timeout);                timeout.tv_sec += 0;  // 100ms超时                timeout.tv_nsec += 100000000;while (g_sensors.lidar_count >= LIDAR_DATA_QUEUE_SIZE) {int ret = pthread_cond_timedwait(&g_sensors.buffer_not_full,                                                       &g_sensors.mutex, &timeout);if (ret == ETIMEDOUT) {// 缓冲区满了太久,丢弃最旧的数据(机器人不能停)                        g_sensors.lidar_head = (g_sensors.lidar_head + 1) % LIDAR_DATA_QUEUE_SIZE;                        g_sensors.lidar_count--;printf("[WARN] Lidar buffer overflow, dropping oldest frame\n");break;                    }                }// 写入新帧                g_sensors.lidar_frames[g_sensors.lidar_tail] = frame;                g_sensors.lidar_tail = (g_sensors.lidar_tail + 1) % LIDAR_DATA_QUEUE_SIZE;                g_sensors.lidar_count++;// 通知SLAM算法线程有新数据                pthread_cond_signal(&g_sensors.lidar_data_ready);                pthread_mutex_unlock(&g_sensors.mutex);            }free(buffer);        }    }    close(lidar_fd);returnNULL;}// IMU数据采集线程(实时性要求最高)void* imu_acquisition_thread(void* arg){int imu_fd = open("/dev/i2c-1", O_RDWR);if (imu_fd < 0) {        perror("Failed to open IMU");returnNULL;    }// 配置IMU为1KHz输出模式    imu_configure(imu_fd, IMU_SAMPLE_RATE);structtimespecnext_sample;    clock_gettime(CLOCK_MONOTONIC, &next_sample);while (g_sensors.running) {imu_data_t imu;structtimevaltv;        gettimeofday(&tv, NULL);        imu.timestamp = tv.tv_sec * 1000000 + tv.tv_usec;// 读取IMU数据(I2C读取)        imu_read_all(imu_fd, &imu);        pthread_mutex_lock(&g_sensors.mutex);// 写入环形缓冲区        g_sensors.imu_buffer[g_sensors.imu_write_pos] = imu;        g_sensors.imu_write_pos = (g_sensors.imu_write_pos + 1) %                                    (IMU_SAMPLE_RATE * 2);// 如果写指针追上了读指针,说明溢出,移动读指针if (g_sensors.imu_write_pos == g_sensors.imu_read_pos) {            g_sensors.imu_read_pos = (g_sensors.imu_read_pos + 1) %                                       (IMU_SAMPLE_RATE * 2);printf("[WARN] IMU buffer overflow\n");        }// 通知算法线程        pthread_cond_signal(&g_sensors.imu_data_ready);        pthread_mutex_unlock(&g_sensors.mutex);// 精确控制1KHz采样率        next_sample.tv_nsec += 1000000;  // 1ms        clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_sample, NULL);    }    close(imu_fd);returnNULL;}

1.2 SLAM算法线程(数据融合)

// SLAM算法线程(融合激光雷达+IMU数据)void* slam_algorithm_thread(void* arg){lidar_frame_t current_lidar;imu_data_t imu_samples[100];  // 缓存100个IMU样本while (g_sensors.running) {        pthread_mutex_lock(&g_sensors.mutex);// 等待激光雷达数据while (g_sensors.lidar_count == 0 && g_sensors.running) {structtimespectimeout;            clock_gettime(CLOCK_REALTIME, &timeout);            timeout.tv_sec += 0;  // 100ms超时            timeout.tv_nsec += 100000000;int ret = pthread_cond_timedwait(&g_sensors.lidar_data_ready,                                               &g_sensors.mutex, &timeout);if (ret == ETIMEDOUT) {// 长时间无激光数据,使用里程计数据推算(紧急模式)                pthread_mutex_unlock(&g_sensors.mutex);                emergency_odometry_navigation();continue;            }        }if (!g_sensors.running) {            pthread_mutex_unlock(&g_sensors.mutex);break;        }// 获取最新一帧激光数据        current_lidar = g_sensors.lidar_frames[g_sensors.lidar_head];        g_sensors.lidar_head = (g_sensors.lidar_head + 1) % LIDAR_DATA_QUEUE_SIZE;        g_sensors.lidar_count--;// 获取这一帧期间的所有IMU数据int imu_count = 0;uint32_t lidar_timestamp = current_lidar.timestamp;while (g_sensors.imu_read_pos != g_sensors.imu_write_pos &&                imu_count < 100) {imu_data_t *imu = &g_sensors.imu_buffer[g_sensors.imu_read_pos];// 只取激光帧时间附近的IMU数据if (imu->timestamp > lidar_timestamp - 50000 &&  // 前后50ms                imu->timestamp < lidar_timestamp + 50000) {                imu_samples[imu_count++] = *imu;            }            g_sensors.imu_read_pos = (g_sensors.imu_read_pos + 1) %                                       (IMU_SAMPLE_RATE * 2);        }// 通知缓冲区有空位        pthread_cond_signal(&g_sensors.buffer_not_full);        pthread_mutex_unlock(&g_sensors.mutex);// 执行SLAM算法(时间同步+点云匹配+IMU积分)printf("[SLAM] Processing lidar frame %d with %d IMU samples\n",                current_lidar.timestamp, imu_count);// 1. IMU预积分(计算位姿变化)        imu_preintegration(imu_samples, imu_count);// 2. 点云畸变校正(利用IMU补偿运动畸变)        undistort_pointcloud(&current_lidar, imu_samples, imu_count);// 3. 扫描匹配(ICP或NDT)        scan_matching(&current_lidar);// 4. 更新地图和机器人位置        update_map_and_pose();    }returnNULL;}

第二部分:电机控制与环形缓冲区(硬实时系统)

2.1 电机控制环形缓冲区

#include<stdint.h>#include<string.h>#include<sched.h>#include<sys/mman.h>// 电机控制指令结构体(严格控制大小,避免缓存行跨越)typedefstruct {int32_t left_wheel_speed;     // 左轮速度 (mm/s)int32_t right_wheel_speed;    // 右轮速度 (mm/s)int32_t left_wheel_position;  // 左轮编码器位置int32_t right_wheel_position; // 右轮编码器位置uint32_t sequence;            // 序列号(防丢包)uint32_t timestamp;           // 时间戳uint8_t  checksum;            // 校验和uint8_t  pad[3];              // 填充到32字节(缓存行对齐)} __attribute__((packed, aligned(32))) motor_command_t;// 实时电机控制环形缓冲区(避免使用互斥锁)typedefstruct {// 生产者(规划线程)私有volatileuint32_t write_idx __attribute__((aligned(64)));char pad1[64 - sizeof(uint32_t)];// 消费者(控制线程)私有volatileuint32_t read_idx __attribute__((aligned(64)));char pad2[64 - sizeof(uint32_t)];// 共享数据区motor_command_t buffer[256] __attribute__((aligned(64)));// 状态标志volatileuint32_t underflow_count;   // 下溢计数(控制线程饿死)volatileuint32_t overflow_count;     // 上溢计数(规划线程太快)} realtime_motor_ring_t;// 初始化实时环形缓冲区voidinit_motor_ring(realtime_motor_ring_t *ring){memset(ring, 0, sizeof(realtime_motor_ring_t));// 锁定内存,防止页面交换(实时性要求)    mlock(ring, sizeof(realtime_motor_ring_t));// 设置线程实时优先级structsched_paramparam;    param.sched_priority = 99;    sched_setscheduler(0, SCHED_FIFO, &param);}// 规划线程写入指令(无锁)intmotor_ring_write(realtime_motor_ring_t *ring, constmotor_command_t *cmd){uint32_t write = ring->write_idx;uint32_t read = ring->read_idx;// 检查缓冲区是否已满(预留一个空位)if ((write + 1) % 256 == read) {        ring->overflow_count++;return-1;  // 缓冲区满,指令丢失    }// 计算写入位置uint32_t index = write % 256;// 写入数据    ring->buffer[index] = *cmd;    ring->buffer[index].sequence = write;// 内存屏障,确保写入完成    __sync_synchronize();// 更新写指针    ring->write_idx = write + 1;return0;}// 控制线程读取指令(无锁)intmotor_ring_read(realtime_motor_ring_t *ring, motor_command_t *cmd){uint32_t read = ring->read_idx;uint32_t write = ring->write_idx;// 检查缓冲区是否为空if (read == write) {        ring->underflow_count++;// 紧急情况:返回上一次的指令(维持当前速度)return-1;  // 无新指令    }// 计算读取位置uint32_t index = read % 256;// 读取数据    *cmd = ring->buffer[index];// 验证序列号(检查数据一致性)if (cmd->sequence != read) {// 数据损坏,丢弃        ring->read_idx = read + 1;  // 跳过损坏数据return-2;    }// 内存屏障    __sync_synchronize();// 更新读指针    ring->read_idx = read + 1;return0;}

2.2 实时电机控制线程

#include<time.h>#include<errno.h>realtime_motor_ring_t g_motor_ring;// 高精度定时器回调(控制线程 - 5ms周期)void* motor_control_thread(void* arg){// 设置实时优先级structsched_paramparam;    param.sched_priority = 98;    sched_setscheduler(0, SCHED_FIFO, &param);// 设置CPU亲和性(绑定到专用核心)cpu_set_t cpuset;    CPU_ZERO(&cpuset);    CPU_SET(1, &cpuset);  // 使用CPU核心1专门处理电机控制    pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);// 使用timerfd创建高精度定时器int timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);structitimerspects = {        .it_interval = {0, 5000000},  // 5ms周期        .it_value = {0, 5000000}    };    timerfd_settime(timer_fd, 0, &ts, NULL);motor_command_t cmd;motor_command_t last_valid_cmd = {0};uint64_t missed_deadlines = 0;uint64_t total_cycles = 0;while (g_sensors.running) {uint64_t expirations;        read(timer_fd, &expirations, sizeof(expirations));        total_cycles++;// 从环形缓冲区读取最新指令int ret = motor_ring_read(&g_motor_ring, &cmd);if (ret == 0) {// 成功读取新指令            last_valid_cmd = cmd;        } else {// 缓冲区下溢,使用上一次的有效指令            cmd = last_valid_cmd;            missed_deadlines++;if (missed_deadlines % 100 == 0) {printf("[CRITICAL] Motor control missed %lu deadlines\n",                        missed_deadlines);            }        }// 计算指令延迟(从规划到执行的延迟)uint32_t now_us = get_timestamp_us();uint32_t delay_us = now_us - cmd.timestamp;if (delay_us > 10000) {  // 延迟超过10msprintf("[WARN] Motor command delayed: %d us\n", delay_us);        }// 发送PWM到电机驱动器        set_motor_pwm(MOTOR_LEFT, cmd.left_wheel_speed);        set_motor_pwm(MOTOR_RIGHT, cmd.right_wheel_speed);// 读取编码器反馈        cmd.left_wheel_position = read_encoder(ENCODER_LEFT);        cmd.right_wheel_position = read_encoder(ENCODER_RIGHT);// 可以通过另一个环形缓冲区反馈给规划线程// motor_feedback_write(&cmd);// 统计控制周期抖动staticstructtimespeclast_time = {0};structtimespecnow;        clock_gettime(CLOCK_MONOTONIC, &now);if (last_time.tv_sec != 0) {int64_t jitter_us = (now.tv_sec - last_time.tv_sec) * 1000000 +                                 (now.tv_nsec - last_time.tv_nsec) / 1000 - 5000;if (abs(jitter_us) > 500) {  // 抖动超过0.5msprintf("[JITTER] Control cycle jitter: %ld us\n", jitter_us);            }        }        last_time = now;    }    close(timer_fd);returnNULL;}// 运动规划线程(生产者 - 非实时)void* motion_planning_thread(void* arg){// 普通优先级structsched_paramparam;    param.sched_priority = 0;    sched_setscheduler(0, SCHED_OTHER, &param);motor_command_t cmd;while (g_sensors.running) {// 根据SLAM结果规划路径// 计算期望速度        cmd.timestamp = get_timestamp_us();        cmd.left_wheel_speed = calculate_left_speed();        cmd.right_wheel_speed = calculate_right_speed();// 写入环形缓冲区int ret = motor_ring_write(&g_motor_ring, &cmd);if (ret == -1) {printf("[WARN] Motor ring buffer overflow\n");// 降速或采取其他措施        }// 规划周期10ms        usleep(10000);    }returnNULL;}

第三部分:多传感器时间同步(工业级方案)

3.1 硬件时间戳

#include<linux/types.h>#include<linux/ioctl.h>// GPIO触发同步信号#define SYNC_GPIO_PIN 17typedefstruct {uint64_t lidar_timestamp;    // 激光雷达触发时间uint64_t imu_timestamp;       // IMU采样时间uint64_t camera_timestamp;    // 相机曝光开始时间uint64_t system_boot_time;    // 系统启动时间(用于对齐)} hardware_timestamp_t;// 硬件同步线程(使用GPIO中断)void* hardware_sync_thread(void* arg){int gpio_fd = open("/sys/class/gpio/gpio17/value", O_RDONLY);// 设置中断触发char trig[32];snprintf(trig, 32, "/sys/class/gpio/gpio17/edge");int edge_fd = open(trig, O_WRONLY);    write(edge_fd, "rising", 6);    close(edge_fd);structpollfdpfd = {        .fd = gpio_fd,        .events = POLLPRI  // 监听中断    };hardware_timestamp_t ts;structtimevaltv;while (g_sensors.running) {// 等待GPIO中断int ret = poll(&pfd, 1, 1000);if (ret > 0 && (pfd.revents & POLLPRI)) {// 读取GPIO值清除中断char buf[2];            lseek(gpio_fd, 0, SEEK_SET);            read(gpio_fd, buf, 1);// 记录精确的硬件时间戳            gettimeofday(&tv, NULL);            ts.system_boot_time = tv.tv_sec * 1000000 + tv.tv_usec;// 通过共享内存分发给各个传感器线程            pthread_mutex_lock(&g_sensors.mutex);// 标记所有传感器需要同步// ... 更新同步状态            pthread_cond_broadcast(&g_sensors.lidar_data_ready);            pthread_cond_broadcast(&g_sensors.imu_data_ready);            pthread_mutex_unlock(&g_sensors.mutex);        }    }    close(gpio_fd);returnNULL;}

3.2 软件时间对齐算法

// 传感器数据时间对齐(用于后期融合)typedefstruct {uint64_t timestamp;void *data;int data_type;  // 0:激光, 1:IMU, 2:相机} sync_data_t;#define SYNC_WINDOW_SIZE 1000  // 1ms时间窗(1000us)// 时间对齐器(滑动窗口)typedefstruct {sync_data_t buffer[10000];  // 10秒数据缓存int head;int tail;// 时间轴统计uint64_t min_timestamp;uint64_t max_timestamp;uint64_t last_publish_time;} time_aligner_t;// 查找最近邻的时间戳数据void* find_nearest_data(time_aligner_t *aligner, uint64_t target_time, int data_type){int best_index = -1;uint64_t min_diff = UINT64_MAX;// 在滑动窗口中查找for (int i = aligner->head; i != aligner->tail; i = (i + 1) % 10000) {if (aligner->buffer[i].data_type != data_type)continue;uint64_t diff;if (target_time > aligner->buffer[i].timestamp) {            diff = target_time - aligner->buffer[i].timestamp;        } else {            diff = aligner->buffer[i].timestamp - target_time;        }if (diff < min_diff && diff < SYNC_WINDOW_SIZE) {            min_diff = diff;            best_index = i;        }    }if (best_index >= 0) {return aligner->buffer[best_index].data;    }returnNULL;  // 未找到匹配数据}

第四部分:优化技巧

4.1 避免优先级反转

// 使用优先级继承互斥锁pthread_mutexattr_t attr;pthread_mutexattr_init(&attr);pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT);  // 优先级继承pthread_mutex_init(&g_sensors.mutex, &attr);// 高优先级实时线程不会被低优先级线程阻塞太久

4.2 使用条件变量的超时机制防止死锁

// 带看门狗的等待intwait_with_watchdog(pthread_cond_t *cond, pthread_mutex_t *mutex, int timeout_ms, watchdog_t *wd){structtimespects;    clock_gettime(CLOCK_REALTIME, &ts);    ts.tv_sec += timeout_ms / 1000;    ts.tv_nsec += (timeout_ms % 1000) * 1000000;// 喂狗    watchdog_pet(wd);int ret = pthread_cond_timedwait(cond, mutex, &ts);if (ret == ETIMEDOUT) {// 超时,检查系统状态if (watchdog_is_system_hung(wd)) {// 触发系统重启或进入安全模式            emergency_shutdown();        }    }return ret;}

4.3 内存屏障的使用

// ARM平台的内存屏障#define dmb() __asm__ __volatile__ ("dmb" : : : "memory")#define dsb() __asm__ __volatile__ ("dsb" : : : "memory")#define isb() __asm__ __volatile__ ("isb" : : : "memory")// 生产者data_ready = 0;// 写入数据ring->buffer[index] = *cmd;// 确保写入完成后再更新标志dmb();data_ready = 1;// 消费者while (!data_ready) {// 等待}dmb();  // 确保看到最新的数据process_data(ring->buffer[index]);

第五部分:性能数据与调试技巧

5.1 机器人平台测试数据

场景
传感器负载
控制周期抖动
CPU占用
内存占用
静止待机
仅IMU
±50us
15%
2.3MB
慢速移动
激光+IMU
±120us
45%
8.7MB
高速导航
激光+IMU+相机
±380us
82%
25MB
紧急制动
所有传感器全速
±220us
95%
28MB

5.2 调试技巧

// 1. 打印条件变量等待时间structtimespecwait_start, wait_end;clock_gettime(CLOCK_MONOTONIC, &wait_start);pthread_cond_wait(&cond, &mutex);clock_gettime(CLOCK_MONOTONIC, &wait_end);uint64_t wait_us = (wait_end.tv_sec - wait_start.tv_sec) * 1000000 +                   (wait_end.tv_nsec - wait_start.tv_nsec) / 1000;if (wait_us > 1000) {  // 等待超过1msprintf("[DEBUG] Long wait: %ld us\n", wait_us);}// 2. 使用ftrace追踪内核调度延迟// echo function_graph > /sys/kernel/debug/tracing/current_tracer// echo 1 > /sys/kernel/debug/tracing/tracing_on// 3. 查看线程实时优先级// ps -e -o pid,pri,rtprio,cmd | grep robot

🎯 总结:机器人开发的同步机制选择指南

什么时候用条件变量?

  • ✅ 多传感器数据等待(激光、IMU、相机同步)
  • ✅ 任务队列有空闲时唤醒工作线程
  • ✅ 需要精确的线程间事件通知
  • ❌ 中断上下文(不能用,改用信号量)

什么时候用环形缓冲区?

  • ✅ 电机控制指令传递(高频率、低延迟)
  • ✅ 传感器数据流缓存(激光点云、图像帧)
  • ✅ 日志记录(避免阻塞业务线程)
  • ❌ 数据大小变化很大(改用链表)

嵌入式Linux开发三要三不要

三要:

  1. 要设置线程优先级和CPU亲和性
  2. 要使用mlock防止内存交换
  3. 要监控缓冲区溢出和等待超时

三不要:

  1. 不要在实时线程中调用printf
  2. 不要使用动态内存分配(malloc/free)
  3. 不要忽视编译器优化(volatile关键字)

遇到问题? 欢迎在评论区贴出你的:

  • 机器人平台型号
  • 传感器类型和频率
  • 遇到的具体同步问题

最新文章

随机文章