详解ARM学习之网卡DM9000
四、DM9000A驱动分析
DM9000A所能支持的功能非常的多,驱动的实现相对比较复杂,搞清楚裸机的网卡驱动,我们再去学习Linux内核的DM9000驱动就相对容易一些。本节将详细讲解DM9000A网卡的数据的收发操作的流程。
1. 相关结构体
struct board_info
Structure/enum declaration -------------------------------
typedef struct board_info {
u32 runt_length_counter; counter: RX length < 64byte
u32 long_length_counter; counter: RX length > 1514byte
u32 reset_counter; counter: RESET
u32 reset_tx_timeout; RESET caused by TX Timeout
u32 reset_rx_status; RESET caused by RX Statsus wrong
u16 tx_pkt_cnt;
u16 queue_start_addr;
u16 dbug_cnt;
u8 phy_addr;
u8 device_wait_reset; device state
unsigned char srom[128];
void (*outblk)(volatile void *data_ptr, int count);
void (*inblk)(void *data_ptr, int count);
void (*rx_status)(u16 *RxStatus, u16 *RxLen);
struct eth_device netdev;
} board_info_t;
static board_info_t dm9000_info;
该结构体是用来维护DM9000系列网卡的结构体,所有和网卡DM9000A信息都保存到该结构体中。struct eth_devicestruct board_info中有一个重要的成员 netdev,该成员是uboot提供的标准的统一的网卡设备接口。
struct eth_device {
char name[16];
unsigned char enetaddr[6];
int iobase;
int state;
int (*init) (struct eth_device *, bd_t *);
int (*send) (struct eth_device *, void *packet, int length);
int (*recv) (struct eth_device *);
void (*halt) (struct eth_device *);
#ifdef CONFIG_MCAST_TFTP
int (*mcast) (struct eth_device *, u32 ip, u8 set);
#endif
int (*write_hwaddr) (struct eth_device *);
struct eth_device *next;
int index;
void *priv;
};
该结构体维护了操作网卡的回调函数等信息,我们只需要把网口的收发数据操作封装到对应的回调函数中,然后注册到系统即可。
2. 网卡注册/注销
进入到arch/arm/lib/board.c 中的 board_init_r 函数:
665 #if defined(CONFIG_CMD_NET)
666 puts("Net: ");
667 eth_initialize(gd->bd);
668 #if defined(CONFIG_RESET_PHY_R)
669 debug("Reset Ethernet PHY");
670 reset_phy();
671 #endif
如果定义了 CONFIG_CMD_NET,就调用 eth_initialize(gd->bd)进行网卡初始化。
这个宏在include/config_cmd_default.h 中定义,这个头文件又被单板配置文件 include/configs/origen.h 所包含。
eth_initialize 函数在 net/eth.c 中定义,下面是该函数部分代码:
308
309 * If board-specific initialization exists, call it.
310 * If not, call a CPU-specific one
311
312 if (board_eth_init != __def_eth_init) {
313 if (board_eth_init(bis) < 0)
314 printf("Board Net Initialization Failed");
315 } else if (cpu_eth_init != __def_eth_init) {
316 if (cpu_eth_init(bis) < 0)
317 printf("CPU Net Initialization Failed");
318 } else
319 printf("Net Initialization Skipped");
这段代码功能是:如果定义了单板相关的初始化函数就调用它,否则调用 CPU 相关的初始化函数。
其中__def_eth_init 函数,同样在net/eth.c 中定义
105 * CPU and board-specific Ethernet initializations. Aliased function
106 * signals caller to move on
107
108 static int __def_eth_init(bd_t *bis)
109 {
110 return -1;
111 }
112 int cpu_eth_init(bd_t *bis) __attribute__((weak, alias("__def_eth_init")));
113 int board_eth_init(bd_t *bis) __attribute__((weak, alias("__def_eth_init")));
这里用到了 gcc 的弱符号和别名属性。如果我们没有定义自己的 board_eth_init 函数,则 board_eth_init 就和__def_eth_init 相同,调用 board_eth_init 就相当于调用__def_eth_init,现在就能明白上面的 if 判断语句了。
board_eth_init 在board/samsung/origen/origen.c 中定义
264 #ifdef CONFIG_CMD_NET
265 int board_eth_init(bd_t *bis)
266 {
267
268 int rc = 0;
269 #ifdef CONFIG_DRIVER_DM9000
270 rc = dm9000_initialize(bis);
271 #endif
272 return rc;
273 }
274 #endif
这里通过配置宏来决定调用哪个网卡初始化函数。
我们使用的是 DM9000A,我们先查看下 DM9000A 的驱动源文件drivers/net/DM9000x.c,初始化函数如下:
626 int dm9000_initialize(bd_t *bis)
627 {
628 struct eth_device *dev = &(dm9000_info.netdev);
629
630 Load MAC address from EEPROM
631 dm9000_get_enetaddr(dev);
632
633 dev->init = dm9000_init;
634 dev->halt = dm9000_halt;
635 dev->send = dm9000_send;
636 dev->recv = dm9000_rx;
637 sprintf(dev->name, "dm9000");
638
639 eth_register(dev);
640
641 return 0;
642 }
该函数就是 DM9000A 的初始化函数。631行dm9000_get_enetaddr 从 EEPROM 加载MAC地址,
static void dm9000_get_enetaddr(struct eth_device *dev)
{
#if !defined(CONFIG_DM9000_NO_SROM)
int i;
for (i = 0; i < 3; i++)
dm9000_read_srom_word(i, dev->enetaddr + (2 * i));
#endif
}
该函数根据宏CONFIG_DM9000_NO_SROM 来决定是否从EEPROM 加载MAC地址, 参考的板子上的 DM9000A 没有接 EEPROM,我们在 origen.h 中定义了这个宏,表示不从 EEPROM 加载 MAC地址。
633~636行是将网卡的初始化和收发数据的函数填充到dev中,用于注册到系统中:
639行,函数eth_register()的参数是dev,该变量地址其实是dm9000_info.netdev的地址。dm9000_info定义在同一文件下:
108 static board_info_t dm9000_info;
函数eth_register()位于net/eth.c中;
功能:用于注册网卡到系统中,如果之前网卡设备链表为空,则直接复制给全局指针变量eth_devices和eth_current ,如果不为空,则把当前网卡插入到链表eth_devices中。int eth_register(struct eth_device *dev)
{
struct eth_device *d;
static int index;
assert(strlen(dev->name) < sizeof(dev->name));
if (!eth_devices) {//网卡设备链表为空
eth_current = eth_devices = dev;
eth_current_changed();
} else {//找到表尾
for (d = eth_devices; d->next != eth_devices; d = d->next)
;
d->next = dev;//插入表尾
}
dev->state = ETH_STATE_INIT;
dev->next = eth_devices;//新的设备指向网卡设备表头
dev->index = index++;
return 0;
}
其中
eth_devices:网卡设备的链表eth_current:用于保存当前使用的网卡
网卡注销网卡注销函数eth_unregister()该函数会将网卡节点dev从链表eth_devices中删除,并重新设置变量eth_current。
int eth_unregister(struct eth_device *dev)
{
struct eth_device *cur;
No device
if (!eth_devices)
return -1;
for (cur = eth_devices; cur->next != eth_devices && cur->next != dev;
cur = cur->next)
;
Device not found
if (cur->next != dev)
return -1;
cur->next = dev->next;
if (eth_devices == dev)
eth_devices = dev->next == eth_devices ? NULL : dev->next;
if (eth_current == dev) {
eth_current = eth_devices;
eth_current_changed();
}
return 0;
}
3. 寄存器
DM9000A 拥有一系列的控制和状态寄存器,这些寄存器可以被处理器所访问,这些寄存器是按字节对齐的。
所有的 CSRs 在软件或者硬件复位后都将被置为默认值,除非他们被另外标识。
编号寄存器描述偏移地址复位后默认值1NCR网络控制寄存器00H00H2NSR网络状态寄存器01H00H3TCR发送控制寄存器02H00H4TSR I发送状态寄存器 103H00H5TSR II发送状态寄存器 204H00H6RCR接收控制寄存器05H00H7RSR接收状态寄存器06H00H8ROCR接收溢出计数寄存器07H00H9BPTR背压阈值寄存器08H37H10FCTR流控制阈值寄存器09H38H11FCRTX/RX 流控制寄存器0AH00H12EPCREEPROM&PHY 控制寄存器0BH00H13EPAREEPROM&PHY 地址寄存器0CH40H14EPDRLEEPROM&PHY 低字节数据寄存器0DHXXH15EPDRHEEPROM&PHY 高字节数据寄存器0EHXXH16WCR唤醒控制寄存器0FH00H17PAR物理地址寄存器10H~15H由 EEPROM决定18MAR广播地址寄存器16H~1DHXXH19GPCR通用目的控制寄存器(8bit 模式)1EH01H20GPR通用目的寄存器1FHXXH21TRPALTX SRAM 读指针地址低字节22H00H22TRPAHTX SRAM 读指针地址高字节23H00H23RWPALRX SRAM 写指针地址低字节24H00H24RWPAHRX SRAM 写指针地址高字节25H0CH25VID厂家 ID28H~29H0A46H26PID产品 ID2AH~2BH9000H27CHIPR芯片版本2CH18H28TCR2发送控制寄存器 22DH00H29OCR操作控制寄存器2EH00H30SMCR特殊模式控制寄存器2FH00H31ETXCSR即将发送控制/状态寄存器30H00H32TCSCR发送校验和控制寄存器31H00H33RCSCSR接收校验和控制状态寄存器32H00H34MRCMDX内存数据预取读命令寄存器(地址不加 1)F0HXXH35MRCMDX1内存数据读命令寄存器(地址不加 1)F1HXXH36MRCMD内存数据读命令寄存器(地址加 1)F2HXXH37MRRL内存数据读地址寄存器低字节F4H00H38MRRH内存数据读地址寄存器高字节F5H00H39MWCMDX内存数据写命令寄存器(地址不加 1)F6HXXH40MWCMD内存数据写命令寄存器(地址加 1)F8HXXH41MWRL内存数据写地址寄存器低字节FAH00H42MWRH内存数据写地址寄存器高字节FBH00H43TXPLLTX 数据包长度低字节寄存器FCHXXH44TXPLHTX 数据包长度高字节寄存器FDHXXH45ISR中断状态寄存器FEH00H46IMR中断屏蔽寄存器FFH00H
关于默认值的要点(Key to Default)在下面寄存器描述中,默认栏采用如下形式:
<Reset Value>, <Access Type>
其中
1 该位设为逻辑 1
0 该位设为逻辑 0
X 没有默认值
P 电源复位恢复默认值
H 硬件复位恢复默认值
S 软件复位恢复默认值
E 从 EEPROM 得到默认值
T 从捆绑引脚(strap pin)得到默认值
:
RO = 只读
RW = 可读可写
R/C = 可读/擦除
RW/C1=可读可写/通过写1擦除
WO = 只写
保留位被隐藏且应写 0,在读访问时保留位没有定义。
如何读取 DM9000A 的寄存器 RSR?假设要读取 DM9000A 的寄存器 RSR(RX Status Register),需要分 2 步:
向 INDEX 端口写入 RSR 寄存器的地址(0x06)条件:nGCS1 信号拉低、 Xm0WEn 信号拉低、 Xm0ADDR2 拉低, 或者说向下面的地址写数据 0x06从 DATA 端口读取 RSR 寄存器的值条件:nGCS1 信号拉低、 Xm0OEn 信号拉低、 Xm0ADDR2 拉高, 或者说从下面的地址读数据
DM9000A的寄存器很多,但是我们并需要都掌握,我们只需要掌握其中几个最重要的寄存器的使用即可。
网络控制寄存器(NCR)
网络状态寄存器(NSR)
ISR
DAVICOM 指定配置和状态寄存器(DSCSR)
4. 网卡的初始化
网卡的初始化函数入口位于文件net/eth.c下的函数eth_init():
404 int eth_init(bd_t *bis)
405 {
406 struct eth_device *old_current, *dev;
……
425 old_current = eth_current;
426 do {
427 debug("Trying %s", eth_current->name);
428
429 if (eth_current->init(eth_current, bis) >= 0) {
430 eth_current->state = ETH_STATE_ACTIVE;
431
432 return 0;
433 }
434 debug("FAIL");
……
440 }
429行即调用我们注册的dm9000A初始化函数,从这也可以看出,整个架构是把网卡的驱动独立分隔开,与硬件操作相关的代码由用户自己填充并注册到系统中即可,便于扩展。进入dm9000_init():
290 static int dm9000_init(struct eth_device *dev, bd_t *bd)
291 {
292 int i, oft, lnk;
293 u8 io_mode;
294 struct board_info *db = &dm9000_info;
295
296 DM9000_DBG("%s", __func__);
297
298 RESET device
299 dm9000_reset();
300
301 if (dm9000_probe() < 0)
302 return -1;
303
304 Auto-detect 8/16/32 bit mode, ISR Bit 6+7 indicate bus width
305 io_mode = DM9000_ior(DM9000_ISR) >> 6;
306
307 switch (io_mode) {
308 case 0x0: 16-bit mode
309 printf("DM9000: running in 16 bit mode");
310 db->outblk = dm9000_outblk_16bit;
311 db->inblk = dm9000_inblk_16bit;
312 db->rx_status = dm9000_rx_status_16bit;
313 break;
314 case 0x01: 32-bit mode
315 printf("DM9000: running in 32 bit mode");
316 db->outblk = dm9000_outblk_32bit;
317 db->inblk = dm9000_inblk_32bit;
318 db->rx_status = dm9000_rx_status_32bit;
319 break;
320 case 0x02: 8 bit mode
321 printf("DM9000: running in 8 bit mode");
322 db->outblk = dm9000_outblk_8bit;
323 db->inblk = dm9000_inblk_8bit;
324 db->rx_status = dm9000_rx_status_8bit;
325 break;
326 default:
327 Assume 8 bit mode, will probably not work anyway
328 printf("DM9000: Undefined IO-mode:0x%x", io_mode);
329 db->outblk = dm9000_outblk_8bit;
330 db->inblk = dm9000_inblk_8bit;
331 db->rx_status = dm9000_rx_status_8bit;
332 break;
333 }
334
335 Program operating register, only internal phy supported
336 DM9000_iow(DM9000_NCR, 0x0);
337 TX Polling clear
338 DM9000_iow(DM9000_TCR, 0);
339 Less 3Kb, 200us
340 DM9000_iow(DM9000_BPTR, BPTR_BPHW(3) | BPTR_JPT_600US);
341 Flow Control : High/Low Water
342 DM9000_iow(DM9000_FCTR, FCTR_HWOT(3) | FCTR_LWOT(8));
343 SH FIXME: This looks strange! Flow Control
344 DM9000_iow(DM9000_FCR, 0x0);
345 Special Mode
346 DM9000_iow(DM9000_SMCR, 0);
347 clear TX status
348 DM9000_iow(DM9000_NSR, NSR_WAKEST | NSR_TX2END | NSR_TX1END);
349 Clear interrupt status
350 DM9000_iow(DM9000_ISR, ISR_ROOS | ISR_ROS | ISR_PTS | ISR_PRS);
351
352 printf("MAC: %pM", dev->enetaddr);
353
354 fill device MAC address registers
355 for (i = 0, oft = DM9000_PAR; i < 6; i++, oft++)
356 DM9000_iow(oft, dev->enetaddr[i]);
357 for (i = 0, oft = 0x16; i < 8; i++, oft++)
358 DM9000_iow(oft, 0xff);
359
360 read back mac, just to be sure
361 for (i = 0, oft = 0x10; i < 6; i++, oft++)
362 DM9000_DBG("%02x:", DM9000_ior(oft));
363 DM9000_DBG("");
364
365 Activate DM9000
366 RX enable
367 DM9000_iow(DM9000_RCR, RCR_DIS_LONG | RCR_DIS_CRC | RCR_RXEN);
368 Enable TX/RX interrupt mask
369 DM9000_iow(DM9000_IMR, IMR_PAR);
370
371 i = 0;
372 while (!(dm9000_phy_read(1) & 0x20)) { autonegation complete bit
373 udelay(1000);
374 i++;
375 if (i == 10000) {
376 printf("could not establish link");
377 return 0;
378 }
379 }
380
381 see what we've got
382 lnk = dm9000_phy_read(17) >> 12;
383 printf("operating at ");
384 switch (lnk) {
385 case 1:
386 printf("10M half duplex ");
387 break;
388 case 2:
389 printf("10M full duplex ");
390 break;
391 case 4:
392 printf("100M half duplex ");
393 break;
394 case 8:
395 printf("100M full duplex ");
396 break;
397 default:
398 printf("unknown: %d ", lnk);
399 break;
400 }
401 printf("mode");
402 return 0;
403 }
299行 函数DM9000_reset()是对dm9000A重置301行 函数dm9000_probe()分别从寄存器VID、PID读取厂家ID、产品ID305行 读取DM9000A的 ISR寄存器,根据bite[6:7]的值来决定最终从DM9000A中读取数位数,并将对应的函数设置到db->outblk和db->inblk这两个变量,最终上层服务想收发数据就通过这两个函数,对于16位模式,就分别赋值dm9000_outblk_16bit、dm9000_inblk_16bit;db->rx_status该函数用于从DM9000A中读取网卡的状态信息和数据包的长度,对于16位模式会赋值为dm9000_rx_status_16bit336~350行 对DM9000A进行初始化配置355~358行 将mac地址写入到DM9000A的PAR寄存器367行 使能数据接收369行 使能SRAM的读/写指针在指针地址超过SRAM的大小时自动跳回起始位置382行 读取phy寄存器DSCSR,打印当前网口的带宽
通过读 bit[15:12]来看经过自动协商后选择的是哪一种模式。网卡自动协商完成后,结果将被写到该位。若该位为 1,意味着操作 1 模式是 100M 全双工模式。
5. 数据的发送
发送流程
清中断,ISR寄存器bit[1] = 1发送写操作,操作MWCMD通过DM9000_DATA写入数据设置数据帧的长度 TXPLL、TXPLH发送发送请求,TCR等待数据发送完毕,轮训检查NSR清中断,ISR寄存器bit[1] = 1
网卡数据的发送函数是dm9000_send()
405
406 Hardware start transmission.
407 Send a packet to media from the upper layer.
408
409 static int dm9000_send(struct eth_device *netdev, void *packet, int length)
410 {
411 int tmo;
412 struct board_info *db = &dm9000_info;
413
414 DM9000_DMP_PACKET(__func__ , packet, length);
415
416 DM9000_iow(DM9000_ISR, IMR_PTM); Clear Tx bit in ISR
417
418 Move data to DM9000 TX RAM
419 DM9000_outb(DM9000_MWCMD, DM9000_IO); Prepare for TX-data
420
421 push the data to the TX-fifo
422 (db->outblk)(packet, length);
423
424 Set TX length to DM9000
425 DM9000_iow(DM9000_TXPLL, length & 0xff);
426 DM9000_iow(DM9000_TXPLH, (length >> 8) & 0xff);
427
428 Issue TX polling command
429 DM9000_iow(DM9000_TCR, TCR_TXREQ); Cleared after TX complete
430
431 wait for end of transmission
432 tmo = get_timer(0) + 5 * CONFIG_SYS_HZ;
433 while ( !(DM9000_ior(DM9000_NSR) & (NSR_TX1END | NSR_TX2END)) ||
434 !(DM9000_ior(DM9000_ISR) & IMR_PTM) ) {
435 if (get_timer(0) >= tmo) {
436 printf("transmission timeout");
437 break;
438 }
439 }
440 DM9000_iow(DM9000_ISR, IMR_PTM); Clear Tx bit in ISR
441
442 DM9000_DBG("transmit done");
443 return 0;
444 }
该函数的参数
struct eth_device *netdev:设备
void *packet :发送数据包存放的内存的首地址
int length :发送的数据包长度
414行 打开debug开关,该行会打印发送的数据包416行 使能数据包发送,将寄存器ISR的bit[1]设置为1419行 通过寄存器MWCMD写入一个地址,并向该地址对应的 SRAM 中写数据。执行写该指令之后,写指针会根据操作模式(8 位或 16 位)自动增加 1 或 2。422行 调用上一节db->outblk所赋值的函数将数据包发送的DM9000A的发送fifo中425~426行 将发送数据包长度写入到寄存器TXPLL/TXPLH中,这两个寄存器分别对应低字节和高字节429行 向寄存器TCR的bit[0]写入1,来请求发送数据,发送完毕该位自动清0432~440行 通过向寄存器ISR的bit[1]写入1,来清楚发送标记位
其中发送函数dm9000_outblk_16bit()定义如下:
159 static void dm9000_outblk_16bit(volatile void *data_ptr, int count)
160 {
161 int i;
162 u32 tmplen = (count + 1) / 2;
163
164 for (i = 0; i < tmplen; i++)
165 DM9000_outw(((u16 *) data_ptr)[i], DM9000_DATA);
166 }
164~165行 就是循环从地址DM9000_DATA读取数据并存储到data_ptr执行的内存中此处我们看到每次都是从相同的地址读取数据,为什么不需要做地址偏移呢?答:寄存器MWCMD已经和我们说的很清楚了,写该指令之后,指写指针根据操作模式(8 位或 16 位)增加 1 或 2。
6. 数据的接收
DM9000A的数据接收
464 static int dm9000_rx(struct eth_device *netdev)
465 {
466 u8 rxbyte, *rdptr = (u8 *) NetRxPackets[0];
467 u16 RxStatus, RxLen = 0;
468 struct board_info *db = &dm9000_info;
469
470 Check packet ready or not, we must check
471 the ISR status first for DM9000A
472 if (!(DM9000_ior(DM9000_ISR) & 0x01)) Rx-ISR bit must be set.
473 return 0;
474
475 DM9000_iow(DM9000_ISR, 0x01); clear PR status latched in bit 0
476
477 There is _at least_ 1 package in the fifo, read them all
478 for (;;) {
479 DM9000_ior(DM9000_MRCMDX); Dummy read
480
481 Get most updated data,
482 only look at bits 0:1, See application notes DM9000
483 rxbyte = DM9000_inb(DM9000_DATA) & 0x03;
484
485 Status check: this byte must be 0 or 1
486 if (rxbyte > DM9000_PKT_RDY) {
487 DM9000_iow(DM9000_RCR, 0x00); Stop Device
488 DM9000_iow(DM9000_ISR, 0x80); Stop INT request
489 printf("DM9000 error: status check fail: 0x%x",
490 rxbyte);
491 return 0;
492 }
493
494 if (rxbyte != DM9000_PKT_RDY)
495 return 0; No packet received, ignore
496
497 DM9000_DBG("receiving packet");
498
499 A packet ready now & Get status/length
500 (db->rx_status)(&RxStatus, &RxLen);
501
502 DM9000_DBG("rx status: 0x%04x rx len: %d", RxStatus, RxLen);
503
504 Move data from DM9000
505 Read received packet from RX SRAM
506 (db->inblk)(rdptr, RxLen);
507
508 if ((RxStatus & 0xbf00) || (RxLen < 0x40)
509 || (RxLen > DM9000_PKT_MAX)) {
510 if (RxStatus & 0x100) {
511 printf("rx fifo error");
512 }
513 if (RxStatus & 0x200) {
514 printf("rx crc error");
515 }
516 if (RxStatus & 0x8000) {
517 printf("rx length error");
518 }
519 if (RxLen > DM9000_PKT_MAX) {
520 printf("rx length too big");
521 dm9000_reset();
522 }
523 } else {
524 DM9000_DMP_PACKET(__func__ , rdptr, RxLen);
525
526 DM9000_DBG("passing packet to upper layer");
527 NetReceive(NetRxPackets[0], RxLen);
528 }
529 }
530 return 0;
531 }
472行 DM9000A的寄存器ISR的bit[0]必须设置为1,否则无法接收数据475行 将ISR的bit[0]设置为1479行 读取寄存器MRCMDX, 以从接收 SRAM 中读数据;执行读取该指令之后,指向内部 SRAM的读指针不变。DM9000A 开始预取 SRAM 中数据到内部数据缓冲中483~494行 从地址DM9000_DATA中读取数据,从SRAM中读取的第一个数据的bit[0]必须是1,否则出错500行 通过函数指针db->rx_status读取网卡的状态和接收到的数据包的长度506行 通过函数指针db->inblk从网卡中读取数据527行 通过函数NetReceive()提交给上层协议栈
真正读取数据的函数是dm9000_inblk_16bit();定义如下:
static void dm9000_inblk_16bit(void *data_ptr, int count)
{
int i;
u32 tmplen = (count + 1) / 2;
for (i = 0; i < tmplen; i++)
((u16 *) data_ptr)[i] = DM9000_inw(DM9000_DATA);
}
原理类似于函数dm9000_outblk_16bit,不再重复。
由此可见,要分析DM9000A的数据收发的原理和流程,就要分析我们注册网卡的以下几个函数:
635 dev->send = dm9000_send;
636 dev->recv = dm9000_rx;
310 db->outblk = dm9000_outblk_16bit;
311 db->inblk = dm9000_inblk_16bit;
最新活动更多
-
即日-11.13立即报名>>> 【在线会议】多物理场仿真助跑新能源汽车
-
11月19日立即报名>> 【线下论坛】华邦电子与恩智浦联合技术论坛
-
11月22日立即报名>> 【线下论坛】华邦电子与莱迪思联合技术论坛
-
11月28日立即报名>>> 2024工程师系列—工业电子技术在线会议
-
精彩回顾立即查看>> 【限时免费下载】TE暖通空调系统高效可靠的组件解决方案
-
精彩回顾立即查看>> 2024德州仪器嵌入式技术创新发展研讨会
推荐专题
- 1 格科微5000万像素产品再传佳讯 剑指中高端手机后主摄市场
- 2 2024最强翻译耳机来了,时空壶W4Pro到底有多震撼?
- 3 Kvaser发布全新软件CanKing 7:便捷CAN总线诊断与分析!
- 4 小米15供应链谁是大赢家?市场高度关注这家企业
- 5 小米入局家用NAS市场!手机厂商要做NAS普及推手?
- 6 长江存储PC41Q 1TB评测:裸条满载仅62度
- 7 国补加持!双11最值得入手的Mini Led电视来了!不买真亏大了
- 8 锐龙7 9800X3D首发评测:网游断崖式领先
- 9 工业加热技术创造烘焙奇迹:Kanthal AF加热元件以37.55秒创造世界最短披萨烘焙时间
- 10 瑞典Ionautics HiPSTER 25重磅新品! SiC晶体管引领HiPIMS高效镀膜新时代,镀膜效率显著提升
发表评论
请输入评论内容...
请输入评论/评论长度6~500个字
暂无评论
暂无评论