Android P 蓝牙与PC端连接失败

本文深入探讨了蓝牙配对失败的常见原因,包括PIN码或配对密钥错误,以及如何在Android系统中定位和解决这些问题。通过分析系统日志和代码路径,详细解释了从用户界面到底层协议栈的错误处理流程。

字符有:PIN码或配对密钥不正确,无法与
对应的字符串为:bluetooth_pairing_pin_error_message

frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java

 /**
         * Called when we have reached the unbonded state.
         *
         * @param reason one of the error reasons from
         *            BluetoothDevice.UNBOND_REASON_*
         */
        private void showUnbondMessage(Context context, String name, int reason) {
            int errorMsg;

            switch(reason) {
            case BluetoothDevice.UNBOND_REASON_AUTH_FAILED:
                errorMsg = R.string.bluetooth_pairing_pin_error_message;
                break;
            case BluetoothDevice.UNBOND_REASON_AUTH_REJECTED:
                errorMsg = R.string.bluetooth_pairing_rejected_error_message;
                break;
            case BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN:
                errorMsg = R.string.bluetooth_pairing_device_down_error_message;
                break;
            case BluetoothDevice.UNBOND_REASON_DISCOVERY_IN_PROGRESS:
            case BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT:
            case BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS:
            case BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED:
                errorMsg = R.string.bluetooth_pairing_error_message;
                break;
            default:
                Log.w(TAG, "showUnbondMessage: Not displaying any message for reason: " + reason);
                return;
            }
            Utils.showError(context, name, errorMsg);
        }
    }

继续查看调用showUnbondMessage的位置:

private class BondStateChangedHandler implements Handler {
        public void onReceive(Context context, Intent intent,
                BluetoothDevice device) {
            if (device == null) {
                Log.e(TAG, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE");
                return;
            }
            int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
                                               BluetoothDevice.ERROR);
            CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
            if (cachedDevice == null) {
                Log.w(TAG, "CachedBluetoothDevice for device " + device +
                        " not found, calling readPairedDevices().");
                if (readPairedDevices()) {
                    cachedDevice = mDeviceManager.findDevice(device);
                }

                if (cachedDevice == null) {
                    Log.w(TAG, "Got bonding state changed for " + device +
                            ", but we have no record of that device.");

                    cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
                    dispatchDeviceAdded(cachedDevice);
                }
            }

            synchronized (mCallbacks) {
                for (BluetoothCallback callback : mCallbacks) {
                    callback.onDeviceBondStateChanged(cachedDevice, bondState);
                }
            }
            cachedDevice.onBondingStateChanged(bondState);

            if (bondState == BluetoothDevice.BOND_NONE) {
                /* Check if we need to remove other Hearing Aid devices */
                if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
                    mDeviceManager.onDeviceUnpaired(cachedDevice);
                }
                int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON,
                        BluetoothDevice.ERROR);

                showUnbondMessage(context, cachedDevice.getName(), reason);
            }
        }

该错误的提示已异常为:BluetoothDevice.UNBOND_REASON_AUTH_FAILED
找到蓝牙服务的位置, 根据蓝牙状态机的显示有:
packages/apps/Bluetooth/src/com/android/bluetooth/btservice/BondStateMachine.java

 private int getUnbondReasonFromHALCode(int reason) {
        if (reason == AbstractionLayer.BT_STATUS_SUCCESS) {
            return BluetoothDevice.BOND_SUCCESS;
        } else if (reason == AbstractionLayer.BT_STATUS_RMT_DEV_DOWN) {
            return BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN;
        } else if (reason == AbstractionLayer.BT_STATUS_AUTH_FAILURE) {
            return BluetoothDevice.UNBOND_REASON_AUTH_FAILED;
        } else if (reason == AbstractionLayer.BT_STATUS_AUTH_REJECTED) {
            return BluetoothDevice.UNBOND_REASON_AUTH_REJECTED;
        } else if (reason == AbstractionLayer.BT_STATUS_AUTH_TIMEOUT) {
            return BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT;
        }

        /* default */
        return BluetoothDevice.UNBOND_REASON_REMOVED;
    }

