基于OpenCPU方案的BC26 NB模组开发总结
文章目录
- 一.概述
- 二.模块简介
- 三.编译流程
- 1.开发环境搭建
- 2.工程编译
- 3.程序烧录
- 四.程序开发流程
- 1.主任务定义
- 2.系统资源初始化
- 3.socket连接流程
- 4.socket连接成功后,发送数据
- 5.在执行初始化工作时,注册了socket接收函数,下面实现它
- 6.dns域名解析
- 五.总结
一.概述
BC26是一款典型的NB模组,支持OpenCPU开发方式,所谓OpenCPU开发方式指的就是模组本身的CPU资源对外部分开放,也就是基于模组本身的固件库提供的API接口做二次开发,以满足不同的业务需要,这样就可以将以往“CPU+模组“的开发方式简化为单”模组“的开发方式。节省了硬件资源,同时避免了CPU和模组之间的通信,提高了通信可靠性和性能。BC26模块就支持Opencpu的开发方案,其提供的API接口是基于FreeRTOS操作系统的。本文就详细介绍BC26模块的OpenCPU开发流程。
二.模块简介
BC26 NB模块主要以串口的方式与外部通信,对外主要提供了两个uart串口,一个为主串口main uart,另一个为调试串口debug uart。主串口主要的作用有两个:一个是烧录应用程序和升级版本固件;另一个是AT指令的通信口。当我们使用opencpu方案做开发时,主串口不再作为外部AT指令的通信口而只作为程序的烧录口用。调试串口主要是用来打印一些调试信息。所以说串口是BC26模块对外提供的主要接口。
另外要介绍的就是模块的复位引脚,因为在给模块烧录程序(或固件)的时候需要使模块复位以做烧录同步。
支持的网络协议包括:UDP/ TCP/ LwM2M /MQTT/ SNTP/ CoAP*/ PPP*/ TLS*/ DTLS*/ HTTP*/ HTTPS*。本文主要是基于UDP和TCP协议完成socket编程。
三.编译流程
1.开发环境搭建
首先我拿到的是移远官方的SDK开发包(V1.6版本):BC26_QuecOpen_NB1_SDK_V1.6,这里有一个小插曲,我最开始拿到的是V1.5版本,在开发过程中发现V1.5版本不支持DNS协议解析,后来分析发现是SDK版本的问题,升级到V1.6版本后就可以了。拿到这个SDK包之后在windows环境下使用代码编辑工具打开(如:source insight、vscode等都可以),我选用的是vscode编辑器。
2.工程编译
SDK开发包中提供make编译工具链,我们只要修改它提供的Makefile文件,将我么自己的添加的工程文件路径放在殡仪目录下就可以了,主要是头文件路径、源文件路径、编译的C文件设置,如下图所示:
在修改好Makefile文件之后,直接打开SDK中的make工具,执行make new重新编译整个工程,执行make编译修改的文件,执行make clean清除工程编译痕迹。make编译工具如下所示:
3.程序烧录
在我们使用make new命令编译好整个工程之后会在SDK的build/gcc目录下生成app_image_bin.cfg文件,这就是我们编译完成的目标文件,等会就是将这个文件烧录到BC26模块中,如下所示:
程序的烧录需要使用到SDK提供的QFlash烧录工具,这个工具在tools目录中,打开QFlash后的界面如下:
将BC26的主串口连接到电脑上,在QFlash工具中选中对应的端口号,选择烧录的波特率(大小都可,不过选择较高的波特率烧录速度会加快,这里选择460800)。设置好串口之后点击Load FW Files按钮选中之前我们编译号的目标文件app_image_bin.cfg(切记:这个目标文件存放发路径一定不能包含中文,否则会烧写失败!!!)。选中之后页面会加载出烧录所需的工程文件路径如上图所示。现在一切准备就绪,可以开始烧录了:点击start之后,系统会停留在等待复位的状态,此时我们开机或者复位都可以做烧录同步,烧录结束后会提示PASS并进度条满格为绿色。至此烧录工作完成。
四.程序开发流程
BC26是基于FreeRTOS系统做二次开发的,在SDK中提供了多种示例,我是基于tcp和udp示例完成了socket通讯工作,以下结合程序简要阐述开发流程。
1.主任务定义
在custom_task_cfg.h文件中创建task,这里我只创建一个主任务proc_main_task:
TASK_ITEM(proc_main_task,main_task_id,10*1024,DEFAULT_VALUE1,DEFAULT_VALUE2) //main task
proc_main_task函数原型:调用了net_task任务,也就是我们编写的主代码
void proc_main_task(s32 taskId)
{ net_task(); //网络任务
}
2.系统资源初始化
完成socket网络通信所使用的系统资源主要包括:uart串口,socket网络,flash存储等。SDK已经给我们封装并提供好了各种API,我们只需要调用即可。初始化部分代码如下:
//被调主任务
void net_task(void)
{ST_MSG msg;s32 ret;u8 read_ip[2]={0}; //读取ip的前两个字节,作为是否已经写入FLASH的标志u32 bund =0; //波特率u8 bund_byte[4]; //波特率临时变量/*1.首先确认是否要对系统配置默认信息*/Ql_Flash_Read(1,309,read_ip,2); //从FLASH中读取ip地址前两位if(read_ip[0]==0 && read_ip[1]==0)Write_DefaultConfig_To_FLASH(); //如果FLASH为空,将系统默认配置信息写入FLASH//Write_DefaultConfig_To_FLASH();/*2.从FLASH中读取波特率并保存到变量bund中*/Ql_Flash_Read(1,309,bund_byte,4); //从FLASH中读取波特率bund = bund_byte[0] | bund_byte[1]<<8 | bund_byte[2]<<16 | bund_byte[3]<<24;/*3.根据读出的bund系统初始化串口*/Ql_UART_Register(m_myUartPort, CallBack_UART_Hdlr, NULL);Ql_UART_Open(m_myUartPort, bund, FC_NONE); //默认串口波特率为115200,可修改/*4.注册Socket回调函数 */ret = Ql_Socket_Recv_Register(callback_socket_recv);//APP_DEBUG("<--register recv callback successful(%d)-->\r\n",ret);/*5.关闭模块睡眠模式并初始化RIL*/Ql_SleepDisable(); //将睡眠模式关闭Ql_RIL_Initialize(); //初始化RILQl_Delay_ms(8000); //延时2S等待连接建立/*6.根据当前选择的中心号获取SOCEKT配置信息*/Ql_Flash_Read(1,300,&Center_num,1); //读取当前的中心号:0或者1或者2Center_analyse(Center_num); //根据中心号初始化参数// APP_DEBUG("Center_num: %d\r\n",Center_num); //输出center号// if(control_num==0){APP_DEBUG("main: ip address\r\n");} //输出协议类型:TCP/UDP(0/1)// else APP_DEBUG("main: domin name\r\n"); //输出协议类型:TCP/UDP(0/1)// if(service_type==0){APP_DEBUG("Protocol: TCP\r\n");} //输出协议类型:TCP/UDP(0/1)// else APP_DEBUG("Protocol: UDP\r\n"); //输出协议类型:TCP/UDP(0/1)// APP_DEBUG("IP_Address: %s\r\n",IP_address); //输出IP地址// APP_DEBUG("Portnum: %s\r\n",portnum); //输出port端口号// APP_DEBUG("Domin_Name: %s\r\n",Domin_Name); //输出域名/*进入串口阻塞等待消息*/while(TRUE) {Ql_OS_GetMessage(&msg); //获取消息}
}
初始化流程分析:
<1>判断芯片是否为首次上电,若为首次上电则将一些初始化配置信息写入FLASH。
<2>根据FLASH中的配置信息初始化主串口,调用系统API函数,同时注册串口接收数据处理的回调函数CallBack_UART_Hdlr,类似于注册中断服务函数。
<3>注册Socket回调函数,因为后面会用到socket编程,因此注册Socket回调函数用来对接收的数据做出了,也类似于接收中断服务函数。
<4>关闭模块睡眠模式并初始化RIL。模块默认是进入睡眠模式的,因此需要手动关闭睡眠模式,初始化RIL为初始化一些库函数,为调用所需要。
<5>初始化完成之后系统就进入while循环中等待串口中断。
3.socket连接流程
建立socket连接是socket通信的核心,其实现函数如下,入口参数为socket配置信息,返回为连接成功或失败标志,bool类型。
/**
* @brief 建立SOCEKT连接
*
* @param *ip_address:IP地址
* *port:端口号
* contextid: context ID, range is 1-3
* service_type: 选择服务类型 0: Start a TCP connection as a client,1: Start a UDP connection as a client
* localport: The local port, range is 1-65535,if <local_port> is 0, then the local port will be assigned automatically, else the local port is assigned as specified
* protocoltype: 0: IPv4,1: IPv6
* connectid: socket service index, range is 0-4
*
*
* @return QL_NO 建立连接失败
* QL_OK 建立连接成功
*/
u8 Connect_To_SOCEKT(socekt_param_user_t *socekt_information)
{u8 srvport[10];u8 *p = NULL;u8 pData[50]={0};s32 ret;//1.command: Set_Srv_Param=<srv ip>,<srv port>Ql_sprintf((char *)pData,"Set_Srv_Param=<%s>,<%s>\r\n",socekt_information->ip_address,socekt_information->port);//APP_DEBUG("%s\r\n",pData);p = Ql_strstr(pData,"Set_Srv_Param=");if (p){Ql_memset(m_SrvADDR, 0, SRVADDR_LEN);if (Analyse_Command(pData, 1, '>', m_SrvADDR)){//APP_DEBUG("SOCKET Address Parameter Error.\r\n");return QL_NO; //SOCKET地址错误,返回NO}Ql_memset(srvport, 0, 10);if (Analyse_Command(pData, 2, '>', srvport)){//APP_DEBUG("SOCKET Port Parameter Error.\r\n");return QL_NO; //SOCKET端口错误,返回NO}socket_param_t.address = Ql_MEM_Alloc(sizeof(u8)*SRVADDR_LEN);if(socket_param_t.address!=NULL){Ql_memset(socket_param_t.address,0,SRVADDR_LEN);Ql_memcpy(socket_param_t.address,m_SrvADDR,sizeof(m_SrvADDR));}socket_param_t.remote_port= Ql_atoi(srvport);//APP_DEBUG("Set Server Parameter Successfully<%s>,<%d>\r\n",socket_param_t.address,socket_param_t.remote_port);}//2.command:SOCKET_ContextID=<contextID>Ql_memset(pData,0,sizeof(pData)); //pData数据缓存区清零Ql_sprintf((char *)pData,"command:SOCKET_ContextID=<%d>\r\n",socekt_information->contextid);//APP_DEBUG("%s\r\n",pData);p = Ql_strstr(pData,"SOCKET_ContextID=");if (p){Ql_memset(temp_buffer, 0, TEMP_BUFFER_LENGTH);if (Analyse_Command(pData, 1, '>', temp_buffer)){//APP_DEBUG("SOCKET Contextid Parameter Error.\r\n");return QL_NO; //SOCKET_ContextID错误,返回NO}socket_param_t.contextID = Ql_atoi(temp_buffer);//APP_DEBUG("Set Contextid Parameter Successfully<%d>\r\n",socket_param_t.contextID);}//3.command:SOCKET_ServiceType=<service_type>Ql_memset(pData,0,sizeof(pData)); //pData数据缓存区清零Ql_sprintf((char *)pData,"SOCKET_ServiceType=<%d>\r\n",socekt_information->servicetype);//APP_DEBUG("%s\r\n",pData);p = Ql_strstr(pData,"SOCKET_ServiceType=");if (p){Ql_memset(temp_buffer, 0, TEMP_BUFFER_LENGTH);if (Analyse_Command(pData, 1, '>', temp_buffer)){//APP_DEBUG("SOCKET service type Parameter Error.\r\n");return QL_NO; //SOCKET_Service类型错误,返回NO}socket_param_t.service_type = Ql_atoi(temp_buffer);//APP_DEBUG("Set service type Parameter Successfully<%d>\r\n",socket_param_t.service_type);}//4.command:SOCKET_LocalPort=<local_port>Ql_memset(pData,0,sizeof(pData)); //pData数据缓存区清零Ql_sprintf((char *)pData,"command:SOCKET_LocalPort=<%d>\r\n",socekt_information->localport);//APP_DEBUG("%s\r\n",pData);p = Ql_strstr(pData,"SOCKET_LocalPort=");if (p){Ql_memset(temp_buffer, 0, TEMP_BUFFER_LENGTH);if (Analyse_Command(pData, 1, '>', temp_buffer)){//APP_DEBUG("SOCKET local port Parameter Error.\r\n");return QL_NO; //socket本地端口错误,返回NO}socket_param_t.local_port = Ql_atoi(temp_buffer);//APP_DEBUG("Set local port Parameter Successfully<%d>\r\n",socket_param_t.local_port);}//5.command:SOCKET_ProtocolType=<protocol_type>Ql_memset(pData,0,sizeof(pData)); //pData数据缓存区清零Ql_sprintf((char *)pData,"SOCKET_ProtocolType=<%d>\r\n",socekt_information->protocoltype);//APP_DEBUG("%s\r\n",pData);p = Ql_strstr(pData,"SOCKET_ProtocolType=");if (p){Ql_memset(temp_buffer, 0, TEMP_BUFFER_LENGTH);if (Analyse_Command(pData, 1, '>', temp_buffer)){//APP_DEBUG("SOCKET protocol type Parameter Error.\r\n");return QL_NO; //protocol类型错误,返回NO}socket_param_t.protocol_type = Ql_atoi(temp_buffer);//APP_DEBUG("Set protocol type Parameter Successfully<%d>\r\n",socket_param_t.protocol_type);}//6.command:SOCKET_Open=<connectid>Ql_memset(pData,0,sizeof(pData)); //pData数据缓存区清零Ql_sprintf((char *)pData,"SOCKET_Open=<%d>\r\n",socekt_information->connectid);//APP_DEBUG("%s\r\n",pData);p = Ql_strstr(pData,"SOCKET_Open=");if (p){Ql_memset(temp_buffer, 0, TEMP_BUFFER_LENGTH);if (Analyse_Command(pData, 1, '>', temp_buffer)){//APP_DEBUG("SOCKET Open Error.\r\n");return QL_NO; //socket打开错误,返回NO}socket_param_t.connectID = Ql_atoi(temp_buffer);ret = RIL_SOC_QIOPEN(&socket_param_t);if(ret == 0){ //APP_DEBUG("<--start to create socket,ret =%d -->\r\n",ret);}else{//APP_DEBUG("<--Open socket failure,ret=%d.-->\r\n",ret);return QL_NO; //socket打开失败,返回NO}}return QL_OK; //全部配置成功,返回OK
}
其中socekt_param_user_t结构体表示配置socket的相关参数,具体如下:
//定义该结构体表示socekt相关信息
typedef struct socekt_param_user
{u8 *ip_address; //IP地址u8 *port; //端口号u8 contextid; //context ID, range is 1-3u8 servicetype; // 选择服务类型 0: Start a TCP connection as a client,1: Start a UDP connection as a clientu8 localport; //The local port, range is 1-65535,if <local_port> is 0, then the local port will be assigned automatically, else the local port is assigned as specifiedu8 protocoltype; //0: IPv4,1: IPv6u8 connectid; //socket service index, range is 0-4
}socekt_param_user_t; //结构体类型重定义
调用Connect_To_SOCEKT函数建立socket连接部分代码如下:
//初始化结构体参数,作为形参传入
socekt_information.ip_address=IP_address; //IP=47.110.249.116
socekt_information.port=portnum; //port=8005
socekt_information.contextid=1; //contextid=1
socekt_information.servicetype=service_type; //servicetype: 1--->UDP 0---->TCP
socekt_information.localport=0; //localport=0: 0:自动分配
socekt_information.protocoltype=0; //protocoltype=0 0: IPv4,1: IPv6
socekt_information.connectid=0; //connectid=0
ret=Connect_To_SOCEKT(&socekt_information); //建立SOCEKT连接
Ql_Delay_ms(2000); //延时2S等待连接建立
if(ret==QL_OK) //判断返回是否成功
{APP_DEBUG("socket连接成功,进入透传模式\r\n"); //进入透传模式
}
else{APP_DEBUG("socket连接失败,请重新连接!!!\r\n"); //socket连接失败
}
4.socket连接成功后,发送数据
在建立socket连接之后,模组进入透传模式,直接调用相关API函数发送数据,具体函数及调用如下:
/**
* @brief UDP发送数据
*
* @param data_buffer:数据缓存区首地址
* len: 数据长度
* connectid: socket service index, range is 0-4,根据上面Connect_To_UDP()函数确定
* data_type: Data_String表示发送的是String类型数据
* Data_Hex表示发送的是Hex类型数据
*
* @return QL_NO 建立连接失败
* QL_OK 建立连接成功
*/
u8 SendData_SOCEKT(u8 *data_buffer,u32 len,u8 connectid,u8 data_type)
{s32 ret;//1.command: SOCKET_SENDDATA=<connectid>,<data> 发送String数据if(data_type==Data_String){ret = RIL_SOC_QISEND(connectid,len,data_buffer);if (ret == 0){//APP_DEBUG("<--Send data successfully->\r\n");}else{//APP_DEBUG("<--Send data failure,ret=%d.-->\r\n",ret);return QL_NO;}}//2.command: SOCKET_SENDDATAHEX=<connectid>,<data> 发送HEX串数据else if(data_type==Data_Hex){ret = RIL_SOC_QISENDEX(connectid,len/2,data_buffer);if (ret == 0){//APP_DEBUG("<--Send data successfully->\r\n"); }else{//APP_DEBUG("<--Send data failure,ret=%d.-->\r\n",ret);return QL_NO;}}return QL_OK; //发送成功,返回QL_OK
}
将串口接收到的数据直接通过模组透传出去:
Ql_memset(send_buffer, 0, sizeof(send_buffer)); //清空串口接收缓存区
HexArrayToString(pData,send_buffer,len); //将数据封装到send_buffer里面,数据长度扩大一倍
//APP_DEBUG("send_buffer=%s ,len=%d\r\n",send_buffer,len);
SendData_SOCEKT(send_buffer,len*2,socekt_information.connectid,Data_Hex); //发送数据HEX,最大为512字节
在透传模式下发送"+++"使模块退出透传模式。
5.在执行初始化工作时,注册了socket接收函数,下面实现它
在recv信息中包含相关头信息,这不是透传的信息,因此先使用指针操作去掉头部信息,只留下网络传回的的透传信息并copy到socket_recv_buffer缓存区中。然后直接通过串口传出,至此完成了一个完整的信息透传回路。
void callback_socket_recv(u8* buffer,u32 length)
{ u8 *p = NULL;u16 recv_len=0; //定义接收数据长度变量//APP_DEBUG("<--tcp buffer(%s),length(%d)-->\r\n",buffer,length); //原始数据p = Ql_strstr(buffer,"0,"); //获取p指针地址if(*(p+3)==','){recv_len=Ql_atoi((p+2)); //获得接收字节数Ql_memcpy(socket_recv_buffer,p+4,recv_len);}else {recv_len=Ql_atoi((p+2));Ql_memcpy(socket_recv_buffer,p+5,recv_len);}//APP_DEBUG("%s",socket_recv_buffer);Ql_UART_Write(UART_PORT0, socket_recv_buffer, recv_len );Ql_memset(socket_recv_buffer, 0, sizeof(socket_recv_buffer));
}
6.dns域名解析
有些时候我们需要进行dns域名解析,同样系统给我们提供了API函数,但是dns解析需要花费一定的时间,因此我们开启一个定时器做判断dns是否解析成功。
/**
* @brief 域名解析函数:
*
* @param *hostname:域名
*
* @return void
*/
void get_ip_by_hostname(u8 *hostname)
{//APP_DEBUG("hostname=%s\r\n",hostname); //输出服务器域名Ql_IpHelper_GetIPByHostName(0, hostname, Callback_GetIpByName); //进行域名解析//开启一个定时器Ql_Timer_Register(UDP_TIMER_ID, Callback_Timer, NULL);Ql_Timer_Start(UDP_TIMER_ID, UDP_TIMER_PERIOD, TRUE);
}static u8 m_ipaddress[IP_ADDR_LEN]; //存储DNS解析完成的IP值
//域名解析回调函数,在此函数中完成dns域名解析工作,并将最终解析出的ip地址存储在全局缓存区m_ipaddress中
void Callback_GetIpByName(u8 contexId,s32 errCode,u32 ipAddrCnt,u8* ipAddr)
{if (errCode == SOC_SUCCESS){Ql_memset(m_ipaddress, 0, IP_ADDR_LEN);Ql_memcpy(m_ipaddress, ipAddr, IP_ADDR_LEN);//APP_DEBUG("<-- %s:contexid=%d,error=%d,num_entry=%d,m_ipaddress(%s) -->\r\n", __func__, contexId,errCode,ipAddrCnt,m_ipaddress);success_flag=1; //DNS解析成功标志置位}
}//定时器中断服务函数,在其中来判断是否dns解析成功并执行相应的动作
static void Callback_Timer(u32 timerId, void* param)
{Ql_Timer_Stop(UDP_TIMER_ID); //停止定时器if(success_flag){success_flag=0; //DNS解析成功标志复位APP_DEBUG("CONFIG_CENTER0 SUCCESS,DNS IP ADDRESS IS:%s\r\n",m_ipaddress);}else{APP_DEBUG("dns failed!!!\r\n");}
}
五.总结
上述详细介绍了基于OpenCPU方案的BC26开发流程,其中开发的关键在于学会使用SDK提供的开发工具套件以及基于SDK提供的example开发自己的程序。上述并未给出完整的程序,只是针对一些关键点给出操作代码,最终的实现的功能为一个透传模块,根据主串口的命令进入透传模式并发送透传数据,发送+++退出透传模式。
基于OpenCPU方案的BC26 NB模组开发总结相关推荐
- 通过微信公众号远程控制设备STM32+NB模组方案
想要实现远程控制,无非就是三端的通信,发送设备端->服务器->接收设备端,服务器端可以选用一些常用的云服务器,阿里.百度等等.接收端就是实现控制的设备,发送设备端一般就是APP端.网页端等 ...
- NB模组选型及整体方案注意事项
一.NB模组选型考虑因素 NB模组的选型评估工作对于项目能否顺利实施至关重要.前期评估验证阶段若未做充分的工作,很可能项目进行到一半发现NB模组并不适合当前应用场景,造成项目时间和前期投入全部白费,实 ...
- NB-IOT(4)---移远NB-IOT BC26模块模组简介和实际应用方向详解
移远NB-IOT BC26模块模组简介和实际应用方向详解 继BC95和BC28之后,2017年底移远在杭州发布了基于MTK平台的新款NB-IOT BC26模组. BC26基于联发科MT2625芯片平台 ...
- PAJ7620手势传感器快速应用- -基于涂鸦CBU模组开发板
PAJ7620手势传感器快速驱动- -基于涂鸦CBU模组开发板 正在,或是想要开发物联网小产品的你是否会觉得一些常用的控制需要繁琐地掏出手机会让你的产品体验不佳?添加实体按键又显得太 low ?不妨来 ...
- NB模组RSRP按比例转换为CSQ范围信号
NB模组的信号强度指示为RSRP, 范围[-140, -44]: 但是GPRS模组信号强度指示多为CSQ, 范围[0, 31]: RSRP信号描述: RSRP是代表无线信号强度的关键参数,反映 ...
- NB模组(BC28/NB86-G)使用域名接入华为云方法
现象 截止目前(2020-05-21),移远NB模组BC28在使用域名的情况下无法接入华为云平台,利尔达的NB模组NB86-G使用域名接入未测试. 方法 通过AT指令进行域名解析,得到IP后使用IP接 ...
- 手把手教学电信NB模组使用OneOS FOTA
背景 由于电信NB模组限制,使用了电信物NB联网卡的设备在连接公网时需要经过电信的CTWing平台,此时设备如果要做 FOTA升级则需要经过CTWing平台配置转发才能使用OneOS FOTA平台实现 ...
- 关于物联网2G/3G/4G/5G/NB模组开机后模组AT指令无反应/模块开机失败/模块开机不响应AT指令等问题
一.背景 近些年物联网行业如日中天,越来越多的设备需要接入网络平台.因此无线通信模组便成为了连接物联网感知层和网络层的关键环节,属于底层硬件环节,具备其不可替代性,且无线通信模块与物联网终端存在一一对 ...
- NB模组中序列号,IMEI,IMSI,ICCID的含义(一些知识科普)
概述 下面简述关于NB模组中使用,IMEI,IMSI,ICCID的含义. 什么是序列号? 序列号是一串标识你手机出生证明以及身材特征的信息,甚至还可用来识别是否为官方翻新机. 你可以简单的将这一串数字 ...
- 涂鸦模组开发光照传感器
涂鸦模组开发光照传感器(OPT3006) 概述 涂鸦智能 视频教学 系统框架设计 OPT3006 超薄环境光传感器 TYZS5 模组 特点 PCB绘制 涂鸦零代码开发 涂鸦模组开发文章 最后 概述 亮 ...
最新文章
- Intellij IDEA 添加jar包
- 软件测试领域的中心化与去中心化
- java自动化开发_Java自动化开发指南
- Nosql数据库的四大分类
- mysql 获取子分类_MySQL 自定义函数获取一个分类的无限级子分类
- Ubuntu16.04 64位系统下安装百度云管家
- python集合和字典的区别_Python中的字典和集合
- VMware 提示”此虚拟机被配置为64位操作系统,然而,64位操作无法进行”
- 服务器怎么存储文件节省空间,超大空间云服务器文件存储
- Java中BigDecimal类型的加减乘除及大小比对
- centos7永久修改主机名
- 数据库-创建数据库-创建数据表
- 应用化工技术学计算机不,化工技术类包括哪些专业
- java100道逻辑题及答案_100道Java面试题收集整理及参考答案
- fileman命令的帮助+?
- Java工具类,随机生成(姓名,年龄,性别,密码,邮箱,地址,)
- [会员积分运营了解]各大主流电商平台会员及积分体系概况集合!
- Python小白的数学建模课-19.网络流优化问题
- 蓝桥杯第12届第三次模拟
- matlab里motor的符号,motor的用法总结大全
热门文章
- 【十次方】十次方项目介绍
- 需要实战项目的看过来: 黑马最新java《十次方》社交项目 请仔细看!
- 【转载】Altera官方资料整理
- 精密单点定位/PPP软件GAMP学习之一
- UNIX 类文件系统模拟实现
- 数据挖掘概念与技术学习笔记(1)
- c语言 2,有一函数: y= 写一程序,输入x,输出y值.,有一函数 ,编写一段程序,输入x的值,输出相应的y值....
- TrueCrypt编译记录
- TrueCrypt加密:TrueCrypt Mount卸载加密卷(3)
- 关于恶意DNS请求监控的一点小思路