klipper — mcu 交互

初始化

  • 在打印机加载配置文件里的模块对象之前,会对mcu进行初始化。
def __init__(self, config, clocksync):
    self._printer = printer = config.get_printer()
    self._clocksync = clocksync
    self._reactor = printer.get_reactor()
    self._name = config.get_name()
    if self._name.startswith('mcu '):
        self._name = self._name[4:]
    # Serial port
    wp = "mcu '%s': " % (self._name)
    self._serial = serialhdl.SerialReader(self._reactor, warn_prefix=wp)
    self._baud = 0
    self._canbus_iface = None
    canbus_uuid = config.get('canbus_uuid', None)
    # can总线配置
    if canbus_uuid is not None:
        self._serialport = canbus_uuid
        self._canbus_iface = config.get('canbus_interface', 'can0')
        cbid = self._printer.load_object(config, 'canbus_ids')
        cbid.add_uuid(config, canbus_uuid, self._canbus_iface)
    else:
        # 串口波特率配置
        self._serialport = config.get('serial')
        if not (self._serialport.startswith("/dev/rpmsg_")
                or self._serialport.startswith("/tmp/klipper_host_")):
            self._baud = config.getint('baud', 250000, minval=2400)
    # Restarts
    # 重启命令
    restart_methods = [None, 'arduino', 'cheetah', 'command', 'rpi_usb']
    self._restart_method = 'command'
    if self._baud:
        rmethods = {m: m for m in restart_methods}
        self._restart_method = config.getchoice('restart_method',
                                                rmethods, None)
    self._reset_cmd = self._config_reset_cmd = None
    self._emergency_stop_cmd = None
    self._is_shutdown = self._is_timeout = False
    self._shutdown_clock = 0
    self._shutdown_msg = ""
    # Config building
    # 将当前的mcu对象注册到打印机引脚管理模块中
    printer.lookup_object('pins').register_chip(self._name, self)
    self._oid_count = 0
    self._config_callbacks = []
    self._config_cmds = []
    self._restart_cmds = []
    self._init_cmds = []
    self._mcu_freq = 0.
    # Move command queuing
    ffi_main, self._ffi_lib = chelper.get_ffi()
    self._max_stepper_error = config.getfloat('max_stepper_error', 0.000025,
                                              minval=0.)
    self._reserved_move_slots = 0
    self._stepqueues = []
    self._steppersync = None
    # Stats
    self._get_status_info = {}
    self._stats_sumsq_base = 0.
    self._mcu_tick_avg = 0.
    self._mcu_tick_stddev = 0.
    self._mcu_tick_awake = 0.
    # Register handlers
    printer.register_event_handler("klippy:connect", self._connect)
    printer.register_event_handler("klippy:mcu_identify",
                                   self._mcu_identify)
    printer.register_event_handler("klippy:shutdown", self._shutdown)
    printer.register_event_handler("klippy:disconnect", self._disconnect)
  • self._clocksync = clocksync:时钟同步对象

  • self._serial = serialhdl.SerialReader(self._reactor, warn_prefix=wp):创建串口读取对象,用于与mcu通信

  • self._config_callbacks = []: 存储配置回调函数,配置更新时触发回调函数

  • self._config_cmds = []:配置命令列表,初始化或重启时会执行这些命令

  • self._restart_cmds = []:存储重启命令

  • self._init_cmds = []:存储初始化命令

  • self._max_stepper_error:设置步进电机移动时的最大误差,默认值是0.000025

  • self._stepqueues = []:存储步进命令

  • self._get_status_info = {}:存储mcu的状态信息

  • self._stats_sumsq_base = 0:计算统计数据的基础值

  • self._mcu_tick_avg = 0:mcu时钟周期的平均值,初始值0

  • self._mcu_tick_stddev:mcu时钟的标准差,初始值0

  • self._mcu_tick_awake:mcu的唤醒时间值,初始值0

  • printer.register_event_handler 事件注册,当触发事件时,回调注册的函数。

  • 打印机初始化完成后,触发 “klippy:mcu_identify”和 “klippy:connect”事件,进行mcu识别和连接。

klippy:mcu_identify事件

依次执行该事件上已注册的函数:MCU._mcu_identify、TMCComandHelper._handle_mcu_identify、PrinterTemperatureMCU._mcu_identify、ProbeEndstopWrapper._handle_mcu_identify

  • mcu连接前的连接方式检查
  • 识别mcu里的步进电机
  • 设置mcu温度的相关值
  • 检查步进电机Z轴的活跃状态
  1. mcu连接前的连接方式检查
