Verilog 팀 프로젝트 - 스마트 수경재배기
1. 서론
- 스마트 수경 재배기 라는 주제로 Verilog 팀 프로젝트를 진행하였습니다.
- 스마트 수경 재배기는 수업을 통해 배운 Verilog 지식과 다양한 센서 모듈을 이용하여 기존의 수경 재배기의 불편함을 반 자동화하여 해소하고자 해당 주제를 팀 프로젝트의 주제로 선정하였습니다.
1.1. 기존 수경 재배기의 불편함
- 아래 사진은 글쓴이의 집에서 실제로 재배하고 있는 수경 재배기 입니다.
- 실제로 수경 재배기을 이용하먼서 다양한 농작물 및 채소를 재배할 수 있었지만, 재배하는 과정에서 다양한 불편함 및 개선사항을 찾을 수 있었습니다.
- 제가 경험한 기존 수경 재배기의 불편함은 다음과 같은 3가지 였습니다.
▶ 1. 식물 작물에 필요한 급수 수동 공급
▶ 2. 재배기의 LED 높이 수동 조절
▶ 3. 더운 날씨로 인한 작물 성장 부진 - 위와 같은 기존 수경 재배기의 불편함을 Basys3와 다양한 센서를 이용하여 해결하기 위해 "스마트 수경 재배기" 라는 주제를 선택하여 프로젝트를 진행하였습니다.
1.2. 스마트 수경 재배기의 특징
1) 식물 작물에 필요한 급수 수동 공급 문제 해결
- 기존 수경 재배기는 사용자가 직접 눈으로 급수가 충분한지 여부를 확인한 뒤, 부족하다고 판단되면 사용자가 수동적으로 수경 재배기에 급수를 보충해야 하는 불편함을 가지고 있었습니다.
- 이는 특히 바쁜 현대인들에게 번거롭고 불편한 요소가 될 수 있습니다.
- 이 문제를 해결하기 위해 수경 재배기의 벽면에 수위 센서를 부착하여 실시간으로 수위를 모니터링하고, 물이 부족할 경우 자동으로 워터 펌프를 작동시켜 충분한 수위에 도달할 때까지 물을 공급함으로써 불편함을 해소할 수 있습니다.
2) 재배기의 LED 높이 수동 조절 문제 해결
- 수경 재배기의 LED는 밤과 같은 광합성이 부족한 환경에서도 충분한 빛을 제공하며, 식물의 성장에 따라 LED의 높이를 조정함으로써 작물에 필요한 광합성을 효과적으로 공급할 수 있습니다.
- 다만, 사용자가 매일 식물의 높이를 확인하고 직접 LED의 높이를 조절해야 하는 번거로움이 있습니다.
- 이러한 불편함을 해결하기 위해 LED에 초음파 센서를 부착하여 실시간으로 작물의 높이를 모니터링하고, LED와의 거리가 너무 가깝다고 판단하면 상단 모터를 작동시켜 LED를 적절한 거리로 자동 조정함으로써 식물에게 필요한 광합성을 효과적으로 공급할 수 있습니다.
3) 작물 성장에 어려운 환경으로부터 보호
- 2024년 여름은 예년보다 유난히 더웠습니다. 이로 인해 작물의 성장이 어려워졌고, 씨앗이 발아하여 싹이 나야 할 시기가 지나도 싹이 트지 않는 문제가 발생하였습니다.
- 이러한 문제점을 해결하고자 외부에 장착된 온습도 센서와 조도 센서를 활용하여, 외부 환경 변화에 맞춰 작물을 보호하고 최적의 성장 환경을 조성함으로써 작물의 성장을 극대화할 수 있습니다.
4) 휴대폰을 통한 재배기 외부 환경 확인 가능
- 재배기 외부의 온도와 습도를 직접 확인하기 위해 현장에 갈 필요 없이, UART 통신을 활용한 블루투스 모듈을 통해 휴대폰에서 실시간으로 환경 정보를 확인할 수 있습니다.
1.3. 3D 구상도
- 스마트 수경 재배기의 3D 하드웨어 구상도는 다음과 같습니다.
2. 본론
2.1. 스마트 수경 재배기의 블록 다이어그램
- 스마트 수경 재배기의 블록 다이어그램은 다음과 같습니다.
- 스마트 수경 재배기의 모듈은 크게 1) 센서 모듈 과 2) 컨트롤 모듈로 나누어 설계하였습니다.
▶ 센서 모듈
- dht11_control [온습도 센서 컨트롤 모듈]
- cds_and_water_level_control [조도 센서와 수위 센서 컨트롤 모듈]
- hc_sr04_control [초음파 센서 컨트롤 모듈]
▶ 컨트롤 모듈
- led_control [수경 재배기의 LED 밝기 조절 컨트롤 모듈]
- led_height_control [LED 높이 컨트롤 모듈]
- electric_fan_control [선풍기 컨트롤 모듈]
- window_control [수경 재배기 창문 컨트롤 모듈]
- water_pump_control [워터 펌프 컨트롤 모듈]
- lcd_display_control [LCD Display 컨트롤 모듈]
- uart_app_control [UART 통신 및 앱 데이터 전달 컨트롤 모듈]
2.2. 스마트 수경 재배기의 동작
- 스마트 수경 재배기는 다음과 같은 기능 및 동작을 지원합니다.
2.3. 스마트 수경 재배기의 컨트롤 버튼 및 스위치
- 수경 재배기의 기능을 제어하기 위해 다음과 같이 버튼과 스위치를 구성하였습니다.
2.4. 스마트 수경 재배기의 모듈 구현
- 스마트 수경 재배기의 모듈에 대한 설명은 소스코드와 함께 주석 및 추가적인 설명을 통해 설명하도록 하겠습니다.
< Source, Top module of smart farm >
// Top moudle of Smarr Farm
module top_module_of_smart_farm (
input clk, reset_p,
input [3:0] btn,
input [15:0] sw,
inout dht11_data,
input vauxp6, vauxn6,
input vauxp15, vauxn15,
input hc_sr04_echo,
input rx,
output tx,
output hc_sr04_trig,
output left_window_pwm, right_window_pwm,
output fan_pwm,
output fan_dir_pwm,
output led_pwm,
output warning_water_level_led,
output pump_on_off,
output [3:0] half_step_mode_sequence,
output scl, sda,
output [3:0] com,
output [7:0] seg_7);
// Button Control module
// Button Chattering 문제를 소프트웨어로 해결하기 위해 1ms delay time을 갖고 버튼 값을 받는다.
wire btn_led_light, btn_window_mode, btn_electric_fan_power;
button_cntr btn0(.clk(clk), .reset_p(reset_p), .btn(btn[0]), .btn_pedge(btn_led_light));
button_cntr btn1(.clk(clk), .reset_p(reset_p), .btn(btn[1]), .btn_pedge(btn_window_mode));
button_cntr btn2(.clk(clk), .reset_p(reset_p), .btn(btn[2]), .btn_pedge(btn_electric_fan_power));
// Declare Switch
// 수경 재배기를 제어하기 위한 스위치 선언
wire sw_led_up, sw_led_down, sw_window_open, sw_window_close, sw_cntr_electirc_fan_dir, sw_electric_fan_mode;
wire sw_led_mode, sw_led_height_mode;
assign sw_led_height_mode = sw[0];
assign sw_led_up = sw[1];
assign sw_led_down = sw[2];
assign sw_window_open = sw[6];
assign sw_window_close = sw[7];
assign sw_led_mode = sw[10];
assign sw_cntr_electirc_fan_dir = sw[11];
assign sw_electric_fan_mode = sw[15];
// Declare sensor variables.
// 센서 모듈에서 제어 모듈로 센서값을 전달에 필요한 Wire 선언
wire [15:0] dht11_value; // 온습도 데이터
wire [7:0] sunlight_value; // 조도 값 (8bit 양자화)
wire water_flag; // 재배기에 물이 부족함을 알려주는 플래그
wire led_up_down; // LED의 높이를 올릴지 내릴지를 알려주는 플래그
wire [21:0] distance_cm; // 현재 LED와 작물과의 거리
// Instance of sensor module
// 센서 모듈 인스턴스 선언
dht11_control dht11_control_instance (.clk(clk), .reset_p(reset_p), .dht11_data(dht11_data), .dht11_value(dht11_value));
cds_and_water_level_control cds_and_water_level_control_instance (.clk(clk), .reset_p(reset_p), .vauxp6(vauxp6), .vauxn6(vauxn6), .vauxp15(vauxp15), .vauxn15(vauxn15), .water_flag(water_flag), .sunlight_value(sunlight_value) );
HC_SR04_cntr HC_SR04_cntr_0(.clk(clk), .reset_p(reset_p), .hc_sr04_echo(hc_sr04_echo), .hc_sr04_trig(hc_sr04_trig), .distance(distance_cm), .led_debug(led_debug));
// Instance of Control module
// 컨트롤 모듈 인스턴스 선언
window_control window_control_instance (.clk(clk), .reset_p(reset_p), .dht11_value(dht11_value), .sw_window_open(sw_window_open), .sw_window_close(sw_window_close), .btn_window_control(btn_window_mode), .left_window_pwm(left_window_pwm), .right_window_pwm(right_window_pwm));
electric_fan_control electric_fan_control_instance (.clk(clk), .reset_p(reset_p), .sw_cntr_electirc_fan_dir(sw_cntr_electirc_fan_dir), .sw_electric_fan_mode(sw_electric_fan_mode), .dht11_value(dht11_value), .btn_electric_fan_power(btn_electric_fan_power), .fan_pwm(fan_pwm), .fan_dir_pwm(fan_dir_pwm));
led_control led_control_instance (.clk(clk), .reset_p(reset_p), .sw_led_mode(sw_led_mode), .btn_led_light(btn_led_light), .water_flag(water_flag), .sunlight_value(sunlight_value), .led_pwm(led_pwm), .warning_water_level_led(warning_water_level_led));
water_pump water_pump_instance (.clk(clk), .reset_p(reset_p), .water_flag(water_flag), .pump_on_off(pump_on_off));
led_height_control led_height_control_instance ( .clk(clk), .reset_p(reset_p), .sw_led_height_mode(sw_led_height_mode), .distance_cm(distance_cm),
.sw_led_up(sw_led_up), .sw_led_down(sw_led_down), .half_step_mode_sequence(half_step_mode_sequence));
// Instance of a module that displays temperature and humidity information
// 현재의 외부 환경 온도, 습도를 LCD 화면과 휴대폰으로 실시간 출력
lcd_display_control lcd_display_control_instance (.clk(clk), .reset_p(reset_p), .dht11_value(dht11_value), .sunlight_value(sunlight_value), .water_flag(water_flag), .scl(scl), .sda(sda));
uart_app_control uart_app_control_instance (.clk(clk), .reset_p(reset_p), .rx(rx), .dht11_value(dht11_value), .tx(tx));
// Show temperature, humidity to FND
// Basys3 의 FND을 통해 외부 환경 온도, 습도를 실시간 출력
show_the_fnd show_the_fnd_instance(.clk(clk), .reset_p(reset_p), .hex_value(dht11_value), .sunlight_value(sunlight_value), .com(com), .seg_7(seg_7), .distance_cm(distance_cm));
endmodule
- Top module of smart-farm은 센서 모듈들로부터 다음과 같은 데이터들 받게 됩니다.
▶ dht11_value : DHT11 센서로부터 얻은 온도, 습도 데이터
▶ sunlight_value : 조도 센서 값을 8bit 양자화하여 디지털 값으로 변환한 데이터
▶ water_flag : 현재 재배기의 물이 부족한지 여부를 알려주는 플래그 [물이 부족할 경우 == 1]
▶ led_up_down : 초음파 센서로 부터 LED 높이를 올릴지 내릴지에 관한 정보를 갖는 데이터
▶ distance_cm : 초음파 센서를 통해 측정한 현재 LED와 작물간에 거리 데이터 - Top module of smart-farm에서는 이렇게 전달 받은 센서 값을 각각의 제어 모듈로 전달되게 되며, 이를 통해 제어 모듈은 각각의 기능들을 컨트롤하게 됩니다.
< Source, The current temperature and humidity data are displayed on the FND. >
module show_the_fnd (
input clk, reset_p,
input [15:0] hex_value,
input [7:0] sunlight_value,
input [21:0] distance_cm,
output [3:0] com,
output [7:0] seg_7);
// Convert from binary to BCD Code
// 이진수로 표현된 데이터를 BCD 코드로 변환한다.
wire [11:0] temperature_bcd, humidity_bcd;
bin_to_dec bcd_temp(.bin({4'b0, hex_value[15:8]}), .bcd(temperature_bcd));
bin_to_dec bcd_humi(.bin({4'b0, hex_value[7:0]}), .bcd(humidity_bcd));
// FND Control module
// Basys3의 FND로 현재의 온도, 습도 출력한다.
fnd_cntr fnd_cntr_instance (.clk(clk), .reset_p(reset_p), .value({temperature_bcd[7:0], humidity_bcd[7:0]}), .com(com), .seg_7(seg_7));
endmodule
- 실시간으로 외부 환경의 온도, 습도 데이터를 전달 받은 뒤, BCD 코드로 변환한 후, Basys3의 FND로 출력합니다.
2.4.1. Sensor Module
< Source, DHT11 Control Module >
// DHT11 Control module
module dht11_control(
input clk, reset_p,
inout dht11_data,
output [15:0] dht11_value);
wire [7:0] humidity, temperature;
dht11_cntrl dth11( .clk(clk), .reset_p(reset_p), .dht11_data(dht11_data), .humidity(humidity), .temperature(temperature), .led_debug(led_debug));
assign dht11_value = {temperature[7:0], humidity[7:0]};
endmodule
- DHT11 온습도 센서를 통해 현재 외부 환경의 온습도 데이터를 얻은 뒤, 16bit wire 변수에 결합하여 Top module로 전달합니다.
- DHT11 control module에 대해서 궁금하다면 아래 게시글을 참고해주세요.
https://jbhdeve.tistory.com/311
Verilog RTL 설계(7월 23일 - 5, DHT11 구현 (4) )
1. DHT11과 Basys3 통신 과정에서 예외 상황이 발생했을 경우이전 게시글인 Verilog RTL 설계(7월 23일 - 4, DHT11 구현 (3)) 에서는 dht11과 Basys3 간에 통신이 원활히 이루어져서 온도, 습도 데이터를 제대
jbhdeve.tistory.com
< Source, Cds and Water Level Control Module >
// cds_and_water_level_control module
module cds_and_water_level_control (
input clk, reset_p,
input vauxp6, vauxn6,
input vauxp15, vauxn15,
output reg water_flag,
output reg [7:0] sunlight_value );
wire [4:0] channel_out;
wire [16:0] do_out;
wire eoc_out;
// ADC Module instance
xadc_wiz_10 xadc_cds (
.daddr_in({2'b0, channel_out}),
.dclk_in(clk),
.den_in(eoc_out),
.reset_in(reset_p),
.vauxp6(vauxp6),
.vauxn6(vauxn6),
.vauxp15(vauxp15),
.vauxn15(vauxn15),
.channel_out(channel_out),
.do_out(do_out),
.eoc_out(eoc_out)
);
// Get positive edge of eoc_out.
wire eoc_out_pedge;
edge_detector_p ed_eoc_out_cds(.clk(clk), .reset_p(reset_p), .cp(eoc_out), .p_edge(eoc_out_pedge));
// Channel_out 변수 값에 따라 변환된 Channel이 무엇인지를 확인 후, 해당 값을 출력
// do_out 값이 100미만 이면 물이 부족한 상태 ----> 1
// do_out 값이 100이상 이면 물이 충분한 상태 ----> 0
always @(posedge clk or posedge reset_p) begin
if(reset_p) begin
sunlight_value = 0;
water_flag = 0;
end
else if(eoc_out_pedge) begin
case(channel_out[3:0])
6 : begin sunlight_value = do_out[15:8]; end
15 : begin water_flag = (do_out[15:9]<100) ? 1 : 0; end
endcase
end
end
endmodule
- 수위 센서를 통해 실시간으로 수경 재배기의 수위 상태를 모니터링 합니다.
- 따라서 물이 부족하다고 판단할 경우, Top module 에게 water_flag 플래그를 통해 "물이 부족한 상태이니, 워터 펌프를 가동해주세요." 라고 알리도록 설계하였습니다.
- 실험을 통해 수위 센서의 아날로그 값을 8bit로 양자화했을 경우, 100이하 일 때, 물이 부족함을 알리도록 설계하였습니다.
★★★★★★★★ 이번 프로젝트를 통해 알게 된 점 ★★★★★★★★ - 이전 프로젝트까지는 하나의 ADC 컨버터만 사용했지만, 이번 프로젝트에서는 두 개의 아날로그 값을 디지털로 변환하는 작업을 수행하였습니다.
- 이 과정에서 서로 다른 두 개의 ADC 변환을 하나의 모듈 내에서 동시에 처리해야 한다는 점을 배우게 되었습니다.
< Source, HC-SR04 Control Module >
// hc_sr04_control
module hc_sr04_control (
input clk, reset_p,
input hc_sr04_echo,
output hc_sr04_trig,
output [7:0] distance_between_plant_and_led );
// Instance of HC_SR04 Control module
HC_SR04_cntr HC_SR04_cntr_1 (.clk(clk), .reset_p(reset_p), .hc_sr04_echo(hc_sr04_echo), .hc_sr04_trig(hc_sr04_trig), .distance(distance_between_plant_and_led));
endmodule
- HC-SR04 초음파 센서를 통해 실시간으로 LED와 식물간에 거리 값을 Top module로 전달하며, 이를 바탕으로 식물에게 효과적으로 광합성을 제공할 수 있는 높이로 LED를 조정하게 됩니다.
- HC_SR04 Control Module에 대해서 궁금하시다면 아래 게시글을 참고 해주세요.
https://jbhdeve.tistory.com/312
Verilog RTL 설계(7월 24일 - 1, HC-SR04 구현 )
1. HC-SR04 초음파 센서굉장히 늦었지만, 이번 게시글에서는 초음파 센서인 HC-SR04에 대해서 다루어보도록 하겠다.HC-SR04 초음파 센서는 40kHz 주파수를 갖는 초음파를 통해 거리를 측정하는 초음파
jbhdeve.tistory.com
2.4.2. Control Module
< Source, led_control Module >
// LED Control module
module led_control (
input clk, reset_p,
input sw_led_mode,
input btn_led_light,
input water_flag,
input [7:0] sunlight_value,
output led_pwm, warning_water_level_led );
// Warning Water Level LED
assign warning_water_level_led = water_flag;
// Main LED Control
// Declare state machine
parameter S_MANUAL_MODE = 2'b01;
parameter S_AUTO_MODE = 2'B10;
// Declare state variable
reg [1:0] state;
// Declare duty variabels
reg [2:0] manual_duty; // 수동 모드의 LED Duty 값
reg [7:0] auto_duty; // 자동 모드의 LED Duty 값
wire manual_pwm, auto_pwm; // 수동 모드의 LED PWM, 자동 모드의 LED PWM
// 언제 다음 상태로 전이되는가?
always @(posedge clk or posedge reset_p) begin
if(reset_p) state = S_MANUAL_MODE;
else if(sw_led_mode) state = S_AUTO_MODE;
else if(!sw_led_mode) state = S_MANUAL_MODE;
end
// 각 상태에 대한 행동 및 다음 상태로 전이되기 위한 조건 정의
always @(negedge clk or posedge reset_p) begin
if(reset_p) begin
manual_duty = 0;
auto_duty = 0;
end
else begin
case(state)
// 수동 모드, LED 밝기 단계는 총 5단계로 나뉜다.
S_MANUAL_MODE : begin
if(btn_led_light) begin
if(manual_duty >= 5) manual_duty = 0;
else manual_duty = manual_duty + 1;
end
end
// 자동모드, 조도 값에 의해 LED 밝기가 결정된다.
S_AUTO_MODE : begin
auto_duty = 256 - sunlight_value;
// auto duty 값이 50이하이면 LED를 끈다.
if(auto_duty <= 50) auto_duty = 0;
end
endcase
end
end
// Instance of pwm control
pwm_cntr #(.pwm_freq(10_000), .duty_step(6)) control_manual_led_pwm (.clk(clk), .reset_p(reset_p), .duty(manual_duty), .pwm(manual_pwm));
pwm_cntr #(.pwm_freq(10_000), .duty_step(256)) control_auto_led_pwm (.clk(clk), .reset_p(reset_p), .duty(auto_duty), .pwm(auto_pwm));
// Select led pwm
// 현재 모드에 따라 최종 LED PWM을 선택하여 LED를 밝히게 된다.
assign led_pwm = (state == S_MANUAL_MODE) ? manual_pwm : auto_pwm;
endmodule
- LED Control Module은 FSM (Finite State Machine)으로 설계하였으며, 다음과 같은 상태 단계를 갖습니다.
▶ S_AUTO_MODE : LED 밝기 자동 모드, 조도 센서에 의해 LED 밝기를 256단계로 나누어 컨트롤
▶ S_MANUAL_MODE : LED 밝기 수동 모드, 버튼에 의해 LED 밝기를 5단계로 나누어 컨트롤 - 각 모드의 LED Duty 값에 의해 LED PWM 값을 얻게 되지만, 현재 모드 (state) 에 따라 최종적으로 외부 (Top module)로 전달되는 LED의 PWM이 결정되게 됩니다.
- LED 모드는 sw_led_mode 에 의해 결정되며, 수동 모드에서는 btn_led_light 버튼에 의해 LED 밝기를 조절할 수 있습니다.
< Source, led_height_control Module >
// LED Height Control module
module led_height_control(
input clk, reset_p,
input sw_led_height_mode,
input [21:0] distance_cm,
input sw_led_up, sw_led_down,
output [3:0] half_step_mode_sequence);
// Declare state machine
parameter S_MANUAL_MODE = 2'b01;
parameter S_AUTO_MODE = 2'b10;
// Declare state variable
reg [1:0] state;
// 언제 다음 state로 전이되는가?
always @(posedge clk or posedge reset_p) begin
if(reset_p) state = S_MANUAL_MODE;
else if(sw_led_height_mode) state = S_AUTO_MODE;
else if(!sw_led_height_mode) state = S_MANUAL_MODE;
end
// Declare necessary variables
// up_down : LED 을 올릴 것인지, 내릴 것인지에 대한 정보를 갖고 있는 레지스터
// motor_enable : Step motor를 동작 시킬 것인지에 대한 정보를 갖고 있는 레지스터
reg up_down, motor_enable;
// 각 상태 단계에서의 동작 및 다음 상태 전이 조건
always @(negedge clk or posedge reset_p) begin
if(reset_p) begin
up_down = 1;
motor_enable = 0;
end
else begin
case(state)
// 수동 모드 : sw_led_up, sw_led_down에 의해 LED 높이 조절
S_MANUAL_MODE :begin
if(sw_led_up) begin
up_down = 1;
if(sw_led_down) motor_enable = 0;
else motor_enable = 1;
end
else if(sw_led_down) begin
up_down = 0;
if(sw_led_up) motor_enable = 0;
else motor_enable = 1;
end
else motor_enable = 0;
end
// 자동 모드 : 초음파 센서에 의해 자동으로 LED 높이 조절
// 작물과 센서간에 거리가 7cm 미만이면 LED 을 올린다.
// 작물과 센서간에 거리가 15cm 초과이면 LED 를 내린다.
// 이를 통해 LED와 작물간에 거리를 7cm ~ 15cm 사이를 유지하게 된다.
S_AUTO_MODE : begin
if(distance_cm < 22'd7) begin
up_down = 1;
motor_enable = 1;
end
else if(distance_cm > 22'd15) begin
up_down = 0;
motor_enable = 1;
end
else motor_enable = 0;
end
endcase
end
end
// Instance of Step motor control module
step_motor_control step_motor_control_instance (.clk(clk), .reset_p(reset_p), .up_down(up_down), .motor_enable(motor_enable), .half_step_mode_sequence(half_step_mode_sequence));
endmodule
- LED Height Control Module은 FSM (Finite State Machine)으로 설계하였으며, 다음과 같은 상태 단계를 갖습니다.
▶ S_AUTO_MODE : LED 높이 자동 조절 모드, 초음파 센서에 의해 LED 높이를 자동적으로 조정합니다.
▶ S_MANUAL_MODE : LED 높이 수동 조절 모드, 스위치에 의해 LED 높이를 수동적으로 조정합니다. - 각 모드에서 조건에 따라 up_down, motor_enable 값을 결정하여 Step motor control module에게 전달하게 됩니다.
- LED 높이 조절 모드는 sw_led_height_mode 에 의해 결정되며, 수동 모드에서는 sw_led_up, sw_led_down 스위치에 의해 LED 높이를 수동으로 조절할 수 있습니다.
- 특히, sw_led_up, sw_led_down 스위치 모두가 ON인 상태인 경우에는 LED를 올려야 할 지, 내려야 할 지 불분명하기 때문에 해당 경우가 발생하면 motor_enable 값을 0으로 설정하여 Step-motor를 동작시키지 않도록 설계하였습니다.
< Source, electric fan control module >
// Electric Fan Control
module electric_fan_control (
input clk, reset_p,
input sw_cntr_electirc_fan_dir,
input sw_electric_fan_mode,
input btn_electric_fan_power,
input [15:0] dht11_value,
output fan_pwm, fan_dir_pwm );
// Declare state machine
parameter S_MANUAL_MODE = 2'b01;
parameter S_AUTO_MODE = 2'b10;
// Declare state, next state
reg [1:0] power_state;
// 언제 다음 상태로 넘어가는가?
// sw_electric_fan_mode 스위치에 의해 선풍기 파워 모드 변경 가능
always @(posedge clk or posedge reset_p) begin
if(reset_p) power_state = S_MANUAL_MODE;
else if(sw_electric_fan_mode) power_state = S_AUTO_MODE;
else if(!sw_electric_fan_mode) power_state = S_MANUAL_MODE;
end
// Register duty of electirc fan.
reg [1:0] fan_manual_duty, fan_auto_duty;
// Declare temperature, humidity
wire [7:0] temperature, humidity;
assign temperature = dht11_value [15:8];
assign humidity = dht11_value [7:0];
// 각 상태의 동작 및 다음 상태 단계로 전이되기 위한 조건 정의
always @(negedge clk or posedge reset_p) begin
if(reset_p) begin
fan_manual_duty = 0;
fan_auto_duty = 0;
end
else begin
case(power_state)
// 수동 모드, btn_electric_fan_power 버튼에 의해 선풍기 파워 수동 조절 가능
S_MANUAL_MODE : begin
if(btn_electric_fan_power) begin
if(fan_manual_duty >= 3) fan_manual_duty = 0;
else fan_manual_duty = fan_manual_duty + 1;
end
end
// 자동 모드, 온도 데이터에 의해 선풍기 파워 자동 조절
S_AUTO_MODE : begin
if(temperature < 27) fan_auto_duty = 0;
else if(27 <= temperature && temperature < 29) fan_auto_duty = 1;
else if(29 <= temperature && temperature <= 31) fan_auto_duty = 2;
else if(temperature > 31) fan_auto_duty = 3;
end
endcase
end
end
// Select duty of electric fan
// 현재 모드에 따라 fan_manual_duty와 fan_auto_duty 중 하나 선택된다.
wire [1:0] duty;
assign duty = (power_state == S_MANUAL_MODE) ? fan_manual_duty : fan_auto_duty;
// Get pwm of electric fan
pwm_cntr #(.pwm_freq(100), .duty_step(4)) control_power_pwm (.clk(clk), .reset_p(reset_p), .duty(duty), .pwm(fan_pwm));
// ********** 해당 기능은 하드웨어상으로 구현하지 못했지만, 소프트웨어로 구현하였습니다. **********
// Declare State Machine
parameter S_SERVO_STOP = 2'b01;
parameter S_SERVO_START = 2'b10;
// Declcare state variable
reg [1:0] dir_state;
// clock divider 1sec
wire clk_1usec, clk_1msec, clk_1sec;
clock_div_100 usec_clk( .clk(clk), .reset_p(reset_p), .clk_div_100(clk_1usec));
clock_div_1000 msec_clk(.clk(clk), .reset_p(reset_p), .clk_source(clk_1usec), .clk_div_1000_nedge(clk_1msec));
clock_div_1000 sec_clk(.clk(clk), .reset_p(reset_p), .clk_source(clk_1msec), .clk_div_1000_nedge(clk_1sec));
// Declare necessary variables
reg [4:0] dir_duty;
reg [4:0] dir_min_duty;
reg [4:0] dir_max_duty;
reg direction_of_fan; // direction_of_fan == 0 이면 감소, direction_of_fan == 1 이면 증가
// 언제 다음 state로 전이되는가?
// sw_cntr_electric_fan_dir 스위치에 의해 선풍기 방향 조절 가능
always @(posedge clk or posedge reset_p) begin
if(reset_p) dir_state = S_SERVO_STOP;
else if(sw_cntr_electirc_fan_dir) dir_state = S_SERVO_START;
else if(!sw_cntr_electirc_fan_dir) dir_state = S_SERVO_STOP;
end
// 각 상태 단계의 동작 및 다음 상태 전이 조건 정의
always @(negedge clk or posedge reset_p) begin
if(reset_p) begin
dir_min_duty = 5'd8;
dir_max_duty = 5'd22;
dir_duty = 5'd15;
direction_of_fan = 0;
end
else if(clk_1sec) begin
case(dir_state)
// 선풍기 회전 기능 중지된 경우
S_SERVO_STOP : begin
dir_duty = dir_duty;
end
// 선풍기 회전 기능 동작시킨 경우
S_SERVO_START : begin
if(direction_of_fan) begin
if(dir_duty == dir_max_duty) begin
dir_duty = dir_duty - 1;
direction_of_fan = ~ direction_of_fan;
end
else dir_duty = dir_duty + 1;
end
else begin
if(dir_duty == dir_min_duty) begin
dir_duty = dir_duty + 1;
direction_of_fan = ~ direction_of_fan;
end
else dir_duty = dir_duty - 1;
end
end
endcase
end
end
// Get pwm of servo-motor
pwm_cntr #(.pwm_freq(50), .duty_step(200)) control_servo_pwm (.clk(clk), .reset_p(reset_p), .duty(dir_duty), .pwm(fan_dir_pwm));
endmodule
- 위 모듈은 선풍기의 파워와 선풍기 회전에 관한 컨트롤 모듈입니다.
- 선풍기 회전 기능은 하드웨어 구조상 회전 기능이 포함되어 있지 않습니다.
- Electric Fan Power Module은 FSM (Finite State Machine)으로 설계하였으며, 다음과 같은 상태 단계를 갖습니다.
▶ S_AUTO_MODE : 선풍기의 자동 파워 조절 모드는 외부 온도가 설정된 기준에 따라 자동으로 파워를 조정합니다.
▶ S_MANUAL_MODE : 선풍기 파워 수동 조절 모드, 버튼을 통해 수동으로 선풍기 파워를 조정할 수 있습니다.
- 현재 상태에 따라 수동 모드의 Fan Duty 또는 자동 모드의 Fan Duty 중 하나가 선택되며, 선택된 값은 pwm_cntr 모듈을 통해 처리된 후 선풍기의 PWM 신호로 변환되어 외부 모듈로 출력됩니다.
- 선풍기 파워 모드는 sw_electric_fan_mode 스위치에 의해 결정되며, 수동 모드에서는 btn_electric_fan_power 버튼을 통해 선풍기 파워를 조절할 수 있습니다.
- 선풍기 PWM은 4단계 duty step를 가지며, fan_duty 값을 전달하면 100Hz 주파수를 갖는 PWM가 만들어지게 됩니다.
< Source, Window Control Module >
// Window control module
module window_control (
input clk, reset_p,
input [15:0] dht11_value,
input sw_window_open,
input sw_window_close,
input btn_window_control,
output left_window_pwm, right_window_pwm);
// Declare temperature, humidity
wire [7:0] temperature, humidity;
assign temperature = dht11_value [15:8];
assign humidity = dht11_value [7:0];
// Declare state machine.
parameter S_MANUAL_MODE = 2'b01;
parameter S_AUTO_MODE = 2'b10;
// Declare state, next state variables
reg [1:0] state, next_state;
// 언제 다음 상태 단계로 전이되는가?
// btn_window_control 버튼에 의해 모드 변경이 가능하다.
always @(posedge clk or posedge reset_p) begin
if(reset_p) state = S_MANUAL_MODE;
else if(btn_window_control) state = next_state;
end
// Declare duty of window
// right_min_duty == Window Open, right_max_duty == Window Close
// left_min_duty == Window Close, left_max_duty == Window Open
reg [5:0] left_duty, right_duty;
reg [5:0] right_min_duty, right_max_duty;
reg [5:0] left_min_duty, left_max_duty;
// 1msec Clock Pulse
wire clk_usec, clk_msec;
clock_div_100 usec_clk(.clk(clk), .reset_p(reset_p), .clk_div_100(clk_usec));
clock_div_1000 msec_clk(.clk(clk), .reset_p(reset_p),
.clk_source(clk_usec), .clk_div_1000(clk_msec));
// 각 상태 단계의 동작 및 다음 상태 전이 조건 정의
always @(negedge clk or posedge reset_p) begin
if(reset_p) begin
next_state = S_MANUAL_MODE;
right_duty = right_max_duty;
left_duty = left_min_duty;
right_max_duty = 6'd18;
right_min_duty = 6'd5;
left_max_duty = 6'd25;
left_min_duty = 6'd11;
end
else if(clk_msec) begin
case (state)
// 1단계) 수동 조작 단계
S_MANUAL_MODE: begin
if(sw_window_open) begin
if(right_duty > right_min_duty) right_duty = right_duty - 1;
if(left_duty < left_max_duty) left_duty = left_duty + 1;
end
else if(sw_window_close) begin
if(right_duty < right_max_duty) right_duty = right_duty + 1;
if(left_duty > left_min_duty) left_duty = left_duty - 1;
end
next_state = S_AUTO_MODE;
end
// 2단계) 자동 조작 단계
// 27도 이상일 경우, 창문을 닫힌다.
S_AUTO_MODE : begin
if(temperature < 8'd27) begin
right_duty = right_max_duty;
left_duty = left_min_duty;
end
else begin
right_duty = right_min_duty;
left_duty = left_max_duty;
end
next_state = S_MANUAL_MODE;
end
endcase
end
end
// Instance of pwm_control module
pwm_Nstep_freq #(
.duty_step(200),
.pwm_freq(50))
pwm_right_servo_motor (.clk(clk), .reset_p(reset_p), .duty(right_duty), .pwm(right_window_pwm));
pwm_Nstep_freq #(
.duty_step(200),
.pwm_freq(50))
pwm_left_servo_motor (.clk(clk), .reset_p(reset_p), .duty(left_duty), .pwm(left_window_pwm));
endmodule
- 위 모듈은 재배기 창문을 컨트롤 하기 위해 설치된 Servo-motor를 제어하는 모듈입니다.
- Window Control Module은 FSM (Finite State Machine)으로 설계하였으며, 다음과 같은 상태 단계를 갖습니다.
▶ S_AUTO_MODE : 재배기 창문 자동 제어 모드, 외부 온도를 통해 자동적으로 창문을 제어하게 됩니다.
▶ S_MANUAL_MODE : 재배기 창문 수동 제어 모드, 두 개의 스위치에 의해 창문을 수동으로 제어할 수 있습니다.
- 현재 모드에 따라 왼쪽, 오른쪽 서보 모터의 duty 값을 결정한 뒤, pwm_Nstep_freq 모듈을 통해 해당 해당 duty 값을 갖는 50Hz PWM을 생성하게 됩니다.
(pwm_Nstep_freq 모듈은 duty step = 200단계로 나뉘어 있으며, 주파수가 50Hz인 PWM을 생성하는 모듈입니다.) - 창문 컨트롤 모드는 btn_window_control 버튼에 의해 결정되며, 수동 모드에서는 sw_window_open, sw_window_close 스위치에 컨트롤할 수 있습니다.
- 자동 모드에서 외부 온도가 27도 초과이면 창문을 닫히며, 27도 이하이면 창문히 닫히도록 설게했습니다.
< Source, Water Pump Control Modue >
// Water Pump Control module
module water_pump (
input clk, reset_p,
input water_flag,
output pump_on_off );
assign pump_on_off = water_flag;
endmodule
- 수위 센서로부터 얻은 water_flag 플래그 변수 값에 따라 워터 펌프 동작 여부가 결정됩니다.
- 수위 센서 컨트롤 모듈에서 "재배기 수위가 낮으니, 워터 펌프 동작시켜줘!" 라고 플래그를 활성화시키면 워터 펌프 컨트롤 모듈에서 해당 플래그가 활성화되는 동안 워터 펌프를 동작하게 됩니다.
< Source, LCD Display Control Module >
module lcd_display_control (
input clk, reset_p,
input [15:0] dht11_value,
input [7:0] sunlight_value,
input water_flag,
output scl, sda);
// Get temperature / humidity from DHT11 Control Module
wire [7:0] temperature, humidity;
wire [15:0] temperature_bcd, humidity_bcd;
assign temperature = dht11_value[15:8];
assign humidity = dht11_value[7:0];
// Convert from binary to BCD Code
bin_to_dec bcd_temp(.bin({4'b0, temperature}), .bcd(temperature_bcd));
bin_to_dec bcd_humi(.bin({4'b0, humidity}), .bcd(humidity_bcd));
// Declare state machine
parameter IDLE = 9'b0_0000_0001; // LCD 디스플레이 대기 상태 단계
parameter INIT = 9'b0_0000_0010; // LCD 초기화 과정
parameter SEND_STRING_TEMPERATURE = 9'b0_0000_0100; // "Temperature" 문자열 출력
parameter SEND_TEMPERATURE_DATA = 9'b0_0000_1000; // 온도 데이터 출력
parameter SEND_COMMAND_NEXT_LINE = 9'b0_0001_0000; // 다음 줄로 전환 명령어 전송
parameter SEND_STRING_HUMIDITY = 9'b0_0010_0000; // "Humidity" 문자열 출력
parameter SEND_HUMIDITY_DATA = 9'b0_0100_0000; // 습도 데이터 출력
parameter WAIT_1SEC = 9'b0_1000_0000; // 모든 데이터를 출력한 후, 1초대기
parameter SEND_COMMAND = 9'b1_0000_0000; // 화면 클리어 명령어 전송
// Get usecond clock
wire clk_usec;
clock_div_100 usec_clk (.clk(clk), .reset_p(reset_p), .clk_div_100_nedge(clk_usec));
// 마이크로세컨드 단위로 카운트
// enable 이 1이면 카운트 동작 , 1이 아니면 0으로 카운트 초기화
reg [21:0] count_usec;
reg count_usec_en;
always @(negedge clk or posedge reset_p)begin
if(reset_p) count_usec = 0;
else if(clk_usec && count_usec_en) count_usec = count_usec + 1;
else if(!count_usec_en) count_usec = 0;
end
// Declare register of text
reg [7:0] send_buffer;
// Declare varables
reg rs, send;
wire busy;
// instance of i2c lcd send byte
i2c_lcd_send_byte i2c_lcd_send_byte_0 (.clk(clk), .reset_p(reset_p),
.addr(7'h27), .send_buffer(send_buffer), .rs(rs), .send(send),
.scl(scl), .sda(sda), .busy(busy));
// Declare state, next state
reg [10:0] state, next_state;
// 언제 다음 상태로 넘어가는가?
always @(negedge clk or posedge reset_p) begin
if(reset_p) state = IDLE;
else state = next_state;
end
// 초기화 했는지 여부를 나타내는 FLAG Register
reg init_flag, flag_reset;
// Counting for data
reg [9:0] cnt_data ;
// 문자열 Register
reg [14*8-1:0] str_temperature;
reg [11*8-1:0] str_humidity;
reg [9:0] cnt_string;
// 각 상태에 대한 동작 정의
always @(posedge clk or posedge reset_p) begin
if(reset_p) begin
next_state = IDLE;
init_flag = 0;
count_usec_en = 0;
cnt_data = 0;
rs = 0;
str_temperature = "Temperature : "; // C언어처럼 마지막에 NULL은 없다.
str_humidity = "Humidity : ";
cnt_string = 0;
flag_reset = 0;
end
else begin
case (state)
// 1단계) IDLE
IDLE : begin
if(init_flag) begin
if(count_usec < 22'd1000) begin
count_usec_en = 1;
end
else begin
count_usec_en = 0;
next_state = SEND_STRING_TEMPERATURE;
end
end
else begin
if(count_usec <= 22'd80_000) begin
count_usec_en = 1;
end
else begin
count_usec_en = 0;
next_state = INIT;
end
end
end
// 2단계) INIT
INIT : begin
if(busy) begin
send = 0;
if(cnt_data >=6) begin
init_flag = 1;
cnt_data = 0;
next_state = IDLE;
end
end
else if(!send) begin // send == 0 && busy == 0일 때만 동작
// busy는 다음 posedge 일 때 0-> 1로 변화하기 때문에
// 8'h33을 보내고, 바로 8'h82로 보내서 위와 같은 조건을 추가
case(cnt_data)
0: send_buffer = 8'h33;
1: send_buffer = 8'h32;
2: send_buffer = 8'h2a; // 2줄, 5X8 Dots 사용
3: send_buffer = 8'h0c; // Display : 1, Cursor : on + 깜빡임
4: send_buffer = 8'h01;
5: send_buffer = 8'h06;
endcase
rs = 0;
cnt_data = cnt_data + 1;
send = 1;
end
end
// 3단계) SEND_STRING_TEMPERATURE
SEND_STRING_TEMPERATURE : begin
if(busy) begin
send = 0;
if(cnt_string >= 14) begin
cnt_string = 0;
next_state = SEND_TEMPERATURE_DATA;
end
end
else if(!send) begin // send == 0 && busy == 0일 때만 동작
// busy는 다음 posedge 일 때 0-> 1로 변화하기 때문에
// 8'h33을 보내고, 바로 8'h82로 보내서 위와 같은 조건을 추가
case(cnt_string)
0: send_buffer = str_temperature[111 : 104];
1: send_buffer = str_temperature[103 : 96];
2: send_buffer = str_temperature[95 : 88];
3: send_buffer = str_temperature[87 : 80];
4: send_buffer = str_temperature[79 : 72];
5: send_buffer = str_temperature[71 : 64];
6: send_buffer = str_temperature[63 : 56];
7: send_buffer = str_temperature[55 : 48];
8: send_buffer = str_temperature[47 : 40];
9: send_buffer = str_temperature[39 : 32];
10: send_buffer = str_temperature[31 : 24];
11: send_buffer = str_temperature[23 : 16];
12: send_buffer = str_temperature[15 : 8];
13: send_buffer = str_temperature[7 : 0];
endcase
rs = 1;
cnt_string = cnt_string + 1;
send = 1;
end
end
// 4단계) SEND_TEMPERATURE_DATA
SEND_TEMPERATURE_DATA : begin
if(busy) begin
send = 0;
if(cnt_data >= 2) begin
cnt_data = 0;
next_state = SEND_COMMAND_NEXT_LINE;
end
end
else if(!send) begin
case(cnt_data)
0 : send_buffer = "0" + temperature_bcd[7:4];
1 : send_buffer = "0" + temperature_bcd[3:0];
endcase
cnt_data = cnt_data + 1;
rs = 1;
send = 1;
end
end
// 5단계) SEND_COMMAND_NEXT_LINE
SEND_COMMAND_NEXT_LINE : begin
if(busy) begin
if(flag_reset) begin
flag_reset = 0;
next_state = SEND_STRING_HUMIDITY;
end
send = 0;
end
else begin
send_buffer = 8'hc0;
rs = 0;
send = 1;
flag_reset = 1;
end
end
// 6단계) SEND_STRING_HUMIDITY
SEND_STRING_HUMIDITY : begin
if(busy) begin
send = 0;
if(cnt_string >= 11) begin
cnt_string = 0;
next_state = SEND_HUMIDITY_DATA;
end
end
else if(!send) begin // send == 0 && busy == 0일 때만 동작
// busy는 다음 posedge 일 때 0-> 1로 변화하기 때문에
// 8'h33을 보내고, 바로 8'h82로 보내서 위와 같은 조건을 추가
case(cnt_string)
0: send_buffer = str_humidity[87 : 80];
1: send_buffer = str_humidity[79 : 72];
2: send_buffer = str_humidity[71 : 64];
3: send_buffer = str_humidity[63 : 56];
4: send_buffer = str_humidity[55 : 48];
5: send_buffer = str_humidity[47 : 40];
6: send_buffer = str_humidity[39 : 32];
7: send_buffer = str_humidity[31 : 24];
8: send_buffer = str_humidity[23 : 16];
9: send_buffer = str_humidity[15 : 8];
10: send_buffer = str_humidity[7 : 0];
endcase
rs = 1;
cnt_string = cnt_string + 1;
send = 1;
end
end
// 7단계) SEND_HUMIDITY_DATA
SEND_HUMIDITY_DATA : begin
if(busy) begin
send = 0;
if(cnt_data >= 2) begin
cnt_data = 0;
next_state = WAIT_1SEC;
end
end
else if(!send) begin
case(cnt_data)
0 : send_buffer = "0" + humidity_bcd[7:4];
1 : send_buffer = "0" + humidity_bcd[3:0];
endcase
cnt_data = cnt_data + 1;
rs = 1;
send = 1;
end
end
// 8단계) WAIT_1SEC
WAIT_1SEC : begin
if(count_usec < 22'd1_000_000) begin
count_usec_en = 1;
end
else begin
count_usec_en = 0;
next_state = SEND_COMMAND;
end
end
// 9단계) SEND_COMMAND
SEND_COMMAND : begin
if(busy) begin
send = 0;
end
else if(!send) begin
if(flag_reset) begin
flag_reset = 0;
next_state = IDLE;
end
else begin
send_buffer = 8'h01;
rs = 0;
send = 1;
flag_reset = 1;
end
end
end
endcase
end
end
endmodule
- 이번 하드웨어 구현에서 HD44780U LCD 디스프레이 모듈을 사용했습니다.
- LCD 디스플레이에서 실시간은 DHT11로부터 온도, 습도 데이터를 전달받아 1초마다 출력하도록 설계하였습니다.
- LCD 디스플레이 컨트롤 모듈은 FSM (Finite State Machine) 방식으로 설계하였으며, 각각의 상태 단계는 다음과 같은 작업들을 수행하게 됩니다.
▶ S_IDLE 단계 : DHT11로부터 데이터를 받아 출력하기 위해 잠시 대기 상태
▶ S_INIT 단계 : LCD 디스플레이 모듈을 사용하기 전, 디스플레이 모듈 초기화
▶ S_SEND_STRING_TEMPERATURE 단계 : LCD 모듈에 "Temperature" 문자열을 전송하여 출력하는 단계
▶ S_TEMPERATURE_DATA 단계 : LCD 모듈에 온도 데이터를 전송하여 출력하는 단계
▶ S_COMMAND_NEXT_LINE 단계 : 다음 줄로 넘어가기 위해 next line 명령어를 전송하는 단계
▶ S_SNED_HUMIDITY 단계 : LCD 모듈에 "Humidity" 문자열을 전송하여 출력하는 단계
▶ S_HUMIDITY_DATA 단계 : LCD 모듈에 습도 데이터를 전송하여 출력하는 단계
▶ S_WAIT_1SEC 단계 : 모든 데이터를 전송한 뒤, LCD 화면에 해당 데이터를 그대로 출력하기 위해 1초 대기
▶ S_COMMAND_CLEAR 단계 : 새로운 데이터를 다시 쓰기 위해 LCD 디스플레이를 클리어하는 단계 - LCD 디스플레이 컨트롤 모듈에 대한 자세한 설명은 아래 게시글을 참고해주세요.
https://jbhdeve.tistory.com/319
Verilog RTL 설계(8월 22일 - 3, I2C 통신을 통한 LCD 컨트롤 - (3))
1. 버튼을 누를 때마다 'A' 문자를 LCD 디스플레이 출력하기이번 게시글에서는 이전 게시글에서 설계한 i2c_master 모듈과 i2c_lcd_send_byte 모듈을 이용하여 I2C 통신을 통해 LCD 모듈에 'A' 문자 데이터를
jbhdeve.tistory.com
< Source, UART-App Control Module >
- 해당 모듈은 소스 코드가 상당히 길기 때문에 본 게시글에 첨부하지 못했습니다.
- 아래 Github QR 코드에 들어가셔 해당 모듈의 소스 코드를 확인해주세요.
3. 시연 영상
4. 해당 프로젝트의 발표 자료
5. Github QR 코드
- 해당 프로젝트의 소스 코드는 아래 QR 코드를 통해 확인하실 수 있습니다.