中m序列與Walsh碼的正確使用與避坑指南)
簡介面向無線通信學(xué)習(xí)者的一份擴頻序列專題文檔圍繞m序列、Walsh碼和Gold序列的原理展開內(nèi)容包含最長線性移位寄存器序列的結(jié)構(gòu)、反饋系數(shù)與起始狀態(tài)的影響Walsh碼的Hadamard矩陣生成思路和64階前向鏈路碼分配以及Gold序列由優(yōu)選對模2加得到的構(gòu)造方法。配套MATLAB函數(shù)代碼演示了從Hadamard矩陣經(jīng)Paley排序到Walsh排序的完整變換便于理解正交擴頻碼的編程實現(xiàn)與矩陣轉(zhuǎn)換過程。壓縮包內(nèi)為單個Word文檔體積355KB內(nèi)容緊湊適合通信課程復(fù)習(xí)、CDMA系統(tǒng)原理自學(xué)或擴頻碼生成實驗對照查閱。文檔還特別分析了多徑傳播和小區(qū)間異步導(dǎo)致的非正交問題說明多址干擾為何不能完全消除有助于理解實際系統(tǒng)的局限。該份資料目前已有636人學(xué)習(xí)適合需要快速梳理擴頻碼概念并希望結(jié)合代碼驗證的初學(xué)者也可作為相關(guān)課程報告的參考素材。1. 擴頻系統(tǒng)里的 m 序列和 Walsh 碼選錯一次就全盤翻車第一次搭 CDMA 物理層仿真時很多人卡在同一個地方手上明明有 m 序列又有 Walsh 碼都是“擴頻序列”到底該用哪個做用戶區(qū)分我見過不少新手拍板選了 m 序列結(jié)果兩路用戶數(shù)據(jù)解出來互相打架誤碼率怎么調(diào)都壓不下去也有反過來的拿 Walsh 碼去干同步捕獲相關(guān)峰糊成一片幀頭永遠(yuǎn)找不準(zhǔn)。問題的根源不是哪個序列“更好”而是這兩類碼在擴頻系統(tǒng)里負(fù)責(zé)的是完全不同的活m 序列是偽隨機碼靠尖銳的自相關(guān)峰做定時同步、擾碼和測距Walsh 碼是正交碼靠嚴(yán)格正交性把各路信號在碼域上分開。這篇筆記就把它們的生成方式、參數(shù)配置、配合使用和最常見的翻車點一次講透適合做通信物理層仿真、軟件無線電和課設(shè)的從業(yè)者照著復(fù)現(xiàn)。2. 用本原多項式生成 m 序列抽頭配置和自相關(guān)特性才是真正的門檻2.1 從 LFSR 到最長序列為什么周期偏偏是 2 的 n 次方減一m 序列的全稱是“最長線性反饋移位寄存器序列”工程里最常見的生成載體就是 LFSR。一個 n 級移位寄存器每個時鐘周期把若干級的異或結(jié)果反饋回輸入端得到的輸出序列就是線性遞歸序列。線性反饋的結(jié)構(gòu)決定了它最多能遍歷 2 的 n 次方減 1 個非零狀態(tài)——因為全 0 狀態(tài)會永遠(yuǎn)鎖死在 0 上所以必須把它排除掉。能剛好走滿這 2 的 n 次方減 1 個狀態(tài)的反饋配置對應(yīng)的特征多項式就是本原多項式。這里有一個新手最容易搞錯的點不是隨便接幾根反饋線就能得到 m 序列。抽頭取自本原多項式才算數(shù)取錯了序列周期會縮短甚至產(chǎn)生多條短周期子序列拼起來的退化序列。比如 4 階 LFSR本原多項式有 x^4x1 和 x^4x^31 兩個等價形式周期都是 15但如果你拿 x^4x^21 這種非本原多項式去搭周期就只有 6頻譜特性完全不對。生成 m 序列時的工程約定有兩個一是初始化寄存器不能是全 0否則異或反饋永遠(yuǎn)輸出 0二是通常把“1”映射為負(fù)電平、“0”映射為正電平來做雙極性擴頻這樣序列的直流分量最小。至于抽頭選哪幾級常見的做法是查本原多項式表而不是自己試探后面給出可以直接用的配置。2.2 生成 m 序列的最小 Python 實現(xiàn)抽頭、初始狀態(tài)和周期驗證下面這段代碼用整數(shù)位移的方式模擬 LFSR比按位列表操作更快也更好理解反饋過程def mseq(n, taps, init1, lengthNone): 生成 m 序列雙極性1 - -1, 0 - 1 :param n: LFSR 階數(shù)移位寄存器級數(shù) :param taps: 反饋抽頭列表如 [4, 3] 表示第 4 級和第 3 級參與異或 :param init: 初始狀態(tài)不能為 0默認(rèn)取 1 :param length: 輸出序列長度默認(rèn) 2^n - 1剛好一個完整周期 :return: numpy 數(shù)組取值 1 和 -1 import numpy as np if init 0: raise ValueError(初始狀態(tài)不能為 0否則 LFSR 鎖死在全零) if length is None: length (1 n) - 1 if length (1 n) - 1: raise ValueError(長度不能超過 m 序列最大周期) state init ((1 n) - 1) # 只保留 n 位有效狀態(tài) mask (1 n) - 1 taps_mask 0 for t in taps: taps_mask | (1 (t - 1)) seq np.zeros(length, dtypeint) for i in range(length): seq[i] state 1 # 取最低位作為輸出 fb bin(state taps_mask).count(1) 1 # 抽頭異或結(jié)果 state ((state 1) | (fb (n - 1))) mask return np.where(seq 1, -1.0, 1.0) # 1 - -1, 0 - 1邏輯說明函數(shù)在每一個時鐘周期先輸出當(dāng)前寄存器的最低位再計算抽頭位置的異或結(jié)果把反饋值移入最高位。bin(state taps_mask).count(1) 1這行是求抽頭位的奇偶校驗等價于多個抽頭的異或。參數(shù)說明n通常取 5 到 15階數(shù)越高周期越長但捕獲時滑動搜索的計算量也越大taps必須配本原多項式的抽頭下面是幾張常用表。這里給出一個常用抽頭配置表直接照著用不會出問題階數(shù) n周期 2^n - 1本原多項式一種形式抽頭 taps說明37x^3x1[3, 1]適合做短幀同步序列415x^4x1[4, 1]仿真驗證最常用周期短、算得快531x^5x^21[5, 2]兼顧周期與計算量7127x^7x1[7, 1]擴頻增益已比較可觀101023x^10x^31[10, 3]常用于衛(wèi)星測距、幀同步碼2.3 自相關(guān)和互相關(guān)m 序列能干什么、不能干什么全看這兩個指標(biāo)m 序列最值錢的特性是周期自相關(guān)函數(shù)主峰等于序列長度 N旁瓣恒定為 -1。歸一化之后主峰是 1旁瓣只有負(fù)的 N 分之一。這意味著接收端拿本地 m 序列和接收信號做滑動相關(guān)只要對齊了就會出現(xiàn)一個非常尖銳的峰錯開哪怕 1 個碼片就會掉到旁瓣電平。這個特性讓它成為同步捕獲和測距的首選?;ハ嚓P(guān)就沒這么好看了。不同抽頭配置生成的兩條 m 序列互相關(guān)峰值大約在 2 的 n/2 次方分之一到 1 之間波動并不是零。換句話說m 序列不能用來做多用戶正交區(qū)分兩個用戶如果各拿一條 m 序列同時發(fā)數(shù)據(jù)解擴之后會殘留約一半功率的互干擾。有人拿 m 序列做 CDMA 用戶碼然后發(fā)現(xiàn)用戶數(shù)一多信噪比急劇惡化就是這個原因——它本來就不是干這個的。驗證這兩個特性的代碼很簡單直接復(fù)用上面的mseq函數(shù)對兩條序列分別做循環(huán)相關(guān)再歸一化即可。實測 4 階 m 序列周期間自相關(guān)旁瓣就是 -1互相關(guān)則可能出現(xiàn) 3、5 這類非零峰值和理論值對得上。3. 從 Hadamard 矩陣造出 Walsh 碼說要正交代價是每個碼片都要對齊3.1 為什么 Walsh 碼“正交”內(nèi)積為零才是硬指標(biāo)Walsh 碼是一組取值 1 和 -1 的正交序列工程上通過 Hadamard 矩陣的行或列來取。Hadamard 矩陣的遞歸構(gòu)造規(guī)則是2N 階矩陣由四個 N 階子塊拼成左上、右上、左下都是原矩陣右下是原矩陣取反。這樣構(gòu)造出來的任意兩行做逐位相乘再求和結(jié)果恒為零用數(shù)學(xué)語言說就是行與行之間兩兩正交。正交性有什么工程意義假設(shè)兩路用戶數(shù)據(jù)分別乘上兩條不同的 Walsh 碼疊加在一起發(fā)送接收端只要拿其中一條 Walsh 碼做內(nèi)積另一路信號的貢獻(xiàn)會被積分成零等效于那路用戶“不存在”。這就是碼分多址最基本的原理。但注意這個結(jié)論有一個隱含前提所有用戶的碼片在接收端必須精確對齊錯半個碼片正交性就開始劣化錯一個碼片基本等于偽正交。所以 Walsh 碼對同步的要求比 m 序列嚴(yán)苛得多這也是后面避坑章節(jié)重點展開的內(nèi)容。3.2 遞歸生成 Walsh 碼的最小實現(xiàn)行號、碼長怎么選生成 Walsh 碼最直接的方式就是遞歸構(gòu)造 Hadamard 矩陣然后按行取序列。下面這段代碼在 Python 里可以一次生成任意 2 的冪次長度的 Walsh 碼集def hadamard_matrix(order): 遞歸構(gòu)造 Hadamard 矩陣 :param order: 矩陣階數(shù)必須為 2 的冪次如 4/8/16/64 :return: numpy 2D 數(shù)組每行是一條 Walsh 碼 import numpy as np if order 1: return np.array([[1.0]]) half hadamard_matrix(order // 2) top np.hstack([half, half]) bottom np.hstack([half, -half]) return np.vstack([top, bottom])邏輯說明代碼遞歸地把小階 Hadamard 矩陣拼成大階矩陣右下角取負(fù)是保證正交性的關(guān)鍵少了這一步拼出來的矩陣行與行之間就不正交了。參數(shù)說明order取 16 時得到 16 條長度為 16 的 Walsh 碼能支持 16 個正交碼道IS-95 前向鏈路用的是 64 階 Walsh 碼對應(yīng) 64 個碼道其中一部分留給導(dǎo)頻和控制信道。仿真階段一般從 16 開始跑通了再放大到 64因為 64 階時滑動相關(guān)和矩陣運算的規(guī)模會翻十幾倍。選取 Walsh 碼時還有一個約定俗成的坑直接從 Hadamard 矩陣取行得到的是“Hadamard 序”即按遞歸生成順序排列而很多教材和協(xié)議文檔里用的是“Walsh 序”也叫 sequency 序按行內(nèi)符號翻轉(zhuǎn)次數(shù)從少到多排列。兩種排序本身都正交但工程對接時如果發(fā)送端按 Hadamard 序、接收端按 sequency 序配置用戶號解擴出來就是另一路用戶的數(shù)據(jù)。對接前先確認(rèn)排序約定這是血淚經(jīng)驗。3.3 別忘了雙極性映射0/1 表示下“正交”會失效一半Walsh 碼的教科書定義往往寫成 0/1 序列但做擴頻調(diào)制時必須映射成 1/-1 雙極性信號。原因很直接用 0 和 1 做相關(guān)運算時兩條不同 Walsh 碼的內(nèi)積不是零而是出現(xiàn)在 {0, 1} 之間的某個值并且同一路信號的自相關(guān)也不等于碼長而是等于碼長的一半因為直流分量混在里面。雙極性映射之后直流分量消失任意兩行內(nèi)積嚴(yán)格為零自相關(guān)等于碼長 N判決閾值也是干凈的零。這個坑在仿真里經(jīng)常表現(xiàn)為數(shù)據(jù)解出來了但幅度總是偏小一半或者疊加兩路用戶后解擴結(jié)果帶有固定偏置。檢查步驟很簡單——看發(fā)端映射函數(shù)里是不是用了2*x-1或np.where(x0, -1, 1)做了雙極性轉(zhuǎn)換。4. 集中避坑同步誤差、正交性假象和誤用錯配的四個常見翻車點4.1 解擴后殘留大量干擾信號錯了一個碼片正交性就垮了現(xiàn)象兩路用戶都按正交 Walsh 碼發(fā)送接收端解擴某一路后另一路用戶的語音/數(shù)據(jù)串?dāng)_清晰可聞信噪比只比沒擴頻時好一點點。原因Walsh 碼的正交性建立在所有碼片在接收端精確時間對齊的基礎(chǔ)上。收發(fā)兩端時鐘不同步、或者多徑信道引入時延都會讓碼片邊界錯位。錯位超過 0.5 個碼片后兩行 Walsh 碼的相關(guān)值從零變成接近碼長一半的偽峰正交性名存實亡。解決在 Walsh 解擴之前必須先做碼片級同步常見做法是用 m 序列做幀頭/導(dǎo)頻捕獲到相關(guān)峰后以此為時間基準(zhǔn)對齊碼片再做 Walsh 內(nèi)積。同步精度要控制在正負(fù) 0.2 個碼片以內(nèi)否則正交性余量會被消耗殆盡。4.2 用 m 序列區(qū)分多用戶用戶數(shù)一多就集體翻車現(xiàn)象給每個用戶分配一條不同抽頭的 m 序列仿真空曠信道下單獨跑一路沒問題加到三路以上誤碼率急劇惡化和信噪比無關(guān)。原因m 序列是偽隨機碼不是正交碼。不同 m 序列之間互相關(guān)峰值最大可以達(dá)到接近 1 的水平遠(yuǎn)談不上正交。多用戶疊加后每路用戶收到的都是其他所有用戶的互相關(guān)干擾之和信噪比增長完全跟不上用戶數(shù)增長。解決區(qū)分用戶用 Walsh 碼或 OVSF 碼這類正交碼m 序列退回去做幀同步和擾碼。這也是實際 CDMA 系統(tǒng)的通用架構(gòu)Walsh 碼做信道化m 序列做小區(qū)擾碼和同步。4.3 拿 Walsh 碼做同步捕獲相關(guān)峰永遠(yuǎn)找不準(zhǔn)現(xiàn)象把本地 Walsh 碼和接收信號做滑動相關(guān)主峰不夠尖銳旁邊連著好幾個大小差不多的旁瓣定時估計抖動很大幀頭位置判斷錯誤頻繁出現(xiàn)。原因Walsh 碼是正交碼設(shè)計目標(biāo)是行間互相關(guān)為零不是自相關(guān)尖銳。它的周期自相關(guān)旁瓣不是 -1 而是可能達(dá)到相當(dāng)高的值碼長越長、旁瓣越多做滑動相關(guān)時根本沒有“唯一峰”的概念。解決定時同步交給 m 序列這類 PN 碼Walsh 碼只做解擴和用戶區(qū)分。若鏈路預(yù)算實在緊張也可以用 m 序列做粗同步后再用 Walsh 做碼片級精同步微調(diào)但主同步手段不能換。4.4 擴頻信號頻譜出現(xiàn)線譜離散峰PN 碼看起來不夠“隨機”現(xiàn)象把 m 序列擴頻后的信號送到頻譜儀看到在載頻附近冒出一根根離散的線譜而不是平滑的擴頻包絡(luò)能量集中度比理論預(yù)期高。原因m 序列是周期的周期短到一定程度頻譜自然出現(xiàn)間隔為碼速率除以周期長度的離散譜線。尤其是 4 階、5 階這類短周期 m 序列周期只有 15 或 31譜線間隔非常寬既不利于隱蔽性也容易形成干擾尖峰。解決長周期化處理。常見做法是拿多個不同相位的 m 序列加長合成、或者用 m 序列和 Walsh 碼相乘混合成復(fù)合擴頻序列也可以把短 m 序列作為擾碼再疊加在 Walsh 擴頻輸出上破壞周期性。工程上如果協(xié)議允許直接把 m 序列階數(shù)提到 10 以上頻譜離散度肉眼可見改善。5. 搭建一條完整的擴頻鏈路m 序列定幀頭、Walsh 做碼分用戶區(qū)分5.1 最小仿真鏈路參數(shù)與整體流程把兩條正交 Walsh 碼分配給兩個用戶數(shù)據(jù)用 BPSK 調(diào)制每符號用一個 16 位的 Walsh 碼做擴頻幀頭用一條 15 位的 4 階 m 序列插在每幀數(shù)據(jù)前面。接收端先拿本地 m 序列做滑動相關(guān)找?guī)瘘c再按對齊后的碼片邊界做 Walsh 解擴恢復(fù)兩個用戶的數(shù)據(jù)。完整參數(shù)如下。參數(shù)取值說明擴頻碼Walsh 碼長度 16支持 16 個正交碼道仿真取 2 路用戶同步碼m 序列4 階周期 15幀頭捕獲相關(guān)峰旁瓣為 -1數(shù)據(jù)調(diào)制BPSK雙極性 1/-1與擴頻碼直接相乘每幀結(jié)構(gòu)m 序列幀頭 8 個數(shù)據(jù)符號幀頭長度短于 Walsh 碼長方便觀測捕獲峰信道AWGN無多徑先排除信道因素單獨驗證碼分正交性用戶數(shù)據(jù)每用戶 8 bit獨立隨機兩路數(shù)據(jù)完全獨立5.2 發(fā)送端幀頭插入與 Walsh 擴頻疊加發(fā)送端的核心操作是每個用戶的數(shù)據(jù)符號與一條固定分配的 Walsh 碼逐碼片相乘兩路用戶的結(jié)果直接相加得到一幀基帶信號再在幀頭位置填入 m 序列。下面是完整的發(fā)送端實現(xiàn)。import numpy as np def tx_frame(m_seq, walsh_mat, user1_bits, user2_bits): 發(fā)送端m 序列作幀頭 兩路用戶 Walsh 擴頻疊加 :param m_seq: 4 階 m 序列長度 15雙極性 :param walsh_mat: 16x16 Walsh 矩陣按行取碼 :param user1_bits: 0/1 數(shù)組長度 8 :param user2_bits: 0/1 數(shù)組長度 8 :return: 基帶發(fā)送序列 w1 walsh_mat[0] # 用戶 1 分配第 0 行 w2 walsh_mat[1] # 用戶 2 分配第 1 行 # BPSK 調(diào)制并擴頻每個符號乘以整條 Walsh 碼 user1_sym np.where(user1_bits 1, 1.0, -1.0) user2_sym np.where(user2_bits 1, 1.0, -1.0) spread1 np.kron(user1_sym, w1) # 符號序列與 Walsh 碼做 Kronecker 積 spread2 np.kron(user2_sym, w2) data_part spread1 spread2 # 兩路用戶碼域疊加 return np.concatenate([m_seq, data_part])邏輯說明np.kron(user_sym, w1)把每個數(shù)據(jù)符號擴展成 16 個碼片等效于符號與 Walsh 碼逐位相乘兩路用戶的擴頻結(jié)果直接相加就是碼分疊加后的信號。幀頭 m 序列拼在數(shù)據(jù)前面共用一個發(fā)射功率。參數(shù)說明walsh_mat[0]和walsh_mat[1]必須取自同一個 Hadamard 矩陣構(gòu)造結(jié)果且行序要按雙方約定好的排序這里的兩個 Walsh 碼在 16 階矩陣下互相關(guān)為零。5.3 接收端滑動相關(guān)捕獲幀頭再做 Walsh 解擴判決接收端拿到一幀完整信號后第一步是先找到幀頭起點。具體做法是拿本地 m 序列與接收信號從第一個碼片開始逐點滑動做相關(guān)找到相關(guān)峰的位置這個位置就是數(shù)據(jù)部分的起始邊界。注意滑動步長是 1 個碼片所以搜索范圍越長計算量越大。def rx_frame(rx, m_seq, walsh_mat, frame_len): 接收端m 序列滑動相關(guān)捕獲幀頭 Walsh 解擴 :param rx: 接收序列已加噪聲 :param m_seq: 本地 m 序列長度 15 :param walsh_mat: 16x16 Walsh 矩陣 :param frame_len: 發(fā)送幀總長度 :return: 恢復(fù)的兩路用戶符號 (0/1 數(shù)組) m_len len(m_seq) # 1. 滑動相關(guān)捕獲幀頭 corr np.array([ np.dot(rx[i:i m_len], m_seq) for i in range(len(rx) - m_len 1) ]) head_pos int(np.argmax(np.abs(corr))) # 相關(guān)峰位置 幀頭起點 # 2. 從幀頭后開始截取數(shù)據(jù)區(qū) data_start head_pos m_len data_part rx[data_start:data_start frame_len - m_len] # 3. 按符號逐個解擴與兩條 Walsh 碼分別內(nèi)積符號判決 w1, w2 walsh_mat[0], walsh_mat[1] n_sym len(data_part) // 16 sym1, sym2 [], [] for s in range(n_sym): chip data_part[s * 16:(s 1) * 16] r1 np.dot(chip, w1) # 用戶 1 的相關(guān)值 r2 np.dot(chip, w2) sym1.append(1 if r1 0 else 0) sym2.append(1 if r2 0 else 0) return np.array(sym1), np.array(sym2)邏輯說明滑動相關(guān)用np.dot(rx[i:im_len], m_seq)計算每個候選位置的相關(guān)系數(shù)捕獲后取最大值位置作為幀起點。Walsh 解擴則是對每個符號片段的 16 個碼片分別與兩條本地 Walsh 碼做內(nèi)積內(nèi)積結(jié)果大于零判為 1、小于零判為 0。參數(shù)說明滑動相關(guān)窗口長度等于 m 序列長度這里幀頭是 15 位所以窗口步長 1 遍歷整個接收幀Walsh 碼長 16所以數(shù)據(jù)區(qū)長度必須是 16 的整數(shù)倍否則最后一個符號會截斷。把幀頭捕獲和 Walsh 解擴分開寫是為了排查問題時能獨立驗證同步模塊和解擴模塊的好壞。6. 驗證鏈路做沒做對測三個指標(biāo)再用同步容差當(dāng)體檢鏈路搭完后不要急著看誤碼率先做三個驗證任何一個不過關(guān)都說明前面的實現(xiàn)有偏差。第一個驗證是 m 序列自相關(guān)峰。把本地 m 序列和自身做循環(huán)相關(guān)主峰應(yīng)為 15旁瓣全部為 -1如果旁瓣有超過 -1 的值說明生成的不是本原序列。第二個驗證是 Walsh 正交性把 Walsh 矩陣任意兩行做內(nèi)積全部為零并把第 0 行和第 1 行分別與自身做內(nèi)積確認(rèn)自相關(guān)為 16這步能直接暴露 0/1 映射沒轉(zhuǎn)雙極性的問題。第三個驗證是無噪聲環(huán)境下解擴結(jié)果發(fā)端 8 bit 隨機數(shù)據(jù)進(jìn)tx_frame接收端rx_frame恢復(fù)后逐一對比誤碼率必須為 0此時再逐步把信噪比降到 10 dB、5 dB觀察誤碼率曲線是否符合 BPSK 理論趨勢。我個人的習(xí)慣是再補一個同步容差測試把接收序列人為向右偏移 1 到 8 個碼片觀察 Walsh 解擴誤碼率從哪個偏移量開始明顯惡化。實測下來偏移 1 個碼片時正交性已損失明顯偏移超過 4 個碼片基本等于沒有擴頻。這個測試能直接告訴你在真實系統(tǒng)里碼片級同步的精度紅線在哪里值得每次改完鏈路都跑一遍。等這套流程跑順了再把 Walsh 碼長升到 64、給每路用戶疊加獨立的 m 序列擾碼就是一套可以往實際工程遷移的 CDMA 前端原型了。希望這些踩過的彎能幫你少走一段。本文還有配套的精品資源點擊獲取