具有依赖项的类似 Arduino 的 Makefile ......? [英] Arduino-like Makefile with dependencies...?

查看:24
本文介绍了具有依赖项的类似 Arduino 的 Makefile ......?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在尝试为 STM8 微控制器使用 SDCC OS 编译器.我的目标是一个(几乎)与 Arduino 类似的 NOOB 兼容设置 - 但使用 make+shellscripts 而不是 IDE(我的野心受到限制......)

I am currently trying to develop C libraries and project templates for the STM8 microcontroller using the SDCC OS compiler. My target is a (nearly) NOOB-compatible setup similar to Arduino - but with make+shellscripts instead of an IDE (there are limits to my ambition...)

目前我正在努力使用 make 来自动检测依赖项.在 Arduino 中,用户只包含相关的标题,例如#include LCD-lib",构建机制自动检测依赖并链接各自的库.无需手动将其添加到 IDE 或 Makefile.

Currently I am struggling with make to auto-detect dependencies. In Arduino the user only includes the relevant headers, e.g. "#include LCD-lib", and the build mechanism automatically detects dependency and links the respective libs. No need to manually add it to an IDE or the Makefile.

我喜欢它的简单性,但到目前为止,我在创建相应的 Makefile 方面失败了.基本上这里是 Makefile 应该实现的:

I love the simplicity of that, but so far I have failed miserably in creating a respective Makefile. Basically here's what the Makefile should achieve:

  1. 扫描项目根目录中的 *.c 文件以获取包含的头文件.请注意,这些文件位于不同的 lib 文件夹中
  2. 将所有包含的头文件和(如果存在)相应的 C 文件添加到构建过程中
  3. 为了最大限度地减少编译时间和大小,在构建期间必须跳过 lib 文件夹中未使用的 C 文件

