4轴运动控制源代码(STM32 + GRBL 1.1移植版)

一、系统架构概览

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    4轴运动控制系统架构                     │
├─────────────────────────────────────────────────────────────┤
│  上位机 (PC/Qt)  │  USB/串口  │  STM32F103  │  驱动电路    │
│  (Candle/自制)   │  通信协议  │  (GRBL核心)  │  (LV8728)    │
│                 │            │              │              │
│  • G-code解析   │  • 115200   │  • 轨迹规划  │  • 4路步进   │
│  • 可视化预览   │  • 实时状态 │  • 插补算法  │  • 1/128细分 │
│  • 参数配置     │  • 急停控制 │  • S型加减速 │  • 过流保护  │
└─────────────────────────────────────────────────────────────┘

二、核心源代码实现

2.1 系统配置文件 (system.h)

c 复制代码
/**
 * @file system.h
 * @brief 4轴运动控制系统配置
 */

#ifndef __SYSTEM_H
#define __SYSTEM_H

#include "stm32f10x.h"
#include <stdint.h>
#include <stdbool.h>
#include <math.h>

// ==================== 硬件配置 ====================
#define MCU_STM32F103RET6    1
#define AXIS_COUNT           4        // X, Y, Z, A四轴
#define STEPPER_COUNT        AXIS_COUNT

// 引脚定义 (根据实际PCB连接修改)
#define X_STEP_PIN          GPIO_Pin_0
#define X_DIR_PIN           GPIO_Pin_1
#define X_ENABLE_PIN        GPIO_Pin_2
#define X_LIMIT_PIN         GPIO_Pin_3

#define Y_STEP_PIN          GPIO_Pin_4
#define Y_DIR_PIN           GPIO_Pin_5
#define Y_ENABLE_PIN        GPIO_Pin_6
#define Y_LIMIT_PIN         GPIO_Pin_7

#define Z_STEP_PIN          GPIO_Pin_8
#define Z_DIR_PIN           GPIO_Pin_9
#define Z_ENABLE_PIN        GPIO_Pin_10
#define Z_LIMIT_PIN         GPIO_Pin_11

#define A_STEP_PIN          GPIO_Pin_12
#define A_DIR_PIN           GPIO_Pin_13
#define A_ENABLE_PIN        GPIO_Pin_14
#define A_LIMIT_PIN         GPIO_Pin_15

// ==================== 运动参数 ====================
#define DEFAULT_STEPS_PER_MM_X    6400.0f    // 1.8°电机,16细分
#define DEFAULT_STEPS_PER_MM_Y    6400.0f
#define DEFAULT_STEPS_PER_MM_Z    6400.0f
#define DEFAULT_STEPS_PER_MM_A    6400.0f

#define DEFAULT_MAX_FEED_RATE     3000.0f    // mm/min
#define DEFAULT_ACCELERATION      500.0f     // mm/s²
#define DEFAULT_JERK              10.0f      // mm/s³ (S型曲线)

// ==================== 系统状态 ====================
typedef enum {
    STATE_IDLE = 0,
    STATE_ALARM,
    STATE_CHECK_MODE,
    STATE_HOMING,
    STATE_CYCLE,
    STATE_HOLD,
    STATE_JOG,
    STATE_SAFETY_DOOR
} SystemState;

typedef enum {
    AXIS_X = 0,
    AXIS_Y = 1,
    AXIS_Z = 2,
    AXIS_A = 3
} AxisIndex;

// ==================== 运动数据结构 ====================
typedef struct {
    float steps_per_mm[AXIS_COUNT];
    float max_rate[AXIS_COUNT];
    float acceleration[AXIS_COUNT];
    float junction_deviation;
    float arc_tolerance;
    float jerk[AXIS_COUNT];
} Settings_t;

typedef struct {
    int32_t position[AXIS_COUNT];      // 当前位置(步数)
    int32_t target_position[AXIS_COUNT]; // 目标位置(步数)
    float current_rate[AXIS_COUNT];     // 当前速度
    float target_rate[AXIS_COUNT];       // 目标速度
    SystemState state;
    bool homing_complete;
} System_t;

