M8系统编程应用之数字时钟的实现

作者:风风 发布于:2010年06月14日 19:48

写在前面的话
自从上一篇文章更新之后,又过了很久。说实话,原本我准备是每周更新一次的。但是,事实却往往事与愿违。之所以隔了那么久,一来是因为工作和学习上刚进入一个新的环境,所以还没有稳定,再者我自己也需要学习一些新的东西,整理一下曾经的内容。不过,现在回想起来我更新的频率远没有原来那么高了,或许是自己老了吧。

写了那么多关于软件相关的东西,我也想试着写一些和硬件有关的文章。所以,这次我想先试验一下写一篇看看,如果觉得不好,请各位不要拍砖。如果觉得好,那还请各位多多支持了。毕竟,你们的支持是我最大的动力。

曾经有一位朋友,看到我说M8的时候问过我,M8是不是某手机的型号。不过很可惜,我这里说的M8是一款由ATMEL生产的一款单片机。至于为什么选这款单片机而不是选择应用相当广泛的51单片机,我想主要有两个原因。第一,M8能够得到GCC的支持。第二,M8的性能和体系结构比同为ATMEL的 AT89S52要好很多。

1 ATmega8微控制器概述
ATmega8是一款基于AVR RISC体系结构的低功耗8位微控制器。能实现每MHz百万条指令的执行效率。从体系结构上讲,其拥有130条指令,且大部分都是单时钟周期的。此外,还拥有32个8位通用寄存器。最高能在16MHz外部时钟频率下达到16MIPS的执行速度。在其内部集成了8KB的可编程Flash ROM,用于存储MCU所执行的程序。其次还拥有1K的内部SRAM。至于其片内功能则也是相当丰富,如USART 1 、计时计数器、AD转换、SPI通讯接口、I2 C通讯接口 等诸多功能。其封装形式主要有双列支插和贴片两种形式。作为一款单片机来说,还是相当不错的,至少同ATMEL的51单片机相比,其优势还是相当高的。另外,对于最小系统,由于51需要外部晶振和复位电路,因此存在最小系统的概念。而ATmega8,由于默认情况下采用的是内部上电复位以及片内时钟,所以不存在最小系统的概念,通电便可工作。

2 I/O端口概述
对于一款MCU来说,他与外部交流的主要途径就是通过其I/O口。所以,在这篇文章中,我就先从I/O端口开始。

2.1 I/O端口物理描述
在ATmega8系列的单片机上,所有的I/O接口都能实现读写功能,而且,引脚之间是互相独立的,因此,你可以通过 SBI或者CBI这种位操作指令改变某一个引脚的功能而不影响其他引脚。 ATmega8拥3个8位的I/O端口,他们分别被命名为B、C和D。如果你曾经有过使用51的经验。那么你或许会对51中的上拉电阻记忆犹新,但是在ATmega8中其拥有内部可选的上拉电阻。也就是说,你可以选择使用内部的上拉电阻或者采用外部上拉电阻。如果你要选择外部上拉电阻,建议使用20kΩ ∼ 50kΩ

从电气性能上讲,ATmega8的引脚驱动力还是相当强的,而且输出和输入的驱动力是对称的,所以你不会遇到在51上遇到的输入能力大于输出能力的情况。根据手册上描述,其驱动电流为20mA。当然在具体的应用时,会存如下几点限制。

所有引脚的电流总和不得超过300mA
C0 ∼ C5 引脚的电流总和不能超过100mA
B0 ∼ B7、C6、D0 ∼ D7和XTAL2引脚的电流总和不应超过200mA
从手册的指标上讲,一个引脚驱动一个5mA的发光二极管应该是没什么问题的。而对于7段数码管来说,也不存在51的适合采用共阴极的限制。

