Verilog_RTL 설계/SoC

[Smart Mobility platform] Implemented Code

juniha 2025. 8. 7. 10:24

AXI_Sensor IP

 1. 사용 코드 _ Ultrasonic_DHT11

// ============================================================================
// module: ultrasonic_correction_dht11
//  - 초음파(raw) + DHT11(온도) → 보정된 거리(dist_corr)
//  - 나눗셈을 순차 서브트랙터(divider)로, 곱셈·나눗셈 파이프라이닝 적용
//  - 원 구조와 제어 흐름은 그대로 유지
// ============================================================================
module ultrasonic_correction_dht11 (
    input          iCLK,        // 시스템 클럭 (100MHz 가정)
    input          iRSTn,       // Active-low 리셋
    input          iEN,         // 초음파 측정 트리거(1clk 엣지)
    inout          ioDATA_DHT11,// DHT11 데이터 라인
    input          iECHO,       // 초음파 Echo 입력
    output         oTRIG,       // 초음파 Trig 출력
    output [11:0]  oDIST_CORR   // 보정된 거리(cm)
);

    //==========================================================================
    // 1) 내부 신호 선언
    //==========================================================================
    wire  [11:0] dist_raw;        // raw 거리(cm)
    wire   [7:0] temperature;     // DHT11 온도(°C)
    reg   [11:0] dist_corr;       // 보정 거리 출력

    // 파이프 스테이지 #1: 음속 인자 (0.1m/s 단위)
    reg   [11:0] factor_reg;
    // 파이프 스테이지 #2: 거리×인자 (24비트)
    reg   [23:0] mult_reg;
    // 파이프 스테이지 #3: 순차 뺄셈 나눗셈
    reg   [23:0] div_reg;
    reg   [11:0] quot_reg;
    reg          div_busy;

    //==========================================================================
    // 2) 출력 매핑
    //==========================================================================
    assign oDIST_CORR = dist_corr;

    //==========================================================================
    // 3) 하위 모듈 인스턴스화
    //==========================================================================
    ultrasonic_sensor u_us (
        .iCLK   (iCLK),
        .iRSTn  (iRSTn),
        .iEN    (iEN),
        .iECHO  (iECHO),
        .oTRIG  (oTRIG),
        .oDIST  (dist_raw),
        .oLED_DEBUG()
    );

    dht11_cntr u_dht (
        .iCLK        (iCLK),
        .iRSTn       (iRSTn),
        .ioDATA_DHT11(ioDATA_DHT11),
        .oHUMI       (),
        .oTEMP       (temperature),
        .oLED_DEBUG  ()
    );

    //==========================================================================
    // 4) 파이프라인 스테이지 #1: factor = (3310 + 6*T)/10  → 0.1m/s 단위
    //    6*T = (T<<2)+(T<<1), 3310는 상수
    //==========================================================================
    always @(posedge iCLK or negedge iRSTn) begin
        if (!iRSTn) begin
            factor_reg <= 12'd0;
        end else begin
            // 온도 기반 음속 인자 계산 (0.1m/s 단위)
            // 331.0m/s → 3310(0.1m/s), 0.6*T → 6*T
            factor_reg <= 12'd3310
                        + (temperature << 2)
                        + (temperature << 1);
        end
    end


    //==========================================================================
    // 5) 파이프라인 스테이지 #2: raw(cm) × factor(0.1m/s 단위): 시프트+덧셈 누적
    //==========================================================================
    integer i;
    always @(posedge iCLK or negedge iRSTn) begin
        if (!iRSTn) begin
            mult_reg <= 24'd0;
        end else begin
            for (i = 0; i < 12; i = i + 1) begin
                if (factor_reg[i])
                    mult_reg = mult_reg + ({12'd0, dist_raw} << i);
            end
        end
    end

    //==========================================================================
    // 6) 파이프라인 스테이지 #3: 순차적 나눗셈(div_reg ÷ 3430) → quot_reg
    //    - iEN 펄스가 오면 mult_reg 적재 후 busy 시작
    //    - 매 클럭 div_reg>=3430 이면 빼고 quot_reg++
    //    - div_reg<3430 이면 종료하고 결과 저장
    //==========================================================================
    always @(posedge iCLK or negedge iRSTn) begin
        if (!iRSTn) begin
            div_reg   <= 24'd0;
            quot_reg  <= 12'd0;
            dist_corr <= 12'd0;
            div_busy  <= 1'b0;
        end
        else if (iEN) begin
            // 새로운 측정 트리거
            div_reg   <= mult_reg;
            quot_reg  <= 12'd0;
            div_busy  <= 1'b1;
        end
        else if (div_busy) begin
            if (div_reg >= 24'd3430) begin
                // 빼기 반복
                div_reg  <= div_reg - 24'd3430;
                quot_reg <= quot_reg + 12'd1;
            end else begin
                // 나눗셈 완료
                div_busy  <= 1'b0;
                dist_corr <= quot_reg;
            end
        end
    end

endmodule

 

   2. 사용코드 _Servo _tilt

//==============================================================================
// module: servo2_tilt_ctrl.v
//  - iSET_ANGLE 을 통한 단일 위치 이동 기능 추가
//  - FSM: S_IDLE → S_MOVE_SET → S_ADJUST → S_DONE → S_IDLE
//==============================================================================
module servo2_tilt_ctrl (
    input                   iCLK,         // 100 MHz 시스템 클럭
    input                   iRSTn,        // Active-low 리셋
    input                   iEN,          // 자동 보정 트리거(상승 엣지)
    input           [3:0]   iSET_ANGLE,   // 임의 이동 스텝(0~14)
    input   signed  [15:0]   iPITCH,       // MPU6050 피치 값(° 단위)
    output                  oSERVO_2,      // 서보2 PWM 출력
    output                  oEN_SERVO_1,  // Servo 1 트리거 신호로 재사용
    output          [7:0]   oANGLE2_CUR,  // 현재 서보2 각도(°)
    output          [15:0]  oLED_DEBUG    // 디버그용(상태 표시)
);

    //--------------------------------------------------------------------------
    // 1) 파라미터 선언
    //--------------------------------------------------------------------------
    localparam SYS_CLK_FREQ     = 100_000_000;   // 100 MHz
    localparam MYCLK_FREQ       = 2;             // 0.5 s 토글
    localparam VALUE_COUNT_SEC  = SYS_CLK_FREQ/(2*MYCLK_FREQ);
    localparam WIDTH_SEC        = $clog2(VALUE_COUNT_SEC);

    localparam CNT_MIN          = 4;   // 0° 듀티 (0.44 ms)
    localparam MAX_STEP         = 14;  // 0~14 → 0°~140°, 10° 단위

    //--------------------------------------------------------------------------
    // 2) 0.5 s 클럭 생성 & 네거티브 엣지 검출
    //--------------------------------------------------------------------------
    reg [WIDTH_SEC-1:0] rCnt_sec;
    reg                 rClk_sec;
    always @(posedge iCLK or negedge iRSTn) begin
        if (!iRSTn) begin
            rCnt_sec <= 0;
            rClk_sec <= 0;
        end
        else if (rCnt_sec == VALUE_COUNT_SEC-1) begin
            rCnt_sec <= 0;
            rClk_sec <= ~rClk_sec;
        end else
            rCnt_sec <= rCnt_sec + 1;
    end

    wire wClk_sec_neg;
    edge_detector u_edge_sec (
        .iCLK      (iCLK),
        .iRSTn     (iRSTn),
        .iSIG      (rClk_sec),
        .oEDGE_NEG (wClk_sec_neg)
    );
    
    wire wCLK_1M_neg;
    clock_div_1ms (
        .iCLK(iCLK),
        .iRSTn(iRSTn),
        .oEDGEn_DIV_1000(wCLK_1M_neg)
    );
    
    reg en_servo_1;
    assign oEN_SERVO_1 = en_servo_1;  // Servo_1 트리거로 재활용

    //--------------------------------------------------------------------------
    // 3) PWM 서보 IP 인스턴스
    //--------------------------------------------------------------------------
    reg  [3:0] rStep;                 // 0~14 스텝
    wire [7:0] wDuty2 = CNT_MIN + rStep;
    pwm_servo_180 u_pwm2 (
        .iCLK  (iCLK),
        .iRSTn (iRSTn),
        .iDUTY (wDuty2),
        .oPWM  (oSERVO_2)
    );

    //--------------------------------------------------------------------------
    // 4) 현재 각도(°) 출력
    //--------------------------------------------------------------------------
    assign oANGLE2_CUR = rStep * 10;

    //--------------------------------------------------------------------------
    // 5) prevEN, prevSET 샘플링
    //--------------------------------------------------------------------------
    reg       prevEN;
    reg [3:0] prevSET;
    always @(posedge iCLK or negedge iRSTn) begin
        if (!iRSTn) begin
            prevEN  <= 1'b0;
            prevSET <= 4'd0;
        end else begin
            prevEN  <= iEN;
            prevSET <= iSET_ANGLE;
        end
    end

    //--------------------------------------------------------------------------
    // 6) FSM 정의
    //--------------------------------------------------------------------------
    localparam [3:0]
        S_IDLE      = 4'b0001,
        S_MOVE_SET  = 4'b0010,  // ★ 임의 위치 이동
        S_ADJUST    = 4'b0100,  // 자동 보정
        S_DONE      = 4'b1000;

    reg [3:0] state, next_state;
    assign oLED_DEBUG = {en_servo_1, 11'd0, ~state};

    // Next-state 로직
    always @(*) begin
        next_state = state;
        case (state)
            S_IDLE: begin
                if (prevSET != iSET_ANGLE)
                    next_state = S_MOVE_SET;
                else if (!prevEN && iEN)
                    next_state = S_ADJUST;
            end

            S_MOVE_SET: begin
                // 0.5 s 후 완료
                if (wClk_sec_neg)
                    next_state = S_IDLE;
            end

            S_ADJUST: begin
                // 수평(스텝7) 도달 후 완료
                if ((rStep == 7) && wCLK_1M_neg)
                    next_state = S_DONE;
            end

            S_DONE: begin
                // DONE 후 바로 IDLE 복귀
                next_state = S_IDLE;
            end
        endcase
    end

    // State 레지스터 + rStep 제어
    always @(posedge iCLK or negedge iRSTn) begin
        if (!iRSTn) begin
            state <= S_IDLE;
            rStep <= 4'd7;  // 초기 수평(70°)
            en_servo_1 <= 0;
        end
        else begin
            state <= next_state;
            case (state)
                S_IDLE: begin
                    
                end

                S_MOVE_SET: begin
                    // ★ iSET_ANGLE 위치로 단일 이동
                    if (wClk_sec_neg && iSET_ANGLE <= MAX_STEP)
                        rStep <= iSET_ANGLE;
                end

                S_ADJUST: begin
                    // ★ iPITCH 기반 자동 보정
                    en_servo_1 <= 0;
                    if (wClk_sec_neg) begin
                        if (iPITCH >  3 && rStep < MAX_STEP)
                            rStep <= rStep + 1;
                        else if (iPITCH < -3 && rStep > 0)
                            rStep <= rStep - 1;
                        else
                            rStep <= 7;
                    end
                end

                S_DONE: begin
                    en_servo_1 <= 1;
                end
            endcase
        end
    end

endmodule

 

  3. 사용 코드_Servo1_PWM

module servo_pwm_top (
    input          iCLK,        // 100MHz 시스템 클럭
    input          iRSTn,       // Active-low 리셋
    input          iEN,         // ★ 스윕 트리거 (상승 엣지)
    input   [4:0]  iSET_ANGLE,  // ★ 설정 각도 인덱스(0~18)
    output         oEN_SONIC,   // 거리 센서 트리거 신호로 재사용
    output         oSERVO,      // 서보 PWM 출력
    output  [4:0]  oANGLE_CUR,  // 현재 각도(°)
    output  [15:0] oLED_DEBUG
);

    //======================================================================
    // 1) 파라미터 선언
    //======================================================================
    localparam SYS_CLK_FREQ   = 100_000_000;
    localparam MYCLK_FREQ     = 2;                    // 0.5s 토글
    localparam VALUE_COUNT_SEC= SYS_CLK_FREQ/(2*MYCLK_FREQ);
    localparam WIDTH_SEC      = $clog2(VALUE_COUNT_SEC);

    localparam CNT_MIN        = 4;    // 0도 대응 (0.44ms)
    localparam CNT_MAX        = 22;   // 180도 대응 (2.44ms)
    localparam MAX_STEP       = 18;   // 0~18 → 0~180° (10° 단위)

    //======================================================================
    // 2) 500ms 타이밍 생성 & 네거티브 엣지 검출
    //======================================================================
    reg [WIDTH_SEC-1:0] rCnt_sec;
    reg                 rClk_sec;
    always @(posedge iCLK or negedge iRSTn) begin
        if (!iRSTn) begin
            rCnt_sec <= 0;
            rClk_sec <= 0;
        end else if (rCnt_sec == VALUE_COUNT_SEC-1) begin
            rCnt_sec <= 0;
            rClk_sec <= ~rClk_sec;
        end else
            rCnt_sec <= rCnt_sec + 1;
    end

    wire wClk_sec_neg;
    edge_detector UED (
        .iCLK    (iCLK),
        .iRSTn   (iRSTn),
        .iSIG    (rClk_sec),
        .oEDGE_NEG(wClk_sec_neg)
    );

    assign oEN_SONIC = wClk_sec_neg;  // 거리 센서 트리거로 재활용

    //======================================================================
    // 3) PWM 서보 드라이버 인스턴스
    //======================================================================
    // ★ FSM 출력 rDUTY (0~18)에 CNT_MIN 더해서 실제 듀티 생성
    reg  [7:0]    rDUTY;
    wire [4:0]    wPWM_DUTY = CNT_MIN + rDUTY;
    pwm_servo_180 U_SERVO (
        .iCLK    (iCLK),
        .iRSTn   (iRSTn),
        .iDUTY   (wPWM_DUTY),
        .oPWM    (oSERVO)
    );

    //======================================================================
    // 4) 현재 각도 & FND 표시
    //======================================================================
    // 10도 단위 → 실제 각도(°)
    assign oANGLE_CUR = rDUTY;

    //======================================================================
    // 5) FSM: IDLE → MOVE_SET → SWEEP → DONE → IDLE
    //======================================================================
    // ★ 상승 엣지/값 변경 감지용 레지스터
    reg       prevEN;
    reg [4:0] prevSET;
    always @(posedge iCLK or negedge iRSTn) begin
        if (!iRSTn) begin
            prevEN  <= 1'b0;
            prevSET <= 5'd0;
        end else begin
            prevEN  <= iEN;
            prevSET <= iSET_ANGLE;
        end
    end

    // 상태 정의
    localparam [3:0]
        S_IDLE     = 4'b0001,
        S_MOVE_SET = 4'b0010,
        S_SWEEP    = 4'b0100,
        S_DONE     = 4'b1000;

    reg [3:0] state, next_state;
    reg [4:0] rStep;  // 스윕 카운터 (0~18)

    assign oLED_DEBUG = ~state;

    // Next-State 로직
    always @(*) begin
        next_state = state;
        case (state)
            S_IDLE:
                // SET 값이 바뀌면 단일 이동
                if (prevSET != iSET_ANGLE)
                    next_state = S_MOVE_SET;
                // iEN 상승 엣지 시 전체 스윕
                else if (!prevEN && iEN)
                    next_state = S_SWEEP;
            S_MOVE_SET:
                // 1회 이동 후 바로 IDLE 복귀
                if (wClk_sec_neg)
                    next_state = S_IDLE;
            S_SWEEP:
                // 스텝 끝나면 DONE
                if (wClk_sec_neg && rStep == MAX_STEP)
                    next_state = S_DONE;
            S_DONE:
                // DONE 후 IDLE
                next_state = S_IDLE;
        endcase
    end

    // State 레지스터 + 출력 제어
    always @(posedge iCLK or negedge iRSTn) begin
        if (!iRSTn) begin
            state <= S_IDLE;
            rDUTY  <= 8'd0;
            rStep  <= 5'd0;
        end else begin
            state <= next_state;

            case (state)
                S_IDLE: begin
                    // 스윕 시작 시 스텝 카운터 리셋
                    if (!prevEN && iEN)
                        rStep <= 5'd0;
                end

                S_MOVE_SET: begin
                    // 단일 위치 이동: wClk_sec_neg 타이밍에 iSET_ANGLE 적용
                    if (wClk_sec_neg)
                        rDUTY <= iSET_ANGLE;
                end

                S_SWEEP: begin
                    // 전체 스윕: wClk_sec_neg마다 rStep→rDUTY
                    if (wClk_sec_neg) begin
                        rDUTY <= rStep;
                        rStep <= rStep + 1;
                    end
                end

                S_DONE: begin
                    rDUTY <= 9;
                end
            endcase
        end
    end

endmodule

 

  4. 사용 코드 _좌표 변환

module cartesian_transform (
    input                               clk,        // 시스템 클럭
    input                               rstn,       // Active-low reset
    input      [4:0]                    angle_idx,  // 0→0°, 1→10° … 18→180°
    input      [11:0]                   dist_in,    // 보정된 거리(cm)
    output reg                          oREADY,
    output reg signed [11:0]            x_out,      // 변환된 X (cm)
    output reg signed [11:0]            y_out       // 변환된 Y (cm)
);

    // 고정소수점 폭
    localparam FP_BITS = 16;

    // 정수×고정소수점 곱셈 결과 폭
    wire signed [12+FP_BITS-1:0] mult_x;  
    wire signed [12+FP_BITS-1:0] mult_y;  

    // ★ sin/cos 값을 직접 할당할 레지스터
    reg signed [FP_BITS-1:0] sin_val;     // Q1.15
    reg signed [FP_BITS-1:0] cos_val;     // Q1.15

    // ① angle_idx 혹은 dist_in 이 바뀌면 새로운 좌표를 계산하고,
    //    그 계산값이 register에 승격되는 클럭 타이밍에 coord_valid 를 1로.
    wire new_input = (angle_idx != prev_angle_idx) || (dist_in != prev_dist_in);
    
    // ② prev_angle_idx, prev_dist_in 저장
    reg [4:0]  prev_angle_idx;
    reg [11:0] prev_dist_in;
    always @(posedge clk or negedge rstn) begin
      if (!rstn) begin
        prev_angle_idx <= 0;
        prev_dist_in   <= 0;
      end else begin
        prev_angle_idx <= angle_idx;
        prev_dist_in   <= dist_in;
      end
    end
    
    // ③ coord_valid 펄스 생성
    reg coord_valid;
    always @(posedge clk or negedge rstn) begin
      if (!rstn)           coord_valid <= 1'b0;
      else if (new_input)  coord_valid <= 1'b1;
      else                  coord_valid <= 1'b0;
    end
    
    // ④ 바로 위 방법대로 oCOORD_RDY 펄스 만들기
    reg prev_coord_valid;
    always @(posedge clk or negedge rstn) begin
      if (!rstn)            prev_coord_valid <= 1'b0;
      else                  prev_coord_valid <= coord_valid;
    end
    
    always @(posedge clk or negedge rstn) begin
      if (!rstn)            oREADY <= 1'b0;
      else                  oREADY <= coord_valid & ~prev_coord_valid;
    end

    //--------------------------------------------------------------------------
    // 1) combinational LUT: angle_idx → sin_val, cos_val
    //--------------------------------------------------------------------------
    always @* begin
        case(angle_idx)
            // -- sin 테이블 (Q1.15)
            5'd0 :  sin_val = 16'h0000;
            5'd1 :  sin_val = 16'h163A;
            5'd2 :  sin_val = 16'h2BC7;
            5'd3 :  sin_val = 16'h4000;
            5'd4 :  sin_val = 16'h5247;
            5'd5 :  sin_val = 16'h620E;
            5'd6 :  sin_val = 16'h6EDA;
            5'd7 :  sin_val = 16'h7848;
            5'd8 :  sin_val = 16'h7E0E;
            5'd9 :  sin_val = 16'h8000;
           5'd10 :  sin_val = 16'h7E0E;
           5'd11 :  sin_val = 16'h7848;
           5'd12 :  sin_val = 16'h6EDA;
           5'd13 :  sin_val = 16'h620E;
           5'd14 :  sin_val = 16'h5247;
           5'd15 :  sin_val = 16'h4000;
           5'd16 :  sin_val = 16'h2BC7;
           5'd17 :  sin_val = 16'h163A;
           5'd18 :  sin_val = 16'h0000;
            default: sin_val = 16'h0000;
        endcase
    end

    always @* begin
        case(angle_idx)
            // -- cos 테이블 (Q1.15)
            5'd0 :  cos_val = 16'h8000;
            5'd1 :  cos_val = 16'h7E0E;
            5'd2 :  cos_val = 16'h7848;
            5'd3 :  cos_val = 16'h6EDA;
            5'd4 :  cos_val = 16'h620E;
            5'd5 :  cos_val = 16'h5247;
            5'd6 :  cos_val = 16'h4000;
            5'd7 :  cos_val = 16'h2BC7;
            5'd8 :  cos_val = 16'h163A;
            5'd9 :  cos_val = 16'h0000;
           5'd10 :  cos_val = 16'hE9C6;
           5'd11 :  cos_val = 16'hD439;
           5'd12 :  cos_val = 16'hC000;
           5'd13 :  cos_val = 16'hADB9;
           5'd14 :  cos_val = 16'h9DF2;
           5'd15 :  cos_val = 16'h9126;
           5'd16 :  cos_val = 16'h87B8;
           5'd17 :  cos_val = 16'h81F2;
           5'd18 :  cos_val = 16'h8000;
            default: cos_val = 16'h8000;
        endcase
    end

    //--------------------------------------------------------------------------
    // 2) 곱셈
    //    dist_in: unsigned[11:0], sin_val/cos_val: signed[15:0]
    //    → signed[27:0] 결과
    //--------------------------------------------------------------------------
    assign mult_x = $signed({1'b0, dist_in}) * sin_val;
    assign mult_y = $signed({1'b0, dist_in}) * cos_val;

    //--------------------------------------------------------------------------
    // 3) Q1.15 → 정수(cm) 변환: 위 곱셈 결과의 상위 비트 취함
    //    mult_x[27:15]  → signed[12:0] → x_out[11:0]으로 트렁케이트
    //--------------------------------------------------------------------------
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            x_out <= 0;
            y_out <= 0;
        end else begin
            x_out <= mult_x[27:16];  // (11+16-1 down to 16)
            y_out <= mult_y[27:16];
        end
    end

endmodule

 

AXI_VIHICLE_IP

  1. 사용 코드 _DCMotor

// ============================================================================
// motor_autonomous_ctrl.v
//  - one-hot 5상태 FSM
//  - ST_IDLE → ST_FWD(지속) → ST_LEFT/RIGHT(0.245s) → ST_STOP(1clk) → ST_IDLE
// ============================================================================
module motor_autonomous_ctrl (
    input          iCLK,        // 100 MHz 시스템 클럭
    input          iRSTn,       // Active-low 리셋
    input          iEN_START,  // 전진 버튼 (펄스)
    input          iEN_STOP,   // 정지 버튼 (펄스)
    input          iEN_LEFT,   // 좌회전 버튼 (펄스)
    input          iEN_RIGHT,  // 우회전 버튼 (펄스)
    output         oMOTOR_A1,   // 왼쪽 모터 전진
    output         oMOTOR_A2,   // 왼쪽 모터 후진 (사용 안 함)
    output         oMOTOR_B1,   // 오른쪽 모터 전진
    output         oMOTOR_B2,   // 오른쪽 모터 후진 (사용 안 함)
    output [15:0]  oLED_DEBUG
);

    //--------------------------------------------------------------------------
    // 1) 상태(one-hot 5비트) 정의
    //--------------------------------------------------------------------------
    localparam [4:0]
        S_IDLE  = 5'b00001,  // 대기
        S_FWD   = 5'b00010,  // 전진(지속)
        S_STOP  = 5'b00100,  // 정지(1clk)
        S_LEFT  = 5'b01000,  // 좌회전(0.245s)
        S_RIGHT = 5'b10000;  // 우회전(0.245s)

    //--------------------------------------------------------------------------
    // 2) 0.245 s 타이머 (100 MHz → 24_500_000 클럭)
    //--------------------------------------------------------------------------
    localparam  ROTATE_TIME = 24_500_000;  // 10도 회전
    localparam  WIDTH_RT    = $clog2(ROTATE_TIME);

    //--------------------------------------------------------------------------
    // 3) 내부 신호
    //--------------------------------------------------------------------------
    wire                      wStartEdge, wStopEdge, wLeftEdge, wRightEdge;
    reg   [4:0]               rState, rNext;
    reg   [WIDTH_RT-1:0]      rTimerCnt;

    //--------------------------------------------------------------------------
    // 4) 버튼 엣지 검출 인스턴스
    //--------------------------------------------------------------------------
    edge_detector uED_START (
        .iCLK      (iCLK),
        .iRSTn     (iRSTn),
        .iSIG      (iEN_START),
        .oEDGE_NEG (wStartEdge)
    );
    
    edge_detector uED_STOP (
        .iCLK      (iCLK),
        .iRSTn     (iRSTn),
        .iSIG      (iEN_STOP),
        .oEDGE_NEG (wStopEdge)
    );
    
    edge_detector uED_LEFT (
        .iCLK      (iCLK),
        .iRSTn     (iRSTn),
        .iSIG      (iEN_LEFT),
        .oEDGE_NEG (wLeftEdge)
    );
    
    edge_detector uED_RIGHT (
        .iCLK      (iCLK),
        .iRSTn     (iRSTn),
        .iSIG      (iEN_RIGHT),
        .oEDGE_NEG (wRightEdge)
    );

    //--------------------------------------------------------------------------
    // 5) FSM Next-State 로직 (combinational)
    //--------------------------------------------------------------------------
    always @(*) begin
        rNext = rState;
        case (rState)

            // IDLE: START만 받아서 전진으로
            S_IDLE: begin
                if      (wStartEdge)   rNext = S_FWD;
                else                   rNext = S_IDLE;
            end

            // FWD: STOP/LEFT/RIGHT 에만 반응, 아니면 계속 전진
            S_FWD: begin
                if      (wStopEdge)    rNext = S_STOP;
                else if (wLeftEdge)    rNext = S_LEFT;
                else if (wRightEdge)   rNext = S_RIGHT;
                else                   rNext = S_FWD;
            end

            // STOP: 1clk 만 유지 후 IDLE
            S_STOP: rNext = S_IDLE;

            // LEFT: 0.245s 타이머 끝나면 IDLE, 아니면 계속 LEFT
            S_LEFT: begin
                if (rTimerCnt >= ROTATE_TIME)  rNext = S_IDLE;
                else                           rNext = S_LEFT;
            end

            // RIGHT: 0.245s 타이머 끝나면 IDLE, 아니면 계속 RIGHT
            S_RIGHT: begin
                if (rTimerCnt >= ROTATE_TIME)  rNext = S_IDLE;
                else                           rNext = S_RIGHT;
            end

            // 기본 복귀
            default: rNext = S_IDLE;
        endcase
    end

    //--------------------------------------------------------------------------
    // 6) FSM 상태 & 타이머 업데이트 (sequential)
    //--------------------------------------------------------------------------
    always @(posedge iCLK or negedge iRSTn) begin
        if (!iRSTn) begin
            rState    <= S_IDLE;
            rTimerCnt <= 0;
        end else begin
            rState <= rNext;

            // 좌/우 회전 중일 때만 카운터 ↑, entry 시 리셋
            if (rState == S_LEFT || rState == S_RIGHT) begin
                if (rState != rNext)
                    rTimerCnt <= 0;
                else
                    rTimerCnt <= rTimerCnt + 1;
            end
            else
                rTimerCnt <= 0;
        end
    end

    //--------------------------------------------------------------------------
    // 7) 모터 드라이브 (combinational)
    //--------------------------------------------------------------------------
    // 전진: A1,B1 = 1
    // 정지/IDLE: 모두 0
    // 좌회전: 왼쪽 A1만 =1
    // 우회전: 오른쪽 B1만 =1
    assign oMOTOR_A1 = (rState == S_FWD) || (rState == S_LEFT);
    assign oMOTOR_A2 = 0;
    assign oMOTOR_B1 = (rState == S_FWD) || (rState == S_RIGHT);
    assign oMOTOR_B2 = 0;

    //--------------------------------------------------------------------------
    // 8) 상태 디버그 출력
    //--------------------------------------------------------------------------
    assign oLED_DEBUG[4:0] = ~rState;

endmodule

 

VITIS_I2C_Control

  1. 사용코드 _I2c 3 Sensor (MPU6050, TXT LCD, OLED)

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "xiic.h"
#include "sleep.h"
#include "font8x8_basic.h"

//------------------------------------------------------------------------------
// I²C 마스터 & 채널
//------------------------------------------------------------------------------
static XIic   iic_inst;
#define IIC_CHNL    XPAR_AXI_IIC_0_BASEADDR

//------------------------------------------------------------------------------
// 장치 7-bit 주소
//------------------------------------------------------------------------------
#define LCD_ADDR    0x3F    // Text LCD (PCF8574 기반)
#define OLED_ADDR   0x3C    // SSD1306 OLED
#define MPU_ADDR   0x68    // MPU6050

//------------------------------------------------------------------------------
// MPU6050 레지스터
//------------------------------------------------------------------------------
#define REG_PWR_MGMT1    0x6B
#define REG_SMPLRT_DIV   0x19
#define REG_CONFIG       0x1A
#define REG_GYRO_CONFIG  0x1B
#define REG_ACCEL_CONFIG 0x1C
#define REG_ACCEL_X_H    0x3B

//------------------------------------------------------------------------------
// OLED Control 바이트
//------------------------------------------------------------------------------
#define OLED_CTL_CMD   0x00
#define OLED_CTL_DAT   0x40

//------------------------------------------------------------------------------
// ASCII 폰트 (0x20~0x7F) extern 선언
// 반드시 font8x8_basic.h 를 프로젝트에 포함해야 합니다.
extern const uint8_t font8x8_basic[96][8];

//==============================================================================
// 전역 버퍼: OLED GDDRAM 에 대응 (페이지 0~7 × 컬럼 0~127)
//==============================================================================
static uint8_t oled_buf[8][128];

//------------------------------------------------------------------------------
// mpu 함수 선언
//------------------------------------------------------------------------------
void mpu_write_reg(uint8_t reg, uint8_t val);
void mpu_read_regs(uint8_t start_reg, uint8_t *dst, int len);
static int16_t compute_mpu_pitch(int16_t ax,int16_t ay,int16_t az);

//------------------------------------------------------------------------------
// lcd 함수 선언
//------------------------------------------------------------------------------
void LCD_WriteCommand(uint8_t cmd);
void LCD_WriteData(uint8_t d);
void LCD_Init(void);
void LCD_GotoXY(uint8_t row, uint8_t col);
void LCD_WriteSignedInt(int16_t v);

//------------------------------------------------------------------------------
// oled 함수 선언
//------------------------------------------------------------------------------
void oled_write_cmd(uint8_t cmd);
void oled_write_data(uint8_t d);
void oled_init(void);
void oled_clear(void);
void oled_puts_str(int page, int col, const char *s);

//------------------------------------------------------------------------------
// oled 픽셀 그리기 & 버퍼 업데이트 함수 선언
//------------------------------------------------------------------------------
void oled_update_column(int page, int col); 
void oled_draw_pixel(int x, int y);

//==============================================================================
// main: 초기화 → 루프(읽기→UART/LCD/OLED 출력)
//==============================================================================
int main(void) {
    uint8_t buf[14];
    int16_t ax, ay, az, gx, gy, gz;

    init_platform();
    xil_printf("\n\r=== I2C 3-Sensor Integrated test ===\n\r");

    // 1) I²C 마스터 초기화·시작
    XIic_Initialize(&iic_inst, IIC_CHNL);
    XIic_Start     (&iic_inst);
    msleep(10);

    // 2) LCD 초기화
    LCD_Init();
    msleep(10);

    // 3) OLED 초기화·클리어
    oled_init();
    oled_clear();
    msleep(10);

    // 4) MPU6050 슬립 해제·감도 설정
    mpu_write_reg(REG_PWR_MGMT1,    0x00);
    mpu_write_reg(REG_SMPLRT_DIV,   0x07);
    mpu_write_reg(REG_CONFIG,       0x06);
    mpu_write_reg(REG_GYRO_CONFIG,  0x18);
    mpu_write_reg(REG_ACCEL_CONFIG, 0x10);
    msleep(10);

    // 5) 메인 루프: 250 ms 간격으로 읽고 출력
    while (1) {
        // — MPU6050 6축 읽기 —
        mpu_read_regs(REG_ACCEL_X_H, buf, 14);
        ax = (buf[0]<<8)|buf[1];
        ay = (buf[2]<<8)|buf[3];
        az = (buf[4]<<8)|buf[5];
        gx = (buf[8]<<8)|buf[9];
        gy = (buf[10]<<8)|buf[11];
        gz = (buf[12]<<8)|buf[13];

        int16_t pitch = compute_mpu_pitch(ax, ay, az);

        // — UART 출력 —
        xil_printf("AX=%6d AY=%6d AZ=%6d  pitch =%4d\n\r", ax, ay, az, pitch);

        // — LCD: 2행에 AX~GZ 한 줄씩 —
        LCD_GotoXY(0,0);
        LCD_WriteSignedInt(ax);
        LCD_WriteSignedInt(ay);
        LCD_WriteSignedInt(az);
        LCD_GotoXY(1,0);
        LCD_WriteSignedInt(gx);
        LCD_WriteSignedInt(gy);
        LCD_WriteSignedInt(gz);

        // — OLED: 페이지0~5 에 AX~GZ 한 줄씩 출력 —
        char tmp[7];
        // 6자리 문자열: 부호+4숫자 + 널
        sprintf(tmp, "%+5d", ax);
        oled_puts_str(0, 0, tmp);
        sprintf(tmp, "%+5d", ay);
        oled_puts_str(1, 0, tmp);
        sprintf(tmp, "%+5d", az);
        oled_puts_str(2, 0, tmp);
        sprintf(tmp, "%+5d", gx);
        oled_puts_str(3, 0, tmp);
        sprintf(tmp, "%+5d", gy);
        oled_puts_str(4, 0, tmp);
        sprintf(tmp, "%+5d", gz);
        oled_puts_str(5, 0, tmp);

        sleep(1);

        // — (x=64,y=32)에 점 하나 찍기 —
        oled_draw_pixel(0, 0);
        sleep(1);
        oled_draw_pixel(120, 0);
        sleep(1);
        oled_draw_pixel(120, 60);
        sleep(1);
        oled_draw_pixel(0, 60);
        sleep(1);
        oled_draw_pixel(60, 30); 
        sleep(1);
    }

    cleanup_platform();
    return 0;
}

//------------------------------------------------------------------------------
// MPU6050 I²C 쓰기 헬퍼
//------------------------------------------------------------------------------
void mpu_write_reg(uint8_t reg, uint8_t val) {
    uint8_t buf[2] = { reg, val };
    XIic_Send(iic_inst.BaseAddress, MPU_ADDR, buf, 2, XIIC_STOP);
    while (XIic_IsIicBusy(iic_inst.BaseAddress));
}

//------------------------------------------------------------------------------
// MPU6050 I²C 연속 읽기 헬퍼
//------------------------------------------------------------------------------
void mpu_read_regs(uint8_t start_reg, uint8_t *dst, int len) {
    // 1) 읽을 레지스터 주소 전송 (Repeated Start 유지)
    XIic_Send(iic_inst.BaseAddress, MPU_ADDR, &start_reg, 1, 0);
    while (XIic_IsIicBusy(iic_inst.BaseAddress));
    // 2) 바로 읽기 → STOP
    XIic_Recv(iic_inst.BaseAddress, MPU_ADDR, dst, len, XIIC_STOP);
    while (XIic_IsIicBusy(iic_inst.BaseAddress));
}

//------------------------------------------------------------------------------
// @brief 근사: raw→pitch(°) 정수
//    sqrt≈max+0.5·min, atan2≈x/(1+0.28x²)
//------------------------------------------------------------------------------
static int16_t compute_mpu_pitch(int16_t ax,int16_t ay,int16_t az) {
    const float G=4096.0f;
    float x=(float)ax/G, y=(float)ay/G, z=(float)az/G;
    float ay0=y<0?-y:y, az0=z<0?-z:z;
    float vmax= ay0>az0? ay0:az0;
    float vmin= ay0>az0? az0:ay0;
    float denom = vmax + 0.5f*vmin + 1e-6f;
    float r = x/denom;
    float rad = r/(1.0f + 0.28f*r*r);
    float deg = rad * 57.2957795f;
    return (int16_t)(deg + (deg>=0?0.5f:-0.5f));
}

//------------------------------------------------------------------------------
// Text LCD 제어 함수들 (기존 코드 그대로)
//------------------------------------------------------------------------------
void LCD_WriteCommand(uint8_t cmd) {
    uint8_t high =  cmd   & 0xF0;
    uint8_t low  = (cmd<<4)& 0xF0;
    uint8_t seq[4] = {
        high|0x0C, high|0x08,
        low |0x0C, low |0x08
    };
    XIic_Send(iic_inst.BaseAddress, LCD_ADDR, seq, 4, XIIC_STOP);
    while (XIic_IsIicBusy(iic_inst.BaseAddress));
}

void LCD_WriteData(uint8_t d) {
    uint8_t high =  d     & 0xF0;
    uint8_t low  = (d <<4)& 0xF0;
    uint8_t seq[4] = {
        high|0x0D, high|0x08,
        low |0x0D, low |0x08
    };
    XIic_Send(iic_inst.BaseAddress, LCD_ADDR, seq, 4, XIIC_STOP);
    while (XIic_IsIicBusy(iic_inst.BaseAddress));
}

void LCD_Init(void) {
    msleep(50);
    LCD_WriteCommand(0x33); msleep(5);
    LCD_WriteCommand(0x32);
    LCD_WriteCommand(0x28);
    LCD_WriteCommand(0x0F);
    LCD_WriteCommand(0x06);
    LCD_WriteCommand(0x01); msleep(5);
}

void LCD_GotoXY(uint8_t row, uint8_t col) {
    uint8_t addr = 0x80 | (row<<6) | col;
    LCD_WriteCommand(addr);
}

// 부호 포함 5자리 찍기
void LCD_WriteSignedInt(int16_t v) {
    char tmp[6];
    // "%+5d" → 항상 부호, 총 5칸
    sprintf(tmp, "%+5d", v);
    for (int i = 0; i < 5; i++) {
        LCD_WriteData((uint8_t)tmp[i]);
    }
}

//------------------------------------------------------------------------------
// OLED 제어 함수들
//------------------------------------------------------------------------------
// Control + 명령어 1바이트
void oled_write_cmd(uint8_t cmd) {
    uint8_t buf[2] = { OLED_CTL_CMD, cmd };
    XIic_Send(iic_inst.BaseAddress, OLED_ADDR, buf, 2, XIIC_STOP);
    while (XIic_IsIicBusy(iic_inst.BaseAddress));
}

// Control + 데이터 1바이트
void oled_write_data(uint8_t d) {
    uint8_t buf[2] = { OLED_CTL_DAT, d };
    XIic_Send(iic_inst.BaseAddress, OLED_ADDR, buf, 2, XIIC_STOP);
    while (XIic_IsIicBusy(iic_inst.BaseAddress));
}

// SSD1306 초기화 (페이지 주소 모드 포함)
void oled_init(void) {
    const uint8_t seq[] = {
      0xAE,           // display off
      0x20,0x02,      // page addressing mode
      0xB0,           // page0
      0x00,0x10,      // col addr = 0
      0x40,           // start line = 0
      0x81,0x7F,      // contrast
      0xA1,           // seg remap
      0xC8,           // com scan dec
      0xA6,           // normal display
      0xA8,0x3F,      // multiplex 1/64
      0xD3,0x00,      // display offset
      0xD5,0x80,      // clk div
      0xD9,0x22,      // pre-charge
      0xDA,0x12,      // com pins
      0xDB,0x20,      // vcomh
      0x8D,0x14,      // charge pump on
      0xAF            // display on
    };
    for (int i = 0; i < (int)sizeof(seq); i++) {
        oled_write_cmd(seq[i]);
    }
}

// 전체 화면 클리어
void oled_clear(void) {
    for (int p = 0; p < 8; p++) {
        oled_write_cmd(0xB0 | p);
        oled_write_cmd(0x00);
        oled_write_cmd(0x10);
        for (int c = 0; c < 128; c++) {
            oled_write_data(0x00);
        }
    }
}

//------------------------------------------------------------------------------
// 문자열(부호 포함 숫자) 출력 helper
//------------------------------------------------------------------------------
// page: 0~7, col: 문자 단위(0~15), s: null-terminated 문자열
void oled_puts_str(int page, int col, const char *s) {
    // 1) 페이지·컬럼 설정
    oled_write_cmd(0xB0 | page);
    uint8_t x = col * 8;
    oled_write_cmd(0x00 | (x & 0x0F));
    oled_write_cmd(0x10 | ((x >> 4) & 0x0F));
    // 2) 글자별 8바이트 폰트 전송
    while (*s) {
        uint8_t buf[9];
        buf[0] = OLED_CTL_DAT;
        // ASCII 0x20~0x7F 대응
        memcpy(buf+1, font8x8_basic[(uint8_t)*s - 0x20], 8);
        XIic_Send(iic_inst.BaseAddress, OLED_ADDR, buf, 9, XIIC_STOP);
        while (XIic_IsIicBusy(iic_inst.BaseAddress));
        s++;
    }
}

//------------------------------------------------------------------------------
// OLED 픽셀 업데이트: 버퍼→실제 GDDRAM (한 컬럼만 갱신)
//------------------------------------------------------------------------------
void oled_update_column(int page, int col) { 
    oled_write_cmd(0xB0 | page);              // page 설정
    oled_write_cmd(0x00 | (col & 0x0F));      // col low
    oled_write_cmd(0x10 | ((col>>4)&0x0F));   // col high
    oled_write_data(oled_buf[page][col]);     // 버퍼값 전송
}

//------------------------------------------------------------------------------
// (x,y) 에 1픽셀 그리기 (버퍼 갱신 + 해당 컬럼만 갱신)
//------------------------------------------------------------------------------
void oled_draw_pixel(int x, int y) {       
    if (x<0||x>127||y<0||y>63) return;
    int page = y / 8;
    int bit  = y % 8;
    oled_buf[page][x] |= (1<<bit);            // 버퍼에 비트 세팅
    oled_update_column(page, x);              // 실제 화면에 바로 반영
}

 

Final System Control ( Vitis C ) 

  1. 사용 코드_ Final C 동작제어 코드

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "xiic.h"
#include "sleep.h"
#include "font8x8_basic.h"
#include <math.h>
#include <stdlib.h>

//------------------------------------------------------------------------------
// I²C 마스터 & 채널
//------------------------------------------------------------------------------
XIic   iic_inst;
#define IIC_CHNL    XPAR_AXI_IIC_0_BASEADDR

//------------------------------------------------------------------------------
// AXI-Sensor-Hub MMIO 베이스 주소 & 레지스터 오프셋
//------------------------------------------------------------------------------
#define HUB_BASE    XPAR_AXI_SENSOR_HUB_0_BASEADDR

//------------------------------------------------------------------------------
// 장치 7-bit 주소
//------------------------------------------------------------------------------
#define LCD_ADDR    0x3F    // Text LCD (PCF8574 기반)
#define OLED_ADDR   0x3C    // SSD1306 OLED
#define MPU_ADDR    0x68    // MPU6050

//------------------------------------------------------------------------------
// MPU6050 레지스터
//------------------------------------------------------------------------------
#define REG_PWR_MGMT1    0x6B
#define REG_SMPLRT_DIV   0x19
#define REG_CONFIG       0x1A
#define REG_GYRO_CONFIG  0x1B
#define REG_ACCEL_CONFIG 0x1C
#define REG_ACCEL_X_H    0x3B

//------------------------------------------------------------------------------
// OLED Control 바이트
//------------------------------------------------------------------------------
#define OLED_CTL_CMD   0x00
#define OLED_CTL_DAT   0x40

//------------------------------------------------------------------------------
// ASCII 폰트 (0x20~0x7F) extern 선언
// 반드시 font8x8_basic.h 를 프로젝트에 포함해야 합니다.
extern const uint8_t font8x8_basic[96][8];

//==============================================================================
// 전역 버퍼: OLED GDDRAM 에 대응 (페이지 0~7 × 컬럼 0~127)
//==============================================================================
static uint8_t oled_buf[8][128];

//------------------------------------------------------------------------------
// mpu 함수 선언
//------------------------------------------------------------------------------
void mpu_write_reg(uint8_t reg, uint8_t val);
void mpu_read_regs(uint8_t start_reg, uint8_t *dst, int len);
static int16_t compute_mpu_pitch(int16_t ax,int16_t ay,int16_t az);

//------------------------------------------------------------------------------
// lcd 함수 선언
//------------------------------------------------------------------------------
void LCD_WriteCommand(uint8_t cmd);
void LCD_WriteData(uint8_t d);
void LCD_Init(void);
void LCD_GotoXY(uint8_t row, uint8_t col);
void LCD_WriteSignedInt(int16_t v);

//------------------------------------------------------------------------------
// oled 함수 선언
//------------------------------------------------------------------------------
void oled_write_cmd(uint8_t cmd);
void oled_write_data(uint8_t d);
void oled_init(void);
void oled_clear(void);
void oled_puts_str(int page, int col, const char *s);

//------------------------------------------------------------------------------
// oled 픽셀 그리기 & 버퍼 업데이트 함수 선언
//------------------------------------------------------------------------------
void oled_update_column(int page, int col); 
void oled_draw_pixel(int x, int y);

//==============================================================================
// main: 초기화 → 2D 매핑 → 최적 각도로 서보1 회전
//==============================================================================
int main(void) {
    uint8_t buf[14];
    int16_t ax, ay, az;

    init_platform();
    xil_printf("\n\r=== I2C Stabilization & Avoidance Test ===\n\r");

    volatile unsigned int *hub_reg = (volatile unsigned int *)HUB_BASE;
    //hub_reg[0] -> set_angle_1(wd)
    //hub_reg[1] -> set_angle_2(wd)
    //hub_reg[2] -> en_servo_2(wd)
    //hub_reg[3] -> mpu_pitch(wd)
    //hub_reg[4] -> x_out(rd)
    //hub_reg[5] -> y_out(rd)
    //hub_reg[6] -> ready(rd)

    // 1) I²C 마스터 초기화·시작
    XIic_Initialize(&iic_inst, IIC_CHNL);
    XIic_Start     (&iic_inst);
 //   msleep(10);

    // 2) LCD 초기화
    LCD_Init();
  //  msleep(10);

    // 3) OLED 초기화·클리어
    oled_init();
    oled_clear();
    msleep(10);

    // 4) MPU6050 슬립 해제·감도 설정
    mpu_write_reg(REG_PWR_MGMT1,    0x00);
    mpu_write_reg(REG_SMPLRT_DIV,   0x07);
    mpu_write_reg(REG_CONFIG,       0x06);
    mpu_write_reg(REG_GYRO_CONFIG,  0x18);
    mpu_write_reg(REG_ACCEL_CONFIG, 0x10);
 //   msleep(10);

    // 5) 메인 루프: 250 ms 간격으로 읽고 출력
    while (1) {
        hub_reg[0] = 14;
        hub_reg[1] = 3;
        hub_reg[2] = 0;
        hub_reg[3] = 0;

        const int NSTEP = 19;        // 0~18 인덱스
        int x_arr[NSTEP], y_arr[NSTEP];
        
        hub_reg[0] = 9;
        msleep(100);
        hub_reg[1] = 10;
        msleep(100);
        // — MPU6050 6축 읽기 —
        mpu_read_regs(REG_ACCEL_X_H, buf, 14);
        ax = (buf[0]<<8)|buf[1];
        ay = (buf[2]<<8)|buf[3];
        az = (buf[4]<<8)|buf[5];
        // gx = (buf[8]<<8)|buf[9];
        // gy = (buf[10]<<8)|buf[11];
        // gz = (buf[12]<<8)|buf[13];

        // “틸트(pitch)” 계산
        int16_t mpu_pitch = compute_mpu_pitch(ax, ay, az);

        // — UART 출력 —
        xil_printf("AX =%6d AY =%6d AZ =%6d     mpu_pitch =%4d\n\r", ax, ay, az, mpu_pitch);
//        msleep(100);

        LCD_GotoXY(0,0);
        LCD_WriteSignedInt(ax);
        LCD_WriteSignedInt(ay);
        LCD_WriteSignedInt(az);
print("1");
        hub_reg[2] = 1;
        usleep(1);

        print("2");


         msleep(10);
         hub_reg[3] = mpu_pitch;
print("3");

        oled_clear();
print("4");

        for (int idx = 0; idx < NSTEP; idx++) {
            // 1) trigger servomove/setangle 은 이미 밖에서 함
            // 2) hub_reg[6] 이 1 될 때까지 기다린다
            xil_printf("idx = %d", idx);

            while ((hub_reg[6] & 0x1) == 0) {
                usleep(1);
            }
            // 3) 1 이 올라왔으면 단 한 번만 읽고
            x_arr[idx] = (int)hub_reg[4];
            y_arr[idx] = (int)hub_reg[5];
            oled_draw_pixel(x_arr[idx], y_arr[idx]);
            // 4) flag clear
            hub_reg[6] = 0;
        }
        print("1");
print("20");

        msleep(100);

        // 최장 거리 좌표 찾기
        int sel_idx  = 0;
        int max_dsq  = 0;
        for (int idx = 0; idx < NSTEP; ++idx) {
            int dsq = x_arr[idx]*x_arr[idx] + y_arr[idx]*y_arr[idx];
            if (dsq > max_dsq) {
                max_dsq = dsq;
                sel_idx = idx;
            }
        }

        // 최장 거리 쪽으로 서보1 회전
        hub_reg[0] = sel_idx;
        msleep(500);

        // LCD 에 선택 각도(°) 표시
        LCD_GotoXY(1, 0);
        LCD_WriteSignedInt(sel_idx * 10);   // 인덱스→° (10° 단위)
        msleep(500);
    }

    cleanup_platform();
    return 0;
}

//------------------------------------------------------------------------------
// MPU6050 I²C 쓰기 헬퍼
//------------------------------------------------------------------------------
void mpu_write_reg(uint8_t reg, uint8_t val) {
    uint8_t buf[2] = { reg, val };
    XIic_Send(iic_inst.BaseAddress, MPU_ADDR, buf, 2, XIIC_STOP);
    while (XIic_IsIicBusy(iic_inst.BaseAddress));
}

//------------------------------------------------------------------------------
// MPU6050 I²C 연속 읽기 헬퍼
//------------------------------------------------------------------------------
void mpu_read_regs(uint8_t start_reg, uint8_t *dst, int len) {
    // 1) 읽을 레지스터 주소 전송 (Repeated Start 유지)
    XIic_Send(iic_inst.BaseAddress, MPU_ADDR, &start_reg, 1, 0);
    while (XIic_IsIicBusy(iic_inst.BaseAddress));
    // 2) 바로 읽기 → STOP
    XIic_Recv(iic_inst.BaseAddress, MPU_ADDR, dst, len, XIIC_STOP);
    while (XIic_IsIicBusy(iic_inst.BaseAddress));
}


//------------------------------------------------------------------------------
// @brief 근사: raw→pitch(°) 정수
//    sqrt≈max+0.5·min, atan2≈x/(1+0.28x²)
//------------------------------------------------------------------------------
static int16_t compute_mpu_pitch(int16_t ax,int16_t ay,int16_t az) {
    const float G=4096.0f;
    float x=(float)ax/G, y=(float)ay/G, z=(float)az/G;
    float ay0=y<0?-y:y, az0=z<0?-z:z;
    float vmax= ay0>az0? ay0:az0;
    float vmin= ay0>az0? az0:ay0;
    float denom = vmax + 0.5f*vmin + 1e-6f;
    float r = x/denom;
    float rad = r/(1.0f + 0.28f*r*r);
    float deg = rad * 57.2957795f;
    return (int16_t)(deg + (deg>=0?0.5f:-0.5f));
}


//------------------------------------------------------------------------------
// Text LCD 제어 함수들 (기존 코드 그대로)
//------------------------------------------------------------------------------
void LCD_WriteCommand(uint8_t cmd) {
    uint8_t high =  cmd   & 0xF0;
    uint8_t low  = (cmd<<4)& 0xF0;
    uint8_t seq[4] = {
        high|0x0C, high|0x08,
        low |0x0C, low |0x08
    };
    XIic_Send(iic_inst.BaseAddress, LCD_ADDR, seq, 4, XIIC_STOP);
    while (XIic_IsIicBusy(iic_inst.BaseAddress));
}

void LCD_WriteData(uint8_t d) {
    uint8_t high =  d     & 0xF0;
    uint8_t low  = (d <<4)& 0xF0;
    uint8_t seq[4] = {
        high|0x0D, high|0x08,
        low |0x0D, low |0x08
    };
    XIic_Send(iic_inst.BaseAddress, LCD_ADDR, seq, 4, XIIC_STOP);
    while (XIic_IsIicBusy(iic_inst.BaseAddress));
}

void LCD_Init(void) {
    msleep(50);
    LCD_WriteCommand(0x33); msleep(5);
    LCD_WriteCommand(0x32);
    LCD_WriteCommand(0x28);
    LCD_WriteCommand(0x0F);
    LCD_WriteCommand(0x06);
    LCD_WriteCommand(0x01); msleep(5);
}

void LCD_GotoXY(uint8_t row, uint8_t col) {
    uint8_t addr = 0x80 | (row<<6) | col;
    LCD_WriteCommand(addr);
}

// 부호 포함 5자리 찍기
void LCD_WriteSignedInt(int16_t v) {
    char tmp[6];
    // "%+5d" → 항상 부호, 총 5칸
    sprintf(tmp, "%+5d", v);
    for (int i = 0; i < 5; i++) {
        LCD_WriteData((uint8_t)tmp[i]);
    }
}

//------------------------------------------------------------------------------
// OLED 제어 함수들
//------------------------------------------------------------------------------
// Control + 명령어 1바이트
void oled_write_cmd(uint8_t cmd) {
    uint8_t buf[2] = { OLED_CTL_CMD, cmd };
    XIic_Send(iic_inst.BaseAddress, OLED_ADDR, buf, 2, XIIC_STOP);
    while (XIic_IsIicBusy(iic_inst.BaseAddress));
}

// Control + 데이터 1바이트
void oled_write_data(uint8_t d) {
    uint8_t buf[2] = { OLED_CTL_DAT, d };
    XIic_Send(iic_inst.BaseAddress, OLED_ADDR, buf, 2, XIIC_STOP);
    while (XIic_IsIicBusy(iic_inst.BaseAddress));
}

// SSD1306 초기화 (페이지 주소 모드 포함)
void oled_init(void) {
    const uint8_t seq[] = {
      0xAE,           // display off
      0x20,0x02,      // page addressing mode
      0xB0,           // page0
      0x00,0x10,      // col addr = 0
      0x40,           // start line = 0
      0x81,0x7F,      // contrast
      0xA1,           // seg remap
      0xC8,           // com scan dec
      0xA6,           // normal display
      0xA8,0x3F,      // multiplex 1/64
      0xD3,0x00,      // display offset
      0xD5,0x80,      // clk div
      0xD9,0x22,      // pre-charge
      0xDA,0x12,      // com pins
      0xDB,0x20,      // vcomh
      0x8D,0x14,      // charge pump on
      0xAF            // display on
    };
    for (int i = 0; i < (int)sizeof(seq); i++) {
        oled_write_cmd(seq[i]);
    }
}

// 전체 화면 클리어
void oled_clear(void) {
    for (int p = 0; p < 8; p++) {
        oled_write_cmd(0xB0 | p);
        oled_write_cmd(0x00);
        oled_write_cmd(0x10);
        for (int c = 0; c < 128; c++) {
            oled_write_data(0x00);
        }
    }
}

//------------------------------------------------------------------------------
// 문자열(부호 포함 숫자) 출력 helper
//------------------------------------------------------------------------------
// page: 0~7, col: 문자 단위(0~15), s: null-terminated 문자열
void oled_puts_str(int page, int col, const char *s) {
    // 1) 페이지·컬럼 설정
    oled_write_cmd(0xB0 | page);
    uint8_t x = col * 8;
    oled_write_cmd(0x00 | (x & 0x0F));
    oled_write_cmd(0x10 | ((x >> 4) & 0x0F));
    // 2) 글자별 8바이트 폰트 전송
    while (*s) {
        uint8_t buf[9];
        buf[0] = OLED_CTL_DAT;
        // ASCII 0x20~0x7F 대응
        memcpy(buf+1, font8x8_basic[(uint8_t)*s - 0x20], 8);
        XIic_Send(iic_inst.BaseAddress, OLED_ADDR, buf, 9, XIIC_STOP);
        while (XIic_IsIicBusy(iic_inst.BaseAddress));
        s++;
    }
}

//------------------------------------------------------------------------------
// OLED 픽셀 업데이트: 버퍼→실제 GDDRAM (한 컬럼만 갱신)
//------------------------------------------------------------------------------
void oled_update_column(int page, int col) { 
    oled_write_cmd(0xB0 | page);              // page 설정
    oled_write_cmd(0x00 | (col & 0x0F));      // col low
    oled_write_cmd(0x10 | ((col>>4)&0x0F));   // col high
    oled_write_data(oled_buf[page][col]);     // 버퍼값 전송
}

//------------------------------------------------------------------------------
// (x,y) 에 1픽셀 그리기 (버퍼 갱신 + 해당 컬럼만 갱신)
//------------------------------------------------------------------------------
void oled_draw_pixel(int x, int y) {       
    if (x<0||x>127||y<0||y>63) return;
    int page = y / 8;
    int bit  = y % 8;
    oled_buf[page][x] |= (1<<bit);            // 버퍼에 비트 세팅
    oled_update_column(page, x);              // 실제 화면에 바로 반영
}