1、模块来源
采购链接:
0.96 寸 OLED 显示屏 12864 液晶屏模块 IIC 显示屏 ssd1306 12864oled
实物图展示| 项目 | 参数 |
|---|---|
| 驱动芯片 | SSD1306 |
| 接口 | IIC(I2C),4 根线:VCC / GND / SCL / SDA |
| 颜色 | 单色 |
| 分辨率 | (1.8寸)128 × 64 |
| 工作电压 | 3.3V |
关键参数
2、实验原理
本实验我们用 IIC 通信让逻辑派驱动我们的屏幕,接下来介绍 IIC 原理和 0.96 寸 OLED 屏幕原理
2.1 IIC协议介绍
IIC 通讯协议是由 Philips 公司开发的一种简单、双向二向同步串行总线,通过时钟线 SCL 和数据线 SDA 两根线即可在一条总线上让主机和多个从机进行通行,常用于小型设备,低速设备,如传感器、存储器和其他设备。
在fpga上应用有以下几个优点
- 可拓展性强:IIC 支持和多个设备连接可以实现多个设备之间的通信
- 低功耗:IIC 的开漏输出决定了空闲状态下 IIC 是高电平,低功耗模式
- 简单,可移植性强:IIC 协议相对比较简单,移植到不同 fpga 上相对简单
缺点是:
- 速度较慢,适用于低速设备
- 多个设备同时发送数据时容易冲突,需要额外的冲突检测和处理机制
2.2 0.96寸OLED屏幕介绍
OLED 全称有机发光二极管,通过配置不同地址 LED 寄存器的高低电平来控制每个像素点的亮灭,从而实现显示图像的作用,OLED 屏幕的优点是低功耗和对比度高
OLED 上的显示并不是同时刷新的而是按照先后顺序一个个刷新的,不过由于刷新速度很快和人眼的暂留效应让我们看到的就是一帧一帧完整的图像
其中有三种刷新方式,分别为行顺序刷新,列顺序刷新和页顺序刷新,如下图
- 行顺序模式,从左到右刷新完一行后朝箭头方向刷新下一行直到刷新完整个屏幕
- 列顺序模式,从上到下刷新完后刷新下一行直到刷新完
- 页顺序模式
3、时序说明
接下来我们来介绍一下 IIC 的时序
IIC 收发逻辑
我们先看 IIC 的收发逻辑- 写操作:写操作一般发三个字节,由起始位开始,每发完一个字节要释放 ACK 等待从机拉低响应。
- 读操作:主机发送设备地址(+ 写标志)→寄存器地址→重新发送起始信号→设备地址(+ 读标志)→接收数据并发送 ACK/NACK
写/读时序
I2C 的写/读时序可以分为以下几个步骤1.空闲时 SCL 和 SDA 均为高电平
2.SCL 高电平的时候 SDA 拉低为起始位标志
3.SCL 为高电平时 SDA 保持不变 ,SCL 为低电平的时候 SDA 改变,此时发送8位信号(1个字节)
4.发送完一个字节后释放 ACK (变为高电平),等待从机应答(拉低ACK)后发送下一个字节
5.发送完 写/读 操作的 三/四 个字节后停止位,SCL 高电平时,SDA 由低→高跳变,标志通信结束
4、程序编写
我们下面给出这个例程的程序实现,整个 OLED 显示驱动分成了以下模块:
oled_demo:顶层模块,例化oled_top,对外只留时钟、复位和 I2C 的两根线。oled_top:OLED 顶层控制模块,用状态机把「初始化 → 清屏 → 显示字符」三步串起来。oled_init:OLED 初始化模块,上电后把 SSD1306 的 27 条初始化命令依次发出去。oled_clear:清屏模块,把整块屏幕刷成黑色。oled_text:字符显示模块,控制 "LJPI-G1" 七个字符显示的位置。font_rom:字库模块,存 "LJPI-G1" 七个字符的 8×16 点阵。oled_mux:数据选择模块,把初始化 / 清屏 / 字符显示三路数据选一路交给 I2C 发送。i2c_master:I2C 主机驱动,负责按 I2C 协议把命令和数据发给 OLED 屏幕。
//顶层:只驱动 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)
);
endmodule2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//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的数据*/
);
endmodule2
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
//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 >= 'd26 && 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 == 'd26 && 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};
'd2: init_data_reg <= {8'h78,8'h00,8'h10};
'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};
'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'h3F};
'd11: init_data_reg <= {8'h78,8'h00,8'hC8};
'd12: init_data_reg <= {8'h78,8'h00,8'hD3};
'd13: init_data_reg <= {8'h78,8'h00,8'h00};
'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'hD8};
'd17: init_data_reg <= {8'h78,8'h00,8'h05};
'd18: init_data_reg <= {8'h78,8'h00,8'hD9};
'd19: init_data_reg <= {8'h78,8'h00,8'hF1};
'd20: init_data_reg <= {8'h78,8'h00,8'hDA};
'd21: init_data_reg <= {8'h78,8'h00,8'h12};
'd22: init_data_reg <= {8'h78,8'h00,8'hDB};
'd23: init_data_reg <= {8'h78,8'h00,8'h30};
'd24: init_data_reg <= {8'h78,8'h00,8'h8D};
'd25: init_data_reg <= {8'h78,8'h00,8'h14};
'd26: init_data_reg <= {8'h78,8'h00,8'hAF};
default:
init_data_reg <= {8'h78,8'h00,8'hAE};
endcase
end
endmodule2
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
//清屏模块,将 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 == 'd7 && clear_idx == 'd130 && wr_done == 1'b1) ? 1'b1 : 1'b0;
always@(posedge sys_clk or negedge rst_n)
begin
if(rst_n == 1'b0)
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_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
endmodule2
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
//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;
//命令/数据选择
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 <= 'd3;
end
else if(char_done == 1'b1 && char_idx == 'd0) //J
begin
col_addr <= 'd44;
page_addr <= 'd3;
end
else if(char_done == 1'b1 && char_idx == 'd1) //P
begin
col_addr <= 'd52;
page_addr <= 'd3;
end
else if(char_done == 1'b1 && char_idx == 'd2) //I
begin
col_addr <= 'd60;
page_addr <= 'd3;
end
else if(char_done == 1'b1 && char_idx == 'd3) //-
begin
col_addr <= 'd68;
page_addr <= 'd3;
end
else if(char_done == 1'b1 && char_idx == 'd4) //G
begin
col_addr <= 'd76;
page_addr <= 'd3;
end
else if(char_done == 1'b1 && char_idx == 'd5) //1
begin
col_addr <= 'd84;
page_addr <= 'd3;
end
else
begin
col_addr <= col_addr;
page_addr <= page_addr;
end
end
//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
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)
);
endmodule2
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
//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
endmodule2
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
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;
endmodule2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
`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 //
);
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; // 节拍计数器
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
//------------------------------------------------------------------
// 字节计数器 byte_cnt
//------------------------------------------------------------------
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
//------------------------------------------------------------------
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
//------------------------------------------------------------------
// 脉冲计数器
//------------------------------------------------------------------
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
//------------------------------------------------------------------
// 从机信息寄存器 addr_reg
// {寄存器地址[15:8], 从机地址[7:0]}
//------------------------------------------------------------------
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 取反一次,形成方波(半周期=100个sys_clk)
//------------------------------------------------------------------
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
//------------------------------------------------------------------
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
endmodule2
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
5、I/O 绑定
引脚绑定图如下
6、程序下载
注:部分图片来自网络