2021寒假在家一起练4——吴肖龙
本项目为小脚丫综合训练板项目,主要实现时间温度测量、OLED显示、蜂鸣器播放音乐,数据通信等功能。
标签
FPGA
bit1120180783
更新2021-03-01
1264

项目任务需求

  1. 实现一个可定时时钟的功能,用小脚丫FPGA核心模块的4个按键设置当前的时间,OLED显示数字钟的当前时间,精确到分钟即可,到整点的时候比如8:00,蜂鸣器报警,播放音频信号,最长可持续30秒;

  2. 实现温度计的功能,小脚丫通过板上的温度传感器实时测量环境温度,并同时间一起显示在OLED的屏幕上;

  3. 定时时钟整点报警的同时,将温度信息通过UART传递到电脑上,电脑上能够显示当前板子上的温度信息(任何显示形式都可以),要与OLED显示的温度值一致;

  4. PC收到报警的温度信号以后,将一段音频文件(自己制作,持续10秒钟左右)通过UART发送给小脚丫FPGA,蜂鸣器播放收到的这段音频文件,OLED屏幕上显示的时间信息和温度信息都停住不再更新;

  5. 音频文件播放完毕,OLED开始更新时间信息和当前的温度信息

项目实现过程

FoJ6CUX5Kb-hfjRGDYHdqvzCJ7Vb

模块功能设计

main主程序

在main界面,主要任务为调整好各信号间的输入输出关系以及pin脚的使用,下面仅显示部分代码,后续内容仅为导入其他模块的内容。

module xjy_main(
	input clk,
	
	input key_hour,//小时+
	input key_min_a,//分钟+
	input key_min_m,//分钟-
	input rst_n,//复位00
	
	input	uart_rxd,				
	input   uart_on,
	
	inout tone_en,
	inout one_wire,                 
	
	output				oled_csn,	//使能信号	
	output				oled_rst,	//复位信号	
	output				oled_dcn,	//指令控制信号
	output				oled_clk,	//时钟信号
	output				oled_dat,	//数据信号

	
	output 				uart_out,	
	output				beeper      
);
wire[15:0] 	d_out;
wire[3:0]	temp1,temp2,temp3,temp4;	
wire[3:0]	hour1,hour2,min1,min2,sec1,sec2;
wire		flag;

OLED模块

在OLED模块处的任务为显示数据信息,根据显示情况表在128*32的点阵上显示字符,由时钟控制OLED屏幕进行定时的刷新,此外OLED要保持好与时钟模块和温度传感模块的数据信息传递,代码基本信息参考电子森林。

