Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- java
- ring counter
- FND
- pwm
- atmega 128a
- LED
- Pspice
- uart 통신
- gpio
- soc 설계
- Algorithm
- Recursion
- stop watch
- BASYS3
- i2c 통신
- Edge Detector
- KEYPAD
- verilog
- Linked List
- hc-sr04
- DHT11
- vivado
- test bench
- D Flip Flop
- structural modeling
- half adder
- prescaling
- dataflow modeling
- behavioral modeling
- ATMEGA128A
Archives
- Today
- Total
거북이처럼 천천히
Verilog RTL 설계(8월 22일 - 3, I2C 통신을 통한 LCD 컨트롤 - (3)) 본문
RTL Design/Verilog RTL 설계
Verilog RTL 설계(8월 22일 - 3, I2C 통신을 통한 LCD 컨트롤 - (3))
유로 청년 2024. 9. 6. 13:341. 버튼을 누를 때마다 'A' 문자를 LCD 디스플레이 출력하기
- 이번 게시글에서는 이전 게시글에서 설계한 i2c_master 모듈과 i2c_lcd_send_byte 모듈을 이용하여 I2C 통신을 통해 LCD 모듈에 'A' 문자 데이터를 보내어 LCD 화면에 버튼을 누를 때마다 'A' 문자를 출력하도록 하겠다.
- i2c_master 모듈과 i2c_lcd_send_byte 모듈에 대해서 궁금하다면 아래 게시글을 참고하길 바란다.
https://jbhdeve.tistory.com/315
https://jbhdeve.tistory.com/318
2. 버튼을 누를 때마다 'A' 문자를 LCD 디스플레이 모듈에게 전송하는 모듈 설계
- LCD 디스플레이 모듈을 사용하기 전에는 LCD 디스플레이 모듈의 초기화 작업이 필요하다.
- LCD 디스플레이 초기화 작업은 이전 작업 환경을 제거한 뒤, 원하는 LCD 환경으로 세팅하는 작업이다. 이를 통해 LCD 화면에 원하는 형태로 문자 및 문자열 출력되도록 설정할 수 있다.
- 이번 구현에서는 I2C 통신을 통해 LCD 디스플레이와 Basys3간에 통신할 것이기 때문에 4bit 모드를 사용할 것이며, 4bit 모드 기준으로 디스플레이 초기화 작업을 수행할 것이다.
- 위 그림은 HD44780U LCD 디스플레이를 4bit 모드를 기준으로 초기화 하는 방법이다.
- 초기화 작업에서 사용자가 원하는 환경으로 HD44780U LCD 디스플레이을 설정할 수 있으며, 원하는 환경으로 설정하기 위해서는 Command Register에 위 과정에 따라 제어 값을 전달하면 된다.
- 자세한 LCD 디스플레이 초기 설정값은 HD44780U LCD의 데이터 시트를 참고하길 바란다.
- 모듈 설계에 대한 설명은 소스 코드와 함께 설명하도록 하겠다.
< Source, Top module의 Input / Output 변수 선언 >
// Top module of LCD Display
module LCD_char_top_module(
input clk, reset_p,
input btn,
output scl, sda );
endmodule
- 버튼을 누를 때마다 LCD 디스플레이 화면에 'A' 문자를 출력하기 위해 btn 이라는 Input 선언하였다.
< Source, Chattering 문제를 Delay를 통해 해결하기 위해 button_cntr 모듈 이용 >
// ******** Button Control module ********
wire btn_a;
button_cntr button_a (.clk(clk), .reset_p(reset_p), .btn(btn), .p_edge(btn_a));
- button의 Chattering 문제를 해결하기 위해 button_cntr 모듈을 이용하여 1ms delay time을 갖고, button의 입력값을 받는다.
< Source, State machine parameter 선언 >
// ******** Declare state machine ********
parameter S_IDLE = 3'b001;
parameter S_INIT = 3'b010;
parameter S_SEND_DATA = 3'b100;
- state machine paramter는 위와 같이 3단계로 나누어 정의하였다,
- 각각의 상태 단계에서는 다음과 같은 동작을 수행하게 된다,
▶ S_IDLE : Button 입력이 들어오기 전까지 잠시 대기한 상태
▶ S_INIT : LCD 디스플레이 모듈을 사용하기 전, LCD 디스플레이 모듈을 사용자가 원하는 설정값으로 초기화한다.
▶ S_SEND_DATA : Button 입력이 들어오면 LCD 디스플레이 모듈에 문자 'A' 를 출력하는 단계
< Source, state, next_state 변수 선언 및 언제 다음 상태로 전이되는지 정의 >
// ******** Declare state, next_state ********
reg [2:0] state, next_state;
// ******** 언제 다음 상태로 전이되는가? ********
always @(posedge clk or posedge reset_p) begin
if (reset_p)
state = S_IDLE;
else
state = next_state;
end
< Source, usecond counter 생성 >
// ******** Making usecond counter ********
reg [21:0] counter_usec;
reg counter_enable;
always @(posedge clk or posedge reset_p) begin
if (reset_p || !counter_enable)
counter_usec = 0;
else if (clk_usec && counter_enable)
counter_usec = counter_usec + 1;
end
< Source, 필요한 레지스터 및 와이어 선언 >
// ******** Declare necessary variables ********
reg [7:0] send_buffer;
reg rs, send;
reg flag_init;
reg [2:0] cnt_data;
wire busy;
- 각각의 레지스터 및 와이어는 다음과 역활 및 의미를 갖는다.
▶ send_buffer : I2C 통신을 통해 LCD 디스플레이 모듈에 전달할 데이터를 임시 저장하는 버퍼
▶ rs : Register Select, Read / Write할 Register를 선택하는 변수
▶ send : 8574칩에서 LCD 디스플레이 모듈로 8bit 데이터를 전달하는 플래그
▶ flag_init : LCD 디스플레이 초기화 했는지 여부를 나타내는 플래그
▶ cnt_data : 현재 몇 번째 데이터를 보내고 있는지 여부를 카운트하는 레지스터
▶ busy : 현재 Basys3에서 8574칩으로 데이터를 보내고 있는지 여부를 확인할 수 있는 플래그
< Source, 각 상태 단계의 동작 및 다음 상태 전이 조건을 정의, 초기화 작업 >
// ******** 각 상태 단계의 동작 정의 및 다음 상태 단계 전이 조건 ********
always @(negedge clk or posedge reset_p) begin
if(reset_p) begin
rs = 0;
next_state = S_IDLE;
flag_init = 0;
counter_enable = 0;
cnt_data = 0;
end
else begin
''' 각 상태 단계의 동작 및 다음 상태 전이 조건 정의 '''
end
end
< Source, 1단계) S_IDLE 단계 >
// 1단계) S_IDLE 단계
S_IDLE: begin
if (flag_init) begin
if (btn_a)
next_state = S_SEND_DATA;
end
else begin
if (counter_usec < 22'd80_000)
counter_enable = 1;
else begin
counter_enable = 0;
next_state = S_INIT;
end
end
end
- flag_init 레지스터는 LCD 디스플레이 초기화 작업을 했는지 여부를 나타내는 플래그이다.
- flag_init == 0 일 경우, 아직 LCD 디스플레이 초기화 작업을 수행하지 않았다고 판단하여 next_state = S_INIT로 다음 상태 단계로 설정한다.
- 반대로 flag_init == 1 일 경우, 아직 LCD 디스플레이 초기화 작업을 수행 했다고 판단하여 버튼이 눌렀음을 확인 될때 마다 next_state = S_SEND_DATA로 다음 상태 단계로 설정하여 전이된다.
★★★★★★★★★★★★★★★★★★★★ - Q) 뭔가 이상하다. flag_init == 0 일 경우, counter_usec 를 이용하여 80ms 를 대기 한뒤, S_INIT 상태로 전이되는가?
- A) 이는 LCD 디스플레이 모듈인 HD44780U의 Data Sheet를 통해 알 수 있다.
- LCD 디스플레이 데이터 시트에 의하면 LCD 모듈의 전원 안정화를 위해 15ms 이상을 기다릴 필요가 있음을 확인할 수 있다. 이는 LCD 모듈의 내부 회로가 정상적으로 작동하려면 안정된 전원 공급이 필요하기 때문이다.
- 하지만, LCD 디스플레이 모듈에 공급되는 전원의 전압을 모르기 때문에 충분히 여유롭게 대기 시간을 갖기 위해서 80ms 대기 시간을 갖도록 설계하였다.
< Source, 2단계) S_INIT 단계 >
// 2단계) S_INIT 단계
S_INIT : begin
if (busy) begin
send = 0;
if (cnt_data >= 6) begin
cnt_data = 0;
flag_init = 1;
next_state = S_IDLE;
end
end
else if (!send) begin
case (cnt_data)
0 : send_buffer = 8'h33;
1 : send_buffer = 8'h32;
2 : send_buffer = 8'h28;
3 : send_buffer = 8'h0f;
4 : send_buffer = 8'h01;
5 : send_buffer = 8'h06;
endcase
send = 1;
cnt_data = cnt_data + 1;
rs = 0;
end
end
- LCD 디스플레이를 초기화하기 위해서는 위와 같이 8bit 데이터, 6개를 LCD 디스플레이 모듈에게 전달해야 한다.
- 6개의 8비트 데이터는 사용자가 원하는 LCD 환경을 설정하기 위한 초기화 명령어들입니다. 이 명령어들은 LCD 모듈에 전달될 때, 반드시 명령어 레지스터(Command Register)로 전송되어야 합니다.
- 하지만, 6개의 명령어 데이터를 연속적으로 보내면 안되며, 반드시 현재 Basys3와 8574칩간에 통신을 하고 있는지 여부를 확인하고, 통신을 하지 않고 있음을 확인하면 명령어 데이터를 순차적으로 보내야 한다.
- 따라서 현재 Basys3와 8574칩간에 통신을 하고 있는지 여부를 확인하기 위해 Busy, send 플래그를 통해 확인한다.
▶ Busy : 현재 Basys3와 8574칩간에 송수신 과정에 있는지 여부를 나타내는 플래그
▶ send : Basys3 측에서 8574칩으로 데이터를 보낸 게 있는지 여부를 나타내는 플래그 - cnt_data 레지스터를 통해 6개의 명령어를 모드 보냈는지 여부를 확인하고, 6개의 명령어를 모두 송신하였으면 flag_init 레지스터 값을 1로 변환한 뒤, S_IDLE 상태 단계로 전이한다.
★★★★★★★★★★★★★★★★★★★★ - Q) 데이터 시트와 비교하였을 때, 4.1ms 대기 시간을 가지라고 적혀있는데, 왜 4.1ms 대기시간을 갖지 않는가?
- A) 데이터 시트를 확인하면 4.1ms 이상 대기 시간을 가질 것을 권장하지만, 실제로 구현해본 결과, 4.1ms 이상의 대기 시간 없이 바로 명령어 데이터를 전송해도 문제 없이 동작함을 확인하였다. 따라서 4.1ms 이상의 대기 시간은 생략하고, 각각의 명령어를 주었다.
< Source, 3단계) S_SEND_DATA 단계 >
// 3단계) S_SEND_DATA 단계
S_SEND_DATA : begin
if (busy) begin
next_state = S_IDLE;
send = 0;
end
else begin
send_buffer = "A";
send = 1;
rs = 1;
end
end
- send_buffer 레지스터에 LCD 디스플레이 화면에 출력할 데이터를 대입한다.
- rs (Register Select) = 1로 설정하여 Data register에 송신할 수도록 만들어 준다.
- send = 1로 대입하여 데이터를 전송한 뒤, busy 인 상태임을 확인하고, S_IDLE를 다음 상태 단계로 설정하여 전이한다.
< Source, i2c_lcd_send_byte 모듈의 인스턴스 선언 >
// ******** Instance of send byte data. ********
i2c_lcd_send_byte i2c_lcd_send_byte_instance (.clk(clk), .reset_p(reset_p), .addr(7'h27),
.send_buffer(send_buffer), .rs(rs), .send(send), .scl(scl), .sda(sda), .busy(busy));
3. 시연 영상
4. 전체 소스 코드
module LCD_char_top_module(
input clk, reset_p,
input btn,
output scl, sda );
// ******** Button Control module ********
wire btn_a;
button_cntr button_a (.clk(clk), .reset_p(reset_p), .btn(btn), .p_edge(btn_a));
// ******** Declare state machine ********
parameter S_IDLE = 3'b001;
parameter S_INIT = 3'b010;
parameter S_SEND_DATA = 3'b100;
// ******** Declare state, next-state ********
reg [2:0] state, next_state;
// ******** 언제 다음 상태로 전이되는가? ********
always @(posedge clk or posedge reset_p) begin
if(reset_p) state = S_IDLE;
else state = next_state;
end
// ******** 1 uescond one cycle pulse ********
wire clk_usec;
clk_div_100 clk_div_usec (.clk(clk), .reset_p(reset_p), .clk_div_100_nedge(clk_usec));
// ******** Making usecond counter ********
reg[21:0] counter_usec;
reg counter_enable;
always @(posedge clk or posedge reset_p) begin
if(reset_p || !counter_enable) counter_usec = 0;
else if(clk_usec && counter_enable) counter_usec = counter_usec + 1;
end
// ********Declare necessary variables. ********
reg [7:0] send_buffer;
reg rs, send;
reg flag_init;
reg [2:0] cnt_data;
wire busy;
// ******** 각 상태 단계의 동작 정의 및 다음 상태 단계 전이 조건 ********
always @(negedge clk or posedge reset_p) begin
if(reset_p) begin
rs = 0;
next_state = S_IDLE;
flag_init = 0;
counter_enable = 0;
cnt_data = 0;
end
else begin
case(state)
// 1단계) S_IDLE 단계
S_IDLE : begin
if(flag_init) begin
if(btn_a) next_state = S_SEND_DATA;
end
else begin
if(counter_usec < 22'd80_000) begin
counter_enable = 1;
end
else begin
counter_enable = 0;
next_state = S_INIT;
end
end
end
// 2단계) S_INIT 단계
S_INIT : begin
if(busy) begin
send = 0;
if(cnt_data >= 6) begin
cnt_data = 0;
flag_init = 1;
next_state = S_IDLE;
end
end
else if(!send)begin
case(cnt_data)
0 : send_buffer = 8'h33;
1 : send_buffer = 8'h32;
2 : send_buffer = 8'h28;
3 : send_buffer = 8'h0f;
4 : send_buffer = 8'h01;
5 : send_buffer = 8'h06;
endcase
send = 1;
cnt_data = cnt_data + 1;
rs = 0;
end
end
// 3단계) S_SEND_DATA 단계
S_SEND_DATA : begin
if(busy) begin
next_state = S_IDLE;
send = 0;
end
else begin
send_buffer = "A";
send = 1;
rs = 1;
end
end
endcase
end
end
// ******** Instance of send byte data. ********
i2c_lcd_send_byte i2c_lcd_send_byte_instance (.clk(clk), .reset_p(reset_p), .addr(7'h27),
.send_buffer(send_buffer), .rs(rs), .send(send), .scl(scl), .sda(sda), .busy(busy));
endmodule
// Button Control
module button_cntr (
input clk, reset_p,
input btn,
output p_edge, n_edge );
// Get 1msec One Cycle Pulse
wire clk_usec, clk_msec;
clk_div_100 clk_div_usec (.clk(clk), .reset_p(reset_p), .clk_div_100_nedge(clk_usec));
clk_div_1000 clk_div_msec (.clk(clk), .reset_p(reset_p), .clk_source(clk_usec), .clk_div_1000_nedge(clk_msec));
reg temp_button;
always @(posedge clk or posedge reset_p) begin
if(reset_p) temp_button = 0;
else if(clk_msec) temp_button = btn;
end
// Get edge of temp button
edge_detector edge_detector_temp_btn (.clk(clk), .reset_p(reset_p), .cp(temp_button), .p_edge(p_edge), .n_edge(n_edge));
endmodule
// Clock divider 1000
module clk_div_1000 (
input clk, reset_p,
input clk_source,
output clk_div_1000,
output clk_div_1000_nedge, clk_div_1000_pedge);
wire clk_source_nedge;
edge_detector edge_detector_source (.clk(clk), .reset_p(reset_p), .cp(clk_source), .n_edge(clk_source_nedge));
reg [9:0] counter;
always @(posedge clk or posedge reset_p) begin
if(reset_p) counter = 0;
else if(clk_source_nedge) begin
if(counter >= 999) counter = 0;
else counter = counter + 1;
end
end
assign clk_div_1000 = (counter < 500)? 0 : 1;
edge_detector edge_detector_1 (.clk(clk), .reset_p(reset_p), .cp(clk_div_1000), .n_edge(clk_div_1000_nedge), .p_edge(clk_div_1000_pedge));
endmodule
'RTL Design > Verilog RTL 설계' 카테고리의 다른 글
Verilog RTL 설계(8월 22일 - 2, I2C 통신을 통한 LCD 컨트롤 - (2)) (0) | 2024.09.05 |
---|---|
Verilog RTL 설계(8월 22일 - 1, I2C 통신을 통한 LCD 컨트롤 - (1)) (0) | 2024.08.25 |
Verilog RTL 설계(8월 12일 - 3, ADC Converter - 3) (4) | 2024.08.24 |
Verilog RTL 설계(8월 21일 - 2, I2C 통신 - 2) (0) | 2024.08.23 |
Verilog RTL 설계(8월 21일 - 1, I2C 통신 - 1) (0) | 2024.08.22 |