-
华为云IoT华为云IoT是面向运营商和企业/行业领域的统一开放云平台,支持SIM和非SIM场景的各种联接和联接管理,是华为公司面向IoT解决方案的关键平台部件。主要分为连接管理平台,设备管理平台和应用使能平台等多个部分,负责联通端侧设备与北向应用的核心产品。可以支撑千万级的海量物联网连接。RESTful Web APIREST 指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 RESTful。目前华为云IoT北向应用提供的就是这样一套规范性API。LwM2M轻量化机器对机器协议CoAP轻量级受限应用层协议MQTT消息队列遥测传输DTLS数据传输安全协议FOTA固件远程升级UART通用异步收发器(异步串行通信口)
-
物联网顾名思义,物联网就是物物相连的互联网。NB-IoT/LPWANB-IoT是IoT领域一个新兴的技术,支持低功耗设备在广域网的蜂窝数据连接,也被叫作低功耗广域网LPWAN,该项技术是目前华为公司最重要的物联网技术之一。LiteOS SDKLiteOS SDK是Huawei LiteOS软件开发工具包(Software Development Kit),包括端云互通组件、FOTA、JS引擎、传感框架等内容。南向设备用于采集数据的嵌入式设备,比如STM32开发板,或者温湿度采集的传感器等。北向应用用于查看IoT云平台上南向设备上报数据或者给南向设备下发控制命令的手机APP或者PC端的应用程序。设备profile用于描述南向设备具有的能力以及上报的数据的格式的一组json格式文件,这些文件需要上传到IoT云平台上。编解码插件用于将南向设备上报的私有数据解析成设备profile描述的并且IoT云平台能够识别和存储的数据,以及将北向应用下发的命令编码成南向设备能够识别的数据格式的一组函数组成的jar文件。简而言之就是南向设备和云平台之间的一个数据转换的程序。AT指令AT指令集是从终端设备(Terminal Equipment,TE)或数据终端设备(Data Terminal Equipment,DTE)向终端适配器(Terminal Adapter,TA)或数据电路终端设备(Data Circuit Terminal Equipment,DCE)发送的。本文通过AT指令操作WIFI或者GSM模组。端云互通组件端云互通组件是华为物联网解决方案中,资源受限终端对接到 IoT云平台的重要组件。
-
Huawei LiteOS 自开源社区发布以来,围绕 NB-IoT 物联网市场从技术、生态、解决方案、商用支持等多维度使能合作伙伴,构建开源的物联网生态,目前已经聚合了 30+ MCU 和解决方案合作伙伴,共同推出一批开源开发套件和行业解决方案,帮助众多行业客户快速的推出物联网终端和服务,客户涵盖抄表、停车、路灯、环保、共享单车、物流等众多行业,为开发者提供 “一站式” 完整软件平台,有效降低开发门槛、缩短开发周期。
-
纵观五花八门的物联网操作系统,我们可以看到它们可以分为三大主要流派。第一流派是由传统的嵌入式实时操作系统发展而来,如FreeRTOS、RT-Thread、Huawei LiteOS等,它们在物联网终端上已经获得广泛支持,硬件推广成本低,但是其软件开发专业度极高,软硬件开发难以隔离。第二流派是由其他领域的软件平台移植而来,如Android Things、Windows 10 IoT等,它们软件资源丰富,开发者众多,软件开发快成本低,但是适配物联网终端硬件有难度,需要解决资源占用、外设接口扩展、低能耗设计等问题。第三流派是由互联网公司的云平台延伸而来,如AWS IoT、AliOS、TencentOS tiny等,它们天生与互联网服务结合密切,方便对接互联网应用,但是基本上是各家产品对各家服务,难以做到平台中立,对软件开发者来说功能性受到限制。随着云计算的普及,这几种流派之间的界限逐步淡化。如华为给Huawei LiteOS提供了完整的云平台支持,让开发者可以轻松自如的开发物联网应用。Huawei LiteOS Kernel的优势高实时性,高稳定性。超小内核,基础内核体积可以裁剪至不到10K。低功耗。支持功能静态裁剪。
-
首先要知道的是,这个是一个轻量级的操作系统,或者说是专门为物联网设计的轻量级操作系统。在,在 2015 华为网络大会上,也就是2015 年 5 月 20 日发布的该系统—— 物联网操作系统 LiteOS。该系统遵循SBSD-3开源许可协议:现今存在的开源协议很多,而经过Open Source Initiative 组织通过批准的开源协议目前有60多种(http://www.opensource.org/licenses/alphabetical )。我们在常见的开源协议如BSD, GPL, LGPL,MIT等都是OSI批准的协议。华为LiteOS可广泛应用于智能家居、个人穿戴、车联网、城市公共服务、制造业等领域,大幅降低设备布置及维护成本,有效降低开发门槛、缩短开发周期。
-
摘要:为了能和大家说清楚LiteOS的硬件接管机制,不得不在这里谈一谈STM32上电后的启动机制。基础知识大家应该注意到stm32开发板上都是几个跳线帽或者拨码开关,用于切换BOOT0和BOOT1的管脚电平,其目的是为了选择启动模式:MODEBOOT0BOOT1FLASH0XSRAM11ISP10这里的0是指将对应管脚和GND连接,1是指将对应管脚和VCC连接,X是指任意状态都可以,默认情况下我们选择从Flash(主闪存存储器)启动。STM32大部分的Flash地址都是由0x0800_0000启动,在启动是0x0800_0000这个地址会被映射到0x0000_0000地址处,相当于我们访问0x0000_0000地址处也就是访问0x0800_0000为什么要说这个启动地址的问题?因为在编译程序时,把“中断向量表”链接到0x0800_0000开始的位置,地址0x0800_0000处存放栈顶地址让CPU读取,地址0x0800_0004处存放第一个中断的处理函数为“Reset”中断,也就是复位中断,单片机上电或者进行了复位操作就会跳转到该中断向量处指令中断处理函数,在Reset_Handler处理函数中设置了堆栈地址等一些初始化操作,最后通过汇编语句跳转至main函数开始执行用户代码。 下面这张表就做“中断向量表”(这里我只截取了一部分),这是由ARM硬件设计时规定的,发生xx中断就跳到某个地址,我们可以在相应地址处写好对应中断的处理函数,当发生该中断就调用其对应的处理函数来处理。 LiteOS接管硬中断的原理 本质上就是将一个中断处理函数(osInterrupt)写到我们使能的中断在向量表中地址对应的内存空间处,举个例子:无论是发生串口中断还是EXTI0中断都先调用统一的osInterrupt函数来处理,在osInterrupt函数内部判断是什么中断、应该如何处理,这就是硬中断接管的本质。
-
liteos 介绍中的JavaScript 支持什么时候能支持, 按照官方 宣传,是可以支持js虚拟机的。都几年过去了,还没有进度?
-
摘要:这里所说的LiteOS是指LiteOS_Lab仓库中的OS组件中的LiteOS,它是操作系统组件中的一个操作系统,是由华为自研的操作系统,并遵循BSD 3-Clause开源协议。Huawei LiteOS是华为面向物联网领域开发的一个基于实时内核的轻量级操作系统,主要功能包括:低功耗框架:LiteOS是轻量级的物联网操作系统,最小内核尺寸仅为6KB,具备快速启动、低功耗等优势,Tickless机制显著降低传感器数据采集功耗。OpenCPU架构:专为LiteOS小内核架构设计,满足硬件资源受限需求,比如LPWA场景下的水表、气表、车检器等,通过MCU和通信模组二合一的OpenCPU架构,显著降低终端体积和终端成本。安全性设计:构建低功耗安全传输机制,支持双向认证、FOTA固件差分升级,DTLS、DTLS+等,构建低功耗安全传输机制端云互通组件:LiteOS SDK端云互通组件是终端对接到IoT云平台的重要组件,集成了 LwM2M、CoAP、MQTT、mbed TLS、LwIP等全套IoT互联互通协议栈,大大减少开发周期,快速入云。SOTA远程升级:通过差分方式降低升级包的尺寸,更能适应低带宽网络环境和电池供电环境,经过特别优化差分合并算法,对RAM资源要求更少,满足海量低资源终端的升级诉求。 本贴属于一个连载系列的汇总贴,随着时间的推移,我将会和大家一起探索Huawei LiteOS中的详细构造,例如硬中断接管机制、任务调度机制等,并将我的见解以帖子的形式发出,并在这个汇总帖下分类汇总。0x01 LiteOS内核详解--stm32启动流程(上)0x02 LiteOS内核详解--浅谈程序编译0x03 LiteOS内核详解--stm32启动流程(下)0x04 LiteOS内核详解--一个elf文件中有什么?0x05 LiteOS内核详解--如何知道LiteOS中调用了哪些文件?0x06 LiteOS内核详解--中断接管机制(上)0x07 LiteOS内核详解--中断接管机制(下)
-
摘要:由于前面几篇文章都属于源码级解析,本篇文章带领大家实践使用整个AT框架,将前面的知识串联在一起讲解,让大家能真正将AT框架运用到实际开发当中。 初始化:我们的初始化流程和上图一致,由下到上进行初始化。1. 在Uart.c文件实现将uart注册进driver层中的函数并调用宏定义等待初始化driver层时自动注册: 2. 调用driver层中的初始化函数,初始化driver层并自动注册之前用OSDRIV_EXPORT注册的设备。现在at层就可以使用driver.h中提供的设备初始化、读、写等函数了 3. 调用at层中的初始化函数,该初始化中创建了一个用于接收数据的任务,并在任务的一开始就调用driver层中的设备初始化函数对串口设备进行了初始化,然后循环检测串口设备中是否有数据,并判断这些数据是否是我们需要的。4. 调用at_oobregister将我们关心的关键词和其对应的处理函数注册进at框架,只要at框架中的接收任务发现串口设备中出现了该关键词,就会自动调用相应的处理函数。当nbiot收到CDP服务器(BC28模块+LWM2M协议)下发的数据就会给单片机发送一条以“\r\n+NNMI:“字符串开头,数据为+NNMI:x,str的异步消息,告诉单片机:“我收到了一条服务器下发的数据,第x号网络接口,内容为str字符串,你准备来解析吧。”at框架中的接收任务就会自动调用刚才我们注册好的”boudica150_rcvdel函数进行解析。” 我做了一个实验,通过华为设备接入服务中的“在线调试”,下发一个数据为10进制的4660,也就是16进制的0x1234,实验结果如下图所示:这是在线调试下发命令的截图:这是BC28收到数据的截图:使用AT框架中提供的函数来收发AT指令:PS:到这里AT框架就算讲解完毕了,大家有啥问题欢迎留言,希望大家能享受组件化编程带来的乐趣!
-
AT框架关闭打印调试信息函数该函数用于关闭AT打印调试信息,如果关闭了AT框架在收到数据时就不会进行相应的打印了。1. int at_debugclose(void) 2. { 3. //for the debug 4. g_at_cb.rxdebugmode = en_at_debug_none; //本质上就是修改了结构体中的成员变量5. g_at_cb.txdebugmode = en_at_debug_none; 6. 7. return 0; 8. } 发送AT指令函数 调函该函数可以将长度为cmdlen的cmd字符串指针指向的指令发送出去,并查看返回信息中是否含有index字符串指针指向的关键字,如果需要处理返回信息还可以传入长度为respbuflen的内存地址指针respbuf来接收返回信息,等待超时时间为timeout。1. int at_command(const void *cmd,size_t cmdlen,const char *index,void *respbuf,\ 2. size_t respbuflen,uint32_t timeout) 3. { 4. int ret = -1; 5. if(NULL == cmd) 6. { 7. return ret; 8. } 9. /* 如果需要判断回复信息中是否包含index关键词 */ 10. if(NULL != index) 11. { 12. /* 创建一个命令结构体,用于发送和接收数据,并同步(信号量/互斥锁) */ 13. ret = __cmd_create(cmd,cmdlen,index,respbuf,respbuflen,timeout); 14. if(0 == ret) 15. { 16. /* 调用底层发送函数将命令结构体中的数据发出 */ 17. ret = __cmd_send(cmd,cmdlen,timeout); 18. if(0 == ret) 19. { 20. /* 通过挂起等待信号量获取回复 */ 21. if(osal_semp_pend(g_at_cb.cmd.respsync,timeout)) 22. { 23. ret = g_at_cb.cmd.respdatalen;//返回回复值的大小 24. } 25. else 26. { 27. ret = -1; 28. } 29. } 30. else 31. { 32. ret = -1; 33. } 34. 35. (void) __cmd_clear(); //清空命令结构体 36. } 37. 38. } 39. else //如果不需要处理回复,直接调用底层发送函数发送即可 40. { 41. ret = __cmd_send(cmd,cmdlen,timeout); 42. } 43. 44. return ret; 45. } AT框架中的接收任务 在初始化AT框架时,就会创建一个接收任务。例1:我们调用at_command函数发送完一帧数据时,并需要回复和处理回复,发送之后我们通过信号量的方式让当前任务(含有发送指令的这个任务)挂起,等待信号量,接收任务中会帮我们去查询和读取回复,只要收到回复,接受任务就会释放信号量,让刚才挂起的任务恢复,来处理回复数据。 例2:有的信息是异步的,例如nbiot设备收到了一条udp服务器下发的信息,正好我们之前通过at_oobregister函数注册了关键词处理函数,这时接收任务就会判断它收到的数据中是否含有index关键词(我们之前注册的“+NSONMI:”),它就会自动调用相应的处理函数帮我们处理数据。1. static int __rcv_task_entry(void *args) 2. { 3. bool_t matchret; 4. int oobret; 5. int rcvlen = 0; 6. 7. g_at_cb.devhandle = los_dev_open(g_at_cb.devname,O_RDWR); //以读写方式开启设备 8. if(NULL == g_at_cb.devhandle) //检查设备操作句柄是否正确 9. { 10. LINK_LOG_DEBUG("%s:open %s err\n\r",__FUNCTION__,g_at_cb.devname); 11. return 0; 12. } 13. 14. while(NULL != g_at_cb.devhandle) 15. { 16. //streammode可能一帧数据分作分为多次下发,只要没有处理完一帧数据(rcvlen == 0),就一直累加 17. if (1 == g_at_cb.streammode) //in stream mode, we need to save previous frames in buffer 18. { 19. if(rcvlen == 0) //为接收缓存区清空一块大小为CONFIG_AT_RECVMAXLEN的内存 20. { 21. (void)memset(g_at_cb.rcvbuf,0,CONFIG_AT_RECVMAXLEN); 22. } 23. rcvlen += __resp_rcv(g_at_cb.rcvbuf+ rcvlen,CONFIG_AT_RECVMAXLEN,cn_osal_timeout_forever); 24. if( rcvlen > 0) //如果收到了数据,开始处理数据 25. { 26. matchret = __cmd_match(g_at_cb.rcvbuf,rcvlen); //判断接收到的数据中是否有cmd结构体中的index 27. if(0 != matchret) 28. { 29. oobret = __oob_match(g_at_cb.rcvbuf,rcvlen);//判断接收到的数据中是否有oob结构体中的index 30. if(oobret != -1) 31. { 32. rcvlen = 0; 33. } 34. } 35. else 36. { 37. rcvlen = 0; 38. } 39. } 40. } 41. else //一帧数据一定是一次下发完毕的 42. { 43. (void) memset(g_at_cb.rcvbuf,0,CONFIG_AT_RECVMAXLEN); 44. rcvlen = __resp_rcv(g_at_cb.rcvbuf,CONFIG_AT_RECVMAXLEN,cn_osal_timeout_forever); 45. if(rcvlen > 0) 46. { 47. matchret = __cmd_match(g_at_cb.rcvbuf,rcvlen); 48. if(0 != matchret) 49. { 50. (void) __oob_match(g_at_cb.rcvbuf,rcvlen); 51. } 52. } 53. } 54. } 55. 56. return 0; 57. }
-
摘要:本贴以前面3篇帖子作为基础(由下至上Uart.c、driver.c),讲解AT框架,这三层之间由下到上是一个依次调用关系,如果对前面的内容不熟悉,请回头看一看(可以通过汇总帖中的链接查看),再来看本贴,你就会更加明白和清晰了。本系列汇总贴:https://bbs.huaweicloud.com/forum/thread-51806-1-1.html下图红框中的位置是本帖所讲解内容在整个系统中AT框架的位置AT.c文件中用到的结构体typedef struct { const char *devname; //we use the device frame work to do this 要使用名称为"devname"的设备作为at收发使用 los_dev_t devhandle;//the device handle used devname的设备句柄,可以通过该句柄对设备进行操作 at_cmd_item cmd; //the at command,only one command could be excuted at_oob_item oob[CONFIG_AT_OOBTABLEN]; //storage the out of band dealer char rcvbuf[CONFIG_AT_RECVMAXLEN]; //used storage one frame,read from the at channel 用于缓存收到的数据,大小可以手动调整 unsigned int rxdebugmode:2; //receive debug mode unsigned int txdebugmode:2; //send debug mode int streammode; }at_cb_t; //这里的cb=control block 控制块的意思AT框架初始化/******************************************************************************* function :this is the at module initialize function parameters :func_read:which will read a frame from the device func_write:which will be used to write a frame to the device instruction :if you want to use the at frame work, please call this function please supply the function read and write.the read function must be a block function controlled by timeout *******************************************************************************/ /* 该函数用于初始化AT框架,在使用AT框架之前需要先调用该函数 */ int at_init() { int ret = -1; (void) memset(&g_at_cb,0,sizeof(g_at_cb)); //清空g_at_cb全局结构体变量中的数据 g_at_cb.devname = CONFIG_AT_DEVNAME; //将我们配置用于AT框架设备的名称复制给devname,可以通过Kconfig配置,也可以手动配置 if(false == osal_semp_create(&g_at_cb.cmd.cmdsync,1,1)) //创建一个信号量,用于同步 { LINK_LOG_DEBUG("%s:cmdsync error\n\r",__FUNCTION__); goto EXIT_CMDSYNC; } if(false == osal_semp_create(&g_at_cb.cmd.respsync,1,0)) //创建一个信号量,用于同步 { LINK_LOG_DEBUG("%s:cmdsync error\n\r",__FUNCTION__); goto EXIT_RESPSYNC; } if(false == osal_mutex_create(&g_at_cb.cmd.cmdlock)) //创建一个互斥锁 { LINK_LOG_DEBUG("%s:cmdlock error\n\r",__FUNCTION__); goto EXIT_CMDLOCK; } if(NULL == osal_task_create("at_rcv",__rcv_task_entry,NULL,0x800,NULL,10)) //创建一个任务,用于接收AT数据 { LINK_LOG_DEBUG("%s:rcvtask create error\n\r",__FUNCTION__); goto EXIT_RCVTASK; } //for the debug g_at_cb.rxdebugmode = en_at_debug_ascii; //这两个模式暂时可以忽略,它们是用于通过shell来调试at设备使用的 g_at_cb.txdebugmode = en_at_debug_ascii; ret = 0; return ret; EXIT_RCVTASK: (void) osal_mutex_del(&g_at_cb.cmd.cmdlock); g_at_cb.cmd.cmdlock = cn_mutex_invalid; EXIT_CMDLOCK: (void) osal_semp_del(&g_at_cb.cmd.respsync); g_at_cb.cmd.respsync = cn_semp_invalid; EXIT_RESPSYNC: (void) osal_semp_del(&g_at_cb.cmd.cmdsync); g_at_cb.cmd.cmdsync = cn_semp_invalid; EXIT_CMDSYNC: return ret; }使用at_oobregister注册关键词处理函数/******************************************************************************* function :you could use this function to register a method to deal the out of band data parameters : instruction :as you know, we only check the frame begin,using memcmp, so you must write the header of the frame as the index *******************************************************************************/ /* 比如说我们的nbiot模块通过udp/tcp协议连接到了服务器,服务器给nbiot模块下发一条数据,这条数据很可能是异步的,就是我们无法预测什么时候会收到该数据, 那么如何能处理这个数据呢?还好每条异步数据都会有一个前缀,例如+NSONMI:0,4,说明nbiot设备收到了一帧UDP数据,0号socket接口,长度为4字节, 我们就可以通过下面这个函数将“+NSONMI:”关键字注册进去,只要at框架发现收到了一帧以“+NSONMI:”开头的数据,就会调用我们注册的的处理函数, 来处理该数据,比如果根据+NSONMI:0,4中的4,设置4字节大小的空间,并发送AT+NSORF=0,4指令,将数据读出存储。 */ /* 传入参数name是该处理结构体的名字,index就是特定数据中会含有的关键字,比如前面说的“+NSONMI:”,len是指index的长度,func是一个函数指针,当发现index 在数据中出现,就会调用该函数指针来处理数据,args是调用该函数指针时,给函数传入的参数。 */ int at_oobregister(const char *name,const void *index,size_t len,fn_at_oob func,void *args) { int ret = -1; at_oob_item *oob; int i = 0; if((NULL == func)||(NULL == index)) { return ret; } for(i =0;i<CONFIG_AT_OOBTABLEN;i++) //在存储oob结构体的表中寻找一个空位 { oob = &g_at_cb.oob[i]; if((oob->func == NULL)&&(oob->index == NULL)) { oob->name = name; //将我们传入的各项参数赋值给OOB结构体 oob->index = index; oob->len = len; oob->func = func; oob->args = args; ret = 0; break; } } return ret; }流模式使能和禁止函数/******************************************************************************* function :you could use this function to to enable or disable at stream mode. parameters :mode:1 for stream mode, 0 for dgram mode. instruction :If stream mode is enabled, we can process data from multiple frames. mode equals 0 by default. *******************************************************************************/ /* 由于UDP和TCP连接的方式有差异,所以根据我们设备选用的模式,可以选择dgram模式和stream模式,默认是dgram模式, * 选择了不同的模式,差异主要体现在接收任务中,执行接收任务时,会判断当前采用的模式,使用不同的接收方式 */ int at_streammode_set(int mode) { g_at_cb.streammode = mode; return 0; }at框架中的内部函数,只在at框架内部使用/* 该函数被at_command函数调用,用于向uart设备写入要发送的数据 */ static int __cmd_send(const void *buf,size_t buflen,uint32_t timeout) { int i = 0; ssize_t ret = 0; int debugmode; ret = los_dev_write(g_at_cb.devhandle,0,buf,buflen,timeout); if(ret > 0) { debugmode = g_at_cb.txdebugmode; switch (debugmode) //如果开启了debug模式,则会将发送的数据通过shell显示 { case en_at_debug_ascii: LINK_LOG_DEBUG("ATSND:%d Bytes:%s\n\r",(int)ret,(char *)buf); break; case en_at_debug_hex: LINK_LOG_DEBUG("ATSND:%d Bytes:",(int)ret); for(i =0;i<ret;i++) { LINK_LOG_DEBUG("%02x ",*((uint8_t *)(buf) + i)); } LINK_LOG_DEBUG("\n\r"); break; default: break; } ret = 0; } else { ret = -1; } return ret; } /* 该函数在at接收任务中被调用,向driver层中读取uart设备中的数据 */ //this function used to receive data from the AT channel static int __resp_rcv(void *buf,size_t buflen,uint32_t timeout) { int i = 0; ssize_t ret = 0; int debugmode; ret = los_dev_read(g_at_cb.devhandle,0,buf,buflen,timeout); //调用driver层中的读函数,以缓存区最大字节数来读 if(ret > 0) //如果读到了数据 { debugmode = g_at_cb.rxdebugmode; //假设我们使能了rxdebugmode,就会将读到的数据在串口打印 switch (debugmode) { case en_at_debug_ascii: LINK_LOG_DEBUG("ATRCV:%d Bytes:%s\n\r",(int)ret,(char *)buf); break; case en_at_debug_hex: LINK_LOG_DEBUG("ATRCV:%d Bytes:",(int)ret); for(i =0;i<ret;i++) { LINK_LOG_DEBUG("%02x ",*((uint8_t *)(buf) + i)); } LINK_LOG_DEBUG("\n\r"); break; default: break; } } return ret; } /* 该函数在at_command函数中被调用,用于向g_at_cb.cmd结构体成员中,这个结构体中的内容最终被任务接收函数调用__cmd_match函数使用 */ static int __cmd_create(const void *cmdbuf,size_t cmdlen,const char *index,void *respbuf,size_t respbuflen,uint32_t timeout) { int ret = -1; at_cmd_item *cmd; //定义一个命令结构体指针 cmd = &g_at_cb.cmd; //将全局at控制块结构体中的cmd结构体成员的地址赋值给cmd指针 if(osal_semp_pend(cmd->cmdsync,timeout)) //申请一个信号量,为了保持同步 { if(osal_mutex_lock(cmd->cmdlock)) //给cmd加锁,防止多进行访问 { cmd->cmd = cmdbuf; //将指令内容、长度、期待的回复、接收回复的内存地址、和接收回复的内存大小都写入cmd结构体 cmd->cmdlen = cmdlen; cmd->index = index; cmd->respbuf = respbuf; cmd->respbuflen = respbuflen; (void) osal_semp_pend(cmd->respsync,0); //used to clear the sync (void) osal_mutex_unlock(cmd->cmdlock); } ret = 0; } return ret; } /* 该函数用于清空g_at_cb.cmd结构中的内容 */ //clear the at command here static int __cmd_clear(void) { at_cmd_item *cmd; cmd = &g_at_cb.cmd; if(osal_mutex_lock(cmd->cmdlock)) { cmd->cmd = NULL; cmd->cmdlen = 0; cmd->index = NULL; cmd->respbuf = NULL; cmd->respbuflen = 0; cmd->respdatalen = 0; (void) osal_mutex_unlock(cmd->cmdlock); } (void) osal_semp_post(cmd->cmdsync); return 0; } /* 该函数在at接收任务中被循环调用,用于检查接收到的数据是否对应我们使用__cmd_send发出去的回应,如果出现我们期待的数据(index),返回0反之返回-1 */ //check if the data received is the at command need static int __cmd_match(const void *data,size_t len) { int ret = -1; int cpylen; at_cmd_item *cmd = NULL; cmd = &g_at_cb.cmd; if(osal_mutex_lock(cmd->cmdlock)) //加锁,防止多个进程同时访问 { if((NULL != cmd->index)&&(NULL != strstr((const char *)data,cmd->index))) //检查index是否为空、收到的数据中是否含有我们需要的数据 { if(NULL != cmd->respbuf) //如果定义了respbuf缓存区并且分配了内存,就将与AT指令向对应的回复拷贝进去 { cpylen = len > cmd->respbuflen?cmd->respbuflen:len; (void) memcpy((char *)cmd->respbuf,data,cpylen); cmd->respdatalen = cpylen; } else { cmd->respdatalen = len; //tell the command that how many data has been get } (void) osal_semp_post(cmd->respsync); ret = 0; } (void) osal_mutex_unlock(cmd->cmdlock); } return ret; } /* 当收到一帧数据时,就会在at接收任务中调用该函数,查看这帧数据中是否有我们向oob表中注册的数据处理函数对应的关键字 如果有的话,就调用其对应处理函数来处理这帧数据 */ //check if any out of band method could deal the data static int __oob_match(void *data,size_t len) { int ret = -1; at_oob_item *oob; int i = 0; for(i =0;i<CONFIG_AT_OOBTABLEN;i++) //遍历存储oob结构体的数组 { oob = &g_at_cb.oob[i]; if((oob->func != NULL)&&(oob->index != NULL)&&\ (0 == memcmp(oob->index,data,oob->len))) { ret = oob->func(oob->args,data,len); break; } } return ret; }由于有字数限制,后面的函数在下一节中写出,并在这里贴上链接。
-
设备开启函数我们在使用某个设备之前,如果该设备具有open和close的能力,就要先去打开该设备,用完之后关闭该设备。/******************************************************************************* function :open the device with the specified name parameters : instruction :if first open the device,then will call the init function if exsited *******************************************************************************/ /* 传入需要开启的设备名称,以及一个flag,该flag的值取决于我们定义设备的open函数指针时定义的 */ los_dev_t los_dev_open (const char *name,unsigned int flag) { bool_t opret = true; //operate result struct driv_cb *driv = NULL; //the driver attached struct dev_cb *dev = NULL; //the device opened if ( NULL == name ) { goto EXIT_PARAERR; } dev = osal_malloc(sizeof(struct dev_cb)); //为设备结构体指针分配内存,用于存储需要操作设备的信息 if (NULL == dev) { goto EXIT_MEMERR; } (void) memset(dev,0,sizeof(struct dev_cb)); opret = osal_mutex_lock(s_los_driv_module.lock); //加锁,防止多个进程同时访问 if(false == opret) { goto EXIT_MUTEXERR; } driv = __driv_match(name); //通过设备名称寻找设备对应的结构体 if(NULL == driv) { goto EXIT_DRIVERR; } //WE DON'T CARE TOO MUCH ABOUT THE RD AND WR FLAG,MAY BE TODO IT IN THE NEXT VERSION HERE if((O_EXCL & (unsigned int )driv->flagmask) && (NULL != driv->devlst)) { goto EXIT_EXCLERR; } if((0 == (driv->drivstatus & cn_driv_status_initialized)) && \ //如果设备未初始化但是有初始化函数 (NULL != driv->op->init)) { opret = driv->op->init(driv->pri); //在这里调用其初始化函数 if(false == opret) { driv->errno = en_dev_err_init; goto EXIT_INITERR; } driv->drivstatus |= cn_driv_status_initialized; } if(NULL != driv->op->open) //如果该设备没有open能力或者说是open函数 { opret = driv->op->open(driv->pri,flag); if(false == opret) { driv->errno = en_dev_err_open; goto EXIT_OPENERR; } } //reach here means all the initialize is ok //add the dev to the list of the drive and attach the driv to the device driv->opencounter++; //开启的设备总数加一 dev->nxt = driv->devlst; //将这个设备的结构体指针挂载到开启的设备链表中 driv->devlst = dev; dev->driv = driv; dev->openflag = flag; (void) osal_mutex_unlock(s_los_driv_module.lock); return dev; EXIT_OPENERR: EXIT_INITERR: EXIT_EXCLERR: EXIT_DRIVERR: (void) osal_mutex_unlock(s_los_driv_module.lock); EXIT_MUTEXERR: osal_free(dev); dev = NULL; EXIT_MEMERR: EXIT_PARAERR: return dev; //返回一个操作句柄,我们读写数据就是通过这个操作句柄来操作 }设备关闭函数在使用完设备之后,我们可以调用设备关闭函数。/******************************************************************************* function :close the device of opened parameters :handle returned by open instruction : *******************************************************************************/ bool_t los_dev_close (los_dev_t dev) //传入操作句柄 { bool_t ret = false; struct dev_cb *devcb = NULL; struct dev_cb *tmp = NULL; struct driv_cb *driv = NULL; if ( NULL == dev ) { goto EXIT_PARAERR; } devcb = dev; if(false == osal_mutex_lock(s_los_driv_module.lock)) //加锁,防止多进程同时访问出错 { goto EXIT_MUTEXERR; } //deattach the dev from the driv driv = devcb->driv; //将设备结构体地址赋值给driv if(NULL == driv) { goto EXIT_DRIVERR; } if(devcb == driv->devlst) //如果需要关闭的设备是链表中第一个 { driv->devlst = devcb->nxt; } else //如果需要关闭的设备不是链表中第一个 { tmp = driv->devlst; while(NULL != tmp) { if(tmp->nxt == devcb) { tmp->nxt = devcb->nxt; break; } tmp = tmp->nxt; } if(NULL == tmp) { goto EXIT_DETACHERR; } } if(NULL != driv->op->close) //调用设备的关闭函数 { driv->op->close(driv->pri); } if((NULL == driv->devlst) && (NULL != driv->op->deinit)) { driv->op->deinit(driv->pri); //调用设备的去除初始化函数 driv->drivstatus &= (~cn_driv_status_initialized); //将设备状态置为未初始化 } osal_free(dev); //释放设备结构体所占用内存 driv->opencounter--; //开启的设备总数减一 (void) osal_mutex_unlock(s_los_driv_module.lock); //解锁 ret = true; EXIT_DETACHERR: EXIT_DRIVERR: (void) osal_mutex_unlock(s_los_driv_module.lock); EXIT_MUTEXERR: EXIT_PARAERR: return ret; }向设备中读取数据/******************************************************************************* function :use this function to read data from the device parameters :dev,returned by the los_dev_open function offet:from where to read,only used for storage device buf:used to storage the data len:the length of the buf timeout:the waittime if no data current instruction :how many data has been read to the buf *******************************************************************************/ //传入操作句柄、需要读取的数据在设备中的偏移量,读到指针对应的某个内存空间,读取的长度,读取超时时间 ssize_t los_dev_read (los_dev_t dev,size_t offset, void *buf,size_t len,uint32_t timeout) { ssize_t ret = 0; struct dev_cb *devcb; struct driv_cb *drivcb; if((NULL != dev)&&(NULL != buf)&&(0 != len)) { devcb = dev; if((0 == ((uint32_t)devcb->openflag & O_WRONLY))) //检查设备属性,是否可读 { drivcb = devcb->driv; if((NULL != drivcb->op)&&(NULL != drivcb->op->read)) { ret = drivcb->op->read( drivcb->pri,offset,buf,len,timeout); //调用设备的读函数,传入参数 if(ret > 0) { drivcb->total_read += ret; } } } } return ret; //返回读出数据的个数 }向设备中写入数据/******************************************************************************* function :use this function to write data to the device parameters :dev,returned by the los_dev_open function offset: from where to write,only used for storage device buf:the data to be written len:the length of the buf timeout:the waittime if no data current instruction :how many data has been written to the device *******************************************************************************/ //传入操作句柄,需要写入的位置,需要被写入数据的地址,写入长度,超时时间 ssize_t los_dev_write (los_dev_t dev,size_t offset,const void *buf,size_t len, uint32_t timeout) { ssize_t ret = 0; struct dev_cb *devcb; struct driv_cb *drivcb; if((NULL != dev) && (NULL != buf) && (len != 0)) { devcb = dev; if((((uint32_t)devcb->openflag) & O_WRONLY) || (((uint32_t)devcb->openflag) & O_RDWR)) //查看设备的属性是否支持写入 { drivcb = devcb->driv; if((NULL != drivcb->op)&&(NULL != drivcb->op->write)) { ret = drivcb->op->write( drivcb->pri,offset,buf,len,timeout); //调用设备的写入函数,并将参数传入 if(ret > 0) { drivcb->total_write += ret; } } } } return ret; //返回写入数据的个数 }
-
摘要:本贴详解driver层,相信大家肯定有疑问,为啥我不说完AT层再说driver层?因为AT层就是通过调用driver层中的API来操作串口的,所以不得不在AT层的详解中先把driver层说了。编程的抽象思想 我们在接到一个客户的需求时,帮客户制作一份程序,大家有没有想过“我能不能把这个客户需要的代码下次卖给其他客户,节约自己的工作量”。要如何才能将刚刚我们提出的想法实现呢?这就需要通过编程抽象思想来实现。 比如说有两位客户,客户①的需求:我要在8051内核单片机上实现一个AT收发的框架;客户②的需求:我要在stm32f103单片机上实现一个AT收发的框架。那么这是我们就可以运用代码接口抽象化、模块化、降低模块与模块间耦合的思想了,将这份代码从下到上分为3层:①串口层:用于初始化串口,将上层的数据通过串口发出。将收到的数据提供给串口,我们在更换不同类型的单片机只需要从新写这一层。②driver层:也就是驱动层,它提供读、写、初始化等等函数给上层模块使用,上层模块操作串口和操作内存卡的感觉是一样的,通过这层完全屏蔽了底层设备的差异,所有能进行读写的设备都可以通过driver层注册并提供读、写函数即可,在更换底层设备,例如将内存卡换成u盘只要适配好内存卡硬件层和u盘硬件层,其余均不用改动。③AT层:AT指令大家都知道,语法为AT+命令=参数、AT+命令?等,我们实现好这一层,只要调用相应函数并传入命令和参数就可以使用了。所以所上面两位客户的需求可以共用相同的AT层和driver层,只用分别添加uart硬件层即可,就算客户后面改需求我们也不怕了,改硬件我们只需要修改硬件层(uart层)即可。driver层中的结构体 使用结构体编程运用了面向对象的思想,把一个设备想象成对象,他有自己的属性和行为,属性对应各种变量(设备名、只读、只写等),行为对应各种函数(初始化函数、读函数、写函数等)。typedef struct { const char *name; //device driver name 设备名称 los_driv_op_t *op; //device operate functions 设备能进行的操作 void *pri; //private data,will be passed to op functions 设备的私有数据,例如MCU有多个串口,使用同样的IO方法,用pri表示操作的不同的串口 uint32_t flag; //flags, like O_RDONLY O_WRONLY O_RDWR 设备的数据:只读、只写、读写 }os_driv_para_t; /* 该结构体中存储设备具有的能力的函数指针,意思就是设备可以具有以下能力,如果没有该能力或者不使用该能力可以不实现相关函数 */ typedef struct { fn_devopen open; //triggered by the application 打开设备 fn_devread read; //triggered by the application 读 fn_devwrite write; //triggered by the application 写 fn_devclose close; //triggered by the application 关闭设备 fn_devioctl ioctl; //triggered by the application 暂时忽略该函数,因为不使用 fn_devseek seek ; //triggered by the application 查看设备是否存在 fn_devinit init; //if first open,then will be called 设备初始化 fn_devdeinit deinit; //if the last close, then will be called 设备去除初始化 }los_driv_op_t; /* 这个是一个driver层中私有的结构体,用于实现一个环形链式数据结构(链表),待会我画一张图给大家看 */ struct driv_cb { void *nxt; //add the deice to device list //结构体指针,用于指向后一个结构体 void *pri; //bsp developed para //结构体指针,用于指向前一个结构体 const char *name; //the device name //设备名 int flagmask; //copy from the register // const los_driv_op_t *op; //operation method //该设备具有的行为(初始化、读、写等) unsigned int drivstatus; //show the state here like init or something like this //设备的状态 los_dev_t devlst; //the open list,support the multi open //设备被哪些地方打开过,类似于互斥锁 //following member used for the debug size_t total_write; //how many data has been sent //向这个设备中一共写入了多少数据 size_t total_read; //how many data has received //向这个设备中一共接收了多少数据 size_t opencounter; //reference counter //这个设备被打开的次数类似于信号量的作用 unsigned int errno; //the last errno has happend //最近的一个错误 }; /* 该结构体用于维护所以在driver层中注册过的设备 */ typedef struct { osal_mutex_t lock; //used to lock the devlst 这是一个互斥锁,可以防止多个进行同时访问driver层导致出错 struct driv_cb *drivlst; //all the dev will be added to the list //通过该结构体指针遍历整个结构体链表可以找出所有注册过的设备的详细信息 unsigned int drivnum; //总的注册设备数量 }los_driv_module;driver层自身初始化/******************************************************************************* function :the device module entry parameters : instruction :call this function to initialize the device module here load the static init from section os_device *******************************************************************************/ /* 主要目的就是为了注册那些我们采用宏定义注册的设备。可以往下看“设备注册函数”,再回头看这里 */ bool_t los_driv_init() { bool_t ret = false; ret = osal_mutex_create(&s_los_driv_module.lock); if(false == ret) { goto EXIT_MUTEX; } //load all the static device init osdriv_load_static(); EXIT_MUTEX: return ret; }设备注册函数设备注册有两种方式,一种是直接调用注册函数注册,另一种是调用宏定义让设备的合适的时候自动注册,先说第一种。/******************************************************************************* function :bsp developer use this function to add a device to the system parameters : instruction :NULL if failed else return the device handle *******************************************************************************/ los_driv_t los_driv_register(os_driv_para_t *para) //事先我们需要实现好一个os_driv_para_t结构体,其中包含设备的各项参数,并将指针传入该函数中 { struct driv_cb *driv = NULL; if((NULL == para->name)||(NULL == para->op)) //如果参数有误,直接返回 { goto EXIT_PARAS; } driv = osal_malloc(sizeof(struct driv_cb)); //为这个设备结构体分配内存 if(NULL == driv) { goto EXIT_MALLOC; } (void) memset(driv,0,sizeof(struct driv_cb)); //清空内存中的数据 //do the member initialize driv->name = para->name; //注册设备名、设备相关操作函数、设备私有数据等等 driv->op = para->op; driv->pri = para->pri; driv->flagmask = para->flag; /* 将这个设备添加到管理设备用的结构体链表中 */ //add it to the device list if no device with the same name exsit if(false == osal_mutex_lock(s_los_driv_module.lock)) //加锁,防止其他进行同时访问,导致出错 { goto EXIT_MUTEX; } if(NULL != __driv_match(para->name)) //查询是否该设备已经添加过 { goto EXIT_EXISTED; } driv->nxt = s_los_driv_module.drivlst; //挂载指针,向链表的头部添加 s_los_driv_module.drivlst = driv; //挂在指针,向链表的头部添加 (void) osal_mutex_unlock(s_los_driv_module.lock); //解锁,恢复访问 s_los_driv_module.drivnum++; //设备总数加1 return driv; EXIT_EXISTED: (void) osal_mutex_unlock(s_los_driv_module.lock); EXIT_MUTEX: osal_free(driv); driv = NULL; EXIT_MALLOC: EXIT_PARAS: return driv; } /* 这是自动注册的宏定义,调用之后它会在合适的时候自动帮我们注册设备,推荐使用这个 */ #define OSDRIV_EXPORT(varname,drivname,operate,pridata,flagmask) \ static const os_driv_para_t varname __attribute__((used,section("osdriv")))= \ { \ .name = drivname, \ .op = operate, \ .pri = pridata, \ .flag = flagmask, \ } /* 该函数初始化driver层时被调用,用于注册我们采用OSDRIV_EXPORT宏注册的设备, 本质原理就是我们在调用宏注册设备时,会通过链接器将这个设备结构体放到一 块特定的空间中,这个函数就去这块特定的空间中寻找需要被注册的设备结构体。 并进行注册操作 */ static void osdriv_load_static(void){ os_driv_para_t *para; unsigned int num = 0; unsigned int i = 0; #if defined (__CC_ARM) //you could add other compiler like this num = ((unsigned int)&osdriv$$Limit-(unsigned int)&osdriv$$Base)/sizeof(os_driv_para_t); para = (os_driv_para_t *) &osdriv$$Base; #elif defined(__GNUC__) para = (os_driv_para_t *)&__osdriv_start; num = ((unsigned int )(uintptr_t)&__osdriv_end - (unsigned int)(uintptr_t)&__osdriv_start)/sizeof(os_driv_para_t); #endif for(i =0;i<num;i++) { (void) los_driv_register(para); para++; } return; } /* 这组宏定义就是定义了一块空间用于存放我们采用OSDRIV_EXPORT宏注册的设备结构体 */ #ifdef __CC_ARM /* ARM C Compiler ,like keil,options for linker:--keep *.o(osdriv)*/ extern unsigned int osdriv$$Base; extern unsigned int osdriv$$Limit; #elif defined(__GNUC__) extern unsigned int __osdriv_start; //这个符号在os.ld文件中 extern unsigned int __osdriv_end; #else #error("unknown compiler here"); #endif设备取消注册函数/******************************************************************************* function :bsp developer use this function to remove a device from the system parameters : instruction :if the device has been refered,then will fail here *******************************************************************************/ /* 该函数本质上就是将我们需要取消注册的设备从设备链表中删除 */ bool_t los_driv_unregister(const char *name) { struct driv_cb *tmp = NULL; struct driv_cb *pre = NULL; bool_t ret = false; if(NULL == name) { return ret; } if(osal_mutex_lock(s_los_driv_module.lock)) { tmp = s_los_driv_module.drivlst; //将设备链表的指针赋值给tmp pre = NULL; while(NULL != tmp) { if( 0 == strcmp(name,tmp->name)) //找到需要被删除的设备结构体 { break; } /* 找到找到需要被删除的设备结构体 */ pre = tmp; //遍历结构体 tmp = tmp->nxt; } if(NULL != tmp) //find the device here { if(pre == NULL) //说明要被删除的是第一个 { s_los_driv_module.drivlst = tmp->nxt; } else { pre->nxt = tmp->nxt; } osal_free(tmp); //释放这块内存 ret = true; s_los_driv_module.drivnum--; //设备总数减一 } (void) osal_mutex_unlock(s_los_driv_module.lock); } return ret; }PS:还有三个函数没说,读设备函数、写设备函数、发现设备函数,由于帖子有字数限制,所以我放到下一篇帖子中来说。
-
邀请人数每周五进行一次公示(见附件)。邀请人数系统统计,将会计算去重后结果。即,一个人在两个人的分享链接进行报名,以第一次报名为准,第二次报名不算在邀请人邀请列表。获奖名单公示期已结束,公示期结束日期截止为6月8日23:59:59。奖品发放以邮件形式进行,请提供华为云账号截图至邮箱(oceanconnect@huawei.com)。反馈截止时间为2020.6.30,过期不候哦~a) 截图示例如下:b) 邮件标题:【LiteOS邀请名单奖励】--100元京东券(奖励名称)邀请奖励发放将在公示期结束后15个工作日内完成。活动报名链接>>>Huawei LiteOS设备开发实战
-
摘要:本贴讲解ring_buffer.c文件,该文件中的函数用于在uart_at.c文件中管理串口接收缓存内存,是我们AT框架的底层,对于深度理解AT框架底层数据的运作起到了至关重要的作用。tag_ring_buffer_t结构体我们在使用ring_buffer.c文件中的函数之前,都需要通过该结构体创建一个其对应的结构体变量,并调用初始化函数,让我们需要管理的内存和这个结构体中的成员进行关联 /* * dataoff * ring buffer map: | ----datalen------- * | / \ * |/ \ * ---------------------------------------------------------- * | empty space |//////valid data///////| | * ---------------------------------------------------------- * | \ / * | \ / * | \ / * | ---------------buflen-------------------------- * buf */ typedef struct { unsigned char *buf; ///< which means the buffer int buflen; ///< which means the buffer limit int datalen; ///< which means how many data in the buffer int dataoff; ///< which means the valid data offset in the buffer }tag_ring_buffer_t;读到这里,大家有没有想过这样做的好处是什么?我们通过特定函数对ring_buf清除或者把数据读走,实际上并没有真正的清除了其中的数据只是修改了dataoff变量和datalen变量,下面我们先来看看初始化函数。tag_ring_buffer_t结构体初始化并和被管理的内存绑定首先,我们需要申请一段内存,并创建一个tag_ring_buffer_t结构体类型的变量,通过调用ring_buffer_init函数,让tag_ring_buffer_t结构体类型的变量和我们申请的这段内存产生管理,从而能上面说的这种机制来管理这段内存。先来看一个uart_at.c文件中的例子uart_at.c文件中创建一个g_atio_cb结构体变量,成员包括了tag_ring_buffer_t创建的结构体变量rcvring,这个变量用于管理rcvringmem这段内存空间,通过ring_buffer_init函数将它们关联到一起。int ring_buffer_init(tag_ring_buffer_t *ring,unsigned char *buf, int buflen,int offset,int datalen) { int ret = -1; if((NULL == ring)) { return ret; } ring->buf = buf; //将传入内存地址指针添加到结构体中 ring->buflen = buflen; //设置内存大小 ring->datalen = datalen; //设置数据长度,初始值为0,因为还没有数据,在读写函数中会去修改该变量 ring->dataoff = offset; //设置有效数据起始位置,初始值为0,因为还没有数据,在读写函数中会去修改该变量 ret = 0; return ret; }向ring_buffer中写数据/* * ring结构体:我们想写入数据的ring_buffer,buf指针:指向要写入的数据的起始地址,len:要写入数据的长度 * 返回值:实际写入数据的个数 */ int ring_buffer_write(tag_ring_buffer_t *ring,unsigned char *buf, int len) { int ret = -1; int cpylen; //the current time we should move int lenleft; //and how many data still left to move int offset; unsigned char *src; unsigned char *dst; if((NULL == ring)||(NULL == buf)||(0 == len)) //传入的参数不对 { return ret;//which means parameters error } /* 如果ring结构体中的数据长度等于内存长度,那说明满了,写不下了 */ if((ring->datalen == ring->buflen)|| (ring->buflen <= 0)) { ret = 0; return ret;//which means you could copy nothing here } /* 如果我们要写的数据长度大于剩余空间的长度,那么只能写一部分进去(剩余空间的大小),反之,可以全部写进去 */ ret = len > (ring->buflen-ring->datalen)?(ring->buflen-ring->datalen):len; /* 这里的意思是我们写数据, 从empty space2的起始位置开始写,如果数据长度大于empty space2的大小,就需要回到empty space1的位置接着写 ,是不是有点ring的味道了? ---------------------------------------------------------------------------------------------- || empty space1 ||//////valid data///////|| empty space2 || ---------------------------------------------------------------------------------------------- */ //now let us think the method to fill the data,take care of the roll back lenleft = ret; //需要写的数据大小 src = buf; //需要写的数据的起始位置 /* 如果有效数据起始位置加上有效数据长度大于内存长度,说明这段内存中包含了一份roll back的数据,长这样 */ /* ---------------------------------------------------------------------------------------------- || valid data2 ||//////empty space///////|| valid data1 || ---------------------------------------------------------------------------------------------- 其中valid data1和valid data2是同一帧数据,该数据由valid data1开始到valid data2结束,所以我们只能从valid data2结束位置开始写新数据 */ if((ring->dataoff+ring->datalen)>ring->buflen) //which means the data has roll back { offset = (ring->dataoff+ring->datalen)%ring->buflen; //we could move it one time 计算一下内存开始位置的数据大小 cpylen = lenleft; //需要写的数据的大小 dst = ring->buf + offset; //设置dst指针指向valid data2结束位置 if(cpylen > 0) { (void) memcpy(dst,src,cpylen); //开始写数据 ring->datalen += cpylen; //更新datalen计数,增加上我们写入数据的大小 lenleft -= cpylen; //计算还有多少数据没写 } } /* 如果效数据起始位置加上需要写的数据长度大于内存大小,我们就需要写到内存末尾转回到内存起始位置接 着写,假设old valid data是之前已经存在的数据,valid data是我们写的一帧数据,那么写完之后就和下图一样 ---------------------------------------------------------------------------------------------- || valid data ||//////empty space/////// || old valid data || valid data || ---------------------------------------------------------------------------------------------- */ else if((ring->dataoff+ring->datalen + lenleft)>ring->buflen) //which means the data will be roll back { //which means roll back,we should copy some here to the tail offset = ring->dataoff + ring->datalen; //计算老的数据的结束位置 cpylen = ring->buflen - offset; //计算新的数据有多少可以写到尾部 dst = ring->buf + offset; //将老的数据的结束位置地址赋值给dst (void) memcpy(dst,src,cpylen); //开始写尾部的数据 src += cpylen; //移动指针,指向剩余的数据 ring->datalen += cpylen; //更新数据长度 lenleft -= cpylen; //计算还有多少数据没写 } //here means we could move it by one time if(lenleft > 0) //如果有数据还没写完 { offset = (ring->dataoff+ring->datalen)%ring->buflen; //we could move it one time 计算下次写的位置 cpylen = lenleft; //将剩余为写入的数据个数写到cpylen中 dst = ring->buf + offset; //设置下次写的位置 (void) memcpy(dst,src,cpylen); //开始写 ring->datalen += cpylen; //更新数据长度计数 } return ret; }向ring_buffer中读数据/* * ring结构体:我们从ring_buffer中读取数据,buf:传入一块内存的地址,将读到的数据存到这块内存中,len:这块内存的大小 * 返回值:返回实际读到的内存大小 */ int ring_buffer_read(tag_ring_buffer_t *ring,unsigned char *buf, int len) { int ret = -1; int cpylen; //the current time we should move int lenleft; //and how many data still left to move int offset; unsigned char *src; unsigned char *dst; if((NULL == ring)||(NULL == buf)||(0 == len)) { return ret;//which means parameters error } /* 如果ring_buffer为空 */ if((ring->datalen == 0) || (ring->buflen <= 0)) { ret = 0; return ret;//which means you could copy nothing here } /* 计算实际读到的数据大小 */ ret = len > ring->datalen?ring->datalen:len; //now let us think the method to fill the data,take care of the roll back lenleft = ret; dst = buf; /* 如果数据的偏移位置大于等于内存大小减去需要读取的数据大小,情况如下图,从old valid data开始读, 读到vaild data2结束 */ /* ---------------------------------------------------------------------------------------------- || valid data2 ||//////empty space/////// || old valid data || valid data1 || ---------------------------------------------------------------------------------------------- */ if(ring->dataoff >= (ring->buflen - lenleft)) //which means the data has roll back { offset =ring->dataoff; //we cpy part 从offset位置开始读 cpylen = ring->buflen - ring->dataoff; //先读到内存末尾 src = ring->buf + offset; //设置开始读的地址 if(cpylen > 0) { (void) memcpy(dst,src,cpylen); ring->dataoff = (ring->dataoff + cpylen)%ring->buflen; //将dataoff移动到内存末尾 ring->datalen -= cpylen; //ring中的数据计数器减去已经读了的数据 lenleft -= cpylen; //计算剩余需要读取的数据 dst += cpylen; //移动dst指针指向用于存储数据的内存的为空的位置 } } //here means we could move it by one time /* 从头部开始读 */ if(lenleft > 0) { offset =ring->dataoff; //we cpy part cpylen = lenleft; src = ring->buf + offset; (void) memcpy(dst,src,cpylen); ring->dataoff = ring->dataoff + cpylen; ring->datalen -= cpylen; } return ret; }剩余函数剩余函数比较简单,可以略过/* 获取ring_buffrt中数据的长度 * */ int ring_buffer_datalen(tag_ring_buffer_t *ring) { int ret = -1; if(NULL != ring) { ret = ring->datalen; } return ret; } /* 获取ring_buffrt中剩余空间 * */ int ring_buffer_freespace(tag_ring_buffer_t *ring) { int ret = -1; if(NULL != ring) { ret = ring->buflen-ring->datalen; } return ret; } /* 重置ring_buffer,本质就是修改两个计数器即可 * */ int ring_buffer_reset(tag_ring_buffer_t *ring) { int ret = -1; if(NULL != ring) { ring->datalen = 0; ring->dataoff = 0; ret = 0; } return ret; } /* ring_buffrt反初始化 * */ int ring_buffer_deinit(tag_ring_buffer_t *ring) { int ret = -1; if(NULL != ring) { (void) memset(ring,0,sizeof(tag_ring_buffer_t)); ret = 0; } return ret; }
上滑加载中
推荐直播
-
华为云AI入门课:AI发展趋势与华为愿景
2024/11/18 周一 18:20-20:20
Alex 华为云学堂技术讲师
本期直播旨在帮助开发者熟悉理解AI技术概念,AI发展趋势,AI实用化前景,了解熟悉未来主要技术栈,当前发展瓶颈等行业化知识。帮助开发者在AI领域快速构建知识体系,构建职业竞争力。
去报名 -
华为云软件开发生产线(CodeArts)10月新特性解读
2024/11/19 周二 19:00-20:00
苏柏亚培 华为云高级产品经理
不知道产品的最新特性?没法和产品团队建立直接的沟通?本期直播产品经理将为您解读华为云软件开发生产线10月发布的新特性,并在直播过程中为您答疑解惑。
去报名
热门标签