前言
- 核验基本流程,了解freeRTOS控制多通道外设的基本方法和策略。
例程实现
xilinx有freertos的例程,可以先了解例程的功能。下载程序,看看程序运行结果,在此基础上,测试自己编写的代码。
直接run as例程,但是端口没有任何输出,意识到没有初始化任何端口。于是,按照初始化流程,添加UART程序等。
添加串口,程度SDK Terminal没有任何反应。于是,重新下载前期的LED闪烁程序,选择野火调试助手,能够正常接收字符。这意味着,xilinx的串口工具存在一些问题,选择野火调试工具更加可靠。
例程功能是,设计两个任务,分别是每秒向一个队列中发送相同字符的TxTask,以及打印队列字符的RxTask。给整个任务设定了一个10s计时器,计时到会触发vTimerCallback函数。在此函数中,满足一定条件会打印信息以及删除两个任务。
避免深究freertos背景以及stm32移植内容。当看到文档中包含amazon标记时,好奇此公司与freertos的关系。
添加新逻辑
例程的逻辑清晰,可以按照基本逻辑添加GPIO与Uart等。
注重基本功能的实现,即在目标功能实现基础上理解函数逻辑和流程等。比如在考虑如何设计一个测试功能时,看到xQueueCreate函数,奇怪如何创建一个内存区域以及如何调用。但是,在理解函数时,却完全无法理解并陷入其中。
考虑在gpioTask中添加sleep延时逻辑,但是考虑到整个程序的执行逻辑不明,因此似乎有必要了解RTOS的延时机制。此帖提醒,延时机制与任务挂起之间的关系。
添加的任务逻辑如下。野火调试助手显示,确实其它两个任务关闭后,此任务仍然进行中。
static void gpioTask( void *pvParameters )
{
const TickType_t x1second = pdMS_TO_TICKS( DELAY_1_SECOND );
/* Delay for 1 second. */
for(;;)
{
print("gpio task\r\n");
vTaskDelay( x1second );
}
}
问题是,在实现了基本逻辑功能后,就没有任何念头:关于实现任何具体的目标功能。这需要重新回顾,前期选择freeRTOS系统的主要目的:比如多个CAN外设同时接收和发送CAN帧。
目标、预期和功能,这些才是首要的。理解各种模型框架,需要建立在功能设计和解决问题基础上。否则,就会完全陷入到各种概念或者对象中。
can口逻辑
实现功能,同时让八个CAN口发送CAN帧。
这个功能有什么意义了?如果只是单纯实现特定功能,确实只会让自己失去方向,纠结于各种技术细节。
设计的基本逻辑是,xilinx已经搭建好freertos系统,只要按照函数设计每个task即可。但是,按照多线程逻辑重新实现简单功能,又没有特别重要的意义。此时,或许可以考虑再次实现rolf_ernst论文基本策略。
基本预期是,能够实现QT、嵌入式、裸机、PCB等所有方面功能,但是是否能够实现既定的意义,不清楚。
以太网功能实现
实现基本的以太网帧收发,实现以太网帧转发CAN帧功能,在此基本上测试转换的时间是多少。
想要实现qt的帧收发,但是目前没有太大意义。
这个到是可以理解,逐步提升CAN带宽,并测量延时。
每条通路的带宽,周期数据特性不同,采用什么缓存方案?有种偏向,觉得实验是可以做得,有种跃跃欲试。
基本参考,数据发送lwip
参考例程,小梅哥lwip资料,实现最简单的收发。感知什么基本内容?忽略协议理论等内容。
先找好参考资料,比如按照米联客例程,实现特定功能的以太网收发,熟悉基本流程。
实现基本的lwip echo。感觉不错,但是感觉过于强烈,没有感知过程。
忽略lwip-uart的协议转换逻辑,聚焦uart是如何从lwip拿到以太网帧数据的。基于这个基本逻辑,思考如何实现以太网和CAN帧之间的转化。
尝试理解lwip-uart的所有逻辑和实现,是没有意义的。
如何拿到以太网帧数据?echo.c文件中包含recv_callback函数,参数中pbuf P中,p->paylaod就是获得的以太网帧数据。
err_r recv_callback(void *arg, struct tcp_pcb *tpcb,
struct pbuf *p, err_t err)
{
/* do not read the packet if we are not in ESTABLISHED state */
if (!p) {
tcp_close(tpcb);
tcp_recv(tpcb, NULL);
print("error recv_callback\r\n");
return ERR_OK;
}
//
// /* indicate that the packet has been received */
// tcp_recved(tpcb, p->len);//这里接收内容
//
// /* echo back the payload */
// /* in this case, we assume that the payload is < TCP_SND_BUF */
// if (tcp_sndbuf(tpcb) > p->len) {
// err = tcp_write(tpcb, p->payload, p->len, 1);
// } else
// xil_printf("no space in tcp_sndbuf\n\r");
//获取pbuf内容,并直接打印
char *original_payload = (char *)p->payload;
xil_printf("uart 通信测试:%s\r\n",original_payload);
/* free the received pbuf */
pbuf_free(p);
return ERR_OK;
}
在这个函数理解基础上,回顾echo例程的整体逻辑,尝试实现以太网和CAN之间的协议转换。右键函数open call hierarchy,可以获得此函数的调用关系。
A[main]-->B[start_application]
B[start_application]-->C[tcp_accept(pcb,accept_callbalk)]
C[tcp_accept(pcb,accept_callbalk)]-->D[accept_callbalk]
D[accept_callbalk]-->E[tcp_recv(newpcb,recv_callback)]
echo文件中,主要实现两个回调函数,分别指向结构体tcp_pcb与结构体tcp_pcb_listen中的回调函数recv_callback与accept_callback。
这意味着,lwip中回调函数是功能实现的一个重要逻辑。
存在两个问题:
- 为什么这个函数调用关系不在中断或者while逻辑中,仍然可以一直工作
- 哪些关键逻辑才是重要,从而自己写程序时,能够精简内容。
由于不理解函数调用机制,以及函数流最终停留在哪里,有必要通过debug理解其中的逻辑。但是,debug as出现无法工作。尝试使用printf打印定位函数流。
通过增加断点,发现奇怪的地方。首先,程序是一直处于main循环中。但是程序问题总是可以在助手发送以太网帧时,可以激活recv_callback函数。问题是,start__application触发的这个函数,其它地方没有才是。结果是,while函数激活xemacif_input(echo_netif),但是函数里面的if和ifdef会编译错误。实际函数进入了阴影部分。
不忽略每个流程细节。后来发现,通过函数中小逻辑,将用户定义的结构体与lwip定义的全局结构体绑定。不过,还不知道,lwip是如何周期性得获取MAC FIFO或者DMA中的数据。
目前面临的问题:
- 函数流到底在哪里?
- 结构体中的pbuf与netif的结构是如何建立联系的?之所有强调全局结构体,是因为自己编写函数时,一般是围绕此结构体展开。
pcb.c文件中包含全局结构体,问题是什么时候将两者绑定,以及什么时候触发这个逻辑的?
主体逻辑是,通过main函数中的结构体与库文件中的结构体建立联系。关键是接口如何与pcb的结构体建立联系的?结果是,tcp_fast或者tcp_slow函数不影响数据接收。
考虑使用屏蔽特定语句的方式,判断那些函数影响以太网帧接收。
仍然分不清,到底这个数据从哪里来的,为什么直接就可以获取相关的数据?
明确两个问题:
-
以太网数据是如何通过**low_level_input(struct netif netif)**拿到?
明明没有参与寄存器相关的操作 -
获取的数据又是怎样通过callback函数进入上层?最后触发start_application中配置的函数?
-
回调函数重要,明确三个回调函数,可以说明主要函数触发逻辑
重新梳理说明,了解当前程序分析的目的,避免解释概念。
直接应用lwip,注重初始化以及callback函数的应用,方便搭建基本功能逻辑。
分以下几个步骤:
- 注释核心逻辑;考虑直接在原工程上添加CAN逻辑。
- 建立新工程,实现串口接收
- 在串口工程中添加以下以太网逻辑。
结果是,某个不起眼的define 定义函数。