单片机的IO的三种状态分别是开漏、推挽、高阻态如图
准双向IO口前面的矩阵按键博文已经有所叙述。
开漏输出与准双向IO口的区别就是开漏输出把内部的上拉电阻去掉了。开漏输出如果要输出高电平,T2(相当于NPN三极管)mos管要关断,IO的电平要靠外部的上拉电阻才能拉成高电平。如果没有外部上拉电阻IO电平就是一个不确定态。
标准51单片机的P0口查数据手册默认就是开漏输出。
我们看到P1-P4是准双向口且是弱上拉,这个弱上拉的意思是上拉电阻比较大电流偏小比如50K
同理强上拉的意思就是上拉电阻较小电流偏大比如50R,甚至你不要这个电阻直接接电源就是强上拉。
(T1相当于PNP三极管,T2相当于NPN三极管)
强推挽输出就是有比较强的驱动能力,如果当内部输出一个高电平时,通过T1 mos管直接输出电流,没有电阻的限流,电流输出能力也比较大;如果内部输出一个低电平,那反向电流(T2导通IO口直接接地)也可以很大,强推挽的特点就是驱动能力强。
单片机IO还有一种状态叫高阻态。通常用来做输入引脚的时候,可以将IO口设置成高组态,高阻态引脚本身如果悬空,用万用表测量电压由可能是高也可能是低,它的状态完全取决于外部输入信号的电平,高阻态引脚对GND的等效电阻很大(理论上是无穷大,但又是有限值而非无穷大)。
上下拉电阻
IO口设置为开漏输出高电平或者高阻态时,默认的电平就是不确定的,外部经过一个电阻接到VCC,也就是上拉电阻,那么相应的引脚就是高电平。同理经过一个电阻到GND,也就是下拉电阻,那么引脚就是一个低电平。
上拉电阻的应用
(1)OC/OD(Open Collector集电极开路,Open Drain漏极开路)门要输出高电平,必须外部加上拉电阻才能使用,其实就相当于单片机IO口的开漏输出
(2)加大普通IO口的驱动能力,标准51单片机的IO口的上拉电阻一般都是几十KΩ,STC89C52内部的上拉电阻是20K。以5V电平来说最大的输出电流是250微安,因此外部加个上拉电阻,可以形成和内部上拉电阻的并联结构,增大高电平时电流的输出能力。
(3)在电平转换电路中,起到限流电阻的作用
(4)单片机中未使用的引脚,比如总线引脚和引脚悬空时,容易受到电磁干扰而处于紊乱状态,虽然不会对程序照成什么影响,但通常会增加单片机的功耗,加上一个对VCC的上拉电阻或者一个对GND的下拉电阻,可以有效的抵抗电磁干扰。
上下拉电阻的选择
(1)从降低功耗的方面考虑应当足够大,因为电阻越大,电流越小。
(2)从确保足够的引脚驱动能力考虑应当足够小,电阻小了,电流才能大。
(3)在开漏输出时,过大的上拉电阻会导致信号上升沿变缓。如图
当然再理想的上升沿都不可能垂直向上,只能是近似,即上升的时间虽小但永远都达不到零。
综合考虑上下拉电阻的取值大多在1K~10KΩ之间。具体根据实际情况选择。
28BYJ-48型步进电机
步进电机分为反应式、永磁式和混合式三种。
(1)反应式步进电机:结构简单成本低,但是动态性能差效率低,发热大,可靠性难以保证,基本淘汰。
(2)永磁式步进电机:动态性能好、输出力矩较大、但误差相对来说大一些,因其价格低而广泛用于消费性产品。
(3)混合式步进电机:力矩大、动态性能好、步距角度小、精度高、但结构相对来说复杂,价格也相对较高,主要应用于工业。
28BYJ-48各参数的具体意思。
28——步进电机的有效最大外径是28毫米
B——表示是步进电机
Y——表示是永磁式
J——表示是减速型
48——表示四相八拍
然后是它的原理图。
如果只看书,没拆过电机内部结构。估计对于教材书上的步距脚360°/(8*4) =11.25度怎么计算出来,有点纳闷甚至还要自己脑补。笔者的教材也没有具体的说明电机内部的机械结构,当然示意图仅在功能上确实是没有问题的,但是步进脚距这么重要数据就这么糊里糊涂的得出结果确实令人寒心,我不知道现在学生的教学质量怎么样,回顾笔者的求学经历,很多课程教材不说一塌糊涂更是男默女泪。对于山区的或者贫困的孩子他们大多只有一本教材没法看到实物,没法实践。这种弄出啦就是误人子弟。
就以笔者来说,不怕各位笑话,在我小的时候关于变压器和通电线圈的铜线它们就这么绕在一起,为什么电流还是沿着导线流的吗?电流的流向不应该四面八方360度的流的吗?它们都碰到一起了,他们不是导体吗?为什么老师一直说沿着导线流的?直到大学毕业我都没搞定这个问题?这种原则性问题搞不清楚,搞不通透可想而知我这门课学的稀巴烂,当然我现在知道了,因为外面有一层绝缘漆。它是漆包线它不是裸铜线,漆包线和铜线的区别他们在讲课的时候就这么理所当然的当作是一样的吗!?
当然各位可能会问你为什么不去问老师啊? 问的好!这是非常有意思的问题!就如胖猫最后选择自杀他如果现在还活着若干年以后估计也就笑笑觉得自己是SB,现在的我不能帮以前的我来做选择。
诸君共勉
闲话不再多说,关于步进电机的讲解笔者推荐几个视频。
改了下博文增加了下磁场方向的内容然后审核就没被通过,之前都是好好的,提示是过度宣传也是无语。博文的一些图片来自这些视频所以链接一定要保留,这三个链接只好做成图片格式发一下,有兴趣的自己搜了。
最好三个视频都看一下每个视频都有所得综合下,得到一个笔者觉得是正确的说法。当然笔者手上有电机但没拆开看是否真如笔者所说,各位自行决定。(声明:下面博文里面的图片来自视频)
首先是它的定子结构
可以看到它的有很多小爪子,这些爪子都是导磁体,它的作用应该是类似铁芯的作用,一旦通电产生磁场,更多的磁力线就会往导磁体这边走。而且它是分两层的,上层线圈的分步16个爪极,上端8个下端8个爪极互相交错分布,同理下面的线包也是16个爪极。最后可知定子的爪极是32个。
用示意图表示就是
可以看到整个圆被分为了32段,可以清楚的看到它的步距是360/32 =11.25°,没有任何的花里胡哨的推理演算,它实际上就是这么朴实无华的看出来了。
然后就是它的接线,电机引出5根线除了一根电源线其他4根都是相线。通过示意图可知,每导通一相电,同一相的8个爪极都通电产生磁性,然后吸引中间的转子转动。可以这么理解但事实不是这样的简单。
先看一下它的线圈引线图
红线是正5V的电源接头,它也是两个线圈的中心抽头。1,3两个抽头是同一个线圈的,同理2, 4两个抽头是同一个线圈的。因为电源是在线圈中心抽头,如果同一线圈其他两个抽头分别接地的话它们产生的磁场是相反的。如图
也就是说对于该电机,如果A相电导通的话假设A相爪极是N极,那么同时C相的爪极就是S极,因为A、C相是同一个线圈的配合爪极。
如图
也就是只要有一相通电那么就有16个爪极有磁性。
同样的如果相邻两个相线通电的话比如AB相,那代表着上下线圈都有一半的绕组通了电,对于该电机来说就是A、B相爪极都是N极了C、D相爪极都是S极了,即36个爪极都有磁性了(应该是相应的爪极都有磁通线了)。
接着我们探讨下转子部分,转子是为圆柱形永久磁铁交替分布着N极和S级,极对数为8,即8个N极8个S级共16个磁极。且N极S极等极距。则可知每一磁极的极距为22.5°
如此定子和转子配合 如图示
现在来探讨下如果对步进电机每相通电步进电机的转动情况,假设A相通电A相是N极同时C相是S极则定子与转子配合如图,转子的带标记点的S磁极为观察磁极。
如果我们关闭A相导通B相则定子的磁极发生变化,AC相不在产生磁性,B相现在是N极同时D相是
S极。
可以看到带标记的转子S极从A相转到了B相这之间的步距是11.25°。以标记的转子S极来说它在转动过程(A转到B)中受到两股力的作用,一是B相的吸引力(向B靠近)二是D相的排斥力(推动离开A相向B靠近),当转到转距最小的位置时受力平衡转子将不再转动,此时转动的距离就是A相到B相的步距。
同样的C相通电,C相爪极是N极同时A相爪极是S极。
然后是D相
最后又回到A相
可以看到转子从起点A相经过4相通电后它又回到了A相,它转过的步距是 45度。
一步我们称为一节拍,刚才的操作有个名字叫
单向绕组通电4节拍,显然转一圈要32节拍。每步以及每节拍的步距就是360/32 =11.25 度。
再来讲解一种更优性能的工作模式,那就是在单四拍的每两个节拍之间再插入一个双绕组导通的中间节拍,组成8拍模式。即A -- AB -- B -- BC -- C -- CD -- D -- DA -- A
A相通电
AB相通电
B相通电
可以看到A相到AB相的步距是之前的一半即11.25/2=5.625度,AB相到B相的步距也是之前的一半即5.625度。 依此类推可知8拍也只转了45度,若想转一圈它的节拍数是8x8=64节拍。这样一来不仅使转动的精度增加了一倍,新增加的中间节拍还增加了电机的整体扭力输出,使电机更“有劲”了。一般来说我们都采用这种8节拍工作模式控制步进电机。
插一段:笔者后来思考了下觉得通电爪极那里说有可能点问题,当然按照之前的理解应该是没有什么大问题的。在此之前先回忆一下关于磁场线的一些知识:
1:磁场线是闭合的并且永不相交,两个磁场靠近他们的磁场线是不相交的,该点磁场的方向是它们合磁场的方向。
2:磁场中某点北极的方向是磁场中小磁针北极指的方向。因为目前未发现磁单极,因此磁场中的南北极是成对出现的。把磁力线北极指的方向加个箭头,那么上述的情况就可以描述为:磁场线的箭头(北极方向)从磁体(小磁针)南极穿入北极穿出。
那么之前磁场图示:
1:右手定则确定磁场大方向
2:爪极以及与爪极相连衬盖应该都是软磁材料,按照前文给出的结论。如图红色的爪极应是南极与之相连的上衬盖是北极(磁场线从磁体的南极穿入北极穿出)。蓝色的爪极是北极与之相连的衬底是南极(磁场线从磁体的南极穿入北极穿出)。正常情况下红色一般是指北极,所以科普视频爪极的南北极应该是搞错了。正确的应是如下图
然和我们看到两个线圈是同轴放置的,爪极和衬盖就相当于铁芯了。由于这种垂直角的铁芯结构,那么通电线圈产生的磁场线更多的是通过这个铁芯结构构成磁路,只有少部分的漏磁会通过过另一个线包。假设通电线圈使能的是AC爪极,那么漏磁也会使BD爪极产生磁场,只不过BD爪极磁性很小。所以这个结果是任意一相电通电都会使所有的爪极有磁性,只不过相邻另一个线包的爪极磁性会小很多。
铁、钴、镍等铁磁性物质内部的电子自旋可以在小范围内自发地排列起来,形成一个自发磁化区,即“磁畴”当这些物质被磁化后,内部的磁畴会整齐排列,使得整体表现出磁性,从而形成磁铁
这些磁畴可以被视为“微小的磁针”,它们的方向原本是随机的,但在外部磁场的作用下会趋向于同一方向。这种排列使该磁化物质能吸引或排斥其他物体
所以它体内小磁针方向如图:大致示意图
笔者学识有限以上都是笔者的推论并未经过实际测试可能存在错误。
统一下相线线颜色和相线爪极称呼。A(4)相橙色, B(3)相黄色 ,C(2)相粉色, D(1)相蓝色,
则它的控制顺序是
然后看一下该电机的其它参数;
可以看到启动频率是大于等于550,单位是P.P.S,即每秒脉冲数。电机保证在每秒给出550个步进脉冲的情况下可以正常启动。换算成单拍的持续时间就是1/550 = 1.8ms 按照逻辑来说启动频率是要大于550,那么持续时间取值应该是小于1.8ms。但这里的意思是大于1.8ms,我们就从实际出发,如果说它是要求的持续时间小于1.8ms,那如果把这个值取的非常的小无限趋于0,显然是不符和现实,笔者觉得这种书写方式可能是一种以前留下的写法,它本质应该是表达大于多少时间,但是在表示的时候又用了频率表示,可能是一种行业约定俗成。所以该数据是,P.P.S要小于等于550,启动时间要大于1.8ms。
本案在电机控制的时候使用了跳线
可以看到只要Q2导通,电机的相电线圈就导通接地,开始产生磁场。要想Q2导通MC0要输出低电平,而MC0就是P1.0端口。
根据实际硬件设计5p插头的2-5的电子线颜色是橙黄粉蓝即
根据之前的步进控制顺序它的步进控制编码是
BeatCode[8] = { 0xE,0xC,0xD,0x9,0xB,0x3,0x7,0x6 }; (A相)0xE = 1110 (AB相) 0xC=1100 (B相)0xD=1101依次类推。
上代码
# include<reg52.h>
unsigned long beats = 0;
void StartMotor(unsigned long angle);
void main()
{
EA = 1;
TMOD = 0x01;
TH0 = 0xFB; //定时2ms
TL0 = 0xCD;
ET0 = 1;
TR0 = 1;
StartMotor(360*25); //转25圈
while(1);
}
void StartMotor(unsigned long angle)
{
//
EA = 0;
beats = (angle*4096)/360;//计算步数
EA = 1;
}
void InterruptTimer0() interrupt 1
{
unsigned char tmp;
static unsigned char index = 0;
unsigned char code BeatCode[8] = {
0xE,0xC,0xD,0x9,0xB,0x3,0x7,0x6
};
TH0 = 0xFB;
TL0 = 0xCD;
if(beats != 0)
{
tmp = P1;
tmp = tmp & 0xF0;//保持高位不动,低位清0
tmp = tmp | BeatCode[index];//端口控制取值
P1 = tmp;
index++;
index = index & 0x07;
beats--;
}
else
{
P1 = P1 |0x0F; //当步数为0,4个端口置1,遂停止
}
}
步进电机转动_哔哩哔哩_bilibili电机转动示意
步进电机开始转动,25圈后它停止转动。前文说了该步进电机是个减速型步进电机减数比是
1:64,是因为里面有齿轮结构。如图
看过该步进电机教材应该都看过这个图,理论上转子转动64圈,电机轴才转动一圈,而转子转一圈要经过64拍,即4096拍步进电机转一圈。但是实际是不是这么理想的,根据齿轮的齿数计算是4076拍。即实际的转一圈的拍数是4076拍。因此程序计算拍数的算式要改动一下4096改成4076
beats = (angle*4076)/360;//计算步数,其实笔者也是倾向于这个式子的,但是事与愿违,笔者在测试的过程中发现4096才是正确的节拍,也就是是笔者手上的步进电机是准确的64:1不是近似值。
笔者经过多次测量结果皆是如此。初始位置
4096拍(25圈)
4076拍(25圈)
可以看到4076拍少转了φ角度。
当然笔者不保证各位读者手上的步进电机是否如此,但是笔者手上的步进电机是如此的,倘若是电机误差,那误差的步数也太凑巧了。
当然也许是步进电机的工艺进步了,电机已经能准确做到64比1了。这是个不错的猜想,倘若各位手上有电机最好自己都去试一下,而不是套用这个结论。
然后我们探讨一下如果单项绕组通电能否驱动电机工作
这是控制驱动的数组为BeatCode[4] = {0xE,0xD,0XB,0x7};即单向绕组通电4节拍,经过笔者测试电机在震动,但是无法带动电机转轴转动。有可能是4拍驱动的扭力不够,无法通过齿轮组带动电机轴转动。
同时发散一下思维,如果把原先的8拍删掉一个中间拍数即变成7拍,电机是否还能够转动?
经过测试无法带动电机,这个其实也是显而易见的,4节拍的扭力不够,8节拍抽掉一节拍,相当与其中一节拍的扭力与4节拍的一样,当该拍的扭力不够电机转子就无法带动齿轮转动就会一直卡住不动,因此电机一直在震动,电机转轴却不动。
前一篇博文我们用矩阵按键实现了简单的数学运算,这篇我们用矩阵按键来控制电机的转动。
数字1-9控制电机转的圈数。 向上键控制正转,向下键控制反转,向左键固定正转90°,向右键固定反转90°,ESC停止转动,空格键一直转动。
#include<reg52.h>
sbit KEY_IN_1 = P2^4;
sbit KEY_IN_2 = P2^5;
sbit KEY_IN_3 = P2^6;
sbit KEY_IN_4 = P2^7;
sbit KEY_OUT_1 = P2^3;
sbit KEY_OUT_2 = P2^2;
sbit KEY_OUT_3 = P2^1;
sbit KEY_OUT_4 = P2^0;
unsigned char code KeyCodeMap[4][4] = { //矩阵按键编号到标准键盘键码的映射表
{ 0x31,0x32,0x33,0x26}, //数字键1,数字键2,数字键3,向上键
{ 0x34,0x35,0x36,0x25}, //数字键4,数字键5,数字键6,向左键
{ 0x37,0x38,0x39,0x28}, //数字键7,数字键8,数字键9,向下键
{ 0x30,0x1B,0x0D,0x27} //数字键0,ESC键, 回车键, 向右键
};
unsigned char KeySta[4][4] = { //全部按键当前态
{1,1,1,1},
{1,1,1,1},
{1,1,1,1},
{1,1,1,1}
};
signed long beats = 0;
bit run = 0; //定义持续转动的标记
void KeyDriver(); //按键驱动函数声明
//void KeyAction(unsigned char keycode);
//void KeyScan();
void main()
{
EA = 1; //中断使能
TMOD = 0x01; //设置T0为模式1
TH0 = 0XFC; //定时1ms
TL0 = 0x67;
ET0 = 1; //启动定时器0中断
TR0 = 1;
while(1)
{
KeyDriver(); // 调用按键驱动函数
}
}
/*步进电机启动函数,angle为需转过的角度 */
void StartMotor(signed long angle)
{
//
EA = 0;
beats = (angle * 4096)/360;
EA = 1;
}
/*步进电机停止函数 */
void StopMotor()
{
EA = 0;
beats = 0;
EA = 1;
}
/*按键动作函数,根据键码执行相应的操作,keycode-按键键码 */
void KeyAction(unsigned char keycode)
{
static bit dirMotor = 0; //定义电机转动方向
if((keycode >= 0x30) &&(keycode <= 0x39))
{
if(dirMotor == 0)
StartMotor(360*(keycode-0x30));
else
StartMotor(-360*(keycode - 0x30));
}
else if (keycode == 0x26) //向上键,控制转动方向正转
{
dirMotor = 0;
}
else if (keycode ==0x28) //向下键,控制转动方向为反转
{
dirMotor = 1;
}
else if (keycode ==0x25) //左键,固定正转90度
{
StartMotor(90);
}
else if (keycode ==0x27) //向右键,固定反转90度
{
StartMotor(-90);
}
else if (keycode == 0x1B) //ESC 停止转动
{
StopMotor();
run = 0;
}
else if (keycode == 0x0D ) // 一直转动
{
if(dirMotor == 0)
StartMotor(90);
else
StartMotor(-90);
run = 1; //为1就一直转
}
}
/*按键驱动函数,检测按键动作,调度相应动作函数,需要在主循环中调用 */
void KeyDriver()
{
unsigned char i,j ;
static unsigned char PastSta[4][4] ={
{1,1,1,1}, //按键值备份,保存前一次的值(前一稳态)
{1,1,1,1},
{1,1,1,1},
{1,1,1,1}
};
for(i=0; i<4; i++) //循环检测4*4的矩阵按键,i是行 j是列
{
for(j=0; j<4; j++)
{
if(PastSta[i][j] != KeySta[i][j]) //检测按键动作
{
if(PastSta[i][j] != 0) //按键前一稳态不是0即按键前一稳态是1即现稳态是0,即按住开关触发
{
KeyAction(KeyCodeMap[i][j]); //调用按键动作函数
}
PastSta[i][j] = KeySta[i][j]; //把现稳态赋值给前态
}
}
}
}
/*按键动作扫描函数,需要定时中断调用,间隔1ms */
void KeyScan()
{
unsigned char i;
static unsigned char keyout = 0; //矩阵按键扫描输出索引
static unsigned char keybuf[4][4] = { //矩阵按键扫描缓冲区,16个建初始都是0xff则说明全部处于弹起状态
{0xFF,0xFF,0xFF,0xFF},
{0xFF,0xFF,0xFF,0xFF},
{0xFF,0xFF,0xFF,0xFF},
{0xFF,0xFF,0xFF,0xFF}
};
//将一行4个按键值移入缓冲区
keybuf[keyout][0] = (keybuf[keyout][0]<<1) | KEY_IN_1;
keybuf[keyout][1] = (keybuf[keyout][1]<<1) | KEY_IN_2;
keybuf[keyout][2] = (keybuf[keyout][2]<<1) | KEY_IN_3;
keybuf[keyout][3] = (keybuf[keyout][3]<<1) | KEY_IN_4;
//消抖后更新按键状态
for(i = 0; i<4; i++) //每行4个按键,所以循环4次
{
if((keybuf[keyout][i] & 0x0F) ==0x00)
{ //连续4次扫描值为0,即4*4ms内部都是按下的状态时,可以认为按键已稳定的按下
KeySta[keyout][i] = 0;
}
else if((keybuf[keyout][i] & 0x0F) ==0x0F)
{ //连续扫描4次扫描值为1,即4*4ms内部都是弹起状态,可认为按键已稳定的弹起
KeySta[keyout][i] = 1;
}
//else{}
}
keyout++; //输出值索引递增
keyout = keyout & 0x03; //索引值加到4即归零
switch(keyout) //根据索引,释放当前输出引脚,拉低下次的输出引脚
{
case 0:KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;
case 1:KEY_OUT_1 = 1; KEY_OUT_2 = 0; break;
case 2:KEY_OUT_2 = 1; KEY_OUT_3 = 0; break;
case 3:KEY_OUT_3 = 1; KEY_OUT_4 = 0; break;
default : break;
}
}
/*电机转动控制函数 */
void TurnMotor()
{
unsigned char tmp;
static unsigned char index = 0;
unsigned char code BeatCode[8] = {0xE,0xC,0xD,0x9,0xB,0x3,0x7,0x6};
if(beats != 0)
{
if(beats > 0)
{
index++;
index = index & 0x07; //用&操作实现到8归零
beats--;
if(run == 1) //如果按下空格则节拍数不变的即一直转下去
{
beats++;
}
}
else //节拍数小于0反转
{
index--;
index = index & 0x07; //用&操作同样实现-1时归7
beats++; //反转时节拍计数递增
if(run == 1)
{
beats--;
}
}
tmp = P1;
tmp = tmp & 0xF0;
tmp = tmp | BeatCode[index];
P1 = tmp;
}
else
{
P1 = P1 | 0x0F;
}
}
/* T0中断服务函数,用于数码管显示扫描与按键扫描 */
void interruptTimer0() interrupt 1
{
static bit div = 0;
TH0 = 0xFC; //重载初值
TL0 = 0x67;
KeyScan(); //执行按键扫描每1ms执行一次
div = ~div; //用1个静态bit变量实现二分频,即定时2ms
if(div == 1)
{
TurnMotor(); //每2ms执行一次函数控制
}
}
这里面几个要点解释一下
本案使用的51单片机,在操作数据时都是按8位即1个字节运行的,那么要操作多个字节(无论读还是写)就必须分多次进行了,程序中的beat的变量定义的是unsigned long型是4个字节,那么对它的赋值也要分4次完成,想象一下,假如在完成第一个字节的赋值后,恰好中断发生了,interrupt Timer0()函数得到执行,而这个函数可能会对beats进行减1操作,减法就有可能发生借位,借位就会改变其他的字节,但因为此时其他的字节还没有被赋入新值,于是错误发生了。
所以要避免这种错误的发生就得先暂时关闭中断。等赋值结束后再打开中断。 而如果使用的是char或者bit型变量的话,就不需要,因为它在CPU中是一次操作完成的,所以即使不关断中断,也不会发生错误。
但事实笔者在测试过程中还是发生了错误,但是不知错误怎么产生的,即按下1键,电机没有转1圈就停止了,而且是发生在刚开机的时候。当然不是每次都这样是偶尔,显然偶尔问题才难找。
负数在C语言中是用补码储存的,-1的补码是1111 1111因此该&操作的结果是0x07本身。
梳理下程序工作流程:
结语:尽信书,则不如无书。也许有很多优秀的教材在它出现的那个年代是不错的指导,但是随着时代的变化,当初的研究对象可能都已经发生了变化,然而教材却一成不变。引用它的人不去验证,曾经的闪光点都可能变成最大的错误。4096还是4076笔者内心其实是倾向4076的,但是实验结果笔者目前试出来是4096,目前不知道哪里出问题了。