2.2 I/O端口相关软件描述
从软件的层面讲,I/O 端口的功能主要通过DDxn、PORTxn这两类寄存器,以及PUD这个bit位来进行控制。其中”x”为端口名,而”n”则为寄存器位。各端口寄存器的功能描述,可以见表1 。表中”X”表示的为任意数,即0或1。

DDxn PORTxn PUD I/O 上拉电阻 说明
0 0 X 输入 无 高阻态
0 1 0 输入 有 与外部引脚变化相当
0 1 1 输入 无 高阻态
1 0 X 输出 无 输出低电平
1 1 X 输出 无 输出高电平

Table 1: M8 I/O端口寄存器功能描述

对于对硬件比较陌生的人,可能会不太理解高阻态。其实,高阻态就相当于引脚上接一个阻值很大的电阻,等同于断开。其中PUD为SFIOR 寄存器中的一位。同时,M8也能够通过可重复编程的熔丝位设置PUD,一般来说M8的上拉电阻是无效的。

2.3 软件操作范例
下面我们将提供一组M8端口的读写C代码2 。在这段代码中,我们将从B口读取一个8位数据,送到D口上。

view plaincopy to clipboardprint?#include #include int main(void) { uint8_t t; DDRB = 0×00; PORTB = 0xff; DDRD = 0xff; PORTD = 0×00; do { __asm__(”nop”); t = PORTB; PORTD = t; } while (1); return(0); } #include
#include

int main(void) {
uint8_t t;
DDRB = 0×00;
PORTB = 0xff;

DDRD = 0xff;
PORTD = 0×00;

do {
__asm__(”nop”);
t = PORTB;
PORTD = t;
} while (1);

return(0);
}

3 Timer0部件概述
计时/计数器是用于限定时间的主要部件。而我们这次要实现的是一个数字时钟。因此,计时/计数器是我们的核心功能器件。这次我们只涉及到两个计时器的部分功能,所以让我们继续看计时/计数器吧。

3.1 Timer0物理描述
M8中的Timer0部件是一个8-bit的计时/计数器。其计时/计数功能是独立于MCU的,也就是说Timer0的工作是不占用MCU的指令空间的。其计时的基准信号能通过软件内部选择。可以是ClkI/O 分频后的信号,也可以是外部T0引脚的输入信号。当采用分频时,则Timer0为计时器,当采用T0引脚的输入信号时,就可以作为计数器了。

Timer0相关的计数器寄存器在每次信号到达后,将累加1。当达到最大值0xFF时, TOV0将被置位,如果中断被允许的话,将触发相关的中断程序,并且将TOV0清零。此时计数器寄存器将从0×00开始重新计数。

3.2 Timer0软件功能描述
Timer0从软件角度看主要通过TCCR0来选择时钟信号,TIMSK中的TOIE0位控制时钟中断, TIFR中的TOV0位判断计数器是否溢出。最后,TCNT0为Timer0的计数寄存器。

CS02 CS01 CS00 说明
0 0 0 无时钟源,计时器停止
0 0 1 采用clkI/O 作为时钟源。
0 1 0 采用clkI/O /8作为时钟源。
0 1 1 采用clkI/O /64作为时钟源。
1 0 0 采用clkI/O /256作为时钟源。
1 0 1 采用clkI/O /1024作为时钟源。
1 1 0 采用T0引脚的下降沿跳变作为时钟源。
1 1 1 采用T0引脚的上升沿跳变作为时钟源。

Table 2: TCCR0 时钟选择寄存器说明

其中TCNT0就直接保存着计数器的值,通过对其赋值便可设定相应的时间。而TCCR0的低三位为 CS02、CS01和CS00用于选择时钟源,其他位为保留位,其具体的意义可见表 2

在具体的使用中,我们经过改变TCNT0的值来等待相应的时间。假设T为分频基数,其数值为 1、8、64、256或者1024中的一个。 S为等待的秒数。则我们根据原理可以得到等式(1 ) 。根据要求,将相应的数代入便得到TCNT0的值了。

