IIC通信方式的详解

Inter-IntegratedCircuit(集成电路总线)

IIC是一种多向控制总线,由飞利浦半导体公司在八十度年代初设计,主要是用来连接整体电路(ICS)。在IIC中多个芯片可以连接到同一总线结构下,同时每个芯片都可以作为实施数据传输的控制源,这种方式简化了信号传容输总线。

  1. IIC的物理层
    典型电路如下:
    IIC通信方式的详解_第1张图片

  2. IIC协议层
    IIC通信方式的详解_第2张图片
    IIC通信方式的详解_第3张图片
    每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据,从机应答主机所需要的时钟仍是主机提供的,应答出现在每一次主机完成8个数据位传输后紧跟着的时钟周期,低电平0表示应答,1表示非应答。

  3. 数据帧格式
    IIC通信方式的详解_第4张图片
    每次数据传送总是由主机产生的终止信号结束。但是,若主机希望继续占用总线进行新的数据传送,则可以不产生终止信号,马上再次发出起始信号对另一从机进行寻址。
    在这里插入图片描述

  4. 传输信号时序图及时序参数
    IIC通信方式的详解_第5张图片IIC通信方式的详解_第6张图片

  5. AT24CXX系列的介绍
    (1)写操作
    AT24CXX系列的EEPROM为了提高写效率,提供了页写功能,内部有个一页大小的写缓冲RAM,地址范围当然就是从00到一页大小,发生写操作时,开始送入的地址对应的页被选中,并将其内容映像到缓冲RAM,数据从低端地址对应的缓冲RAM地址开始修改,超过这个地址范围就回到00,写完后,就会把开始确定的EEPROM页擦除,再把一整页RAM数据写入。所有写数据都发生在开始写地址时确定的页上。
    24C01、24C02 这两个型号是 8 个字节一个页,而 24C04、24C08、24C16 是 16 个字节一页。以页容量128为例,一页都是从00开始按128字节分成一个个的页,0页就是0—7F,1页就是80—FF,类推,边界就是128字节的整数倍地址。页RAM的地址范围为7位00—7F,写入时高端地址就是页号。发生写操作,开始送入的地址对应的页被锁存,后续不论写多少,都在这个页中,只是一个页内的地址进行加一,超过就归零开始。从F0开始写32个字节,那么开始送入的地址为F0,就会锁定在1号页(第2个页)上,底端7位页内部地址开始从70H开始写,到达7F时回到00再到10H,也就是写在了F0—FF,80—8F。也就是,从01开始写也只能到7F,再往80写就跑到00上去了,这就是写操作的翻卷,datasheet上都有说明。就是从边界前写两个字节也要分两次写。页是绝对的,按整页大小排列,不是从开始写入的地址开始算。
    (2)读操作
    读没有页的问题,可以从任意地址开始读取任意大小数据,只是超过整个存储器容量时地址才回卷。但一次性访问的数据长度也不要太大。

  6. Verliog代码设计
    在正点原子的三段式IIC驱动代码的基础上进行修改,实现对AT24C64的多字节读写操作。这里需要格外注意的有两点:
    (1)写操作完成最后一个字节数据后是从机产生应答;读操作完成最后一个字节数据后是主机产生应答。
    (2)分页的存储器要做好存储器管理,尽量将同时读写的数据放在一个页上。
    代码如下:

    `timescale 1ns/1ps
    
    module iic_driver
    #(// slave address(器件地址),放此处方便参数传递
    	parameter   SLAVE_ADDR =  7'b1010000   ,
    	parameter   CLK_FREQ   = 26'd50_000_000,   // iic_dri模块的驱动时钟频率(CLK_FREQ)
    	parameter   IIC_FREQ   = 18'd250_000       // IIC的SCL时钟频率
    )
    (
    	input 				CLK,      		// iic_dri模块的驱动时钟(CLK_FREQ)
    	input 				RST_N,     		// 复位信号
    	input				iic_exec,		// IIC触发执行信号
    	input				bit_ctrl,		// 字地址位控制(16b/8b)
    	input				iic_rh_wl,		// IIC读写控制信号(1:read 0:write)
    	input 		[15:0] 	iic_addr,     	// IIC器件内地址
    	input 		[ 7:0] 	iic_data_w,    	// IIC要写的数据
    	
    	output reg 	[ 7:0] 	iic_data_r,		// IIC读出的数据
    	output wire 		done,    		// IIC一次操作完成
    	output reg 			SCL,     		// IIC的SCL时钟信号
    	inout 				SDA,     		// IIC的SDA信号
    	
    	input 		[ 7:0] 	wdata_num,    	// IIC要写数据的个数
    	input 		[ 7:0] 	rdata_num,    	// IIC要读数据的个数
    	output wire			wr_data_vaild,  // IIC写入数据有效
    	output wire			rd_data_vaild,  // IIC读出数据有效
    	output reg 			dri_clk       	// 驱动IIC操作的驱动时钟
    
    );
    
    //localparam define
    localparam  st_idle     = 8'b0000_0001;          // 空闲状态						1							
    localparam  st_sladdr   = 8'b0000_0010;          // 发送器件地址(slave address)	2
    localparam  st_addr16   = 8'b0000_0100;          // 发送16位字地址					4
    localparam  st_addr8    = 8'b0000_1000;          // 发送8位字地址					8
    localparam  st_data_wr  = 8'b0001_0000;          // 写数据(8 bit)				16
    localparam  st_addr_rd  = 8'b0010_0000;          // 发送器件地址读					32
    localparam  st_data_rd  = 8'b0100_0000;          // 读数据(8 bit)				64
    localparam  st_stop     = 8'b1000_0000;          // 结束IIC操作					128
    
    //reg define
    reg 			sda_dir;				// I2C数据(SDA)方向控制
    reg 			sda_out;				// SDA输出信号
    reg 			st_done;				// 状态结束
    reg				iic_exec_r;				// 起始信号寄存器
    reg 			wr_flag;				// 写标志
    reg [6:0] 		cnt;					// 计数
    reg [7:0] 		cur_state;			// 状态机当前状态
    reg [7:0] 		next_state;			// 状态机下一状态
    reg [15:0] 		addr_t;				// 地址
    reg [7:0] 		data_r;				// 读取的数据
    reg [9:0] 		clk_cnt;			// 分频时钟计数
    reg 	 		ack;				// SDA应答
    reg	[7:0]		wdata_cnt;			// 写数据计数器
    reg	[7:0]		rdata_cnt;			// 读数据计数器
    reg	[1:0]		wr_data_vaild_r;  	// IIC写入数据有效
    reg	[1:0]		rd_data_vaild_r; 	// IIC写入数据有效
    reg				iic_done;
    reg	[1:0]		iic_done_r;
    //wire define
    wire 			sda_in;				// SDA输入信号
    wire [7:0] 		data_wr_t;			// IIC需写的数据保存
    wire [8:0] 		clk_divide;			// 模块驱动时钟的分频系数
    
    
    //SDA控制
    assign  SDA = sda_dir ?  (sda_out ? 1'bz :1'b0 ) : 1'bz;	
    assign  sda_in = SDA ;							// SDA数据输入
    assign  clk_divide = (CLK_FREQ/IIC_FREQ) >> 3;	// 模块驱动时钟的分频系数
    
    //寄存1个时钟周期的起始信号
    always @(posedge CLK or negedge RST_N) 
    begin
    	if(!RST_N)
    		iic_exec_r <= 1'b0;
    	else if(iic_exec)
    		iic_exec_r <= 1'b1;
    	else if(iic_done)
    		iic_exec_r <= 1'b0;
    	else 
    		iic_exec_r <= iic_exec_r;
    end
    
    //生成IIC的SCL的四倍频率(1M)的驱动时钟用于驱动i2c的操作
    always @(posedge CLK or negedge RST_N) 
    begin
        if(!RST_N) 
    		begin
    			dri_clk <=  1'b1;
    			clk_cnt <= 10'd0;
    		end
        else if(clk_cnt == clk_divide - 1'd1) 	//	F=50M/(25*2)=1M
    		begin
    			clk_cnt <= 10'd0;
    			dri_clk <= ~dri_clk;
    		end
        else
            clk_cnt <= clk_cnt + 1'b1;
    end
    
    //(三段式状态机)同步时序描述状态转移
    always @(posedge dri_clk or negedge RST_N) begin
        if(!RST_N)
            cur_state <= st_idle;
        else
            cur_state <= next_state;
    end
    
    //组合逻辑判断状态转移条件
    always @( * ) begin
    	case(cur_state)
    		st_idle: begin                           	// 空闲状态
    			if(iic_exec_r) 
    			begin
    				next_state = st_sladdr;
    			end
    			else
    				next_state = st_idle;
    			end
    		st_sladdr: begin
    			if(st_done) 
    			begin
    				if(bit_ctrl)                     	// 判断是16位还是8位字地址
    					next_state = st_addr16;
    				else
    					next_state = st_addr8 ;
    			end
    			else
    				 next_state = st_sladdr;
    		end
    		st_addr16: begin                         	// 写16位字地址
    			if(st_done) 
    			begin
    				next_state = st_addr8;
    			end
    			else 
    			begin
    				next_state = st_addr16;
    			end
    		end
    		st_addr8: begin                          	// 8位字地址
    			if(st_done)
    			begin
    				if(wr_flag==1'b0)               	// 读写判断
    					next_state = st_data_wr;
    					
    				else
    					next_state = st_addr_rd;
    			end
    			else  
    			begin
    				next_state = st_addr8;
    			end
    		end
    		st_data_wr: begin                        	// 写数据(8 bit)
    			if(st_done)
    			begin
    				next_state = st_stop;
    			end
    			else
    				next_state = st_data_wr;
    		end
    		st_addr_rd: begin                        	// 写地址以进行读数据
    			if(st_done) 
    			begin
    				next_state = st_data_rd;
    			end
    			else 
    				next_state = st_addr_rd;
    		end
    		st_data_rd: begin                        	// 读取数据(8 bit)
    			if(st_done)
    				next_state = st_stop;
    
    			else
    				next_state = st_data_rd;
    		end
    		st_stop: begin                           	// 结束IIC操作
    			if(st_done)
    				next_state = st_idle;
    			else
    				next_state = st_stop ;
    		end
    		default: 
    				next_state = st_idle;
        endcase
    	end
    
    //时序电路描述状态输出
    always @(posedge dri_clk or negedge RST_N) begin
        //复位初始化
    	if(!RST_N) begin
    	  SCL       <= 1'b1;
    	  sda_out	<= 1'b1;
    	  sda_dir   <= 1'b1;
    	  iic_done  <= 1'b0;
    	  cnt       <= 1'b0;
    	  st_done   <= 1'b0;
    	  data_r    <= 1'b0;
    	  iic_data_r<= 1'b0;
    	  wr_flag   <= 1'b0;
    	  addr_t    <= 1'b0;
    	  ack		<= 1'b1;
    	  wdata_cnt <= 1'b0;
    	  rdata_cnt <= 1'b0;
    	end
    	else begin
            st_done <= 1'b0 ;
            cnt     <= cnt +1'b1 ;
            case(cur_state)
    			st_idle: begin                            // 空闲状态
    				SCL     <= 1'b1;
                    sda_out <= 1'b1;
                    sda_dir <= 1'b1;
                    iic_done<= 1'b0;
                    cnt     <= 7'b0;
                    if(iic_exec_r) begin
                        wr_flag   <= iic_rh_wl ;
                        addr_t    <= iic_addr  ;
                    end 
    			end
                st_sladdr: begin                           // 写地址(器件地址和字地址)
                    case(cnt)
                        7'd1 : sda_out <= 1'b0;            // 开始IIC   起始位
                        7'd3 : SCL <= 1'b0;
                        7'd4 : sda_out <= SLAVE_ADDR[6];   // 传送器件地址  SCL低电平中间改变数据
                        7'd5 : SCL <= 1'b1;
                        7'd7 : SCL <= 1'b0;
                        7'd8 : sda_out <= SLAVE_ADDR[5];
                        7'd9 : SCL <= 1'b1;
                        7'd11: SCL <= 1'b0;
                        7'd12: sda_out <= SLAVE_ADDR[4];
                        7'd13: SCL <= 1'b1;
                        7'd15: SCL <= 1'b0;
                        7'd16: sda_out <= SLAVE_ADDR[3];
                        7'd17: SCL <= 1'b1;
                        7'd19: SCL <= 1'b0;
                        7'd20: sda_out <= SLAVE_ADDR[2];
                        7'd21: SCL <= 1'b1;
                        7'd23: SCL <= 1'b0;
                        7'd24: sda_out <= SLAVE_ADDR[1];
                        7'd25: SCL <= 1'b1;
                        7'd27: SCL <= 1'b0;
                        7'd28: sda_out <= SLAVE_ADDR[0];
                        7'd29: SCL <= 1'b1;
                        7'd31: SCL <= 1'b0;
                        7'd32: sda_out <= 1'b0;            // 0:写
                        7'd33: SCL <= 1'b1;
                        7'd35: SCL <= 1'b0;
                        7'd36: begin
                            sda_dir <= 1'b0; ack <= sda_in; // 从机应答
                        end
                        7'd37: SCL     <= 1'b1;
                        7'd38: st_done <= 1'b1;
                        7'd39: begin
                            SCL <= 1'b0; cnt <= 1'b0;
                        end
                        default :  ;
                    endcase
                end
                st_addr16: begin
                    case(cnt)
                        7'd0 : begin
                            sda_dir <= 1'b1; sda_out <= addr_t[15]; // 传送字地址        
                        end
                        7'd1 : SCL <= 1'b1;
                        7'd3 : SCL <= 1'b0;
                        7'd4 : sda_out <= addr_t[14];
                        7'd5 : SCL <= 1'b1;
                        7'd7 : SCL <= 1'b0;
                        7'd8 : sda_out <= addr_t[13];
                        7'd9 : SCL <= 1'b1;
                        7'd11: SCL <= 1'b0;
                        7'd12: sda_out <= addr_t[12];
                        7'd13: SCL <= 1'b1;
                        7'd15: SCL <= 1'b0;
                        7'd16: sda_out <= addr_t[11];
                        7'd17: SCL <= 1'b1;
                        7'd19: SCL <= 1'b0;
                        7'd20: sda_out <= addr_t[10];
                        7'd21: SCL <= 1'b1;
                        7'd23: SCL <= 1'b0;
                        7'd24: sda_out <= addr_t[9];
                        7'd25: SCL <= 1'b1;
                        7'd27: SCL <= 1'b0;
                        7'd28: sda_out <= addr_t[8];
                        7'd29: SCL <= 1'b1;
                        7'd31: SCL <= 1'b0;
                        7'd32: begin
                            sda_dir <= 1'b0; ack <= sda_in ;              // 从机应答     
                        end
                        7'd33: SCL     <= 1'b1;
                        7'd34: st_done <= 1'b1;
                        7'd35: begin
                            SCL <= 1'b0; cnt <= 1'b0;
                        end
                        default :  ;
                    endcase
                end
                st_addr8: begin
                    case(cnt)
                        7'd0: begin
                           sda_dir <= 1'b1; sda_out <= addr_t[7]; // 字地址       
                        end
                        7'd1 : SCL <= 1'b1;
                        7'd3 : SCL <= 1'b0;
                        7'd4 : sda_out <= addr_t[6];
                        7'd5 : SCL <= 1'b1;
                        7'd7 : SCL <= 1'b0;
                        7'd8 : sda_out <= addr_t[5];
                        7'd9 : SCL <= 1'b1;
                        7'd11: SCL <= 1'b0;
                        7'd12: sda_out <= addr_t[4];
                        7'd13: SCL <= 1'b1;
                        7'd15: SCL <= 1'b0;
                        7'd16: sda_out <= addr_t[3];
                        7'd17: SCL <= 1'b1;
                        7'd19: SCL <= 1'b0;
                        7'd20: sda_out <= addr_t[2];
                        7'd21: SCL <= 1'b1;
                        7'd23: SCL <= 1'b0;
                        7'd24: sda_out <= addr_t[1];
                        7'd25: SCL <= 1'b1;
                        7'd27: SCL <= 1'b0;
                        7'd28: sda_out <= addr_t[0];
                        7'd29: SCL <= 1'b1;
                        7'd31: SCL <= 1'b0;
                        7'd32: begin
                            sda_dir <= 1'b0; ack <= sda_in ;              // 从机应答
                        end
                        7'd33: SCL     <= 1'b1;
                        7'd34: st_done <= 1'b1;
                        7'd35: begin
                            SCL <= 1'b0;
                            cnt <= 1'b0;
                        end
                        default :  ;
                    endcase
                end
                st_data_wr: begin                         			 // 写数据(8 bit)
                    case(cnt)
                        7'd0 :begin 
    						sda_dir <= 1'b1; sda_out <= iic_data_w[7];
    					end
                        7'd1 : SCL <= 1'b1; 
                        7'd3 : SCL <= 1'b0;
                        7'd4 : sda_out <= iic_data_w[6];
                        7'd5 : SCL <= 1'b1;
                        7'd7 : SCL <= 1'b0;
                        7'd8 : sda_out <= iic_data_w[5];
                        7'd9 : SCL <= 1'b1;
                        7'd11: SCL <= 1'b0;
                        7'd12: sda_out <= iic_data_w[4];
                        7'd13: SCL <= 1'b1;
                        7'd15: SCL <= 1'b0;
                        7'd16: sda_out <= iic_data_w[3];
                        7'd17: SCL <= 1'b1;
                        7'd19: SCL <= 1'b0;
                        7'd20: sda_out <= iic_data_w[2];
                        7'd21: SCL <= 1'b1;
                        7'd23: SCL <= 1'b0;
                        7'd24: sda_out <= iic_data_w[1];
                        7'd25: SCL <= 1'b1;
                        7'd27: SCL <= 1'b0;
                        7'd28: sda_out <= iic_data_w[0];
                        7'd29: SCL <= 1'b1;
                        7'd31: SCL <= 1'b0;
                        7'd32: begin
                            sda_dir <= 1'b0; ack <= sda_in ; // 从机应答	
    						wdata_cnt <= wdata_cnt + 1;
                        end
                        7'd33: SCL <= 1'b1; 
                        7'd34: begin 
    						 if(wdata_cnt == wdata_num)begin 
    							 st_done <= 1'b1; wdata_cnt <= 0;
    						end			
    					end
                        7'd35: begin
    					    SCL  <= 1'b0; cnt  <= 1'b0;
                        end
                        default:;
                    endcase
                end
                st_addr_rd: begin                          // 写地址以进行读数据
                    case(cnt)
                        7'd0 : begin
                            sda_dir <= 1'b1; sda_out <= 1'b1;
                        end
                        7'd1 : SCL <= 1'b1;
                        7'd2 : sda_out <= 1'b0;            // 重新开始
                        7'd3 : SCL <= 1'b0;
                        7'd4 : sda_out <= SLAVE_ADDR[6];   // 传送器件地址
                        7'd5 : SCL <= 1'b1;
                        7'd7 : SCL <= 1'b0;
                        7'd8 : sda_out <= SLAVE_ADDR[5];
                        7'd9 : SCL <= 1'b1;
                        7'd11: SCL <= 1'b0;
                        7'd12: sda_out <= SLAVE_ADDR[4];
                        7'd13: SCL <= 1'b1;
                        7'd15: SCL <= 1'b0;
                        7'd16: sda_out <= SLAVE_ADDR[3];
                        7'd17: SCL <= 1'b1;
                        7'd19: SCL <= 1'b0;
                        7'd20: sda_out <= SLAVE_ADDR[2];
                        7'd21: SCL <= 1'b1;
                        7'd23: SCL <= 1'b0;
                        7'd24: sda_out <= SLAVE_ADDR[1];
                        7'd25: SCL <= 1'b1;
                        7'd27: SCL <= 1'b0;
                        7'd28: sda_out <= SLAVE_ADDR[0];
                        7'd29: SCL <= 1'b1;
                        7'd31: SCL <= 1'b0;
                        7'd32: sda_out <= 1'b1;            	  // 1:读
                        7'd33: SCL <= 1'b1;
                        7'd35: SCL <= 1'b0;
                        7'd36: begin
                            sda_dir <= 1'b0; ack <= sda_in ; // 从机应答
                        end
                        7'd37: SCL     <= 1'b1;
                        7'd38: st_done <= 1'b1;
                        7'd39: begin
                            SCL <= 1'b0;
                            cnt <= 1'b0;
                        end
                        default : ;
                    endcase
                end
                st_data_rd: begin                          // 读取数据(8 bit)
                    case(cnt)
                        7'd0: sda_dir <= 1'b0;
                        7'd1: begin
    						SCL	<= 1'b1; data_r[7] <= sda_in;	
                        end
                        7'd3: SCL <= 1'b0;
                        7'd5: begin   
                            SCL <= 1'b1; data_r[6] <= sda_in ;
                        end
                        7'd7: SCL <= 1'b0;
                        7'd9: begin
                            SCL	<= 1'b1; data_r[5] <= sda_in;
                        end
                        7'd11: SCL <= 1'b0;
                        7'd13: begin
                            SCL	<= 1'b1; data_r[4] <= sda_in;
                        end
                        7'd15:SCL <= 1'b0;
                        7'd17: begin
    						SCL	<= 1'b1; data_r[3] <= sda_in;
                        end
                        7'd19: SCL <= 1'b0;
                        7'd21: begin
                            SCL	<= 1'b1; data_r[2] <= sda_in;
                        end
                        7'd23: SCL  <= 1'b0;
                        7'd25: begin
                            SCL	<= 1'b1; data_r[1] <= sda_in;
                        end
                        7'd27: SCL  <= 1'b0;
                        7'd29: begin
                            SCL	<= 1'b1; data_r[0] <= sda_in;
                        end
                        7'd31: begin 
    						SCL  <= 1'b0; rdata_cnt <= rdata_cnt + 1; iic_data_r <= data_r;
    					end
    					7'd32: begin
    						if(rdata_cnt < rdata_num)begin 
    							sda_dir <= 1'b1; sda_out <= 1'b0;	    				//应答
    						end
    						else begin 
    							sda_dir <= 1'b1; sda_out <= 1'b1;         			 	//非应答
    						end
                        end
    					7'd33: SCL  <= 1'b1;
                        7'd35: begin 
    						SCL <= 1'b0; 
    						if(rdata_cnt < rdata_num)
    							cnt <= 1'b0;
    					end 
                        7'd36: st_done <= 1'b1;  
                        7'd37: begin
                            SCL <= 1'b1; cnt <= 1'b0;
    						if(rdata_cnt == rdata_num) rdata_cnt <= 0;
                        end
                        default  :  ;
                    endcase
                end
                st_stop: begin                            // 结束IIC操作
                    case(cnt)
                        7'd0: begin
                            sda_dir <= 1'b1;              // 结束IIC
                            sda_out <= 1'b0;
                        end
                        7'd1 : SCL     <= 1'b1;
                        7'd3 : sda_out <= 1'b1;
                        7'd15: st_done <= 1'b1;
                        7'd16: begin
                            cnt      <= 1'b0;
                            iic_done <= 1'b1;             // 向上层模块传递IIC结束信号
                        end
                        default  : ;
                    endcase
                end
            endcase
        end
    end
    
    always @(posedge dri_clk or negedge RST_N) 
    begin
    	if(!RST_N)
    		wr_data_vaild_r[0] <= 0;
    	else if(((cur_state == st_data_wr)&&(cnt == 29))||((cur_state == st_addr8)&&(cnt == 29)&&(wr_flag==1'b0)))
    		wr_data_vaild_r[0] <= 1;
    	else 
    		wr_data_vaild_r[0] <= 0;
    end
    
    always @(posedge dri_clk or negedge RST_N) 
    begin
    	if(!RST_N)
    		rd_data_vaild_r[0] <= 0;
    	else if((cur_state == st_data_rd)&&(cnt == 31))
    		rd_data_vaild_r[0] <= 1;
    	else 
    		rd_data_vaild_r[0] <= 0;
    end	 
    
    always @(posedge CLK or negedge RST_N) 
    begin
    	if(!RST_N)begin 
    		iic_done_r[0]	   <= 0;
    		wr_data_vaild_r[1] <= 0;
    		rd_data_vaild_r[1] <= 0;
    	end
    	else begin 
    		iic_done_r[0]	   <= iic_done;
    		wr_data_vaild_r[1] <= wr_data_vaild_r[0];
    		rd_data_vaild_r[1] <= rd_data_vaild_r[0];
    	end 
    end
    
    assign wr_data_vaild = ({wr_data_vaild_r[1],wr_data_vaild_r[0]}==2'b01);
    assign rd_data_vaild = ({rd_data_vaild_r[1],rd_data_vaild_r[0]}==2'b01);
    assign done	= ({iic_done_r[0],iic_done}== 2'b01);
    endmodule 
    
    

你可能感兴趣的:(FPGA之协议设计)