001 使用單片機實現的邏輯分析儀——吸收篇

本內容記錄于韋東山老師的畢設級開源學習項目,含個人觀點,請理性閱讀。

個人筆記,沒有套路,一步到位,歡迎交流!

00單片機的邏輯分析儀與商業版FPGA的邏輯分析儀異同

對比維度自制STM32邏輯分析儀商業版邏輯分析儀
采樣率通常較低(如1MHz,依賴STM32時鐘配置)極高(可達8GHz或更高)
通道數量較少(如8-16通道,受限于STM32引腳與資源)多(32-300通道以上,支持多總線同步采集)
存儲深度較淺(受STM32內存限制,如KB級)深(512MB或更大,支持長時間數據捕獲)
觸發功能基礎觸發(如電平觸發、簡單邏輯組合)復雜觸發(支持協議觸發、毛刺檢測、多條件組合觸發)
協議支持需依賴軟件解碼(如PulseView或自定義解析)內置多種協議解碼(SPI、I2C、UART等),支持自動化分析
實時性依賴CPU處理能力,實時性較差硬件加速處理,實時性高(如FPGA協處理)
成本低成本(約數百元,依賴STM32開發板與配件)高成本(數萬至數十萬元)
適用場景教育、開發調試、簡單信號分析工業級測試、復雜系統調試、高精度協議驗證
獨立性依賴上位機軟件(如USB傳輸數據)獨立運行(內置操作系統與存儲,無需外接設備)
擴展性可通過代碼自定義功能,靈活性高功能固化,但支持模塊化擴展(如增加探頭或協議庫)
信號保真度可能受STM32采樣精度與噪聲影響高精度采樣(支持電壓等級分析,非僅邏輯0/1)

01 上位機協議分析

1.PulseView協議分析

PulseView 作為一款開源邏輯分析儀軟件,是屬于sigrok開源軟件組織的產品,其協議支持和通信機制主要圍繞?硬件接口協議?和?軟件解碼協議?兩方面展開。以下是詳細分析:


一、硬件通信協議

PulseView 通過?Sigrok 底層庫libsigrok)與硬件設備通信,支持多種硬件接口協議,具體包括:

  1. SUMP協議/OLS(openbench-logic-sniffer)

也是韋東山老師的案例。

  1. FX2LAFW 協議
    • 用于基于 CY7C68013A 或 CBM9002A 芯片的邏輯分析儀(如 UINIO-Logic-24MHz、Saleae 克隆版),通過 USB 接口傳輸數據,需配合 Zadig 工具安裝驅動。
  2. 自定義固件協議
    • 支持通過修改硬件固件(如改變 USB 設備標識符)適配不同硬件,例如樹莓派 RP2040 板通過 Rust 編譯固件實現協議兼容。

二、軟件解碼協議

PulseView 依賴?Sigrok 解碼庫libsigrokdecode)實現協議解析,支持以下兩類協議:

  1. 硬件通信協議解碼
    • 常見協議:包括 I2C、SPI、UART、CAN、SD、1-Wire、IrDA 等 90+ 種協議,覆蓋數字信號的波形解析與數據提取。
    • 自定義協議:用戶可通過 Python 腳本擴展解碼功能,例如支持UFCS快充協議等定制化需求。
  2. 數據文件格式支持
    • VCD 文件:支持導入 Verilog 仿真生成的 VCD 波形文件,用于數字設計驗證。
    • 二進制數據:通過?sigrok-cli?導出采樣數據,支持 CSV、WAV 等格式

2.SUMP協議/OLS(openbench-logic-sniffer)

上位機發送的命令和數據有:

其中0x82:

#define CAPTURE_FLAG_RLEMODE1            (1 << 15)
#define CAPTURE_FLAG_RLEMODE0            (1 << 14)
#define CAPTURE_FLAG_RESERVED1           (1 << 13)
#define CAPTURE_FLAG_RESERVED0           (1 << 12)
#define CAPTURE_FLAG_INTERNAL_TEST_MODE  (1 << 11)
#define CAPTURE_FLAG_EXTERNAL_TEST_MODE  (1 << 10)
#define CAPTURE_FLAG_SWAP_CHANNELS       (1 << 9)
#define CAPTURE_FLAG_RLE                 (1 << 8)
#define CAPTURE_FLAG_INVERT_EXT_CLOCK    (1 << 7)
#define CAPTURE_FLAG_CLOCK_EXTERNAL      (1 << 6)
#define CAPTURE_FLAG_DISABLE_CHANGROUP_4 (1 << 5)
#define CAPTURE_FLAG_DISABLE_CHANGROUP_3 (1 << 4)
#define CAPTURE_FLAG_DISABLE_CHANGROUP_2 (1 << 3)
#define CAPTURE_FLAG_DISABLE_CHANGROUP_1 (1 << 2)
#define CAPTURE_FLAG_NOISE_FILTER        (1 << 1)
#define CAPTURE_FLAG_DEMUX               (1 << 0)
#define CMD_RESET                     0x00
#define CMD_ARM_BASIC_TRIGGER         0x01
#define CMD_ID                        0x02
#define CMD_METADATA                  0x04
#define CMD_FINISH_NOW                0x05 /* extension of Demon Core */
#define CMD_QUERY_INPUT_DATA          0x06 /* extension of Demon Core */
#define CMD_QUERY_CAPTURE_STATE       0x07 /* extension of Demon Core */
#define CMD_RETURN_CAPTURE_DATA       0x08 /* extension of Demon Core */
#define CMD_ARM_ADVANCED_TRIGGER      0x0F /* extension of Demon Core */
#define CMD_XON                       0x11
#define CMD_XOFF                      0x13
#define CMD_SET_DIVIDER               0x80
#define CMD_CAPTURE_SIZE              0x81
#define CMD_SET_FLAGS                 0x82
#define CMD_CAPTURE_DELAYCOUNT        0x83 /* extension of Pepino */
#define CMD_CAPTURE_READCOUNT         0x84 /* extension of Pepino */
#define CMD_SET_ADVANCED_TRIG_SEL     0x9E /* extension of Demon Core */
#define CMD_SET_ADVANCED_TRIG_WRITE   0x9F /* extension of Demon Core */
#define CMD_SET_BASIC_TRIGGER_MASK0   0xC0 /* 4 stages: 0xC0, 0xC4, 0xC8, 0xCC */
#define CMD_SET_BASIC_TRIGGER_VALUE0  0xC1 /* 4 stages: 0xC1, 0xC5, 0xC9, 0xCD */
#define CMD_SET_BASIC_TRIGGER_CONFIG0 0xC2 /* 4 stages: 0xC2, 0xC6, 0xCA, 0xCE */

