AutoSAR实战教程--MCAL之ETH Driver移植集成LwIP以太网协议栈(英飞凌Tc3xx)
近日天气转凉,偶感风寒,百感交集,遂赋诗一首: 年长头发稀,未老人先贫。 满目山河恨,落花下津门。 注:当下裁员、降薪、和美帝那点事,故曰:满目山河恨,借鉴杜少陵"感时花溅泪,恨别鸟惊心"。 |
工作背景
其实这个方法曾福大佬已经写过了,他是基于NXP芯片的MCAL做的,我在他的博客下面把关键代码也贴上了。大家想看的话可以点击LWIP基于MCAL ETH Driver。之所以我今天写这个文章是看他写的,我仍然有疑惑,所以在巨人的肩膀上,我把这个事再说透一些。
我的Base工程是英飞凌提供的基于iLLD实现的LwIP工程。
port文件夹下文件其实不多,
所以我重点是要看这两个.c文件,里面对外暴露的接口。这里面只有2个暴露的接口,聪明的你会发现怎么缺少了发送接口呢?肿么肥事?
实际上low_level_output在ifx_netif_init中被绑定了,所以low_level_output就没有再对外暴露的必要。总结下来,我们要实现的最重要的3个函数就是
err_t ifx_netif_init(struct netif *netif); err_t ifx_netif_input(struct netif *netif); static sint8 low_level_output(netif_t *netif, pbuf_t *p)先做到心中有数,好了,继续我们的工作。
第一步:编译通过
叔考研期间快把谭浩强老爷子的C语言课本摸秃噜皮了,至今仍能记住的一句话是:自顶向下,逐步细化,模块化设计,结构化编码。所以我们做事情要有先后顺序,轻重缓急。
由于iLLD和MCAL代码差别很大,我的Base工程里面基于iLLD实现的很多函数,在MCAL环境下都不能用,所以,我要先适配接口,做法就是把各个函数功能屏蔽了,只留个空壳,一些非常必须的数据类型复制粘贴之外,尽量不引入其他额外的东西。
比如这样,用一个宏把函数内容屏蔽了,这样后期我用它,就解开这个宏,不用就留着供我参考。
第二步:接口适配
| MCAL接口 | iLLD接口 |
|---|---|
EthIf_RxIndication | Ifx_Lwip_pollReceiveFlags |
| 隶属于ifx_netif_input | |
| 隶属于隶属于low_level_input | |
IrqEthernet_Init | ifx_netif_init |
Eth_Init | |
EthTrcv_Init | |
Eth_Transmit | low_level_output |
Eth_ProvideTxBuffer |
初始化函数
对于init部分,这个不用我多说,都是初始化,也很容易看懂,你可以直接追加MCAL提供的Init接口,也可以把IrqEthernet_Init/Eth_Init/EthTrcv_Init放在ifx_netif_init里面。
这里注意一点,我们看:
这个g_Lwip是个全局变量,也就是对Lwip各项配置属性做个一个结构体,所以这一块我们还是要保留的。后面有很多函数都是以g_Lwip作为参数,比如:
接收函数
这个iLLD函数周期性的去polling接收数据,它跟MCAL中的EthIf_RxIndication被接收中断触发,功能几乎是一样的。但是!
我们看Ifx_Lwip_pollReceiveFlags它下面的函数ifx_netif_input的参数&g_Lwip.netif,这是个什么玩意?对比下接口差异
MCAL接口
iLLD接口
我们对比这两种接口就会发现:
1. iLLD中的payload是通过遍历链表中获取的数据。
2. iLLD接口中获取的是原始帧,包含头。
3. MCAL接口在EthIf_RxIndication接口之前已经完成了链表遍历,并且把ETH头信息去掉了。
所以我们在适配这两个接口的时候要把EthIf_RxIndication中的DataPtr数据+头信息,给还原回一个完整的ETH原始数据,并且不用再做遍历描述符链表的操作了(MCAL代码已经做了),这样才能给ifx_netif_input用。
EthIf_RxIndication适配
//This MCAL API Replace the Ifx_Lwip_pollReceiveFlags void EthIf_RxIndication ( uint8 CtrlIdx, uint16 FrameType, boolean IsBroadcast, uint8 * PhysAddrPtr, uint8 * DataPtr, uint16 LenByte ) { //---->ifx_netif_input //Trans CtrlIdx, FrameType, IsBroadcast, PhysAddrPtr, DataPtr, LenByte --> pbuf_t #if ETH_PAD_SIZE uint8 offset_ETH_PAD_SIZE = 0;//ETH_PAD_SIZE; #else uint8 offset_ETH_PAD_SIZE = 0; #endif if(IsBroadcast) { rawEthFrame[0+offset_ETH_PAD_SIZE] = 0xFF;// rawEthFrame[1+offset_ETH_PAD_SIZE] = 0xFF;// rawEthFrame[2+offset_ETH_PAD_SIZE] = 0xFF;// rawEthFrame[3+offset_ETH_PAD_SIZE] = 0xFF;// rawEthFrame[4+offset_ETH_PAD_SIZE] = 0xFF;// rawEthFrame[5+offset_ETH_PAD_SIZE] = 0xFF;// } else { rawEthFrame[0+offset_ETH_PAD_SIZE] = ethAddr.addr[0];// rawEthFrame[1+offset_ETH_PAD_SIZE] = ethAddr.addr[1];// rawEthFrame[2+offset_ETH_PAD_SIZE] = ethAddr.addr[2];// rawEthFrame[3+offset_ETH_PAD_SIZE] = ethAddr.addr[3];// rawEthFrame[4+offset_ETH_PAD_SIZE] = ethAddr.addr[4];// rawEthFrame[5+offset_ETH_PAD_SIZE] = ethAddr.addr[5];// } rawEthFrame[6+offset_ETH_PAD_SIZE] = PhysAddrPtr[0]; rawEthFrame[7+offset_ETH_PAD_SIZE] = PhysAddrPtr[1]; rawEthFrame[8+offset_ETH_PAD_SIZE] = PhysAddrPtr[2]; rawEthFrame[9+offset_ETH_PAD_SIZE] = PhysAddrPtr[3]; rawEthFrame[10+offset_ETH_PAD_SIZE] = PhysAddrPtr[4]; rawEthFrame[11+offset_ETH_PAD_SIZE] = PhysAddrPtr[5]; rawEthFrame[12+offset_ETH_PAD_SIZE] = FrameType >> 8;//Frame Type rawEthFrame[13+offset_ETH_PAD_SIZE] = FrameType & 0xFF;//Frame Type (void)ifx_netif_input(CtrlIdx, FrameType, rawEthFrame, DataPtr, LenByte); }low_level_input适配
static pbuf_t *low_level_input(uint8 CtrlIdx, uint8 * ethHeader, uint8 * DataPtr, uint16 LenByte) { //IfxGeth_Eth *ethernetif = netif->state; pbuf_t *p; u16_t len; len = LenByte + 14; #if ETH_PAD_SIZE len += ETH_PAD_SIZE; /* allow room for Ethernet padding */ #endif /* We allocate a pbuf chain of pbufs from the pool. */ p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL); if (p != NULL) { #if ETH_PAD_SIZE pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */ #endif //u8_t *src = IfxGeth_Eth_getReceiveBuffer(ethernetif, IfxGeth_RxDmaChannel_0); memcpy(p->payload, ethHeader, 14);//把头信息还原出来 memcpy(((uint8 *)(p->payload)) + 14, DataPtr, LenByte); /* We iterate over the pbuf chain until we have read the entire * packet into the pbuf. */ #if ETH_PAD_SIZE pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */ #endif LINK_STATS_INC(link.recv); } else { //TODO: drop packet(); LINK_STATS_INC(link.memerr); LINK_STATS_INC(link.drop); } return p; }这里再注意一点,我把low_level_input的参数改了,以便接收EthIf_RxIndication传来的数据。
static pbuf_t *low_level_input(uint8 CtrlIdx, uint8 * ethHeader, uint8 * DataPtr, uint16 LenByte)这里再注意一点,也是很容易出错的地方,low_level_input中有为了数据对齐做的pad,这里一定要注意,去掉pad字符进行填充,填充完再加上pad字符!!!
发送函数
用MCAL接口Eth_Transmit+Eth_ProvideTxBuffer去实现low_level_output,还是比较简单的。
static sint8 low_level_output(netif_t *netif, pbuf_t *p) { err_enum_t res = ERR_USE; BufReq_ReturnType buffRes; Std_ReturnType transRes; uint32 TmpBuffIdx; uint16 Tmp_Txlenghth1; uint8 *txBuf; uint16 frameType; boolean ifTxConfim = 1; uint8 ethHeader[16]; uint8 desMac[16]; #if ETH_PAD_SIZE pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */ #endif memcpy(ethHeader, p->payload, 16); desMac[0] = ethHeader[0]; desMac[1] = ethHeader[1]; desMac[2] = ethHeader[2]; desMac[3] = ethHeader[3]; desMac[4] = ethHeader[4]; desMac[5] = ethHeader[5]; Tmp_Txlenghth1 = p->len - 14; frameType = (ethHeader[12] << 8) + ethHeader[13]; if(p->tot_len < 2) { return ERR_USE; } if(p->if_idx == 0xFF) { return ERR_USE; } //Eth_ProvideTxBuffer(0, &TmpBuffIdx, &TmpBuffPtr, &Tmp_Txlenghth1); buffRes = Eth_ProvideTxBuffer(0, &TmpBuffIdx, &txBuf, &Tmp_Txlenghth1); if(BUFREQ_OK == buffRes) { memcpy(txBuf, ((uint8 *)p->payload) + 14, p->len-14); //Eth_Transmit(0, TmpBuffIdx, 0x0806u, TxConfirmation, Tmp_Txlenghth1, &MacDestBrodaddress[0]); transRes = Eth_Transmit(0, TmpBuffIdx, frameType, ifTxConfim, p->len - 14, desMac); //EthIf_TxConfirmation( uint8 CtrlIdx, uint32 BufIdx ) if(E_OK == transRes) { //EthIf_TxConfirmation(0, ); } else { return ERR_USE; } } else { return ERR_USE; } #if ETH_PAD_SIZE pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */ #endif LINK_STATS_INC(link.xmit); return ERR_OK;时间戳接口
1. 使用中断,STM中断服务函数去做时间戳。
2.不用中断的话,即使不配置STM也可以用Mcal_DelayGetTick()来获取STM内的计数。
但是叔这回就是倔了!就非得用STM中断来做!
Ifx_Lwip_onTimerTick函数的逻辑几乎不用改变,它会改变timer的值,然后该timer再被Ifx_Lwip_pollTimerFlags周期性调用更新。
好了接下来该看成果了:
OK!收工!
