Android 7 btsnoop代码介绍

原创 专注于无线通信的蓬勃 2022-10-15 16:06

本文假设你有btsnoop的概念,会在以上基础上进行android 7的btsnoop的代码介绍,如果你没有btsnoop相关的基础,那么移步到大专兰看btsnoop的概念,再来看本文,协议栈大专栏以及btsnoop的相关的文章连接如下:

一篇文章足够你学习蓝牙技术,提供史上最全的蓝牙技术(传统蓝牙/低功耗蓝牙)文章总结,文档下载总结(2020/12/11更新)_Wireless_Link的博客-CSDN博客_蓝牙eir

蓝牙协议栈学习/开发利器-BTSNOOP介绍_Wireless_Link的博客-CSDN博客_btsnoop

本文通过以下几个内容来介绍下Android 7的btsnoop

1)btsnoop的分类

2)btsnoop的启动以及结束实现

3)btsnoop的写入实现以及调用

一.Btsnoop的分类

我们可以看到Android7的AOSP代码中有3中btsnoop的实现,分别是btsnoop,btsnoop_net,btsnoop_mem,下面我们就要分别介绍下:

1.btsnoop

此方式就是普通的btsnoop,把hci的数据写入到文件中,然后导出文件来查看,文件源码跟头文件分别是:

system/bt/hci/src/btsnoop.c

system/bt/hci/include/btsnoop.h

2.btsnoop_net

此方式是通过socket来调试btsnoop,这种方式会把hci数据写入到local host的tcp 8872端口上,然后配合自己敲的指令,来实时抓取log,文件源码是:

system/bt/hci/src/btsnoop_net.c

这个功能的使用文档在

system/bt/doc

3.btsnoop_mem

此方式是通过把btsnoop的数据抓下来,在btif层保存到一个ring buffer中,然后通过dump的方式在通过dprintf打印出来!,文件源码跟头文件分别是:

system/bt/hci/src/btsnoop_mem.c

system/bt/hci/include/btsnoop_mem.h

二.btsnoop的启动实现

btsnoop的启动分为以下几个步骤:

1)模块启动

2)模块启动的源码分析

下面我们就一一分析下以上几个步骤

1.模块启动

Android的协议栈把很多功能都分成了一个个的子模块,叫做module,通过module_init来初始化,通过module_start_up来开始,通过module_shut_down来结束,module的实现不在本文章的讨论范围内,我们只需要知道模块提前根据以下结构体注册,然后每个函数指针调用到特定的函数即可!

typedef struct {
  const char *name;
  module_lifecycle_fn init;
  module_lifecycle_fn start_up;
  module_lifecycle_fn shut_down;
  module_lifecycle_fn clean_up;
  const char *dependencies[];
} module_t;

btsnoop的module结构体如下:

EXPORT_SYMBOL const module_t btsnoop_module = {
  .name = BTSNOOP_MODULE,
  .init = NULL,
  .start_up = start_up,
  .shut_down = shut_down,
  .clean_up = NULL,
  .dependencies = {
    STACK_CONFIG_MODULE,
    NULL
  }
};

因为btsnoop没有init函数,只有startup跟shurdown函数,分别调用位置如下:

void bte_main_enable()
{
    APPL_TRACE_DEBUG("%s", __FUNCTION__);
    
    module_start_up(get_module(BTSNOOP_MODULE)); //模块启动
    module_start_up(get_module(HCI_MODULE));
    
    BTU_StartUp();
}
void bte_main_disable(void)
{
    APPL_TRACE_DEBUG("%s", __FUNCTION__);

    module_shut_down(get_module(HCI_MODULE)); //模块结束
    module_shut_down(get_module(BTSNOOP_MODULE));

    BTU_ShutDown();
}

2. btsnoop的启动以及结束实现

2.1 btsnoop的开启

btsnoop的startup函数实现如下:

