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

news2024/11/16 19:46:41

文章目录

    • 原启
    • 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)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

私立医院的革命者:大数据解决方案全面解析

第一部分&#xff1a;背景 在信息化飞速发展的今天&#xff0c;医疗行业正经历着一场深刻的数字化转型。特别是对于私立医院来说&#xff0c;要在这个变革的浪潮中立于不败之地&#xff0c;就必须拥抱新技术&#xff0c;优化服务流程&#xff0c;提高医疗质量。大数据技术&…

Python教程-SchemDraw绘制电路图

电路图是电子工程师和电子爱好者的重要工具&#xff0c;用于图形化表示电子元件之间的连接关系。在Python中&#xff0c;有许多库可以用于绘制电路图&#xff0c;其中之一就是SchemDraw。本文将介绍如何使用SchemDraw库&#xff0c;通过简单的Python代码绘制出清晰、美观的电路…

力扣 617-合并二叉树

二叉树使用递归&#xff0c;就要想使用前中后哪种遍历方式&#xff1f; 本题使用哪种遍历都是可以的&#xff01; 我们下面以前序遍历为例。 那么我们来按照递归三部曲来解决&#xff1a; 确定递归函数的参数和返回值&#xff1a; 首先要合入两个二叉树&#xff0c;那么参…

学习java第二天

一.注释 单行注释&#xff1a; // 这是一个单行注释 int x 10; // 初始化一个变量x为10 多行注释&#xff1a; /* 这是一个多行注释 可以用来注释多行代码 */ int y 20; // 初始化一个变量y为20 文档注释&#xff1a; /* 这是一个多行注释 可以用来注释多行代码 */ int…

51单片机基础篇系列-LED灯点亮代码部分

&#x1f308;个人主页: 会编辑的果子君 &#x1f4ab;个人格言:“成为自己未来的主人~” #include<reg52.h> //包含单片机内部寄存器 void main() //&#xff08;&#xff09;{P10xfe;//1111 1110while(1); // } 上面是第一个 LED实验 #include<reg52.h>…

PythonWeb——Django框架

框架介绍 1.什么是框架? 框架就是程序的骨架&#xff0c;主体结构&#xff0c;也是个半成品。 2.框架的优缺点 可重用、成熟,稳健、易扩展、易维护 3.Python中常见的框架 大包大揽 Django被官方称之为完美主义者的Web框架。力求精简web.py和Tornado新生代微框架Flask和B…

GEE python高阶——如何使用geemap和eemont包基于MODIS影像计算GNDVI,NBR,NDWI指数并可视化(山西省太原市为例)

这里我们进行使用geemap和eemont包基于MODIS影像计算GNDVI,NBR,NDWI指数&#xff0c;这里很方便的省去了计算指数、去云和缩放等功能&#xff0c;非常方便。 简介 GNDVI (Green Normalized Difference Vegetation Index)是一种用于评估植被覆盖状况的指数。它是通过测量红光波…

iconfont 字体应用

1、登录 打开阿里图标 https://www.iconfont.cn/ 2、选择心仪的图标制作 iconfont 字体。 3、图标全部选择入库之后&#xff0c; 点右上角的购物车。 添加到项目&#xff0c;是方便管理图标字体的。 也可以直接下载代码的 4、下载到本地之后&#xff0c;把里面的 iconfont.…

深入理解 CSS——CSS进阶与实践(5w字高频面试题整理)

本文总结了CSS高频面试题&#xff0c;并搭配了演示动画进行CSS样式演示。介绍了关于如何理解盒模型&#xff0c;如何实现块级元素水平居中&#xff0c;如何实现两侧固定中间自适应的三栏布局、如何实现两栏布局&#xff0c;如何进行响应式设计&#xff0c;对BFC的理解&#xff…

【c 语言】算术操作符详解

&#x1f388;个人主页&#xff1a;豌豆射手^ &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;C语言 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进步&…