extern Settings_t settings;
extern System_t system;

#endif /* __SYSTEM_H */

2.2 步进电机驱动核心 (stepper.c)

c 复制代码
/**
 * @file stepper.c
 * @brief 4轴步进电机核心驱动(移植自GRBL 1.1)
 */

#include "system.h"
#include "stepper.h"
#include "motion_control.h"
#include "planner.h"

// 步进电机状态机
typedef struct {
    uint8_t step_outbits;        // 步进输出位
    uint8_t dir_outbits;         // 方向输出位
    uint8_t step_port_invert_mask;
    uint8_t dir_port_invert_mask;
    uint8_t enable_port_invert_mask;
    uint32_t step_pulse_time;   // 脉冲持续时间(us)
    uint32_t step_delay;        // 步进延迟(us)
    int32_t steps_remaining[AXIS_COUNT];
    int32_t step_counter[AXIS_COUNT];
    bool direction[AXIS_COUNT];
} Stepper_t;

static Stepper_t stepper;

// 定时器1中断服务函数(步进脉冲生成)
void TIM1_UP_IRQHandler(void) {
    if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET) {
        TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
        
        // 执行步进脉冲
        if (stepper.steps_remaining[AXIS_X] > 0) {
            GPIO_SetBits(GPIOA, X_STEP_PIN);
            stepper.steps_remaining[AXIS_X]--;
            stepper.step_counter[AXIS_X]++;
        }
        
        if (stepper.steps_remaining[AXIS_Y] > 0) {
            GPIO_SetBits(GPIOA, Y_STEP_PIN);
            stepper.steps_remaining[AXIS_Y]--;
            stepper.step_counter[AXIS_Y]++;
        }
        
        if (stepper.steps_remaining[AXIS_Z] > 0) {
            GPIO_SetBits(GPIOA, Z_STEP_PIN);
            stepper.steps_remaining[AXIS_Z]--;
            stepper.step_counter[AXIS_Z]++;
        }
        
        if (stepper.steps_remaining[AXIS_A] > 0) {
            GPIO_SetBits(GPIOA, A_STEP_PIN);
            stepper.steps_remaining[AXIS_A]--;
            stepper.step_counter[AXIS_A]++;
        }
        
        // 清除脉冲(脉冲宽度控制)
        delay_us(stepper.step_pulse_time);
        GPIO_ResetBits(GPIOA, X_STEP_PIN | Y_STEP_PIN | Z_STEP_PIN | A_STEP_PIN);
    }
}

// 初始化步进电机
void st_init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    
    // 1. 初始化GPIO
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    GPIO_InitStructure.GPIO_Pin = X_STEP_PIN | X_DIR_PIN | X_ENABLE_PIN |
                                 Y_STEP_PIN | Y_DIR_PIN | Y_ENABLE_PIN |
                                 Z_STEP_PIN | Z_DIR_PIN | Z_ENABLE_PIN |
                                 A_STEP_PIN | A_DIR_PIN | A_ENABLE_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 2. 初始化定时器1(步进脉冲生成)
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
    
    TIM_TimeBaseStructure.TIM_Period = 1000 - 1;  // 1kHz基础频率
    TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1; // 72MHz/72 = 1MHz
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
    
    // 3. 配置中断
    NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    
    // 4. 启动定时器
    TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE);
    TIM_Cmd(TIM1, ENABLE);
    
    // 5. 初始化步进电机状态
    memset(&stepper, 0, sizeof(Stepper_t));
    stepper.step_pulse_time = 5;  // 5us脉冲宽度
    stepper.step_delay = 100;     // 100us步进间隔
    
    // 6. 使能所有轴
    GPIO_ResetBits(GPIOA, X_ENABLE_PIN | Y_ENABLE_PIN | Z_ENABLE_PIN | A_ENABLE_PIN);
}

