在最开始的

#define MAIN_Fosc	24000UL

定义主频为24.000MHz

在main函数中,开头可添加如下代码

EAXFR = 1; // 使能扩展的特殊寄存器
WTST = 0x00; // 设置代码等待时间为0,CPU 执行程序的速度最快 
CKCON = 0x00; // 设置外部总线速度最快

以使得程序运行速度最快


点亮一个LED

sbit LED1 = P0 ^ 0;		
sbit LED2 = P0 ^ 1;

LED1的对应引脚是P00 LED2的对应引脚是P01

以上代码一般添加在最开头(main函数之前) 用来定义引脚

P0M1 = 0x00; P0M0 = 0x00;

将P0端口的所有引脚都设置为推挽输出模式

因为要使用P0引脚,以上代码暂且理解为初始化 (目前这么理解 2025.3.29,知道具体为什么来还愿)

LED1 = 0; 
LED2 = 1;

LED1输出低电平,LED2输出高电平

通过输出高低电平控制LED亮灭


编程实现LED闪烁&LED流水灯&数码管显示

LED闪烁

LED1 = ON;
delay_ms(500);
LED1 = OFF;
delay_ms(500);

很简单,在LED亮灭间穿插延时就好了

LED流水灯&数码管显示

u8 code LED_Index[] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};

u8 code SEG_Index[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6d,0x7d,0x07,0x7f,0x6f};

LED数组的作用,通过十六进制转二进制可以得到LED阵列的状态。如0x08就代表00001000,可得出LED状态

SEG数组的作用,通过不同的十六进制可让数码管显示不同的字符,如0x3F代表1,0x06代表2

void blink_3(void)
{
	static u8 j;
	P2 = LED_Index[j];
	j++;
	if(j > 8) j = 0;
	delay_ms(500);
}

void blink_4(void)
{
	static u8 k;
	P0 = SEG_Index[k];
	k++;
	if(k > 9) k = 0;
	delay_ms(500);
}

顺着数组做for循环,通过前面定义好的数组就可以实现LED流水灯或数码管计数了


I/O输入与输出控制按键

按键检测代码

while(1)
{
    delay_ms(20); // 延时20毫秒
    if(KEY1 == 0) // 检测按键KEY1是否被按下
    {
        if(i > 3) i = 0; 
        P2 = LED[i]; // 将P2端口的输出设置为LED数组的第i个元素
        i++;
        while(KEY1 == 0); // 等待按键释放(按键防抖或按键长按处理)
    }
}

注意需要添加消抖延迟函数 & 等待按键释放的while函数

延时代码

void delay_ms(u16 ms)
{
    u16 i;
    do{
        i = MAIN_Fosc / 6000;
        while(--i);
    }while(--ms);
}

例:需要延时200ms → delay_ms(200)


定时器中断

需要去ISP软件中选择“定时器计算器”工具,生成所需要的定时器初始化函数与定时器执行函数。

例:主频为24.000MHz进行500毫秒中断的定时器函数

void Timer0_Init(void)	
{
	TM0PS = 0x0F;
	AUXR &= 0x7F;
	TMOD &= 0xF0;
	TL0 = 0xDC;	
	TH0 = 0x0B;	
	TF0 = 0;				
	TR0 = 1;			
	ET0 = 1;
	EA=1;				
}

注意!

  • 一定要有EA=1定时器中断才能正常开启。
  • 需要在main函数中进行初始化: Timer0_Init();

定时器执行函数

void Timer0_Isr(void) interrupt 1
{
	//在这里编写需要执行的代码
}

定时器的周期调用

u16 Count_ms[3] = {0, 0, 0};
u8 state1=1, state2=1, state3=1, state4=1;

void Timer0_Isr(void) interrupt 1
{
	u8 i;
	for(i=0; i<3; i++)
	{
		Count_ms[i]++;
	}
	if(Count_ms[0]>=300)
		{
			Count_ms[0] = 0;
			state1 = !state1;
			P20 = state1;
			P23 = !state1;
		}
	if(Count_ms[1]>=600)
		{
			Count_ms[1] = 0;
			state2 = !state2;
			P21 = state2;
		}
	if(Count_ms[2]>=900)
		{
			Count_ms[2] = 0;
			state3 = !state3;
			P22 = state3;
		}
		if(state1==0 && state2==0 && state3==0) P24=0;
		else P24=1;
}
  • 定时器中断的进阶用法: 通过定义三个不同阈值的计数器,定时器每中断一次,计数器加一。 实现在300ms,600ms,900ms时分别中断执行不同任务

数码管与定时器

任务:按下KEY1,数码管开始计时(如00:00:00), 按下KEY2,数码管显示年月日(如2024:12:11), 按下KEY3,数码管计时暂停,再按一下KEY3又恢复计时 按下KEY4,数码管计时清零。