static future_t *start_up(void) {
  module_started = true;
  update_logging();

  return NULL;
}
static void update_logging() {
  bool should_log = module_started &&
    (logging_enabled_via_api || stack_config->get_btsnoop_turned_on());

  if (should_log == is_logging)
    return;

  is_logging = should_log;
  if (should_log) {
    btsnoop_net_open();

    const char *log_path = stack_config->get_btsnoop_log_path();

    // Save the old log if configured to do so
    if (stack_config->get_btsnoop_should_save_last()) {
      char last_log_path[PATH_MAX];
      snprintf(last_log_path, PATH_MAX, "%s.%" PRIu64, log_path,
               btsnoop_timestamp());
      if (!rename(log_path, last_log_path) && errno != ENOENT)
        LOG_ERROR(LOG_TAG, "%s unable to rename '%s' to '%s': %s", __func__, log_path, last_log_path, strerror(errno));
    }

    logfile_fd = open(log_path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
    if (logfile_fd == INVALID_FD) {
      LOG_ERROR(LOG_TAG, "%s unable to open '%s': %s", __func__, log_path, strerror(errno));
      is_logging = false;
      return;
    }

    write(logfile_fd, "btsnoop\0\0\0\0\1\0\0\x3\xea", 16);
  } else {
    if (logfile_fd != INVALID_FD)
      close(logfile_fd);

    logfile_fd = INVALID_FD;
    btsnoop_net_close();
  }
}

startup主要是update_logging函数的实现,我们来分析一下

我们看到是否开启要依赖于should_log这个变量,这个变量为true就做一些动作,比如btsnoop写文件的open,btsnoop_net的开启,如果为false就做btsoop写文件的关闭以及btsnoop_net的关闭。

那么shoud_log都会依赖于什么呢?他的条件是这样的module_started && (logging_enabled_via_api || stack_config->get_btsnoop_turned_on());

首先会依赖于module_started ,我们可以看到btsnoop startup的时候就把这个设置为true了,那么其他两个条件是从什么地方而来呢?

logging_enabled_via_api

config_hci_snoop_log-》btsnoop_get_interface()->set_api_wants_to_log(enable)-》logging_enabled_via_api = value,可以看出来config_hci_snoop_log这个bluetooth.c中的hal实现,所以这个是跟上层jni调用,由上层来决定

那么stack_config->get_btsnoop_turned_on()这个条件呢?其中函数实现是get_btsnoop_turned_on

static bool get_btsnoop_turned_on(void) {
  return config_get_bool(config, CONFIG_DEFAULT_SECTION, BTSNOOP_TURNED_ON_KEY, false);
}

所以看代码我们可以得出他是从"/etc/bluetooth/bt_stack.conf" 配置文件中读取BtSnoopLogOutput的key value来决定。

基于以上条件,我们就能进入正式的开启文件等动作了,我们通过代码注释来解析

if (should_log) {
    //btsnoop net的open,这个稍后分析
    btsnoop_net_open();
    // 通过/etc/bluetooth/bt_stack.conf配置文件的BtSnoopFileName value值来决定btsnoop的路径
    // 默认路径是/data/misc/bluedroid/btsnoop_hci.log
    const char *log_path = stack_config->get_btsnoop_log_path(); 

    // 通过/etc/bluetooth/bt_stack.conf配置文件的BtSnoopSaveLog value值来决定是否保存上一次的
    // btsnoop,这个功能主要是会把上一次的snoop修改名称,做一个备份
    // Save the old log if configured to do so
    if (stack_config->get_btsnoop_should_save_last()) {
      char last_log_path[PATH_MAX];
      snprintf(last_log_path, PATH_MAX, "%s.%" PRIu64, log_path,
               btsnoop_timestamp());
      if (!rename(log_path, last_log_path) && errno != ENOENT)
        LOG_ERROR(LOG_TAG, "%s unable to rename '%s' to '%s': %s", __func__, log_path, last_log_path, strerror(errno));
    }

    // 常规的打开文件,保存路径就是我们上面从解析文件中得到的
    logfile_fd = open(log_path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
    if (logfile_fd == INVALID_FD) {
      LOG_ERROR(LOG_TAG, "%s unable to open '%s': %s", __func__, log_path, strerror(errno));
      is_logging = false;
      return;
    }
    // 写btsnoop的file header format,里面值不懂的可以回头看看我们的btsnoop的概念
    write(logfile_fd, "btsnoop\0\0\0\0\1\0\0\x3\xea", 16);
  }

现在回头来看看btsnoop_net的open

// 如果定义的宏,并且为TRUE,就创建一个现成,执行listen_fn_函数
void btsnoop_net_open() {
#if (!defined(BT_NET_DEBUG) || (BT_NET_DEBUG != TRUE))
  return;               // Disable using network sockets for security reasons
#endif

  listen_thread_valid_ = (pthread_create(&listen_thread_, NULL, listen_fn_, NULL) == 0);
  if (!listen_thread_valid_) {
    LOG_ERROR(LOG_TAG, "%s pthread_create failed: %s", __func__, strerror(errno));
  } else {
    LOG_DEBUG(LOG_TAG, "initialized");
  }
}


static void *listen_fn_(UNUSED_ATTR void *context) {

  prctl(PR_SET_NAME, (unsigned long)LISTEN_THREAD_NAME_, 0, 0, 0);

  // 创建一个TCP的socket
  listen_socket_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (listen_socket_ == -1) {
    LOG_ERROR(LOG_TAG, "%s socket creation failed: %s", __func__, strerror(errno));
    goto cleanup;
  }

  int enable = 1;
  if (setsockopt(listen_socket_, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) == -1) {
    LOG_ERROR(LOG_TAG, "%s unable to set SO_REUSEADDR: %s", __func__, strerror(errno));
    goto cleanup;
  }

  // 设置IP地址为local host,也就是本地交互,设置端口号为8872
  struct sockaddr_in addr;
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = htonl(LOCALHOST_);
  addr.sin_port = htons(LISTEN_PORT_);
  if (bind(listen_socket_, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
    LOG_ERROR(LOG_TAG, "%s unable to bind listen socket: %s", __func__, strerror(errno));
    goto cleanup;
  }

  // 启动socket监听
  if (listen(listen_socket_, 10) == -1) {
    LOG_ERROR(LOG_TAG, "%s unable to listen: %s", __func__, strerror(errno));
    goto cleanup;
  }

  // 有设备接入 ,发送btsnoop的header file format过去
  for (;;) {
    int client_socket;
    OSI_NO_INTR(client_socket = accept(listen_socket_, NULL, NULL));
    if (client_socket == -1) {
      if (errno == EINVAL || errno == EBADF) {
        break;
      }
      LOG_WARN(LOG_TAG, "%s error accepting socket: %s", __func__, strerror(errno));
      continue;
    }

    /* When a new client connects, we have to send the btsnoop file header. This allows
       a decoder to treat the session as a new, valid btsnoop file. */
    pthread_mutex_lock(&client_socket_lock_);
    safe_close_(&client_socket_);
    client_socket_ = client_socket;

    OSI_NO_INTR(send(client_socket_, "btsnoop\0\0\0\0\1\0\0\x3\xea", 16, 0));
    pthread_mutex_unlock(&client_socket_lock_);
  }

cleanup:
  safe_close_(&listen_socket_);
  return NULL;
}

2.2 btsnoop的关闭

{
    if (logfile_fd != INVALID_FD)
      close(logfile_fd); //关闭掉btsnoop的文件描述符

    logfile_fd = INVALID_FD;
    btsnoop_net_close(); // 关闭掉btsnoop_net
  }
void btsnoop_net_close() {
#if (!defined(BT_NET_DEBUG) || (BT_NET_DEBUG != TRUE))
  return;               // Disable using network sockets for security reasons
#endif

  if (listen_thread_valid_) {
    shutdown(listen_socket_, SHUT_RDWR);
    pthread_join(listen_thread_, NULL);
    safe_close_(&client_socket_);
    listen_thread_valid_ = false;
  }
}

三.btsnoop的写入实现以及调用

1.btsnoop的写入实现

首先他是通过capture接口来向外开放

static void capture(const BT_HDR *buffer, bool is_received) {
  const uint8_t *p = buffer->data   buffer->offset;

  btsnoop_mem_capture(buffer);

  if (logfile_fd == INVALID_FD)
    return;

  switch (buffer->event & MSG_EVT_MASK) {
    case MSG_HC_TO_STACK_HCI_EVT:
      btsnoop_write_packet(kEventPacket, p, false);
      break;
    case MSG_HC_TO_STACK_HCI_ACL:
    case MSG_STACK_TO_HC_HCI_ACL:
      btsnoop_write_packet(kAclPacket, p, is_received);
      break;
    case MSG_HC_TO_STACK_HCI_SCO:
    case MSG_STACK_TO_HC_HCI_SCO:
      btsnoop_write_packet(kScoPacket, p, is_received);
      break;
    case MSG_STACK_TO_HC_HCI_CMD:
      btsnoop_write_packet(kCommandPacket, p, true);
      break;
  }
}
static void btsnoop_write_packet(packet_type_t type, const uint8_t *packet, bool is_received) {
  int length_he = 0;
  int length;
  int flags;
  int drops = 0;
  switch (type) {
    case kCommandPacket:
      length_he = packet[2]   4;
      flags = 2;
      break;
    case kAclPacket:
      length_he = (packet[3] << 8)   packet[2]   5;
      flags = is_received;
      break;
    case kScoPacket:
      length_he = packet[2]   4;
      flags = is_received;
      break;
    case kEventPacket:
      length_he = packet[1]   3;
      flags = 3;
      break;
  }

  uint64_t timestamp = btsnoop_timestamp();
  uint32_t time_hi = timestamp >> 32;
  uint32_t time_lo = timestamp & 0xFFFFFFFF;

  length = htonl(length_he);
  flags = htonl(flags);
  drops = htonl(drops);
  time_hi = htonl(time_hi);
  time_lo = htonl(time_lo);

  btsnoop_write(&length, 4);
  btsnoop_write(&length, 4);
  btsnoop_write(&flags, 4);
  btsnoop_write(&drops, 4);
  btsnoop_write(&time_hi, 4);
  btsnoop_write(&time_lo, 4);
  btsnoop_write(&type, 1);
  btsnoop_write(packet, length_he - 1);
}
static void btsnoop_write(const void *data, size_t length) {
  if (logfile_fd != INVALID_FD)
    write(logfile_fd, data, length);

  btsnoop_net_write(data, length);
}
void btsnoop_net_write(const void *data, size_t length) {
#if (!defined(BT_NET_DEBUG) || (BT_NET_DEBUG != TRUE))
  return;               // Disable using network sockets for security reasons
#endif

  pthread_mutex_lock(&client_socket_lock_);
  if (client_socket_ != -1) {
    ssize_t ret;
    OSI_NO_INTR(ret = send(client_socket_, data, length, 0));

    if (ret == -1 && errno == ECONNRESET) {
      safe_close_(&client_socket_);
    }
  }
  pthread_mutex_unlock(&client_socket_lock_);
}

以上代码我觉得除了格式之外没有什么可讲的,如果你对为什么这么写格式不了解,我还是建议你回头看看。btsnoop的概念,下面我们来说明下调用地方

2.btsnoop的写入调用

通过以下函数实现来获取到btsnoop的interface函数操作

const hci_t *hci_layer_get_interface() {
  buffer_allocator = buffer_allocator_get_interface();
  hal = hci_hal_get_interface();
  btsnoop = btsnoop_get_interface(); // 通过这个interface来实现
  hci_inject = hci_inject_get_interface();
  packet_fragmenter = packet_fragmenter_get_interface();
  vendor = vendor_get_interface();
  low_power_manager = low_power_manager_get_interface();

  init_layer_interface();
  return &interface;
}

host->controller方向的写入实现,通过这个调用btsnoop->capture(packet, false);

static void transmit_fragment(BT_HDR *packet, bool send_transmit_finished) {
  uint16_t event = packet->event & MSG_EVT_MASK;
  serial_data_type_t type = event_to_data_type(event);

  btsnoop->capture(packet, false);
  hal->transmit_data(type, packet->data   packet->offset, packet->len);

  if (event != MSG_STACK_TO_HC_HCI_CMD && send_transmit_finished)
    buffer_allocator->free(packet);
}

controller->host方向的写入实现,这个函数比较复杂,你们暂时只需要知道调用了btsnoop->capture(packet, false)写入到btsnoop即可,因为其他实现暂时不在我们本文章套路范围之内

static void hal_says_data_ready(serial_data_type_t type) {
  packet_receive_data_t *incoming = &incoming_packets[PACKET_TYPE_TO_INBOUND_INDEX(type)];

  uint8_t byte;
  while (hal->read_data(type, &byte, 1) != 0) {
    switch (incoming->state) {
      case BRAND_NEW:
        // Initialize and prepare to jump to the preamble reading state
        incoming->bytes_remaining = preamble_sizes[PACKET_TYPE_TO_INDEX(type)];
        memset(incoming->preamble, 0, PREAMBLE_BUFFER_SIZE);
        incoming->index = 0;
        incoming->state = PREAMBLE;
        // INTENTIONAL FALLTHROUGH
      case PREAMBLE:
        incoming->preamble[incoming->index] = byte;
        incoming->index  ;
        incoming->bytes_remaining--;

        if (incoming->bytes_remaining == 0) {
          // For event and sco preambles, the last byte we read is the length
          incoming->bytes_remaining = (type == DATA_TYPE_ACL) ? RETRIEVE_ACL_LENGTH(incoming->preamble) : byte;

          size_t buffer_size = BT_HDR_SIZE   incoming->index   incoming->bytes_remaining;
          incoming->buffer = (BT_HDR *)buffer_allocator->alloc(buffer_size);

          if (!incoming->buffer) {
            LOG_ERROR(LOG_TAG, "%s error getting buffer for incoming packet of type %d and size %zd", __func__, type, buffer_size);
            // Can't read any more of this current packet, so jump out
            incoming->state = incoming->bytes_remaining == 0 ? BRAND_NEW : IGNORE;
            break;
          }

          // Initialize the buffer
          incoming->buffer->offset = 0;
          incoming->buffer->layer_specific = 0;
          incoming->buffer->event = outbound_event_types[PACKET_TYPE_TO_INDEX(type)];
          memcpy(incoming->buffer->data, incoming->preamble, incoming->index);

          incoming->state = incoming->bytes_remaining > 0 ? BODY : FINISHED;
        }

        break;
      case BODY:
        incoming->buffer->data[incoming->index] = byte;
        incoming->index  ;
        incoming->bytes_remaining--;

        size_t bytes_read = hal->read_data(type, (incoming->buffer->data   incoming->index), incoming->bytes_remaining);
        incoming->index  = bytes_read;
        incoming->bytes_remaining -= bytes_read;

        incoming->state = incoming->bytes_remaining == 0 ? FINISHED : incoming->state;
        break;
      case IGNORE:
        incoming->bytes_remaining--;
        if (incoming->bytes_remaining == 0) {
          incoming->state = BRAND_NEW;
          // Don't forget to let the hal know we finished the packet we were ignoring.
          // Otherwise we'll get out of sync with hals that embed extra information
          // in the uart stream (like H4). #badnewsbears
          hal->packet_finished(type);
          return;
        }

        break;
      case FINISHED:
        LOG_ERROR(LOG_TAG, "%s the state machine should not have been left in the finished state.", __func__);
        break;
    }

    if (incoming->state == FINISHED) {
      incoming->buffer->len = incoming->index;
      btsnoop->capture(incoming->buffer, true);

      if (type != DATA_TYPE_EVENT) {
        packet_fragmenter->reassemble_and_dispatch(incoming->buffer);
      } else if (!filter_incoming_event(incoming->buffer)) {
        // Dispatch the event by event code
        uint8_t *stream = incoming->buffer->data;
        uint8_t event_code;
        STREAM_TO_UINT8(event_code, stream);

        data_dispatcher_dispatch(
          interface.event_dispatcher,
          event_code,
          incoming->buffer
        );
      }

      // We don't control the buffer anymore
      incoming->buffer = NULL;
      incoming->state = BRAND_NEW;
      hal->packet_finished(type);

      // We return after a packet is finished for two reasons:
      // 1. The type of the next packet could be different.
      // 2. We don't want to hog cpu time.
      return;
    }
  }
}

专注于无线通信的蓬勃 朝气蓬勃——不积跬步 无以至千里, 不积小流 无以成江海
评论 (0)
  • 北京贞光科技有限公司作为紫光同芯授权代理商,专注于为客户提供车规级安全芯片的硬件供应与软件SDK一站式解决方案,同时配备专业技术团队,为选型及定制需求提供现场指导与支持。随着新能源汽车渗透率突破40%(中汽协2024数据),智能驾驶向L3+快速演进,车规级MCU正迎来技术范式变革。作为汽车电子系统的"神经中枢",通过AEC-Q100 Grade 1认证的MCU芯片需在-40℃~150℃极端温度下保持μs级响应精度,同时满足ISO 26262 ASIL-D功能安全要求。在集中式
    贞光科技 2025-04-02 14:50 120浏览
  • 随着汽车向智能化、场景化加速演进,智能座舱已成为人车交互的核心承载。从驾驶员注意力监测到儿童遗留检测,从乘员识别到安全带状态判断,座舱内的每一次行为都蕴含着巨大的安全与体验价值。然而,这些感知系统要在多样驾驶行为、复杂座舱布局和极端光照条件下持续稳定运行,传统的真实数据采集方式已难以支撑其开发迭代需求。智能座舱的技术演进,正由“采集驱动”转向“仿真驱动”。一、智能座舱仿真的挑战与突破图1:座舱实例图智能座舱中的AI系统,不仅需要理解驾驶员的行为和状态,还要同时感知乘员、儿童、宠物乃至环境中的潜在
    康谋 2025-04-02 10:23 95浏览
  • 职场之路并非一帆风顺,从初入职场的新人成长为团队中不可或缺的骨干,背后需要经历一系列内在的蜕变。许多人误以为只需努力工作便能顺利晋升,其实核心在于思维方式的更新。走出舒适区、打破旧有框架,正是让自己与众不同的重要法宝。在这条道路上,你不只需要扎实的技能,更需要敏锐的观察力、不断自省的精神和前瞻的格局。今天,就来聊聊那改变命运的三大思维转变,让你在职场上稳步前行。工作初期,总会遇到各式各样的难题。最初,我们习惯于围绕手头任务来制定计划,专注于眼前的目标。然而,职场的竞争从来不是单打独斗,而是团队协
    优思学院 2025-04-01 17:29 198浏览
  • 文/郭楚妤编辑/cc孙聪颖‍不久前,中国发展高层论坛 2025 年年会(CDF)刚刚落下帷幕。本次年会围绕 “全面释放发展动能,共促全球经济稳定增长” 这一主题,吸引了全球各界目光,众多重磅嘉宾的出席与发言成为舆论焦点。其中,韩国三星集团会长李在镕时隔两年的访华之行,更是引发广泛热议。一直以来,李在镕给外界的印象是不苟言笑。然而,在论坛开幕前一天,李在镕却意外打破固有形象。3 月 22 日,李在镕与高通公司总裁安蒙一同现身北京小米汽车工厂。小米方面极为重视此次会面,CEO 雷军亲自接待,小米副董
    华尔街科技眼 2025-04-01 19:39 209浏览
  • 据先科电子官方信息,其产品包装标签将于2024年5月1日进行全面升级。作为电子元器件行业资讯平台,大鱼芯城为您梳理本次变更的核心内容及影响:一、标签变更核心要点标签整合与环保优化变更前:卷盘、内盒及外箱需分别粘贴2张标签(含独立环保标识)。变更后:环保标识(RoHS/HAF/PbF)整合至单张标签,减少重复贴标流程。标签尺寸调整卷盘/内盒标签:尺寸由5030mm升级至**8040mm**,信息展示更清晰。外箱标签:尺寸统一为8040mm(原7040mm),提升一致性。关键信息新增新增LOT批次编
    大鱼芯城 2025-04-01 15:02 196浏览
  • 提到“质量”这两个字,我们不会忘记那些奠定基础的大师们:休哈特、戴明、朱兰、克劳士比、费根堡姆、石川馨、田口玄一……正是他们的思想和实践,构筑了现代质量管理的核心体系,也深远影响了无数企业和管理者。今天,就让我们一同致敬这些质量管理的先驱!(最近流行『吉卜力风格』AI插图,我们也来玩玩用『吉卜力风格』重绘质量大师画象)1. 休哈特:统计质量控制的奠基者沃尔特·A·休哈特,美国工程师、统计学家,被誉为“统计质量控制之父”。1924年,他提出世界上第一张控制图,并于1931年出版《产品制造质量的经济
    优思学院 2025-04-01 14:02 145浏览
  • 引言在语音芯片设计中,输出电路的设计直接影响音频质量与系统稳定性。WT588系列语音芯片(如WT588F02B、WT588F02A/04A/08A等),因其高集成度与灵活性被广泛应用于智能设备。然而,不同型号在硬件设计上存在关键差异,尤其是DAC加功放输出电路的配置要求。本文将从硬件架构、电路设计要点及选型建议三方面,解析WT588F02B与F02A/04A/08A的核心区别,帮助开发者高效完成产品设计。一、核心硬件差异对比WT588F02B与F02A/04A/08A系列芯片均支持PWM直推喇叭
    广州唯创电子 2025-04-01 08:53 188浏览
  • 在智能交互设备快速发展的今天,语音芯片作为人机交互的核心组件,其性能直接影响用户体验与产品竞争力。WT588F02B-8S语音芯片,凭借其静态功耗<5μA的卓越低功耗特性,成为物联网、智能家居、工业自动化等领域的理想选择,为设备赋予“听得懂、说得清”的智能化能力。一、核心优势:低功耗与高性能的完美结合超低待机功耗WT588F02B-8S在休眠模式下待机电流仅为5μA以下,显著延长了电池供电设备的续航能力。例如,在电子锁、气体检测仪等需长期待机的场景中,用户无需频繁更换电池,降低了维护成本。灵活的
    广州唯创电子 2025-04-02 08:34 148浏览
  • 探针本身不需要对焦。探针的工作原理是通过接触被测物体表面来传递电信号,其精度和使用效果取决于探针的材质、形状以及与检测设备的匹配度,而非对焦操作。一、探针的工作原理探针是检测设备中的重要部件,常用于电子显微镜、坐标测量机等精密仪器中。其工作原理主要是通过接触被测物体的表面,将接触点的位置信息或电信号传递给检测设备,从而实现对物体表面形貌、尺寸或电性能等参数的测量。在这个过程中,探针的精度和稳定性对测量结果具有至关重要的影响。二、探针的操作要求在使用探针进行测量时,需要确保探针与被测物体表面的良好
    锦正茂科技 2025-04-02 10:41 69浏览
  • 文/Leon编辑/cc孙聪颖‍步入 2025 年,国家进一步加大促消费、扩内需的政策力度,家电国补政策将持续贯穿全年。这一利好举措,为行业发展注入强劲的增长动力。(详情见:2025:消费提振要靠国补还是“看不见的手”?)但与此同时,也对家电企业在战略规划、产品打造以及市场营销等多个维度,提出了更为严苛的要求。在刚刚落幕的中国家电及消费电子博览会(AWE)上,家电行业的竞争呈现出胶着的态势,各大品牌为在激烈的市场竞争中脱颖而出,纷纷加大产品研发投入,积极推出新产品,试图提升产品附加值与市场竞争力。
    华尔街科技眼 2025-04-01 19:49 206浏览
  • 退火炉,作为热处理设备的一种,广泛应用于各种金属材料的退火处理。那么,退火炉究竟是干嘛用的呢?一、退火炉的主要用途退火炉主要用于金属材料(如钢、铁、铜等)的热处理,通过退火工艺改善材料的机械性能,消除内应力和组织缺陷,提高材料的塑性和韧性。退火过程中,材料被加热到一定温度后保持一段时间,然后以适当的速度冷却,以达到改善材料性能的目的。二、退火炉的工作原理退火炉通过电热元件(如电阻丝、硅碳棒等)或燃气燃烧器加热炉膛,使炉内温度达到所需的退火温度。在退火过程中,炉内的温度、加热速度和冷却速度都可以根
    锦正茂科技 2025-04-02 10:13 64浏览
我要评论
0
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