CH343 使用USB转串口发送CAN报文

文章目录

    • 原启
    • UART 走CAN收发器
    • CH343 模拟CAN发送
    • CPP ASIO SocketCAN
    • VXCAN
    • Github Link

原启

早些年自动驾驶激光雷达还不支持PTP之类的时间同步, 很多都是用PPS时间同步, 激光雷达一般装的离控制器或者GNSS天线较远, 车上的线束一般数据电源各种都包在一起的, 如果3.3V直接从域控制器出, 信号将惨不忍睹, 为了解决长距离3.3V PPS传输受干扰的问题, 各种骚操作都整出来:

  • 同轴线, 走SMA或者FAKRA接口出
  • 高电压大电流, 最简单的拿个三极管反相, 12V出, 接收端再反过来, 同时增大传输线中的电流, 抗干扰效果不错

直到看到某家域控制器居然是拿CANFD收发器来传输PPS, 顿时三观炸裂, 直呼卧槽.

CAN收发器通俗讲只是电平转换, 和串口分TTL, RS232, RS485类似, 协议大体相似, 只是传输电平不同. 传PPS自然没有问题. 那大胆一点, UART远距离传输直接走CAN收发器是不是也行. 毕竟国产的CAN收发器价格上和RS232的电平转换芯片也相差不多了.

UART 走CAN收发器

那就这样测试一下

在这里插入图片描述

实物图

在这里插入图片描述

果然可以

在这里插入图片描述

需要注意的地方:

  • UART收发独立, 是全双工的, 如果收发不能时间上错开, 那收发引脚就各自接一片CAN收发器

那再扩展一下, B站上都能拿CH340来当音频设备放歌了, 这CH343能到6Mbps, 模拟下500Kbit/s的CAN发送, 和CAN分析仪通信好像问题不大.

CH343 模拟CAN发送

串口通常的帧格式为 空闲高, 1低电平起始位 + 5到9数据位 + 0或1校验位 + 1到2停止位

在这里插入图片描述
在这里插入图片描述

以CAN标准数据帧为例, 刚好也是空闲高电平, 1bit帧起始(0), 11bitID, 1bit远程帧, 1bit扩展帧, 1bit占位符(0), 4位的数据长度代码, 0~8字节的数据, 15+1bit的CRC, 1+1bit的ACK(01), 7bit帧结束(1111111), 7bit帧间隔(1111111), 来源 Introduction to the Controller Area Network (CAN) (Rev. B)

在这里插入图片描述

还要注意填充位: 在相同逻辑电平的五个连续位之后, 需要插入1bit相反电平, 不然会被认为错误帧.

逻辑分析仪抓出来的波形:

在这里插入图片描述

CH343模拟CAN发送 的原理分析:

  • 串口助手上可以设置数据位5 6 7 8, 校验位None Even Mark Odd Space, 停止位 1 1.5 2, 也就是串口一个字节最少是1起始位+5数据位+0校验位+1停止位, 也就是7bit, 最多1-8-1-2, 也就是14bit
  • 这里500Kbit/s进行CAN测试, CAN或CANFD的位的采样点一般在 75% ~ 87.5%, 串口的起始位和停止位控制不了, 所以要尽量让CAN的采样点落在串口的数据位, 把数据位搞的多多的, 停止位和校验位搞的少少的
  • 给串口4Mbps, 要想串口一字节(一帧的意思)对应500Kbit/s的CAN的一位, 那串口一个字节8bit, 对应 1起始位 + 0停止位 + 1停止位 搞到最小, 剩下最多的 6bit 都给数据, CAN采样点 80% 会落在 8 * 80% = 6.4, 刚好在数据的最后1bit, 能控制就挺好

上面逻辑分析仪抓出来的CAN Bits行数据 000100100011000001010101011111001101110010001011111111

1bit膨胀成UART的1帧(字节), 直接翻译成十六进制串口数据, 0 翻译成00, 1翻译成全1, 6bit数据对应0b111111, 也就是0x3F, 这里测试发现直接写成0xFF也没有问题

