基于MM32生态实现I2C接口通讯的几种方式

概述:i2c通讯接口,是我们日常应用中使用最多的mcu外设。最早在mcu没有硬件i2c之前,都是通过gpio口模拟i2c的时序来完成i2c通讯的。后来mcu带有了i2c外设接口,其硬件i2c的使用也变成了日常。更主要的是,在节省mcu资源的同时,其操作也变得更加简单和易用。再后来经过市场需求的变化,开始有了支持i2c多从机地址通讯功能的mcu,让i2c的应用紧跟市场需求。 虽然从i2c特性上知晓具有不同i2c地址的器件是可以挂载在同一个i2c总线上进行通讯的,但是,如果需要操作的i2c器件地址冲突呢?mcu的硬件i2c接口数量不够呢?或者说mcu的i2c不支持从机多地址通讯功能呢?这时候,我们还是需要通过gpio口来模拟i2c时序完成i2c主机/从机的功能。所以,并不是有了硬件i2c,软件i2c就没有发挥的空间了,恰恰是软件和硬件这两种实现方式共存互相补充。
对于i2c的基本概念及时序等知识点,本文不再详细描述,大家可以下载附件中的《i2c总线概要》和《i2c总线规范》进行研究。本文将通过如下四个方面,讲述i2c在mm32f032/mm32f0140系列mcu上的实现,以及使用i2c工具(图莫斯usb2xxx总线适配器)进行实际测试: 硬件i2c主机通讯 软件模拟i2c主机通讯 硬件i2c从机通讯 软件模拟i2c从机通讯(有难度) mm32f032系列mcu带有1路硬件i2c接口,支持标准模式(数据传输速率为0~100kbps)和快速模式(数据最大传输速率为400kbps)两种工作速率模式,其主要特征如下所示: i2c总线协议转换器/并行总线; 半双工同步操作; 支持主从模式; 支持7位地址和10位地址; 支持标准模式100kbps、快速模式400kbps; 产生start、stop、repeated start,以及acknowledge信号检测; 在主机模式下只支持一个主机; 分别有2个字节的发送和接收缓冲; 在scl和sda上增加了无毛刺电路; 支持dam、中断和查询操作方式; mm32f0140系列mcu在mm32f032的基础上i2c做了更丰富的功能,支持多从机地址通讯的功能、支持时钟延展等等……具体的可以参考官方的数据手册。  
一、硬件i2c主机通讯
mm32的硬件i2c是我使用到现在,在代码程序段操作最为简洁的了;不需要再去考虑start信号、ack信号,以及各种event事件等……这些复杂的操作、或者是可以省略的操作都由官方的底层库程序和芯片ip去实现了,让我们在设计驱动程序时变量简单了。对于硬件i2c主机的配置,我们只需要复用的gpio端口引脚、i2c通讯参数,以及从机地址即可;然后就可以编程去读写i2c从机设备了,初始化配置及对i2c从机设备的读写操作的实现代码如下:
void hi2c_master_init(uint8_t slaveaddress){ gpio_inittypedef gpio_initstructure; i2c_inittypedef i2c_initstructure; rcc_apb1periphclockcmd(rcc_apb1enr_i2c1, enable); i2c_structinit(&i2c_initstructure); i2c_initstructure.i2c_mode = i2c_mode_master; i2c_initstructure.i2c_ownaddress = 0; i2c_initstructure.i2c_speed = i2c_speed_standard; i2c_initstructure.i2c_clockspeed = 100000; i2c_init(i2c1, &i2c_initstructure); i2c_send7bitaddress(i2c1, slaveaddress, i2c_direction_transmitter); i2c_cmd(i2c1, enable); rcc_ahbperiphclockcmd(rcc_ahbenr_gpiob, enable); gpio_pinafconfig(gpiob, gpio_pinsource6, gpio_af_1); gpio_pinafconfig(gpiob, gpio_pinsource7, gpio_af_1); gpio_structinit(&gpio_initstructure); gpio_initstructure.gpio_pin = gpio_pin_6 | gpio_pin_7; gpio_initstructure.gpio_speed = gpio_speed_2mhz; gpio_initstructure.gpio_mode = gpio_mode_af_od; gpio_init(gpiob, &gpio_initstructure);}void hi2c_master_read(uint8_t address, uint8_t *buffer, uint8_t length){ uint8_t flag = 0, count = 0; i2c_senddata(i2c1, address); while(!i2c_getflagstatus(i2c1, i2c_status_flag_tfe)); for(uint8_t i = 0; i < length; i++) { while(1) { if((i2c_getflagstatus(i2c1, i2c_status_flag_tfnf)) && (flag == 0)) { i2c_readcmd(i2c1); count++; if(count == length) flag = 1; } if(i2c_getflagstatus(i2c1, i2c_status_flag_rfne)) { buffer[i] = i2c_receivedata(i2c1); break; } } } i2c_generatestop(i2c1, enable); while(!i2c_getflagstatus(i2c1, i2c_flag_stop_det));}void hi2c_master_write(uint8_t address, uint8_t *buffer, uint8_t length){ i2c_senddata(i2c1, address); while(!i2c_getflagstatus(i2c1, i2c_status_flag_tfe)); for(uint8_t i = 0; i < length; i++) { i2c_senddata(i2c1, *buffer++); while(!i2c_getflagstatus(i2c1, i2c_status_flag_tfe)); } i2c_generatestop(i2c1, enable); while(!i2c_getflagstatus(i2c1, i2c_flag_stop_det));}void hi2c_master_shell_handler(uint8_t mode){ uint8_t buffer[10] = {0x12, 0x23, 0x34, 0x45, 0x56, 0x67, 0x78, 0x89, 0x90, 0xaa}; if(mode == 1) { hi2c_master_write(0x00, buffer, sizeof(buffer)); } else { hi2c_master_read(0x00, buffer, sizeof(buffer)); printf(hi2c master read : ); for(uint8_t i = 0; i scl_gpio, si2c->scl_pin, bit_set)#define si2c_master_scl_l(si2c) gpio_writebit(si2c->scl_gpio, si2c->scl_pin, bit_reset)#define si2c_master_sda_h(si2c) gpio_writebit(si2c->sda_gpio, si2c->sda_pin, bit_set)#define si2c_master_sda_l(si2c) gpio_writebit(si2c->sda_gpio, si2c->sda_pin, bit_reset)#define si2c_master_scl_get(si2c) gpio_readoutputdatabit(si2c->scl_gpio, si2c->scl_pin)#define si2c_master_sda_get(si2c) gpio_readinputdatabit( si2c->sda_gpio, si2c->sda_pin)void si2c_master_delay(uint32_t tick){ while(tick--);}void si2c_master_sda_setdirection(si2c_master_typedef *si2c, uint8_t direction){ gpio_inittypedef gpio_initstructure; rcc_ahbperiphclockcmd(si2c->sda_rcc, enable); gpio_structinit(&gpio_initstructure); gpio_initstructure.gpio_pin = si2c->sda_pin; gpio_initstructure.gpio_speed = gpio_speed_50mhz; if(direction) /* input */ { gpio_initstructure.gpio_mode = gpio_mode_in_floating; } else /* output */ { gpio_initstructure.gpio_mode = gpio_mode_out_pp; } gpio_init(si2c->sda_gpio, &gpio_initstructure);}void si2c_master_scl_setdirection(si2c_master_typedef *si2c, uint8_t direction){ gpio_inittypedef gpio_initstructure; rcc_ahbperiphclockcmd(si2c->scl_rcc, enable); gpio_structinit(&gpio_initstructure); gpio_initstructure.gpio_pin = si2c->scl_pin; gpio_initstructure.gpio_speed = gpio_speed_50mhz; if(direction) /* input */ { gpio_initstructure.gpio_mode = gpio_mode_in_floating; } else /* output */ { gpio_initstructure.gpio_mode = gpio_mode_out_pp; } gpio_init(si2c->scl_gpio, &gpio_initstructure);}void si2c_master_generatestart(si2c_master_typedef *si2c){ si2c_master_sda_h(si2c); si2c_master_delay(si2c->time); si2c_master_scl_h(si2c); si2c_master_delay(si2c->time); si2c_master_sda_l(si2c); si2c_master_delay(si2c->time); si2c_master_scl_l(si2c); si2c_master_delay(si2c->time);}void si2c_master_generatestop(si2c_master_typedef *si2c){ si2c_master_sda_l(si2c); si2c_master_delay(si2c->time); si2c_master_scl_h(si2c); si2c_master_delay(si2c->time); si2c_master_sda_h(si2c); si2c_master_delay(si2c->time);}void si2c_master_putack(si2c_master_typedef *si2c, uint8_t ack){ if(ack) si2c_master_sda_h(si2c); /* nack */ else si2c_master_sda_l(si2c); /* ack */ si2c_master_delay(si2c->time); si2c_master_scl_h(si2c); si2c_master_delay(si2c->time); si2c_master_scl_l(si2c); si2c_master_delay(si2c->time);}uint8_t si2c_master_getack(si2c_master_typedef *si2c){ uint8_t ack = 0; si2c_master_sda_h(si2c); si2c_master_delay(si2c->time); si2c_master_sda_setdirection(si2c, 1); si2c_master_scl_h(si2c); si2c_master_delay(si2c->time); ack = si2c_master_sda_get(si2c); si2c_master_scl_l(si2c); si2c_master_delay(si2c->time); si2c_master_sda_setdirection(si2c, 0); return ack;}uint8_t si2c_master_readbyte(si2c_master_typedef *si2c){ uint8_t data = 0; si2c_master_sda_h(si2c); /* must set sda before read */ si2c_master_sda_setdirection(si2c, 1); for(uint8_t i = 0; i time); data time); }}void si2c_master_init(si2c_master_typedef *si2c){ si2c_master_sda_setdirection(si2c, 0); si2c_master_scl_setdirection(si2c, 0); si2c_master_scl_h(si2c); si2c_master_delay(si2c->time); si2c_master_sda_h(si2c); si2c_master_delay(si2c->time);}uint8_t si2c_master_read(si2c_master_typedef *si2c, uint8_t address, uint8_t *buffer, uint8_t length){ if(length == 0) return 0; si2c_master_generatestart(si2c); si2c_master_writebyte(si2c, si2c->slaveaddress); if(si2c_master_getack(si2c)) { si2c_master_generatestop(si2c); return 1; } si2c_master_writebyte(si2c, address); if(si2c_master_getack(si2c)) { si2c_master_generatestop(si2c); return 1; } si2c_master_generatestart(si2c); si2c_master_writebyte(si2c, si2c->slaveaddress + 1); if(si2c_master_getack(si2c)) { si2c_master_generatestop(si2c); return 1; } while(1) { *buffer++ = si2c_master_readbyte(si2c); if(--length == 0) { si2c_master_putack(si2c, 1); break; } si2c_master_putack(si2c, 0); } si2c_master_generatestop(si2c); return 0;}uint8_t si2c_master_write(si2c_master_typedef *si2c, uint8_t address, uint8_t *buffer, uint8_t length){ uint8_t i = 0; if(length == 0) return 0; si2c_master_generatestart(si2c); si2c_master_writebyte(si2c, si2c->slaveaddress); if(si2c_master_getack(si2c)) { si2c_master_generatestop(si2c); return 1; } si2c_master_writebyte(si2c, address); if(si2c_master_getack(si2c)) { si2c_master_generatestop(si2c); return 1; } for(i = 0; i < length; i++) { si2c_master_writebyte(si2c, *buffer++); if(si2c_master_getack(si2c)) break; } si2c_master_generatestop(si2c); if(i == length) return 0; else return 1;}void si2c_master_shell_handler(uint8_t mode){ uint8_t buffer[10] = {0x11, 0x22, 0x33, 0x44, 0x55, 0xaa, 0xbb, 0xcc, 0xdd, 0xee}; if(mode == 1) { si2c_master_write(&si2c_master, 0x00, buffer, sizeof(buffer)); } else { si2c_master_read(&si2c_master, 0x00, buffer, sizeof(buffer)); printf(si2c master read : ); for(uint8_t i = 0; i scl_gpio, si2c->scl_pin);}bool si2c_slave_read_sda(si2c_slave_typedef *si2c){ return gpio_readinputdatabit(si2c->sda_gpio, si2c->sda_pin);}/******************************************************************************* * [url=home.php?mod=space&uid=247401]@brief[/url] 配置模拟i2c的gpio端口, 默认设置成输入模式, 并使能相应的外部触发 * 中断功能(上升沿和下降沿) * @param * @retval * [url=home.php?mod=space&uid=93590]@attention[/url] *******************************************************************************/void si2c_slave_init(si2c_slave_typedef *si2c){ gpio_inittypedef gpio_initstructure; exti_inittypedef exti_initstructure; nvic_inittypedef nvic_initstructure; rcc_ahbperiphclockcmd(si2c->scl_rcc, enable); rcc_ahbperiphclockcmd(si2c->sda_rcc, enable); rcc_apb2periphclockcmd(rcc_apb2periph_syscfg, enable); gpio_structinit(&gpio_initstructure); gpio_initstructure.gpio_pin = si2c->scl_pin; gpio_initstructure.gpio_speed = gpio_speed_50mhz; gpio_initstructure.gpio_mode = gpio_mode_in_floating; gpio_init(si2c->scl_gpio, &gpio_initstructure); gpio_structinit(&gpio_initstructure); gpio_initstructure.gpio_pin = si2c->sda_pin; gpio_initstructure.gpio_speed = gpio_speed_50mhz; gpio_initstructure.gpio_mode = gpio_mode_in_floating; gpio_init(si2c->sda_gpio, &gpio_initstructure); syscfg_extilineconfig(si2c->scl_exti_portsource, si2c->scl_exti_pinsource); exti_structinit(&exti_initstructure); exti_initstructure.exti_line = si2c->scl_exti_line; exti_initstructure.exti_mode = exti_mode_interrupt; exti_initstructure.exti_trigger = exti_trigger_rising_falling; exti_initstructure.exti_linecmd = enable; exti_init(&exti_initstructure); syscfg_extilineconfig(si2c->sda_exti_portsource, si2c->sda_exti_pinsource); exti_structinit(&exti_initstructure); exti_initstructure.exti_line = si2c->sda_exti_line; exti_initstructure.exti_mode = exti_mode_interrupt; exti_initstructure.exti_trigger = exti_trigger_rising_falling; exti_initstructure.exti_linecmd = enable; exti_init(&exti_initstructure); nvic_initstructure.nvic_irqchannel = exti4_15_irqn; nvic_initstructure.nvic_irqchannelpriority = 0x00; nvic_initstructure.nvic_irqchannelcmd = enable; nvic_init(&nvic_initstructure);}/******************************************************************************* * [url=home.php?mod=space&uid=247401]@brief[/url] 设置sda信号线的输入输出方便, 0代表output输出, 1代表input输入 * @param * @retval * [url=home.php?mod=space&uid=93590]@attention[/url] *******************************************************************************/void si2c_slave_sda_setdirection(si2c_slave_typedef *si2c, uint8_t direction){ gpio_inittypedef gpio_initstructure; if(direction) /* input */ { gpio_structinit(&gpio_initstructure); gpio_initstructure.gpio_pin = si2c->sda_pin; gpio_initstructure.gpio_speed = gpio_speed_50mhz; gpio_initstructure.gpio_mode = gpio_mode_in_floating; gpio_init(si2c->sda_gpio, &gpio_initstructure); } else /* output */ { gpio_structinit(&gpio_initstructure); gpio_initstructure.gpio_pin = si2c->sda_pin; gpio_initstructure.gpio_speed = gpio_speed_50mhz; gpio_initstructure.gpio_mode = gpio_mode_out_od; gpio_init(si2c->sda_gpio, &gpio_initstructure); }}/****************************************************************************** * @brief 设置sda信号线的输出电平(高电平 / 低电平) * @param * @retval * @attention ******************************************************************************/void si2c_slave_sda_setlevel(si2c_slave_typedef *si2c, uint8_t level){ si2c_slave_sda_setdirection(si2c, 0); if(level) { gpio_writebit(si2c->sda_gpio, si2c->sda_pin, bit_set); } else { gpio_writebit(si2c->sda_gpio, si2c->sda_pin, bit_reset); }}/****************************************************************************** * @brief 当scl触发上升沿外部中断时的处理 * @param * @retval * @attention ******************************************************************************/void si2c_slave_scl_risehandler(si2c_slave_typedef *si2c){ /* scl为上升沿, 数据锁定, 主从机从sda总线上获取数据位 */ switch(si2c_slave_state) { case si2c_slave_state_add: /* i2c发送遵义msb, 先发送高位, 再发送低位, 所以在接收的时候, 数据进行左移 */ si2c_slave_slaveaddress <<= 1; si2c_slave_shiftcounter += 1; if(si2c_slave_read_sda(si2c) == bit_set) { si2c_slave_slaveaddress |= 0x01; } /* 当接收到8位地址位后, 从机需要在第9个时钟给出ack应答, 等待scl下降沿的时候给出ack信号 */ if(si2c_slave_shiftcounter == 8) { si2c_slave_state = si2c_slave_state_add_ack; } break; case si2c_slave_state_add_ack: /* 从机地址的ack回复后, 切换到收发数据状态 */ si2c_slave_state = si2c_slave_state_dat; si2c_slave_shiftcounter = 0; /* 数据移位计数器清零 */ si2c_slave_receiveddata = 0; /* si2c_slave的接收数据清零 */ break; case si2c_slave_state_dat: if((si2c_slave_slaveaddress & 0x01) == 0x00) { /* 主机写操作:此时从机应该获取主机发送的sda信号线电平状态, 进行位存储 */ si2c_slave_receiveddata <<= 1; si2c_slave_shiftcounter += 1; if(si2c_slave_read_sda(si2c) == bit_set) { si2c_slave_receiveddata |= 0x01; } /* 当收到一个完整的8位数据时, 将收到的数据存放到i2c接收消息队列中, 状态转换到给主机发送ack应答 */ if(si2c_slave_shiftcounter == 8) { queue_write(queue_si2c_slave_idx, si2c_slave_receiveddata); si2c_slave_shiftcounter = 0; /* 数据移位计数器清零 */ si2c_slave_receiveddata = 0; /* si2c_slave的接收数据清零 */ si2c_slave_state = si2c_slave_state_dat_ack; } } else { /* 主机读操作:在scl上升沿的时候, 主机获取当前sda的状态位, 如果到了第8个数位的上升沿, * 那接下来就是主机回复从机的应答或非应答信号了, 所以将状态切换到等待ack的状态, 同时准备下一个需要发送的数据 */ if(si2c_slave_shiftcounter == 8) { si2c_slave_shiftcounter = 0; /* si2c_slave的接收数据清零 */ si2c_slave_transmitdata = si2c_slave_transmitbuffer[si2c_slave_transmitindex++]; si2c_slave_transmitindex %= 16; si2c_slave_state = si2c_slave_state_dat_ack; } } break; case si2c_slave_state_dat_ack: if((si2c_slave_slaveaddress & 0x01) == 0x00) { /* 主机写操作:从机发送ack, 等待主机读取从机发送的ack信号 */ si2c_slave_state = si2c_slave_state_dat; /* 状态切换到数据接收状态 */ } else { /* 主机读操作:主机发送ack, 从机可以读取主机发送的ack信号 */ uint8_t ack = si2c_slave_read_sda(si2c); if(ack == bit_reset) { si2c_slave_state = si2c_slave_state_dat; /* 接收到 ack, 继续发送数据 */ } else { si2c_slave_state = si2c_slave_state_sto; /* 接收到nack, 停止发送数据 */ } } break; default: break; }}/****************************************************************************** * @brief 当scl触发下降沿外部中断时的处理 * @param * @retval * @attention ******************************************************************************/void si2c_slave_scl_fallhandler(si2c_slave_typedef *si2c){ /* scl为下降沿, 数据可变 */ switch(si2c_slave_state) { case si2c_slave_state_sta: /* * 检测到start信号后, scl第一个下降沿表示开始传输slave address, * 根据数据有效性的规则, 地址的第一位需要等到scl变为高电平时才可以读取 * 切换到获取slave address的状态, 等待scl的上升沿触发 */ si2c_slave_state = si2c_slave_state_add; si2c_slave_shiftcounter = 0; /* 数据移位计数器清零 */ si2c_slave_slaveaddress = 0; /* si2c_slave的从机地址清零 */ si2c_slave_receiveddata = 0; /* si2c_slave的接收数据清零 */ break; case si2c_slave_state_add: /* * 在主机发送slave address的时候, 从机只是读取sda状态, 进行地址解析, 所以这边没有处理 */ break; case si2c_slave_state_add_ack: /* scl低电平的时候, 给i2c总线发送地址的应答信号, 状态不发生改变, 等待下一个上升沿将ack发送出去 */ si2c_slave_sda_setlevel(si2c, 0); /* 将sda信号拉低, 向主机发送ack信号 */ break; case si2c_slave_state_dat: /* 在scl时钟信号的下降沿, sda信号线处理可变的状态 */ if((si2c_slave_slaveaddress & 0x01) == 0x00) { /* 主机写操作:将sda信号线设置成获取状态, 等待下一个scl上升沿时获取数据位 */ si2c_slave_sda_setdirection(si2c, 1); } else { /* 主机读操作:根据发送的数据位设置sda信号线的输出电平, 等待下一个scl上升沿时发送数据位 */ if(si2c_slave_transmitdata & 0x80) { si2c_slave_sda_setlevel(si2c, 1); } else { si2c_slave_sda_setlevel(si2c, 0); } si2c_slave_transmitdata <<= 1; si2c_slave_shiftcounter += 1; } break; case si2c_slave_state_dat_ack: /* 在第8个scl时钟信号下降沿的处理 */ if((si2c_slave_slaveaddress & 0x01) == 0x00) { /* 主机写操作:从机在接收到数据后, 需要给主机一个ack应答信号, 状态不发生改变, 等待下一个上升沿将ack发送出去 */ si2c_slave_sda_setlevel(si2c, 0); /* 将sda信号拉低, 向主机发送ack信号 */ } else { /* 主机读操作:从机需要释放当前的sda信号线, 以便主机发送ack或nack给从机, 状态不发生改变, 等待下一个上升沿读取ack信号 */ si2c_slave_sda_setdirection(si2c, 1); } break; default: break; }}/** * @brief 当sda触发上升沿外部中断时的处理 * @param none * @retval none */void si2c_slave_sda_risehandler(si2c_slave_typedef *si2c){ if(si2c_slave_read_scl(si2c) == bit_set) /* scl为高时,sda为上升沿:stop */ { si2c_slave_state = si2c_slave_state_sto; } else /* scl为低时,sda为上升沿:数据的变化 */ { }}/** * @brief 当sda触发下降沿外部中断时的处理 * @param none * @retval none */void si2c_slave_sda_fallhandler(si2c_slave_typedef *si2c){ if(si2c_slave_read_scl(si2c) == bit_set) /* scl为高时,sda为下降沿:start */ { si2c_slave_state = si2c_slave_state_sta; } else /* scl为低时,sda为下降沿:数据的变化 */ { }}/******************************************************************************* * @brief * @param * @retval * @attention *******************************************************************************/void exti4_15_irqhandler(void){ /* i2c scl */ if(exti_getitstatus(si2c_slave.scl_exti_line) != reset) { if(si2c_slave_read_scl(&si2c_slave) == bit_set) { si2c_slave_scl_risehandler(&si2c_slave); } else { si2c_slave_scl_fallhandler(&si2c_slave); } exti_clearitpendingbit(si2c_slave.scl_exti_line); } /* i2c sda */ if(exti_getitstatus(si2c_slave.sda_exti_line) != reset) { if(si2c_slave_read_sda(&si2c_slave) == bit_set) { si2c_slave_sda_risehandler(&si2c_slave); } else { si2c_slave_sda_fallhandler(&si2c_slave); } exti_clearitpendingbit(si2c_slave.sda_exti_line); }} 实测结果如下所示:
以上就是基于mm32生态实现i2c接口通讯的几种方式了,如果有需要查看原图、软件工程源代码、i2c相关资料的小伙伴,请点击底部“阅读原文”进行下载。  

机器人位置传感器应用更青睐哪种ADC?
红旗ls5国产SUV大哥大,高配置外观极像路虎,路虎要完蛋了么?
电池的种类和型号
采用电力载波芯片ST7538实现家电智能控制系统设计
第十四代PowerEdge服务器将采用AMD芯片
基于MM32生态实现I2C接口通讯的几种方式
关于远程预付费电能管理系统的设计以及实际应用
变频器专用输出滤波器的作用
苹果将3D人脸感测带进手机应用,促使VCSEL技术获得各方瞩目
硬件方面 VR独立头显带动增长,且在应用方面更有趣多元
库克:全球使用苹果设备超16.5亿部 营收创新高
ST和微软云Azure合作构建物联网应用
蓝牙推出全新架构 将数十亿设备连接至物联网
2020年是区块链公司跟传统行业建立稳定关系的重要一年
电压比较器电路原理图、应用电路
芯海科技车规系列产品精彩亮相“2022中国国际汽车电子高峰论坛”
英特尔® 至强® 处理器,铸造质量和可靠性的典范
蒸汽拖把好用吗 揭秘它的工作原理
华阳通用荣获长城哈弗2022年度“优秀供应商-卓越质量奖”
苹果公司正下重赌注在AR技术上