// 设置步进脉冲
void st_set_step(uint8_t axis, bool state) {
    switch(axis) {
        case AXIS_X:
            if(state) GPIO_SetBits(GPIOA, X_STEP_PIN);
            else GPIO_ResetBits(GPIOA, X_STEP_PIN);
            break;
        case AXIS_Y:
            if(state) GPIO_SetBits(GPIOA, Y_STEP_PIN);
            else GPIO_ResetBits(GPIOA, Y_STEP_PIN);
            break;
        case AXIS_Z:
            if(state) GPIO_SetBits(GPIOA, Z_STEP_PIN);
            else GPIO_ResetBits(GPIOA, Z_STEP_PIN);
            break;
        case AXIS_A:
            if(state) GPIO_SetBits(GPIOA, A_STEP_PIN);
            else GPIO_ResetBits(GPIOA, A_STEP_PIN);
            break;
    }
}

// 设置方向
void st_set_direction(uint8_t axis, bool direction) {
    switch(axis) {
        case AXIS_X:
            if(direction) GPIO_SetBits(GPIOA, X_DIR_PIN);
            else GPIO_ResetBits(GPIOA, X_DIR_PIN);
            break;
        case AXIS_Y:
            if(direction) GPIO_SetBits(GPIOA, Y_DIR_PIN);
            else GPIO_ResetBits(GPIOA, Y_DIR_PIN);
            break;
        case AXIS_Z:
            if(direction) GPIO_SetBits(GPIOA, Z_DIR_PIN);
            else GPIO_ResetBits(GPIOA, Z_DIR_PIN);
            break;
        case AXIS_A:
            if(direction) GPIO_SetBits(GPIOA, A_DIR_PIN);
            else GPIO_ResetBits(GPIOA, A_DIR_PIN);
            break;
    }
}

// 执行直线运动
void st_linear_move(int32_t *target_steps, float feed_rate) {
    uint8_t axis;
    
    // 计算各轴需要移动的步数
    for(axis = 0; axis < AXIS_COUNT; axis++) {
        stepper.steps_remaining[axis] = abs(target_steps[axis] - system.position[axis]);
        stepper.direction[axis] = (target_steps[axis] > system.position[axis]);
        
        // 设置方向引脚
        st_set_direction(axis, stepper.direction[axis]);
        
        // 更新系统位置
        system.position[axis] = target_steps[axis];
    }
    
    // 计算主轴(移动步数最多的轴)
    uint32_t max_steps = 0;
    uint8_t master_axis = 0;
    for(axis = 0; axis < AXIS_COUNT; axis++) {
        if(stepper.steps_remaining[axis] > max_steps) {
            max_steps = stepper.steps_remaining[axis];
            master_axis = axis;
        }
    }
    
    // 计算步进间隔
    if(max_steps > 0) {
        float step_frequency = (feed_rate * settings.steps_per_mm[master_axis]) / 60.0f;
        uint32_t step_period_us = (uint32_t)(1000000.0f / step_frequency);
        
        // 设置定时器周期
        TIM_SetAutoreload(TIM1, step_period_us - 1);
    }
}

2.3 运动控制核心 (motion_control.c)

c 复制代码
/**
 * @file motion_control.c
 * @brief 运动控制核心(直线/圆弧插补、S型加减速)
 */

#include "system.h"
#include "motion_control.h"
#include "stepper.h"
#include "planner.h"

// 直线插补
void mc_line(float *target, plan_line_data_t *pl_data) {
    int32_t target_steps[AXIS_COUNT];
    uint8_t axis;
    
    // 1. 将毫米坐标转换为步数
    for(axis = 0; axis < AXIS_COUNT; axis++) {
        target_steps[axis] = (int32_t)(target[axis] * settings.steps_per_mm[axis]);
    }
    
    // 2. 添加到规划器缓冲区
    plan_buffer_line(target_steps, pl_data);
    
    // 3. 执行运动
    st_linear_move(target_steps, pl_data->feed_rate);
}

