单片机以太网方案
- 如果 MCU 内部集成 MAC 控制器只需要外接一个 PHY 芯片就够了
- 如果 MCU 内部没有 MAC 需要外接的控制器 MAC 芯片和 PHY 这两个芯片可以分立或集成在一个芯片中
以上两种方案,MAC PHY 完成了 TCP/IP 物理层和数据链路层是五层模型的最低两层。网络层、传输层、应用层等上述层需要在 MCU 中实现。当然,普通程序员想要实现 IP 层、TCP/UDP 层还是很难的。但别担心,我们可以使用成熟的开源 TCP/IP 协议栈,如 uIP、LwIP。它们都是轻量级的 TCP/IP 协议栈适用于资源有限的单片机。
- 使用硬件 TCP/IP 协议栈,MCU 只需实现应用层代码即可。
例如,使用 W5500 芯片、传输层及以下均由外部芯片完成,MCU 只需要配置 W5500,从 W5500 收发数据,完成应用层逻辑。另一种方案是 MCU 原理和使用外接串口转以太网模块 W5500 方案类似。
ENC28J60
ENC28J60 属于上述方案 2,ENC28J60 单颗芯片集成了 MAC 和 PHY,提供 SPI 接口用来和 MCU 通信。
寄存器 —— 体现了硬件的灵活性
在方案 2 网络层、传输层、应用层 以软件的形式 CPU 物理层和数据链路层以硬件的形式实现 ENC28J60 尽管如此 ENC28J60 它是一个硬件,我们不能编程和修改它的功能,但它仍然具有一定的灵活性,即可配置的寄存器。我们可以设置寄存器来控制最低两层,或者读取寄存器来监控最低两层。 例如,我们可以设置它 ECON1 控制接收使能和发送请求的寄存器DMA、选择存储区等。 还可以设置 PHCON1 控制寄存器 PHY 模块复位等功能。
缓冲器 —— 硬件的价值体现
说到底 ENC28J60 的功能是从 IP 层将数据并发送,或将收到的数据发送给 IP 层。这两个方向的操作都需要缓冲器来临时存储数据。
ENC28J60 读写特性
ENC28J60 存储器有三种类型: ? 控制寄存器 ? 以太网缓冲器 ? PHY 寄存器 其中 MCU 可以通过 SPI 用指令直接读写界面 和 。但不能通过 SPI 直接访问接口 PHY 寄存器,只可以通过 MAC 中的 MII 访问这些寄存器。 可以理解为 SPI 是和 MAC 相连的,可以直接访问 MAC 但是 PHY 只与 MAC 通过 RMII、MIIM 接口连接,所以想读 PHY 寄存器的值必须通过 MAC,进一步说就是通过 MAC 的 MIIM 接口,或通过 MAC 的 MII 寄存器。 ENC28J60 所有的存取器都是静态的 RAM 实现的方式。 可用于控制寄存器存储空间 ECON1 选择寄存器中存储区的位置 BSEL1:BSEL0 进行选择。 每个存储区都是 32 字节长,可用 5 地址值寻址。 所有存储区最后五个单元 (1Bh 到 1Fh)都指向同一组寄存器:EIE、EIR、ESTAT、ECON2 和 ECON1。 它们是控制和监控设备工作的关键寄存器, 下图为所有控制寄存器: 能够从任意 Bank 访问 ECON1 因为 ECON1 中存有选择 Bank 的两个 bit,倘若 ECON1 只能在 Bank 0 一旦访问权转换为中访, Bank 2,而 Bank 2 又无法访问 ECON1 来选择 Bank,那不是悲催吗?只能停留在 Bank 2 了。。。??
读取 PHY 寄存器实例
下面大致写一段代码读取 PHY 的 PHSTAT1 该寄存器的寄存器 bit2 表示链路状态 我们一步一步地写 代码
int main(void) {
/* USER CODE BEGIN 1 */ uint8_t tx_data = 0x00; uint8_t rx_data = 0x00; uint16_t pyh_data; /* USER CODE END 1 */ /* MCU Configuration----------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* Configure the system clock */ SystemClock_Config(); /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_SPI1_Init(); MX_USART1_UART_Init(); /* USER CODE BEGIN 2 */ /* 1111111111111111111. */ /* select bank 2 begin */ tx_data = 0x0 | 0x1F; // BFC | ECONT1 // BF
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, &tx_data, 1, 10);
tx_data = 0x03; // 清除最低两位 // 03
HAL_SPI_Transmit(&hspi1, &tx_data, 1, 10);
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET); // 拉高 CS 引脚可结束 BFC 命令
tx_data = 0x80 | 0x1F; // BFS | ECONT1 // 9F
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, &tx_data, 1, 10);
tx_data = 0x02; // 10h 表示选中 bank2 // 02
HAL_SPI_Transmit(&hspi1, &tx_data, 1, 10);
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET); // 拉高 CS 引脚可结束 BFS 命令
/* select bank 2 end */
/* MIREGADR(14h) <-- PHSTAT1(01h) begin */
tx_data = 0x40 | 0x14; // WCR | MIREGADR(14h) // 地址 // 54
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, &tx_data, 1, 10);
tx_data = 0x01; // PHSTAT1(01h) // 值 // 01
HAL_SPI_Transmit(&hspi1, &tx_data, 1, 10);
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET);
/* MIREGADR(14h) <-- PHSTAT1(01h) end */
/* 2222222222222222222 */
/* MICMD(12h) <-- MIIRD(01h) begin */ // 92
tx_data = 0x80 | 0x12; // BFS | MICMD(12h) // 地址
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, &tx_data, 1, 10);
tx_data = 0x01; // MIIRD(01h) // 值 // 01
HAL_SPI_Transmit(&hspi1, &tx_data, 1, 10);
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET);
/* MICMD(12h) <-- MIIRD(01h) end */
/* select bank 3 begin */
tx_data = 0x80 | 0x1F; // BFS | ECONT1 // 9F
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, &tx_data, 1, 10);
tx_data = 0x03; // 11h 表示选中 bank2 // 03
HAL_SPI_Transmit(&hspi1, &tx_data, 1, 10);
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET); // À¸ß CS Òý½Å¿É½áÊø BFS ÃüÁî
/* select bank 3 end */
/* MISTAT(0Ah) <-- BUSY(01h) begin */
tx_data = 0x80 | 0x0A; // BFS | MICMD(0Ah) // 地址 // 8A
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, &tx_data, 1, 10);
tx_data = 0x01; // BUSY(01h) // 值 // 01
HAL_SPI_Transmit(&hspi1, &tx_data, 1, 10);
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET);
/* MISTAT(0Ah) <-- BUSY(01h) end */
/* 333333333333333333. */
/* 等待 10.24us */
// HAL_Delay(1);
/* MISTAT(0Ah) */
// tx_data = 0x00 | 0x0A; // RCR | MISTAT(0Ah) // µØÖ·
// HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);
// HAL_SPI_Transmit(&hspi1, &tx_data, 1, 10);
//
// HAL_SPI_Transmit(&hspi1, &rx_data, 1, 10); // ¶ÁÖµ
// HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET);
///* MIREGADR(14h) <-- PHSTAT1(01h) end */
// printf("rx_data = 0x%x\r\n", rx_data);
/* 4 */
/* select bank 2 begin */
tx_data = 0xA0 | 0x1F; // BFC | ECONT1
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, &tx_data, 1, 10);
tx_data = 0x03; // Çå³ý×îµÍÁ½Î»
HAL_SPI_Transmit(&hspi1, &tx_data, 1, 10);
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET); // À¸ß CS Òý½Å¿É½áÊø BFC ÃüÁî
tx_data = 0x80 | 0x1F; // BFS | ECONT1
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, &tx_data, 1, 10);
tx_data = 0x02; // 10h ±íʾѡÖÐ bank2
HAL_SPI_Transmit(&hspi1, &tx_data, 1, 10);
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET); // À¸ß CS Òý½Å¿É½áÊø BFS ÃüÁî
/* select bank 2 end */
/* MICMD(12h) <-- MIIRD(01h) begin */
tx_data = 0xA0 | 0x12; // BFC | MICMD(12h) // µØÖ·
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, &tx_data, 1, 10);
tx_data = 0x01; // MIIRD(01h) // Öµ
HAL_SPI_Transmit(&hspi1, &tx_data, 1, 10);
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET);
/* MICMD(12h) <-- MIIRD(01h) end */
/* 5 */
/* read MIRDL(18h) begin */
tx_data = 0x00 | 0x18; // RCR | MIRDL(18h) // µØÖ·
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, &tx_data, 1, 10);
HAL_SPI_Receive(&hspi1, &rx_data, 1, 10); // ¶ÁÖµ
// printf("rx_data = 0x%x\r\n", rx_data);
HAL_SPI_Receive(&hspi1, &rx_data, 1, 10); // ¶ÁÖµ
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET);
// printf("rx_data = 0x%x\r\n", rx_data);
pyh_data = rx_data;
/* read MIRDH(19h) begin */
tx_data = 0x00 | 0x19; // RCR | MIRDH(19h) // µØÖ·
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, &tx_data, 1, 10);
HAL_SPI_Receive(&hspi1, &rx_data, 1, 10);
HAL_SPI_Receive(&hspi1, &rx_data, 1, 10); // ¶ÁÖµ
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET);
/* read MIRDH(19h) end */
// printf("rx_data = 0x%x\r\n", rx_data);
pyh_data = pyh_data + (rx_data << 8);
printf("pyh_data = 0x%x\r\n", pyh_data);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
// printf("helo\r\n");
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
HAL_Delay(1000);
// printf("0x%X: 0x%x\n", tx_data, rx_data);
// cr_addr++;
}
/* USER CODE END 3 */
}
插拔网线 读取到的 PHSTAT1 寄存器值 插上网线,值为 0x1804,即 bit2 = 1; 拔掉网线,值为 0x1800,即 bit2 = 0。
读到的寄存器状态和网线连接状态一致,成功!