def _mcu_identify(self):
    if self.is_fileoutput():
        self._connect_file()
    else:
        resmeth = self._restart_method
        # 根据配置方法,检查mcu连接方式
        if resmeth == 'rpi_usb' and not os.path.exists(self._serialport):
            # usb连接检查
            # Try toggling usb power
            self._check_restart("enable power")
        try:
            if self._canbus_iface is not None:
                # can总线方式连接检查
                cbid = self._printer.lookup_object('canbus_ids')
                nodeid = cbid.get_nodeid(self._serialport)
                self._serial.connect_canbus(self._serialport, nodeid,
                                            self._canbus_iface)
            elif self._baud:
                # UART 连接
                # Cheetah boards require RTS to be deasserted
                # else a reset will trigger the built-in bootloader.
                rts = (resmeth != "cheetah")
                self._serial.connect_uart(self._serialport, self._baud, rts)
            else:
                # 管道连接
                self._serial.connect_pipe(self._serialport)
            self._clocksync.connect(self._serial)
        except serialhdl.error as e:
            raise error(str(e))
    logging.info(self._log_info())
    # 获取引脚解析器,并保留引脚
    ppins = self._printer.lookup_object('pins')
    pin_resolver = ppins.get_pin_resolver(self._name)
    for cname, value in self.get_constants().items():
        if cname.startswith("RESERVE_PINS_"):
            for pin in value.split(','):
                pin_resolver.reserve_pin(pin, cname[13:])
    # 获取mcu时钟同步频率
    self._mcu_freq = self.get_constant_float('CLOCK_FREQ')
    # 获取统计平方和的基础值
    self._stats_sumsq_base = self.get_constant_float('STATS_SUMSQ_BASE')
    # 急停命令
    self._emergency_stop_cmd = self.lookup_command("emergency_stop")
    # 重启命令
    self._reset_cmd = self.try_lookup_command("reset")
    # 配置重置命令
    self._config_reset_cmd = self.try_lookup_command("config_reset")
    ext_only = self._reset_cmd is None and self._config_reset_cmd is None
    # 串口的消息解析器
    msgparser = self._serial.get_msgparser()
    # 获取串口的波特率
    mbaud = msgparser.get_constant('SERIAL_BAUD', None)
    if self._restart_method is None and mbaud is None and not ext_only:
        self._restart_method = 'command'

    # 获取mcu版本信息
    version, build_versions = msgparser.get_version_info()
    self._get_status_info['mcu_version'] = version
    self._get_status_info['mcu_build_versions'] = build_versions
    self._get_status_info['mcu_constants'] = msgparser.get_constants()
    # 响应事件注册
    self.register_response(self._handle_shutdown, 'shutdown')
    self.register_response(self._handle_shutdown, 'is_shutdown')
    self.register_response(self._handle_mcu_stats, 'stats')
  • self._connect_file():通过命令行参数 mcu数据字典文件方式进行mcu识别和连接
  • 服务器上用的 UART 连接方式,通过串口对象进行连接
  • self._serial.connect_uart(self._serialport, self._baud, rts):通过串口对象连接mcu
    • 该函数会启用一个后台线程(死循环),处理mcu返回的信息
    • 通过串口队列获取信息
  • self._clocksync.connect(self._serial):时钟同步与串口对象连接,确保mcu时钟同步
def _connect(self):
    config_params = self._send_get_config()
    if not config_params['is_config']:
        if self._restart_method == 'rpi_usb':
            # Only configure mcu after usb power reset
            self._check_restart("full reset before config")
        # Not configured - send config and issue get_config again
        self._send_config(None)
        config_params = self._send_get_config()
        if not config_params['is_config'] and not self.is_fileoutput():
            raise error("Unable to configure MCU '%s'" % (self._name,))
    else:
        start_reason = self._printer.get_start_args().get("start_reason")
        if start_reason == 'firmware_restart':
            raise error("Failed automated reset of MCU '%s'"
                        % (self._name,))
        # Already configured - send init commands
        self._send_config(config_params['crc'])
    # Setup steppersync with the move_count returned by get_config
    move_count = config_params['move_count']
    if move_count < self._reserved_move_slots:
        raise error("Too few moves available on MCU '%s'" % (self._name,))
    ffi_main, ffi_lib = chelper.get_ffi()
    self._steppersync = ffi_main.gc(
        ffi_lib.steppersync_alloc(self._serial.serialqueue,
                                  self._stepqueues, len(self._stepqueues),
                                  move_count-self._reserved_move_slots),
        ffi_lib.steppersync_free)
    ffi_lib.steppersync_set_time(self._steppersync, 0., self._mcu_freq)
    # Log config information
    move_msg = "Configured MCU '%s' (%d moves)" % (self._name, move_count)
    logging.info(move_msg)
    log_info = self._log_info() + "\n" + move_msg
    self._printer.set_rollover_info(self._name, log_info, log=False)
  • config_params = self._send_get_config():获取mcu的当前配置参数,检查mcu是否已配置
  • 如果未配置,发送配置重新配置
  • 如果已配置,检查启动原因
  • self._send_config(config_params['crc']):发送初始化命令,调用串口对象的send() 方法实现交互
  • move_count = config_params['move_count']:获取可用的移动计数
  • ffi_lib.steppersync_alloc():分配步进同步对象,以支持步进电机的同步控制
  • ffi_lib.steppersync_set_time(self._steppersync, 0., self._mcu_freq):根据mcu时钟频率,设置步进同步的时间
  1. 识别mcu里的步进电机