// 圆弧插补(G02/G03)
void mc_arc(float *target, float *offset, float radius, uint8_t axis_0, uint8_t axis_1, uint8_t is_clockwise) {
    // 圆弧插补算法(DDA数字微分分析法)
    float center_x = system.position[axis_0] / settings.steps_per_mm[axis_0] + offset[0];
    float center_y = system.position[axis_1] / settings.steps_per_mm[axis_1] + offset[1];
    
    float start_angle = atan2(system.position[axis_1] / settings.steps_per_mm[axis_1] - center_y,
                             system.position[axis_0] / settings.steps_per_mm[axis_0] - center_x);
    
    float end_angle = atan2(target[axis_1] - center_y, target[axis_0] - center_x);
    
    float angle_diff = end_angle - start_angle;
    if(is_clockwise) {
        if(angle_diff > 0) angle_diff -= 2 * M_PI;
    } else {
        if(angle_diff < 0) angle_diff += 2 * M_PI;
    }
    
    // 离散化为小线段
    uint16_t segments = (uint16_t)(fabs(angle_diff) * radius * 10); // 每段0.1mm
    float angle_increment = angle_diff / segments;
    
    float current_angle = start_angle;
    float interpolated_target[AXIS_COUNT];
    
    for(uint16_t i = 0; i < segments; i++) {
        current_angle += angle_increment;
        
        interpolated_target[axis_0] = center_x + radius * cos(current_angle);
        interpolated_target[axis_1] = center_y + radius * sin(current_angle);
        interpolated_target[2] = target[2]; // Z轴保持不变
        interpolated_target[3] = target[3]; // A轴保持不变
        
        mc_line(interpolated_target, NULL);
    }
}

// S型加减速规划
void mc_s_curve_acceleration(plan_block_t *block) {
    float t = 0.0f;
    float dt = 0.001f; // 1ms时间步长
    float current_velocity = 0.0f;
    float current_acceleration = 0.0f;
    
    while(t < block->acceleration_time) {
        // 计算加加速度(Jerk)
        float jerk = settings.jerk[0];
        
        // 计算加速度
        current_acceleration += jerk * dt;
        if(current_acceleration > settings.acceleration[0]) {
            current_acceleration = settings.acceleration[0];
        }
        
        // 计算速度
        current_velocity += current_acceleration * dt;
        if(current_velocity > block->max_entry_speed) {
            current_velocity = block->max_entry_speed;
        }
        
        // 更新规划器
        block->entry_speed = current_velocity;
        
        t += dt;
    }
}

2.4 路径规划器 (planner.c)

c 复制代码
/**
 * @file planner.c
 * @brief 路径规划器(前瞻控制、速度规划)
 */

#include "system.h"
#include "planner.h"

#define BLOCK_BUFFER_SIZE 16

typedef struct {
    int32_t steps[AXIS_COUNT];
    float entry_speed;
    float exit_speed;
    float max_entry_speed;
    float acceleration;
    float nominal_speed;
    float millimeters;
    bool recalculate_flag;
} plan_block_t;

typedef struct {
    plan_block_t block_buffer[BLOCK_BUFFER_SIZE];
    uint8_t write_index;
    uint8_t read_index;
    uint8_t available_blocks;
} plan_buffer_t;

static plan_buffer_t plan_buffer;

// 初始化规划器
void plan_init(void) {
    memset(&plan_buffer, 0, sizeof(plan_buffer_t));
}

// 前瞻速度规划(Look-ahead)
void plan_lookahead_speed_planning(void) {
    uint8_t current_index = plan_buffer.read_index;
    uint8_t previous_index = (current_index == 0) ? BLOCK_BUFFER_SIZE - 1 : current_index - 1;
    
    // 计算拐角速度限制
    float junction_speed = settings.junction_deviation;
    
    // 限制当前块的退出速度
    if(plan_buffer.block_buffer[current_index].available_blocks > 0) {
        plan_block_t *current_block = &plan_buffer.block_buffer[current_index];
        plan_block_t *previous_block = &plan_buffer.block_buffer[previous_index];
        
        // 计算拐角角度
        float cos_theta = 0.0f;
        for(uint8_t axis = 0; axis < AXIS_COUNT; axis++) {
            cos_theta += (current_block->steps[axis] - previous_block->steps[axis]) *
                        (current_block->steps[axis] - previous_block->steps[axis]);
        }
        
        // 应用拐角速度限制
        float limited_speed = junction_speed * fabs(cos_theta);
        if(current_block->exit_speed > limited_speed) {
            current_block->exit_speed = limited_speed;
        }
    }
}