TCNT0 =255 − clkI/O

——————————————————————————–

T
S
(1)

3.3 软件操作与范例
在这里,我将提供一组C代码。采用中断方式响应计时计数器溢出事件。这里我们将让计时器每隔约50ms发生一次,并假设时钟频率为1MHz。

view plaincopy to clipboardprint?#include #include #include int main(void) { TCCR0 = 0×05; TIMSK |= _BV(TOIE0); TCNT0 = 255 - 48; sei(); while(1); return(0); } ISR(TIMER0_OVF_vect) { TCNT0 = 255 - 48; //some code here } #include
#include
#include

int main(void) {
TCCR0 = 0×05;
TIMSK |= _BV(TOIE0);
TCNT0 = 255 - 48;
sei();

while(1);
return(0);
}

ISR(TIMER0_OVF_vect) {
TCNT0 = 255 - 48;
//some code here
}

4 Timer1部件概述
Timer1相对于Timer0的功能更多,但是在这里我们只使用Timer1中的CTC模式。

4.1 Timer1物理描述
Timer1是一个16bit的计时/计数器。除了拥有Timer0的功能之外,还拥有PWM模式以及CTC模式等。在计数器读写方面由于M8的数据总线是8位的,所以Timer1的各个16为寄存器都采用双缓存模式。Timer1的控制主要有 TCNT1、OCR1A/B、ICR1 这些16bit寄存器和一个TCCR1A/B八位寄存器对其控制。

由于我们这里只用到了CTC模式,因此其他具体的硬件描述就暂且省略了。不过,正因为Timer1是一个16bit的计时/计数器,因此其计数范围远大于Timer0。 Normal模式就和Timer0的功能是一样的,不过主要是计数范围更广。 CTC模式即Clear Timer on Compare Match。主要运用OCR1A或者 ICR1 两个寄存器。在CTC模式下,当TCNT1的值等于OCR1A或者ICR1时,TCNT1将被清零。

4.2 Timer1软件功能描述
Timer1的功能应该是在M8的3个计时计数器中功能最强大的一个了。但限于篇幅问题,我这里只简单阐述一下CTC的运用。

根据M8的手册,我们可以看到,当Timer1工作在Mode4和Mode12时,其工作模式为CTC。其中Mode4时,采用的比较寄存器为OCR1A,而Mode12采用的是ICR1。除此之外,从软件角度考虑,这两种CTC模式没有太大的区别。

在比较寄存器设置上,鉴于CTC的工作模式,我们可以得到和Timer0类似的公式。 公式 2 中,S 为等待的秒数,T为分频的基数。将相应的值代入后便可得到OCR1A的值了。

同时,因为CTC模式下,比较数是存储在OCR1A寄存器中的,而在比较结果相同时, TCNT1会自动清零,因此在设置完寄存器后,不用像Timer0那样,每次都要手动重新设置TCNT中的值。

OCR1A = clkI/O

——————————————————————————–

T
S
(2)

对于时钟频率的选择,Timer1的TCCR1B的第三位同Timer0的TCCR0的第三位一样,是用于选择时钟频率的。而且,数值也类似,只是在外部平率的选择上, Timer0采用的是T0引脚,而Timer1采用的是T1引脚。除此之外,都类似。

4.3 软件操作与范例
下面的代码也将采用中断响应模式,对计时器比较事件进行响应。这里,我们将让计时器每个1s发生一次中断事件,并假设时钟频率为1MHz。

view plaincopy to clipboardprint?#include #include #include int main(void) { TCCR1A = 0×00; TCCR1B = 0×0d; OCR1A = 0×03d0; TCNT1 = 0×0000; TIMSK |= _BV(OCIE1A); sei(); while(1); return(0); } ISR(TIMER1_COMPA_vect) { //some code here } #include
#include
#include

