前言
提供一个基于STM32CubeMX+STM32F429BIT6+DP83848+NetX Duo的工程
https://download.csdn.net/download/mdzz6666/54176335
一、驱动框架
在NetX Duo驱动中,主要需要实现ETH的初始化(包括ETH控制器、TX描述符、RX描述符的初始化),ETH的开启与关闭、帧发送函数,接收函数是放在中断中调用的。
VOID nx_driver_stm32f429(NX_IP_DRIVER *driver_req_ptr)
{
switch (driver_req_ptr -> nx_ip_driver_command)
{
case NX_LINK_INTERFACE_ATTACH: _nx_driver_interface_attach(driver_req_ptr); break;
case NX_LINK_INITIALIZE: _nx_driver_initialize(driver_req_ptr); break;
case NX_LINK_ENABLE: _nx_driver_enable(driver_req_ptr); break;
case NX_LINK_DISABLE: _nx_driver_disable(driver_req_ptr); break;
case NX_LINK_ARP_SEND:
case NX_LINK_ARP_RESPONSE_SEND:
case NX_LINK_PACKET_BROADCAST:
case NX_LINK_RARP_SEND:
case NX_LINK_PACKET_SEND: _nx_driver_packet_send(driver_req_ptr); break;
case NX_LINK_MULTICAST_JOIN: _nx_driver_multicast_join(driver_req_ptr); break;
case NX_LINK_MULTICAST_LEAVE: _nx_driver_multicast_leave(driver_req_ptr); break;
default: driver_req_ptr -> nx_ip_driver_status = NX_UNHANDLED_COMMAND; break;
}
}
二、ETH初始化
1、ETH控制器初始化
ETH控制器初始化直接调用HAL的初始化函数即可。但是默认接收模式是循环检测,需要设置中断接收。
HETH.Init.RxMode = ETH_RXINTERRUPT_MODE; //循环检测 改为 中断接收
2、TX描述符初始化
①因为NetX在发出发送请求时,才能根据NX_PACK链表确定包地址。所以在HAL的TX初始化API中不给描述符分配BUFF地址。
②将所有的TX描述符的“完成时中断”打开,默认是关闭的。
③注释掉关于ETH_CHECKSUM_BY_HARDWARE的处理。
dmatxdesc->Status = ETH_DMATXDESC_TCH | ETH_DMATXDESC_IC;
f429_nx_driver_information.nx_packets_send[i] = NX_NULL;
// /* Set Second Address Chained bit */
// dmatxdesc->Status = ETH_DMATXDESC_TCH;
// /* Set Buffer1 address pointer */
// dmatxdesc->Buffer1Addr = (uint32_t)(&TxBuff[i*ETH_TX_BUF_SIZE]);
// if ((heth->Init).ChecksumMode == ETH_CHECKSUM_BY_HARDWARE)
// {
// /* Set the DMA Tx descriptors checksum insertion */
// dmatxdesc->Status |= ETH_DMATXDESC_CHECKSUMTCPUDPICMPFULL;
// }
3、RX描述符初始化
RX描述符的Buffer1Addr、ControlBufferSize并不从连续的RX_BUFF分配,而是先从NETX的包池申请NX_RECEIVE_PACKET,然后将NX_RECEIVE_PACKET的包缓存地址赋值给Buffer1Addr,NX_RECEIVE_PACKET的包缓存长度赋值给ControlBufferSize。
①注释掉HAL的RX初始化API中的Buffer1Addr、ControlBufferSize以及关于ETH_RXINTERRUPT_MODE的处理。
②从NetX包池中申请NX_RECEIVE_PACKET。
③将NX_RECEIVE_PACKET的包缓存地址赋值给Buffer1Addr,NX_RECEIVE_PACKET的包缓存长度赋值给ControlBufferSize。
注:不注释掉heth->RxDesc = DMARxDescTab;,程序可能无法运行,但注释后,HAL的RX接收函数将无法使用。该情况仅供参考,可能是我什么地方没有设置好。
// heth->RxDesc = DMARxDescTab;
.
.
.
// /* Set Buffer1 size and Second Address Chained bit */
// DMARxDesc->ControlBufferSize = ETH_DMARXDESC_RCH | ETH_RX_BUF_SIZE;
//
// /* Set Buffer1 address pointer */
// DMARxDesc->Buffer1Addr = (uint32_t)(&RxBuff[i*ETH_RX_BUF_SIZE]);
//
// if((heth->Init).RxMode == ETH_RXINTERRUPT_MODE)
// {
// /* Enable Ethernet DMA Rx Descriptor interrupt */
// DMARxDesc->ControlBufferSize &= ~ETH_DMARXDESC_DIC;
// }
if (nx_packet_allocate(RecPackPool, &packet_ptr, NX_RECEIVE_PACKET, NX_NO_WAIT) == NX_SUCCESS)
{
packet_ptr -> nx_packet_prepend_ptr += 2;
DMARxDesc->Buffer1Addr = (uint32_t) packet_ptr -> nx_packet_prepend_ptr;
DMARxDesc->ControlBufferSize = ETH_DMARXDESC_RCH | (packet_ptr -> nx_packet_data_end - packet_ptr -> nx_packet_data_start);
f429_nx_driver_information.nx_packets_receive[i] = packet_ptr;
}
else
return HAL_ERROR;
三、帧发送函数
NetX Duo协议栈发出发送请求时,帧数据保存在请求机构体driver_req_ptr 的 nx_ip_driver_packet中,是一个链表。该链表在调用ETH发送前,需要进行一些规定预处理,可参考官方文档,此处不做赘述。
因为NetX的拆包逻辑和HAL不同,所以HAL的发送函数改动很大,但基本原理相同。
/* 1、使用 TX_NX_PACK 链表,代替FrameLength
* a、根据 packet_ptr -> nx_packet_next 是否为NULL,判断头包/身体包/尾包
* 2、尾包描述符添加 中断 功能,使其在发送完一帧后,产生发送中断
* 3、send_nx_pack管理
*/
HAL_StatusTypeDef ETH_TransmitFrame(ETH_HandleTypeDef *heth, NX_PACKET *packet_ptr)
{
NX_PACKET *pktIdx;
ULONG curIdx = f429_nx_driver_information.send_current_index;
uint32_t bufcount = 0U;
/* Process Locked */
__HAL_LOCK(heth);
/* Set the ETH peripheral state to BUSY */
heth->State = HAL_ETH_STATE_BUSY;
for (pktIdx = packet_ptr; pktIdx != NX_NULL; pktIdx = pktIdx -> nx_packet_next) //判断是否是尾包
{
bufcount++;
if(((heth->TxDesc)->Status & ETH_DMATXDESC_OWN) != (uint32_t)RESET) //判断该描述符是否空闲
{
heth->State = HAL_ETH_STATE_BUSY_TX;
__HAL_UNLOCK(heth);
return HAL_ERROR;
}
/* Clear FIRST and LAST segment bits */
heth->TxDesc->Status &= ~(ETH_DMATXDESC_FS | ETH_DMATXDESC_LS); //清除头包/尾包标志
if (pktIdx == packet_ptr) //头包
heth->TxDesc->Status |= ETH_DMATXDESC_FS;
else
curIdx = (curIdx + 1) & (F429_NX_TXDESC_COUNT - 1); //计数,非头包 = 自减1
if (pktIdx -> nx_packet_next == NX_NULL) //尾包
heth->TxDesc->Status |= ETH_DMATXDESC_LS | ETH_DMATXDESC_IC; //添加中断标志位,使其在发送完一帧后,产生一次中断
heth->TxDesc->Buffer1Addr = (ULONG)packet_ptr->nx_packet_prepend_ptr;
heth->TxDesc->ControlBufferSize = ((packet_ptr -> nx_packet_append_ptr - packet_ptr->nx_packet_prepend_ptr) & ETH_DMATXDESC_TBS1);
heth->TxDesc->Status |= ETH_DMATXDESC_OWN;
heth->TxDesc = (ETH_DMADescTypeDef *)(heth->TxDesc->Buffer2NextDescAddr);
}
f429_nx_driver_information.nx_packets_send[curIdx] = packet_ptr; //保存帧头到当前指针指向的数组,但是保存的数据并不连续????
f429_nx_driver_information.send_current_index = (curIdx + 1) & (F429_NX_TXDESC_COUNT - 1);
f429_nx_driver_information.send_in_use_index += bufcount;
/* When Tx Buffer unavailable flag is set: clear it and resume transmission */
if (((heth->Instance)->DMASR & ETH_DMASR_TBUS) != (uint32_t)RESET)
{
/* Clear TBUS ETHERNET DMA flag */
(heth->Instance)->DMASR = ETH_DMASR_TBUS;
/* Resume DMA transmission*/
(heth->Instance)->DMATPDR = 0U;
}
/* Set ETH HAL State to Ready */
heth->State = HAL_ETH_STATE_READY;
/* Process Unlocked */
__HAL_UNLOCK(heth);
/* Return function status */
return HAL_OK;
}
四、ETH中断函数
帧发送完成、接收到帧都会产生中断,所以需要在中断中处理这两种情况。
注意:HAL提供的RX与TX回调函数有问题,不能直接使用。所以我们直接修改void ETH_IRQHandler(void);
void ETH_IRQHandler(void)
{
ULONG status;
status = ETH->DMASR;
ETH->DMASR = ETH_DMA_IT_R | ETH_DMA_IT_T | ETH_DMA_IT_NIS;
if(status & ETH_DMA_IT_T)
_nx_driver_packet_transmitted(); //发送完成的后续处理
if(status & ETH_DMA_IT_R)
_nx_driver_packet_received(); //接收包处理
}
五、发送完成的后续处理
帧发送完成后,需要将保存帧数据的NX_PACK链表释放掉,使其返回NetX的包池中,供后续业务申请使用。
在发送函数中将NX_PACK链表中首节点地址保存在数组f429_nx_driver_information.nx_packets_send[16]中。
VOID _nx_driver_packet_transmitted(VOID)
{
ULONG numOfBuf = f429_nx_driver_information.send_in_use_index;
ULONG idx = f429_nx_driver_information.send_release_index;
while (numOfBuf--)
{
if (f429_nx_driver_information.nx_packets_send[idx] == NX_NULL) //在 nx_packets_send 找到刚才发送的帧头
{
idx = (idx + 1) & (F429_NX_TXDESC_COUNT - 1);
continue;
}
if ((ETH_DMATxDescTab[idx].Status & ETH_DMATXDESC_OWN) == 0) //idx也指向 帧尾 的描述符,该判断语句表示帧尾发送完成,表明整个帧都发送完成
{
f429_nx_driver_information.nx_packets_send[idx] -> nx_packet_prepend_ptr = f429_nx_driver_information.nx_packets_send[idx] -> nx_packet_prepend_ptr + NX_DRIVER_ETHERNET_FRAME_SIZE;
f429_nx_driver_information.nx_packets_send[idx] -> nx_packet_length = f429_nx_driver_information.nx_packets_send[idx] -> nx_packet_length - NX_DRIVER_ETHERNET_FRAME_SIZE;
nx_packet_transmit_release(f429_nx_driver_information.nx_packets_send[idx]);
f429_nx_driver_information.nx_packets_send[idx] = NX_NULL;
idx = (idx + 1) & (F429_NX_TXDESC_COUNT - 1);
f429_nx_driver_information.send_in_use_index = numOfBuf;
f429_nx_driver_information.send_release_index = idx;
}
else
break;
}
}
六、中断接收函数及接收完成后的后续处理
因为RX初始化中的BUG,在注释掉heth->RxDesc = DMARxDescTab;之后,HAL提供的RX接收函数无法使用,所以此处基本参考的是“安富莱”的代码,后续解决该BUG后,再来更新。