00 00 00 FF 00 00 FF 00 00 00 FF FF 00 00 00 00 00 FF 00 FF 00 FF 00 FF 00 FF FF FF FF FF 00 00 FF FF 00 FF FF FF 00 00 FF 00 00 00 FF 00 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF

因为串口空闲也是高电平, 所以最后的FF都不发了, ACK是其它收发器回的, 也不用发, 最终发的是

00 00 00 FF 00 00 FF 00 00 00 FF FF 00 00 00 00 00 FF 00 FF 00 FF 00 FF 00 FF FF FF FF FF 00 00 FF FF 00 FF FF FF 00 00 FF 00 00 00 FF

连线
在这里插入图片描述

实物

在这里插入图片描述

先打开CAN分析仪 500Kbit/s, 80%采样点, 开启内部终端电阻.

再打开逻辑分析仪, 给通道同时配置UART和CAN协议解析.

最后打开串口调试助手, 4Mbps, 数据位6, 校验位None, 停止位1, 十六进制定时发送, 周期10ms

在这里插入图片描述

在CAN分析仪上位机中可以看到收到了数据(指示灯有几秒一次的闪红错误帧, 大概有1/19~1/20的错误帧)

在这里插入图片描述

逻辑分析仪的抓取

在这里插入图片描述

放大一下

在这里插入图片描述

功能上好像还点效果的. 至于偶发的错误帧, 大概是下图这样某帧发送32字节后, 没有连续发送的情形, 导致CAN的CRC算出来不正确, 可能是串口4M高速率的整包被USB截断了?

在这里插入图片描述

串口定时发送周期从 10ms 改成 100ms 后, 错误帧出现的概率依然不太变化, 情况并没有改善.

如果不用USB的包传输, 直接使用MCU的串口模拟CAN发送, 应该不会出现这样的问题? 但也要注意MCU的串口一般16字节FIFO, 可能要用DMA来搬数据, 不要出现传16字节断一会的情况.

换成 2Mbps, 数据位6, 校验位None, 停止位1, 十六进制定时发送, 周期10ms, 对应的CAN速率250kbit/s, 效果好了很多, 几乎不出现错误帧了:

在这里插入图片描述

下面用 Cpp Asio SocketCAN 写下任意帧发送的代码试试, 在WSL里面测试一下.

CPP ASIO SocketCAN

先把CH343挂载进WSL里面:

在这里插入图片描述

下面的代码先把SocketCAN的数据的每1bit, 扩充成串口的每一个字节, bit 0对应0x00, bit1 对应0xFF, 然后计算CRC15, 最后插入填充位, 进行发送:

#include <linux/can.h>

#include <asio.hpp>
#include <chrono>
#include <cstdbool>
#include <cstdint>
#include <iostream>
#include <thread>
#include <vector>

