基于FPGA的LMS自适应滤波器

传统数字滤波器设计中,滤波器系数生成麻烦,参数修改需重新计算。为此可设计自适应数字滤波器,通过计算误差更新系数。介绍了系数更新算法,进行了Matlab仿真,还实现了滤波器的FPGA设计,分析了输出波形不完美的原因。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在传统的数字滤波器设计中,最重要的就是滤波器的系数。不管是用matlab的fdatool工具实现,还是用别的滤波器系数生成工具产生该系数,过程都是比较麻烦的。而且生成滤波器系数以后,滤波器的幅频响应,相频响应都已经被固定,如果要修改滤波器的参数,必须要从重新对滤波器的系数进行计算和量化并生成coe文件,才可以更改滤波器。所以基于以上原因,可以设计一个自适应的数字滤波器 ,通过计算输出信号和期望信号的误差来进行对滤波器系数的更新。

下面简单介绍一下系数更新算法:

 从上面的公式我们可以看出,下一时刻的滤波器系数是上一个时刻的滤波器系数加步长因子(u)与误差和输入信号的乘积。(步长因子决定了滤波器的收敛情况,通常情况下,步长因子为输入信号的自相关矩阵的特征值分之2)

自适应滤波器的matlab仿真:

下面是对于LMS函数的实现:

function [w,e,yn] = my_LMS(xn,dn)

%   输入:
% xn   输入信号       
% dn   理想信号          
% L    迭代次数      
% k    滤波器的阶数   
%   输出:
% w    滤波器的系数矩阵    大小为 k×L  每一列代表一次迭代后的系数
% e    误差信号            大小为 L×1  每一行代表一次迭代后产生的误差
% yn   滤波器的输出信号    

%% 参数配置
k=128;              %滤波器阶数
L=length(xn);       %迭代次数=输入信号长度

%% 初始化
yn=zeros(1,L);      %初始化滤波输出信号
yn(1:k)=xn(1:k);    %初始化输出信号前k位数据,保证
w=zeros(1,k);       %初始化权重
e=zeros(1,L);       %初始化误差