int main(void) {

TCCR1A = 0×00;
TCCR1B = 0×0d;

OCR1A = 0×03d0;
TCNT1 = 0×0000;
TIMSK |= _BV(OCIE1A);

sei();

while(1);
return(0);
}

ISR(TIMER1_COMPA_vect) {
//some code here
}

5 硬件环境概述
对于一个单片机系统而言,光有单片机是不够的,还需要外部电路的支持。当然,这个数字时钟从理论上说,也算一个微型系统。因此,单片机外围的硬件也同样是免不了的。这里的内容主要是针对在硬件方面不太了解的人所设置的,如果您是硬件牛人,那就跳过啦。

5.1 7段数码管的连接描述
对于一个系统而言,我们都需要有一组交互界面。比如,输出功能能之类的。在硬件的开发中,比较简单而常见的,就是LED作为输出,即发光二极管。这个玩意儿其实就是一个具有二极管单向导通性且会发光的器件。一般来说,一个发光二极管的驱动电流在5mA以上即可。当然,高出一点也是可以的,根据一般的经验来说,发光二极管还是非常经得起”折磨”的。

那7段数码管又是个啥呢?就是我们日常生活中,在电梯或者电子秤上看到的那种显示数字用的那种器件。一般来说,那7个管全部点亮时,显示的是数字8。不过这里其实有个误导,7段数码管,一般来说有8个LED,最后一个LED就是数字边上的小数点。从结构上讲,7段数码管就是7个LED拼成一个数字8的形状,再用一个LED 表示一个小数点。

在实际的使用中,为了简化连线和便于使用,常把那一组LED的某个极性的引脚连在一起作为一个引脚使用。所以,7段数码管常被分为共阴和共阳两种。这两种的区别就在于,共阴极是把所有LED的阴极连在一起,而共阳极则是把所有LED的阳极连在一起。一般来说,因为M8的输出电流和输入电流的驱动力是一样的,所以我个人比较喜欢共阴极的。

当然,共极连接也会带来一个问题,那就是LED控制的问题。因为是共阴,所以,当8个LED全部点亮的时候,其共阴极的电流将至少达到40mA。这对于单片机来说,如果要控制的话,是一个不小的负担。因此,在使用的时候,我们会在共阴极上用一个NPN三级管搭一个控制电路,利用小电流来控制三极管的截止和导通,起到开关的作用。一般来说这个电流不会超过1mA。

5.2 LED的动态刷新法描述
前面,我已经说过了,LED是硬件输出最基本的形式。但是,随之会有另一个问题产生。单片机的引脚是有限的,而一个7段数码管就要占用8个引脚。而对于一般的系统而言,一个7段数码管是不够的。就比如这次实现的数字时钟,至少要用4个7段数码管。如此一来,我们需要至少32个引脚。但是,我们使用的M8是28引脚的。怎么算也不可能用32个引脚控制啊。

对于这种问题,有两种解决办法。其中比较直接的就是使用外部的寄存器,将每个脚的数值送到寄存器上锁住,然后驱动数码管。这种方式被称为”静态显示法”。这种现实方式的优点是便于控制,并且显示方面也比较稳定。缺点么自然很明朗啦,那就是成本太高了。

另一种现实方式,就是我们标题所提到的,”动态刷新法”。动态刷新法的原理比较简单,就是利用人眼睛的暂留现象,快速的现实每一位的内容,就和电影的原理差不多。在器件上来说,同样是4个7段数码管,我们只要用12个引脚就够了。这种方式的优点是节约了引脚的资源,而且连线也减少了。但缺点是需要一个定时器来控制刷新。

在实践中,有些人会发生利用动态刷新法时,显示的数字会闪烁,或者出现乱码的情况。其实,出现这种问题的原因,除了是程序编写方面的问题,比如刷新间隔太长,或者 7段数码显示方面的编码问题,也可能是因为刷新方式不对。在使用共阴极数码管进行动态刷新显示的时候,先要将共阴极置高位熄灭所有LED,然后将信息送到数码管的各引脚上,最后拉低共阴极的电平点亮指定的数码管。通常,采用这个刷新顺序可以保证显示的结果相对比较稳定。

