MiniFly Firmware V1.1開源四軸程式碼分析二:atpk.c
指令格式:幀頭(AA+AF)+功能ID+資料長度+資料+校驗
校驗幀頭:AA+AF
功能ID: 表示該資料的意義,msgID分為 上行指令ID :upmsgID_e ;下行指令ID :downmsgID_e,這是兩個列舉變數
資料長度:dataLen
資料陣列:data 最大為30
atkpTxTask: ATKP 資料包傳送任務。 該任務主要是獲取 stabilizerTask 中的感測器資料、姿態資料、電機 PWM 輸出資料等資料以定週期傳送給 radiolinkTask 和 usblinkTxTask, 由這兩個任務分別傳送飛遙控器和上位機。
atkpRxAnlTask: ATKP 資料包接收處理任務。 該任務主要是處理遙控器和上位機發下來的資料包, 解析到的控制指令則傳送到 stabilizerTask 中去。
#include <string.h>
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include "atkp.h"
#include "radiolink.h"
#include "usblink.h"
#include "usbd_usr.h"
#include "stabilizer.h"
#include "motors.h"
#include "commander.h"
#include "flip.h"
#include "pm.h"
#include "pid.h"
#include "attitude_pid.h"
#include "sensors.h"
#include "position_pid.h"
#include "config_param.h"
#include "power_control.h"
#include "remoter_ctrl.h"
/*FreeRTOS相關標頭檔案*/
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "queue.h"
//資料拆分巨集定義
#define BYTE0(dwTemp) ( *( (u8 *)(&dwTemp) ) )
#define BYTE1(dwTemp) ( *( (u8 *)(&dwTemp) + 1) )
#define BYTE2(dwTemp) ( *( (u8 *)(&dwTemp) + 2) )
#define BYTE3(dwTemp) ( *( (u8 *)(&dwTemp) + 3) )
//資料返回週期時間(單位ms)
#define PERIOD_STATUS 30
#define PERIOD_SENSOR 10
#define PERIOD_RCDATA 40
#define PERIOD_POWER 100
#define PERIOD_MOTOR 40
#define PERIOD_SENSOR2 40
#define PERIOD_SPEED 50
#define ATKP_RX_QUEUE_SIZE 10 /*ATKP包接收佇列訊息個數*/
typedef struct
{
u16 roll;
u16 pitch;
u16 yaw;
u16 thrust;
}joystickFlyui16_t;
bool isConnect = false;
bool isInit = false;
bool flyable = false;
static joystickFlyui16_t rcdata;
static xQueueHandle rxQueue;
extern PidObject pidAngleRoll;
extern PidObject pidAnglePitch;
extern PidObject pidAngleYaw;
extern PidObject pidRateRoll;
extern PidObject pidRatePitch;
extern PidObject pidRateYaw;
//把ATKP包傳送到radiolink和usblink的“傳送佇列txQueue”
static void atkpSendPacket(atkp_t *p)
{
radiolinkSendPacket(p);//傳送到radiolink的“傳送佇列txQueue”
if(getusbConnectState())
{
usblinkSendPacket(p);//傳送到usblink的“傳送佇列txQueue”
}
}
/***************************傳送至匿名上位機指令******************************/
static void sendStatus(float roll, float pitch, float yaw, s32 alt, u8 fly_model, u8 armed)
{
u8 _cnt=0;
atkp_t p;
vs16 _temp;
vs32 _temp2 = alt;
p.msgID = UP_STATUS;
_temp = (int)(roll*100);
p.data[_cnt++]=BYTE1(_temp);
p.data[_cnt++]=BYTE0(_temp);
_temp = (int)(pitch*100);
p.data[_cnt++]=BYTE1(_temp);
p.data[_cnt++]=BYTE0(_temp);
_temp = (int)(yaw*100);
p.data[_cnt++]=BYTE1(_temp);
p.data[_cnt++]=BYTE0(_temp);
p.data[_cnt++]=BYTE3(_temp2);
p.data[_cnt++]=BYTE2(_temp2);
p.data[_cnt++]=BYTE1(_temp2);
p.data[_cnt++]=BYTE0(_temp2);
p.data[_cnt++] = fly_model;
p.data[_cnt++] = armed;
p.dataLen = _cnt;
atkpSendPacket(&p);//把ATKP包傳送到radiolink和usblink的“傳送佇列txQueue”
}
//傳送MPU九軸感測器資料
static void sendSenser(s16 a_x,s16 a_y,s16 a_z,s16 g_x,s16 g_y,s16 g_z,s16 m_x,s16 m_y,s16 m_z)
{
u8 _cnt=0;
atkp_t p;
vs16 _temp;
p.msgID = UP_SENSER;
_temp = a_x;
p.data[_cnt++]=BYTE1(_temp);
p.data[_cnt++]=BYTE0(_temp);
_temp = a_y;
p.data[_cnt++]=BYTE1(_temp);
p.data[_cnt++]=BYTE0(_temp);
_temp = a_z;
p.data[_cnt++]=BYTE1(_temp);
p.data[_cnt++]=BYTE0(_temp);
_temp = g_x;
p.data[_cnt++]=BYTE1(_temp);
p.data[_cnt++]=BYTE0(_temp);
_temp = g_y;
p.data[_cnt++]=BYTE1(_temp);
p.data[_cnt++]=BYTE0(_temp);
_temp = g_z;
p.data[_cnt++]=BYTE1(_temp);
p.data[_cnt++]=BYTE0(_temp);
_temp = m_x;
p.data[_cnt++]=BYTE1(_temp);
p.data[_cnt++]=BYTE0(_temp);
_temp = m_y;
p.data[_cnt++]=BYTE1(_temp);
p.data[_cnt++]=BYTE0(_temp);
_temp = m_z;
p.data[_cnt++]=BYTE1(_temp);
p.data[_cnt++]=BYTE0(_temp);
_temp = 0;
p.data[_cnt++]=BYTE1(_temp);
p.data[_cnt++]=BYTE0(_temp);
p.dataLen = _cnt;
atkpSendPacket(&p);//把ATKP包傳送到radiolink和usblink的“傳送佇列txQueue”
}
static void sendRCData(u16 thrust,u16 yaw,u16 roll,u16 pitch,u16 aux1,u16 aux2,u16 aux3,u16 aux4,u16 aux5,u16 aux6)
{
u8 _cnt=0;
atkp_t p;
p.msgID = UP_RCDATA;
p.data[_cnt++]=BYTE1(thrust);
p.data[_cnt++]=BYTE0(thrust);
p.data[_cnt++]=BYTE1(yaw);
p.data[_cnt++]=BYTE0(yaw);
p.data[_cnt++]=BYTE1(roll);
p.data[_cnt++]=BYTE0(roll);
p.data[_cnt++]=BYTE1(pitch);
p.data[_cnt++]=BYTE0(pitch);
p.data[_cnt++]=BYTE1(aux1);
p.data[_cnt++]=BYTE0(aux1);
p.data[_cnt++]=BYTE1(aux2);
p.data[_cnt++]=BYTE0(aux2);
p.data[_cnt++]=BYTE1(aux3);
p.data[_cnt++]=BYTE0(aux3);
p.data[_cnt++]=BYTE1(aux4);
p.data[_cnt++]=BYTE0(aux4);
p.data[_cnt++]=BYTE1(aux5);
p.data[_cnt++]=BYTE0(aux5);
p.data[_cnt++]=BYTE1(aux6);
p.data[_cnt++]=BYTE0(aux6);
p.dataLen = _cnt;
atkpSendPacket(&p);//把ATKP包傳送到radiolink和usblink的“傳送佇列txQueue”
}
static void sendPower(u16 votage, u16 current)
{
u8 _cnt=0;
atkp_t p;
p.msgID = UP_POWER;
p.data[_cnt++]=BYTE1(votage);
p.data[_cnt++]=BYTE0(votage);
p.data[_cnt++]=BYTE1(current);
p.data[_cnt++]=BYTE0(current);
p.dataLen = _cnt;
atkpSendPacket(&p);//把ATKP包傳送到radiolink和usblink的“傳送佇列txQueue”
}
static void sendMotorPWM(u16 m_1,u16 m_2,u16 m_3,u16 m_4,u16 m_5,u16 m_6,u16 m_7,u16 m_8)
{
u8 _cnt=0;
atkp_t p;
p.msgID = UP_MOTOR;
p.data[_cnt++]=BYTE1(m_1);
p.data[_cnt++]=BYTE0(m_1);
p.data[_cnt++]=BYTE1(m_2);
p.data[_cnt++]=BYTE0(m_2);
p.data[_cnt++]=BYTE1(m_3);
p.data[_cnt++]=BYTE0(m_3);
p.data[_cnt++]=BYTE1(m_4);
p.data[_cnt++]=BYTE0(m_4);
p.data[_cnt++]=BYTE1(m_5);
p.data[_cnt++]=BYTE0(m_5);
p.data[_cnt++]=BYTE1(m_6);
p.data[_cnt++]=BYTE0(m_6);
p.data[_cnt++]=BYTE1(m_7);
p.data[_cnt++]=BYTE0(m_7);
p.data[_cnt++]=BYTE1(m_8);
p.data[_cnt++]=BYTE0(m_8);
p.dataLen = _cnt;
atkpSendPacket(&p);//把ATKP包傳送到radiolink和usblink的“傳送佇列txQueue”
}
static void sendSenser2(s32 bar_alt,u16 csb_alt)//傳送氣壓計資訊
{
u8 _cnt=0;
atkp_t p;
p.msgID = UP_SENSER2;
p.data[_cnt++]=BYTE3(bar_alt);
p.data[_cnt++]=BYTE2(bar_alt);
p.data[_cnt++]=BYTE1(bar_alt);
p.data[_cnt++]=BYTE0(bar_alt);
p.data[_cnt++]=BYTE1(csb_alt);
p.data[_cnt++]=BYTE0(csb_alt);
p.dataLen = _cnt;
atkpSendPacket(&p);//把ATKP包傳送到radiolink和usblink的“傳送佇列txQueue”
}
static void sendPid(u8 group,float p1_p,float p1_i,float p1_d,float p2_p,float p2_i,float p2_d,float p3_p,float p3_i,float p3_d)
{
u8 _cnt=0;
atkp_t p;
vs16 _temp;
p.msgID = 0x10+group-1;
_temp = p1_p * 10 ;
p.data[_cnt++]=BYTE1(_temp);
p.data[_cnt++]=BYTE0(_temp);
_temp = p1_i * 10 ;
p.data[_cnt++]=BYTE1(_temp);
p.data[_cnt++]=BYTE0(_temp);
_temp = p1_d * 10 ;
p.data[_cnt++]=BYTE1(_temp);
p.data[_cnt++]=BYTE0(_temp);
_temp = p2_p * 10 ;
p.data[_cnt++]=BYTE1(_temp);
p.data[_cnt++]=BYTE0(_temp);
_temp = p2_i * 10 ;
p.data[_cnt++]=BYTE1(_temp);
p.data[_cnt++]=BYTE0(_temp);
_temp = p2_d * 10 ;
p.data[_cnt++]=BYTE1(_temp);
p.data[_cnt++]=BYTE0(_temp);
_temp = p3_p * 10 ;
p.data[_cnt++]=BYTE1(_temp);
p.data[_cnt++]=BYTE0(_temp);
_temp = p3_i * 10 ;
p.data[_cnt++]=BYTE1(_temp);
p.data[_cnt++]=BYTE0(_temp);
_temp = p3_d * 10 ;
p.data[_cnt++]=BYTE1(_temp);
p.data[_cnt++]=BYTE0(_temp);
p.dataLen = _cnt;
atkpSendPacket(&p);//把ATKP包傳送到radiolink和usblink的“傳送佇列txQueue”
}
static void sendCheck(u8 head, u8 check_sum)
{
atkp_t p;
p.msgID = UP_CHECK;
p.dataLen = 2;
p.data[0] = head;
p.data[1] = check_sum;
atkpSendPacket(&p);//把ATKP包傳送到radiolink和usblink的“傳送佇列txQueue”
}
/****************************************************************************/
/*資料週期性傳送給上位機,每1ms呼叫一次*/
static void atkpSendPeriod(void)
{
static u16 count_ms = 1;
//if(!()) ,只有當裡面的為0時,!0就是真。
if(!(count_ms % PERIOD_STATUS))//count_ms/30ms,每30ms傳送一次
{
attitude_t attitude;
getAttitudeData(&attitude);
int positionZ = getPositionZ() * 100.f;
sendStatus(attitude.roll, attitude.pitch, attitude.yaw, positionZ, 0, flyable); //傳送狀態
}
if(!(count_ms % PERIOD_SENSOR))//count_ms/10ms,每10ms傳送一次MPU資料
{
Axis3i16 acc;
Axis3i16 gyro;
Axis3i16 mag;
getSensorRawData(&acc, &gyro, &mag);
sendSenser(acc.x, acc.y, acc.z, gyro.x, gyro.y, gyro.z, mag.x, mag.y, mag.z);//傳送MPU九軸感測器資料
}
if(!(count_ms % PERIOD_RCDATA))//count_ms/40ms,每40ms傳送一次遙控資料(油門,偏航,翻滾,俯仰)
{
sendRCData(rcdata.thrust, rcdata.yaw, rcdata.roll,
rcdata.pitch, 0, 0, 0, 0, 0, 0);//傳送遙控器的資料
}
if(!(count_ms % PERIOD_POWER))//count_ms/100ms,每100ms傳送一次電壓資料
{
float bat = pmGetBatteryVoltage();
sendPower(bat*100,500);//傳送電量資訊
}
if(!(count_ms % PERIOD_MOTOR))//count_ms/40ms,每40ms傳送一次電機PWMxxx資料
{
u16 m1,m2,m3,m4;
motorPWM_t motorPWM;
getMotorPWM(&motorPWM);
m1 = (float)motorPWM.m1/65535*1000;
m2 = (float)motorPWM.m2/65535*1000;
m3 = (float)motorPWM.m3/65535*1000;
m4 = (float)motorPWM.m4/65535*1000;
sendMotorPWM(m1,m2,m3,m4,0,0,0,0);
}
if(!(count_ms % PERIOD_SENSOR2))//count_ms/40ms,每40ms傳送一次氣壓計資料
{
int baro = getBaroData() * 100.f;
sendSenser2(baro,0);//傳送氣壓計資訊
}
if(++count_ms>=65535)
count_ms = 1;
}
//將幀頭,地址,幀資料部分的長度,每個資料求和作為校驗
static u8 atkpCheckSum(atkp_t *packet)//分析atkp_t結構體(包),得出校驗值
{
u8 sum;
sum = DOWN_BYTE1;/*下行幀頭*/
sum += DOWN_BYTE2;
sum += packet->msgID;//幀地址
sum += packet->dataLen;//幀資料部分的長度
for(int i=0; i<packet->dataLen; i++)
{
sum += packet->data[i];
}
return sum;
}
//解包,根據msgID==下行指令ID(列舉型別)和data[0]做出處理
static void atkpReceiveAnl(atkp_t *anlPacket)
{
if(anlPacket->msgID == DOWN_COMMAND)//DOWN_COMMAND = 0x01,下行指令
{
switch(anlPacket->data[0])
{
case D_COMMAND_ACC_CALIB:
break;
case D_COMMAND_GYRO_CALIB:
break;
case D_COMMAND_MAG_CALIB:
break;
case D_COMMAND_BARO_CALIB:
break;
//上面的命令直接退出switch
case D_COMMAND_FLIGHT_LOCK:
flyable = false;//不能飛,上鎖
break;
case D_COMMAND_FLIGHT_ULOCK:
flyable = true;//能飛,解鎖
}
}
else if(anlPacket->msgID == DOWN_ACK)//DOWN_ACK = 0x02,
{
if(anlPacket->data[0] == D_ACK_READ_PID)/*讀取PID引數*/
{
sendPid(1,pidRateRoll.kp, pidRateRoll.ki, pidRateRoll.kd,
pidRatePitch.kp, pidRatePitch.ki, pidRatePitch.kd,
pidRateYaw.kp, pidRateYaw.ki, pidRateYaw.kd
);
sendPid(2,pidAngleRoll.kp, pidAngleRoll.ki, pidAngleRoll.kd,
pidAnglePitch.kp, pidAnglePitch.ki, pidAnglePitch.kd,
pidAngleYaw.kp, pidAngleYaw.ki, pidAngleYaw.kd
);
float kp, ki, kd;
getPositionPIDZ(&kp, &ki, &kd);
sendPid(3,kp,ki,kd,0,0,0,0,0,0);
}
if(anlPacket->data[0] == D_ACK_RESET_PARAM)/*恢復預設引數*/
{
resetConfigParamPID();
positionControlInit();
attitudeControlInit();
sendPid(1,pidRateRoll.kp, pidRateRoll.ki, pidRateRoll.kd,
pidRatePitch.kp, pidRatePitch.ki, pidRatePitch.kd,
pidRateYaw.kp, pidRateYaw.ki, pidRateYaw.kd
);
sendPid(2,pidAngleRoll.kp, pidAngleRoll.ki, pidAngleRoll.kd,
pidAnglePitch.kp, pidAnglePitch.ki, pidAnglePitch.kd,
pidAngleYaw.kp, pidAngleYaw.ki, pidAngleYaw.kd
);
float kp, ki, kd;
getPositionPIDZ(&kp, &ki, &kd);
sendPid(3,kp,ki,kd,0,0,0,0,0,0);
}
}
else if(anlPacket->msgID == DOWN_RCDATA)
{
rcdata = *((joystickFlyui16_t*)anlPacket->data);
}
else if(anlPacket->msgID == DOWN_POWER)/*nrf51822*/
{
pmSyslinkUpdate(anlPacket);
}
else if(anlPacket->msgID == DOWN_REMOTER)/*遙控器*/
{
remoterCtrlProcess(anlPacket);
}
else if(anlPacket->msgID == DOWN_PID1)//線上PID除錯
{
pidRateRoll.kp = 0.1*((s16)(*(anlPacket->data+0)<<8)|*(anlPacket->data+1));
pidRateRoll.ki = 0.1*((s16)(*(anlPacket->data+2)<<8)|*(anlPacket->data+3));
pidRateRoll.kd = 0.1*((s16)(*(anlPacket->data+4)<<8)|*(anlPacket->data+5));
pidRatePitch.kp = 0.1*((s16)(*(anlPacket->data+6)<<8)|*(anlPacket->data+7));
pidRatePitch.ki = 0.1*((s16)(*(anlPacket->data+8)<<8)|*(anlPacket->data+9));
pidRatePitch.kd = 0.1*((s16)(*(anlPacket->data+10)<<8)|*(anlPacket->data+11));
pidRateYaw.kp = 0.1*((s16)(*(anlPacket->data+12)<<8)|*(anlPacket->data+13));
pidRateYaw.ki = 0.1*((s16)(*(anlPacket->data+14)<<8)|*(anlPacket->data+15));
pidRateYaw.kd = 0.1*((s16)(*(anlPacket->data+16)<<8)|*(anlPacket->data+17));
u8 cksum = atkpCheckSum(anlPacket);
sendCheck(anlPacket->msgID,cksum);
}
else if(anlPacket->msgID == DOWN_PID2)
{
pidAngleRoll.kp = 0.1*((s16)(*(anlPacket->data+0)<<8)|*(anlPacket->data+1));
pidAngleRoll.ki = 0.1*((s16)(*(anlPacket->data+2)<<8)|*(anlPacket->data+3));
pidAngleRoll.kd = 0.1*((s16)(*(anlPacket->data+4)<<8)|*(anlPacket->data+5));
pidAnglePitch.kp = 0.1*((s16)(*(anlPacket->data+6)<<8)|*(anlPacket->data+7));
pidAnglePitch.ki = 0.1*((s16)(*(anlPacket->data+8)<<8)|*(anlPacket->data+9));
pidAnglePitch.kd = 0.1*((s16)(*(anlPacket->data+10)<<8)|*(anlPacket->data+11));
pidAngleYaw.kp = 0.1*((s16)(*(anlPacket->data+12)<<8)|*(anlPacket->data+13));
pidAngleYaw.ki = 0.1*((s16)(*(anlPacket->data+14)<<8)|*(anlPacket->data+15));
pidAngleYaw.kd = 0.1*((s16)(*(anlPacket->data+16)<<8)|*(anlPacket->data+17));
u8 cksum = atkpCheckSum(anlPacket);
sendCheck(anlPacket->msgID,cksum);
}
else if(anlPacket->msgID == DOWN_PID3)
{
float kp = 0.1*((s16)(*(anlPacket->data+0)<<8)|*(anlPacket->data+1));
float ki = 0.1*((s16)(*(anlPacket->data+2)<<8)|*(anlPacket->data+3));
float kd = 0.1*((s16)(*(anlPacket->data+4)<<8)|*(anlPacket->data+5));
setPositionPIDZ(kp, ki, kd);
u8 cksum = atkpCheckSum(anlPacket);
sendCheck(anlPacket->msgID,cksum);
}
else if(anlPacket->msgID == DOWN_PID4)
{
u8 cksum = atkpCheckSum(anlPacket);
sendCheck(anlPacket->msgID,cksum);
}
else if(anlPacket->msgID == DOWN_PID5)
{
u8 cksum = atkpCheckSum(anlPacket);
sendCheck(anlPacket->msgID,cksum);
}
else if(anlPacket->msgID == DOWN_PID6)
{
// s16 temp1 = ((s16)(*(anlPacket->data+0)<<8)|*(anlPacket->data+1));
// s16 temp2 = ((s16)(*(anlPacket->data+2)<<8)|*(anlPacket->data+3));
// s16 temp3 = ((s16)(*(anlPacket->data+4)<<8)|*(anlPacket->data+5));
s16 enable = ((s16)(*(anlPacket->data+6)<<8)|*(anlPacket->data+7));
s16 m1_set = ((s16)(*(anlPacket->data+8)<<8)|*(anlPacket->data+9));
s16 m2_set = ((s16)(*(anlPacket->data+10)<<8)|*(anlPacket->data+11));
s16 m3_set = ((s16)(*(anlPacket->data+12)<<8)|*(anlPacket->data+13));
s16 m4_set = ((s16)(*(anlPacket->data+14)<<8)|*(anlPacket->data+15));
setMotorPWM(enable,m1_set,m2_set,m3_set,m4_set);
attitudePIDwriteToConfigParam();
u8 cksum = atkpCheckSum(anlPacket);
sendCheck(anlPacket->msgID,cksum);
}
}
void atkpTxTask(void *param)
{
sendMsgACK();//返回四軸資訊
while(1)
{
atkpSendPeriod();//資料週期性傳送給上位機,每1ms呼叫一次
vTaskDelay(1);
}
}
//ATKP 資料包接收處理任務,主要是處理遙控器和上位機發下來的資料包,\
解析到的控制指令則傳送到 stabilizerTask 中去
void atkpRxAnlTask(void *param)
{
atkp_t p;
while(1)
{
xQueueReceive(rxQueue, &p, portMAX_DELAY);//從“接收佇列rxQueue”提取一個包,存入緩衝區p
atkpReceiveAnl(&p);//雙向值傳遞,分析處理包
}
}
void atkpInit(void)//檢測是否建立了“接收佇列”,沒有則建立
{
if(isInit) return;
rxQueue = xQueueCreate(ATKP_RX_QUEUE_SIZE, sizeof(atkp_t));
ASSERT(rxQueue);
isInit = true;
}
bool atkpReceivePacketBlocking(atkp_t *p)//接收ATKP包,帶阻塞(死等),\
只被usblinkRxTask和atkpPacketDispatch呼叫
{
ASSERT(p);
ASSERT(p->dataLen <= ATKP_MAX_DATA_SIZE);
return xQueueSend(rxQueue, p, portMAX_DELAY);
}
流程圖:
相關文章
- 正點原子Minifly遙控器V1.1—程式碼分析:radiolink.c、usblink.c、atpk.c三個檔案之間的關係
- 如何選擇開源許可協議(二):開放程式碼協議
- STM32F103 主控 DIY 組裝的 開源四軸飛行器
- 開源四足機器人 附設計圖及程式碼機器人
- Android開源原始碼分析Android原始碼
- 阿里開源superAGI程式碼分析【prompt部分】-核心還是react阿里React
- 開源小程式原始碼原始碼
- 四軸PID思路整理
- 四軸機械臂控制機械臂
- 使用SQL查詢Java程式碼分析的開源專案:JQLSQLJava
- ACProtect V1.1主程式
- 二維陣列程式碼案例分析陣列
- 鴻蒙 OS 程式碼正式開源!!鴻蒙
- 搶紅包案例分析以及程式碼實現(四)
- SQL隱碼攻擊原理及程式碼分析(二)SQL
- IPOD firmware 1.2.3 升級
- 第三週作業(二):分析程式碼
- ecshop 二次開發,原始碼分析原始碼
- preact原始碼分析(四)React原始碼
- openGauss不僅程式碼開源
- iOS 開源庫系列 Aspects核心原始碼分析iOS原始碼
- 走進開源專案 - urlcat 原始碼分析原始碼
- Android 開源專案PhotoView原始碼分析AndroidView原始碼
- 傳奇原始碼分析-客戶端(遊戲邏輯處理源分析二)原始碼客戶端遊戲
- 開源的四股力量
- 搶紅包案例分析以及程式碼實現(二)
- 開源閉源專案程式碼質量對比
- 阿里開源 iOS 協程開發框架 coobjc原始碼分析阿里iOS框架OBJ原始碼
- 【開源社】您會為開源專案貢獻程式碼嗎?
- EE4J程式碼開啟開源之旅
- 美團外賣開源路由框架 WMRouter 原始碼分析路由框架原始碼
- 開源專案Running Life 原始碼分析(一)原始碼
- 一刻社群程式碼開源啦
- 如何研究開源專案的程式碼?
- OkHttpClient原始碼分析(四)—— CacheInterceptorHTTPclient原始碼
- 開源無程式碼 / 低程式碼平臺 NocoBase 0.20:支援多資料來源
- 【全開源】AJAX家政系統原始碼小程式前後端開源原始碼原始碼後端
- 開源低程式碼平臺開發實踐二:從 0 構建一個基於 ER 圖的低程式碼後端後端