int main(int argc, char *argv[]) {
  asio::io_context iocxt;

  // serial, 1 start bit, 8 data bits, 1 stop bit, no parity, 2.5M baud
  // CAN 100K, Serial 1M, 1 start bit, 8 data bits, 1 stop bit, no parity
  // CAN 250K, Serial 2M, 1 start bit, 6 data bits, 1 stop bit, no parity
  // CAN 500K, Serial 4M, 1 start bit, 6 data bits, 1 stop bit, no parity
  asio::serial_port ser(iocxt, "/dev/ttyACM0");
  ser.set_option(asio::serial_port::baud_rate(2000000));
  ser.set_option(asio::serial_port::character_size(6));
  ser.set_option(
      asio::serial_port::stop_bits(asio::serial_port::stop_bits::one));
  ser.set_option(asio::serial_port::parity(asio::serial_port::parity::none));
  ser.set_option(
      asio::serial_port::flow_control(asio::serial_port::flow_control::none));
  if (!ser.is_open()) {
    std::cerr << "Failed to open serial port" << std::endl;
    return -1;
  }

  constexpr uint8_t Bit0 = 0x00;
  constexpr uint8_t Bit1 = 0xFF;

  // can crc15 calculation
  auto crc15 = [&](const std::vector<uint8_t> &data) {
    bool crc[15] = {0};
    for (int i = 0; i < data.size(); i++) {
      bool inv = (data[i] == Bit1) ^ crc[14];
      crc[14] = crc[13] ^ inv;
      crc[13] = crc[12];
      crc[12] = crc[11];
      crc[11] = crc[10];
      crc[10] = crc[9] ^ inv;
      crc[9] = crc[8];
      crc[8] = crc[7] ^ inv;
      crc[7] = crc[6] ^ inv;
      crc[6] = crc[5];
      crc[5] = crc[4];
      crc[4] = crc[3] ^ inv;
      crc[3] = crc[2] ^ inv;
      crc[2] = crc[1];
      crc[1] = crc[0];
      crc[0] = inv;
    }
    uint16_t res = 0;
    for (int i = 0; i < 15; i++) {
      res |= crc[i] << i;
    }
    return res;
  };

  // fill stuff bits
  auto fsb_insert = [&](std::vector<uint8_t> &data) {
    uint8_t count = 0;
    bool last = true;
    std::vector<uint8_t> newdata;
    for (auto it = data.begin(); it != data.end(); it++) {
      bool current = *it == Bit0 ? false : true;
      newdata.push_back(*it);
      if (current == last) {
        count++;
      } else {
        count = 1;
      }
      if (count == 5) {
        newdata.push_back(current ? Bit0 : Bit1);
        count = 1;
        last = !current;
      } else {
        last = current;
      }
    }
    return newdata;
  };

  // socketcan frame to serial frame
  auto can2ser = [&](const can_frame &frame) {
    std::vector<uint8_t> data;
    uint32_t id = 0;
    bool is_extended = frame.can_id & CAN_EFF_FLAG ? true : false;
    bool is_remote = frame.can_id & CAN_RTR_FLAG;
    uint8_t dlc = frame.len;
    data.push_back(Bit0);  // SOF, Start of frame
    if (is_extended) {
      id = frame.can_id & CAN_EFF_MASK;
      // High 11 bits id
      for (uint8_t i = 0; i < 11; i++) {
        uint8_t tmp = ((uint8_t)(id >> (28 - i)) & 0x01) ? Bit1 : Bit0;
        data.push_back(tmp);
      }
      data.push_back(Bit1);  // SRR, Substitute remote request
      data.push_back(Bit1);  // IDE, Identifier extension
      // Low 18 bits id
      for (uint8_t i = 0; i < 18; i++) {
        uint8_t tmp = ((uint8_t)(id >> (17 - i)) & 0x01) ? Bit1 : Bit0;
        data.push_back(tmp);
      }
      // RTR
      if (is_remote) {
        data.push_back(Bit1);
      } else {
        data.push_back(Bit0);
      }
      data.push_back(Bit0);  // RB1, reserved bit 1
    } else {
      id = frame.can_id & CAN_SFF_MASK;
      for (uint8_t i = 0; i < 11; i++) {
        uint8_t tmp = ((uint8_t)(id >> (10 - i)) & 0x01) ? Bit1 : Bit0;
        data.push_back(tmp);
      }
      // RTR
      if (is_remote) {
        data.push_back(Bit1);
      } else {
        data.push_back(Bit0);
      }
      data.push_back(Bit0);  // IDE, Identifier extension
    }
    data.push_back(Bit0);  // RB0, reserved bit 0
    // 4 bits DLC
    for (uint8_t i = 0; i < 4; i++) {
      uint8_t tmp = ((uint8_t)(dlc >> (3 - i)) & 0x01) ? Bit1 : Bit0;
      data.push_back(tmp);
    }
    // 0~64 bits data
    for (uint8_t i = 0; i < frame.len; i++) {
      for (uint8_t j = 0; j < 8; j++) {
        uint8_t tmp = ((frame.data[i] >> (7 - j)) & 0x01) ? Bit1 : Bit0;
        data.push_back(tmp);
      }
    }
    // CRC15
    uint16_t crc = crc15(data);
    for (uint8_t i = 0; i < 15; i++) {
      uint8_t tmp = ((crc >> (14 - i)) & 0x01) ? Bit1 : Bit0;
      data.push_back(tmp);
    }
    data.push_back(Bit1);  // CRC Delimiter
    // ACK, ACK Delimiter, EOF, IFS will not be cared
    return data;
  };

  // test can2ser
  auto test = [&]() {
    static uint8_t cnt = 0;
    can_frame frame;
    // use 0x92345678, 8 bytes of 0x3C to test fill stuff bits
    frame.can_id = 0x12345678 | CAN_EFF_FLAG;
    frame.len = 8;
    frame.__pad = 0;
    frame.__res0 = 0;
    frame.len8_dlc = 0;
    for (uint8_t i = 0; i < 8; i++) {
      frame.data[i] = cnt + i;
    }
    cnt++;
    auto data0 = can2ser(frame);
    auto data = fsb_insert(data0);
    // asio::write(ser, asio::buffer(data));
    asio::async_write(
        ser, asio::buffer(data),
        [&](const asio::error_code &ec, std::size_t bytes_transferred) {
          if (ec) {
            std::cerr << "Write error: " << ec.message() << std::endl;
          }
        });
  };

  // timer
  auto ms = std::chrono::milliseconds(10);
  asio::steady_timer t(iocxt, ms);
  std::function<void(const asio::error_code &)> timer =
      [&](const asio::error_code &ec) {
        if (ec) {
          std::cerr << "Timer error: " << ec.message() << std::endl;
        } else {
          test();
        }
        t.expires_at(t.expiry() + ms);
        t.async_wait(timer);
      };
  t.async_wait(timer);

  iocxt.run();

  return 0;
}