6 数字时钟实现概述
在前面那么多篇幅的基础知识准备后,真正描写数字时钟的篇幅却是那么的少。不过,从另一方面来说,前面的内容其实都是在围绕这个数字时钟的。因此,这篇文章也不能完全的说是偏题。

6.1 硬件环境的简单描述
通过上面一系列的描述,硬件的链接已经不是啥秘密了。当然,唯一需要说明的是,为了保证时钟的精确性,我考虑下来准备采用外部 8MHz晶振。使用外部晶振的原因主要是因为,M8内部的振荡电路是RC振荡,因此随外界温度的变化会有所误差,当然对于这种变化幅度允许比较大的时钟来说, RC振荡也是足够了,毕竟想想我们的父辈们,利用555单稳态电路都能做时钟,那我们这个近8MHz的RC振荡电路上的一点温度系数的误差又算得了啥?但是,本着对将时钟从非洲带到北极都能让时钟比较正常工作的目标,选择一个外部晶振也是可以的。

显示部分,我设计采用的是4个7段数码管进行动态刷新。而输入,则利用M8自带的两个外部中断来实现。内部的两个时钟Timer0和Timer1。因为Timer1的计数范围更广而且拥有CTC模式,因此用于秒的计数。而Timer0,则用于动态刷新的计时,因为动态刷新的定时对时间的误差要求不是很高。

电路图么,其实都是一些简单的连线,也没什么特别的。如果有特别需要,或者有什么不明白的,可以留言于我。这里就不给出了。

6.2 数字时钟相关的数据结构
在刚开始设计这个玩意儿的时候,我想到的是记录从某一时刻开始到现在的秒数,也就是像Unix中的那个时间戳那种。但是,由于我们这里用的是单片机,虽然性能很强大,不过在频繁的高密度的16bit乘除法面前,还是会有些问题的。因此,我最后用空间换取了时间。C语言描述的数据结构如下。其中status为时钟所处的状态,即比如是显示时间还是年月等。其他的根据意义都可以理解。至于为什么year要采用16bit,是因为年的数字远大于255,现在都已经是2000年以后了。

view plaincopy to clipboardprint?typedef struct { uint8_t status; uint8_t sec; uint8_t min; uint8_t hour; uint8_t day; uint8_t month; uint16_t year; } x_time_t; typedef struct {
uint8_t status;
uint8_t sec;
uint8_t min;
uint8_t hour;
uint8_t day;
uint8_t month;
uint16_t year;
} x_time_t;

这样,我们计时部分的代码就是简单的加法和比较跳转指令。效率远比将秒数转换成具体的时间要高,更重要的是,也方便了时钟调整功能的实现。

6.3 动态刷新的代码
在前面,我们已经了解了动态刷新法的刷新过程。接下来,我以动态刷新法写一段代码。其中,假设B端口连着各7段数码管的那八个引脚上。D端口的低四位接在4个数码管的共阴极控制电路上。

view plaincopy to clipboardprint?#include #include #include #define X_CPU_HZ 1000000 #define X_SW_FPS 36 #define X_TCNT0_SET (0xff - X_CPU_HZ / 1024 / X_SW_FPS) #define X_LED0 0b00111111 #define X_LED1 0b00000011 #define X_LED2 0b01101101 #define X_LED3 0b01100111 uint8_t sw_list[4] __attribute__((section(”.noinit”))); int main(void) { TCCR0 = 0×05; TCNT0 = X_TCNT0_SET; TIMSK |= _BV(TOIE0); DDRB = 0xff; PORTB = 0×00; DDRD = 0×0f; PORTD = 0×00; sw_list[0] = X_LED0; sw_list[1] = X_LED1; sw_list[2] = X_LED2; sw_list[3] = X_LED3; sei(); while(1); return(0); } ISR(TIMER0_OVF_vect) { static uint8_t c = 0; PORTD = 0×00; PORTB = sw_list[c]; PORTD = (0×01 << c); c = (c + 1) % 4; } #include
#include
#include