def _handle_mcu_identify(self):
    # Lookup stepper object
    force_move = self.printer.lookup_object("force_move")
    self.stepper = force_move.lookup_stepper(self.stepper_name)
    # Note pulse duration and step_both_edge optimizations available
    # 将步进电机的默认脉冲持续时间设置为 0.000000100 秒(即 100 纳秒)
    self.stepper.setup_default_pulse_duration(.000000100, True)
  1. 设置mcu的温度相关值
def _mcu_identify(self):
    # Obtain mcu information
    mcu = self.mcu_adc.get_mcu()
    self.debug_read_cmd = mcu.lookup_query_command(
        "debug_read order=%c addr=%u", "debug_result val=%u")
    self.mcu_type = mcu.get_constants().get("MCU", "")
    # Run MCU specific configuration
    cfg_funcs = [
        ('rp2040', self.config_rp2040),
        ('sam3', self.config_sam3), ('sam4', self.config_sam4),
        ('same70', self.config_same70),
        ('samd21', self.config_samd21), ('samd51', self.config_samd51),
        ('stm32f1', self.config_stm32f1), ('stm32f2', self.config_stm32f2),
        ('stm32f4', self.config_stm32f4),
        ('stm32f042', self.config_stm32f0x2),
        ('stm32f070', self.config_stm32f070),
        ('stm32f072', self.config_stm32f0x2),
        ('stm32g0', self.config_stm32g0),
        ('', self.config_unknown)]
    for name, func in cfg_funcs:
        if self.mcu_type.startswith(name):
            func()
            break
    logging.info("mcu_temperature '%s' nominal base=%.6f slope=%.6f",
                 mcu.get_name(), self.base_temperature, self.slope)
    # Setup manual base/slope override
    # 设置温度基准值和斜率
    if self.temp1 is not None:
        if self.temp2 is not None:
            self.slope = (self.temp2 - self.temp1) / (self.adc2 - self.adc1)
        self.base_temperature = self.calc_base(self.temp1, self.adc1)
    # Setup min/max checks
    adc_range = [self.calc_adc(t) for t in [self.min_temp, self.max_temp]]
    self.mcu_adc.setup_minmax(SAMPLE_TIME, SAMPLE_COUNT,
                              minval=min(adc_range), maxval=max(adc_range),
                              range_check_count=RANGE_CHECK_COUNT)
  1. 检查步进电机Z轴的活跃状态
def _handle_mcu_identify(self):
    kin = self.printer.lookup_object('toolhead').get_kinematics()
    for stepper in kin.get_steppers():
        if stepper.is_active_axis('z'):
            self.add_stepper(stepper)
  • 如果Z轴是活跃状态,则将当前电机加入步进电机对象里

波特率

  • 单位时间内传送的码元号的个数,它是对符号传输速率的一种度量,它用单位时间载波调制状态改变的次数来表示,波特率即指一个单位时间内传输符号的个数。

CAN-BUS

  • CAN-BUS即CAN,全称为“控制器局域网总线技术(ControllerAreaNetwork-BUS)”。CAN总线的通讯介质可采用双绞线,同轴电缆和光导纤维。通讯距离与波持率有关,最大通讯距离可达10km,最大通讯波持率可达1Mdps。CAN总线仲裁采用11位标识和非破坏性位仲裁总线结构机制,可以确定数据块的优先级,保证在网络节点冲突时最高优先级节点不需要冲突等待。CAN总线采用了多主竞争式总线结构,具有多主站运行和分散仲裁的串行总线以及广播通信的特点。CAN总线上任意节点可在任意时刻主动地向网络上其它节点发送信息而不分主次,因此可在各节点之间实现自由通信。

UART

  • UART是通用异步收发器(异步串行通信口)的英文缩写,它包括了RS232RS449RS423RS422RS485等接口标准规范和总线标准规范,即UART是异步串行通信口的总称。而RS232、RS449、RS423、RS422和RS485等,是对应各种异步串行通信口的接口标准和总线标准,它规定了通信口的电气特性、传输速率、连接特性和接口的机械特性等内容。实际上是属于通信网络中的物理层(Physical Layer)的概念,与通信协议没有直接关系。而通信协议,是属于通信网络中的数据链路层(Data Link Layer)的概念。
  • klipper 与MCU连接使用的该协议。

SPI

  • SPI(Serial Peripheral Interface一一串行外设接口)总线是Motorola公司 推出的一种同步串行接口技术。SPI总线系统是一种同步串行外接口,允许MCU与各种外围设备以串行方式进行通信,数据交换。外围设备包括FLASHRAM、A/D转换器网络控制器、MCU等。SPI是一种高速的,全双工,同步的通信总线。
  • klipper与步进电机TMC2130 的连接使用该协议进行通信的。

posted on 2025-03-12 18:27  logicalsky  阅读(371)  评论(0)    收藏  举报

导航