// 添加直线到规划器
void plan_buffer_line(int32_t *steps, plan_line_data_t *pl_data) {
    // 检查缓冲区是否满
    if(plan_buffer.available_blocks >= BLOCK_BUFFER_SIZE) {
        return; // 缓冲区满,等待
    }
    
    // 获取当前写入位置
    plan_block_t *block = &plan_buffer.block_buffer[plan_buffer.write_index];
    
    // 填充块数据
    memcpy(block->steps, steps, sizeof(int32_t) * AXIS_COUNT);
    block->entry_speed = pl_data->feed_rate;
    block->max_entry_speed = pl_data->feed_rate;
    block->acceleration = settings.acceleration[0];
    block->recalculate_flag = true;
    
    // 更新写入索引
    plan_buffer.write_index = (plan_buffer.write_index + 1) % BLOCK_BUFFER_SIZE;
    plan_buffer.available_blocks++;
    
    // 执行前瞻规划
    plan_lookahead_speed_planning();
}

2.5 G代码解析器 (gcode.c)

c 复制代码
/**
 * @file gcode.c
 * @brief G代码解析器
 */

#include "system.h"
#include "gcode.h"
#include "motion_control.h"

typedef struct {
    float position[AXIS_COUNT];
    float feed_rate;
    uint8_t modal_group;
    bool absolute_mode;
} gc_state_t;

static gc_state_t gc_state;

// 初始化G代码解析器
void gc_init(void) {
    memset(&gc_state, 0, sizeof(gc_state_t));
    gc_state.absolute_mode = true;
    gc_state.feed_rate = settings.max_rate[0];
}

// 解析G代码行
uint8_t gc_execute_line(char *line) {
    char *letter;
    float value;
    uint8_t group = 0;
    
    // 移除空格和注释
    gc_remove_spaces_and_comments(line);
    
    letter = line;
    while(*letter) {
        if(*letter >= 'A' && *letter <= 'Z') {
            char command = *letter;
            letter++;
            
            // 解析数值
            value = gc_parse_float(&letter);
            
            switch(command) {
                case 'G': // G代码
                    gc_execute_g_command((uint16_t)value);
                    break;
                case 'M': // M代码
                    gc_execute_m_command((uint16_t)value);
                    break;
                case 'X': // X坐标
                    gc_state.position[AXIS_X] = value;
                    break;
                case 'Y': // Y坐标
                    gc_state.position[AXIS_Y] = value;
                    break;
                case 'Z': // Z坐标
                    gc_state.position[AXIS_Z] = value;
                    break;
                case 'A': // A坐标
                    gc_state.position[AXIS_A] = value;
                    break;
                case 'F': // 进给速度
                    gc_state.feed_rate = value;
                    break;
            }
        } else {
            letter++;
        }
    }
    
    return GC_OK;
}

// 执行G代码命令
void gc_execute_g_command(uint16_t code) {
    plan_line_data_t pl_data;
    pl_data.feed_rate = gc_state.feed_rate;
    
    switch(code) {
        case 0: // 快速定位
            pl_data.feed_rate = settings.max_rate[0];
            mc_line(gc_state.position, &pl_data);
            break;
            
        case 1: // 直线插补
            mc_line(gc_state.position, &pl_data);
            break;
            
        case 2: // 顺时针圆弧
            // 需要解析圆心偏移量I,J,K
            break;
            
        case 3: // 逆时针圆弧
            break;
            
        case 28: // 回原点
            mc_homing_cycle();
            break;
            
        case 90: // 绝对坐标
            gc_state.absolute_mode = true;
            break;
            
        case 91: // 相对坐标
            gc_state.absolute_mode = false;
            break;
    }
}

2.6 主程序 (main.c)

c 复制代码
/**
 * @file main.c
 * @brief 4轴运动控制系统主程序
 */

