[Servo Motor]
📌 -90˚~90˚까지 순차적으로 구동
module Servo_Motor_Top(
input clk, rstp,
output servo,
output [3:0] com,
output [7:0] seg_7
);
reg [21:0] clk_div; // 너무 빨리 동작하면 안 되니까, 분주기 생성하여 적용
always @(posedge clk) clk_div = clk_div + 1;
wire clk_div_21_ne;
edge_detector_p edg(.clk(clk), .cp_in(clk_div[21]), .rst(rstp), .n_edge(clk_div_21_ne)); // Edge Detector
reg [9:0] duty;
reg down_up;
parameter UP = 0;
parameter DOWN = 1;
always @(posedge clk, posedge rstp)begin
if(rstp) begin
duty = 50;
down_up = 0;
end
else if(clk_div_21_ne) begin
if(down_up) begin
if(duty < 28) down_up = UP;
else duty = duty - 1;
end
else begin
if(duty > 128) down_up = DOWN;
else duty = duty + 1;
end
end
end
PWM_1000 pwm_servo(.clk(clk), .rstp(rstp), .duty(duty), .pwm_freq(50), .pwm_1000pc(servo));
endmodule
📌 구동 각도를 FND로 출력
module Servo_Motor_Top(
input clk, rstp,
output servo,
output [3:0] com,
output [7:0] seg_7
);
reg [21:0] clk_div;
always @(posedge clk) clk_div = clk_div + 1;
wire clk_div_21_ne;
edge_detector_p edg(.clk(clk), .cp_in(clk_div[21]), .rst(rstp), .n_edge(clk_div_21_ne)); // Edge Detector
reg [9:0] duty;
reg down_up;
parameter UP = 0;
parameter DOWN = 1;
always @(posedge clk, posedge rstp)begin
if(rstp) begin
duty = 50;
down_up = 0;
end
else if(clk_div_21_ne) begin
if(down_up) begin
if(duty < 28) down_up = UP;
else duty = duty - 1;
end
else begin
if(duty > 128) down_up = DOWN;
else duty = duty + 1;
end
end
end
PWM_1000 pwm_servo(.clk(clk), .rstp(rstp), .duty(duty), .pwm_freq(50), .pwm_1000pc(servo));
wire [15:0] bcd_duty;
bin_to_dec btd(.bin({2'b00, duty}), .bcd(bcd_duty));
FND_4digit_cntr fnd_cntr(.clk(clk), .rst(reset_p), .value(bcd_duty), .com(com), .seg_7(seg_7));
endmodule
[DC Motor]
📌 Motor Driver에 대해
📌 이전 코드 활용하여 구동
DC 모터는 100~1kHz의 주파수로 구동이 가능하고
(낮으면 진동이 발생하며 기동, 높으면 모터 동작 안 함)
우리가 쓰는 출력이 적은 모터같은 경우에는
Servo Motor 코드를 그대로 사용해도 구동시킬 수는 있다.
module Servo_Motor_Top(
input clk, rstp,
output servo,
output [3:0] com,
output [7:0] seg_7
);
reg [21:0] clk_div;
always @(posedge clk) clk_div = clk_div + 1;
wire clk_div_21_ne;
edge_detector_p edg(.clk(clk), .cp_in(clk_div[21]), .rst(rstp), .n_edge(clk_div_21_ne)); // Edge Detector
reg [9:0] duty;
reg down_up;
parameter UP = 0;
parameter DOWN = 1;
always @(posedge clk, posedge rstp)begin
if(rstp) begin
duty = 50;
down_up = 0;
end
else if(clk_div_21_ne) begin
if(down_up) begin
if(duty < 28) down_up = UP;
else duty = duty - 1;
end
else begin
if(duty > 128) down_up = DOWN;
else duty = duty + 1;
end
end
end
PWM_1000 pwm_servo(.clk(clk), .rstp(rstp), .duty(duty), .pwm_freq(50), .pwm_1000pc(servo));
wire [15:0] bcd_duty;
bin_to_dec btd(.bin({2'b00, duty}), .bcd(bcd_duty));
FND_4digit_cntr fnd_cntr(.clk(clk), .rst(reset_p), .value(bcd_duty), .com(com), .seg_7(seg_7));
endmodule
📌 DC모터 구동 시작점 찾기
PWM을 활용하여 전압을 높이거나 낮춰서 모터의 회전 속도를 조정할 수 있다.
정확하게 조절하기 위해서 모터가 회전을 시작하는 duty cycle의 시작점을 알아야 하는데,
부품마다 그 시점이 다 다르므로 직접 찾아줘야 한다.
또한 모터에 부하가 달라질 경우,
기동부하 이상의 힘이 출력되는 시점을 찾아줘야 하고
이떄는 최대 부하 상태에서 duty 값을 하나씩 늘려가면서 그 시점을 찾으면 된다.
버튼입력으로 0~99까지 세는 카운터를 활용하여 0~99까지 duty 값을 올려가며 구동 시작점을 찾아보도록 하자.
module DC_Motor_Top(
input clk, rstp,
input [3:0] btn,
output DC,
output [3:0] com,
output [7:0] seg_7
);
wire [2:0] cnt;
button_cntr btn_cntr_mode(.clk(clk), .reset_p(rstp), .btn(btn[0]), .btn_pe(cnt_btn));
reg [7:0] duty;
// 구동 시작점을 찾기 위한 카운터
always @(posedge clk, posedge rstp)begin
if(rstp) begin
duty = 0;
end
else if(cnt_btn) begin // 버튼 입력에 따라 duty 값이 증가한다.
if(duty >= 100) duty = 0;
else duty = duty + 1;
end
end
PWM_100 pwm_dc(.clk(clk), .rstp(rstp), .duty(duty), .pwm_freq(100), .pwm_100pc(DC));
wire [15:0] bcd_duty;
bin_to_dec btd(.bin({2'b00, duty}), .bcd(bcd_duty));
FND_4digit_cntr fnd_cntr(.clk(clk), .rst(reset_p), .value(bcd_duty), .com(com), .seg_7(seg_7));
endmodule
📌 DC모터 Max까지 돌려보기
우리는 100의 정밀도를 가진 PWM 모듈을 사용하여 모터의 속도를 제어해볼 것이다.
100의 정밀도를 가진다는 것은 모터 속도를 총 100단계로 조절할 수 있다는 의미로 보면 된다.
우리는 pwm_freq를 100Hz로 줬는데,
만약 1kHz까지 주고 사용한다면, PWM 주기가 더욱 더 짧아져서
보다 연속된 출력을 얻을 수 있다.
module DC_Motor_Top(
input clk, rstp,
output DC,
output [3:0] com,
output [7:0] seg_7
);
reg [21:0] clk_div;
always @(posedge clk) clk_div = clk_div + 1;
wire clk_div_21_ne;
edge_detector_p edg(.clk(clk), .cp_in(clk_div[21]), .rst(rstp), .n_edge(clk_div_21_ne)); // Edge Detector
reg [7:0] duty;
reg down_up;
parameter UP = 0;
parameter DOWN = 1;
always @(posedge clk, posedge rstp)begin
if(rstp) begin
duty = 0;
down_up = 0;
end
else if(clk_div_21_ne) begin
if(down_up) begin
if(duty < 11) down_up = UP;
else duty = duty - 1;
end
else begin
if(duty > 99) down_up = DOWN;
else duty = duty + 1;
end
end
end
PWM_100 pwm_dc(.clk(clk), .rstp(rstp), .duty(duty), .pwm_freq(100), .pwm_100pc(DC));
wire [15:0] bcd_duty;
bin_to_dec btd(.bin({4'b0000, duty}), .bcd(bcd_duty));
FND_4digit_cntr fnd_cntr(.clk(clk), .rst(reset_p), .value(bcd_duty), .com(com), .seg_7(seg_7));
endmodule
[ADC]
Cds 같은 부품은 전압 값으로 입력이 들어온다 - 빛의 밝기에 따라 저항값이 달라지는 부품이므로
온도 센서는 온도에 따라 저항이 달라지며, 이에 따라 흐르는 전압이 달라진다.
결국 우리는 전압을 읽어야 하는데, 이러한 전압값은 아날로그 값이다.
하지만 FPGA Board는 디지털 신호로 동작하기때문에 아날로그 신호를
디지털 신호로 변환시켜주는 기능이 필요하고 이 기능을 하는 ADC 회로가 이미 보드에 달려있다.
(FPGA로는 ADC를 구현하지 못하기 떄문에 추가로 ADC 회로가 달려 있다.)
📌 ADC Module Setting
좌측 상단의 IP Catalog를 클릭하여 ADC Wizard로 들어가보자.
위 그림과 같이 ADC Wizard 창이 뜨고 이걸로 ADC 모듈을 Setting할 수 있다.
여러 개의 채널을 쓸 때는 Independent ADC 사용
Channel Sequencer
: 여러 개의 채널을 사용하고 있을 때, 스케쥴링에 따라서 스캔하는 역할을 하고
스캔이 완료되면 eos_out 신호를 보내주는 역할도 한다.
Continuous mode : 계속 읽는다
Event mode : 내가 원하는 시기에 읽는다.
ADC Conversion Rate(KSPS) 1000 : 1초에 1000번 읽는다.(Continuous 일 때)
그런데 왼쪽의 모듈을 보면, ADC 모듈은 하나인데 채널은 16개나 존재하고
이럴 때는 어떻게 해야 돼? MUX써서 채널을 골라주면 된다.(ADC MUX Register)
근데 이 채널들을 다 쓸 수는 없디.
채널이 16개가 존재하지만, I/O Block의 한계로 인해
실제로 사용할 수 있는 것은 아래의 xdc 파일에서처럼 0~5까지 6개만 사용할 수 있다.
Channel Averaging
: Running Average를 몇으로 할 것이냐? 64정도로 준다
-> 노이즈때문에 오작동하는 것을 방지하기 위해 필터링
위 기능을 쓸 거니까 Enable CALIBRATION Averaging 체크
칩의 온도가 125도 이상이 되면 알람을 발생시켜주겠다는 뜻.
ot_out 여기에 부저를 달아주면 칩 온도가 한계 이상으로 올라가면 부저가 울릴 것이다.
위와 같이 ADC 모듈이 생성된 것을 확인할 수 있다.
왼쪽 아래 화살표 누르고 xadc_wiz_0를 더블클릭하면 아래와 같이 모듈의 코드를 확인할 수 있다.
이제 이 모듈을 인스턴스하여 사용하면 된다.
근데 input/output이 선언된 것을 보면 , 우리가 쓰던 문법과 좀 다르다는 것을 느낄 수 있다.
이게 원래 표준 문법인데, 지금 우리가 쓰는 inout 선언은 개정된 방법이고
위와 같이 공식적으로 만들어진 모듈에서는 표준 문법을 사용한다.
필요없는거 다 주석처리하자.
module adc_ch6_top(
input clk, rstp,
input vauxp6, vauxn6, // (-)단자, (+)단자 / 입력 받아야 측정할 수 있으니까 여기 선언, p에 아날로그 신호주고 n에 접지주면 된다.
output [3:0] com,
output [7:0] seg_7
);
wire [4:0] channel_out;
wire [15:0] do_out;
wire eoc_out;
xadc_wiz_0 adc_ch6(
.daddr_in({2'b00, channel_out}), // 이 ADC 모듈의 주소를 주면 된다.channel out에서 주소가 나온다 원래 안 줘도 되는 건데
.dclk_in(clk),
.den_in(eoc_out), // 변환이 끝났을 때 enavble되어야 한다.
// di_in, // Input data bus for the dynamic reconfiguration port
// dwe_in, // Write Enable for the dynamic reconfiguration port
.reset_in(1'b0), // 리셋 안 할거니까 0준다(1에서 리셋됨.)
.vauxp6(vauxp6), // Auxiliary channel 6
.vauxn6(vauxn6),
// .busy_out(), // 아직 변환이 안 끝났다는 뜻. 변환 안 끝났으면 1
.channel_out(channel_out), // Channel Selection Outputs
.do_out(do_out), // 변한작업이 다 끝나면 do_out(변환 결과)을 읽는다.얘는 분해능이 12bit인데 왜 16bit나 할당되었냐?
// 변환이 완료되었다는 정보를 담은 4bit 추가된 것
// drdy_out, // Data ready signal for the dynamic reconfiguration port
.eoc_out(eoc_out) //변환 작업이 끝났을 때 1이 출력된다. 여기에 1뜨면 do_out을 읽으면 돈다.
// eos_out, // Sequencer 쓸 때, 스캔이 다 끝났으면 여기서 1이 뜬다. 1뜨면 do_out을 읽으면 된다.
// alarm_out, // OR'ed output of all the Alarms
// vp_in, // Dedicated Analog Input Pair
// vn_in
); // ADC 모듈 인스턴스
wire eoc_out_pe;
edge_detector_n edg_eoc(.clk(clk), .rst(rstp), .cp_in(eoc_out), .p_edge(eoc_out_pe)); // Edga Detector
reg [11:0] adc_value;
always @(posedge clk) begin
if(eoc_out_pe) adc_value = do_out[15:4];
// if(eoc_out_pe) adc_value = {2'b00, do_out[15:6]};
else adc_value = adc_value;
end
wire [15:0] bcd_adc;
bin_to_dec btd(.bin(adc_value), .bcd(bcd_adc)); // do_out의 상위 12bit를 쓰면 된다. 최대값은 4095가 나올 것이다.
// 여기서 do_out을 그대로 줘버렸는데, 그러면 바뀌는 값이 그대로 나온다. 따라서 정석대로 eoc_out이 1 나왔을 때 읽자.
FND_4digit_cntr fnd_cntr(.clk(clk), .rst(rstp), .value(bcd_adc), .com(com), .seg_7(seg_7));
endmodule
우리는 eoc_out 이 1로 변하면 do_out 읽어서 출력하면 된다.
cora z7 data sheet 두 번쨰 페이지 보면 이미 GND로 연결이 되어 있는 것을 볼 수 있다.
핀맵을 보고 an_p[2]가 어디에 연결되는지 찾아보자.
📌 Operation - 가변저항
64로 이동평균하여 튀는 값들을 필터링했는데, 그래도 아직 튀는 값들이 발생한다.
이건 256으로 수정하면 해결될 듯?
reg [11:0] adc_value;
always @(posedge clk) begin
// if(eoc_out_pe) adc_value = do_out[15:4];
if(eoc_out_pe) adc_value = {2'b00, do_out[15:6]};
else adc_value = adc_value;
end
그래도 안 되면 아래 2bit 버리자.
그럼 4로 나뉜 값이 나올 거니까 1023까지 나올 것이다.
📌 Operation - CDS, Joystick
어짜피 다 아날로그 신호를 디지털 신호로 변환하여 사용하는 부품들이므로
해당 ADC 모듈 하나로 다 입력을 받을 수 있다.
'# Semiconductor > - Semicon Academy' 카테고리의 다른 글
[Harman 세미콘 아카데미] 66일차 - Verilog(다기능 선풍기 프로젝트 발표 및 마무리) (1) | 2023.10.02 |
---|---|
[Harman 세미콘 아카데미] 63~65일차 - Verilog(Project : Portable Fan) (0) | 2023.09.23 |
[Harman 세미콘 아카데미] 61일차 - Verilog(PWM을 활용한 Servo Motor의 각도 제어) (0) | 2023.09.20 |
[Harman 세미콘 아카데미] 59~61일차 - Verilog(PWM 활용, 다기능 시계 구현 프로젝트) (0) | 2023.09.18 |
[Harman 세미콘 아카데미] 58일차 - Verilog(PWM 코드 및 시뮬레이션) (0) | 2023.09.15 |