recan
recan
一个专注于嵌入式软件架构设计的新生代农名工

注册于 4 months ago

回答
113
文章
0
关注者
0

看你的使用scons编译报错,
undefined reference to `xxx'
大概率是这个xxx函数的实现库你没有加入链接列表。
先确认下这个xxx函数在哪里实现的,是你的源码还是库里面实现的。

有一点需要明确的是,网络通讯是全双工的,也就是说收和发是互不影响的;
所以如果你把收发放在同一个线程来做,除非客户端和服务端的交互就是一来一回的;
这样没有问题,假如有服务端是不是主动下发一个消息给你,或者客户端需要是不是上报一个类似心跳的数据包给服务器的话;
那么将收发放在一个线程的最大弊端就是响应的实时性问题,这是不得不考虑的问题。
我个人在项目中,是使用收发线程分离的,即一个专门的收线程,这个线程不干别的,就仅负责接收服务器的消息,并做应用协议层的基本解析,比如去掉包头、包尾,校验这些,之后把校验ok的数据包写到一个消息队列(接收消息队列),通过消息队列传递到你真正需要使用这段应用数据的线程,这个线程就只从消息队列取数据;
网络发送线程也类似的思路,它只负责从消息队列(发送消息队列)中取数据包,然后组包加校验,发送到服务器;
而真正需要发送应用数据的线程,只需要把自己的数据包发动到消息队列(发送消息队列)就行,其他事它不关心。
整个模型图类似如下所示:
image.png
这个模式,最大的好处就是解耦,各司其职,互不干扰,易于扩展。
以上答案提供点思路,仅供参考。

很有可能是tcp_client任务的栈开得小了?
栈溢出导致内存被破坏?

最核心的原理就是把一个个命令处理当作一个object,这个object包含最关键的名字和执行的函数地址,然后把所有的object定义在一个指定段名称的段里面,这是在程序链接阶段处理的。
使用的时候从这个段里面以object的大小做变量,取出一个object,比较,匹配的话就执行。
指定段名称,每种编译器的写法不太一样,比如:

ifdef _MSC_VER

define MSH_FUNCTION_EXPORT_CMD(name, cmd, desc) \

            const char __fsym_##cmd##_name[] = #cmd;            \
            const char __fsym_##cmd##_desc[] = #desc;           \
            __declspec(allocate("FSymTab$f"))                   \
            const struct finsh_syscall __fsym_##cmd =           \
            {                           \
                __fsym_##cmd##_name,    \
                __fsym_##cmd##_desc,    \
                (syscall_func)&name     \
            };

pragma comment(linker, "/merge:FSymTab=mytext")

elif defined(__TI_COMPILER_VERSION__)

define MSH_FUNCTION_EXPORT_CMD(name, cmd, desc) \

            __TI_FINSH_EXPORT_FUNCTION(__fsym_##cmd);           \
            const char __fsym_##cmd##_name[] = #cmd;            \
            const char __fsym_##cmd##_desc[] = #desc;           \
            const struct finsh_syscall __fsym_##cmd =           \
            {                           \
                __fsym_##cmd##_name,    \
                __fsym_##cmd##_desc,    \
                (syscall_func)&name     \
            };

else

define MSH_FUNCTION_EXPORT_CMD(name, cmd, desc) \

            const char __fsym_##cmd##_name[] RT_SECTION(".rodata.name") = #cmd;    \
            const char __fsym_##cmd##_desc[] RT_SECTION(".rodata.name") = #desc;   \
            RT_USED const struct finsh_syscall __fsym_##cmd RT_SECTION("FSymTab")= \
            {                           \
                __fsym_##cmd##_name,    \
                __fsym_##cmd##_desc,    \
                (syscall_func)&name     \
            };

endif

分别对应微软的编译环境、TI的编译器、GCC等其他编译器。

卖NB模组比较好的国内就那么几家:
移远、有方、广和通、移柯、中兴微、华为,
首推移远的BC26,这块模块,我以前调试过,
用的基带芯片是MTK的MT2625,是国内做NB比较好,性价比比较高的芯片,
很多模组厂家都是拿这块芯片做封装,做自己的模组来卖;
其中又算移远的最好,毕竟大厂可靠。
楼上说的opencpu方式,移远的BC26也有:标准版(固件是出厂内置的不能二次开发)和open版本(支持二次开发)。
前一份工作,我的前东家就是跟这些模组厂商深度合作的(我们的软件方案需要内嵌到他们的固件中),所以这里罗列的模组厂商,几乎都有合作过,其中合作度最最深入的就是两块NB芯片(证明市场认可度高):
MTK的MT2625和高通的MDM9206/MDM9206,
对应移远的模组是BC26(2G和NB)和BG95(2G/CAT1/NB)。
以上信息可以参考下,希望能够帮助到你,也欢迎来交流模组相关的技术。

可以尝试将make.exe文件的路径配置在PATH环境变量中,配置环境变量的方法,
参见:Windows配置PATH环境变量
配置成功后,重新开一个cmd命令行,直接输入make;
如果没有报找不到make之类的错误,证明配置成功了,
接下来再进行你如上的操作。
以上答案希望能够帮助到你。

如图:
image.png
以stm32f072-st-nucleo这个bsp为例,
配置链接脚本是在rtconfig.py中,而链接文件是位于board/linker_scripts/link.lds
这个有个需要特别注意的就是:相对路径的问题。
猜测你的问题可能是相对路径没有配置对,或者是链接脚本文件的名称不对,
检查下看看,希望能帮助到你。

邮箱可以理解成值传递。
为什么这样说?
如果是这样定义:
struct msg
{

rt_uint8_t* data_arr;
rt_uint32_t data_len;

};
那么你通过邮箱发送的消息体的真实有效数据是data_arr指针指向的数据,试想下,如果这个data_arr指针都没有指向空间(无论是全局的数据空间还是malloc出来的堆空间),自然接收方去访问这个data_arr的时候很大概率是出异常的。
与此形成对比的是,如果消息体这么定义:
struct msg
{

rt_uint8_t data_arr[128];
rt_uint32_t data_len;

};
这样的话,就不需要额外去申请空间了,因为这个msg中的data_arr本身就带了128字节的数组,它就可以用来存放真实的消息体数据。
那为何要定义成指针而不是数组呢?
原因是在邮箱传递消息时,数据长度是sizeof(struct msg),这样的话,使用数组定义比使用指针定义,size就大很大了,而且一定是数组定义,这个data_arr是定长的,不能超过定义的最大长度,使用上不够灵活。
以上答案,希望能解决你的困惑。

mac下面应该是跟linux一样的,需要安装scons;
如果要进行menuconfig,应该是scons --menuconfig;
如下图所示:
image.png
image.png
另外,调试qemu,可以使用这个脚image.png

以上答案,希望能够帮助到你。

我猜测这段JSON数据肯定是已经收到了的,只不过打印函数在处理 中文编码的时候,无法正常打印。
这个时候,你需要用打印内存字节的方式,一个个字节打印出来。
推荐一个封装函数,使用hexdump的方式打印字节数组(也就是你收到的json字符串)
可以参考下hexdump打印接口
为此我还特意试下一下打印中文字符串,分别用printf和hexdump打印,区别如下:
image.png

image.png

以上答案,希望能够帮助到你。

好文,先收藏了。
恰好最近刚好要把一个RISC-V架构的处理器移植要RTT上面,
看了楼主的文章,感觉思路又清晰了些,
后续有空再请教请教楼主。

先说下这个错误
../applications/main.c:54:15: warning: passing argument 1 of 'uart1_txd' from incompatible pointer type [-Wincompatible-pointer-types]

 uart1_txd(PREES2); 

核心含义就是,你传入的第1个参数与uart1_txd函数需要的参数不匹配;
为何不匹配:
你定义的 char *a[], 而你传入的是 char a[]。
再说一点,这个接口原型
int uart1_txd(char *uart1_trans[])
设计得不合理。
看你的实现,你就是想通过uart1发送一段buffer出去。
比较建议的函数原型:
int uart1_txd(char *uart1_trans, int len);
然后你调用的时候,就是
uart1_txd(PREES2, sizeof(PREES2));
接口设计成这样,第一个参数实则是一个指针,也可以理解成一个数组的起始地址,第二个参数指定这个指针指向内存的字节数,这个字节数由调用者绝对;而你现有的代码,这个长度8写在uart1_txd里面是不合适的。
一般设计带buffer处理的接口的传参,都是 char *p, int len这种写法,可以参考C库的一个接口你就明白了:
ssize_t write(int fd, const void *buf, size_t count);
image.png
以上答案,提供点思路,希望能够帮助到你。

跟进补充:
自己摸索了下,发现
手动在rtconfig.h中加上这个宏定义

#define HAVE_SYS_SELECT_H 1

就能编译过了。
感谢关注,结帖。

c文件要想调用到c++的接口,建议按照以下步骤进行:
1.编写你的c++类,做好代码实现,比如命名为a.cpp;
2.建议一个wrapper的c++文件,把c++类的方法转换成c接口;比如命名为wrapper.cpp;
注意wrapper里面的封装代码,要使用

ifdef __cplusplus

extern "C" {

endif

//wrapper source code

ifdef __cplusplus

}

endif

包起来,这样编译的时候才会按照c的接口形式编译;
同时导出wrapper的c接口到头文件中,也使用上面这个包起来。
3.建立你的c调用文件,调用wrapper导出的c接口;比如命名为test.c;
4.开始编译:使用g++编译a.cpp和wrapper.cpp,使用gcc编译test.c;
然后链接成可执行文件,同时包含c++的库。
这样就可以完成c调用c++了。
你可以试试看,希望能帮助到你。

下次如果再碰到类似
undefined reference to `xxx'
错误时,排查思路如下;
1.明确下,这个错误的根本原因是找不到xxx函数的实现;
2.查找下这个xxx函数是在哪个C文件定义实现的,如果有源码的,确保这个C文件参与了编译,最终有被链接;
3.如果这个xxx函数是在库里面实现,那么需要确认这个实现xxx函数的库文件,是否有被加入到链接列表里面。
注意一点:这个错误跟包不包含头文件没有直接的关系;处理头文件是在预编译阶段完成的,而这个错误是在链接阶段出现的。
希望答案,希望对你有帮助,谢谢。

回到
顶部

发布
问题

投诉
建议