#include "system.h"
#include "stepper.h"
#include "motion_control.h"
#include "planner.h"
#include "gcode.h"
#include "serial.h"

int main(void) {
    // 1. 系统初始化
    SystemInit();
    serial_init(115200);
    st_init();
    plan_init();
    gc_init();
    
    printf("4-Axis Motion Control System Ready!\r\n");
    printf("GRBL 1.1 Compatible\r\n");
    
    // 2. 回原点
    mc_homing_cycle();
    
    // 3. 主循环
    char gcode_line[128];
    uint8_t line_index = 0;
    
    while(1) {
        // 从串口接收G代码
        if(serial_available()) {
            char c = serial_read();
            
            if(c == '\n' || c == '\r') {
                gcode_line[line_index] = '\0';
                
                if(line_index > 0) {
                    // 执行G代码
                    uint8_t result = gc_execute_line(gcode_line);
                    
                    if(result == GC_OK) {
                        printf("ok\r\n");
                    } else {
                        printf("error:%d\r\n", result);
                    }
                }
                
                line_index = 0;
            } else if(line_index < 127) {
                gcode_line[line_index++] = c;
            }
        }
        
        // 执行运动控制
        mc_run();
        
        // 系统状态监控
        if(system.state == STATE_IDLE) {
            // 空闲状态,可以执行其他任务
        }
        
        delay_ms(1);
    }
}

参考代码 4轴运动控制 原理图 PCB图 源代码 www.youwenfan.com/contentcsu/60460.html

三、编译与部署

3.1 编译环境

  • IDE: Keil MDK-ARM 或 STM32CubeIDE
  • 编译器: ARM GCC
  • 调试器: ST-Link V2

3.2 编译指令

bash 复制代码
# 使用Makefile编译
make clean
make all

# 或使用STM32CubeIDE直接编译

3.3 烧录到STM32

  1. 使用ST-Link Utility或STM32CubeProgrammer
  2. 选择生成的.hex.bin文件
  3. 烧录到STM32F103RET6

四、上位机软件

4.1 Qt Candle上位机(推荐)

cpp 复制代码
// 简单的Qt串口通信示例
void MainWindow::sendGCode(QString gcode) {
    QByteArray data = gcode.toUtf8() + "\n";
    serialPort->write(data);
}

void MainWindow::on_pushButton_MoveX_clicked() {
    sendGCode("G0 X10 F3000");
}

4.2 测试G代码

复制代码
G21 ; 设置单位为毫米
G90 ; 绝对坐标模式
G0 X0 Y0 Z0 A0 ; 快速移动到原点
G1 X100 Y50 F3000 ; 直线插补到(100,50)
G2 X150 Y100 I25 J0 F2000 ; 顺时针圆弧
M03 S1000 ; 主轴启动
G0 Z10 ; Z轴上升
M05 ; 主轴停止
相关推荐
0南城逆流02 小时前
【STM32】RTT-Studio中HAL库开发教程十一:WS2812彩色RGB模块使用
stm32·单片机·嵌入式硬件
恶魔泡泡糖2 小时前
stm32F103C8T6标准库外部中断点灯
stm32·单片机·嵌入式硬件
The_superstar62 小时前
衡山派LVGL注意点
单片机·lvgl·衡山派·俊杰
fengfuyao9852 小时前
STM32 ADC音频采样与FFT频谱分析实现
stm32·嵌入式硬件·音视频
张海森-1688202 小时前
海思原生 isp调试工具使用方法
单片机
踏着七彩祥云的小丑3 小时前
嵌入式测试学习第 4 天:集成电路、芯片、FPGA
单片机·嵌入式硬件
项目題供诗3 小时前
STM32-对射式红外传感器计次&旋转编码器计次(九)
人工智能·stm32·嵌入式硬件
llilian_163 小时前
如何甄选专业级失真度测量仪校准装置
人工智能·功能测试·单片机·嵌入式硬件·测试工具·51单片机
bubiyoushang8883 小时前
利用数字电位器MCP41010控制开关电源输出电压
单片机