对应的cmake文件

cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
project(chcan LANGUAGES CXX)

add_executable(${PROJECT_NAME} main.cpp)

# https://github.com/cpm-cmake/CPM.cmake
include(CPM.cmake)

# https://github.com/chriskohlhoff/asio
CPMAddPackage("gh:chriskohlhoff/asio#asio-1-28-2@1.28.2")
find_package(Threads REQUIRED)
if(asio_ADDED)
  add_library(asio INTERFACE)
  target_include_directories(asio SYSTEM INTERFACE ${asio_SOURCE_DIR}/asio/include)
  target_compile_definitions(asio INTERFACE ASIO_STANDALONE ASIO_NO_DEPRECATED)
  target_link_libraries(asio INTERFACE Threads::Threads)
endif()

target_link_libraries(${PROJECT_NAME} PRIVATE 
  asio
)
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)

编译测试:

$ mkdir build && cd build
$ cmake ..
$ make
$ sudo ./chcan

如图

在这里插入图片描述

VXCAN

为了更实用一点, 如直接使用can_utilscansend等命令, 用VXCAN虚拟出一对SocketCAN:

#!/bin/sh
sudo modprobe can_raw
sudo modprobe vxcan

if ip link show can0 > /dev/null 2>&1; then
    sudo ip link delete dev can0 type vxcan
fi

sudo ip link add dev can0 type vxcan
sudo ip link set up can0
sudo ip link set dev vxcan0 up

运行检测:

$ chmod 777 vxcan.sh
$ ./vxcan.sh
$ ifconfig

下面编写 chvxcan, 使用对内的vxcan0进行收发. (这里在Ubuntu20测试, 里面 frame.len 写成了 frame.can_dlc)

#include <linux/can.h>
#include <linux/can/raw.h>

#include <asio.hpp>
#include <chrono>
#include <cstdbool>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <thread>
#include <vector>