可以知道是:AbstractionLayer.BT_STATUS_AUTH_FAILURE这个异常导致;
此处有蓝牙状态变化的处理函数:

 private class PendingCommandState extends State {

        @Override
        public void enter() {
            infoLog("Entering PendingCommandState State");
            BluetoothDevice dev = (BluetoothDevice) getCurrentMessage().obj;
        }

        @Override
        public synchronized boolean processMessage(Message msg) {
            BluetoothDevice dev = (BluetoothDevice) msg.obj;
            DeviceProperties devProp = mRemoteDevices.getDeviceProperties(dev);
            boolean result = false;
            if (mDevices.contains(dev) && msg.what != CANCEL_BOND
                    && msg.what != BONDING_STATE_CHANGE && msg.what != SSP_REQUEST
                    && msg.what != PIN_REQUEST) {
                deferMessage(msg);
                return true;
            }

            switch (msg.what) {
                case CREATE_BOND:
                    OobData oobData = null;
                    if (msg.getData() != null) {
                        oobData = msg.getData().getParcelable(OOBDATA);
                    }

                    result = createBond(dev, msg.arg1, oobData, false);
                    break;
                case REMOVE_BOND:
                    result = removeBond(dev, false);
                    break;
                case CANCEL_BOND:
                    result = cancelBond(dev);
                    break;
                case BONDING_STATE_CHANGE:
                    int newState = msg.arg1;
                    int reason = getUnbondReasonFromHALCode(msg.arg2);
                    sendIntent(dev, newState, reason);
                    if (newState != BluetoothDevice.BOND_BONDING) {
                        // check if bond none is received from device which
                        // was in pairing state otherwise don't transition to
                        // stable state.
                        if (newState == BluetoothDevice.BOND_NONE &&
                            !mDevices.contains(dev) && mDevices.size() != 0) {
                            infoLog("not transitioning to stable state");
                            break;
                        }
                        /* this is either none/bonded, remove and transition */
                        result = !mDevices.remove(dev);
                        if (mDevices.isEmpty()) {
                            // Whenever mDevices is empty, then we need to
                            // set result=false. Else, we will end up adding
                            // the device to the list again. This prevents us
                            // from pairing with a device that we just unpaired
                            result = false;
                            transitionTo(mStableState);
                        }
                        if (newState == BluetoothDevice.BOND_NONE) {
                            mAdapterService.setPhonebookAccessPermission(dev,
                                    BluetoothDevice.ACCESS_UNKNOWN);
                            mAdapterService.setMessageAccessPermission(dev,
                                    BluetoothDevice.ACCESS_UNKNOWN);
                            mAdapterService.setSimAccessPermission(dev,
                                    BluetoothDevice.ACCESS_UNKNOWN);
                            // Set the profile Priorities to undefined
                            clearProfilePriority(dev);
                        }
                    } else if (!mDevices.contains(dev)) {
                        result = true;
                    }
                    break;
                case SSP_REQUEST:
                    int passkey = msg.arg1;
                    int variant = msg.arg2;
                    if (devProp == null)
                    {
                        Log.e(TAG,"Received msg from an unknown device");
                        return false;
                    }
                    sendDisplayPinIntent(devProp.getAddress(), passkey, variant);
                    break;
                case PIN_REQUEST:
                    BluetoothClass btClass = dev.getBluetoothClass();
                    int btDeviceClass = btClass.getDeviceClass();
                    if (devProp == null)
                    {
                        Log.e(TAG,"Received msg from an unknown device");
                        return false;
                    }
                    if (btDeviceClass == BluetoothClass.Device.PERIPHERAL_KEYBOARD || btDeviceClass
                            == BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING) {
                        // Its a keyboard. Follow the HID spec recommendation of creating the
                        // passkey and displaying it to the user. If the keyboard doesn't follow
                        // the spec recommendation, check if the keyboard has a fixed PIN zero
                        // and pair.
                        //TODO: Maintain list of devices that have fixed pin
                        // Generate a variable 6-digit PIN in range of 100000-999999
                        // This is not truly random but good enough.
                        int pin = 100000 + (int) Math.floor((Math.random() * (999999 - 100000)));
                        sendDisplayPinIntent(devProp.getAddress(), pin,
                                BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN);
                        break;
                    }

                    if (msg.arg2 == 1) { // Minimum 16 digit pin required here
                        sendDisplayPinIntent(devProp.getAddress(), 0,
                                BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS);
                    } else {
                        // In PIN_REQUEST, there is no passkey to display.So do not send the
                        // EXTRA_PAIRING_KEY type in the intent( 0 in SendDisplayPinIntent() )
                        sendDisplayPinIntent(devProp.getAddress(), 0,
                                BluetoothDevice.PAIRING_VARIANT_PIN);
                    }

                    break;
                default:
                    Log.e(TAG, "Received unhandled event:" + msg.what);
                    return false;
            }
            if (result) {
                mDevices.add(dev);
            }

            return true;
        }
    }