#define X_CPU_HZ 1000000
#define X_SW_FPS 36
#define X_TCNT0_SET (0xff - X_CPU_HZ / 1024 / X_SW_FPS)

#define X_LED0 0b00111111
#define X_LED1 0b00000011
#define X_LED2 0b01101101
#define X_LED3 0b01100111

uint8_t sw_list[4] __attribute__((section(”.noinit”)));

int main(void) {

TCCR0 = 0×05;
TCNT0 = X_TCNT0_SET;
TIMSK |= _BV(TOIE0);

DDRB = 0xff;
PORTB = 0×00;
DDRD = 0×0f;
PORTD = 0×00;

sw_list[0] = X_LED0;
sw_list[1] = X_LED1;
sw_list[2] = X_LED2;
sw_list[3] = X_LED3;

sei();

while(1);
return(0);

}

ISR(TIMER0_OVF_vect) {
static uint8_t c = 0;

PORTD = 0×00;
PORTB = sw_list[c];
PORTD = (0×01 << c);
c = (c + 1) % 4;
}

6.4 计时功能的代码
根据我文章开始的描述,计时功能主要依赖于那个计时模块。没秒钟执行一次计时函数,更新其中的数值。其代码如下。

view plaincopy to clipboardprint?#include #include #include #define X_CPU_HZ 1000000 typedef struct { uint8_t sec; uint8_t min; uint8_t hour; uint8_t day; uint8_t month; uint16_t year; } x_time_t; x_time_t g_time_s __attribute__((section(”.noinit”))); int main(void) { TCCR1A = 0×00; TCCR1B = 0×0D; TIMSK |= _BV(OCIE1A); OCR1A = X_CPU_HZ / 1024; g_time_s.sec = 0; g_time_s.min = 0; g_time_s.hour = 0; g_time_s.day = 0; g_time_s.month = 0; g_time_s.year = 2000; sei(); while(1); return(0); } ISR(TIMER1_COMPA_vect){ uint8_t month[] = { 31,28,31,30,31,30,31,31,30,31,30,31 }; uint8_t day_fix; day_fix = g_time_s.day == 1 && g_time_s.year % 4 == 0 ? 1 : 0; g_time_s.sec += 1; do { if (g_time_s.sec == 60) { g_time_s.sec = 0; g_time_s.min += 1; } else break; if (g_time_s.min == 60) { g_time_s.min = 0; g_time_s.hour += 1; } else break; if (g_time_s.hour == 24) { g_time_s.hour = 0; g_time_s.day += 1; } else break; if (g_time_s.day == (month[g_time_s.month] + day_fix)) { g_time_s.day = 0; g_time_s.month += 1; } else break; if (g_time_s.month == 12) { g_time_s.month = 0; g_time_s.year += 1; } }while(0); } #include
#include
#include

#define X_CPU_HZ 1000000

typedef struct {
uint8_t sec;
uint8_t min;
uint8_t hour;
uint8_t day;
uint8_t month;
uint16_t year;
} x_time_t;

x_time_t g_time_s __attribute__((section(”.noinit”)));

int main(void) {

TCCR1A = 0×00;
TCCR1B = 0×0D;
TIMSK |= _BV(OCIE1A);
OCR1A = X_CPU_HZ / 1024;

g_time_s.sec = 0;
g_time_s.min = 0;
g_time_s.hour = 0;
g_time_s.day = 0;
g_time_s.month = 0;
g_time_s.year = 2000;

sei();

while(1);
return(0);
}