https://www.eetree.cn/wiki/oled_spi_verilog

				MAIN:begin
						if(cnt_main >= 5'd6) cnt_main <= 5'd5;
						else cnt_main <= cnt_main + 1'b1;
						case(cnt_main)	//MAIN状态
							5'd0:	begin state <= INIT; end
								5'd1:	begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "TIME:           ";						 state <= SCAN; end
								5'd2:	begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "TEMP:           ";						 state <= SCAN; end	
								5'd3:	begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";						 state <= SCAN; end
								5'd4:	begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";						 state <= SCAN; end	
							
							5'd5:	begin 
									y_p <= 8'hb0; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd 5; char <= {4'd0, hour1, 4'd0,hour2, ":", 4'd0,min1, 4'd0, min2}; state <= SCAN; 									
									end
                            5'd6:if(temp1 == 4'd2)						
									begin 
									y_p <= 8'hb3; x_ph <= 8'h12; x_pl <= 8'h08; num <= 5'd 6; char <= {"-", 4'd0, temp3, 4'd0, temp4, ".", 4'd0, temp1,8'h7b,"C"}; state <= SCAN; 									
									end
								else if(temp2 == 4'd1)
									begin 
									y_p <= 8'hb3; x_ph <= 8'h12; x_pl <= 8'h08; num <= 5'd 6; char <= {4'd0, temp2, 4'd0, temp3, 4'd0, temp4, ".", 4'd0, temp1,8'h7b,"C"}; state <= SCAN; 									
									end
								else 
									begin 
									y_p <= 8'hb3; x_ph <= 8'h12; x_pl <= 8'h08; num <= 5'd 6; char <= {4'd0, temp3, 4'd0, temp4, ".", 4'd0, temp1,8'h7b,"C"}; state <= SCAN; 									
									end
							default: state <= IDLE;
						endcase

					 end
				INIT:begin	//初始化状态
						case(cnt_init)
							5'd0:	begin oled_rst <= LOW; cnt_init <= cnt_init + 1'b1; end
							5'd1:	begin num_delay <= 16'd25000; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end	//延时大于3us
							5'd2:	begin oled_rst <= HIGH; cnt_init <= cnt_init + 1'b1; end
							5'd3:	begin num_delay <= 16'd25000; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end	//延时大于220us
							5'd4:	begin 
										if(cnt>=INIT_DEPTH) begin
											cnt <= 1'b0;
											cnt_init <= cnt_init + 1'b1;
										end else begin	
											cnt <= cnt + 1'b1; num_delay <= 16'd5;
											oled_dcn <= CMD; char_reg <= cmd[cnt]; state <= WRITE; state_back <= INIT;
										end
									end
							5'd5:	begin cnt_init <= 1'b0; state <= MAIN; end	//初始化完成,返回MAIN状态
							default: state <= IDLE;
						endcase
					end

温度传感模块

温度传感模块主要利用FPGA驱动底板上的DS18B20Z单总线温度传感器进行温度数据的采集。在此环节中,我们完成对采集到的温度信息传送到OLED上显示。电子森林代码参考:

https://www.eetree.cn/wiki/temp_sensor_verilog

下面仅展示部分代码

				READ:begin		//按照DS18B20Z芯片单总线时序进行读操作
						if(cnt <= 3'd6) begin	//共需要接收8bit的数据,这里控制循环的次数
							if(cnt_read >= 3'd5) begin cnt_read <= 1'b0; cnt <= cnt + 1'b1; end
							else begin cnt_read <= cnt_read + 1'b1; cnt <= cnt; end
						end else begin
							if(cnt_read >= 3'd6) begin cnt_read <= 1'b0; cnt <= 1'b0; end	//两个变量都恢复初值
							else begin cnt_read <= cnt_read + 1'b1; cnt <= cnt; end
						end
						case(cnt_read)
							//读取 1bit 数据的用时在60~120us之间,总线拉低后15us时间内读取数据,参考数据手册
							3'd0: begin one_wire_buffer <= 1'b0; end	//总线拉低
							3'd1: begin num_delay <= 20'd2;state <= DELAY;state_back <= READ; end	//延时2us时间
							3'd2: begin one_wire_buffer <= 1'bz; end	//总线释放
							3'd3: begin num_delay <= 20'd5;state <= DELAY;state_back <= READ; end	//延时5us时间
							3'd4: begin temperature_buffer[cnt] <= one_wire; end	//读取DS18B20Z返回的总线数据,先收最低位
							3'd5: begin num_delay <= 20'd60;state <= DELAY;state_back <= READ; end	//延时60us时间
							//back to main
							3'd6: begin state <= MAIN; end	//返回MAIN状态
							default: state <= IDLE;
						endcase
					end
				DELAY:begin		//延时控制
						if(cnt_delay >= num_delay) begin	//延时控制,延时时间由num_delay指定
							cnt_delay <= 1'b0;
							state <= state_back; 	//很多状态都需要延时,延时后返回哪个状态由state_back指定
						end else cnt_delay <= cnt_delay + 1'b1;
					end

时钟控制与分频模块

在该部分一方面对于时间24小时进制,分钟60进制循环进行控制,另一方面基于12MHz的晶振进行所需周期频率clock信号的代码设计,内容基本与直播课上展示的一致,此基本主要参考了刘建伟同学的代码,部分代码如下:

always@(posedge clk1, negedge rst_n)
begin
	if(!rst_n)
	begin
		hour1_q <= 4'd0;
		hour2_q <= 4'd0; 
		min1_q <= 4'd0; 
		min2_q <= 4'd0; 
		sec1_q <= 4'd0; 
		sec2_q <= 4'd0;
	end
	else
	begin
		case({key_hour, key_min_a, key_min_m})
		3'b111:
		begin
			if(sec2_q < 9)
				sec2_q<= sec2_q + 1;
			else
			begin
				sec2_q <= 4'd0;
				if(sec1_q < 5)
					sec1_q <= sec1_q + 1;
				else
				begin
					sec1_q <= 4'd0;
					if(min2_q  < 9)
						min2_q  <= min2_q  + 1;
					else
					begin
						min2_q <= 4'd0;
						if(min1_q < 5)
							min1_q <= min1_q + 1;
						else
						begin
							min1_q <= 4'd0;
							if(hour2_q == 3 && hour1_q == 2)
							begin
								hour2_q <= 0;
								hour1_q <= 0;
							end
							else if(hour2_q < 9)
								hour2_q <= hour2_q + 1;
							else
							begin
								hour2_q<= 4'd0;
								if(hour1_q < 2)
									hour1_q <= hour1_q + 1;
								else
									hour1_q <= 0;
							end
						end
					end
				end
			end
		end
		3'b110://小时减少
		begin
			if(hour2_q > 0)
				hour2_q <= hour2_q - 1;
			else//five_now = 0
			begin
				if(hour1_q == 2)	
				begin
					hour2_q <= 9;
					hour1_q <= 1;
				end
				else if(hour1_q == 1)
				begin
					hour2_q <= 9;
					hour1_q <= 0;
				end	
				else//if(six_now == 0)
				begin
					hour2_q <= 3;
					hour1_q <= 2;
				end
			end
		end

		3'b101://分钟增加
		begin
			if(min2_q  > 0)
				min2_q  <= min2_q  - 1;
			else
			begin
				min2_q  <= 9;
				if(min1_q> 0)
					min1_q <= min1_q - 1;
				else
					min1_q<= 5;
			end
		end
		default://分钟减少
		begin
			if(min2_q  < 9)
				min2_q  <= min2_q  + 1;
			else
			begin
				min2_q  <= 0;
				if(min1_q < 5)
					min1_q <= min1_q + 1;
				else
					min1_q  <= 0;
			end
		end
		endcase
	end
end

蜂鸣器模块

该模块主要是根据自己所需要的音乐设置相应频率所对应的代码,通过不同频率的音符播放实现响铃功能。电子森林代码参考:

https://www.eetree.cn/wiki/%E8%9C%82%E9%B8%A3%E5%99%A8%E6%A8%A1%E5%9D%97

//设定音乐乐谱
always @(posedge clk) 
begin
	if(count1 < TIME)             
      count1 = count1 + 1'b1;
	else 
	begin
		count1 = 24'd0;
		if(state == 8'd63)
			state = 8'd0;
		else
			state = state + 1'b1;
			case(state)
				8'd0:count_end =M1;  
				8'd1:count_end=M1;
				8'd2:count_end=M2;
				8'D3:count_end=M1;
				8'D4:count_end=M5;
				8'D5:count_end=M3;
				8'D6:count_end=M5;
				8'D7:count_end=M1;
				8'D8:count_end=M1;
				8'D9:count_end=M2;
				8'D10:count_end=M3;
				8'D11:count_end=M5;
				8'D12,8'D13:count_end=M2;
				8'D14:count_end=L5;
				8'D15:count_end=L6;
				
				8'D16:count_end=L6;
				8'D17:count_end=L3;
				8'D18:count_end=M2;
				8'D19:count_end=M1;
				8'D20:count_end=M1;
				8'D21:count_end=L7;
				8'D22:count_end=M3;
				8'D23:count_end=M2;
				
				8'D24:count_end=M2;
				8'D25:count_end=L5;
				8'D26:count_end=L6;
				8'D27:count_end=L6;
				8'D28:count_end=M1;
				8'D29:count_end=M2;
				8'D30:count_end=M3;
				8'D31:count_end=M5;
				
				8'd32:count_end = M3;  
				8'd33:count_end=M2;
				8'd34:count_end=L6;
				8'D35:count_end=L6;
				8'D36:count_end=L7;
				8'D37:count_end=M2;
				8'D38:count_end=L6;
				8'D39:count_end=L5;
			   
				8'D40,8'D41:count_end=M5;
				8'D42:count_end=M5;
				8'D43:count_end=M5;
				8'D44:count_end=M6;
				8'D45:count_end=M3;
				8'D46:count_end=M5;
				8'D47:count_end=M3;
				
				8'D48:count_end=M2;
				8'D49:count_end=L7;
				8'D50:count_end=M5;
				8'D51:count_end=M5;
				8'D52:count_end=M3;
				8'D53:count_end=M2;
				8'D54:count_end=L5;
				8'D55:count_end=M1;
				
				8'D56:count_end=M2;
				8'D57:count_end=M1;
				8'D58:count_end=L6;
				8'D59:count_end=M1;
				8'D60:count_end=M2;
				8'D61:count_end=M3;
				8'D62:count_end=M6;
				8'D63:count_end=M5;

				default: count_end = 16'h0;
			endcase
	end
end

BCD码模块

将温度信息以BCD码的形式进行输入,代码参考杨晓楠同学:

always@*
begin
	scanch = data_in;
	if(scanch[15:12] == 4'b1111)
	begin
		scanch = ~scanch + 1'b1;
		d_outq2 = 4'd2;
	end
	else
		d_outq2 = 4'b0;
	if(scanch >> 8 >= 4'b0110)
	begin
		d_outq2 = 4'b1;
		scanch = scanch- 12'b0110_0100_0000;
	end		
	if(scanch>>4 >= 8'b0101_1010)
	begin
		d_outq3  = 4'd9;
		scanch = scanch - 12'b0101_1010_0000;
	end
	else if(scanch>>4 >= 8'b0101_0000)
	begin
		d_outq3  = 4'd8;
		scanch = scanch - 12'b0101_0000_0000;
	end
	else if(scanch>>4 >= 8'b0100_0110)
	begin
		d_outq3  = 4'd7;
		scanch = scanch - 12'b0100_0110_0000;
	end
	else if(scanch>>4 >= 8'b0011_1100)
	begin
		d_outq3  = 4'd6;
		scanch = scanch - 12'b0011_1100_0000;
	end
	else if(scanch>>4 >= 8'b0011_0010)
	begin
		d_outq3  = 4'd5;
		scanch = scanch - 12'b0011_0010_0000;
	end
	else if(scanch>>4 >= 8'b0010_1000)
	begin
		d_outq3  = 4'd4;
		scanch = scanch - 12'b0010_1000_0000;
	end
	else if(scanch>>4 >= 8'b0001_1110)
	begin
		d_outq3 = 4'd3;
		scanch = scanch - 12'b0001_1110_0000;
	end
	else if(scanch>>4 >= 8'b0001_0100)
	begin
		d_outq3  = 4'd2;
		scanch = scanch- 12'b0001_0100_0000;
	end
	else if(scanch>>4 >= 8'b0000_1010)
	begin
		d_outq3  = 4'd1;
		scanch = scanch - 12'b0000_1010_0000;
	end
	else
		d_outq3  = 4'd0;
	if(scanch >>4 >= 4'd9)
	begin
		d_outq4  = 4'd9;
		scanch = scanch - 4'd9;
	end
	else if(scanch >>4 >= 4'd8)
	begin
		d_outq4  = 4'd8;
		scanch = scanch - 4'd8;
	end
	else if(scanch >>4 >= 4'd7)
	begin
		d_outq4  = 4'd7;
		scanch = scanch - 4'd7;
	end
	else if(scanch >>4 >= 4'd6)
	begin
		d_outq4  = 4'd6;
		scanch = scanch - 4'd6;
	end
	else if(scanch >>4 >= 4'd5)
	begin
		d_outq4  = 4'd5;
		scanch = scanch - 4'd5;
	end
	else if(scanch >>4 >= 4'd4)
	begin
		d_outq4  = 4'd4;
		scanch = scanch - 4'd4;
	end
	else if(scanch >>4 >= 4'd3)
	begin
		d_outq4  = 4'd3;
		scanch = scanch - 4'd3;
	end
	else if(scanch >>4 >= 4'd2)
	begin
		d_outq4  = 4'd2;
		scanch = scanch - 4'd2;
	end
	else if(scanch >>4 >= 4'd1)
	begin
		d_outq4  = 4'd1;
		scanch = scanch - 4'd1;
	end
	else
		d_outq4  = 4'd0;
	scanch = (scanch <<12) >> 12;
	if(scanch >= 4'd14)
		d_outq1 = 4'd9;
	else if(scanch >= 13)
		d_outq1 = 4'd8;
	else if(scanch >= 11)
		d_outq1 = 4'd7;
	else if(scanch >= 9)
		d_outq1 = 4'd6;
	else if(scanch >= 8)
		d_outq1 = 4'd5;
	else if(scanch >= 6)
		d_outq1 = 4'd4;
	else if(scanch >= 4)
		d_outq1 = 4'd3;
	else if(scanch >= 3)
		d_outq1= 4'd2;
	else if(scanch >= 1)
		d_outq1 = 4'd1;
	else
		d_outq1 = 4'd0;
		
	
end

串口发送接收模块

主要利用第三方串口调试工具进行,在这里使用了XCOM工具2.6版本,故在代码中主要完成接口的匹配和数据的线路控制以及存储即可。该模块主要参考了李卓然同学的代码。

module uart_send
(
   input           clk_in,
   input   [3:0]   temp1,temp3,temp4,
   input   [3:0]   hour1,hour2,min1,min2,sec1,sec2,
   input			uart_sw,//发送串口开关
   
   output reg		uart_out//输出
);

localparam IDLE = 2'b0;
localparam SEND = 2'b1;

reg clk_uart;
reg[9:0] times;
always @(posedge clk_in) begin
    if(times < 625)
        times = times+1;
    else begin
        clk_uart = ~clk_uart;
        times = 0;
    end
end

reg clk_en;
reg [23:0] clk_en_cnt;
always @(posedge clk_in) begin
   if (clk_en_cnt <= 12_000_000) begin
       clk_en_cnt = clk_en_cnt + 1;
   end else begin
       clk_en_cnt = 0;
       clk_en = ~clk_en;
   end
end
reg flag_1,flag_2,state;
reg [120:0] uart_data;
reg [7:0] i;
always @(posedge clk_en) 
begin
	if((sec1 == 2 && sec2 == 0 &&min1 == 0 && min2 == 0 && uart_sw) || (temp3 >= 3 && temp4 >= 4))
	begin	
		uart_data = {  //输送串口数据
			  1'd1,8'd13,1'd0,                  
			  //1'd1,8'd10,1'd0,                  
			  1'd1,4'd3,min2,1'd0,       
			  1'd1,4'd3,min1,1'd0,     
			  1'd1,8'd58,1'd0,                  
			  1'd1,4'd3,hour2,1'd0,      
			  1'd1,4'd3,hour1,1'd0,     
			  1'd1,8'd32,1'd0,                   
			  1'd1,8'd67,1'd0,                   
			  1'd1,4'd3,temp1,1'd0,         	
			  1'd1,8'd46,1'd0,                  
			  1'd1,4'd3,temp4,1'd0,          
			  1'd1,4'd3,temp3,1'd0,          
			  1'd1,1'd1
		  };
		flag_1 = ~flag_1;
	end
end 

always @(posedge clk_uart) begin
    case(state)
        IDLE: begin
            if(flag_2 != flag_1) begin
                flag_2 = flag_1;
                state = SEND;
            end
        end
        SEND: begin
            if(i < 122) begin
                uart_out = uart_data[i];
                i = i+1;
            end else begin
                i = 0;
                state = IDLE;
            end
        end  
    endcase
end

endmodule
module uart_receive(
    input			  clk,                 //系统时钟
    input             rst_n,                //系统复位  
    input             uart_rxd,                 //UART接收端口

    output  reg       uart_en,                
    output  reg [7:0] uart_data                 

    );
    
   //时钟分频
    parameter  CLK_FREQ = 12_000_000;                 
    parameter  UART_BPS = 9600;                     
    localparam BPS_CNT  = CLK_FREQ/UART_BPS;        
                                                    
    //时钟计数
    reg        uart_rxd0;
    reg        uart_rxd1;
    reg [15:0] clk_cnt;                             
    reg [ 3:0] rx_cnt;                              
    reg        rx_flag;                             
    reg [ 7:0] rxdata;                              

 
   

     wire       wflag;
    assign  wflag = uart_rxd1 & (~uart_rxd0);    

   
    always @(posedge clk or negedge rst_n) begin 
        if (!rst_n) begin 
            uart_rxd0 <= 1'b0;
            uart_rxd1 <= 1'b0;          
        end
        else begin
            uart_rxd0  <= uart_rxd;                   
            uart_rxd1  <= uart_rxd0;
        end   
    end
       
    always @(posedge clk or negedge rst_n) begin         
        if (!rst_n)                                  
            rx_flag <= 1'b0;
        else begin
            if(wflag)                          
                rx_flag <= 1'b1;                    
            else if((rx_cnt == 4'd9)&&(clk_cnt == BPS_CNT/2))
                rx_flag <= 1'b0;                   
            else
                rx_flag <= rx_flag;
        end
    end

    
    always @(posedge clk or negedge rst_n) begin         
        if (!rst_n) begin                             
            clk_cnt <= 16'd0;                                  
            rx_cnt  <= 4'd0;
        end                                                      
        else if ( rx_flag ) begin                   
                if (clk_cnt < BPS_CNT - 1) begin
                    clk_cnt <= clk_cnt + 1'b1;
                    rx_cnt  <= rx_cnt;
                end
                else begin
                    clk_cnt <= 16'd0;               
                    rx_cnt  <= rx_cnt + 1'b1;       
                end
            end
            else begin                              
                clk_cnt <= 16'd0;
                rx_cnt  <= 4'd0;
            end
    end

    
    always @(posedge clk or negedge rst_n) begin 
        if ( !rst_n)  
            rxdata <= 8'd0;                                     
        else if(rx_flag)                            
            if (clk_cnt == BPS_CNT/2) begin         
                case ( rx_cnt )
                4'd1 : rxdata[0] <= uart_rxd1;  
                4'd2 : rxdata[1] <= uart_rxd1;
                4'd3 : rxdata[2] <= uart_rxd1;
                4'd4 : rxdata[3] <= uart_rxd1;
                4'd5 : rxdata[4] <= uart_rxd1;
                4'd6 : rxdata[5] <= uart_rxd1;
                4'd7 : rxdata[6] <= uart_rxd1;
                4'd8 : rxdata[7] <= uart_rxd1;   
                default:;                                    
                endcase
            end
            else 
                rxdata <= rxdata;
        else
            rxdata <= 8'd0;
    end
    //数据接收并寄存输出接收到的数据
    always @(posedge clk or negedge rst_n) begin        
        if (!rst_n) begin
            uart_data <= 8'd0;                               
            uart_en <= 1'b0;
        end
        else if(rx_cnt == 4'd9) begin                          
            uart_data <= rxdata;                    
            uart_en <= 1'b1;                      
        end
        else begin
            uart_data <= 8'd0;                                   
            uart_en<= 1'b0; 
        end    
    end

endmodule	

项目难题与进展

我本人对于FPGA完全不了解,从0开始相对有些不习惯,对于verilog的语法编写,对Diamond软件的使用都显得极为不熟练。

对于项目目标而言,所有数据的采集、显示、收发功能都基本实现,由于在操作中不够熟练,经常会长期使用综合训练板,导致温度长期偏高且跨度很大,故任务目标4中对于达到报警温度的数据控制并未实现,但后续功能以及完成。

在实验过程中,由于一开始对于XCOM工具的版本使用不当,经常在数据收发环节出现问题,耽误了很多的时间。

未来计划与期望

完全实现所有预期功能。

解决目前明显的延迟问题和加入按键防抖功能。

附件下载
XCOM V2.6.rar
xcom串口调试工具
xjy.rar
完整工程内容
团队介绍
北京理工大学信息与电子学院
团队成员
吴肖龙
好好学习天天向上
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号