while(1)
    {
        delay_ms(20);
        if(P31 ==0){  
            state=1;  
            while(P30==0);
        }
        if(P30 == 0){  
            state=0;
            while(P31==0);
        }
        if(P32 == 0){  
            if(state==1){
                state=2;  
            }else if(state==2){
                state=1; 
            }
            while(P32==0);
        }
        if(P33 == 0){ 
            miao=fen=shi=0;
            while(P33==0);
        }
        
        switch(state){
            case 0:seg_date();break;
            case 1:seg_count();break;
            case 2:seg_count_pause();break;
        }
    }

在while1中持续检测按键情况,哪个按键被按下则执行相对应的任务

数码管显示日期

void seg(u8 loc, u16 num)
{
    switch(loc)
    {
        case 1:P24=1;P23=1;P22=1;break;
        case 2:P24=1;P23=1;P22=0;break;
        case 3:P24=1;P23=0;P22=1;break;
        case 4:P24=1;P23=0;P22=0;break;
        case 5:P24=0;P23=1;P22=1;break;
        case 6:P24=0;P23=1;P22=0;break;
        case 7:P24=0;P23=0;P22=1;break;
        case 8:P24=0;P23=0;P22=0;break;
    }
    P0=SEG_NUM[num];
    delay_ms(1); //延时的目的是为了让数码管有足够的时间显示当前的数字,避免显示闪烁
    P0 = 0x00;  //在显示下一个数字之前,先将当前的显示清除,避免串扰
}
// 该函数用于在不同的数码管位置显示不同的数字,每个case对应一个数码管的位置

void seg_date(void)
{
    seg(1,2);seg(2,0);seg(3,2);seg(4,5);
    seg(5,0);seg(6,1);seg(7,0);seg(8,1);
}

如何用数码管显示20250101

数码管显示计数器

void Timer0_Isr(void) interrupt 1
{
    if(state==1){ 
        miao ++;
        if( miao>59 )
        {
            miao = 0;
            fen++;
            if( fen>59 )
            {
                fen = 0;
                shi ++;
                if( shi>23 )
                    shi = 0;
            }
        }
    }
}

利用定时器中断进行时分秒计数

void seg_count_pause(void)
{
    seg(1,shi/10);seg(2,shi%10);seg(3,SEG_NUM[16]);seg(4,fen/10);
    seg(5,fen%10);seg(6,SEG_NUM[16]);seg(7,miao/10);seg(8,miao%10);
}

将计数器显示在数码管上


PWM

任务:PWM驱动电机,按键K1按下,电机转速提高(一共三档),按键K2按下,电机转速减慢,按键K3按下,电机停止

while(1)
	{
		delay_ms(20);
		if(P31==0)
		{
			Speed++;
			if(Speed > 3) Speed = 3; 
			while(P31==0);
		} 
		if(P30==0)
		{
			Speed--;
			if(Speed < 0) Speed= 0;
			while(P30==0);
		}
		if(P33==0)
		{
			Speed = 0;
			while(P33==0);
		}
		switch(Speed)
		{
			case 0: Compare = 0; break;    
		    case 1: Compare = 50; break;   
		    case 2: Compare = 75; break;   
		    case 3: Compare = 100; break;
		}
		seg(1,Speed);
	}
}

一个普通的按键检测代码,按下对应按键执行对应速度并在数码管显示

通过定时器中断来控制电机的占空比(PWM功能)

先上定时器中断执行代码:

void Timer0_Isr(void) interrupt 1
{
	TL0 = 0x38;				
	TH0 = 0xFF;				
	counter++;
	counter%=100;
	if(counter<Compare)
	{
		motor = 1;
	}else
	{
		motor = 0;
	}
}

逐步分析:

TL0 = 0x38;				
TH0 = 0xFF;				
  • TL0TH0 是定时器0的低8位和高8位寄存器。 这两行代码的作用是重新装载定时器0的初始值
  • 0x380xFF 是定时器的初始值,具体数值取决于单片机的时钟频率和定时器的模式。这些值决定了定时器溢出的时间间隔
  • 每当定时器溢出时,就会触发中断,执行这个中断服务例程。
counter++;
counter %= 100;

用于记录定时器中断的次数,当等于100时清零

PWM逻辑

if (counter < Compare)
    motor = 1;
else
    motor = 0;

Compare表示PWM的占空比阈值

  • 这段代码的作用是根据计数器的值和占空比阈值来控制电机的状态:

    • 如果 counter 小于 Compare,则将 motor 设置为1(表示电机开启或输出高电平)。

    • 如果 counter 大于或等于 Compare,则将 motor 设置为0(表示电机关闭或输出低电平)。

通过调整 Compare 的值,可以改变PWM的占空比,从而控制电机的速度或功率

PWM原理

  • 占空比:占空比是指在一个周期内,高电平时间与总周期时间的比例。例如,如果占空比为50%,则在一个周期内,高电平和低电平各占一半时间。

  • 在这段代码中,counter 的范围是0到99,总共100个计数值。Compare 的值决定了高电平的持续时间。例如:

    • 如果 Compare = 50,则高电平持续50个计数值,占空比为50%。

    • 如果 Compare = 20,则高电平持续20个计数值,占空比为20%。