%% 求收敛常数u
fe = max(eig(xn*xn.'));%求解输入xn的自相关矩阵的最大特征值fe,A = eig(B),意为将矩阵B的特征值组成向量A
u = 2*(1/fe);

%% 迭代更新滤波器的参数
for i=(k+1):L             %要保证输入延时后的信号有效,所以实际的迭代次数只有(L-k)次,
    XN=xn((i-k+1):(i));   %将输入信号延迟,使得滤波器的每个抽头都有输入
    yn(i)=w*XN';          %计算出滤波器的输出
    e(i)=dn(i)-yn(i);     %得出误差信号
    w=w+u*e(i)*XN;        %迭代更新滤波器的系数
end

end

下面便调用上述函数:

要将所生成的正弦波和加噪后的正弦波量化,在后面的vivado中会用到该量化好的数据。

(要注意加噪后的量化信号,有的时候会出现错误(在vivado中无法读取),所以在生成的txt文件中自行修改错误的数据)

clear;
clc;
close all;

L=1024;    %信号长度
a=1;       %原始信号幅度
t=1:L;      
dn=a*sin(0.05*pi*t);%原始正弦信号
subplot(411);plot(dn);axis([0,L,-a-1,a+1]);
%fidc= fopen('C:/Users/Lenovo/Desktop/LMS/matlab/sin_data.txt','wt'); //将正弦波信号量化
%for x = 1 : L
%    fprintf(fidc,'%x\n',round((dn+2.12)*58));
%end  
%fclose(fidc); 

xn=awgn(dn,1);   %添加信噪比5dB的白高斯噪声

subplot(412);plot(xn);axis([0,L,-a-1,a+1]);
title('信号加高斯白噪声后的时域波形');
fidc= fopen('C:/Users/Lenovo/Desktop/LMS/matlab/add_noise_data.txt','wt'); //将加噪正弦波量化
for x = 1 : L
    fprintf(fidc,'%x\n',round((xn+2.12)*58));
end  
fclose(fidc); 

[w,e,yn] = my_LMS(xn,dn);%调用滤波算法

subplot(413);plot(yn);axis([0,L,-a-1,a+1]);
title('LMS算法自适应滤波后的输出时域波形');

subplot(414);plot(e);axis([0,L,-a-1,a+1]);
title('LMS算法自适应滤波后与原始信号误差');

matlab仿真波形:

 从上面的图我们可以看出该自适应滤波器已经完成了滤波。下面开始滤波器的fpga实现:

顶层模块:

module lms_top(
    input clk_i,
    input rst_n_i,
    input signed [15:0] data_in,        //输入待滤波数据
    input signed [15:0] data_ref,       //参考数据
    output signed [15:0] error_o,         //误差
    output signed [31:0] data_o         //输出数据
    );

    wire signed [15:0] coef1;
    wire signed [15:0] coef2;
    wire signed [15:0] coef3;
    wire signed [15:0] coef4;
    wire signed [15:0] coef5;
    wire signed [15:0] coef6;
    wire signed [15:0] coef7;
    wire signed [15:0] coef8;
    wire signed [15:0] coef9;

fir fir_inst (
      .clk_i (clk_i ),
      .rst_n_i (rst_n_i ),
      .data_in (data_in ),
      .coef1 (coef1 ),
      .coef2 (coef2 ),
      .coef3 (coef3 ),
      .coef4 (coef4 ),
      .coef5 (coef5 ),
      .coef6 (coef6 ),
      .coef7 (coef7 ),
      .coef8 (coef8 ),
      .coef9 (coef9 ),
      .data_o(data_o)
    );
  
error_calcu error_calcu_inst (
      .clk_i (clk_i ),
      .rst_n_i (rst_n_i ),
      .data_in (data_o ), //data_in  data_o
      .data_ref (data_ref ),
      .error_o  ( error_o)
    );

coef_update coef_update_inst(
      .clk_i (clk_i ),
      .rst_n_i (rst_n_i ),
      .error_o (error_o ),
      .data_in (data_in ),
      .coef1 (coef1 ),
      .coef2 (coef2 ),
      .coef3 (coef3 ),
      .coef4 (coef4 ),
      .coef5 (coef5 ),
      .coef6 (coef6 ),
      .coef7 (coef7 ),
      .coef8 (coef8 ),
      .coef9 (coef9 )
    );
    
endmodule

FIR滤波器模块:

module fir(
    input                clk_i,
    input                rst_n_i,
    input  signed [15:0] data_in,                       //fir_i,

    input signed [15:0] coef1,                          //权值
    input signed [15:0] coef2,
    input signed [15:0] coef3,
    input signed [15:0] coef4,
    input signed [15:0] coef5,
    input signed [15:0] coef6,
    input signed [15:0] coef7,
    input signed [15:0] coef8,
    input signed [15:0] coef9,

    output reg [31:0] data_o                   //fir_o        
    );


reg[15:0] delay_pipeline1 ;//延时模块
reg[15:0] delay_pipeline2 ;
reg[15:0] delay_pipeline3 ;
reg[15:0] delay_pipeline4 ;
reg[15:0] delay_pipeline5 ;
reg[15:0] delay_pipeline6 ;
reg[15:0] delay_pipeline7 ;
reg[15:0] delay_pipeline8 ;
reg[15:0] delay_pipeline9 ;
/*第一级流水,将输入信号进行延时,每到来一个时钟信号,
便将输入信号保存到delay_pipelin1中,然后将剩下的依次移动一位。*/
always@(posedge clk_i or negedge rst_n_i)
      if(rst_n_i == 1'b0)
               begin
                    delay_pipeline1 <= 16'b0 ;
                    delay_pipeline2 <= 16'b0 ;
                    delay_pipeline3 <= 16'b0 ;
                    delay_pipeline4 <= 16'b0 ;
                    delay_pipeline5 <= 16'b0 ;
                    delay_pipeline6 <= 16'b0 ;
                    delay_pipeline7 <= 16'b0 ;
                    delay_pipeline8 <= 16'b0 ;
                    delay_pipeline9 <= 16'b0 ;
               end
       else begin
                    delay_pipeline1 <= data_in     ;
                    delay_pipeline2 <= delay_pipeline1 ;
                    delay_pipeline3 <= delay_pipeline2 ;
                    delay_pipeline4 <= delay_pipeline3 ;
                    delay_pipeline5 <= delay_pipeline4 ;
                    delay_pipeline6 <= delay_pipeline5 ;
                    delay_pipeline7 <= delay_pipeline6 ;
                    delay_pipeline8 <=delay_pipeline7 ;
                    delay_pipeline9<= delay_pipeline8 ;
               end
//乘积结果保存寄存器
reg signed [31:0] multi_data1 ;
reg signed [31:0] multi_data2 ;
reg signed [31:0] multi_data3 ;
reg signed [31:0] multi_data4 ;
reg signed [31:0] multi_data5 ;
reg signed [31:0] multi_data6 ;
reg signed [31:0] multi_data7 ;
reg signed [31:0] multi_data8 ;
reg signed [31:0] multi_data9 ;
 
//x(n) * h(n-k)
always@(posedge clk_i or negedge rst_n_i) 
      if(rst_n_i == 1'b0) 
      begin                                 
          multi_data1 <= 32'b0 ;
      end
       else begin
          multi_data1 <= delay_pipeline1 * coef1 ; 
       end          
//x(1) * h(1)
always@(posedge clk_i or negedge rst_n_i) 
      if(rst_n_i == 1'b0)                                   
          multi_data2 <= 32'b0 ;
       else
          multi_data2 <= delay_pipeline2 * coef2 ;
//x(2) * h(2)   
always@(posedge clk_i or negedge rst_n_i) 
      if(rst_n_i == 1'b0)                                   
          multi_data3 <= 32'b0 ;
       else
          multi_data3 <= delay_pipeline3 * coef3 ;
  //x(3) * h(3)        
always@(posedge clk_i or negedge rst_n_i) 
      if(rst_n_i == 1'b0)                                   
          multi_data4 <= 32'b0 ;
       else
          multi_data4 <= delay_pipeline4 * coef4 ;
//x(4) * h(4)     
 always@(posedge clk_i or negedge rst_n_i) 
      if(rst_n_i == 1'b0)                                   
          multi_data5 <= 32'b0 ;
       else
          multi_data5 <= delay_pipeline5 * coef5 ;
//x(5) * h(5)   
always@(posedge clk_i or negedge rst_n_i) 
      if(rst_n_i == 1'b0)                                   
          multi_data6 <= 32'b0 ;
       else
          multi_data6 <= delay_pipeline6 * coef6 ;
//x(6) * h(6)          
always@(posedge clk_i or negedge rst_n_i) 
      if(rst_n_i == 1'b0)                                   
          multi_data7 <= 32'b0 ;
       else
          multi_data7 <= delay_pipeline7 * coef7;
//x(7) * h(7)          
always@(posedge clk_i or negedge rst_n_i) 
      if(rst_n_i == 1'b0)                                   
          multi_data8 <= 32'b0 ;
       else
          multi_data8 <= delay_pipeline8 * coef8;
//x(8) * h(8)        
always@(posedge clk_i or negedge rst_n_i) 
      if(rst_n_i == 1'b0)                                   
          multi_data9 <= 32'b0 ;
       else
          multi_data9 <= delay_pipeline9 * coef9 ;
//将乘积累加,累加的结果就是滤波后的信号                                                       
always@(posedge clk_i or negedge rst_n_i)
      if(rst_n_i == 1'b0)                                  
          data_o <= 32'b0;
       else
          data_o <= multi_data1 + multi_data2 + multi_data3 + 
          multi_data4 +multi_data5 + multi_data6 + multi_data7 +
           multi_data8 + multi_data9 ;
endmodule

误差计算模块:

module error_calcu(
    input                clk_i,
    input                rst_n_i,
    input  signed [31:0] data_in,           //滤波完成数据32
    input  signed [15:0] data_ref,          //参考数据
    output signed [15:0] error_o            //误差输出
    );

    wire  signed [15:0] error;

//延时寄存参考数据
    reg signed [15:0] data_shift1;
    reg signed [15:0] data_shift2;
    reg signed [15:0] data_shift3;
    reg signed [15:0] data_shift4;
    reg signed [15:0] data_shift5;
    reg signed [15:0] data_shift6;
    reg signed [15:0] data_shift7;
    reg signed [15:0] data_shift8;
    reg signed [15:0] data_shift9;
    
    always @(posedge clk_i or negedge rst_n_i) begin
        if(rst_n_i == 1'b0)
        begin
            data_shift1 <= 16'd0;
            data_shift2 <= 16'd0;
            data_shift3 <= 16'd0;
            data_shift4 <= 16'd0;
            data_shift5 <= 16'd0;
            data_shift6 <= 16'd0;
            data_shift7 <= 16'd0;
            data_shift8 <= 16'd0;
            data_shift9 <= 16'd0;
        end
        else begin
            data_shift1 <= data_ref   ;
            data_shift2 <= data_shift1;
            data_shift3 <= data_shift2;
            data_shift4 <= data_shift3;
            data_shift5 <= data_shift4;
            data_shift6 <= data_shift5;
            data_shift7 <= data_shift6;
            data_shift8 <= data_shift7;
            data_shift9 <= data_shift8;
        end
    end

assign error = data_in - data_shift9;
assign error_o = error;

endmodule

系数更新模块:

module coef_update(                     //系数更新模块
    input               clk_i,
    input               rst_n_i,
    input signed [15:0] error_o,          //误差
    input signed [15:0] data_in,        //待滤波数据

    //权值更新
    output reg [15:0] coef1,
    output reg [15:0] coef2,
    output reg [15:0] coef3,
    output reg [15:0] coef4,
    output reg [15:0] coef5,
    output reg [15:0] coef6,
    output reg [15:0] coef7,
    output reg [15:0] coef8,
    output reg [15:0] coef9
    );

    reg signed [15:0] mu;           //遗忘因子(步长)
    reg        [3:0] cnt;           //计数器(计算一组滤波系数)
    reg        [3:0] cnt_flag;      //计数器标志信号
    reg              cnt_flag_en;   //计算使能信号

//延时寄存输入数据
    reg signed [15:0] data_shift1;
    reg signed [15:0] data_shift2;
    reg signed [15:0] data_shift3;
    reg signed [15:0] data_shift4;
    reg signed [15:0] data_shift5;
    reg signed [15:0] data_shift6;
    reg signed [15:0] data_shift7;
    reg signed [15:0] data_shift8;
    reg signed [15:0] data_shift9;
    
    always @(posedge clk_i or negedge rst_n_i) begin
        if(rst_n_i == 1'b0)begin
            data_shift1 <= 16'd0;
            data_shift2 <= 16'd0;
            data_shift3 <= 16'd0;
            data_shift4 <= 16'd0;
            data_shift5 <= 16'd0;
            data_shift6 <= 16'd0;
            data_shift7 <= 16'd0;
            data_shift8 <= 16'd0;
            data_shift9 <= 16'd0;
            mu          <= 16'd3;             //步长不知道是多少,随便写了一个;
        end
        else begin
            data_shift1 <= data_in   ;
            data_shift2 <= data_shift1;
            data_shift3 <= data_shift2;
            data_shift4 <= data_shift3;
            data_shift5 <= data_shift4;
            data_shift6 <= data_shift5;
            data_shift7 <= data_shift6;
            data_shift8 <= data_shift7;
            data_shift9 <= data_shift8;
        end
    end       
//权值更新

    //mu*error*data_in*2
    reg signed [15:0] coef1_reg;
    reg signed [15:0] coef2_reg;
    reg signed [15:0] coef3_reg;
    reg signed [15:0] coef4_reg;
    reg signed [15:0] coef5_reg;
    reg signed [15:0] coef6_reg;
    reg signed [15:0] coef7_reg;
    reg signed [15:0] coef8_reg;
    reg signed [15:0] coef9_reg;
    
 always @(posedge clk_i or negedge rst_n_i)
    if(rst_n_i == 1'b0)
        cnt <= 4'b0;
    else if(cnt == 4'd13)
        cnt <= 4'b0;
    else
        cnt <= cnt + 1'b1;
        
 always @(posedge clk_i or negedge rst_n_i)
    if(rst_n_i == 1'b0)
        cnt_flag <= 1'b0;
    else if(cnt == 4'd13) 
        cnt_flag <= 1'b1;
    else
        cnt_flag <= 1'b0;    
        
  always @(posedge clk_i or negedge rst_n_i)
    if(rst_n_i == 1'b0)
        cnt_flag_en <= 1'b0; 
    else if(cnt_flag == 1'b1)
        cnt_flag_en <= 1'b1;
    else
        cnt_flag_en <= cnt_flag_en;     
     
always@(posedge clk_i or negedge rst_n_i) 
begin
    if(rst_n_i == 1'b0)
    begin
        coef1_reg <= 16'd0;//2
        coef2_reg <= 16'd0;
        coef3_reg <= 16'd0;
        coef4_reg <= 16'd0;
        coef5_reg <= 16'd0;
        coef6_reg <= 16'd0;
        coef7_reg <= 16'd0;
        coef8_reg <= 16'd0;
        coef9_reg <= 16'd0;
    end
    else begin       
        coef1_reg <= (mu * error_o * data_shift1);
        coef2_reg <= (mu * error_o * data_shift2);
        coef3_reg <= (mu * error_o * data_shift3);
        coef4_reg <= (mu * error_o * data_shift4);
        coef5_reg <= (mu * error_o * data_shift5);
        coef6_reg <= (mu * error_o * data_shift6);
        coef7_reg <= (mu * error_o * data_shift7);
        coef8_reg <= (mu * error_o * data_shift8);
        coef9_reg <= (mu * error_o * data_shift9);
    end
end

always @(posedge clk_i or negedge rst_n_i) 
    if(rst_n_i == 1'b0)
    begin
        coef1 <= 16'd0;//0
        coef2 <= 16'd0;
        coef3 <= 16'd0;
        coef4 <= 16'd0;
        coef5 <= 16'd0;
        coef6 <= 16'd0;
        coef7 <= 16'd0;
        coef8 <= 16'd0;
        coef9 <= 16'd0;  
     end
     else if(cnt_flag_en == 1'b1)
     begin
        coef1 <= coef1 + coef1_reg;
        coef2 <= coef2 + coef2_reg;
        coef3 <= coef3 + coef3_reg;
        coef4 <= coef4 + coef4_reg;
        coef5 <= coef5 + coef5_reg;
        coef6 <= coef6 + coef6_reg;
        coef7 <= coef7 + coef7_reg;
        coef8 <= coef8 + coef8_reg;
        coef9 <= coef9 + coef9_reg;
      end

endmodule

testbench:

module tb_lms(
);
reg clk_i;
reg rst_n_i;
reg [15:0] data_in;
reg [15:0] data_ref;
wire [15:0] error_o;
wire [31:0] data_o;
reg [15:0] mem1[1:4096];   //设计一个rom放读入的数据      
reg [15:0] mem2[1:4096];                                 
reg [12:0] i;

 //例化FIR滤波器
lms_top lms_top_inst(
    .clk_i(clk_i),
    .rst_n_i(rst_n_i),
    .data_in(data_in),        //输入待滤波数据
    .data_ref(data_ref),       //参考数据
    .error_o(error_o),         //误差
    .data_o(data_o)         //输出数据
    );
initial                                               
       begin 
                     $readmemh("C:/Users/Lenovo/Desktop/LMS/matlab/add_noise_data.txt",mem1);//将待滤波信号读入mem
                     $readmemh("C:/Users/Lenovo/Desktop/LMS/matlab/sin_data.txt",mem2);                   
                     rst_n_i= 0;
                     clk_i= 0;
                     #50;rst_n_i= 1;
                     #50000;
                     $stop;
       end  
       
initial
       forever
          #50 clk_i = ~clk_i;//时钟生成
 
always@(posedge clk_i or negedge rst_n_i) 
      if(!rst_n_i)                                
          data_in <= 16'b0 ;
       else
          data_in <= mem1[i];     //读入数据
          
always@(posedge clk_i or negedge rst_n_i) 
      if(!rst_n_i)                                
          data_ref <= 16'b0 ;
       else
          data_ref <= mem2[i];     //读入数据
 
always@(posedge clk_i or negedge rst_n_i) 
      if(!rst_n_i)
         i <= 12'd0;
       else
         i <= i + 1'd1;
endmodule

整体RTL视图:

仿真:

 在这里我们可以看到data_in信号经过滤波器后,输出的data_o和期望信号data_ref的波形(已近很接近了),为什么输出不是一个完美的正弦波,其原因如下:1.步长因子。(在FPGA实现中我都步长因子设定为常数3)2.仿真时间(由于fpga的仿真时间一般都是以ns为单位的,滤波器的系数还没有迭代出一个完美系数)

(以上就是全部了,希望大佬们的指正。)

### 回答1: LMS算法是一种常见的自适应滤波算法,用于解决信号处理中的相关问题。FPGA是一种可编程逻辑器件,可用于实现各种数字电路和系统,具有灵活性和高效性。 LMS算法的FPGA代码实现,通常包括以下步骤: 1. 定义输入和输出的数据格式和接口:根据实际应用场景,确定输入和输出数据的格式和接口,包括数据位宽、数据类型、时钟频率等。 2. 初始化LMS算法参数:初始化算法需要的各种参数,如滤波器系数、均方误差、步长等。 3. 实现LMS算法核心部分:根据LMS算法的数学模型,在FPGA中实现算法的核心部分,包括输入信号、更新滤波器系数、计算误差和输出滤波结果等。 4. 根据实际场景优化算法:根据实际应用场景,对算法进行优化和改进,如采用定点算法、调整步长、增加滤波器阶数等,以提高处理速度和滤波效果。 5. 调试和测试:在FPGA上运行算法代码,并通过实验验证算法的正确性和效果。 以上是LMS算法FPGA代码实现的基本步骤。值得注意的是,FPGA代码的实现需要根据实际场景和应用需求进行定制,因此不同的场景和需求可能需要有不同的实现方案。 ### 回答2: LMS算法是一种用于数字信号处理中的自适应滤波算法,它可以对输入信号进行滤波并输出滤波后的信号。FPGA是一种可编程逻辑器件,它可以根据用户的需求设定具体的逻辑电路,实现各种不同的应用。在LMS算法中,FPGA可用于实现算法核心,加速计算过程,提高算法效率,并且可实现算法的实时性。 对于LMS算法FPGA代码的实现,需要掌握FPGA开发工具的使用方法,熟悉Verilog HDL或VHDL等硬件描述语言,以及了解数字信号处理的基本原理和算法流程。一般来讲,代码的实现分为三个主要部分:输入处理、滤波计算和输出处理。在输入部分,需要将信号从外部输入到FPGA中,同时对信号进行采样和量化;在滤波计算部分,需要根据LMS算法的流程,实现适当的乘法、加法、取反等运算,并且根据需要添加适当的缓存器或调整滤波器长度等参数;在输出处理部分,需要将经过滤波计算后的信号重新输出到外部设备中。 总的来说,LMS算法FPGA代码的实现相对来说比较复杂,需要有一定的电路设计和编程经验,同时具备一定的信号处理与算法理论基础。个人建议初学者首先掌握硬件描述语言和FPGA开发工具的基础使用方法,然后再逐步掌握数字信号处理和LMS算法的基本原理与流程,并且将理论运用于具体的代码实现中。 ### 回答3: LMS算法是一种常用的自适应信号处理算法,常用于噪声抑制、系统辨识等领域。FPGA作为一种可编程逻辑器件,其硬件加速能力和低功耗的特性与LMS算法的计算密集特性相吻合,因此在实际应用中,FPGA被广泛用于LMS算法的实现。 对于LMS算法的FPGA代码实现,具体实现流程可以分为以下几个步骤:首先,将LMS算法的数学表达式转换为硬件级别的逻辑表达式;其次,针对LMS算法的特性,对FPGA逻辑电路进行合理的设计和优化,尽可能降低功耗和时延;最后,根据硬件电路设计结果,编写针对FPGA平台的Verilog/VHDL代码,并进行功能仿真、逻辑综合和多周期时序约束等各阶段的电路设计流程。 在实际工程中,LMS算法的FPGA代码可以应用于各种自适应信号处理场合,如噪声抑制、语音识别和通信信道均衡等。对于不同的应用场景,可以根据硬件资源和性能要求等方面进行适当的调整和优化,以满足场景下的要求。
评论 30
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值