1. 必须根据DP83848的自动协商结果配置ETH_MACCR的DM(duplex mode)和FES(fast ethernet speed)位。
网线上数据发送线和接收线是分开的。发送用的是白橙(正线)、橙(负线)这对双绞线,而接收用的是白绿(正线)、绿(负线)这对双绞线。
当以太网集线器上只插了两根网线时,一定不会产生碰撞,既可以配置为半双工也可以配置为全双工,配置为半双工也就意味着强制不允许发送和接收同时进行。如果集线器上插了三台及以上的电脑,则有可能产生碰撞,此时只能配置为半双工模式。
2. 必须在RCC中同时打开ETH的三个RCC时钟,哪怕只想发送数据,也必须打开MACRX的时钟。
注意:以太网外设的DMA是专用DMA,与其他外设所用的DMA1和DMA2没有关系。
RCC->AHBENR |= RCC_AHBENR_ETHMACEN | RCC_AHBENR_ETHMACTXEN | RCC_AHBENR_ETHMACRXEN;3. 发送描述符指向的缓冲区地址TDES2和TDES3必须为SRAM中的地址(无需对齐),不允许为Flash中的地址。大小TBS1和TBS2为0~8191中的任意整数。
在lwip协议栈中如果遇到q->payload指向Flash区域的情况,必须把数据复制到SRAM中,否则帧肯定会发送失败。
uint8_t tbuf[768];4. 注意正确设置FS和LS的值。当发送描述符列表循环使用时,不要忘了清除FS和LS位,例如下面程序中的两个else分支不能去掉:
uint16_t tbuf_used = 0;
desc_tx_ptr[1] = q->len;
desc_tx_ptr[2] = (uint32_t)q->payload;
if ((desc_tx_ptr[2] & 0xffff0000) != 0x20000000)
{
// must be in the 64KB SRAM
printf("Data 0x%08x isn't in SRAM!\n", desc_tx_ptr[2]);
desc_tx_ptr[2] = (uint32_t)tbuf + tbuf_used;
memcpy((void *)desc_tx_ptr[2], q->payload, q->len);
tbuf_used += q->len;
}
if (q == p)
desc_tx_ptr[0] |= ETH_TDES0_FS;
else
desc_tx_ptr[0] &= ~ETH_TDES0_FS; // 注意: 描述符是循环使用的, 因此不要忘记清除FS和LS位
if (q->next == NULL)
desc_tx_ptr[0] |= ETH_TDES0_LS;
else
desc_tx_ptr[0] &= ~ETH_TDES0_LS;
【示例程序】
main.c:
#include <stdio.h>ETH.h:
#include <stm32f10x.h>
#include "ETH.h"
// 要发送的数据包内容(数据链路层)
uint8_t packet[] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x42, 0x52, 0x4d, 0x4e, 0x45, 0x54, 0x08, 0x06, 0x00, 0x01,
0x08, 0x00, 0x06, 0x04, 0x00, 0x01, 0x42, 0x52, 0x4d, 0x4e, 0x45, 0x54, 0xc0, 0xa8, 0x1e, 0x0a,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x1e, 0x0a
}; // 192.168.30.10
// 发送、接收描述符
uint32_t desc_tx[10][4];
uint32_t desc_rx[10][4];
// 接收缓冲区
uint8_t rx_buffer[10][2][256];
// 以16进制格式显示数据包内容
void dump_data(const void *data, uint16_t len)
{
const uint8_t *p = data;
while (len--)
printf("%02X", *p++);
printf("\n");
}
// 用于printf, 项目属性必须勾选Use MicroLIB后才能用
int fputc(int ch, FILE *fp)
{
if (fp == stdout)
{
if (ch == '\n')
{
while ((USART3->SR & USART_SR_TXE) == 0);
USART3->DR = '\r';
}
while ((USART3->SR & USART_SR_TXE) == 0);
USART3->DR = ch;
}
return ch;
}
/* 读写PHY上的寄存器, 具体请参阅DP83848手册中的寄存器表, 以及STM32参考手册中的29.4.1 Station management interface: SMI */
/* PHY模块的地址为0x01 */
uint16_t read_reg(uint8_t addr)
{
ETH->MACMIIAR = (1 << ETH_MACMIIAR_PA_Pos) | (addr << ETH_MACMIIAR_MR_Pos) | ETH_MACMIIAR_CR_Div42 | ETH_MACMIIAR_MB;
while (ETH->MACMIIAR & ETH_MACMIIAR_MB);
return ETH->MACMIIDR;
}
void write_reg(uint8_t addr, uint16_t value)
{
ETH->MACMIIDR = value;
ETH->MACMIIAR = (1 << ETH_MACMIIAR_PA_Pos) | (addr << ETH_MACMIIAR_MR_Pos) | ETH_MACMIIAR_CR_Div42 | ETH_MACMIIAR_MW | ETH_MACMIIAR_MB;
while (ETH->MACMIIAR & ETH_MACMIIAR_MB);
}
/* 查看PHY各寄存器的值 (串口发送b) */
void read_regs(void)
{
uint8_t i;
for (i = 0x00; i <= 0x1d; i++)
printf("Register 0x%02x: 0x%04x\n", i, read_reg(i));
}
/* 查看desc_rx的内容 (串口发送c) */
void disp_rx(void)
{
uint8_t i;
for (i = 0; i < sizeof(desc_rx) / sizeof(desc_rx[0]); i++)
printf("Buffer %d: 0x%08x 0x%08x 0x%08x 0x%08x\n", i, desc_rx[i][0], desc_rx[i][1], desc_rx[i][2], desc_rx[i][3]);
}
// STM32官方的ETH库en.stsw-stm32045(官网可下载)中的ETH_Init函数可自动完成这一步骤
void auto_negotiation(void)
{
uint16_t value;
while ((read_reg(DP83848_BMSR) & DP83848_BMSR_LS) == 0); // 等待网线插好
value = read_reg(DP83848_BMCR);
if ((value & DP83848_BMCR_ANE) == 0) // 若DP83848外部接线没有接成自动协商模式
write_reg(DP83848_BMCR, value | DP83848_BMCR_ANE); // 则手动执行自动协商
while ((read_reg(DP83848_BMSR) & DP83848_BMSR_ANC) == 0); // 等待自动协商完毕
// 根据自动协商结果配置MACCR寄存器
value = read_reg(DP83848_PHYSTS);
if (value & DP83848_PHYSTS_DS)
ETH->MACCR |= ETH_MACCR_DM;
if ((value & DP83848_PHYSTS_SS) == 0)
ETH->MACCR |= ETH_MACCR_FES;
}
int main(void)
{
uint8_t i;
RCC->APB1ENR = RCC_APB1ENR_USART3EN | RCC_APB1ENR_UART5EN;
RCC->APB2ENR = RCC_APB2ENR_AFIOEN | RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPCEN | RCC_APB2ENR_IOPDEN;
/* 请参阅 29.3 Ethernet pins */
GPIOA->CRL = 0x44444f44; // PA2: MDIO复用开漏输出(因为已经接了外部上拉电阻)
GPIOA->CRH = 0x8884444b; // PA8: MCO复用推挽输出
GPIOB->CRH = 0x44bbbb44; // PB10: 串口3的发送引脚, PB11~13: TX_EN, TXD0~1, 全部设为复用推挽输出
GPIOC->CRL = 0x444444b4; // PC1: MDC复用推挽输出
// 其余I/O均为默认值
// 串口5的接收引脚PD2为浮空输入(默认值, 无需配置)
// 给DP83848提供50MHz时钟后, 网卡接口上的灯才会亮, PA8要和PA1接到一起
RCC->CFGR2 |= RCC_CFGR2_PLL3MUL_3; // PLL3CLK: 50MHz
RCC->CR |= RCC_CR_PLL3ON;
while ((RCC->CR & RCC_CR_PLL3RDY) == 0);
RCC->CFGR |= RCC_CFGR_MCO_3 | RCC_CFGR_MCO_1 | RCC_CFGR_MCO_0; // MCO(PA8)=PLL3CLK
AFIO->MAPR = AFIO_MAPR_MII_RMII_SEL | AFIO_MAPR_ETH_REMAP; // 使用RMII接口, 打开ETH Remap
RCC->AHBENR |= RCC_AHBENR_ETHMACEN | RCC_AHBENR_ETHMACTXEN | RCC_AHBENR_ETHMACRXEN; // ETH的三个时钟必须全部打开, 否则ETH_DMA将无法工作
// 用USART3发送字符(PB10), UART5接收字符(PD2)
USART3->BRR = 312;
USART3->CR1 = USART_CR1_UE | USART_CR1_TE;
UART5->BRR = 312;
UART5->CR1 = USART_CR1_UE | USART_CR1_RE | USART_CR1_RXNEIE; // 开串口接收中断
NVIC_EnableIRQ(UART5_IRQn);
printf("STM32F107VC Ethernet\n");
// PHY自动协商(非常重要!!!!)
// 根据自动协商结果配置ETH_MACCR的DM(duplex mode)和FES(fast ethernet speed)位
auto_negotiation();
ETH->MACCR |= ETH_MACCR_TE | ETH_MACCR_RE; // 允许发送/接收
ETH->DMAIER = ETH_DMAIER_NISE | ETH_DMAIER_AISE | ETH_DMAIER_TIE | ETH_DMAIER_RIE | ETH_DMAIER_RBUIE; // 打开发送/接收完毕中断, 以及接收缓冲满的中断
NVIC_EnableIRQ(ETH_IRQn);
desc_tx[0][0] = ETH_TDES0_OWN | ETH_TDES0_IC | ETH_TDES0_TER | ETH_TDES0_LS | ETH_TDES0_FS; // IC必须为1, 否则无法触发发送完成中断
desc_tx[0][1] = sizeof(packet); // 大小(任意)
desc_tx[0][2] = (uint32_t)packet; // 要发送的数据包 (必须在SRAM中的任意地址, 不能在Flash中!!!)
if ((desc_tx[0][2] & 0xffff0000) != 0x20000000)
printf("Error: Data must be in SRAM!\n");
ETH->DMATDLAR = (uint32_t)desc_tx; // 设置发送描述符的首地址
// 初始化接收描述符
for (i = 0; i < sizeof(desc_rx) / sizeof(desc_rx[0]); i++)
{
desc_rx[i][0] = ETH_RDES0_OWN;
desc_rx[i][1] = (sizeof(rx_buffer[0][0]) << ETH_RDES1_RBS2_Pos) | (sizeof(rx_buffer[0][0]) << ETH_RDES1_RBS1_Pos);
desc_rx[i][2] = (uint32_t)rx_buffer[i][0];
desc_rx[i][3] = (uint32_t)rx_buffer[i][1];
}
desc_rx[i - 1][1] |= ETH_RDES1_RER;
ETH->DMARDLAR = (uint32_t)desc_rx;
printf("ETH->DMATDLAR=0x%08x, ETH->DMARDLAR=0x%08x\n", ETH->DMATDLAR, ETH->DMARDLAR); // 描述符的首地址必须是32位对齐!
ETH->DMAOMR |= ETH_DMAOMR_TSF | ETH_DMAOMR_RSF | ETH_DMAOMR_ST | ETH_DMAOMR_SR; // 开始发送和接收
while (1)
__WFI();
}
void ETH_IRQHandler(void)
{
printf("ETH->DMASR=0x%08x\n", ETH->DMASR);
if (ETH->DMASR & ETH_DMASR_TS) // 发送完毕
{
ETH->DMASR = ETH_DMASR_NIS | ETH_DMASR_TS; // 写1清除标志位
printf("Transmitted! desc_tx[0]: 0x%08x, 0x%08x, 0x%08x, 0x%08x\n", desc_tx[0][0], desc_tx[0][1], desc_tx[0][2], desc_tx[0][3]);
}
else if (ETH->DMASR & ETH_DMASR_RS) // 接收完毕
{
ETH->DMASR = ETH_DMASR_NIS | ETH_DMASR_RS;
printf("Received!\n");
}
else if (ETH->DMASR & ETH_DMASR_RBUS) // 接收缓冲满
{
ETH->DMASR = ETH_DMASR_AIS | ETH_DMASR_RBUS;
printf("Receive buffer unavailable!\n");
}
}
void UART5_IRQHandler(void)
{
uint8_t data = UART5->DR;
if (data == 'a')
printf("ETH->DMASR=0x%08x\n", ETH->DMASR);
else if (data == 'b')
read_regs();
else if (data == 'c')
disp_rx();
}
// 补充stm32f10x.h中缺少的一些寄存器位的定义
#define _BV(n) (1u << (n))
#define ETH_MACMIIAR_PA_Pos 11
#define ETH_MACMIIAR_MR_Pos 6
#define ETH_TDES0_OWN _BV(31)
#define ETH_TDES0_IC _BV(30)
#define ETH_TDES0_LS _BV(29)
#define ETH_TDES0_FS _BV(28)
#define ETH_TDES0_TER _BV(21)
#define ETH_TDES0_TCH _BV(20)
#define ETH_RDES0_OWN _BV(31)
#define ETH_RDES1_DIC _BV(31)
#define ETH_RDES1_RBS2_Pos 16
#define ETH_RDES1_RER _BV(15)
#define ETH_RDES1_RCH _BV(14)
#define ETH_RDES1_RBS1_Pos 0
// DP83848中的一些寄存器位
#define DP83848_BMCR 0x00 // Basic Mode Control Register
#define DP83848_BMCR_ANE _BV(12) // Auto-Negotiation Enable
#define DP83848_BMSR 0x01 // Basic Mode Status Register
#define DP83848_BMSR_ANC _BV(5) // Auto-Negotiation Complete
#define DP83848_BMSR_LS _BV(2) // Link Status
#define DP83848_PHYSTS 0x10 // PHY Status Register
#define DP83848_PHYSTS_DS _BV(2) // Duplex
#define DP83848_PHYSTS_SS _BV(1) // Speed10
【程序运行结果】
STM32F107VC Ethernet如果没有打开MACRX时钟,则无法触发发送完毕中断。
ETH->DMATDLAR=0x20000030, ETH->DMARDLAR=0x200000d0
Transmitted! desc_tx[0]: 0x70200000, 0x0000002a, 0x20000000, 0x00000000
ETH->DMASR=0x00670404
received!
received!
received!
received!
received!
received!
received!
received!
received!
received!
receive buffer unavailable!
receive buffer unavailable!
receive buffer unavailable!
receive buffer unavailable!
receive buffer unavailable!
receive buffer unavailable!
如果没有配置好STM32中的以太网的速度(FES位)和半/全双工模式(DM位),则只能触发发送完毕中断,不能触发接收完毕中断。