int main(int argc, char *argv[]) {
  asio::io_context iocxt;

  // serial, 1 start bit, 8 data bits, 1 stop bit, no parity, 2.5M baud
  // CAN 100K, Serial 1M, 1 start bit, 8 data bits, 1 stop bit, no parity
  // CAN 250K, Serial 2M, 1 start bit, 6 data bits, 1 stop bit, no parity
  // CAN 500K, Serial 4M, 1 start bit, 6 data bits, 1 stop bit, no parity
  asio::serial_port ser(iocxt, "/dev/ttyACM0");
  ser.set_option(asio::serial_port::baud_rate(2000000));
  ser.set_option(asio::serial_port::character_size(6));
  ser.set_option(
      asio::serial_port::stop_bits(asio::serial_port::stop_bits::one));
  ser.set_option(asio::serial_port::parity(asio::serial_port::parity::none));
  ser.set_option(
      asio::serial_port::flow_control(asio::serial_port::flow_control::none));
  if (!ser.is_open()) {
    std::cerr << "Failed to open serial port" << std::endl;
    return -1;
  }

  // can
  int dev = socket(PF_CAN, SOCK_RAW, CAN_RAW);
  if (dev < 0) {
    std::cerr << "Failed to open can" << std::endl;
    return -1;
  }
  struct ifreq ifr;
  std::strcpy(ifr.ifr_name, "vxcan0");
  if (ioctl(dev, SIOCGIFINDEX, &ifr) < 0) {
    std::cerr << "Failed to ioctl can" << std::endl;
    return -1;
  }
  struct sockaddr_can addr;
  addr.can_family = AF_CAN;
  addr.can_ifindex = ifr.ifr_ifindex;
  int enable_canfd = 1;
  if (setsockopt(dev, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &enable_canfd,
                 sizeof(enable_canfd)) < 0) {
    std::cerr << "Failed to setsockopt canfd" << std::endl;
    return -1;
  }
  if (bind(dev, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
    std::cerr << "Failed to bind can" << std::endl;
    return -1;
  }
  asio::posix::stream_descriptor can(iocxt, dev);

  constexpr uint8_t Bit0 = 0x00;
  constexpr uint8_t Bit1 = 0xFF;

  // can crc15 calculation
  auto crc15 = [&](const std::vector<uint8_t> &data) {
    bool crc[15] = {0};
    for (int i = 0; i < data.size(); i++) {
      bool inv = (data[i] == Bit1) ^ crc[14];
      crc[14] = crc[13] ^ inv;
      crc[13] = crc[12];
      crc[12] = crc[11];
      crc[11] = crc[10];
      crc[10] = crc[9] ^ inv;
      crc[9] = crc[8];
      crc[8] = crc[7] ^ inv;
      crc[7] = crc[6] ^ inv;
      crc[6] = crc[5];
      crc[5] = crc[4];
      crc[4] = crc[3] ^ inv;
      crc[3] = crc[2] ^ inv;
      crc[2] = crc[1];
      crc[1] = crc[0];
      crc[0] = inv;
    }
    uint16_t res = 0;
    for (int i = 0; i < 15; i++) {
      res |= crc[i] << i;
    }
    return res;
  };

  // fill stuff bits
  auto fsb_insert = [&](std::vector<uint8_t> &data) {
    uint8_t count = 0;
    bool last = true;
    std::vector<uint8_t> newdata;
    for (auto it = data.begin(); it != data.end(); it++) {
      bool current = *it == Bit0 ? false : true;
      newdata.push_back(*it);
      if (current == last) {
        count++;
      } else {
        count = 1;
      }
      if (count == 5) {
        newdata.push_back(current ? Bit0 : Bit1);
        count = 1;
        last = !current;
      } else {
        last = current;
      }
    }
    return newdata;
  };

  // socketcan frame to serial frame
  auto can2ser = [&](const can_frame &frame) {
    std::vector<uint8_t> data;
    uint32_t id = 0;
    bool is_extended = frame.can_id & CAN_EFF_FLAG ? true : false;
    bool is_remote = frame.can_id & CAN_RTR_FLAG;
    uint8_t dlc = frame.can_dlc;
    data.push_back(Bit0);  // SOF, Start of frame
    if (is_extended) {
      id = frame.can_id & CAN_EFF_MASK;
      // High 11 bits id
      for (uint8_t i = 0; i < 11; i++) {
        uint8_t tmp = ((uint8_t)(id >> (28 - i)) & 0x01) ? Bit1 : Bit0;
        data.push_back(tmp);
      }
      data.push_back(Bit1);  // SRR, Substitute remote request
      data.push_back(Bit1);  // IDE, Identifier extension
      // Low 18 bits id
      for (uint8_t i = 0; i < 18; i++) {
        uint8_t tmp = ((uint8_t)(id >> (17 - i)) & 0x01) ? Bit1 : Bit0;
        data.push_back(tmp);
      }
      // RTR
      if (is_remote) {
        data.push_back(Bit1);
      } else {
        data.push_back(Bit0);
      }
      data.push_back(Bit0);  // RB1, reserved bit 1
    } else {
      id = frame.can_id & CAN_SFF_MASK;
      for (uint8_t i = 0; i < 11; i++) {
        uint8_t tmp = ((uint8_t)(id >> (10 - i)) & 0x01) ? Bit1 : Bit0;
        data.push_back(tmp);
      }
      // RTR
      if (is_remote) {
        data.push_back(Bit1);
      } else {
        data.push_back(Bit0);
      }
      data.push_back(Bit0);  // IDE, Identifier extension
    }
    data.push_back(Bit0);  // RB0, reserved bit 0
    // 4 bits DLC
    for (uint8_t i = 0; i < 4; i++) {
      uint8_t tmp = ((uint8_t)(dlc >> (3 - i)) & 0x01) ? Bit1 : Bit0;
      data.push_back(tmp);
    }
    // 0~64 bits data
    for (uint8_t i = 0; i < frame.can_dlc; i++) {
      for (uint8_t j = 0; j < 8; j++) {
        uint8_t tmp = ((frame.data[i] >> (7 - j)) & 0x01) ? Bit1 : Bit0;
        data.push_back(tmp);
      }
    }
    // CRC15
    uint16_t crc = crc15(data);
    for (uint8_t i = 0; i < 15; i++) {
      uint8_t tmp = ((crc >> (14 - i)) & 0x01) ? Bit1 : Bit0;
      data.push_back(tmp);
    }
    data.push_back(Bit1);  // CRC Delimiter
    // ACK, ACK Delimiter, EOF, IFS will not be cared
    return data;
  };

  // read socketcan frame and write to serial
  uint8_t can_buffer[1024];
  std::function<void(const asio::error_code &, std::size_t)> can2ser_read =
      [&](const asio::error_code &ec, std::size_t bytes_transferred) {
        if (ec) {
          std::cerr << "Read error: " << ec.message() << std::endl;
        } else {
          if (bytes_transferred == CAN_MTU) {
            struct can_frame frame;
            std::memcpy(&frame, can_buffer, sizeof(can_frame));
            auto data0 = can2ser(frame);
            auto data = fsb_insert(data0);

            asio::async_write(
                ser, asio::buffer(data),
                [&](const asio::error_code &ec, std::size_t bytes_transferred) {
                  if (ec) {
                    std::cerr << "Write error: " << ec.message() << std::endl;
                  }
                });
          }
        }
        can.async_read_some(asio::buffer(can_buffer), can2ser_read);
      };
  can.async_read_some(asio::buffer(can_buffer), can2ser_read);

  iocxt.run();

  return 0;
}

测试如图

在这里插入图片描述

到这里, 就可以用你喜欢的语言, 如Python, Rust, C等对can0进行发送了.

Github Link

domain_controller_orin_x2_tc397/ch343_can at main · weifengdq/domain_controller_orin_x2_tc397 (github.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/458567.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ASP.NET区域检验云LIS平台源码 标本全生命周期管理

目录 一、云LIS系统功能亮点 二、收费项目管理 三、检验项目管理 系统功能 云LIS系统源码是一款全面的实验室信息管理系统源码&#xff0c;其主要功能包括样本管理、检测项目管理、质控管理、报告管理、数据分析、两癌筛查等多个方面。具有独立的配套SaaS模式运维管理系统&…

ABAP 服务的代码调试

文章目录 ABAP 服务的代码调试事务代码SICF-服务的代码调试 ABAP 服务的代码调试 事务代码SICF-服务的代码调试

爱普生晶振发布RTC模块晶振(压电侠)

爱普生晶振一直以”省&#xff0c;小&#xff0c;精”技术作为资深核心&#xff0c;并且已经建立了一个原始的垂直整合制造模型&#xff0c;可以自己创建独特的核心技术和设备&#xff0c;使用这些作为基地的规划和设计提供独特价值的产品. 世界领先的石英晶体技术精工爱普生公…

echarts tooltip提示组件框自定义浮窗内容

echarts tooltip提示组件框自定义浮窗内容 tooltip提示组件框 有三种浮窗展示方法 第一种&#xff1a;默认展示 第二种&#xff1a;字符串模板 第三种&#xff1a;回调函数 第二种 formatter&#xff08;字符串模板&#xff09; 模板变量有 {a}, {b}&#xff0c;{c}&#xff0…

【rk3368 android6.0 恢复出厂设置功能】

rk3368 android6.0 恢复出厂设置功能 恢复出厂设置三种方法一&#xff0c;设置--进入恢复出厂设置页面二&#xff0c;发送广播形式三&#xff0c;命令形式总结 郑重声明:本人原创博文&#xff0c;都是实战&#xff0c;均经过实际项目验证出货的 转载请标明出处:攻城狮2015 恢复…

C++的类和对象(五):赋值运算符重载与日期类的实现

目录 比较两个日期对象 运算符重载 赋值运算符重载 连续赋值 日期类的实现 Date.h文件 Date.cpp文件 Test.cpp文件 const成员 取地址及const取地址操作符重载 比较两个日期对象 问题描述&#xff1a;内置类型可直接用运算符比较&#xff0c;自定义类型的对象是多个…

视觉系统对透明胶水的检测都有哪些方案?

透明胶水的检测在工业生产中是一个挑战&#xff0c;因为传统的基于RGB相机的视觉系统通常难以检测透明物体。然而&#xff0c;随着技术的发展&#xff0c;现在有多种方法可以有效地检测透明胶水。 1. 高光谱相机&#xff1a;高光谱相机可以提供不同于传统RGB相机的解决方案。例…

企业为什么需要内容管理平台?应该如何搭建?

企业进行内容管理在提升员工工作效率、促进知识共享、优化业务流程、支持数字化转型和创新以及增强企业竞争力等方面发挥着重要作用。因此&#xff0c;对于希望在现代商业环境中保持竞争力的企业来说&#xff0c;建立和维护一个高效的内容管理平台是非常必要的。 | 企业搭建内容…

数据结构与算法试卷第六套

1.带权路径长度之和 带权路径长度之和只需计算叶子节点的即可&#xff1b; **哈夫曼树&#xff1a;**最小的两个节点构造成一个新的节点 带权路径之和计算叶子节点即可 2.快排复习 3.链表判空条件 4.树的遍历 前序&#xff1a; 根左右 后序&#xff1a; 左右根 B: 分析如下…

结构体之成绩统计2

题目描述 有N个学生,每个学生的数据包括学号、姓名、3门课的成绩,从键盘输入N个学生的数据,要求打印出3门课的总平均成绩,以及最高分的学生的数据(包括学号、姓名、3门课成绩) 输入格式 学生数量N占一行每个学生的学号、姓名、三科成绩占一行,空格分开。 输出格式 各门…

【从零开始构建GPT模型】(四)Linux篇:基于Linux内核的Ubuntu系统安装教程

目录 附&#xff1a;所需工具 一、安装包下载 1、下载安装包 2、注册账户 二、与Windows系统共存安装 1、制作系统启动U盘 2、进入安装界面 3、享受Ubuntu 注&#xff1a;本人为保险起见&#xff0c;并未安装Ubuntu系统&#xff0c;因要待新买一块硬盘之后&#xff0c;将…

【算法】一类支持向量机OC-SVM(1)

【算法】一类支持向量机OC-SVM 前言一类支持向量机OC-SVM 概念介绍示例编写数据集创建实现一类支持向量机OC-SVM完整的示例输出 前言 由于之前毕设期间主要的工具就是支持向量机&#xff0c;从基础的回归和分类到后来的优化&#xff0c;在接触到支持向量机还有一类支持向量机的…

WIN11环境下MYSQL5.7.44免安装版配置

一、MySQL下载 MySQL官网下载页面MySQL :: Download MySQL Community Server (Archived Versions)https://downloads.mysql.com/archives/community/ 若想要安装之前版本的MySQL&#xff0c;则点击Archives 进去页面后则可以轻松找到自己想要下载的MySQL版本 二、安装配置MySQ…

代码之美:探秘Java中的优雅参数验证

大家好,我是小米!今天我来和大家分享一下 Java 开发中一项非常重要的技术——参数校验。参数校验在我们的代码中起着至关重要的作用,它能够确保我们的应用程序接收到正确的数据,并且保证了系统的安全性和稳定性。在过去,我们可能会通过繁琐的 if-else 来进行参数校验,但是…

从零开始学习深度学习库-3:更多优化器

系列文章&#xff1a; 从零开始学习深度学习库-1&#xff1a;前馈网络 从零开始学习深度学习库-2&#xff1a;反向传播 欢迎来到本系列的第三部分&#xff0c;在这里&#xff0c;我们将从零开始构建一个深度学习库。 在这篇文章中&#xff0c;我们将向我们的库中添加更多的优…

数据结构从入门到精通——堆

堆 前言一、二叉树的顺序结构及实现 (堆&#xff09;1.1二叉树的顺序结构1.2堆的概念及结构 二、堆的练习题答案 三、堆的实现3.1堆向下调整算法3.2堆的创建3.3建堆时间复杂度3.4堆的插入3.5堆的删除3.6堆的代码实现 四、堆的具体实现代码Heap.hHeap.cTest.c堆的初始化堆的销毁…

基于单片机的温度控制系统设计

基于单片机的温度控制系统设计 摘要: 最近这些年&#xff0c;随着科学技术的不断发展和进步&#xff0c;单片机技术通过在各行各业中的应用也日臻完善。而温度测控系统也因单片机所特有的强大处理能力、功耗低以及体积小等优点向着小型化和智能化发展。本设计以STC89C52单片机…

Linux服务器之间免密登录

文章目录 1. 原理2. 密钥文件作用解释3. 实操 1. 原理 2. 密钥文件作用解释 #mermaid-svg-uJJwWJXgqGtbNwNB {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-uJJwWJXgqGtbNwNB .error-icon{fill:#552222;}#mermaid-s…

AttributeError: module ‘tensorflow‘ has no attribute ‘placeholder‘解决办法

1.报错代码 self.inputs_base_structure_left tf.placeholder(dtypetf.float32, shape[None, 2048, 2], name"inputs_left") # initial a inputs to siamese_network2. 报错原因 AttributeError: module tensorflow has no attribute placeholder 这个错误发生的…

基于Java+SpringBoot+vue的智能农场管理系统详细设计和实现

基于JavaSpringBootvue的智能农场管理系统详细设计和实现 博主介绍&#xff1a;多年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐留言 文…