ISR(TIMER1_COMPA_vect){
uint8_t month[] = {
31,28,31,30,31,30,31,31,30,31,30,31
};
uint8_t day_fix;
day_fix = g_time_s.day == 1 && g_time_s.year % 4 == 0 ? 1 : 0;

g_time_s.sec += 1;
do {
if (g_time_s.sec == 60) {
g_time_s.sec = 0;
g_time_s.min += 1;
} else break;

if (g_time_s.min == 60) {
g_time_s.min = 0;
g_time_s.hour += 1;
} else break;

if (g_time_s.hour == 24) {
g_time_s.hour = 0;
g_time_s.day += 1;
} else break;

if (g_time_s.day == (month[g_time_s.month] + day_fix)) {
g_time_s.day = 0;
g_time_s.month += 1;
} else break;

if (g_time_s.month == 12) {
g_time_s.month = 0;
g_time_s.year += 1;
}
}while(0);
}

7 文外音
这次的文章距上一次间隔比较长,其中有很多原因,很大原因是我个人的因素。这篇文章主要还是用于尝试写点新的内容,有不足的地方还请各位多多包涵并指出。至于为什么会开始玩玩硬件么,这个故事就说来话长了。感觉最近发生在身边的感触都能写成一本书了,我想主要是现在周遭的环境不好吧,有时候觉得,在国内,技术和人际关系,貌似人际关系会比较重要,技术则可有可无。同时我又觉得,在国内的技术现状中,真正想为社会做贡献的人才,往往会受到各种奇怪的阻挠,最后却事与愿违。感觉自己最近貌似牢骚比较多了。

最后还是这句话,如果你要转载此文,请转载时注明出处,以便读者能尽快的得到回复。如果你们有任何的问题,可以到 http://blog.csdn.net/visioncat 上留下你的疑惑,我会第一时间内回答你的。如需要获取此文的PDF版,也可以留言索取。最后的最后,我要感谢各位对我一直以来的关注和支持,谢谢你们。

作者:风风
来源:北风技术专栏
原文链接:http://column.ibeifeng.com/allg0/20100614357.shtml

( 内容完 )

添加收藏到:

您可能还对这些文章感兴趣:

  • 基于EJB3.0的留言板项目的开发全过程
  • iBatis已经更名为MyBatis,并搬迁到Google Code
  • Hibernate+Struts的J2EE应用开发
  • 轻量级工作流引擎jBPM 4.4正式发布
  • OSGI中的service依赖关系管理
  • asp中对ip进行过滤限制函数
  • ikon999原创:JsUnit测试之三
  • 创新工厂面试经历
  • struts2系列1:环境搭建(helloworld)
  • 如何避免JQuery Dialog的内存泄露
  • 没有评论, 我来评论

    小贴士:评论需要管理员审核后才会显示。请不要发布与国家法律相抵触的言论,北风网将保留追究责任的权利。
    类似“顶”、“沙发”、“支持”之类没有营养的文字,对勤劳贡献的作者来说是令人沮丧的反馈信息。
    请勿到处挖坑绊人、招贴广告。既占空间让人厌烦,又没人会搭理,于人于己都无利。
    如果您发现自己的评论没有被审核或者不见了,请参考以上三条。

    

    每周之星

    陈臣陈臣

    七年Java和JEE开发经验,JEE应用设计和高级架构师,拥有Sun的多项Java和J2EE方面的技能认证,多年项目经理、技术部经理的管理经验。拥有全面、扎实的Java和JEE理论知识,丰富的JEE应用开发经验。

    更多作者:

  • Adam
  • ikon999
  • jk1234
  • jk2345
  • libin_8745
  • lifengxing
  • taohuang100
  • xingkong
  • 北风
  • 呆子
  • 子晨
  • 小白
  • 张章
  • 张维亮
  • 陈臣
  • 陶宝哥
  • 风风
  • 最新内容

    推荐内容

    标签

    分类