【管理咨询宝藏37】某四大咨询公司K记PPT模板

本报告首发于公号“管理咨询宝藏”&#xff0c;如需阅读完整版报告内容&#xff0c;请查阅公号“管理咨询宝藏”。 【管理咨询宝藏37】某四大咨询公司K记PPT模板 【格式】PPT版本&#xff0c;可编辑&#xff0c; 【关键词】PPT模板&#xff0c;PPT图表 【文件核心观点】 - 3…

负数的四舍五入

负数的四舍五入 标签:基础 System.out.println(Math.round(-0.2)); System.out.println(Math.round(-0.5)); System.out.println(Math.round(-0.6));0 0 -1理解:四舍五入,找一个离目标小数近的整数,-0.2和0近,-0.6和-1近,中间的往右靠

KubeSphere多集群管理

多集群管理 各种组织跨不同的云厂商或者在不同的基础设施上运行和管理多个 Kubernetes 集群的做法非常普遍。由于每个 Kubernetes 集群都是一个相对独立的单元&#xff0c;因此在多集群的场景下&#xff0c;需要多集群管理解决方案。 KubeSphere 的多集群管理为用户提供统一的…

机器学习--Transformer 1

Transformer 是一个基于自注意力的序列到序列模型&#xff0c;与基于循环神经网络的序列到序列模型不同&#xff0c;其可以能够并行计算。 一、序列到序列模型 序列到序列模型输入和输出都是一个序列&#xff0c;输入与输出序列长度之间的关系有两种情况。第一种情况下&#…

【前端小技巧】各种奇奇怪怪的技巧合集

1&#xff0c;任意网页内容可编辑 使用方法&#xff1a;打开控制台&#xff0c;在console内输入这句话按回车&#xff1a;document.body.contentEditable‘true’ 然后你就可以编辑页面上的内容了&#xff0c;比如这样 2&#xff0c;浏览器直接运行HTML代码 使用方法&…

文件和流IO

文件可以看作是数据的集合&#xff0c;一般保存在磁盘或其他存储介质上文件I/O&#xff08;数据的输入/输出&#xff09;通过流&#xff08;Stream&#xff09;来实现&#xff1b;流提供一种向存储写入字节和从存储读取字节的方式对于流有5 种基本的操作&#xff1a;打开、读取…

Linux:进程

进程 知识铺垫冯诺依曼体系结构操作系统&#xff08;OS&#xff09; 进程概念进程的查看ps 命令获取进程 pid文件内查看进程终止进程的方式kill命令快捷键 进程的创建 forkfork 返回值问题 进程状态运行状态 &#xff1a;R休眠状态&#xff1a;S &#xff08;可中断&#xff09…

PCBA方案设计充气泵设计

随着科技的不断进步&#xff0c;充气泵在户外活动、露营和旅行中变得越来越常见。而充气泵的性能和稳定性主要依赖于其控制系统&#xff0c;其中芯片的设计和开发是充气泵方案的关键。SIC8833芯片是一款专门为充气泵设计的芯片&#xff0c;接下来我们来讲下充气泵方案芯片SIC88…

[java基础揉碎]面向对象多态

目录 问题引出多态: 代码如下: 多态(多种状态)的介绍: 多态的具体体现: 方法的多态: 对象的多态: 我们用多态解决最上面的问题: ​编辑 ​编辑 多态的注意事项和细节: 多态的向上转型: ​编辑 多态的向下转型 属性的重写问题 问题引出多态: 代码如下: 新建一个食物…

Open3D 利用四个点计算球心和半径 (28)

Open3D 利用四个点计算球心和半径 (28) 一、算法介绍二、算法实现1.代码2.结果一、算法介绍 给定的四个点坐标,计算球心和半径,提供验证的四个点来比较最终的结果是否准确。 二、算法实现 1.代码 代码如下(示例): import numpy as npdef calculate_sphere_center_…