测试环境:stm32f401rct6、rt-thread版本: v4.1.0、rt-thread studio版本: 2.2.6、网络硬件使用ec800m移植at_socket使用sal框架。
1、移植介绍
ryanmqtt 库希望应用程序为以下接口提供实现:
system 接口
ryanmqtt 需要 rtos 支持,必须实现如下接口才可以保证 mqtt 客户端的正常运行
network 接口
ryanmqtt 依赖于底层传输接口 api,必须实现该接口 api 才能在网络上发送和接收数据包
mqtt 协议要求基础传输层能够提供有序的、可靠的、双向传输(从客户端到服务端 和从服务端到客户端)的字节流
time 接口
ryanmqtt 依靠函数生成毫秒时间戳,用于计算持续时间和超时,内部已经做了数值溢出处理
2、开始移植
得益于rt-thread驱动应用层分离的思想和sal框架,platform/rtthread的适配层可以适应任何rt-thread代码,所以我们就不拿rt-thread来移植了。
使用freertos内核来移植,使用cmsis-rtos v2兼容层。
system 接口
系统接口,需要移植rtos的接口。为方便管理类型使用平台结构体,修改platformsystem.h里面的结构体
就是线程和互斥锁
typedef struct{ osthreadid_t thread;} platformthread_t;typedef struct{ osmutexid_t mutex;} platformmutex_t;再来实现platformsystem.c里面的函数定义
注意里面的 platformprint 函数,由于freertos没有官方的打印接口。记得修改为你的打印接口
#include platformsystem.h
/**
@brief 申请内存@param size@return void*
/
void platformmemorymalloc(size_t size)
{
return pvportmalloc(size);
}
/@brief 释放内存@param ptr
/
void platformmemoryfree(void ptr)
{
vportfree(ptr);
}
/@brief ms延时@param ms
/
void platformdelay(uint32_t ms)
{
osdelay(ms);
}
/ *@brief 打印字符串函数,可通过串口打印出去@param str@param strlen
/
void platformprint(char str, uint16_t strlen)
{
}
/@brief 初始化并运行线程@param userdata@param platformthread@param name@param entry@param param@param stacksize@param priority@return ryanmqtterror_e
*/
ryanmqtterror_e platformthreadinit(void *userdata,
platformthread_t *platformthread,
const char *name,
void (*entry)(void ),
void const param,
uint32_t stacksize,
uint32_t priority)
{
const osthreadattr_t mytask02_attributes = {
.name = name,
.stack_size = stacksize,
.priority = (ospriority_t)priority,
};
platformthread->thread = osthreadnew(entry, param, &mytask02_attributes);
if (null == platformthread->thread)
return ryanmqttnorescourceerror;
return ryanmqttsuccesserror;
}
/@brief 销毁自身线程@param userdata@param platformthread@return ryanmqtterror_e
*/
ryanmqtterror_e platformthreaddestroy(void userdata, platformthread_t platformthread)
{
osthreadterminate(platformthread->thread);
return ryanmqttsuccesserror;
}
/@brief 开启线程@param userdata@param platformthread@return ryanmqtterror_e
*/
ryanmqtterror_e platformthreadstart(void userdata, platformthread_t platformthread)
{
osthreadresume(platformthread->thread);
return ryanmqttsuccesserror;
}
/@brief 挂起线程@param userdata@param platformthread@return ryanmqtterror_e
*/
ryanmqtterror_e platformthreadstop(void userdata, platformthread_t platformthread)
{
osthreadsuspend(platformthread->thread);
return ryanmqttsuccesserror;
}
/@brief 互斥锁初始化@param userdata@param platformmutex@return ryanmqtterror_e
*/
ryanmqtterror_e platformmutexinit(void userdata, platformmutex_t platformmutex)
{
const osmutexattr_t mymutex01_attributes = {
.name = mqttmutex};
platformmutex->mutex = osmutexnew(&mymutex01_attributes);
return ryanmqttsuccesserror;
}
/@brief 销毁互斥锁@param userdata@param platformmutex@return ryanmqtterror_e
*/
ryanmqtterror_e platformmutexdestroy(void userdata, platformmutex_t platformmutex)
{
osmutexdelete(platformmutex->mutex);
return ryanmqttsuccesserror;
}
/@brief 阻塞获取互斥锁@param userdata@param platformmutex@return ryanmqtterror_e
*/
ryanmqtterror_e platformmutexlock(void userdata, platformmutex_t platformmutex)
{
osmutexacquire(platformmutex->mutex, oswaitforever);
return ryanmqttsuccesserror;
}
/@brief 释放互斥锁@param userdata@param platformmutex@return ryanmqtterror_e
*/
ryanmqtterror_e platformmutexunlock(void userdata, platformmutex_t platformmutex)
{
osmutexrelease(platformmutex->mutex);
return ryanmqttsuccesserror;
}
/@brief 进入临界区 / 关中断/
void platformcriticalenter(void)
{
oskernellock();
}
/ *
@brief 退出临界区 / 开中断*/
void platformcriticalexit(void)
{
oskernelunlock();
}
time 接口
time接口,只需要提供一个ms时间戳就行,直接修改函数,这里使用freertos的心跳。
uint32_t platformuptimems(void)
{
if (1000 == oskernelgettickfreq())
return (uint32_t)oskernelgettickcount();
else
{
uint32_t tick = 0;
tick = oskernelgettickcount() * 1000;
return (uint32_t)((tick + oskernelgettickcount() - 1) / oskernelgettickcount());
}
}
network 接口
mqtt 协议要求基础传输层能够提供有序的、可靠的、双向传输(从客户端到服务端 和从服务端到客户端)的字节流
由于freertos没有规定标准的网络层,你可以选择 freertos-plus-tcp / freertos-cellular-interface/ lwip / w5500等网络方法,几乎rt-thread支持的你也可以在freertos仓库找到。
这里以lwip为例,使用socket接口来实现,网络阻塞发送和接收使用 so_sndtimeo 和 so_rcvtimeo 来实现,你也可以选择select / poll / epoll等方式。
修改 platformnetwork_t 结构体以支持 socket
typedef struct{ int socket;} platformnetwork_t;接着实现platformnetwork.c里面的函数
#define rlogenable 1 // 是否使能日志
#define rlogcolorenable 1 // 是否使能日志颜色
#define rloglevel (rloglvlwarning) // 日志打印等级
#define rlogtag ryanmqttnet // 日志tag
#include platformnetwork.h
#include ryanmqttlog.h
/**
@brief 连接mqtt服务器@param userdata@param platformnetwork@param host@param port@return ryanmqtterror_e成功返回ryanmqttsuccesserror, 失败返回错误信息
*/
ryanmqtterror_e platformnetworkconnect(void *userdata, platformnetwork_t *platformnetwork, const char *host, const char port)
{
ryanmqtterror_e result = ryanmqttsuccesserror;
struct addrinfo addrlist = null;
struct addrinfo hints = {
.ai_family = af_unspec,
.ai_socktype = sock_stream,
.ai_protocol = ipproto_tcp};
if (getaddrinfo(host, port, &hints, &addrlist) != 0)
{
result = ryansocketfailederror;
goto exit;
}
platformnetwork->socket = socket(addrlist->ai_family, addrlist->ai_socktype, addrlist->ai_protocol);
if (platformnetwork->socket socket, addrlist->ai_addr, addrlist->ai_addrlen) != 0)
{
platformnetworkclose(userdata, platformnetwork);
result = ryanmqttsocketconnectfailerror;
goto exit;
}
exit:
if (null != addrlist)
freeaddrinfo(addrlist);
return result;
}
/@brief 非阻塞接收数据@param userdata@param platformnetwork@param recvbuf@param recvlen@param timeout@return ryanmqtterror_esocket错误返回 ryansocketfailederror接收超时或者接收数据长度不等于期待数据接受长度 ryanmqttrecvpackettimeouterror接收成功 ryanmqttsuccesserror
*/
ryanmqtterror_e platformnetworkrecvasync(void *userdata, platformnetwork_t *platformnetwork, char recvbuf, int recvlen, int timeout)
{
int32_t recvresult = 0;
int32_t offset = 0;
int32_t timeout2 = timeout;
struct timeval tv = {0};
platformtimer_t timer = {0};
if (-1 == platformnetwork->socket)
return ryansocketfailederror;
platformtimercutdown(&timer, timeout);
while ((offset < recvlen) && (0 != timeout2))
{
tv.tv_sec = timeout2 / 1000;
tv.tv_usec = timeout2 % 1000 * 1000;
if (tv.tv_sec socket, recvbuf + offset, recvlen - offset, 0);
if (recvresult socket)
return ryansocketfailederror;
platformtimercutdown(&timer, timeout);
while ((offset < sendlen) && (0 != timeout2))
{
tv.tv_sec = timeout2 / 1000;
tv.tv_usec = timeout2 % 1000 * 1000;
if (tv.tv_sec socket, sendbuf + offset, sendlen - offset, 0);
if (sendresult socket >= 0)
{
closesocket(platformnetwork->socket);
platformnetwork->socket = -1;
}
return ryanmqttsuccesserror;
}
3、总结
可以看到,ryanmqtt移植非常简单,有专门的platform层用来移植。
如何去实现一个输入滤波器的简单设计呢
巴航工业的E195-E2喷气商用飞机将再次在中国演示飞行
AR500C高原型无人直升机在江西成功首飞
台积电在先进制程上一直领跑业界,奠定了Foundry一哥地位
区块链可以改变汽车行业使其实现真正的业务价值
RyanMqtt移植指南
窥视美国2017年物联网和VR发展现状
大直径测径仪在橡胶阀座生产中起着重要作用
关于IEEE 802.15.4的CC2530无线数据收发设计
摩托罗拉放弃NVIDIA 投入高通怀抱?
台积电在海外的首座封测厂!
京东方和华星光电正在积极调整65英寸和75英寸电视供应链
山西省发布了关于免费开放公共资源支持5G基站建设的通知
电子墨水屏标签:低功耗处理器技术
禾多科技亮相2021华为智能汽车解决方案生态论坛
《电动汽车安全要求》3项强制性国家标准名称、主要内容等一览表
直击工业4.0的智能工厂解决方案展
小巧方便的非接触式验电器
智慧管廊:管廊安全监测系统、综合管廊环境实时在线监测系统
iPhone8今晚在美国发布,二手的苹果7拍出天价比iPhone8还要贵,欲意何为?