【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验二十:SDRAM模块③ — 页读写 α
实验二十:SDRAM模块③ — 页读写 α
完成单字读写与多字读写以后,接下来我们要实验页读写。丑话当前,实验二十的页读写只是实验性质的东西,其中不存在任何实用价值,笔者希望读者可以把它当成页读写的热身运动。
表示20.1 Mode Register的内容。
Mode Register |
||||||||||||
A12 |
A11 |
A10 |
A9 |
A8 |
A7 |
A6 |
A5 |
A4 |
A3 |
A2 |
A1 |
A0 |
0 |
0 |
OP Code |
0 |
0 |
CAS Latency |
BT |
Burst Length |
|
|
||||||||||||||||||||||||||||||||||||||
|
|
所谓页读写就是全列读写,而且表20.1告诉我们,页读写必须将 A2~A0设置为3’b111。然而,Verilog的描述结果如代码20.1所示:
7: // Send LMR Cmd. Burst Read & Write, 3'b010 mean CAS latecy = 3, Sequential,Full Page
begin rCMD <= _LMR; rBA <= 2'b11; rA <= { 3'd0, 1'b0, 2'd0, 3'b011, 1'b0, 3'b111 }; i <= i + 1'b1; end
代码20.1
如果我们一页一页的叫,基本上“一页”的定义是非常暧昧的,因为“一页”所指定的范围会随着该存储器的容量而有所改变。举例HY57V2562GTR 这只SDRAM,地址的指定范围有 BA1~BA0,R12~R0,C8~C0,其中“一页”是全列,亦即C8~C0。根据计算,C8~C0等价29 = 512,或者说页读写有512的地址偏移量。
页写操作:
图20.1 页写操作的理想时序图。
图20.1是笔者自定义的页写操作的理想时序图,其中C1是为了控制读写的次数。页读写相较字读写,前者好比一只不会停下冲锋的山猪。一旦读写开始,SDRAM内部的计数器就会从0开始计数,计数结果为511又会从0重新计数。因为如此,页读写需要利用BSTP命令禁止山猪继续冲锋。
此外,自动预充对页读写来说是无效的东西,因此A10拉不拉高都没有关系,而且页写操作也不需要满足 TWR/TDPL与TPR。图20.1大致的时序过程如下:
l T1,发送ACT命令,BANK地址与行地址;
l T1半周期,SDRAM读取;
l T2,满足TRCD;
l T3,发送WR命令,BANK地址与列地址,还有第0数据;
l T3半周期,SDRAM读取
l T4,发送第1~511数据,然后发送BSTP命令结束页写。
Verilog则可以这样描述,结果如代码20.2所示:
1. 1: // Send Active Command with Bank and Row address
2. begin rCMD <= _ACT; rBA <= iAddr[23:22]; rA <= iAddr[21:9]; i <= i + 1'b1; end
3.
4. 2: // wait TRCD 20ns
5. if( C1 == TRCD -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
6. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
7.
8. 3: // Send Write command with row address
9. begin rCMD <= _WR; rBA <= iAddr[23:22]; rA <= { 4'b0010, iAddr[8:0] }; D1 <= iData; i <= i + 1'b1; end
10.
11. 4: // continue write until end and send BSTP
12. if( C1 == 512 -1 ) begin rCMD <= _BSTP; C1 <= 14'd0; i <= i + 1'b1 ;end
13. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; D1 <= D1 + 1'b1; end
代码20.2
如代码20.2所示,步骤3写第0数据,步骤4则写入第1~511数据并且发送 BSTP命令。
页读操作:
图20.2 页读操作的理想时序图。
图20.2也是笔者自定义的理想时序图。同样,页读操也是一只不断冲锋的山猪,因此它需要BSTP这支停下的告示牌。除此之外,页读也没有自行预充电的必要,而且TPR也不用满足。实验二十要实验的页读比较单纯,我们读取第0数据以后立即发送BSTP命令来结束也操作。图20.2大致的时序过程如下:
l T1,发送ACT命令,BANK地址与行地址;
l T1半周期,SDRAM读取;
l T2,满足TRCD;
l T3,发送RD命令,BANK地址与列地址;
l T3半周期,SDRAM读取命令。
l T4,满足 CAS Latency。
l T5,读取第0数据,然后发送BSTP命令。
Verilog则可以这样描述,结果如代码20.3所示:
1. 1: // Send Active command with Bank and Row address
2. begin rCMD <= _ACT; rBA <= iAddr[23:22]; rA <= iAddr[21:9]; i <= i + 1'b1; end
3.
4. 2: // wait TRCD 20ns
5. if( C1 == TRCD -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
6. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
7.
8. 3: // Send Read command and column address
9. begin rCMD <= _RD; rBA <= iAddr[23:22]; rA <= { 4'b0010, iAddr[8:0]}; i <= i + 1'b1; end
10.
11. 4: // wait CL 3 clock
12. if( C1 == CL -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
13. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
14.
15. 5: // Read Data
16. begin D1 <= S_DQ; rCMD <= _BSTP; i <= i + 1'b1; end
代码20.3
如代码20.3所示,步骤5读取数据以后立即发送 BSTP命令以示结束页读操作。理解完毕以后我们便可以开始建模了。
图20.3 SDRAM基础模块的建模图。
图20.3是SDRAM基础模块的建模图,外表上和实验十八差不多,不过SDRAM功能模块的内容却有一些改变。
sdram_funcmod.v
1. module sdram_funcmod
2. (
3. input CLOCK,
4. input RESET,
5.
6. output S_CKE, S_NCS, S_NRAS, S_NCAS, S_NWE,
7. output [1:0]S_BA,
8. output [12:0]S_A,
9. output [1:0]S_DQM,
10. inout [15:0]S_DQ,
11.
12. input [3:0]iCall,
13. output oDone,
14. input [23:0]iAddr, // [23:22]BA,[21:9]Row,[8:0]Column
15. input [15:0]iData,
16. output [15:0]oData
17. );
第3~16行是相关的输入端声明。
18. parameter T100US = 14'd13300;
19. // tRP 20ns, tRRC 63ns, tRCD 20ns, tMRD 2CLK, tWR/tDPL 2CLK, CAS Latency 3CLK
20. parameter TRP = 14'd3, TRRC = 14'd9, TMRD = 14'd2, TRCD = 14'd3, TWR = 14'd2, CL = 14'd3;
21. parameter _INIT = 5'b01111, _NOP = 5'b10111, _ACT = 5'b10011, _RD = 5'b10101, _WR = 5'b10100,
22. _BSTP = 5'b10110, _PR = 5'b10010, _AR = 5'b10001, _LMR = 5'b10000;
23.
第18~22行是相关的常量声明。
24. reg [4:0]i;
25. reg [13:0]C1;
26. reg [15:0]D1;
27. reg [4:0]rCMD;
28. reg [1:0]rBA;
29. reg [12:0]rA;
30. reg [1:0]rDQM;
31. reg isOut;
32. reg isDone;
33.
34. always @ ( posedge CLOCK or negedge RESET )
35. if( !RESET )
36. begin
37. i <= 4'd0;
38. C1 <= 14'd0;
39. D1 <= 16'd0;
40. rCMD <= _NOP;
41. rBA <= 2'b11;
42. rA <= 13'h1fff;
43. rDQM <= 2'b00;
44. isOut <= 1'b1;
45. isDone <= 1'b0;
46. end
第24~46行是相关的寄存器声明与复位操作。
47. else if( iCall[3] )
48. case( i )
49.
50. 0: // Set IO to output State
51. begin isOut <= 1'b1; i <= i + 1'b1; end
52.
53. 1: // Send Active Command with Bank and Row address
54. begin rCMD <= _ACT; rBA <= iAddr[23:22]; rA <= iAddr[21:9]; i <= i + 1'b1; end
55.
56. 2: // wait TRCD 20ns
57. if( C1 == TRCD -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
58. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
59.
60. /*********************************************/
61.
62. 3: // Send Write command with row address
63. begin rCMD <= _WR; rBA <= iAddr[23:22]; rA <= { 4'b0010, iAddr[8:0] }; D1 <= iData; i <= i + 1'b1; end
64.
65. 4: // continue write until end and send BSTP
66. if( C1 == 512 -1 ) begin rCMD <= _BSTP; C1 <= 14'd0; i <= i + 1'b1 ;end
67. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; D1 <= D1 + 1'b1; end
68.
69. /**********************************************/
70.
71. 5: // Generate done signal
72. begin rCMD <= _NOP; isDone <= 1'b1; i <= i + 1'b1; end
73.
74. 6:
75. begin isDone <= 1'b0; i <= 4'd0; end
76.
77. endcase
以上内容为页写操作,注意步骤3写入第0数据,步骤4则写入第1~511数据并且发送BSTP命令。
78. else if( iCall[2] )
79. case( i )
80.
81. 0:
82. begin isOut <= 1'b0; D1 <= 16'd0; i <= i + 1'b1; end
83.
84. 1: // Send Active command with Bank and Row address
85. begin rCMD <= _ACT; rBA <= iAddr[23:22]; rA <= iAddr[21:9]; i <= i + 1'b1; end
86.
87. 2: // wait TRCD 20ns
88. if( C1 == TRCD -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
89. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
90.
91. /********************/
92.
93. 3: // Send Read command and column address
94. begin rCMD <= _RD; rBA <= iAddr[23:22]; rA <= { 4'b0010, iAddr[8:0]}; i <= i + 1'b1; end
95.
96. 4: // wait CL 3 clock
97. if( C1 == CL -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
98. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
99.
100. /********************/
101.
102. 5: // Read Data
103. begin D1 <= S_DQ; rCMD <= _BSTP; i <= i + 1'b1; end
104.
105. /********************/
106.
107. 6: // Generate done signal
108. begin rCMD <= _NOP; isDone <= 1'b1; i <= i + 1'b1; end
109.
110. 7:
111. begin isDone <= 1'b0; i <= 4'd0; end
112.
113. endcase
以上内容为页读操作,注意步骤5是读取第0数据并且发送 BSTP命令。
114. else if( iCall[1] )
115. case( i )
116.
117. 0: // Send Precharge Command
118. begin rCMD <= _PR; i <= i + 1'b1; end
119.
120. 1: // wait TRP 20ns
121. if( C1 == TRP -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
122. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
123.
124. 2: // Send Auto Refresh Command
125. begin rCMD <= _AR; i <= i + 1'b1; end
126.
127. 3: // wait TRRC 63ns
128. if( C1 == TRRC -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
129. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
130.
131. 4: // Send Auto Refresh Command
132. begin rCMD <= _AR; i <= i + 1'b1; end
133.
134. 5: // wait TRRC 63ns
135. if( C1 == TRRC -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
136. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
137.
138. /********************/
139.
140. 6: // Generate done signal
141. begin isDone <= 1'b1; i <= i + 1'b1; end
142.
143. 7:
144. begin isDone <= 1'b0; i <= 4'd0; end
145.
146. endcase
以上内容是刷新操作。
147. else if( iCall[0] )
148. case( i )
149.
150. 0: // delay 100us
151. if( C1 == T100US -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
152. else begin C1 <= C1 + 1'b1; end
153.
154. /********************/
155.
156. 1: // Send Precharge Command
157. begin rCMD <= _PR; { rBA, rA } <= 15'h3fff; i <= i + 1'b1; end
158.
159. 2: // wait TRP 20ns
160. if( C1 == TRP -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
161. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
162.
163. 3: // Send Auto Refresh Command
164. begin rCMD <= _AR; i <= i + 1'b1; end
165.
166. 4: // wait TRRC 63ns
167. if( C1 == TRRC -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
168. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
169.
170. 5: // Send Auto Refresh Command
171. begin rCMD <= _AR; i <= i + 1'b1; end
172.
173. 6: // wait TRRC 63ns
174. if( C1 == TRRC -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
175. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
176.
177. /********************/
178.
179. 7: // Send LMR Cmd. Burst Read & Write, 3'b010 mean CAS latecy = 3, Sequential,Full Page
180. begin rCMD <= _LMR; rBA <= 2'b11; rA <= { 3'd0, 1'b0, 2'd0, 3'b011, 1'b0, 3'b111 }; i <= i + 1'b1; end
181.
182. 8: // Send 2 nop CLK for tMRD
183. if( C1 == TMRD -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
184. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
185.
186. /********************/
187.
188. 9: // Generate done signal
189. begin isDone <= 1'b1; i <= i + 1'b1; end
190.
191. 10:
192. begin isDone <= 1'b0; i <= 4'd0; end
193.
194. endcase
195.
以上内容是初始化,注意步骤7的Mode Register 内容,Busrt Length 为 3’b111。
196. assign { S_CKE, S_NCS, S_NRAS, S_NCAS, S_NWE } = rCMD;
197. assign { S_BA, S_A } = { rBA, rA };
198. assign S_DQM = rDQM;
199. assign S_DQ = isOut ? D1 : 16'hzzzz;
200. assign oDone = isDone;
201. assign oData = D1;
202.
203. endmodule
第196~201行是相关的输出驱动。
sdram_ctrlmod.v
该控制模块的内容与实验十八一致。
sdram_basemod.v
该组合模块的内容也与实验十八一致
sdram_demo.v
图20.4 实验二十的建模图。
图20.4是实验二十的建模图,外观上与实验十八一样,不过核心操作的内容却有所不同,具体内容我们还是来看代码吧。
1. module sdram_demo
2. (
3. input CLOCK,
4. input RESET,
5. output S_CLK,
6. output S_CKE, S_NCS, S_NRAS, S_NCAS, S_NWE,
7. output [12:0]S_A,
8. output [1:0]S_BA,
9. output [1:0]S_DQM,
10. inout [15:0]S_DQ,
11. output TXD
12. );
以上内容为相关的出入端声明。
13. wire CLOCK1,CLOCK2;
14.
15. pll_module U1
16. (
17. .inclk0 ( CLOCK ), // 50Mhz
18. .c0 ( CLOCK1 ), // 133Mhz -210 degree phase
19. .c1 ( CLOCK2 ) // 133Mhz
20. );
21.
以上内容为PLL模块的实例化。
22. wire [1:0]DoneU2;
23. wire [15:0]DataU2;
24.
25. sdram_basemod U2
26. (
27. .CLOCK( CLOCK1 ),
28. .RESET( RESET ),
29. .S_CKE( S_CKE ),
30. .S_NCS( S_NCS ),
31. .S_NRAS( S_NRAS ),
32. .S_NCAS( S_NCAS ),
33. .S_NWE( S_NWE ),
34. .S_A( S_A ),
35. .S_BA( S_BA ),
36. .S_DQM( S_DQM ),
37. .S_DQ( S_DQ ),
38. .iCall( isCall ),
39. .oDone( DoneU2 ),
40. .iAddr( D1 ),
41. .iData( D2 ),
42. .oData( DataU2 )
43. );
44.
以上内容为SDRAM基础模块的实例化。
45. parameter B115K2 = 11'd1157, TXFUNC = 6'd16;
46.
47. reg [5:0]i,Go;
48. reg [10:0]C1;
49. reg [23:0]D1;
50. reg [15:0]D2,D3;
51. reg [10:0]T;
52. reg [1:0]isCall;
53. reg rTXD;
54.
55. always @ ( posedge CLOCK1 or negedge RESET )
56. if( !RESET )
57. begin
58. i <= 6'd0;
59. Go <= 6'd0;
60. C1 <= 11'd0;
61. D1 <= 24'd0;
62. D2 <= 16'd0;
63. D3 <= 16'd0;
64. T <= 11'd0;
65. isCall <= 2'b00;
66. rTXD <= 1'b1;
67. end
68. else
以上内容为相关的寄存器声明还有复位操作。第45行是波特率还有伪函数入口的常量声明。
69. case( i )
70.
71. 0:
72. if( DoneU2[1] ) begin isCall[1] <= 1'b0; i <= i + 1'b1; end
73. else begin isCall[1] <= 1'b1; D1 <= 24'd0; D2 <= 16'hA000; end
74.
75. 1:
76. if( DoneU2[0] ) begin D3 <= DataU2; isCall[0] <= 1'b0; i <= i + 1'b1; end
77. else begin isCall[0] <= 1'b1; end
78.
79. 2:
80. begin T <= { 2'b11, D3[15:8], 1'b0 }; i <= TXFUNC; Go <= i + 1'b1; end
81.
82. 3:
83. begin T <= { 2'b11, D3[7:0], 1'b0 }; i <= TXFUNC; Go <= i + 1'b1; end
84.
85. 4:
86. if( D1 == 24'd511 ) i <= i + 1'b1;
87. else begin D1 <= D1 + 1'b1; i <= 6'd1; end
88.
89. 5:
90. i <= i;
91.
92. /******************************/
93.
以上内容为部分核心操作。步骤0将数据 16’hA×××从地址0写至地址511,其中×××会经由页写而自行递增。换句话说,数据16’hA000~16’hA1FF从地址0写至地址511。
步骤1则用来读取数据,步骤2~3将读出的数据一一发送出去。步骤4用来递增地址,从0~511,然后返回步骤1,直至人为页读结束。
94. 16,17,18,19,20,21,22,23,24,25,26:
95. if( C1 == B115K2 -1 ) begin C1 <= 11'd0; i <= i + 1'b1; end
96. else begin rTXD <= T[i - 16]; C1 <= C1 + 1'b1; end
97.
98. 27:
99. i <= Go;
100.
101. endcase
102.
103. assign S_CLK = CLOCK2;
104. assign TXD = rTXD;
105.
106. endmodule
以上内容为部分核心操作。步骤16~27是发送一帧数据的伪函数。第103~104行则是相关的输出驱动。综合完毕并且下载程序,如果串口调试软件出现数据 A000~A1FF表示实验成功。
【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验二十:SDRAM模块③ — 页读写 α的更多相关文章
- [黑金原创教程] FPGA那些事儿《设计篇 III》- 图像处理前夕·再续
简介 一本为入门图像处理的入门书,另外还教你徒手搭建平台(片上系统),内容请看目录. 注意 为了达到最好的实验的结果,请准备以下硬件. AX301开发板, OV7670摄像模块, VGA接口显示器, ...
- [黑金原创教程] FPGA那些事儿《设计篇 II》- 图像处理前夕·续
简介 一本为入门图像处理的入门书,另外还教你徒手搭建平台(片上系统),内容请看目录. 注意 为了达到最好的实验的结果,请准备以下硬件. AX301开发板, OV7670摄像模块, VGA接口显示器, ...
- [黑金原创教程] FPGA那些事儿《设计篇 I》- 图像处理前夕
简介 一本为入门图像处理的入门书,另外还教你徒手搭建平台(片上系统),内容请看目录. 注意 为了达到最好的实验的结果,请准备以下硬件. AX301开发板, OV7670摄像模块, VGA接口显示器, ...
- [黑金原创教程] FPGA那些事儿《数学篇》- CORDIC 算法
简介 一本为完善<设计篇>的书,教你CORDIC算法以及定点数等,内容请看目录. 贴士 这本教程难度略高,请先用<时序篇>垫底. 目录 Experiment 01:认识CORD ...
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】原创教程连载导读【连载完成,共二十九章】
前言: 无数昼夜的来回轮替以后,这本<驱动篇I>终于编辑完毕了,笔者真的感动到连鼻涕也流下来.所谓驱动就是认识硬件,还有前期建模.虽然<驱动篇I>的硬件都是我们熟悉的老友记,例 ...
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】连载导读
前言: 无数昼夜的来回轮替以后,这本<驱动篇I>终于编辑完毕了,笔者真的感动到连鼻涕也流下来.所谓驱动就是认识硬件,还有前期建模.虽然<驱动篇I>的硬件都是我们熟悉的老友记,例 ...
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验二:按键模块① - 消抖
实验二:按键模块① - 消抖 按键消抖实验可谓是经典中的经典,按键消抖实验虽曾在<建模篇>出现过,而且还惹来一堆麻烦.事实上,笔者这是在刁难各位同学,好让对方的惯性思维短路一下,但是惨遭口 ...
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验十八:SDRAM模块① — 单字读写
实验十八:SDRAM模块① — 单字读写 笔者与SDRAM有段不短的孽缘,它作为冤魂日夜不断纠缠笔者.笔者尝试过许多方法将其退散,不过屡试屡败的笔者,最终心情像橘子一样橙.<整合篇>之际, ...
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验七:PS/2模块① — 键盘
实验七:PS/2模块① — 键盘 实验七依然也是熟烂的PS/2键盘.相较<建模篇>的PS/2键盘实验,实验七实除了实现基本的驱动以外,我们还要深入解PS/2时序,还有PS/2键盘的行为.不 ...
随机推荐
- LNMP架构介绍 MySQL安装 PHP安装 Nginx介绍
- linux echo命令提示权限不够的解决办法
该文章转载于此:http://blog.csdn.net/u010780613/article/details/51491237 问题描述: 在使用Ubuntu系统的时候,遇到这样的一个问题 ...
- 近5年常考Java面试题及答案整理(一)
下列面试题都是在网上收集的,本人抱着学习的态度找了下参考答案,有不足的地方还请指正. 1.面向对象的特征有哪些方面? 抽象:将同类对象的共同特征提取出来构造类. 继承:基于基类创建新类. 封装:将数据 ...
- 妙味远程课堂-JS属性
html由属性名和属性值组成 属性读操作:获取.找到 元素.innerHtml//读取元素内的html内容 元素.属性名 案例1:点击按钮弹出文本框的内容(value值) <!DOCTYPE h ...
- workerman定时器使用 php定时任务
add int \Workerman\Lib\Timer::add(float $time_interval, callable $callback [,$args = array(), bool $ ...
- [原]unity3d调用android webView
1.配置dialog xml文件: <resources> <style name="dialogStyleWindow" parent="@an ...
- Android 程序打包及签名(转)
为什么要签名??? 开发Android的人这么多,完全有可能大家都把类名,包名起成了一个同样的名字,这时候如何区分?签名这时候就是起区分作用的. 由于开发商可能通过使用相同的Package Name来 ...
- Git Step by Step – (2) 本地Repo
前面一篇文章简单介绍了Git,并前在Windows平台上搭建了Git环境,现在就正式的Git使用了. Git基本概念 在开始Git的使用之前,需要先介绍一些概念,通过这些概念对Git有些基本的认识,这 ...
- yii 前端js动态添加验证规则
在使用 activeForm 生成表单及验证时,默认是按照 model 里的 rules 生成js验证,model 验证在加载完页面后生效,不可修改,如果需要扩展.动态验证,需要使用js来配合 直接上 ...
- iOS开发--tarBarItem右上方显示badgeValue
直接设置tabBarItem.badgeValue没有效果,找原因半天发现ViewController被NavigationViewController包着 需这样设置才行: self.navigat ...