1、模块来源
采购链接: 0.91 寸 OLED 显示屏 12832 液晶屏显串口屏 iic 接口屏 i2cssd1306
资料下载链接: https://pan.baidu.com/s/1fKmD5lr4bA0WB54ukonI-A 资料提取码:1111
实物图展示
| 项目 | 参数 |
|---|---|
| 驱动芯片 | SSD1306 |
| 接口 | IIC(I2C),4 根线:VCC / GND / SCL / SDA |
| 颜色 | 单色 |
| 分辨率 | 128 × 32 |
| 工作电压 | 3~5 V |
关键参数
2、实验原理
本实验我们用 IIC 通信让逻辑派驱动我们的屏幕,接下来介绍 IIC 原理和 0.91 寸 OLED 屏幕原理
具体的原理,IIC时序,OLED 刷新原理可以移步0.96寸IIC那一章,原理是通用的,不过屏幕参数不同
3、程序编写
我们下面给出这个例程的程序实现,整个 OLED 显示驱动分成了以下模块:
oled_demo:顶层模块,例化oled_top,对外只留时钟、复位和 I2C 的两根线。oled_top:OLED 顶层控制模块,用状态机把「初始化 → 清屏 → 显示字符」三步串起来。oled_init:OLED 初始化模块,上电后把 SSD1306 的 25 条初始化命令依次发出去。oled_clear:清屏模块,把整块屏幕刷成黑色。oled_text:字符显示模块,控制 "LJPI-G1" 七个字符显示的位置。font_rom:字库模块,存 "LJPI-G1" 七个字符的 8×16 点阵。oled_mux:数据选择模块,把初始化 / 清屏 / 字符显示三路数据选一路交给 I2C 发送。i2c_master:I2C 主机驱动,负责按 I2C 协议把命令和数据发给 OLED 屏幕。
verilog
//顶层:只驱动 OLED 显示 "LJPI-G1"
module oled_demo(
input sys_clk,
input rst_n,
output oled_scl,
inout oled_sda
);
oled_top oled_top_inst(
.sys_clk (sys_clk),
.rst_n (rst_n),
//OLED I2C
.oled_scl (oled_scl),
.oled_sda (oled_sda)
);
endmodule1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
verilog
//OLED顶层模块
module oled_top(
input sys_clk,
input rst_n,
//OLED IIC
output oled_scl,
inout oled_sda
);
localparam ST_INIT = 'd0; //初始化
localparam ST_CLEAR = 'd1; //刷新,将oled全部写0
localparam ST_TEXT = 'd2; //显示字符
localparam ST_IDLE = 'd3; //空闲(显示完就保持)
reg[4:0] cur_state , nxt_state;
//IIC相关信号
wire wr_req;
wire[23:0] wr_data;
wire wr_done;
//初始化相关信号
wire init_done;
wire[23:0] init_data;
wire init_start;
//refresh相关信号
wire clear_done;
wire[23:0] clear_data;
wire clear_start;
//字符显示相关信号
wire text_done;
wire[23:0] text_data;
wire text_start;
assign init_start = (cur_state == ST_INIT) ? 1'b1 : 1'b0;
assign clear_start = (cur_state == ST_CLEAR) ? 1'b1 : 1'b0;
assign text_start = (cur_state == ST_TEXT) ? 1'b1 : 1'b0;
always@(posedge sys_clk or negedge rst_n)
begin
if(rst_n == 1'b0)
cur_state <= ST_INIT;
else
cur_state <= nxt_state;
end
always@(*)
begin
case(cur_state)
ST_INIT:
if(init_done == 1'b1)
nxt_state <= ST_CLEAR;
else
nxt_state <= ST_INIT;
ST_CLEAR:
if(clear_done == 1'b1)
nxt_state <= ST_TEXT;
else
nxt_state <= ST_CLEAR;
ST_TEXT:
if(text_done == 1'b1)
nxt_state <= ST_IDLE;
else
nxt_state <= ST_TEXT;
ST_IDLE:
nxt_state <= ST_IDLE; //显示完,保持不动
default: nxt_state <= ST_INIT;
endcase
end
oled_init oled_init_inst(
.sys_clk (sys_clk),
.rst_n (rst_n),
.init_start (init_start), //初始化请求
.wr_done (wr_done), //一组初始化数据完成信号
.init_done (init_done), //初始化完成输出
.init_data (init_data) //初始化的数据
);
oled_clear oled_clear_inst(
.sys_clk (sys_clk),
.rst_n (rst_n),
.clear_start (clear_start), //清屏请求
.wr_done (wr_done), //一组数据完成信号
.clear_done (clear_done), //清屏完成输出
.clear_data (clear_data) //清屏的数据
);
oled_text oled_text_inst(
.sys_clk (sys_clk),
.rst_n (rst_n),
.text_start (text_start), //字符显示请求
.wr_done (wr_done), //iic一组数据写完成
.text_data (text_data), //字符显示数据
.text_done (text_done) //字符显示完成
);
//数据选择
oled_mux oled_mux_inst(
.sys_clk (sys_clk),
.rst_n (rst_n),
.init_start (init_start),
.init_data (init_data),
.clear_start (clear_start),
.clear_data (clear_data),
.text_start (text_start),
.text_data (text_data),
.wr_req (wr_req),
.wr_data (wr_data)
);
i2c_master i2c_master_inst(
.sys_clk (sys_clk), /*系统时钟*/
.rst_n (rst_n), /*系统复位*/
.scl (oled_scl), /*I2C 时钟输出*/
.sda (oled_sda), /*I2C 数据线*/
.reg_slave ({wr_data[15:8],wr_data[23:16]}), /*从机 8bit的寄存器地址 + 8bit的从机地址*/
.wr_req (wr_req), /*I2C写寄存器请求*/
.wr_done (wr_done), /*I2C写寄存器完成*/
.data_byte (wr_data[7:0]) /*I2C发送数据 8bit的数据*/
);
endmodule1
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
verilog
//oled_init 初始化模块
module oled_init(
input sys_clk,
input rst_n,
input init_start, //初始化请求
input wr_done, //一组初始化数据完成信号
output init_done, //初始化完成输出
output[23:0] init_data //初始化的数据
);
localparam RST_ACTIVE = 1'b0; //复位有效
reg[23:0] init_data_reg;
reg[4:0] init_idx;
assign init_data = init_data_reg;
assign init_done = (init_idx >= 'd24 && wr_done == 1'b1) ? 1'b1 : 1'b0; //初始化完成信号
always@(posedge sys_clk or negedge rst_n)
begin
if(rst_n == RST_ACTIVE)
init_idx <= 'd0;
else if(init_idx == 'd24 && wr_done == 1'b1 )
init_idx <= 'd0;
else if(wr_done == 1'b1 && init_start == 1'b1)
init_idx <= init_idx + 1'b1;
else
init_idx <= init_idx;
end
always@(*)
begin
case(init_idx)
'd0: init_data_reg <= {8'h78,8'h00,8'hAE}; //显示关
'd1: init_data_reg <= {8'h78,8'h00,8'h00}; //列地址低 4 位
'd2: init_data_reg <= {8'h78,8'h00,8'h10}; //列地址高 4 位
'd3: init_data_reg <= {8'h78,8'h00,8'h40}; //显示起始行
'd4: init_data_reg <= {8'h78,8'h00,8'hB0}; //页地址
'd5: init_data_reg <= {8'h78,8'h00,8'h81}; //对比度设置
'd6: init_data_reg <= {8'h78,8'h00,8'hFF}; //对比度 255
'd7: init_data_reg <= {8'h78,8'h00,8'hA1}; //段重映射
'd8: init_data_reg <= {8'h78,8'h00,8'hA6}; //正常显示(非反显)
'd9: init_data_reg <= {8'h78,8'h00,8'hA8}; //多路复用比
'd10: init_data_reg <= {8'h78,8'h00,8'h1F}; //32 行(0x1F)
'd11: init_data_reg <= {8'h78,8'h00,8'hC8}; //COM 扫描方向
'd12: init_data_reg <= {8'h78,8'h00,8'hD3}; //显示偏移
'd13: init_data_reg <= {8'h78,8'h00,8'h00}; //偏移 0
'd14: init_data_reg <= {8'h78,8'h00,8'hD5}; //时钟分频
'd15: init_data_reg <= {8'h78,8'h00,8'h80}; //分频值
'd16: init_data_reg <= {8'h78,8'h00,8'hD9}; //预充电周期
'd17: init_data_reg <= {8'h78,8'h00,8'h1F}; //预充电值
'd18: init_data_reg <= {8'h78,8'h00,8'hDA}; //COM 引脚配置
'd19: init_data_reg <= {8'h78,8'h00,8'h00}; //配置值 0x00
'd20: init_data_reg <= {8'h78,8'h00,8'hDB}; //VCOMH 电压
'd21: init_data_reg <= {8'h78,8'h00,8'h40}; //0x40
'd22: init_data_reg <= {8'h78,8'h00,8'h8D}; //电荷泵设置
'd23: init_data_reg <= {8'h78,8'h00,8'h14}; //电荷泵使能
'd24: init_data_reg <= {8'h78,8'h00,8'hAF}; //显示开
default:
init_data_reg <= {8'h78,8'h00,8'hAE};
endcase
end
endmodule1
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
verilog
//清屏模块,将 OLED 屏幕全部变为黑色
module oled_clear(
input sys_clk,
input rst_n,
input clear_start, //清屏请求
input wr_done, //一组数据完成信号
output clear_done, //清屏完成输出
output[23:0] clear_data //清屏的数据
);
reg[23:0] clear_data_reg;
reg[10:0] clear_idx;
reg[2:0] page_addr;
assign clear_data = clear_data_reg;
assign clear_done = (page_addr == 'd3 && clear_idx == 'd130 && wr_done == 1'b1) ? 1'b1 : 1'b0; //128*32 只有 4 页(0~3)
always@(posedge sys_clk or negedge rst_n)
begin
if(rst_n == 1'b0)
clear_idx <= 'd0;
else if(clear_start == 1'b0) //非清屏时归 0,防止初始化阶段就计数
clear_idx <= 'd0;
else if(clear_idx == 'd130 && wr_done == 1'b1)
clear_idx <= 'd0;
else if(wr_done == 1'b1)
clear_idx <= clear_idx + 1'b1;
else
clear_idx <= clear_idx;
end
always@(posedge sys_clk or negedge rst_n)
begin
if(rst_n == 1'b0)
page_addr <= 'd0;
else if(clear_start == 1'b0) //非清屏时归 0
page_addr <= 'd0;
else if(clear_idx == 'd130 && wr_done == 1'b1)
page_addr <= page_addr + 1'b1;
else
page_addr <= page_addr;
end
always@(*)
begin
case(clear_idx)
'd0: clear_data_reg <= {8'h78,8'h00,8'hB0 + page_addr};
'd1: clear_data_reg <= {8'h78,8'h00,8'h00};
'd2: clear_data_reg <= {8'h78,8'h00,8'h10};
default: clear_data_reg <= {8'h78,8'h40,8'h00};
endcase
end
endmodule1
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
verilog
//OLED 字符显示:只显示 "LJPI-G1" 七个字符(每个 8*16)
module oled_text(
input sys_clk,
input rst_n,
input text_start, //字符显示请求
input wr_done, //iic一组数据写完成
output[23:0] text_data, //字符显示数据
output text_done //字符显示完成
);
reg[8:0] text_idx; //0,1,2是命令, 3~10是8个数据字节
reg[23:0] text_data_reg;
wire[7:0] font_byte;
reg[2:0] char_idx; //当前第几个字符 0~6
reg[7:0] col_addr; //列坐标
reg[3:0] page_addr; //页坐标
reg half_sel; //0=上半页 1=下半页
wire char_done;
//一个字符写完(上半8字节 + 下半8字节)
assign char_done = ((text_idx == 'd10) && (half_sel == 1'b1) && wr_done == 1'b1) ? 1'b1 : 1'b0;
//7个字符全部写完
assign text_done = (char_done == 1'b1 && char_idx == 'd6) ? 1'b1 : 1'b0;
assign text_data = text_data_reg;
//text_idx 计数
always@(posedge sys_clk or negedge rst_n)
begin
if(rst_n == 1'b0)
text_idx <= 'd0;
else if(char_done == 1'b1)
text_idx <= 'd0;
else if(text_idx == 'd10 && wr_done == 1'b1)
text_idx <= 'd0;
else if(wr_done == 1'b1 && text_start == 1'b1)
text_idx <= text_idx + 1'b1;
else
text_idx <= text_idx;
end
//命令/数据选择
always@(*)
begin
case(text_idx)
'd0: text_data_reg = {8'h78,8'h00,8'hB0 + page_addr + half_sel}; //设置页地址
'd1: text_data_reg = {8'h78,8'h00,8'h00 + col_addr[3:0]}; //列地址低4位
'd2: text_data_reg = {8'h78,8'h00,8'h10 + col_addr[7:4]}; //列地址高4位
default: text_data_reg = {8'h78,8'h40,font_byte}; //数据
endcase
end
//half_sel 控制(写满8个数据字节后翻转)
always@(posedge sys_clk or negedge rst_n)
begin
if(rst_n == 1'b0)
half_sel <= 1'b0;
else if(char_done == 1'b1)
half_sel <= 1'b0;
else if(text_idx == 'd10 && wr_done == 1'b1)
half_sel <= 1'b1;
else
half_sel <= half_sel;
end
//char_idx 计数
always@(posedge sys_clk or negedge rst_n)
begin
if(rst_n == 1'b0)
char_idx <= 'd0;
else if(text_done == 1'b1)
char_idx <= 'd0;
else if(char_done == 1'b1)
char_idx <= char_idx + 1'b1;
else
char_idx <= char_idx;
end
//每个字符的坐标
always@(posedge sys_clk or negedge rst_n)
begin
if(rst_n == 1'b0) //L
begin
col_addr <= 'd36;
page_addr <= 'd1;
end
else if(char_done == 1'b1 && char_idx == 'd0) //J
begin
col_addr <= 'd44;
page_addr <= 'd1;
end
else if(char_done == 1'b1 && char_idx == 'd1) //P
begin
col_addr <= 'd52;
page_addr <= 'd1;
end
else if(char_done == 1'b1 && char_idx == 'd2) //I
begin
col_addr <= 'd60;
page_addr <= 'd1;
end
else if(char_done == 1'b1 && char_idx == 'd3) //-
begin
col_addr <= 'd68;
page_addr <= 'd1;
end
else if(char_done == 1'b1 && char_idx == 'd4) //G
begin
col_addr <= 'd76;
page_addr <= 'd1;
end
else if(char_done == 1'b1 && char_idx == 'd5) //1
begin
col_addr <= 'd84;
page_addr <= 'd1;
end
else
begin
col_addr <= col_addr;
page_addr <= page_addr;
end
end
font_rom font_rom_inst(
.sys_clk (sys_clk),
.rst_n (rst_n),
.half_sel (half_sel),
.char_sel (char_idx),
.col (text_idx - 'd3),
.font_byte (font_byte)
);
endmodule1
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
verilog
//8*16 字母数字点阵,只存 "LJPI-G1" 七个字符
module font_rom(
input sys_clk,
input rst_n,
input half_sel, //0=上半(行0~7) 1=下半(行8~15)
input[2:0] char_sel, //0=L 1=J 2=P 3=I 4=- 5=G 6=1
input[8:0] col, //列号 0~7
output reg[7:0] font_byte
);
reg[7:0] glyph0[15:0]; //L
reg[7:0] glyph1[15:0]; //J
reg[7:0] glyph2[15:0]; //P
reg[7:0] glyph3[15:0]; //I
reg[7:0] glyph4[15:0]; //-
reg[7:0] glyph5[15:0]; //G
reg[7:0] glyph6[15:0]; //1
always@(posedge sys_clk or negedge rst_n)
begin
if(rst_n == 'd0)
font_byte <= 'd0;
else if(char_sel == 'd0)
font_byte <= glyph0[col + 'd8 * half_sel];
else if(char_sel == 'd1)
font_byte <= glyph1[col + 'd8 * half_sel];
else if(char_sel == 'd2)
font_byte <= glyph2[col + 'd8 * half_sel];
else if(char_sel == 'd3)
font_byte <= glyph3[col + 'd8 * half_sel];
else if(char_sel == 'd4)
font_byte <= glyph4[col + 'd8 * half_sel];
else if(char_sel == 'd5)
font_byte <= glyph5[col + 'd8 * half_sel];
else if(char_sel == 'd6)
font_byte <= glyph6[col + 'd8 * half_sel];
else
font_byte <= font_byte;
end
//L
always@(posedge sys_clk or negedge rst_n)
begin
if(rst_n == 1'b0)
begin
glyph0[0] = 8'hFF;
glyph0[1] = 8'h00;
glyph0[2] = 8'h00;
glyph0[3] = 8'h00;
glyph0[4] = 8'h00;
glyph0[5] = 8'h00;
glyph0[6] = 8'h00;
glyph0[7] = 8'h00;
glyph0[8] = 8'h7F;
glyph0[9] = 8'h40;
glyph0[10] = 8'h40;
glyph0[11] = 8'h40;
glyph0[12] = 8'h40;
glyph0[13] = 8'h40;
glyph0[14] = 8'h40;
glyph0[15] = 8'h40;
end
end
//J
always@(posedge sys_clk or negedge rst_n)
begin
if(rst_n == 1'b0)
begin
glyph1[0] = 8'h00;
glyph1[1] = 8'h00;
glyph1[2] = 8'h00;
glyph1[3] = 8'h00;
glyph1[4] = 8'h00;
glyph1[5] = 8'h00;
glyph1[6] = 8'hFF;
glyph1[7] = 8'h00;
glyph1[8] = 8'h30;
glyph1[9] = 8'h40;
glyph1[10] = 8'h40;
glyph1[11] = 8'h40;
glyph1[12] = 8'h40;
glyph1[13] = 8'h40;
glyph1[14] = 8'h3F;
glyph1[15] = 8'h00;
end
end
//P
always@(posedge sys_clk or negedge rst_n)
begin
if(rst_n == 1'b0)
begin
glyph2[0] = 8'hFF;
glyph2[1] = 8'h41;
glyph2[2] = 8'h41;
glyph2[3] = 8'h41;
glyph2[4] = 8'h41;
glyph2[5] = 8'h41;
glyph2[6] = 8'h3E;
glyph2[7] = 8'h00;
glyph2[8] = 8'h7F;
glyph2[9] = 8'h00;
glyph2[10] = 8'h00;
glyph2[11] = 8'h00;
glyph2[12] = 8'h00;
glyph2[13] = 8'h00;
glyph2[14] = 8'h00;
glyph2[15] = 8'h00;
end
end
//I
always@(posedge sys_clk or negedge rst_n)
begin
if(rst_n == 1'b0)
begin
glyph3[0] = 8'h01;
glyph3[1] = 8'h01;
glyph3[2] = 8'hFF;
glyph3[3] = 8'h01;
glyph3[4] = 8'h01;
glyph3[5] = 8'h01;
glyph3[6] = 8'h00;
glyph3[7] = 8'h00;
glyph3[8] = 8'h40;
glyph3[9] = 8'h40;
glyph3[10] = 8'h7F;
glyph3[11] = 8'h40;
glyph3[12] = 8'h40;
glyph3[13] = 8'h40;
glyph3[14] = 8'h00;
glyph3[15] = 8'h00;
end
end
//-
always@(posedge sys_clk or negedge rst_n)
begin
if(rst_n == 1'b0)
begin
glyph4[0] = 8'h80;
glyph4[1] = 8'h80;
glyph4[2] = 8'h80;
glyph4[3] = 8'h80;
glyph4[4] = 8'h80;
glyph4[5] = 8'h80;
glyph4[6] = 8'h00;
glyph4[7] = 8'h00;
glyph4[8] = 8'h00;
glyph4[9] = 8'h00;
glyph4[10] = 8'h00;
glyph4[11] = 8'h00;
glyph4[12] = 8'h00;
glyph4[13] = 8'h00;
glyph4[14] = 8'h00;
glyph4[15] = 8'h00;
end
end
//G
always@(posedge sys_clk or negedge rst_n)
begin
if(rst_n == 1'b0)
begin
glyph5[0] = 8'hFE;
glyph5[1] = 8'h01;
glyph5[2] = 8'h01;
glyph5[3] = 8'h01;
glyph5[4] = 8'h41;
glyph5[5] = 8'h41;
glyph5[6] = 8'hC1;
glyph5[7] = 8'h42;
glyph5[8] = 8'h3F;
glyph5[9] = 8'h40;
glyph5[10] = 8'h40;
glyph5[11] = 8'h40;
glyph5[12] = 8'h40;
glyph5[13] = 8'h40;
glyph5[14] = 8'h5F;
glyph5[15] = 8'h20;
end
end
//1
always@(posedge sys_clk or negedge rst_n)
begin
if(rst_n == 1'b0)
begin
glyph6[0] = 8'h00;
glyph6[1] = 8'h00;
glyph6[2] = 8'h02;
glyph6[3] = 8'hFF;
glyph6[4] = 8'hFF;
glyph6[5] = 8'h00;
glyph6[6] = 8'h00;
glyph6[7] = 8'h00;
glyph6[8] = 8'h00;
glyph6[9] = 8'h40;
glyph6[10] = 8'h40;
glyph6[11] = 8'h7F;
glyph6[12] = 8'h7F;
glyph6[13] = 8'h40;
glyph6[14] = 8'h40;
glyph6[15] = 8'h00;
end
end
endmodule1
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
verilog
module oled_mux(
input sys_clk,
input rst_n,
input init_start,
input[23:0] init_data,
input clear_start,
input[23:0] clear_data,
input text_start,
input[23:0] text_data,
output wr_req,
output[23:0] wr_data
);
assign wr_req = init_start | text_start | clear_start;
assign wr_data = (init_start == 1'b1) ? init_data : (clear_start == 1'b1) ? clear_data : text_data;
endmodule1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
verilog
`timescale 1ns/1ps
module i2c_master(
input sys_clk, // 系统时钟
input rst_n, // 复位,低电平有效
output scl, // I2C 时钟线
inout sda, // I2C 数据线
input[15:0] reg_slave, // {寄存器地址[15:8], 从机地址[7:0]}
input wr_req, // 写请求
output wr_done, // 写完成
input[7:0] data_byte // 要写的数据(第3个字节)
);
localparam ST_IDLE = 3'd0; // 空闲:等写请求
localparam ST_START = 3'd1; // 起始:发 START 条件
localparam ST_DATA = 3'd2; // 写数据:发一个字节的 8 位
localparam ST_ACK = 3'd3; // 应答:发第 9 个时钟,等 ACK
localparam ST_STOP = 3'd4; // 停止:发 STOP 条件
localparam SCL_DIV = 100; // SCL 半个周期 = 100 个 sys_clk 周期
// 所以 SCL 频率 = sys_clk / 200
reg[2:0] cur_state; // 当前状态
reg[2:0] nxt_state; // 下一状态
reg[21:0] div_cnt; // 节拍计数器:0~100(起始态 0~200)
reg[3:0] bit_cnt; // 位计数器
reg[1:0] pulse_cnt; // 脉冲计数器
reg[2:0] byte_cnt; // 字节计数器
reg[15:0] addr_reg; // 存 {寄存器地址, 从机地址},靠对调实现"始终发低8位"
reg sda_out; // 要发到 SDA 上的数据
reg scl_out; // 内部生成的 SCL 时钟
// 应答态释放 SDA(高阻,让从机拉低回应);其余时间发 sda_out
assign sda = (cur_state == ST_ACK) ? 1'bz : sda_out;
// SCL 直接输出内部生成的时钟
assign scl = scl_out;
// 写完成:STOP 结束、要回 IDLE 的那一拍,输出一个脉冲
assign wr_done = (cur_state != nxt_state && cur_state == ST_STOP) ? 1'b1 : 1'b0;
always @(posedge sys_clk or negedge rst_n) begin
if(rst_n == 1'b0)
cur_state <= ST_IDLE;
else
cur_state <= nxt_state;
end
//------------------------------------------------------------------
// 状态机
//------------------------------------------------------------------
always @(*) begin
case(cur_state)
// 空闲:有写请求就发起始,否则继续等
ST_IDLE: begin
if(wr_req == 1'b1)
nxt_state = ST_START;
else
nxt_state = ST_IDLE;
end
// 起始:摆 200 拍起始时序,摆完去发数据
ST_START: begin
if(div_cnt == SCL_DIV * 2)
nxt_state = ST_DATA;
else
nxt_state = ST_START;
end
// 写数据:8 位发完且 SCL 回低,去应答
ST_DATA: begin
if(bit_cnt == 8 && scl_out == 1'b0)
nxt_state = ST_ACK;
else
nxt_state = ST_DATA;
end
// 应答:第 9 个时钟打完,看发完没
ST_ACK: begin
if(pulse_cnt == 1 && scl_out == 1'b0) begin
if(byte_cnt == 2) // 3 个字节都发完
nxt_state = ST_STOP; // 去停止
else
nxt_state = ST_DATA; // 否则发下一字节
end
else
nxt_state = ST_ACK;
end
// 停止:摆好停止时序,回空闲,一次写结束
ST_STOP: begin
if(pulse_cnt == 1 && div_cnt == SCL_DIV/4 && scl_out == 1'b1)
nxt_state = ST_IDLE;
else
nxt_state = ST_STOP;
end
default: nxt_state = ST_IDLE;
endcase
end
//------------------------------------------------------------------
// 字节计数器
//------------------------------------------------------------------
always @(posedge sys_clk or negedge rst_n) begin
if(rst_n == 1'b0)
byte_cnt <= 'd0;
else if(cur_state == ST_IDLE)
byte_cnt <= 'd0;
else if(cur_state == ST_ACK && cur_state != nxt_state) // 离开 ACK 的那一刻
byte_cnt <= byte_cnt + 1'b1;
// 其它情况保持
end
//------------------------------------------------------------------
// 节拍计数器 div_cnt:所有时序的"节拍器"
// 写数据/应答/停止态 0~100 循环;起始态 0~200
//------------------------------------------------------------------
always @(posedge sys_clk or negedge rst_n) begin
if(rst_n == 1'b0)
div_cnt <= 'd0;
else if(div_cnt == SCL_DIV && cur_state != ST_START) // 非起始态:数到 100 清零
div_cnt <= 'd0;
else if(cur_state != nxt_state) // 状态要切换:清零
div_cnt <= 'd0;
else if(cur_state == ST_START) // 起始态:一直数(到 200 靠换状态清零)
div_cnt <= div_cnt + 1'b1;
else if(cur_state == ST_DATA || cur_state == ST_ACK || cur_state == ST_STOP)
div_cnt <= div_cnt + 1'b1; // 写数据/应答/停止态:+1
end
//------------------------------------------------------------------
// 位计数器 bit_cnt(0~7)
//------------------------------------------------------------------
always @(posedge sys_clk or negedge rst_n) begin
if(rst_n == 1'b0)
bit_cnt <= 'd0;
else if(cur_state == ST_IDLE || cur_state == ST_ACK) // 空闲/应答态:归 0
bit_cnt <= 'd0;
else if(cur_state == ST_DATA && div_cnt == SCL_DIV/2 && scl_out == 1'b1)
bit_cnt <= bit_cnt + 1'b1; // 每过一个 SCL 高电平:位 +1
// 其它情况保持
end
//------------------------------------------------------------------
// 脉冲计数器 ACK/STOP 态发了几个 SCL 脉冲
//------------------------------------------------------------------
always @(posedge sys_clk or negedge rst_n) begin
if(rst_n == 1'b0)
pulse_cnt <= 'd0;
else if(cur_state != nxt_state) // 状态要切换:清零
pulse_cnt <= 'd0;
else if(cur_state == ST_ACK || cur_state == ST_STOP) begin
if(div_cnt == SCL_DIV/2 && scl_out == 1'b1) // 数到 50 且 SCL 高:+1
pulse_cnt <= pulse_cnt + 1'b1;
// 否则保持
end
else // 其它状态:清零
pulse_cnt <= 'd0;
end
//------------------------------------------------------------------
// 从机信息寄存器
// 存 {寄存器地址[15:8], 从机地址[7:0]}
// 技巧:每发完一个字节高低 8 位对调,让低 8 位始终是"下一个要发的字节"
//------------------------------------------------------------------
always @(posedge sys_clk or negedge rst_n) begin
if(rst_n == 1'b0)
addr_reg <= 'd0;
else if(wr_req == 1'b1 && cur_state == ST_IDLE) // 请求来:装入 {寄存器地址, 从机地址}
addr_reg <= reg_slave;
else if(cur_state == ST_ACK && cur_state != nxt_state) // 每发完一个字节:高低对调
addr_reg <= {addr_reg[7:0], addr_reg[15:8]};
// 其它情况保持
end
//------------------------------------------------------------------
// SCL 时钟生成
// 写数据/应答态:div_cnt 每数到 100 取反一次,形成方波
//------------------------------------------------------------------
always @(posedge sys_clk or negedge rst_n) begin
if(rst_n == 1'b0)
scl_out <= 1'b1; // 复位后 SCL 高(I2C 空闲是高)
else if(cur_state == ST_START && div_cnt == SCL_DIV*2) // 起始末尾:SCL 拉低
scl_out <= 1'b0;
else if(cur_state == ST_DATA && div_cnt == SCL_DIV) // 写数据:数到 100 取反
scl_out <= ~scl_out;
else if(cur_state == ST_ACK && div_cnt == SCL_DIV) // 应答:数到 100 取反
scl_out <= ~scl_out;
else if(cur_state == ST_STOP && div_cnt == SCL_DIV) // 停止:数到 100 拉高
scl_out <= 1'b1;
end
//------------------------------------------------------------------
// SDA 发送控制 sda_out:决定每个时刻 SDA 上发什么
//------------------------------------------------------------------
always @(posedge sys_clk or negedge rst_n) begin
if(rst_n == 1'b0)
sda_out <= 1'b1; // 复位后 SDA 高(I2C 空闲是高)
else if(cur_state == ST_START && div_cnt == SCL_DIV/4) // 起始前段:SDA 保持高
sda_out <= 1'b1;
else if(cur_state == ST_START && div_cnt == SCL_DIV/2) // 起始中段:SDA 拉低(= 起始条件)
sda_out <= 1'b0;
else if(cur_state == ST_DATA && div_cnt == SCL_DIV/2) begin // 发数据:数到 50(SCL 低电平中段)
if(scl_out == 1'b0 && byte_cnt == 2) // 第 3 字节:发 data_byte
sda_out <= data_byte[7 - bit_cnt];
else if(scl_out == 1'b0) // 前 2 字节:发 addr_reg
sda_out <= addr_reg[7 - bit_cnt];
// SCL 高时保持
end
else if(cur_state == ST_STOP && div_cnt == SCL_DIV) begin
if(scl_out == 1'b1)
sda_out <= 1'b1; // SCL 高时 SDA 拉高(= 停止条件)
// 否则保持
end
else if(cur_state == ST_IDLE)
sda_out <= 1'b1; // 空闲时 SDA 保持高
end
endmodule1
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
4、I/O 绑定
引脚绑定关系如下:
| 信号 | 引脚 |
|---|---|
| sys_clk | T7 |
| rst_n | F10 |
| oled_scl | R14 |
| oled_sda | T15 |
5、程序下载
程序下载后现象如下图
可以看到屏幕上显示 逻辑派 G1 的英文缩写