我相信 make 可以完成上述所有操作 - 但不符合我对 make 的经验水平... :-(

I am confident that make can do all the above - but not at my level of experience with make... :-(

这是我想到的文件夹结构:

Here's the folder structure I have in mind:

├── Library
│   ├── Base
│   │   ├── general STM8 sources and headers 
│   ├── STM8S_Discovery
│   │   └── board specific sources and headers
│   └── User
│       └── optional user library sources and headers
├── Projects
│   ├── Examples (to be filled)
│   │   └── Basic_Project
│   │       ├── compile_upload.sh  --> double-click to build and upload
│   │       ├── config.h
│   │       ├── main.c
│   │       └── Makefile           --> should detect dependencies in ./*.c and ./*.h
│   └── User_Projects (still empty)
└── Tools
    ├── programmer.py              --> for programming (already works from make)
    └── terminal.py                --> for serial terminal (already works from make)

我知道有很多问题要问,但方便的 Makefile 是我的主要障碍点.任何帮助都非常感谢!!!非常感谢提前!

I know it's a lot to ask, but a convenient Makefile is my main blocking point. Any help is highly appreciated!!! Thanks a lot in advance!

问候,Georg Icking-Konert

Regards, Georg Icking-Konert

推荐答案

注意:我意识到这个答案不能满足您的所有要求,实际上这种方法仍然需要您列出相关 Arduino 的名称您在项目中使用的库,以及应包含在项目中的目录路径列表.但是,这个解决方案是我能想到的最接近您的要求的解决方案,它可能仍然有助于其他人在未来阅读此问题.

我使用 Arduino Makefile:

  1. Makefile.master 放入您的主工作区目录
  2. 当您开始一个新的 Arduino 项目时,您将其创建为工作区中的子目录
    • 创建一个带有 .pde/.ino 扩展名的文件,其中包含 setup() 和 `loop() 方法
    • 把剩下的逻辑放到.c/.cpp/.h/.hpp文件中
  1. put Makefile.master in your main work-space directory
  2. when you start a new Arduino project, you create it as a sub-directory in your workspace
    • create a single file with .pde/.ino extension containing setup() and `loop() methods
    • put the remaining logic into .c/.cpp/.h/.hpp files

添加一个 project Makefile,用于在此子目录中设置项目精炼的设置,例如:

add a project Makefile that sets project-refined settings in this sub-directory, e.g.:

# Your Arduino environment.
ARD_HOME = /usr/share/arduino
ARD_BIN = $(ARD_HOME)/hardware/tools/avr/bin

# Monitor Baudrate
MON_SPEED = 4800

# Board settings.
BOARD = uno
PORT = /dev/ttyACM0
PROGRAMMER = stk500v2

# Where to find header files and libraries.
INC_DIRS =
MY_LIB_DIRS =
LIBS =
LIB_DIRS = $(addprefix $(ARD_HOME)/libraries/, $(LIBS)) $(MY_LIB_DIRS)

include ../Makefile.master

  • 使用make allmake uploadmake monitor等编译运行

    确保您在 Unix/Linux 机器(或等效机器)上安装了 picocom 作为控制台串行监视器.在MAC-OS上,您可以通过相应地设置MON_CMD变量来使用screen.

    Ensure that you have picocom installed on your Unix/Linux machine (or equivalent) as console serial monitor. On MAC-OS, you can use screen by setting the MON_CMD variable accordingly.

    Makefile.master:

    最初的Makefile.masterAlan Burlison编写,由Matthieu Weber修改,可以在这里.

    The original Makefile.master was written by Alan Burlison and modified by Matthieu Weber, and can be found here.

    我进行了一些更改以使其适合我的配置,特别是我添加了以下代码行:

    I made some changes so that it fits my configuration, in particular I've added these lines of code:

    ### DEBUG Compilation ###
    ifeq ($(DEBUG), 1)
        ARD_FLAGS += -DDEBUG_PROJ
        C_FLAGS += -g
        CXX_FLAGS += -g
    else
        ARD_FLAGS += -DNDEBUG_PROJ
    endif
    

    并随后从 Makefile.master 中的默认 C/CXX _FLAGS 条目中删除了 -g 选项.这样符号信息就不会被添加到发布代码中,只有当代码被编译为DEBUG=1时,代码才被

    and subsequently removed -g option from default C/CXX _FLAGS entries in Makefile.master. In this way symbol information is not added on release code, and only when code is compiled with DEBUG=1 the code shielded by

    #ifdef DEBUG_PROJ
        /* debug code here */
    #endif
    // or
    #ifndef NDEBUG_PROJ
        /* debug code here */
    #endif
    

    进入二进制文件,从而产生更小的发布可执行文件.

    finds its way into the binary, thus resulting smaller release executables.

    在这里您可以找到我自己的版本的Makefile.master:

    Here you can find my own version of the Makefile.master:

    #
    # Copyright 2011 Alan Burlison, alan@bleaklow.com.  All rights reserved.
    # Subsequently modified by Matthieu Weber, matthieu.weber@jyu.fi.
    # Subsequently modified by Patrick Trentin, patrick.trentin.88@gmail.com
    # Use is subject to license terms.
    #
    # Redistribution and use in source and binary forms, with or without
    # modification, are permitted provided that the following conditions are met:
    #
    #  1. Redistributions of source code must retain the above copyright notice,
    #     this list of conditions and the following disclaimer.
    #
    #  2. Redistributions in binary form must reproduce the above copyright notice,
    #     this list of conditions and the following disclaimer in the documentation
    #     and/or other materials provided with the distribution.
    #
    # THIS SOFTWARE IS PROVIDED BY ALAN BURLISON "AS IS" AND ANY EXPRESS OR IMPLIED
    # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
    # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
    # EVENT SHALL ALAN BURLISON OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
    # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
    # OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
    # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
    # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
    # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    #
    # Makefile for building Arduino projects outside of the Arduino environment
    #
    # This makefile should be included into a per-project Makefile of the following
    # form:
    #
    # ----------
    # BOARD = mega
    # PORT = /dev/term/0
    # INC_DIRS = ../common
    # LIB_DIRS = ../libraries/Task ../../libraries/VirtualWire
    # include ../../Makefile.master
    # ----------
    #
    # Where:
    #   BOARD    : Arduino board type, from $(ARD_HOME)/hardware/boards.txt
    #   PORT     : USB port
    #   INC_DIRS : List pf directories containing header files
    #   LIB_DIRS : List of directories containing library source
    #
    # Before using this Makefile you can adjust the following macros to suit
    # your environment, either by editing this file directly or by defining them in
    # the Makefile that includes this one, in which case they will override the
    # definitions below:
    #   ARD_REV      : arduino software revision, e.g. 0017, 0018
    #   ARD_HOME     : installation directory of the Arduino software.
    #   ARD_BIN      : location of compiler binaries
    #   AVRDUDE      : location of avrdude executable
    #   AVRDUDE_CONF : location of avrdude configuration file
    #   PROGRAMMER   : avrdude programmer type
    #   MON_TERM     : terminal command for serial monitor
    #   MON_CMD      : serial monitor command
    #   MON_SPEED    : serial monitor speed
    #
    
    # Global configuration.
    ARD_REV ?= 100
    ARD_HOME ?= /usr/local/arduino
    ARD_BIN ?= /usr/bin
    AVRDUDE ?= $(ARD_HOME)/hardware/tools/avrdude
    AVRDUDE_CONF ?= $(ARD_HOME)/hardware/tools/avrdude.conf
    MON_TERM ?= xterm
    MON_SPEED ?= 57600
    MON_CMD ?= picocom
    PORT ?= $(HOME)/dev/arduino
    BOARD ?= atmega328
    
    ### Nothing below here should require editing. ###
    
    # Check for the required definitions.
    
    ifndef BOARD
        $(error $$(BOARD) not defined)
    endif
    ifndef PORT
        $(error $$(PORT) not defined)
    endif
    
    # Version-specific settings
    ARD_BOARDS = $(ARD_HOME)/hardware/arduino/boards.txt
    ARD_SRC_DIR = $(ARD_HOME)/hardware/arduino/cores/arduino
    ARD_MAIN = $(ARD_SRC_DIR)/main.cpp
    
    # Standard macros.
    SKETCH = $(notdir $(CURDIR))
    BUILD_DIR = build
    VPATH = $(LIB_DIRS)
    
    # Macros derived from boards.txt
    MCU := $(shell sed -n 's/$(BOARD)\.build\.mcu=\(.*\)/\1/p' < $(ARD_BOARDS))
    F_CPU := $(shell sed -n 's/$(BOARD)\.build\.f_cpu=\(.*\)/\1/p' < $(ARD_BOARDS))
    UPLOAD_SPEED := \
        $(shell sed -n 's/$(BOARD)\.upload\.speed=\(.*\)/\1/p' < $(ARD_BOARDS))
    PROGRAMMER := \
        $(shell sed -n 's/$(BOARD)\.upload\.protocol=\(.*\)/\1/p' < $(ARD_BOARDS))
    ARD_VAR := \
        $(shell sed -n 's/$(BOARD)\.build\.variant=\(.*\)/\1/p' < $(ARD_BOARDS))
    
    # More Version-specific settings
    ARD_VAR_DIR = $(ARD_HOME)/hardware/arduino/variants/$(ARD_VAR)
    
    # Build tools.
    CC = $(ARD_BIN)/avr-gcc
    CXX = $(ARD_BIN)/avr-g++
    CXXFILT = $(ARD_BIN)/avr-c++filt
    OBJCOPY = $(ARD_BIN)/avr-objcopy
    OBJDUMP = $(ARD_BIN)/avr-objdump
    AR = $(ARD_BIN)/avr-ar
    SIZE = $(ARD_BIN)/avr-size
    NM = $(ARD_BIN)/avr-nm
    MKDIR = mkdir -p
    RM = rm -rf
    MV = mv -f
    LN = ln -f
    
    # Compiler flags.
    INC_FLAGS = \
        $(addprefix -I,$(INC_DIRS)) $(addprefix -I,$(LIB_DIRS)) -I$(ARD_SRC_DIR) -I$(ARD_VAR_DIR)
    ARD_FLAGS = -mmcu=$(MCU) -DF_CPU=$(F_CPU) -DARDUINO=$(ARD_REV)
    C_CXX_FLAGS = \
        -Wall -Wextra -Wundef -Wno-unused-parameter \
        -fdiagnostics-show-option -Wa,-adhlns=$(BUILD_DIR)/$*.lst
    C_FLAGS = \
        $(C_CXX_FLAGS) -std=gnu99 -Wstrict-prototypes -Wno-old-style-declaration
    CXX_FLAGS = $(C_CXX_FLAGS)
    
    ### DEBUG Compilation ###
    ifeq ($(DEBUG), 1)
        ARD_FLAGS += -DDEBUG_PROJ
        C_FLAGS += -g
        CXX_FLAGS += -g
    else
        ARD_FLAGS += -DNDEBUG_PROJ
    endif
    
    # Optimiser flags.
    #     optimise for size, unsigned by default, pack data.
    #     separate sections, drop unused ones, shorten branches, jumps.
    #     don't inline, vectorise loops. no exceptions.
    #     no os preamble, use function calls in prologues.
    # http://gcc.gnu.org/onlinedocs/gcc-4.3.5/gcc/
    # http://www.tty1.net/blog/2008-04-29-avr-gcc-optimisations_en.html
    OPT_FLAGS = \
         -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums \
        -ffunction-sections -fdata-sections -Wl,--gc-sections,--relax \
        -fno-inline-small-functions -fno-tree-scev-cprop -fno-exceptions \
        -ffreestanding -mcall-prologues
    
    # Build parameters.
    IMAGE = $(BUILD_DIR)/$(SKETCH)
    ARD_C_SRC = $(wildcard $(ARD_SRC_DIR)/*.c)
    ARD_CXX_SRC = $(wildcard $(ARD_SRC_DIR)/*.cpp)
    ARD_C_OBJ = $(patsubst %.c,%.o,$(notdir $(ARD_C_SRC)))
    ARD_CXX_OBJ = $(patsubst %.cpp,%.o,$(notdir $(ARD_CXX_SRC)))
    ARD_LIB = arduino
    ARD_AR = $(BUILD_DIR)/lib$(ARD_LIB).a
    ARD_AR_OBJ = $(ARD_AR)($(ARD_C_OBJ) $(ARD_CXX_OBJ))
    ARD_LD_FLAG = -l$(ARD_LIB)
    
    # Workaround for http://gcc.gnu.org/bugzilla/show_bug.cgi?id=34734
    $(ARD_AR)(Tone.o) : CXX_FLAGS += -w
    
    # Sketch libraries.
    LIB_C_SRC = $(foreach ld,$(LIB_DIRS),$(wildcard $(ld)/*.c))
    LIB_CXX_SRC = $(foreach ld,$(LIB_DIRS),$(wildcard $(ld)/*.cpp))
    LIB_SRC = $(LIB_C_SRC) $(LIB_CXX_SRC)
    ifneq "$(strip $(LIB_C_SRC) $(LIB_CXX_SRC))" ""
        LIB_C_OBJ = $(patsubst %.c,%.o,$(notdir $(LIB_C_SRC)))
        LIB_CXX_OBJ = $(patsubst %.cpp,%.o,$(notdir $(LIB_CXX_SRC)))
        LIB_LIB = library
        LIB_AR = $(BUILD_DIR)/lib$(LIB_LIB).a
        LIB_AR_OBJ = $(LIB_AR)($(LIB_C_OBJ) $(LIB_CXX_OBJ))
        LIB_LD_FLAG = -l$(LIB_LIB)
    endif
    
    # Sketch PDE source.
    SKT_PDE_SRC = $(wildcard *.pde *.ino)
    ifneq "$(strip $(SKT_PDE_SRC))" ""
        SKT_PDE_OBJ = $(BUILD_DIR)/$(SKETCH)_pde.o
    endif
    
    # C and C++ source.
    SKT_C_SRC = $(wildcard *.c)
    SKT_CXX_SRC = $(wildcard *.cpp)
    ifneq "$(strip $(SKT_C_SRC) $(SKT_CXX_SRC))" ""
        SKT_C_OBJ = $(patsubst %.c,%.o,$(SKT_C_SRC))
        SKT_CXX_OBJ = $(patsubst %.cpp,%.o,$(SKT_CXX_SRC))
        SKT_LIB = sketch
        SKT_AR = $(BUILD_DIR)/lib$(SKT_LIB).a
        SKT_AR_OBJ = $(SKT_AR)/($(SKT_C_OBJ) $(SKT_CXX_OBJ))
        SKT_LD_FLAG = -l$(SKT_LIB)
    endif
    
    # Definitions.
    define run-cc
        @ $(CC) $(ARD_FLAGS) $(INC_FLAGS) -M -MT '$@($%)' -MF $@_$*.dep $<
        $(CC) -c $(C_FLAGS) $(OPT_FLAGS) $(ARD_FLAGS) $(INC_FLAGS) \
            $< -o $(BUILD_DIR)/$%
        @ $(AR) rc $@ $(BUILD_DIR)/$%
        @ $(RM) $(BUILD_DIR)/$%
        @ $(CXXFILT) < $(BUILD_DIR)/$*.lst > $(BUILD_DIR)/$*.lst.tmp
        @ $(MV) $(BUILD_DIR)/$*.lst.tmp $(BUILD_DIR)/$*.lst
    endef
    
    define run-cxx
        @ $(CXX) $(ARD_FLAGS) $(INC_FLAGS) -M -MT '$@($%)' -MF $@_$*.dep $<
        $(CXX) -c $(CXX_FLAGS) $(OPT_FLAGS) $(ARD_FLAGS) $(INC_FLAGS) \
            $< -o $(BUILD_DIR)/$%
        @ $(AR) rc $@ $(BUILD_DIR)/$%
        @ $(RM) $(BUILD_DIR)/$%
        @ $(CXXFILT) < $(BUILD_DIR)/$*.lst > $(BUILD_DIR)/$*.lst.tmp
        @ $(MV) $(BUILD_DIR)/$*.lst.tmp $(BUILD_DIR)/$*.lst
    endef
    
    # Rules.
    .PHONY : all clean upload monitor upload_monitor
    
    all : $(BUILD_DIR) $(IMAGE).hex
    
    clean :
        $(RM) $(BUILD_DIR)
    
    $(BUILD_DIR) :
        $(MKDIR) $@
    
    $(SKT_PDE_OBJ) : $(SKT_PDE_SRC)
        if [ $(ARD_REV) -ge 100 ]; then \
        echo '#include "Arduino.h"' > $(BUILD_DIR)/$(SKETCH)_pde.cpp; \
        else \
        echo '#include "WProgram.h"' > $(BUILD_DIR)/$(SKETCH)_pde.cpp; \
        fi
        echo '#include "$(SKT_PDE_SRC)"' >> $(BUILD_DIR)/$(SKETCH)_pde.cpp
        $(LN) $(SKT_PDE_SRC) $(BUILD_DIR)/$(SKT_PDE_SRC)
        cd $(BUILD_DIR) && $(CXX) -c $(subst build/,,$(CXX_FLAGS)) \
            $(OPT_FLAGS) $(ARD_FLAGS) -I.. \
            $(patsubst -I..%,-I../..%,$(INC_FLAGS)) \
            $(SKETCH)_pde.cpp -o $(@F)
    
    (%.o) : $(ARD_SRC_DIR)/%.c
        $(run-cc)
    
    (%.o) : $(ARD_SRC_DIR)/%.cpp
        $(run-cxx)
    
    (%.o) : %.c
        $(run-cc)
    
    (%.o) : %.cpp
        $(run-cxx)
    
    $(BUILD_DIR)/%.d : %.c
        $(run-cc-d)
    
    $(BUILD_DIR)/%.d : %.cpp
        $(run-cxx-d)
    
    $(IMAGE).hex : $(ARD_AR_OBJ) $(LIB_AR_OBJ) $(SKT_AR_OBJ) $(SKT_PDE_OBJ)
        $(CC) $(CXX_FLAGS) $(OPT_FLAGS) $(ARD_FLAGS) -L$(BUILD_DIR) \
            $(SKT_PDE_OBJ) $(SKT_LD_FLAG) $(LIB_LD_FLAG) $(ARD_LD_FLAG) -lm \
            -o $(IMAGE).elf
        $(OBJCOPY) -O ihex -j .eeprom --set-section-flags=.eeprom=alloc,load \
            --no-change-warnings --change-section-lma .eeprom=0 $(IMAGE).elf \
            $(IMAGE).eep
        $(OBJCOPY) -O ihex -R .eeprom $(IMAGE).elf $(IMAGE).hex
        $(OBJDUMP) -h -S $(IMAGE).elf | $(CXXFILT) -t > $(IMAGE).lst
        $(SIZE) $(IMAGE).hex
    
    upload : all
        - pkill -f '$(MON_CMD).*$(PORT)'
        - sleep 1
        - stty -F $(PORT) hupcl
        - $(AVRDUDE) -V -C$(AVRDUDE_CONF) -p$(MCU) -c$(PROGRAMMER) -P$(PORT) \
            -b$(UPLOAD_SPEED) -D -Uflash:w:$(IMAGE).hex:i
    
    monitor :
        LD_LIBRARY_PATH= LD_PRELOAD= \
            $(MON_TERM) -title '$(BOARD) $(PORT)' \
            -e '$(MON_CMD) -b $(MON_SPEED) $(PORT)' &
    
    upload_monitor : upload monitor
    
    -include $(wildcard $(BUILD_DIR)/*.dep))
    # vim:ft=make
    

    <小时>

    使用示例:

    给定一个目录树,如下所示:

    Base_Dir
    ├── Library
    │   ├── Base
    │   │   ├── general STM8 sources and headers 
    │   ├── STM8S_Discovery
    │   │   └── board specific sources and headers
    │   └── User
    │       └── optional user library sources and headers
    ├── Projects
    │   ├── Examples (to be filled)
    │   │   └── Basic_Project
    │   │       ├── config.h
    │   │       ├── example.ino
    │   │       └── Makefile           --> should detect dependencies in ./*.c and ./*.h
    ...
    

    您可以将 Makefile.master 放在 Projects 中,然后假设:

    You could place Makefile.master within Projects, then assuming that:

    • 你只需要这个项目的Library/BaseLibrary/User中的内容
    • 您需要在您的项目中使用 LiquidCrystal Arduino 库
    • You only need what is in Library/Base and Library/User for this project
    • You need to use LiquidCrystal Arduino Library in your project

    然后将以下 Makefile 添加到 Basic Project 中:

    then you would add the following Makefile into Basic Project:

    # Your Arduino environment.
    BASE_DIR = /path/to/Base_Dir              # to edit
    ARD_HOME = /usr/share/arduino             # to edit, maybe
    ARD_BIN = $(ARD_HOME)/hardware/tools/avr/bin
    
    # Monitor Baudrate
    MON_SPEED = 4800
    
    # Board settings.
    BOARD = uno
    PORT = /dev/ttyACM0
    PROGRAMMER = stk500v2
    
    # Where to find header files and libraries.
    INC_DIRS =
    MY_LIB_DIRS= $(BASE_DIR)/Library/Base $(BASE_DIR)/Library/User 
    LIBS= LiquidCrystal
    LIB_DIRS = $(addprefix $(ARD_HOME)/libraries/, $(LIBS)) $(MY_LIB_DIRS)
    
    include ../../Makefile.master
    

    注意common.h应该是自动检测的,因为它位于.中,应该不需要将后者添加到INC_DIRS代码>.

    Note that common.h should be automatically detected because it is located in ., and there should be no need to add the latter to INC_DIRS.

    最终说明:上次我测试这个配置时,我使用的是 Arduino 源代码的 1.0.5 版本,它运行完美.

    Final Note: last time I tested this configuration I was using version 1.0.5 of Arduino source code, and it was working flawlessly.

    这篇关于具有依赖项的类似 Arduino 的 Makefile ......?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