【国产FPGA评测】安路(型号SF1S60CG121I) 05OLED 驱动模块设计、DDS显示以及问题
[复制链接]
本帖最后由 EPTmachine 于 2023-4-2 23:34 编辑
本文通过设计驱动OLED的FPGA模块,实现在OLED屏幕上显示字符的功能,并于之前的DDS模块相结合,最终实现输出波形的参数显示,但是在调试过程中出现了问题,这里对驱动的过程进行介绍。
1.1 硬件连接
扩展板上的OLED屏幕与SF1 Demo板的连接如下表所示
SF1S60CG1121I引脚
|
OLED引脚
|
IO_R10P_0,GCLK13,D0(A10)
|
DC
|
IO_R11P_0(B9)
|
RST
|
IO_R11N_0(B8)
|
SPI_MOSI
|
IO_R12P_0,GCLK14,USRCLK(A9)
|
SPI_SCK
|
IO_R12N_0,GCLK15,SCLK(A8)
|
板上按键K2(6号接口)
|
连接的实物图如图所示。
1.2 OLED驱动模块设计
驱动OLED屏幕的模块主要包括字库和运行状态机两部分组成。字库模块在复位时进行初始化。具体的代码如下:
//5*8点阵字库数据
always@(posedge rst_n)
begin
mem[ 0] = {8'h3E, 8'h51, 8'h49, 8'h45, 8'h3E}; // 48 0
mem[ 1] = {8'h00, 8'h42, 8'h7F, 8'h40, 8'h00}; // 49 1
mem[ 2] = {8'h42, 8'h61, 8'h51, 8'h49, 8'h46}; // 50 2
mem[ 3] = {8'h21, 8'h41, 8'h45, 8'h4B, 8'h31}; // 51 3
mem[ 4] = {8'h18, 8'h14, 8'h12, 8'h7F, 8'h10}; // 52 4
mem[ 5] = {8'h27, 8'h45, 8'h45, 8'h45, 8'h39}; // 53 5
mem[ 6] = {8'h3C, 8'h4A, 8'h49, 8'h49, 8'h30}; // 54 6
mem[ 7] = {8'h01, 8'h71, 8'h09, 8'h05, 8'h03}; // 55 7
mem[ 8] = {8'h36, 8'h49, 8'h49, 8'h49, 8'h36}; // 56 8
mem[ 9] = {8'h06, 8'h49, 8'h49, 8'h29, 8'h1E}; // 57 9
mem[ 10] = {8'h7C, 8'h12, 8'h11, 8'h12, 8'h7C}; // 65 A
mem[ 11] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h36}; // 66 B
mem[ 12] = {8'h3E, 8'h41, 8'h41, 8'h41, 8'h22}; // 67 C
mem[ 13] = {8'h7F, 8'h41, 8'h41, 8'h22, 8'h1C}; // 68 D
mem[ 14] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h41}; // 69 E
mem[ 15] = {8'h7F, 8'h09, 8'h09, 8'h09, 8'h01}; // 70 F
mem[ 32] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00}; // 32 sp
mem[ 33] = {8'h00, 8'h00, 8'h2f, 8'h00, 8'h00}; // 33 !
mem[ 34] = {8'h00, 8'h07, 8'h00, 8'h07, 8'h00}; // 34
mem[ 35] = {8'h14, 8'h7f, 8'h14, 8'h7f, 8'h14}; // 35 #
mem[ 36] = {8'h24, 8'h2a, 8'h7f, 8'h2a, 8'h12}; // 36 $
mem[ 37] = {8'h62, 8'h64, 8'h08, 8'h13, 8'h23}; // 37 %
mem[ 38] = {8'h36, 8'h49, 8'h55, 8'h22, 8'h50}; // 38 &
mem[ 39] = {8'h00, 8'h05, 8'h03, 8'h00, 8'h00}; // 39 '
mem[ 40] = {8'h00, 8'h1c, 8'h22, 8'h41, 8'h00}; // 40 (
mem[ 41] = {8'h00, 8'h41, 8'h22, 8'h1c, 8'h00}; // 41 )
mem[ 42] = {8'h14, 8'h08, 8'h3E, 8'h08, 8'h14}; // 42 *
mem[ 43] = {8'h08, 8'h08, 8'h3E, 8'h08, 8'h08}; // 43 +
mem[ 44] = {8'h00, 8'h00, 8'hA0, 8'h60, 8'h00}; // 44 ,
mem[ 45] = {8'h08, 8'h08, 8'h08, 8'h08, 8'h08}; // 45 -
mem[ 46] = {8'h00, 8'h60, 8'h60, 8'h00, 8'h00}; // 46 .
mem[ 47] = {8'h20, 8'h10, 8'h08, 8'h04, 8'h02}; // 47 /
mem[ 48] = {8'h3E, 8'h51, 8'h49, 8'h45, 8'h3E}; // 48 0
mem[ 49] = {8'h00, 8'h42, 8'h7F, 8'h40, 8'h00}; // 49 1
mem[ 50] = {8'h42, 8'h61, 8'h51, 8'h49, 8'h46}; // 50 2
mem[ 51] = {8'h21, 8'h41, 8'h45, 8'h4B, 8'h31}; // 51 3
mem[ 52] = {8'h18, 8'h14, 8'h12, 8'h7F, 8'h10}; // 52 4
mem[ 53] = {8'h27, 8'h45, 8'h45, 8'h45, 8'h39}; // 53 5
mem[ 54] = {8'h3C, 8'h4A, 8'h49, 8'h49, 8'h30}; // 54 6
mem[ 55] = {8'h01, 8'h71, 8'h09, 8'h05, 8'h03}; // 55 7
mem[ 56] = {8'h36, 8'h49, 8'h49, 8'h49, 8'h36}; // 56 8
mem[ 57] = {8'h06, 8'h49, 8'h49, 8'h29, 8'h1E}; // 57 9
mem[ 58] = {8'h00, 8'h36, 8'h36, 8'h00, 8'h00}; // 58 :
mem[ 59] = {8'h00, 8'h56, 8'h36, 8'h00, 8'h00}; // 59 ;
mem[ 60] = {8'h08, 8'h14, 8'h22, 8'h41, 8'h00}; // 60 <
mem[ 61] = {8'h14, 8'h14, 8'h14, 8'h14, 8'h14}; // 61 =
mem[ 62] = {8'h00, 8'h41, 8'h22, 8'h14, 8'h08}; // 62 >
mem[ 63] = {8'h02, 8'h01, 8'h51, 8'h09, 8'h06}; // 63 ?
mem[ 64] = {8'h32, 8'h49, 8'h59, 8'h51, 8'h3E}; // 64 @
mem[ 65] = {8'h7C, 8'h12, 8'h11, 8'h12, 8'h7C}; // 65 A
mem[ 66] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h36}; // 66 B
mem[ 67] = {8'h3E, 8'h41, 8'h41, 8'h41, 8'h22}; // 67 C
mem[ 68] = {8'h7F, 8'h41, 8'h41, 8'h22, 8'h1C}; // 68 D
mem[ 69] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h41}; // 69 E
mem[ 70] = {8'h7F, 8'h09, 8'h09, 8'h09, 8'h01}; // 70 F
mem[ 71] = {8'h3E, 8'h41, 8'h49, 8'h49, 8'h7A}; // 71 G
mem[ 72] = {8'h7F, 8'h08, 8'h08, 8'h08, 8'h7F}; // 72 H
mem[ 73] = {8'h00, 8'h41, 8'h7F, 8'h41, 8'h00}; // 73 I
mem[ 74] = {8'h20, 8'h40, 8'h41, 8'h3F, 8'h01}; // 74 J
mem[ 75] = {8'h7F, 8'h08, 8'h14, 8'h22, 8'h41}; // 75 K
mem[ 76] = {8'h7F, 8'h40, 8'h40, 8'h40, 8'h40}; // 76 L
mem[ 77] = {8'h7F, 8'h02, 8'h0C, 8'h02, 8'h7F}; // 77 M
mem[ 78] = {8'h7F, 8'h04, 8'h08, 8'h10, 8'h7F}; // 78 N
mem[ 79] = {8'h3E, 8'h41, 8'h41, 8'h41, 8'h3E}; // 79 O
mem[ 80] = {8'h7F, 8'h09, 8'h09, 8'h09, 8'h06}; // 80 P
mem[ 81] = {8'h3E, 8'h41, 8'h51, 8'h21, 8'h5E}; // 81 Q
mem[ 82] = {8'h7F, 8'h09, 8'h19, 8'h29, 8'h46}; // 82 R
mem[ 83] = {8'h46, 8'h49, 8'h49, 8'h49, 8'h31}; // 83 S
mem[ 84] = {8'h01, 8'h01, 8'h7F, 8'h01, 8'h01}; // 84 T
mem[ 85] = {8'h3F, 8'h40, 8'h40, 8'h40, 8'h3F}; // 85 U
mem[ 86] = {8'h1F, 8'h20, 8'h40, 8'h20, 8'h1F}; // 86 V
mem[ 87] = {8'h3F, 8'h40, 8'h38, 8'h40, 8'h3F}; // 87 W
mem[ 88] = {8'h63, 8'h14, 8'h08, 8'h14, 8'h63}; // 88 X
mem[ 89] = {8'h07, 8'h08, 8'h70, 8'h08, 8'h07}; // 89 Y
mem[ 90] = {8'h61, 8'h51, 8'h49, 8'h45, 8'h43}; // 90 Z
mem[ 91] = {8'h00, 8'h7F, 8'h41, 8'h41, 8'h00}; // 91 [
mem[ 92] = {8'h55, 8'h2A, 8'h55, 8'h2A, 8'h55}; // 92 .
mem[ 93] = {8'h00, 8'h41, 8'h41, 8'h7F, 8'h00}; // 93 ]
mem[ 94] = {8'h04, 8'h02, 8'h01, 8'h02, 8'h04}; // 94 ^
mem[ 95] = {8'h40, 8'h40, 8'h40, 8'h40, 8'h40}; // 95 _
mem[ 96] = {8'h00, 8'h01, 8'h02, 8'h04, 8'h00}; // 96 '
mem[ 97] = {8'h20, 8'h54, 8'h54, 8'h54, 8'h78}; // 97 a
mem[ 98] = {8'h7F, 8'h48, 8'h44, 8'h44, 8'h38}; // 98 b
mem[ 99] = {8'h38, 8'h44, 8'h44, 8'h44, 8'h20}; // 99 c
mem[100] = {8'h38, 8'h44, 8'h44, 8'h48, 8'h7F}; // 100 d
mem[101] = {8'h38, 8'h54, 8'h54, 8'h54, 8'h18}; // 101 e
mem[102] = {8'h08, 8'h7E, 8'h09, 8'h01, 8'h02}; // 102 f
mem[103] = {8'h18, 8'hA4, 8'hA4, 8'hA4, 8'h7C}; // 103 g
mem[104] = {8'h7F, 8'h08, 8'h04, 8'h04, 8'h78}; // 104 h
mem[105] = {8'h00, 8'h44, 8'h7D, 8'h40, 8'h00}; // 105 i
mem[106] = {8'h40, 8'h80, 8'h84, 8'h7D, 8'h00}; // 106 j
mem[107] = {8'h7F, 8'h10, 8'h28, 8'h44, 8'h00}; // 107 k
mem[108] = {8'h00, 8'h41, 8'h7F, 8'h40, 8'h00}; // 108 l
mem[109] = {8'h7C, 8'h04, 8'h18, 8'h04, 8'h78}; // 109 m
mem[110] = {8'h7C, 8'h08, 8'h04, 8'h04, 8'h78}; // 110 n
mem[111] = {8'h38, 8'h44, 8'h44, 8'h44, 8'h38}; // 111 o
mem[112] = {8'hFC, 8'h24, 8'h24, 8'h24, 8'h18}; // 112 p
mem[113] = {8'h18, 8'h24, 8'h24, 8'h18, 8'hFC}; // 113 q
mem[114] = {8'h7C, 8'h08, 8'h04, 8'h04, 8'h08}; // 114 r
mem[115] = {8'h48, 8'h54, 8'h54, 8'h54, 8'h20}; // 115 s
mem[116] = {8'h04, 8'h3F, 8'h44, 8'h40, 8'h20}; // 116 t
mem[117] = {8'h3C, 8'h40, 8'h40, 8'h20, 8'h7C}; // 117 u
mem[118] = {8'h1C, 8'h20, 8'h40, 8'h20, 8'h1C}; // 118 v
mem[119] = {8'h3C, 8'h40, 8'h30, 8'h40, 8'h3C}; // 119 w
mem[120] = {8'h44, 8'h28, 8'h10, 8'h28, 8'h44}; // 120 x
mem[121] = {8'h1C, 8'hA0, 8'hA0, 8'hA0, 8'h7C}; // 121 y
mem[122] = {8'h44, 8'h64, 8'h54, 8'h4C, 8'h44}; // 122 z
end
OLED屏幕驱动包括初始化状态机和运行状态机,主要可以分为Idle,Init,Main,Scan ,Write ,Delay。相互之间的切换关系如图所示。
状态机的代码如下:
always @ (posedge clk_oled or negedge rst_n)begin
if(!rst_n)begin
cnt_main <= 1'b0; cnt_init <= 1'b0; cnt_scan <= 1'b0; cnt_write <= 1'b0;
y_p <= 1'b0; x_ph <= 1'b0; x_pl <= 1'b0;
num <= 1'b0; char <= 1'b0; char_reg <= 1'b0;
num_delay <= 16'd5; cnt_delay <= 1'b0; cnt <= 1'b0;
oled_rst <= HIGH; oled_dcn <= CMD; oled_clk <= HIGH; oled_dat <= LOW;
state <= IDLE; state_back <= IDLE;
end else begin
case(state)
IDLE:begin
cnt_main <= 1'b0; cnt_init <= 1'b0; cnt_scan <= 1'b0; cnt_write <= 1'b0;
y_p <= 1'b0; x_ph <= 1'b0; x_pl <= 1'b0;
num_delay <= 16'd5; cnt_delay <= 1'b0; cnt <= 1'b0;
oled_rst <= HIGH; oled_dcn <= CMD; oled_clk <= HIGH; oled_dat <= LOW;
state <= MAIN; state_back <= MAIN;
end
MAIN:begin
if(cnt_main >= 5'd12) cnt_main <= 5'd9;//接下来执行空操作,实现数据只刷新一次//1值为最后一个case的值 2值为动态数据起始值
else cnt_main <= cnt_main + 1'b1;
case(cnt_main) //MAIN状态
5'd0:begin state <= INIT; end
//y_p 是页寻址模式下的 设置页起始地址 从b0到b7 1页是8行
//x_ph 是页寻址模式下的 设置起始列地址高位 从10到17 8大列
//x_pl 是页寻址模式下的 设置起始列地址低位 从00到0f 每一大列里面有16小列
//num 表示写入的字符串的位数 会不断-1直到0
5'd1:begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
5'd2:begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
5'd3:begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
5'd4:begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
5'd5:begin y_p <= 8'hb4; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
5'd6:begin y_p <= 8'hb5; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
5'd7:begin y_p <= 8'hb6; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
5'd8:begin y_p <= 8'hb7; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
5'd9 : begin y_p <= 8'hb2; x_ph <= 8'h00; x_pl <= 8'h08; num <= 5'd16; char <= "WAVE: ";state <= SCAN; end
5'd10: begin y_p <= 8'hb4; x_ph <= 8'h00; x_pl <= 8'h08; num <= 5'd16; char <= "FRE: ";state <= SCAN; end
5'd11: begin y_p <= 8'hb6; x_ph <= 8'h00; x_pl <= 8'h08; num <= 5'd16; char <= "AMP: ";state <= SCAN; end
5'd12: begin y_p <= 8'hb0; x_ph <= 8'h00; x_pl <= 8'h08; num <= 5'd16; char <= " EEWORLD-DDS ";state <= SCAN; end
// 5'd13: begin y_p <= 8'hb2; x_ph <= 8'h12; x_pl <= 8'h08; num <= 5'd3; char <= dis_type_buff;state <= SCAN; end
// 5'd14: begin y_p <= 8'hb4; x_ph <= 8'h12; x_pl <= 8'h08; num <= 5'd10; char <= dis_freq_buff;state <= SCAN; end
// 5'd15: begin y_p <= 8'hb6; x_ph <= 8'h12; x_pl <= 8'h08; num <= 5'd7; char <= dis_amp_buff;state <= SCAN; end
default: state <= IDLE; //如果你需要动态刷新一些信息,此行应该取消注释
endcase
end
INIT:begin
case(cnt_init)
5'd0: begin oled_rst <= LOW; cnt_init <= cnt_init + 1'b1; end //复位有效
5'd1: begin num_delay <= 16'd25000; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end //延时大于3us
5'd2: begin oled_rst <= HIGH; cnt_init <= cnt_init + 1'b1; end //复位恢复
5'd3: begin num_delay <= 16'd25000; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end //延时大于220us
5'd4: begin
if(cnt>=INIT_DEPTH) begin //当23条初始化指令及数据发出后,配置完成
cnt <= 1'b0;
cnt_init <= cnt_init + 1'b1;
end else begin
cnt <= cnt + 1'b1; num_delay <= 16'd5;
oled_dcn <= CMD; char_reg <= cmd[cnt]; state <= WRITE; state_back <= INIT;
end
end
5'd5: begin cnt_init <= 1'b0; state <= MAIN; end //初始化完成,返回MAIN状态
default:state<=IDLE;
endcase
end
SCAN:begin
if(cnt_scan == 5'd11) begin
if(num) cnt_scan <= 5'd3;
else cnt_scan <= cnt_scan + 1'b1;
end
else if(cnt_scan == 5'd12) cnt_scan <= 1'b0;
else cnt_scan <= cnt_scan + 1'b1;
case(cnt_scan)
5'd00: begin oled_dcn <= CMD; char_reg <= y_p; state <= WRITE; state_back <= SCAN; end //定位列页地址
5'd01: begin oled_dcn <= CMD; char_reg <= x_pl; state <= WRITE; state_back <= SCAN; end //定位行地址低位
5'd02: begin oled_dcn <= CMD; char_reg <= x_ph; state <= WRITE; state_back <= SCAN; end //定位行地址高位
5'd03: begin num <= num - 1'b1;end
5'd04: begin oled_dcn <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end //将5*8点阵转换为8*8显示
5'd05: begin oled_dcn <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end //将5*8点阵转换为8*8显示
5'd06: begin oled_dcn <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end //将5*8点阵转换为8*8显示
5'd07: begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][39:32]; state <= WRITE; state_back <= SCAN; end
5'd08: begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][31:24]; state <= WRITE; state_back <= SCAN; end
5'd09: begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][23:16]; state <= WRITE; state_back <= SCAN; end
5'd10: begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][15: 8]; state <= WRITE; state_back <= SCAN; end
5'd11: begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][ 7: 0]; state <= WRITE; state_back <= SCAN; end
5'd12: begin state <= MAIN; end
default: state <= IDLE;
endcase
end
WRITE:begin
if(cnt_write >= 5'd17) cnt_write <= 1'b0;
else cnt_write <= cnt_write + 1'b1;
case(cnt_write)
5'd00: begin oled_csn <= LOW; end //9位数据最高位为命令数据控制位
5'd01: begin oled_clk <= LOW; oled_dat <= char_reg[7]; end //先发高位数据
5'd02: begin oled_clk <= HIGH; end
5'd03: begin oled_clk <= LOW; oled_dat <= char_reg[6]; end
5'd04: begin oled_clk <= HIGH; end
5'd05: begin oled_clk <= LOW; oled_dat <= char_reg[5]; end
5'd06: begin oled_clk <= HIGH; end
5'd07: begin oled_clk <= LOW; oled_dat <= char_reg[4]; end
5'd08: begin oled_clk <= HIGH; end
5'd09: begin oled_clk <= LOW; oled_dat <= char_reg[3]; end
5'd10: begin oled_clk <= HIGH; end
5'd11: begin oled_clk <= LOW; oled_dat <= char_reg[2]; end
5'd12: begin oled_clk <= HIGH; end
5'd13: begin oled_clk <= LOW; oled_dat <= char_reg[1]; end
5'd14: begin oled_clk <= HIGH; end
5'd15: begin oled_clk <= LOW; oled_dat <= char_reg[0]; end //后发低位数据
5'd16: begin oled_clk <= HIGH; end
5'd17: begin oled_csn <= HIGH; state <= DELAY; end //
default: state <= IDLE;
endcase
end
DELAY:begin
if(cnt_delay >= num_delay) begin
cnt_delay <= 16'd0; state <= state_back;
end else cnt_delay <= cnt_delay + 1'b1;
end
default:state <= IDLE;
endcase
end
end
代码中的state_back指定了当前状态机在运行结束后返回到哪个状态,便于管理状态机的切换。
程序系统上电后进入Idle状态,随后进入Main状态进行OLED初始化和显示逻辑管理。
Init状态实现OLED屏幕的初始化,扩展板上的OLED屏幕的初始化指令序列如下:
//OLED初始化配置指令数据
always@(posedge rst_n)
begin
cmd[0 ] = {8'hae}; //--turn off oled panel
cmd[1 ] = {8'hd5}; //set display clock divide ratio/oscillator frequency
cmd[2 ] = {8'h80}; //--set divide ratio, Set Clock as 100 Frames/Sec
cmd[3 ] = {8'ha8}; //--set multiplex ratio(1 to 64)
cmd[4 ] = {8'h3f}; //--1/64 duty
cmd[5 ] = {8'hd3}; //-set display offset Shift Mapping RAM Counter (0x00~0x3F)
cmd[6 ] = {8'h00}; //-not offset
cmd[7 ] = {8'h40}; //--set start line address Set Mapping RAM Display Start Line
cmd[8 ] = {8'h8d}; //--set Charge Pump enable/disable
cmd[9 ] = {8'h14}; //--set(0x10) disable
cmd[10] = {8'h20}; //-Set Page Addressing Mode (0x00/0x01/0x02)
cmd[11] = {8'h02}; //
cmd[12] = {8'hc8}; //Set COM/Row Scan Direction 0xc0上下反置 0xc8正常
cmd[13] = {8'ha1}; //--Set SEG/Column Mapping 0xa0左右反置 0xa1正常
cmd[14] = {8'hda}; //--set com pins hardware configuration
cmd[15] = {8'h12}; //
cmd[16] = {8'h81}; //--set contrast control register
cmd[17] = {8'hcf}; // Set SEG Output Current Brightness
cmd[18] = {8'hd9}; //--set pre-charge period
cmd[19] = {8'hf1}; //Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
cmd[20] = {8'hdb}; //--set vcomh
cmd[21] = {8'h40}; //Set VCOM Deselect Level
cmd[22] = {8'haf}; //--turn on oled panel
end
在Init状态中,通过控制OLED的RST引脚电平和Delay状态机实现对OLED的复位,随后通过设定state_back=Init,跳转到Write状态和Dealy状态(实现SPI通信时序),向OLED发送配置数据。在配置完成后,跳转回Main状态。
Main状态主要负责管理刷新到OLED屏幕的字符内容和显示的位置,确定要刷新的字符后,通过state_back=Main,跳转到Scan状态将字符数据发送到OLED屏幕。
Scan状态通过查找字库中字符对应的点阵信息,确定发送的数据,在通过Write和Delay状态将数据发送出去。
OLED屏幕的驱动过程大致如上。
1.3 OLED模块与DDS模块相结合
之前实现的DDS模块的控制了波形的类型、频率以及幅度三个参数,通过将这三个控制信号发送到OLED模块中,并通过对信号的判断,改变显示在OLED屏幕上的信息。OLED模块的定义如下:
module oeld_show(
clk_oled,
rst_n,
wave_type,
wave_freqm,
wave_amp,
oled_csn,
oled_rst,
oled_dcn,
oled_clk,
oled_dat);
input clk_oled;
input rst_n;
input [2:0] wave_type;
input [27:0] wave_freqm;
input [7:0] wave_amp;
output reg oled_csn;
output reg oled_rst;
output reg oled_dcn;
output reg oled_clk;
output reg oled_dat;
对信号类型的判断,以及字符串更新程序如下:
reg [(3*8-1):0] dis_type_buff; //显示波形类型
always@(posedge clk_oled or negedge rst_n)begin
if(!rst_n)begin
dis_type_buff <= "Sin";
end else begin
case(wave_type)
3'd0: dis_type_buff <= "Sin";
3'd1: dis_type_buff <= "Squ";
3'd2: dis_type_buff <= "Trg";
3'd3: dis_type_buff <= "Saw";
endcase
end
end
虽然从逻辑上来说按照上述流程是可以实现信息的更新的,但是在调试的时候出现了一些意外,程序运行效果如视频所示:
动态刷新失败
屏幕信息显示错误的同时,DDS模块的运行也出现了问题,具体原因没有确定,问题应该是模块设计问题,我在其他的FPGA芯片上也尝试了,出现了相同的问题,就算降低OLED的驱动频率也不行。
1.4 总结
OLED的驱动模块虽然可以显示静态的文字,但是在动态更新时出现了问题,这一问题有待解决。
|