根据该函数的调用位置可以知道是BONDING_STATE_CHANGE变化时候的提示;
继续追踪可以找到是蓝牙协议调用层:
vendor/qcom/opensource/commonsys/system/bt/btif/src/btif_dm.cc

/*******************************************************************************
 *
 * Function         btif_dm_auth_cmpl_evt
 *
 * Description      Executes authentication complete event in btif context
 *
 * Returns          void
 *
 ******************************************************************************/
static void btif_dm_auth_cmpl_evt(tBTA_DM_AUTH_CMPL* p_auth_cmpl) {
  /* Save link key, if not temporary */
  bt_status_t status = BT_STATUS_FAIL;
  bt_bond_state_t state = BT_BOND_STATE_NONE;
 if (p_auth_cmpl->success) {
// Do not call bond_state_changed_cb yet. Wait until remote service
    // discovery is complete
  } else {
    // Map the HCI fail reason  to  bt status
    switch (p_auth_cmpl->fail_reason) {
      case HCI_ERR_PAGE_TIMEOUT:
      case HCI_ERR_LMP_RESPONSE_TIMEOUT:
        if (pairing_cb.timeout_retries) {
          BTIF_TRACE_WARNING("%s() - Pairing timeout; retrying (%d) ...",
                             __func__, pairing_cb.timeout_retries);
          --pairing_cb.timeout_retries;
          btif_dm_cb_create_bond(bd_addr, BTA_TRANSPORT_UNKNOWN);
          return;
        }
      /* Fall-through */
      case HCI_ERR_CONNECTION_TOUT:
        status = BT_STATUS_RMT_DEV_DOWN;
        break;

      case HCI_ERR_PAIRING_NOT_ALLOWED:
        btif_storage_remove_bonded_device(&bd_addr);
        status = BT_STATUS_AUTH_REJECTED;
        break;

      /* Dont fail the bonding for key missing error as stack retry security */
      case HCI_ERR_KEY_MISSING:
        btif_storage_remove_bonded_device(&bd_addr);
        if (p_auth_cmpl->is_sm4_dev) {
          return;
        } else {
          BTIF_TRACE_WARNING("%s() legacy remote ,move bond state to none", __FUNCTION__);
        }

      /* map the auth failure codes, so we can retry pairing if necessary */
      case HCI_ERR_AUTH_FAILURE:
        btif_storage_remove_bonded_device(&bd_addr);
      case HCI_ERR_HOST_REJECT_SECURITY:
      case HCI_ERR_ENCRY_MODE_NOT_ACCEPTABLE:
      case HCI_ERR_UNIT_KEY_USED:
      case HCI_ERR_PAIRING_WITH_UNIT_KEY_NOT_SUPPORTED:
      case HCI_ERR_INSUFFCIENT_SECURITY:
      case HCI_ERR_PEER_USER:
      case HCI_ERR_UNSPECIFIED:
      case HCI_ERR_REPEATED_ATTEMPTS:
        BTIF_TRACE_DEBUG(" %s() Authentication fail reason %d",
                __FUNCTION__, p_auth_cmpl->fail_reason);
        if (pairing_cb.autopair_attempts  == 1) {
          /* Create the Bond once again */
          BTIF_TRACE_WARNING("%s() auto pair failed. Reinitiate Bond",
                             __func__);
          btif_dm_cb_create_bond(bd_addr, BTA_TRANSPORT_UNKNOWN);
          return;
        } else {

          /* if autopair attempts are more than 1, or not attempted */
          status =  BT_STATUS_AUTH_FAILURE;
        }
        break;

      default:
        status = BT_STATUS_FAIL;
    }

查看注释上写明:

 /* if autopair attempts are more than 1, or not attempted */
          status =  BT_STATUS_AUTH_FAILURE;

另外还有一处调用:

/*******************************************************************************
 *
 * Function         btif_dm_ble_auth_cmpl_evt
 *
 * Description      Executes authentication complete event in btif context
 *
 * Returns          void
 *
 ******************************************************************************/
static void btif_dm_ble_auth_cmpl_evt(tBTA_DM_AUTH_CMPL* p_auth_cmpl) {
  /* Save link key, if not temporary */
  bt_status_t status = BT_STATUS_FAIL;
  bt_bond_state_t state = BT_BOND_STATE_NONE;

  RawAddress bd_addr = p_auth_cmpl->bd_addr;

  /* Clear OOB data */
  memset(&oob_cb, 0, sizeof(oob_cb));

  if ((p_auth_cmpl->success == true) && (p_auth_cmpl->key_present)) {
    /* store keys */
  }
  if (p_auth_cmpl->success) {
    status = BT_STATUS_SUCCESS;
    state = BT_BOND_STATE_BONDED;
    int addr_type;
    RawAddress bdaddr = p_auth_cmpl->bd_addr;
    if (btif_storage_get_remote_addr_type(&bdaddr, &addr_type) !=
        BT_STATUS_SUCCESS)
      btif_storage_set_remote_addr_type(&bdaddr, p_auth_cmpl->addr_type);

    /* Test for temporary bonding */
    if (btm_get_bond_type_dev(p_auth_cmpl->bd_addr) == BOND_TYPE_TEMPORARY) {
      BTIF_TRACE_DEBUG("%s: sending BT_BOND_STATE_NONE for Temp pairing",
                       __func__);
      btif_storage_remove_bonded_device(&bdaddr);
      state = BT_BOND_STATE_NONE;
    } else {
      btif_dm_save_ble_bonding_keys();
      BTA_GATTC_Refresh(bd_addr);
      if(!p_auth_cmpl->smp_over_br)
         btif_dm_get_remote_services_by_transport(&bd_addr, BTA_GATT_TRANSPORT_LE);
      else
         btif_dm_get_remote_services(bd_addr);
    }
  } else {
    /*Map the HCI fail reason  to  bt status  */
    switch (p_auth_cmpl->fail_reason) {
      case BTA_DM_AUTH_SMP_PAIR_AUTH_FAIL:
      case BTA_DM_AUTH_SMP_CONFIRM_VALUE_FAIL:
      case BTA_DM_AUTH_SMP_UNKNOWN_ERR:
      case BTA_DM_AUTH_SMP_CONN_TOUT:
        btif_dm_remove_ble_bonding_keys();
        status = BT_STATUS_AUTH_FAILURE;
        break;
      case BTA_DM_AUTH_SMP_PAIR_NOT_SUPPORT:
        status = BT_STATUS_AUTH_REJECTED;
        break;
      default:
        btif_dm_remove_ble_bonding_keys();
        status = BT_STATUS_FAIL;
        break;
    }
  }
  bond_state_changed(status, bd_addr, state);
}

以上两种方式,我们初步可以判断是BLE协议和传统蓝牙协议;
而PC端与设备端调用我们初步判断是传统蓝牙协议,故执行btif_dm_auth_cmpl_evt


    case BTA_DM_AUTH_CMPL_EVT:
      btif_dm_auth_cmpl_evt(&p_data->auth_cmpl);
      break;

    case BTA_DM_BLE_AUTH_CMPL_EVT:
      BTIF_TRACE_DEBUG("BTA_DM_BLE_AUTH_CMPL_EVT. ");
      btif_dm_ble_auth_cmpl_evt(&p_data->auth_cmpl);
      break;

后续可以抓取log一步步跟踪尝试。
临时修改方案,当Auth异常时候,不显示其Toast显示,直接以log信息提示即可。

<think>首先,用户的问题是关于如何通过单片机更改蓝牙模块的设备名称。用户提到了参考引用,但这些引用是关于蓝牙串口模块的一般信息,是直接针对更改设备名称的。我需要基于我的知识和这些引用提供帮助。 关键点: - 用户想修改单片机连接蓝牙设备名称。 - 具体来说,是通过单片机更改蓝牙模块的设备名称。 - 引用中提到了蓝牙串口模块作为主设备发起连接,以及透传技术。 系统级指令: - 所有行内数学表达式用 $...$ 格式。 - 独立公式用 $$...$$ 格式并单独成段。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾自然地添加引用标识,如 [^1]。 - 回答结构清晰,逐步解决问题。 - 尽量保证回答真实可靠。 我的知识: - 蓝牙模块通常通过AT命令来配置设置,如设备名称。 - 单片机可以通过串口发送AT命令到蓝牙模块来更改设备名称。 - 步骤大致包括:初始化串口通信,发送特定AT命令(如AT+NAME=new_name),然后处理响应。 从引用中提取相关信息: - 引用[1]:蓝牙串口模块可以配置为主设备,发起连接。这暗示了模块可以通过命令配置。 - 引用[2]:提到了透传技术,数据原样传输。更改设备名称可能需要发送配置命令,而是透传数据。 回答结构: 1. **介绍问题**:简要解释更改蓝牙设备名称的重要性。 2. **步骤详解**:逐步说明如何通过单片机发送AT命令来更改设备名称。 3. **代码示例**:提供伪代码或具体代码示例(基于通用单片机)。 4. **注意事项**:提醒用户可能的陷阱。 5. **生成相关问题**:在最后添加相关问题。 确保使用中文。 生成相关问题: - 系统指令要求生成相关问题,放在最后,用 §§相关问题§§ 标记。 - 例如:如何测试更改是否成功?其他可配置的参数?常见错误处理? 具体步骤: 1. **理解蓝牙模块**:大多数蓝牙模块(如HC-05, HC-06)支持AT命令模式。 2. **进入AT模式**:通常需要将模块的KEY引脚拉高或发送特定序列。 3. **发送AT命令**:命令如AT+NAME="NewName"。 4. **处理响应**:模块会返回OK或错误。 5. **退出AT模式**:恢复正常操作。 代码示例:使用伪代码或基于Arduino/C的示例,因为用户提到了单片机。 引用标识:在回答中,如果引用站内信息,添加 [^1] 或 [^2]。但用户提供的引用是直接相关,所以只在必要时引用。 最终回答框架: - 开头:直接回答问题。 - 中间:详细步骤。 - 结尾:相关问题。</think>### 如何通过单片机更改蓝牙模块的设备名称 通过单片机更改蓝牙模块的设备名称是一个常见的需求,例如在嵌入式系统中自定义蓝牙设备标识以方便识别和管理。这通常通过发送AT命令(配置命令)来实现,因为蓝牙串口模块(如常见的HC-05或HC-06模块)支持AT指令集进行参数配置[^1]。以下是详细步骤,我将逐步解释整个过程,确保结构清晰可靠。整个过程基于标准蓝牙串口模块的操作,假设您已连接单片机和蓝牙模块(通常通过UART串口)。 #### 步骤1: 准备硬件和进入AT命令模式 蓝牙模块在正常工作模式下是数据透传模式(即数据原样传输)[^2],但更改设备名称需要先进入AT命令配置模式。操作步骤如下: - **硬件连接**:确保单片机的UART引脚(如TX、RX)正确连接蓝牙模块的对应引脚。同时,大多数蓝牙模块有一个KEY或EN引脚用于切换模式: - 将KEY引脚拉高(接VCC,通常3.3V或5V)以进入AT命令模式。模块上的LED指示灯通常会慢闪(如每秒一次),表示进入配置状态。 - 如果模块没有KEY引脚,可能需要发送特定序列(如"+++")来激活AT模式,具体取决于模块型号(参考模块手册)。 - **串口初始化**:在单片机代码中,初始化UART串口通信,设置波特率(通常为38400或9600,默认值见模块文档)。例如,对于51单片机,波特率需匹配模块的AT模式设置。 #### 步骤2: 发送AT命令更改设备名称 在AT命令模式下,通过单片机发送标准AT命令来修改设备名称。命令格式一般为`AT+NAME=<new_name>`,其中`<new_name>`是您自定义的名称(例如"New_Device")。关键点: - **命令语法**:确保名称用双引号包裹,且长度符合模块限制(通常超过20字符)。发送后,模块会返回响应(如"OK"表示成功)。 - **发送示例**:使用单片机的串口发送函数,连续发送命令字符串。注意添加回车换行符(`\r\n`)作为命令结束符,因为AT命令通常需要此格式。 - **处理响应**:单片机应监听串口接收缓冲区,读取模块的响应(如"OK"或"ERROR")。如果响应超时或无响应,检查波特率或连接。 以下是一个基于51单片机的简单代码示例(使用C语言和Keil环境)。假设蓝牙模块已连接至P3.0 (RX) 和 P3.1 (TX),波特率设为9600: ```c #include <reg52.h> #include <string.h> #include <stdio.h> // 串口初始化函数 void UART_Init() { TMOD = 0x20; // 定时器1工作在模式2(自动重载) TH1 = 0xFD; // 波特率9600(晶振11.0592MHz) TL1 = 0xFD; TR1 = 1; // 启动定时器1 SCON = 0x50; // 串口模式1,允许接收 ES = 1; // 允许串口中断 EA = 1; // 全局中断允许 } // 串口发送字符串函数 void UART_SendString(char *str) { while (*str != '\0') { SBUF = *str; // 发送字符 while (!TI); // 等待发送完成 TI = 0; // 清除发送中断标志 str++; } } void main() { UART_Init(); // 初始化串口 Delay_ms(1000); // 延时1秒,确保模块稳定(自定义延时函数) // 发送AT命令更改设备名称(例如改为"MyBT_Module") UART_SendString("AT+NAME=\"MyBT_Module\"\r\n"); // 发送命令,注意引号和结束符 // 等待并处理响应(简化版:实际中应添加接收解析逻辑) while (1) { if (RI) { // 如果接收到数据 char response = SBUF; RI = 0; // 清除接收中断标志 // 这里可添加代码检查响应是否为"OK" // 例如:if (strstr(response, "OK")) { /* 成功 */ } } } } ``` #### 步骤3: 退出AT模式并验证更改 - **退出AT模式**:更改名称后,发送命令`AT+RESET`或拉低KEY引脚(如果使用硬件控制),使模块重启并返回透传模式[^2]。LED指示灯会恢复快闪,表示可正常通信。 - **验证更改**:使用手机或PC蓝牙扫描工具(如Android的"蓝牙扫描器"APP)搜索设备,检查新名称是否生效。如果未显示,可能是命令格式错误或波特率匹配。 #### 注意事项 - **模块兼容性**:蓝牙模块(如HC-05 vs HC-06)的AT命令可能略有差异。务必查阅您的模块手册(例如,HC-06的命令是`AT+NAMExxx`)。如果确定,先发送`AT`测试命令检查连接。 - **波特率问题**:AT模式和工作模式的波特率可能同。如果发送命令无响应,尝试常见波特率(9600、38400、115200)。 - **名称规则**:设备名称能包含特殊字符(如空格或中文),且长度有限制。避免名称冲突。 - **错误处理**:在代码中添加超时机制和错误重试逻辑(如发送失败后重新初始化串口)。 - **电源稳定**:确保模块供电稳定(3.3V或5V),电压波动可能导致配置失败。 通过以上步骤,您可以可靠地更改蓝牙模块名称。此方法基于标准AT命令集,适用于大多数串口透传模块[^1][^2]。如果遇到问题,提供模块型号可进一步诊断。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值