02 下位機通信分析

1.對上位機的響應(流程分析):

?1. 掃描連接設備階段

我們使用串口接收中斷,不斷循環檢測接收到的命令或者數據:
  	while (1){  if (uart_recv(&c, TIMEOUT_FOREVER) == 0){cmd_buffer[cmd_index] = c;	switch (cmd_buffer[0]){
  • ?連接設備時:上位機(PulseView)會先發送復位命令(CMD_RESET)??(命令碼0x00),用于初始化下位機狀態并清除緩存
                    case CMD_RESET:{break;}
  • 應答回復:上位機(PulseView)會先發送CMD_ID 命令(“0x02”),下位機要回復 4 個字節“1ALS”。
                    case CMD_ID:{/* 上報4個字節"1ALS" */                uart_send((uint8_t *)"1ALS", 4, TIMEOUT_DEFAULT);break;}
  • ?設備識別:隨后發送查詢設備信息命令?(命令碼0x04),要求下位機返回設備固件版本、支持的通道數和最大采樣率等信息。獲取下位機的默認配置(如內存大小、支持的觸發模式),
    ?下位機要回復的參數格式為“1 字節的數據類別,多個字節的數據”,說明如下:
                    case CMD_METADATA:{uint32_t virtual_bufferSize = getBufferSize();uint32_t maxFrequency = getMaxFrequency();/* 上報參數 */ // 一個字節的發送,且最大等待時間TIMEOUT_DEFAULT=100ms//NAME   send_byte(0x01, TIMEOUT_DEFAULT);send_string("100ASK_LogicalNucleo", TIMEOUT_DEFAULT);send_byte(0x00, TIMEOUT_DEFAULT);//SAMPLE MEMsend_byte(0x21, TIMEOUT_DEFAULT);send_uint32(virtual_bufferSize, TIMEOUT_DEFAULT);//DYNAMIC MEMsend_byte(0x22, TIMEOUT_DEFAULT);send_uint32(0, TIMEOUT_DEFAULT);//SAMPLE RATEsend_byte(0x23, TIMEOUT_DEFAULT);send_uint32(maxFrequency, TIMEOUT_DEFAULT);//Number of Probessend_byte(0x40, TIMEOUT_DEFAULT);send_byte(8, TIMEOUT_DEFAULT);//Protocol Versionsend_byte(0x41, TIMEOUT_DEFAULT);send_byte(0x02, TIMEOUT_DEFAULT);//ENDsend_byte(0x00, TIMEOUT_DEFAULT);break;}//大端序處理:將32位整數拆分為4字節,按高位優先(MSB first)傳輸
    static int send_uint32(uint32_t val, int timeout)
    {uint8_t buffer[4]; // 用于存儲整數的各個字節  buffer[3] = BYTE0(val);  buffer[2] = BYTE1(val);  buffer[1] = BYTE2(val);  buffer[0] = BYTE3(val); uart_send(buffer, 4, timeout);return 0;
    }
    //為什么要這么處理呢?
    /*
    1.?大端序轉換,統一字節順序避免端序歧義(無論內存大端小端)
    2.強制4字節長度避免粘包問題
    */
    static int send_string(char *str, int timeout)
    {return uart_send((uint8_t *)str, strlen(str), timeout);
    }

2. 參數配置階段

?觸發配置,上位機發送觸發掩碼和值?,定義觸發信號的邏輯條件:

  • ?使能觸發:通過0xC0命令設置通道使能
    				case CMD_SET_BASIC_TRIGGER_MASK0:{cmd_index++;if(cmd_index < 5)//需要 ?5字節數據?(1字節命令 + 4字節掩碼),小于5,則跳過后//續代碼(continue),繼續接收數據。當 cmd_index 達到5時,說明已接收完整數據。continue;setTriggerMask(*(uint32_t *)(cmd_buffer + 1));//取首地址+1及后32位break;}

  • ?觸發條件:通過0xC1系列命令設置觸發值(如高\低電平觸發)
    				case CMD_SET_BASIC_TRIGGER_VALUE0:{cmd_index++;if(cmd_index < 5)continue;setTriggerValue(*(uint32_t *)(cmd_buffer + 1));break;}

  • 通道使能:通過0xC2命令啟動觸發(如0x00 0x00 0x00?0x08中最后一個字節的bit3=1表示啟動觸發)
    				case CMD_SET_BASIC_TRIGGER_CONFIG0:{cmd_index++;if(cmd_index < 5)continue;uint8_t serial = (*((uint8_t*)(cmd_buffer + 4)) & 0x04) > 0 ? 1 : 0;uint8_t state = (*((uint8_t*)(cmd_buffer + 4)) & 0x08) > 0 ? 1 : 0;if(serial == 1)setTriggerState(0);//Not supportedelsesetTriggerState(state);break;}
?設置采樣參數,上位機通過命令組?配置以下參數:

采樣次數與采樣前舍去次數設置:?

  • ?采樣率:通過0x80命令是根據設置的采樣頻率算出分頻系數,例如X=(100Mhz/200Khz)-1 》可得X=499》》0xf3 0x01 0x00 0x00表示0000 01f3=499(其中采樣頻率<100Mhz時按100Mhz算? ? ? ? ? ? ? ? >100Mhz時按200Mhz算)
    				case CMD_SET_DIVIDER: {cmd_index++;if(cmd_index < 5)continue;uint32_t divider = *((uint32_t *)(cmd_buffer + 1));setSamplingDivider(divider);break;}static void setSamplingDivider (uint32_t divider)
    {int f = 100000000 / (divider + 1);if(f > MAX_FREQUENCY)f = MAX_FREQUENCY;g_samplingRate = f;
    }

當下位機buffer超過256k,采樣次數與采樣前舍去次數分開下發

  • ?采樣次數:?通過0x84命令+32位數據表示
  • 采樣前舍去次數(延時次數):?通過0x83命令+32位數表示
    				case CMD_CAPTURE_DELAYCOUNT://83{cmd_index++;if(cmd_index < 5)continue;uint32_t delayCount  = *((uint32_t*)(cmd_buffer + 1));setSamplingDelay(4 * delayCount);								break;}									case CMD_CAPTURE_READCOUNT://84{cmd_index++;if(cmd_index < 5)continue;uint32_t readCount  = *((uint32_t*)(cmd_buffer + 1));setSampleNumber(4 * readCount);								break;}

當下位機buffer小于256k,前兩位表示采樣次數后兩位表示采樣前舍去次數

  • 采樣次數和延時次數:?通過0x81命令+32位數表示
    				case CMD_CAPTURE_SIZE:{cmd_index++;if(cmd_index < 5)continue;uint16_t readCount  = *((uint16_t*)(cmd_buffer + 1));uint16_t delayCount = * ((uint16_t*)(cmd_buffer + 3));setSampleNumber(4 * readCount);setSamplingDelay(4 * delayCount);break;}

  • 通道使能:通過0x82命令啟用/禁用特定通道
    				case CMD_SET_FLAGS:{cmd_index++;if(cmd_index < 5)continue;setFlags(*(uint32_t *)(cmd_buffer + 1));break;}

3. 觸發與數據采集階段

  • ?啟動采集:發送運行命令CMD_ARM_BASIC_TRIGGER(命令碼0x01),下位機開始采樣,采樣結束后上報數據。下位機要注意接收停止命令CMD_XOFF (命令碼0x13)
    				case CMD_ARM_BASIC_TRIGGER:{run();break;}case CMD_XOFF:{//stop ();break;}
    static void run (void)
    {/* 采集數據 */start();/* 上報數據 */upload();
    }

協議可有的功能:

  • ?觸發事件處理
    • 當觸發條件滿足時,下位機停止采樣并緩存觸發點前后的數據(預觸發和觸發后數據)
    • 上位機通過讀取狀態命令(命令碼0x20)?輪詢下位機狀態,直到收到觸發完成標志
  • ?數據回傳:上位機發送讀取數據命令(命令碼0x20)?,下位機按SUMP協議格式返回二進制數據塊(包含時間戳和通道狀態)

注意:數據格式與傳輸特點

  • ?數據塊結構:SUMP協議要求數據以32位小端存儲格式傳輸(最大支持 32 個采樣通道),每個采樣點按通道順序打包(例如通道0對應最低位),與STM32的變量存儲方式一樣,還有串口通信、USB協議都是規定低位先行……最后是數組元素在內存中是連續存儲的,且地址從低到高依次排列。
    所以(uint32_t *)buff[1]一到四字節直接可用且分別對應 group1 的channel 0~7,group2、3、4等同理。
  • 傳輸順序:它上報的數據是:先上報最后一個采樣的數據,最后上報第 1 個采樣點的數據。
  • ?數據流控制:上位機可能分批次請求數據(通過分段讀取命令),避免單次傳輸過大導致緩沖區溢出

2.娛樂部分:

可以自行使用邏輯分析儀監控USB模擬的串口通信?(用另一臺PulseView檢測),觀察命令碼和數據是否符合SUMP協議規范

注意USB模擬的串口在48Mhz以上的采樣頻率下會比較清晰

03 軟件實現與性能壓榨

1.采樣頻率優化——使用匯編語句

測量讀 GPIO 操作、讀寫 buffer、NOP 指令的時間、邏輯右移、加法操作的時間測量方法類似如下(使用匯編語句):

結論:循環一次耗時 44+24+16+23=107ns,理論上最高的采樣頻率=1/107ns=9MHz。

測量處理 Tick 中斷函數的時間:

匯編優化數據采集


BUFFER_SIZE equ 3100  ; 注意這個數值要跟logicanalyzer.c中的BUFFER_SIZE保持一致; 聲明后續代碼使用 Thumb 指令集THUMB	;16 位指令集_大多數 Cortex-M 芯片默認使用 Thumb 模式AREA    |.text|, CODE, READONLY	;匯編偽指令_|.text|為標準代碼段名——CODE 表示這是一個代碼段,READONLY 表示只讀; sample_function handler
sample_function    PROC	; 函數開始標記EXPORT  sample_function	;相當于C語言中的 extern 聲明,但方向相反(這里是匯編導出給外部使用)IMPORT g_rxdata_bufIMPORT g_rxcnt_bufIMPORT g_cur_posIMPORT g_cur_sample_cntIMPORT get_stop_cmdIMPORT g_convreted_sample_countPUSH     {R4, R5, R6, R7, R8, R9, R10, R11, R12, LR}	; 函數入口保存寄存器——LR(Link Register)存儲了函數執行完畢后應返回的地址LDR R0, =g_rxdata_buf  ; 得到這些變量的地址,并不是得到它們的值LDR R1, =g_rxcnt_buf   ; 得到g_rxcnt_buf變量的地址,并不是得到它的值LDR R2, =g_cur_pos     ; 得到當前緩沖區位置g_cur_pos變量的地址,并不是得到它的值LDR R2, [R2]           ; 得到當前緩沖區位置g_cur_pos變量的值LDR R3, =g_cur_sample_cnt	;采樣計數器 LDR R3, [R3]	;LDR R4, =get_stop_cmd	;停止命令LDR R5, =g_convreted_sample_count	;實際需要采集的樣本數LDR R5, [R5]LDR R8, [R0]  ; pre_dataLDR R10, =BUFFER_SIZELDR  R6, =0x40010C08	;GPIOB_IDR地址用于讀取PB8-PB15引腳狀態; 設置PA15的值備用LDR R11, =0X40010810LDR R12, =(1<<15)LDR LR, =(1<<31)
Loop    ; 設置PA15輸出高電平STR R12, [R11]
;read dataLDRH R7, [R6]  ; 讀GPIOB_IDRLSR R7, #8    ; data = (*data_reg) >> 8;CMP R7, R8	;條件執行__與ADDNE條件碼NE關聯ADDNE R2, #1  ; g_cur_pos += (data != pre_data)? 1 : 0;STRB R7, [R0, R2] ; g_rxdata_buf[g_cur_pos] = data;    ;目標地址 = R0 + R2MOV R8, R7        ; pre_data = dataLDR R7, [R1, R2, LSL #2] ; R7 = g_rxcnt_buf[g_cur_pos]	;乘以4(即左移2位);目標地址 = 數組基地址(R1) + 下標(R2) * 元素大小(4)ADD R7, #1STR R7, [R1, R2, LSL #2] ; g_rxcnt_buf[g_cur_pos]++;ADD R3, #1    ; g_cur_sample_cnt++;CMP R3, R5    ; if (g_cur_sample_cnt >= g_convreted_sample_count) break;BGE LoopDoneLDR R7, [R4]  ; R7 = get_stop_cmdCMP R7, #0    ; if (get_stop_cmd) break;BNE LoopDone    ;基于零標志位(Z)判斷相等或不等CMP R2, R10    ; if (g_cur_pos >= BUFFER_SIZE) break;BGE LoopDone    ;大于或等于時跳轉NOPNOP         ; 延時, 湊出2MHz; 設置PA15輸出高電平STR LR, [R11]B LoopLoopDoneLDR R0, =g_cur_pos     ; 得到g_cur_pos變量的地址,并不是得到它的值STR R2, [R0]           ; 保存g_cur_pos變量的值LDR R0, =g_cur_sample_cntSTR R3, [R0]           ; 保存g_cur_sample_cnt變量的值POP     {R4, R5, R6, R7, R8, R9, R10, R11, R12, PC}	; 函數出口恢復寄存器;?為什么用 PC 而不是 LR_因為 LR 可能在函數內部被修改(例如嵌套調用),而棧中保存的是原始的返回地址ENDP	 ; 函數結束標記

2.內存保存采樣數據優化

在有限的內存里,我們需要提高內存的使用效率:不變的數據就不要保存了。新方案
如下:
① 定義兩個數組:uint8_t data_buf[BUFFER_SIZE]、uint8_t cnt_buf[BUFFER_SIZE]
① 以比較高的、頻率周期性地讀取 GPIO 的值
② 只有 GPIO 值發生變化了,才存入 data_buf[i++];GPIO 值無變化時,cnt_buf[i-1]累
③ 以后,根據 data_buf、cnt_buf 恢復各個采樣點的數據,上報給上位機
        /* 4.1 讀取數據 */data = (*data_reg) >> 8;/* 4.2 保存數據 */        g_cur_pos += (data != pre_data)? 1 : 0; /* 數據不變的話,寫位置不變 */g_rxdata_buf[g_cur_pos] = data;         /* 保存數據 */g_rxcnt_buf[g_cur_pos]++;               /* 增加"相同的數據"個數 */g_cur_sample_cnt++;                     /* 累加采樣個數 */pre_data = data;

3.USB串口傳輸優化

優勢或特點

  • 使用環形緩沖區+?DMA持續寫入數據,不阻塞硬件接收。主程序按需讀取數據,無需實時響應每個字節。緩沖區未滿時,新數據持續寫入;緩沖區滿時,也可觸發溢出處理。內存固定、無拷貝開銷——天然的多任務運行!
    static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
    {/* USER CODE BEGIN 6 */for (uint32_t i = 0; i < *Len; i++){circle_buf_write(&g_uart_rx_bufs, Buf[i]);}USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);USBD_CDC_ReceivePacket(&hUsbDeviceFS);return (USBD_OK);/* USER CODE END 6 */
    }
  • 下位機想通過 USB 口發送數據時,要確保上次傳輸完成
uint8_t usb_send(uint8_t *datas, int len, int timeout)
{USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*)hUsbDeviceFS.pClassData;while(1){if (hcdc->TxState == 0){break;}if (timeout--){mdelay(1);}else{return HAL_BUSY;}}return CDC_Transmit_FS(datas, len);
}

硬件問題:

/*********************************************************************** 函數名稱: uart_save_in_buf_and_send* 功能描述: 使用USB傳輸時,一個一個字節地傳輸效率非常低,盡量一次傳輸64字節* 輸入參數: datas - 保存有要發送的數據*            len - 數據長度*            timeout - 超時時間(ms)*            flush - 1(即刻發送), 0(可以先緩存起來)***********************************************************************/
static void uart_save_in_buf_and_send(uint8_t *datas, int len, int timeout, int flush)
{static uint8_t buf[64];static int32_t cnt = 0;for( int32_t i = 0; i < len; i++ ){buf[cnt++] = datas[i]; /* 先存入buf, 湊夠63字節再發送 */if (cnt == 63){/* 對于USB傳輸,它內部發送64字節數據后還要發送一個零包* 所以我們只發送63字節以免再發送零包*/uart_send(buf, cnt, timeout);cnt = 0;}}/* 如果指定要"flush"(比如這是最后要發送的數據了), 則發送剩下的數據 */if (flush && cnt){uart_send(buf, cnt, timeout);cnt = 0;}
}

4.使用 RLE 提升重復數據的傳輸效率

1.RLE規則(SUMP 協議里規定)

編碼規則
  • ?長度字段:最高位為1,低7位表示(重復次數-1)(如重復10次編碼為0x89)。
  • ?數據字段:最高位置0(data = g_rxdata_buf[i] & ~0x80),避免與長度字段沖突。

2.代碼實現

                if (g_flags & CAPTURE_FLAG_RLE){/* RLE : Run Length Encoding, 在數據里嵌入長度, 在傳輸重復的數據時可以提高效率* 先傳輸長度: 最高位為1表示長度, 去掉最高位的數值為n, 表示有(n+1)個數據* 再傳輸數據本身 (數據的最高位必須為0)* 例子1: 對于8通道的數據, channel 7就無法使用了* 要傳輸10個數據 0x12時, 只需要傳輸2字節: 0x89 0x12* 0x89的最高位為1, 表示有(9+1)個相同的數據, 數據為0x12* * 例子2: 對于32通道的數據, channel 31就無法使用了* 要傳輸10個數據 0x12345678時, 只需要傳輸8字節: 0x09 0x00 0x00 0x80 0x78 0x56 0x34 0x12* "0x09 0x00 0x00 0x80"的最高位為1, 表示有(9+1)個相同的數據, 數據為"0x78 0x56 0x34 0x12"*/data = g_rxdata_buf[i] & ~0x80; /* 使用RLE時數據的最高位要清零 */;if (rle_cnt == 0){pre_data = data;rle_cnt = 1;}else if (pre_data == data){rle_cnt++; /* 數據相同則累加個數 */}else if (pre_data != data){/* 數據不同則上傳前面的數據 */if (rle_cnt == 1) /* 如果前面的數據只有一個,則無需RLE編碼 */uart_save_in_buf_and_send(&pre_data, 1, 100, 0);else{/* 如果前面的數據大于1個,則使用RLE編碼 */rle_cnt = 0x80 | (rle_cnt - 1);uart_save_in_buf_and_send(&rle_cnt, 1, 100, 0);//長度字段uart_save_in_buf_and_send(&pre_data, 1, 100, 0);}pre_data = data;rle_cnt = 1;}if(rle_cnt == 128){/* 對于只有8個通道的邏輯分析儀, 只使用1個字節表示長度,最大長度為128* 當相同數據個數累加到128個時,* 就先上傳*/rle_cnt = 0x80 | (rle_cnt - 1);uart_save_in_buf_and_send(&rle_cnt, 1, 100, 0);uart_save_in_buf_and_send(&pre_data, 1, 100, 0);rle_cnt = 0;}}else{/* 上位機沒有起到RLE功能則直接上傳 */uart_save_in_buf_and_send(&g_rxdata_buf[i], 1, 100, 0);}cnt = 0;}…………………………………………………………………………………………/* 發送最后的數據 *因為可能數據遍歷完了,但沒有達到發送數據的條件:(pre_data != data)、(rle_cnt == 128),*即有有>=1個以上的數據沒有上傳*/if ((g_flags | CAPTURE_FLAG_RLE) && rle_cnt){if (rle_cnt == 1)uart_save_in_buf_and_send(&pre_data, 1, 100, 0);else{rle_cnt = 0x80 | (rle_cnt - 1);uart_save_in_buf_and_send(&rle_cnt, 1, 100, 0);uart_save_in_buf_and_send(&pre_data, 1, 100, 0);}}/* 為了提高USB上傳效率,我們原本一直是"湊夠一定量的數據后才發送",* 現在都到最后一步了,剩下的數據全部flush、上傳*/uart_save_in_buf_and_send(NULL, 0, 100, 1);

03 代碼實現

1.解析命令

void LogicalAnalyzerTask(void)	
{uint8_t cmd_buffer[5];uint8_t cmd_index = 0;uint8_t c;while (1){if (uart_recv(&c, TIMEOUT_FOREVER) == 0){cmd_buffer[cmd_index] = c;	switch (cmd_buffer[0]){case CMD_RESET://00{break;}case CMD_ID://02{/* 上報4個字節"1ALS" */                uart_send((uint8_t *)"1ALS", 4, TIMEOUT_DEFAULT);break;}case CMD_METADATA://04{uint32_t virtual_bufferSize = getBufferSize();uint32_t maxFrequency = getMaxFrequency();/* 上報參數 */ //NAMEsend_byte(0x01, TIMEOUT_DEFAULT);send_string("100ASK_LogicalNucleo", TIMEOUT_DEFAULT);send_byte(0x00, TIMEOUT_DEFAULT);//SAMPLE MEMsend_byte(0x21, TIMEOUT_DEFAULT);send_uint32(virtual_bufferSize, TIMEOUT_DEFAULT);//DYNAMIC MEMsend_byte(0x22, TIMEOUT_DEFAULT);send_uint32(0, TIMEOUT_DEFAULT);//SAMPLE RATEsend_byte(0x23, TIMEOUT_DEFAULT);send_uint32(maxFrequency, TIMEOUT_DEFAULT);//Number of Probessend_byte(0x40, TIMEOUT_DEFAULT);send_byte(8, TIMEOUT_DEFAULT);//Protocol Versionsend_byte(0x41, TIMEOUT_DEFAULT);send_byte(0x02, TIMEOUT_DEFAULT);//ENDsend_byte(0x00, TIMEOUT_DEFAULT);break;}case CMD_ARM_BASIC_TRIGGER://01{run();break;}case CMD_XON:{//start();break;}case CMD_XOFF:{//stop ();break;}case CMD_CAPTURE_SIZE://81{cmd_index++;if(cmd_index < 5)continue;uint16_t readCount  = *((uint16_t*)(cmd_buffer + 1));uint16_t delayCount = * ((uint16_t*)(cmd_buffer + 3));setSampleNumber(4 * readCount);setSamplingDelay(4 * delayCount);break;}case CMD_SET_DIVIDER: //80{cmd_index++;if(cmd_index < 5)continue;uint32_t divider = *((uint32_t *)(cmd_buffer + 1));setSamplingDivider(divider);break;}case CMD_SET_BASIC_TRIGGER_MASK0://c0{cmd_index++;if(cmd_index < 5)continue;setTriggerMask(*(uint32_t *)(cmd_buffer + 1));break;}case CMD_SET_BASIC_TRIGGER_VALUE0://c1{cmd_index++;if(cmd_index < 5)continue;setTriggerValue(*(uint32_t *)(cmd_buffer + 1));break;}case CMD_SET_BASIC_TRIGGER_CONFIG0://c2{cmd_index++;if(cmd_index < 5)continue;uint8_t serial = (*((uint8_t*)(cmd_buffer + 4)) & 0x04) > 0 ? 1 : 0;uint8_t state = (*((uint8_t*)(cmd_buffer + 4)) & 0x08) > 0 ? 1 : 0;if(serial == 1)setTriggerState(0);//Not supportedelsesetTriggerState(state);break;}case CMD_SET_FLAGS://82{cmd_index++;if(cmd_index < 5)continue;setFlags(*(uint32_t *)(cmd_buffer + 1));break;}case CMD_CAPTURE_DELAYCOUNT://83{cmd_index++;if(cmd_index < 5)continue;uint32_t delayCount  = *((uint32_t*)(cmd_buffer + 1));setSamplingDelay(4 * delayCount);								break;}									case CMD_CAPTURE_READCOUNT://84{cmd_index++;if(cmd_index < 5)continue;uint32_t readCount  = *((uint32_t*)(cmd_buffer + 1));setSampleNumber(4 * readCount);								break;}default:{}}cmd_index = 0;memset(cmd_buffer, 0, sizeof(cmd_buffer));//清除原先buff}}
}

2. 采集數據

  • 必須嚴格按照設定的采樣頻率采集信號,確保時間分辨率(如1MHz采樣率對應1μs時間精度)。
  • ?時序準確性是邏輯分析儀的核心指標,直接影響信號分析的可靠性
① 禁止中斷:這是為了在采集數據時以最快的頻率采集,不讓中斷干擾。
除了串口中斷之外,其他中斷都禁止。下位機只有 tick 中斷、串口中斷,所以只需要
禁止 tick 中斷。
保留串口中斷的原因在于:上位機可能發來命令停止采樣。
② 等待觸發條件:用戶可能設置觸發采樣的條件
③ 觸發條件滿足后,延時一會:沒有必要
④ 循環:以最高頻率采樣
退出的條件有三:收到上位機發來的停止命令、采集完畢、數據 buffer 已經滿
⑤ 恢復中斷

static void start (void)
{extern void sample_function();uint8_t data;uint8_t pre_data;volatile uint16_t *data_reg = (volatile uint16_t *)0x40010C08; /* GPIOB_IDR用于讀取PB8-PB15引腳狀態。 */volatile uint32_t *pa15_reg = (volatile uint32_t *)0X40010810; /* GPIOA_BSRR通過PA15引腳輸出信號 *///計算實際需要采集的樣本數,因為我們直接使用max采樣f運行//上位機次數*(MAX采樣f/)g_convreted_sample_count = g_sampleNumber * (MAX_FREQUENCY / g_samplingRate);get_stop_cmd = 0;//用戶停止標志,初始化為0//清空數據緩沖區位置和采樣計數器。g_cur_pos = 0;g_cur_sample_cnt = 0;(void)pre_data;(void)pa15_reg;/* 1. 除了串口中斷,其他中斷都禁止 */Disable_TickIRQ();//關閉系統定時器中斷(如SysTick),防止中斷干擾實時采樣。memset(g_rxcnt_buf, 0, sizeof(g_rxcnt_buf));//?清空計數緩沖區/* 2. 等待觸發條件 */if (g_triggerState && g_triggerMask)//判讀上位機設置的端口是否開啟{while (1)//監測GPIO引腳狀態,當滿足預設的觸發條件(高/低電平)時退出等待{data = (*data_reg) >> 8;/* 有沒有期待的高電平? */if (data & g_triggerMask & g_triggerValue)break;/* 有沒有期待的低電平? */if (~data & g_triggerMask & ~g_triggerValue)break;/* 用戶選擇停止? */if (get_stop_cmd)//若用戶發送停止信號(get_stop_cmd=1),立即退出函數。return;}}/* 3. 這里可以延時g_sampleDelay個采樣周期,但是沒有必要 */(void)g_sampleDelay;//沒有用到data = (*data_reg) >> 8;g_rxdata_buf[0] = data;g_rxcnt_buf[0] = 1;g_cur_sample_cnt = 1;pre_data = data;/* 4. 以最高的頻率采集數據 */
#ifdef USE_ASM_TO_SAMPLEsample_function();
#else//1Mhz運行while (1){        *pa15_reg = (1<<15); /* PA15輸出高電平 *//* 4.1 讀取數據 */data = (*data_reg) >> 8;/* 4.2 保存數據 */        g_cur_pos += (data != pre_data)? 1 : 0; /* 數據不變的話,寫位置不變 */g_rxdata_buf[g_cur_pos] = data;         /* 保存數據 */g_rxcnt_buf[g_cur_pos]++;               /* 增加"相同的數據"個數 */g_cur_sample_cnt++;                     /* 累加采樣個數 */pre_data = data;/* 4.3 串口收到停止命令 */if (get_stop_cmd)break;/* 4.4 采集完畢? */if (g_cur_sample_cnt >= g_convreted_sample_count)break;/* 4.5 buffer滿? */if (g_cur_pos >= BUFFER_SIZE)break;/* 4.6 加入這些延時湊出1MHz,加入多少個nop需要使用示波器或邏輯分析儀觀察、調整 */__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );*pa15_reg = (1UL<<31); /* PA15輸出低電平 */}
#endif/* 5. 使能被禁止的中斷 */Enable_TickIRQ();
}

3. 上報數據

  • 無需嚴格實時傳輸:上報頻率可以與采樣頻率解耦,但需保證數據順序和時序信息完整。
  • ?關鍵要求
    • 數據順序正確(先采樣的數據先上報)。
    • 每個數據點的時間戳或等效時間信息可被上位機還原(如通過固定采樣率計算時間偏移)
采集數據時是以最大頻率采集的,比如以 1MHz 采集。如果上位機要求的采樣頻率是
200KHz:1MHz/200KHz=5,采集到的數據量是上報數據量的 5 倍。我們只需要每隔 5 個數據
上報一個即可。
static void upload (void)
{int32_t i = g_cur_pos;uint32_t j;uint32_t rate = MAX_FREQUENCY / g_samplingRate;int cnt = 0;uint8_t pre_data;//數據字段uint8_t data;uint8_t rle_cnt = 0;//長度字段for (; i >= 0; i--)//外層循環遍歷數據位置(i從g_cur_pos遞減到0){for (j = 0; j < g_rxcnt_buf[i]; j++)//內層循環遍歷每個位置的重復次數,逐個檢查是否達到降采樣點,僅上傳周期匹配的數據點{cnt++;  /* 我們以最大頻率采樣, 假設最大頻率是1MHz* 上位機想以200KHz的頻率采樣* 那么在得到的數據里, 每5個里只需要上報1個*/if (cnt == rate) {if (g_flags & CAPTURE_FLAG_RLE){/* RLE : Run Length Encoding, 在數據里嵌入長度, 在傳輸重復的數據時可以提高效率* 先傳輸長度: 最高位為1表示長度, 去掉最高位的數值為n, 表示有(n+1)個數據* 再傳輸數據本身 (數據的最高位必須為0)* 例子1: 對于8通道的數據, channel 7就無法使用了* 要傳輸10個數據 0x12時, 只需要傳輸2字節: 0x89 0x12* 0x89的最高位為1, 表示有(9+1)個相同的數據, 數據為0x12* * 例子2: 對于32通道的數據, channel 31就無法使用了* 要傳輸10個數據 0x12345678時, 只需要傳輸8字節: 0x09 0x00 0x00 0x80 0x78 0x56 0x34 0x12* "0x09 0x00 0x00 0x80"的最高位為1, 表示有(9+1)個相同的數據, 數據為"0x78 0x56 0x34 0x12"*/data = g_rxdata_buf[i] & ~0x80; /* 使用RLE時數據的最高位要清零 */;if (rle_cnt == 0){pre_data = data;rle_cnt = 1;}else if (pre_data == data){rle_cnt++; /* 數據相同則累加個數 */}else if (pre_data != data){/* 數據不同則上傳前面的數據 */if (rle_cnt == 1) /* 如果前面的數據只有一個,則無需RLE編碼 */uart_save_in_buf_and_send(&pre_data, 1, 100, 0);else{/* 如果前面的數據大于1個,則使用RLE編碼 */rle_cnt = 0x80 | (rle_cnt - 1);uart_save_in_buf_and_send(&rle_cnt, 1, 100, 0);//長度字段uart_save_in_buf_and_send(&pre_data, 1, 100, 0);}pre_data = data;rle_cnt = 1;}if(rle_cnt == 128){/* 對于只有8個通道的邏輯分析儀, 只使用1個字節表示長度,最大長度為128* 當相同數據個數累加到128個時,* 就先上傳*/rle_cnt = 0x80 | (rle_cnt - 1);uart_save_in_buf_and_send(&rle_cnt, 1, 100, 0);uart_save_in_buf_and_send(&pre_data, 1, 100, 0);rle_cnt = 0;}}else{/* 上位機沒有起到RLE功能則直接上傳 */uart_save_in_buf_and_send(&g_rxdata_buf[i], 1, 100, 0);}cnt = 0;}}}/* 發送最后的數據 *因為可能數據遍歷完了,但沒有達到發送數據的條件:(pre_data != data)、(rle_cnt == 128),*即有有>=1個以上的數據沒有上傳*/if ((g_flags | CAPTURE_FLAG_RLE) && rle_cnt){if (rle_cnt == 1)uart_save_in_buf_and_send(&pre_data, 1, 100, 0);else{rle_cnt = 0x80 | (rle_cnt - 1);uart_save_in_buf_and_send(&rle_cnt, 1, 100, 0);uart_save_in_buf_and_send(&pre_data, 1, 100, 0);}}/* 為了提高USB上傳效率,我們原本一直是"湊夠一定量的數據后才發送",* 現在都到最后一步了,剩下的數據全部flush、上傳*/uart_save_in_buf_and_send(NULL, 0, 100, 1);
}

細節:“幕后工作”

已經實現:

1.USB上傳緩存cnt清0(收尾工作)

 uart_save_in_buf_and_send(NULL, 0, 100, 1);static void uart_save_in_buf_and_send(uint8_t *datas, int len, int timeout, int flush)
{static uint8_t buf[64];static int32_t cnt = 0;for( int32_t i = 0; i < len; i++ ){buf[cnt++] = datas[i]; /* 先存入buf, 湊夠63字節再發送 */if (cnt == 63){/* 對于USB傳輸,它內部發送64字節數據后還要發送一個零包* 所以我們只發送63字節以免再發送零包*/uart_send(buf, cnt, timeout);cnt = 0;}}/* 如果指定要"flush"(比如這是最后要發送的數據了), 則發送剩下的數據 */if (flush && cnt){uart_send(buf, cnt, timeout);cnt = 0;}
}

2.清除樣本緩存(開幕工作)

在static void start (void)中已經實現:

//清空數據緩沖區位置和采樣計數器。
g_cur_pos = 0;
g_cur_sample_cnt = 0;memset(g_rxcnt_buf, 0, sizeof(g_rxcnt_buf));//?清空計數緩沖區

至于其他大多采取覆蓋的形式:

比如:(在上傳時我們也是根據索引變量采取后進先出的形式)

uint8_t g_rxdata_buf[BUFFER_SIZE];?

還有USB串口用到的環形緩沖區……

這么一來下面這段自以為是改進的代碼就顯得贅述了

?
case CMD_RESET://00
{reset_globals(); // 調用全局變量重置函數break;
}void reset_globals() {g_cur_pos = 0;g_cur_sample_cnt = 0;memset(g_rxdata_buf, 0, BUFFER_SIZE); // 可選:清空緩沖區memset(g_rxcnt_buf, 0, BUFFER_SIZE * sizeof(uint32_t));
}?

不得不驚嘆到韋東山老師團隊的厲害和大義!感謝他們為嵌入式人才培養做出的貢獻!!!

04 到此,項目基本功能已經掌握!下一篇,我們來實現“單片機邏輯分析儀”的擴展功能。未完待續~

一首童年最燃的《再飛行》送給你

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/75528.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/75528.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/75528.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基數排序算法解析與TypeScript實現

基數排序&#xff08;Radix Sort&#xff09;是一種高效的非比較型整數排序算法&#xff0c;通過逐位分配與收集的方式實現排序。本文將深入解析其工作原理&#xff0c;并給出完整的TypeScript實現。 一、算法原理 1. 核心思想 多關鍵字排序&#xff1a;將整數按位數切割成不同…

最新全開源碼支付系統,贈送3套模板

最新全開源碼支付系統&#xff0c;贈送3套模板 碼支付是專為個人站長打造的聚合免簽系統&#xff0c;擁有卓越的性能和豐富的功能。它采用全新輕量化的界面UI 讓您能更方便快捷地解決知識付費和運營贊助的難題&#xff0c;同時提供實時監控和管理功能&#xff0c;讓您隨時隨地…

PHP基礎二【變量/輸出/數據類型/常量/字符串/運算符】

PHP基礎二 1. PHP變量2. PHP輸出3. 數據類型3.1 字符串3.2 整型3.3 浮點型3.4 布爾型3.5 數組3.6 對象3.7 NULL3.8 資源類型3.9 類型比較 4. 常量5. 運算符 1. PHP變量 1. 我們來看一個實例&#xff1a; <?php$x 5;$y 6;$z $x $y;echo $z; // echo 是輸出&#xff0c;…

ue5 仿鬼泣5魂類游戲角色和敵人沒有碰撞

UE5系列文章目錄 文章目錄 UE5系列文章目錄前言一、問題原因二、設置碰撞2.讀入數據 總結 前言 ue5 仿鬼泣5魂類游戲角色和敵人沒有碰撞 一、問題原因 在UE5中&#xff0c;角色和敵人沒有碰撞可能是由多種原因導致的&#xff0c;以下是一些可能的原因及解決方法&#xff1a…

《AdaBoost:從弱分類器到強模型的進化之路》

目錄 1. AdaBoost 的核心思想 2. AdaBoost 的關鍵步驟 步驟 1&#xff1a;初始化樣本權重 步驟 2&#xff1a;迭代訓練弱分類器 步驟 3&#xff1a;組合弱分類器 3. 用例子詳解 AdaBoost 數據集&#xff1a; 迭代過程&#xff1a; 第1輪&#xff08;t1&#xff09;&am…

Android Settings 有線網設置界面優化

Android Settings 有線網設置界面優化 文章目錄 Android Settings 有線網設置界面優化一、前言二、簡單修改1、修改的EthernetSettings代碼&#xff1a;2、有線網ip獲取代碼&#xff1a;3、AndroidManifest.xml定義有線網的Activity4、修改后界面&#xff1a; 三、其他1、有線網…

基于web的生產過程執行管理系統(源碼+lw+部署文檔+講解),源碼可白嫖!

摘要 隨著世界經濟信息化、全球化的到來和電子商務的飛速發展&#xff0c;推動了很多行業的改革。若想達到安全&#xff0c;快捷的目的&#xff0c;就需要擁有信息化的組織和管理模式&#xff0c;建立一套合理、暢通、高效的線上管理系統。當前的生產過程執行管理存在管理效率…

XSS 攻擊風險與防御實踐

? 框架與 XSS 防護概況 框架是否默認轉義高危場景建議防御措施React? 是使用 dangerouslySetInnerHTML避免使用&#xff0c;必要時做內容清洗Vue.js? 是使用 v-html避免使用&#xff0c;或使用 DOMPurify 清洗Angular? 是使用 innerHTML、bypassSecurityTrustHtml謹慎繞過…

Cesium 時間線 及 坐標轉換

文章目錄 Cesium 基礎理解&#xff08;二&#xff09;TimeLine & Clock 應用場景核心代碼實例及解釋代碼解釋 Cesium 之 實體動畫構建實體動畫的技巧1. 利用時間屬性2. 組合動畫效果3. 使用動畫曲線 優化點1. 減少屬性更新頻率2. 優化實體數量3. 合理使用材質和紋理 注意事…

ngx_regex_init

定義在 src\core\ngx_regex.c void ngx_regex_init(void) { #if !(NGX_PCRE2)pcre_malloc ngx_regex_malloc;pcre_free ngx_regex_free; #endif } NGX_PCRE21 #if !(NGX_PCRE2) 就為假 條件不成立 ngx_regex_init 函數就成了空實現 NGX_PCRE2 被定義&#xff0c;則表示 Ngin…

第二期:深入理解 Spring Web MVC [特殊字符](核心注解 + 進階開發)

前言&#xff1a; 歡迎來到 Spring Web MVC 深入學習 的第二期&#xff01;在第一期中&#xff0c;我們介紹了 Spring Web MVC 的基礎知識&#xff0c;學習了如何 搭建開發環境、配置 Spring MVC、編寫第一個應用&#xff0c;并初步了解了 控制器、視圖解析、請求處理流程 等核…

一文讀懂數據倉庫:從概念到技術落地

數據倉庫是一個面向主題的、集成的、相對穩定的、反映歷史變化的數據集合&#xff0c;用于支持管理決策。以下是關于數據倉庫的詳細介紹&#xff1a; 一、特點 面向主題&#xff1a;數據倉庫圍繞特定主題組織數據&#xff0c;如客戶、產品、銷售等&#xff0c;而不是像傳統數…

JavaScript學習18-css操作和事件處理程序(html/DOM0/DOM2)

一、css操作 第一種&#xff1a;容易出錯 第二種&#xff1a;有效避免錯誤 第三種&#xff1a; 二、事件處理程序 1.HTML事件 2.DOM0級事件處理 3.DOM2級事件處理

npm設置代理和取消代理

設置代理 具體代理端口要根據自己的來 npm config set proxy http://127.0.0.1:7890 npm config set https-proxy http://127.0.0.1:7890取消代理 npm config delete proxy npm config delete https-proxy查看代理 npm config get proxy # 應返回 null npm config get…

從零開始訓練Codebook:基于ViT的圖像重建實踐

完整代碼在文末&#xff0c;可以一鍵運行。 1. 核心原理 Codebook是一種離散表征學習方法&#xff0c;其核心思想是將連續特征空間映射到離散的碼本空間。我們的實現方案包含三個關鍵組件&#xff1a; 1.1 ViT編碼器 class ViTEncoder(nn.Module):def __init__(self, codebo…

大數據筆試題_第一階段配套筆試題02

已知一個字符類型的日期&#xff1a;2022-01-20&#xff0c;請用SQL顯示出此日期對應的下個月的月份&#xff0c;結果要求為Number類型&#xff08;202201&#xff09;。 參考答案 sql SELECT to_date(2022-01-20, yyyy-mm-dd) a1,add_months(to_date(2022-01-20, yyyy-mm-d…

C++實現對象單例模式

在 C 中實現單例模式有多種方法&#xff0c;以下是線程安全的現代 C 實現方式&#xff08;推薦 C11 及以上版本&#xff09;&#xff1a; 1. Meyers’ Singleton&#xff08;推薦&#xff09; class Singleton { public:// 刪除拷貝構造和賦值運算符Singleton(const Singleto…

企業常用Linux服務搭建

1.需要兩臺centos 7服務器&#xff0c;一臺部署DNS服務器&#xff0c;另一臺部署ftp和Samba服務器。 2. 部署DNS 服務器? #!/bin/bash# 更新系統 echo "更新系統..." sudo yum update -y# 安裝 BIND 和相關工具 echo "安裝 BIND 和相關工具..." sudo y…

UE5Actor模塊源碼深度剖析:從核心架構到實踐應用

UE5 Actor模塊源碼深度剖析:從核心架構到實踐應用 a. UE5 Actor模塊架構概述 在UE5引擎中,Actor扮演著至關重要的角色,它是整個游戲世界中各類可交互對象的基礎抽象。從本質上來說,所有能夠被放置到關卡中的對象都屬于Actor的范疇,像攝像機、靜態網格體以及玩家起始位置…

DreamDiffusion代碼學習及復現

論文解讀在這里 File path | Description /pretrains ┣ &#x1f4c2; models ┃ ┗ &#x1f4dc; config.yaml ┃ ┗ &#x1f4dc; v1-5-pruned.ckpt┣ &#x1f4c2; generation ┃ ┗ &#x1f4dc; checkpoint_best.pth ┣ &#x1f4c2; eeg_pretain ┃ ┗ …