jianhong_wu
发表于 2014-5-30 11:19
longcomeon 发表于 2014-5-30 10:56 static/image/common/back.gif
if(key_sr1==1)//IO是高电平,说明两个按键没有全部被按下,这时要及时清零一些标志位
{
ucKeyLo ...
先短按下去再松手的时候就会发现ucShortTouchFlag1==1了
longcomeon
发表于 2014-5-30 14:05
jianhong_wu 发表于 2014-5-30 11:19 static/image/common/back.gif
先短按下去再松手的时候就会发现ucShortTouchFlag1==1了
明白,谢谢
longcomeon
发表于 2014-5-30 15:25
void main()
{
initial_myself();
delay_long(100);
initial_peripheral();
while(1)
{
led_flicker();
}
}
大虾,第十七节中,main函数中怎么没有驱动函数,定时函数里面也没有驱动函数
jianhong_wu
发表于 2014-5-30 21:19
longcomeon 发表于 2014-5-30 15:25 static/image/common/back.gif
void main()
{
initial_myself();
led_flicker()函数内部已经包含了驱动函数hc595_drive()
szdzjs
发表于 2014-6-2 15:23
向楼主学习啊,难得的机会
panamatw
发表于 2014-6-3 08:17
謝謝
菜鸟变老鸟
发表于 2014-6-3 08:56
你说的这些都会了,怎么办?
jianhong_wu
发表于 2014-6-3 12:35
菜鸟变老鸟 发表于 2014-6-3 08:56 static/image/common/back.gif
你说的这些都会了,怎么办?
有了系统的框架思想,就可以开始接一些单片机项目来做,边做项目边学习。
yao1318
发表于 2014-6-5 08:29
有压力才有动力,在项目中会找到很多平时看不到和想不到的东西。学以致用吧!
raoxianbin
发表于 2014-6-5 11:16
楼主做过USB通信吗?
jianhong_wu
发表于 2014-6-5 15:12
raoxianbin 发表于 2014-6-5 11:16 static/image/common/back.gif
楼主做过USB通信吗?
我用CH376做过U盘通讯的,但是没做过USB通讯的。用CH376也可以做USB通讯,而且厂家提供了大量的资料,应该不会很难。
jianhong_wu
发表于 2014-6-5 15:13
第五十节:利用ADC0832采集电压信号,用平均法和区间法进行软件滤波处理。
开场白:
ADC0832是一款常用的8位AD采样芯片,通过它可以把外部的模拟电压信号转换成数字信号,然后给单片机进行换算,显示等处理。
这一节要教会大家五个知识点:
第一个:分辨率的算法。有些书上说8位AD最高分辩可达到256级(0xff+1),当输入电压是0---5V时,电压精度为19.53mV(5000mV除以256),我认为这种说法是错误的。8位AD的最高分辨率应该是255级(0xff),当输入电压是0---5V时,电压精度为19.61mV(5000mV除以255)。
第二个:用求平均值的滤波法,可以使AD采样的数据更加圆滑,去除小毛刺。
第三个:用区间滤波法,在一些干扰很大的场合,可以避免末尾小数点的数据频繁跳动。
第四个:如何使系统可以采集到更高的电压。由于ADC0832直接采集的电压最大不能超过5V,如果要采集的最大电压是25V该怎么办?我们只要在外部多增加1个10K的电阻和1个40K的电阻组成分压电路,把25V分压成5V,然后再让ADC0832采样,这时采样到的数据只要乘以5的系数,就可以得到超过5V的实际电压。选择分压电阻时,阻值尽量不要太小,一般要10K级别以上,阻值大一点,对被采样的系统干扰影响就越小。
第五个:如何有效保护AD通道口。我在一些电压不稳定的工控场合,一般是在AD通道口对负极反接一个瞬变二极管SA5.0A。当电压超过5V时,瞬变二极管会导通吸收掉多余的能量,把电压降下来,避免AD通道口烧坏。
具体内容,请看源代码讲解。
(1) 硬件平台.
基于朱兆祺51单片机学习板。
(2)实现功能:
本程序有2个局部显示。
第1个局部是第8,7,6,5位数码管,显示没有经过滤波处理的实际电压值。此时能观察到未经滤波的数据不太稳定,末尾小数点数据会有跳动的现象
第2个局部是第4,3,2,1位数码管,显示经过平均法,区间法滤波的实际电压值。此时能观察到经过滤波后的数据很稳定,没有跳动的现象
系统保留3位小数点。手动调节可调电阻时,可以看到显示的数据在变化。
(3)源代码讲解如下:#include "REG52.H"
#define const_voice_short40 //蜂鸣器短叫的持续时间
void initial_myself(void);
void initial_peripheral(void);
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
//驱动数码管的74HC595
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
void display_drive(void); //显示数码管字模的驱动函数
void display_service(void); //显示的窗口菜单服务程序
//驱动LED的74HC595
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void T0_time(void);//定时中断函数
void ad_sampling_service(void); //AD采样与处理的服务程序
sbit led_dr=P3^5;//LED灯
sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit dig_hc595_sh_dr=P2^0; //数码管的74HC595程序
sbit dig_hc595_st_dr=P2^1;
sbit dig_hc595_ds_dr=P2^2;
sbit hc595_sh_dr=P2^3; //LED灯的74HC595程序
sbit hc595_st_dr=P2^4;
sbit hc595_ds_dr=P2^5;
sbit adc0832_clk_dr = P1^2;// 定义adc0832的引脚
sbit adc0832_cs_dr = P1^0;
sbit adc0832_data_sr_dr = P1^1;
unsigned char ucDigShow8;//第8位数码管要显示的内容
unsigned char ucDigShow7;//第7位数码管要显示的内容
unsigned char ucDigShow6;//第6位数码管要显示的内容
unsigned char ucDigShow5;//第5位数码管要显示的内容
unsigned char ucDigShow4;//第4位数码管要显示的内容
unsigned char ucDigShow3;//第3位数码管要显示的内容
unsigned char ucDigShow2;//第2位数码管要显示的内容
unsigned char ucDigShow1;//第1位数码管要显示的内容
unsigned char ucDigDot8;//数码管8的小数点是否显示的标志
unsigned char ucDigDot7;//数码管7的小数点是否显示的标志
unsigned char ucDigDot6;//数码管6的小数点是否显示的标志
unsigned char ucDigDot5;//数码管5的小数点是否显示的标志
unsigned char ucDigDot4;//数码管4的小数点是否显示的标志
unsigned char ucDigDot3;//数码管3的小数点是否显示的标志
unsigned char ucDigDot2;//数码管2的小数点是否显示的标志
unsigned char ucDigDot1;//数码管1的小数点是否显示的标志
unsigned char ucDigShowTemp=0; //临时中间变量
unsigned char ucDisplayDriveStep=1;//动态扫描数码管的步骤变量
unsigned char ucWd1Part1Update=1;//在窗口1中,局部1的更新显示标志
unsigned char ucWd1Part2Update=1; //在窗口1中,局部2的更新显示标志
unsigned char ucTemp1=0;//中间过渡变量
unsigned char ucTemp2=0;//中间过渡变量
unsigned char ucTemp3=0;//中间过渡变量
unsigned char ucTemp4=0;//中间过渡变量
unsigned char ucTemp5=0;//中间过渡变量
unsigned char ucTemp6=0;//中间过渡变量
unsigned char ucTemp7=0;//中间过渡变量
unsigned char ucTemp8=0;//中间过渡变量
unsigned char ucAD=0; //AD值
unsigned char ucCheckAD=0; //用来做校验对比的AD值
unsigned long ulTemp=0;//参与换算的中间变量
unsigned long ulTempFilterV=0; //参与换算的中间变量
unsigned long ulBackupFilterV=5000;//备份最新采样数据的中间变量
unsigned char ucSamplingCnt=0; //统计采样的次数本程序采样8次后求平均值
unsigned long ulV=0; //未经滤波处理的实时电压值
unsigned long ulFilterV=0; //经过滤波后的实时电压值
//根据原理图得出的共阴数码管字模表
code unsigned char dig_table[]=
{
0x3f,//0 序号0
0x06,//1 序号1
0x5b,//2 序号2
0x4f,//3 序号3
0x66,//4 序号4
0x6d,//5 序号5
0x7d,//6 序号6
0x07,//7 序号7
0x7f,//8 序号8
0x6f,//9 序号9
0x00,//无 序号10
0x40,//- 序号11
0x73,//P 序号12
};
void main()
{
initial_myself();
delay_long(100);
initial_peripheral();
while(1)
{
ad_sampling_service(); //AD采样与处理的服务程序
display_service(); //显示的窗口菜单服务程序
}
}
void ad_sampling_service(void) //AD采样与处理的服务程序
{
unsigned char i;
ucAD=0; //AD值
ucCheckAD=0; //用来做校验对比的AD值
/* 片选信号置为低电平 */
adc0832_cs_dr = 0;
/* 第一个脉冲,开始位 */
adc0832_data_sr_dr = 1;
adc0832_clk_dr= 0;
delay_short(1);
adc0832_clk_dr= 1;
/* 第二个脉冲,选择通道 */
adc0832_data_sr_dr = 1;
adc0832_clk_dr= 0;
adc0832_clk_dr= 1;
/* 第三个脉冲,选择通道 */
adc0832_data_sr_dr = 0;
adc0832_clk_dr= 0;
adc0832_clk_dr= 1;
/* 数据线输出高电平 */
adc0832_data_sr_dr = 1;
delay_short(2);
/* 第一个下降沿 */
adc0832_clk_dr= 1;
adc0832_clk_dr= 0;
delay_short(1);
/* AD值开始送出 */
for (i = 0; i < 8; i++)
{
ucAD <<= 1;
adc0832_clk_dr = 1;
adc0832_clk_dr = 0;
if (adc0832_data_sr_dr==1)
{
ucAD |= 0x01;
}
}
/* 用于校验的AD值开始送出 */
for (i = 0; i < 8; i++)
{
ucCheckAD >>= 1;
if (adc0832_data_sr_dr==1)
{
ucCheckAD |= 0x80;
}
adc0832_clk_dr = 1;
adc0832_clk_dr = 0;
}
/* 片选信号置为高电平 */
adc0832_cs_dr = 1;
if(ucCheckAD==ucAD)//检验相等
{
ulTemp=0;//把char类型数据赋值给long类型数据之前,必须先清零
ulTemp=ucAD; //把char类型数据赋值给long类型数据,参与乘除法运算的数据,为了避免运算结果溢出,我都用long类型
/* 注释一:
* 因为保留3为小数点,这里的5000代表5.000V。ulTemp/255代表分辨率.
* 有些书上说8位AD最高分辩可达到256级(0xff+1),我认为这种说法是错误的。
* 8位AD最高分辩应该是255级(0xff),所以这里除以255,而不是256.
*/
ulTemp=5000*ulTemp/255;//进行电压换算
ulV=ulTemp; //得到未经滤波处理的实时电压值
ucWd1Part1Update=1; //局部更新显示未经滤波处理的电压
ulTempFilterV=ulTempFilterV+ulTemp;//累加8次后求平均值
ucSamplingCnt++;//统计已经采样累计的次数
if(ucSamplingCnt>=8)
{
/* 注释二:
* 求平均值滤波法,为了得到的数据更加圆滑,去除小毛刺。
* 向右边移动3位相当于除以8。
*/
ulTempFilterV=ulTempFilterV>>3; //求平均值滤波法
/* 注释三:
* 以下区间滤波法,为了避免末尾小数点的数据频繁跳动。
* 这里的20用于区间滤波法的正负偏差,这里的20代表0.020V。
* 意思是只要最近采集到的数据在正负0.020V偏差范围内,就不更新。
*/
if(ulBackupFilterV>=20)//最近备份的上一次数据大于等于0.02V的情况下
{
if(ulTempFilterV<(ulBackupFilterV-20)||ulTempFilterV>(ulBackupFilterV+20)) //在正负0.020V偏差范围外,更新
{
ulBackupFilterV=ulTempFilterV;//备份最新采样的数据,方便下一次对比判断
ulFilterV=ulTempFilterV; //得到经过滤波处理的实时电压值
ucWd1Part2Update=1; //局部更新显示经过滤波处理的电压
}
}
else //最近备份的上一次数据小于0.02V的情况下
{
if(ulTempFilterV>(ulBackupFilterV+20))//在正0.020V偏差范围外,更新
{
ulBackupFilterV=ulTempFilterV;//备份最新采样的数据,方便下一次对比判断
ulFilterV=ulTempFilterV; //得到经过滤波处理的实时电压值
ucWd1Part2Update=1; //局部更新显示经过滤波处理的电压
}
}
ucSamplingCnt=0;//清零,为下一轮采样滤波作准备。
ulTempFilterV=0;
}
}
}
void display_service(void) //显示的窗口菜单服务程序
{
if(ucWd1Part1Update==1)//未经滤波处理的实时电压更新显示
{
ucWd1Part1Update=0;
ucTemp8=ulV%10000/1000;//显示电压值个位
ucTemp7=ulV%1000/100; //显示电压值小数点后第1位
ucTemp6=ulV%100/10; //显示电压值小数点后第2位
ucTemp5=ulV%10; //显示电压值小数点后第3位
ucDigShow8=ucTemp8; //数码管显示实际内容
ucDigShow7=ucTemp7;
ucDigShow6=ucTemp6;
ucDigShow5=ucTemp5;
}
if(ucWd1Part2Update==1)//经过滤波处理后的实时电压更新显示
{
ucWd1Part2Update=0;
ucTemp4=ulFilterV%10000/1000;//显示电压值个位
ucTemp3=ulFilterV%1000/100; //显示电压值小数点后第1位
ucTemp2=ulFilterV%100/10; //显示电压值小数点后第2位
ucTemp1=ulFilterV%10; //显示电压值小数点后第3位
ucDigShow4=ucTemp4; //数码管显示实际内容
ucDigShow3=ucTemp3;
ucDigShow2=ucTemp2;
ucDigShow1=ucTemp1;
}
}
void display_drive(void)
{
//以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
switch(ucDisplayDriveStep)
{
case 1://显示第1位
ucDigShowTemp=dig_table;
if(ucDigDot1==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfe);
break;
case 2://显示第2位
ucDigShowTemp=dig_table;
if(ucDigDot2==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfd);
break;
case 3://显示第3位
ucDigShowTemp=dig_table;
if(ucDigDot3==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfb);
break;
case 4://显示第4位
ucDigShowTemp=dig_table;
if(ucDigDot4==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xf7);
break;
case 5://显示第5位
ucDigShowTemp=dig_table;
if(ucDigDot5==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xef);
break;
case 6://显示第6位
ucDigShowTemp=dig_table;
if(ucDigDot6==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xdf);
break;
case 7://显示第7位
ucDigShowTemp=dig_table;
if(ucDigDot7==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xbf);
break;
case 8://显示第8位
ucDigShowTemp=dig_table;
if(ucDigDot8==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0x7f);
break;
}
ucDisplayDriveStep++;
if(ucDisplayDriveStep>8)//扫描完8个数码管后,重新从第一个开始扫描
{
ucDisplayDriveStep=1;
}
}
//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
{
unsigned char i;
unsigned char ucTempData;
dig_hc595_sh_dr=0;
dig_hc595_st_dr=0;
ucTempData=ucDigStatusTemp16_09;//先送高8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)dig_hc595_ds_dr=1;
else dig_hc595_ds_dr=0;
dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
dig_hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
ucTempData=ucDigStatusTemp08_01;//再先送低8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)dig_hc595_ds_dr=1;
else dig_hc595_ds_dr=0;
dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
dig_hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
dig_hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(1);
dig_hc595_st_dr=1;
delay_short(1);
dig_hc595_sh_dr=0; //拉低,抗干扰就增强
dig_hc595_st_dr=0;
dig_hc595_ds_dr=0;
}
//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
unsigned char i;
unsigned char ucTempData;
hc595_sh_dr=0;
hc595_st_dr=0;
ucTempData=ucLedStatusTemp16_09;//先送高8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)hc595_ds_dr=1;
else hc595_ds_dr=0;
hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
ucTempData=ucLedStatusTemp08_01;//再先送低8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)hc595_ds_dr=1;
else hc595_ds_dr=0;
hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(1);
hc595_st_dr=1;
delay_short(1);
hc595_sh_dr=0; //拉低,抗干扰就增强
hc595_st_dr=0;
hc595_ds_dr=0;
}
void T0_time(void) interrupt 1 //定时中断
{
TF0=0;//清除中断标志
TR0=0; //关中断
display_drive();//数码管字模的驱动函数
TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;
TR0=1;//开中断
}
void delay_short(unsigned int uiDelayShort)
{
unsigned int i;
for(i=0;i<uiDelayShort;i++)
{
; //一个分号相当于执行一条空语句
}
}
void delay_long(unsigned int uiDelayLong)
{
unsigned int i;
unsigned int j;
for(i=0;i<uiDelayLong;i++)
{
for(j=0;j<500;j++)//内嵌循环的空指令数量
{
; //一个分号相当于执行一条空语句
}
}
}
void initial_myself(void)//第一区 初始化单片机
{
led_dr=0;//LED灯默认关闭
beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
hc595_drive(0x00,0x00);//关闭所有经过另外两个74HC595驱动的LED灯
TMOD=0x01;//设置定时器0为工作方式1
TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;
}
void initial_peripheral(void) //第二区 初始化外围
{
ucDigDot8=1; //显示未经过滤波电压的小数点
ucDigDot7=0;
ucDigDot6=0;
ucDigDot5=0;
ucDigDot4=1;//显示经过滤波后电压的小数点
ucDigDot3=0;
ucDigDot2=0;
ucDigDot1=0;
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
}
总结陈词:
这节用区间滤波法虽然可以解决小数点后面的数据出现频繁跳动的现象,但是也存在一个小问题,就是精度受到了影响,比如我们设置的正负偏差是0.02V,那就意味着系统存在0.02V的误差。有没有更好的办法解决这个问题?如果系统的末尾数据一直不断处于频繁跳动中,那么只能牺牲一点精度,我认为用区间法已经是最好的解决办法了,但是经过本次实验,我观察到未经过滤波处理的数据只是偶尔跳动,并非频繁跳动,所以下一节我会给大家介绍一种不用牺牲精度,又可以很好滤波的方法。欲知详情,请听下回分解-----利用ADC0832采集电压信号,用连续N次一致性的方法进行滤波处理。
(未完待续,下节更精彩,不要走开哦)
raoxianbin
发表于 2014-6-5 15:31
jianhong_wu 发表于 2014-6-5 15:12 static/image/common/back.gif
我用CH376做过U盘通讯的,但是没做过USB通讯的。用CH376也可以做USB通讯,而且厂家提供了大量的资料,应 ...
en我用的是单片机集成是USB的,现在不是很清楚如何和上位机进行交互。。。
lh18753385
发表于 2014-6-5 17:58
mark
yao1318
发表于 2014-6-7 11:30
时刻关注着!
cjseng
发表于 2014-6-7 12:05
AD转换本来就存在量化误差,这个是无法避免的,从这一点上来说,最后一位数字跳动是正常的,不跳都是“做假”的。
平均值滤波可以降低跳动的概率,但也无法完全避免。
DIYWODIY
发表于 2014-6-7 18:32
这是论坛最有价值的好帖,谢谢!很多帖子都是来晒照片的,一点用都没用有。
zh1981829
发表于 2014-6-7 21:31
感触颇深
xiaoyuan_ly
发表于 2014-6-7 23:34
hbc0602 发表于 2014-3-21 00:25 static/image/common/back.gif
我也是个初学者,我有个疑惑:如果我定时器定为2ms,像楼主说的那样,定义一堆标志位来累计定时中断次数去 ...
这个框架不是万能的,也没有万能的框架。你说的这个问题,就是抢占也解决不了。你要知道自己的任务函数集合的最大执行时间,是否在你的最大中断定时范围内。如果没有,你还需要一个全局变量来同步,即使是同步也解决不了因硬件实时的需求,最多是将执行推后来执行,如一定要硬着头皮去解决,这时就需要同步的变量来停止解决时间中断状态机的状态迁移(不然必会漏掉状态中的一个或几个状态),而结果与现实时间轴的对比(效果是好像CPU的时间是停止的),推后执行是必然的。你再找点什么是实时的资料来看看吧?!毕竟非实时事务函数和实时的控制函数在时间轴上的要求是不一样的,你只要不极端的使用这个框架,或者知道使用这个框架的实质和本质问题,那你就尽情享用他带来的莫大好处,尽可能的避免极端情况发生,这个比所谓的“多任务操作系统”在理解和使用上简单和实用多了,特别在高速的CPU中,表现的会出乎你意料的好,真的。
xiaoyuan_ly
发表于 2014-6-7 23:55
本帖最后由 xiaoyuan_ly 于 2014-6-7 23:56 编辑
jianhong_wu 发表于 2014-4-5 23:06 static/image/common/back.gif
其实你说的这些问题之前也有很多人跟我质疑过。
(1)如果不是做实时时钟,我这样的时间精度可以满足绝大 ...
楼主,别理会这些东西,如果一个想法变成了现实,就是有形的。有形就是一个基本要求。这个是一个同步系统框架,就是有形的,也只有同步才会是简单编程,也只有关中断才能可靠的解决非同步而使其推后执行进入同步状态。除非能计算或测试出最长执行路径时间,而让你的中断定时大于你的最长执行路径时间。