EV3100电梯专用变频器源代码

翻开源码包看到motorcontrol.c文件里躺着的svpwmgenerate函数,这玩意儿绝对是整个变频器的灵魂。咱们电梯运行时那种丝滑的启停体验,八成是靠这个空间矢量脉宽调制算法撑起来的。看这段代码:

void SVPWM_Generate(MotorState* motor) {
    // 计算扇区号
    float theta = motor->electric_angle % (2*PI);
    int sector = (int)(theta / (PI/3));
    
    // 计算相邻矢量作用时间
    float T1 = motor->modulation_index * sin(sector*PI/3 - theta);
    float T2 = motor->modulation_index * sin(theta - (sector-1)*PI/3);
    float T0 = 1 - T1 - T2;
    
    // 设置PWM比较寄存器
    PWM_SetCompare(sector_map[sector][0], T1 * PWM_PERIOD);
    PWM_SetCompare(sector_map[sector][1], T2 * PWM_PERIOD);
    PWM_SetDeadTime(T0 * 0.1 * PWM_PERIOD); // 死区时间补偿
}

这个扇区判断逻辑让我想起披萨切块——把整个电周期切成6块,每块60度。T1和T2这两个时间参数的计算其实就是矢量合成的数学魔术,用正弦函数把旋转磁场拆解成两个相邻矢量的线性组合。注意看那个modulation_index参数,相当于调制深度,超过1就会进入过调制区域,这时候波形会出现削顶,就像音响开太大破音似的。

安全回路处理部分的代码更有意思,在safety_monitor.c里藏着个状态机:

typedef enum {
    SAFE,
    BRAKE_ENGAGED,
    OVERSPEED,
    DOOR_OPEN
} SafetyState;

void update_safety_state(Elevator* elev) {
    static SafetyState prev_state = SAFE;
    
    if(elev->current_speed > elev->rated_speed * 1.15) {
        trigger_emergency_brake();
        prev_state = OVERSPEED;
        log_error("超速125%触发安全钳");
    }
    else if(!door_sensor_get_status()) {
        if(prev_state == BRAKE_ENGAGED && elev->current_speed > 0.2) {
            cut_power_supply();
            log_warning("门区异常移动");
        }
        prev_state = DOOR_OPEN;
    }
    // 其他状态判断...
}

这个状态机的跳转条件设计得很讲究,特别是门锁信号和移动速度的联合判断。注意到当门区打开时,如果电梯还在以0.2m/s以上的速度移动,就会直接切断电源——这个阈值比国标要求的0.8m/s还严格,看来厂家在安全余量上留了后手。

调试时最头疼的可能是楼层位置自学习功能。在homing.c里有段磁极对齐的代码:

void perform_homing() {
    // 低速旋转寻找Z脉冲
    set_motor_speed(30); // 30rpm低速
    while(!z_pulse_detected()) {
        feed_watchdog();
        if(timeout_check(5000)) {
            throw_exception(HOMING_TIMEOUT);
        }
    }
    
    // 精确定位阶段
    for(int i=0; i<3; i++) { // 三次平均减少误差
        precise_adjustment();
        save_encoder_position();
    }
    write_nvram(homing_data); // 掉电保存
}

这个自学习过程就像盲人摸象,先低速旋转找零位信号Z脉冲,找到后再做三次微调取平均。注意到喂狗操作和超时处理,这是工业代码的典型特征——永远要考虑最坏情况。write_nvram那行说明位置信息需要掉电保存,下次上电直接读取,避免每次都要重新寻址。

翻到通讯协议部分,modbus_rtu.c里的CRC校验函数写法很妖:

uint16_t crc16(uint8_t *data, uint8_t length) {
    uint16_t crc = 0xFFFF;
    while(length--) {
        crc ^= *data++;
        for(int i=0; i<8; i++) {
            if(crc & 0x0001) {
                crc = (crc >> 1) ^ 0xA001;
            } else {
                crc >>= 1;
            }
        }
    }
    return (crc << 8) | (crc >> 8); // 高低字节交换
}

这个位运算实现的CRC16查表法,比查表法省ROM空间,但牺牲了点速度。注意最后那个高低字节交换操作,Modbus协议要求CRC校验码是低位在前,而算法计算出来的是高位在前,所以需要手动调换顺序。这种细节就像吃鱼要挑刺,稍不注意就会被卡住。

最后在故障记录模块里发现个巧妙的设计:

#pragma pack(1)
typedef struct {
    uint32_t timestamp;
    uint16_t error_code;
    float motor_current;
    uint8_t drive_temp;
    uint16_t reserved;
} ErrorLogEntry;
#pragma pack()

void save_error_log(ErrorLogEntry entry) {
    uint8_t buffer[sizeof(ErrorLogEntry)];
    memcpy(buffer, &entry, sizeof(entry));
    flash_write(current_address, buffer);
    current_address += sizeof(entry);
    if(current_address >= MAX_LOG_SIZE) {
        current_address = LOG_BASE_ADDRESS; // 循环覆盖
    }
}

用#pragma pack(1)取消结构体对齐,保证存储时紧密排列,这样flash空间利用率最高。循环覆盖策略避免日志爆仓,老故障自动被新记录覆盖,类似行车记录仪的工作原理。这种设计在资源受限的嵌入式系统中很常见,但要注意电源突然断开时的数据完整性。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