njslmcc
发表于 2025-9-15 07:03:22
第八集 定时器周期性调度任务 学习笔记
如何修正编译错误,
ISP更多设置,分包时间显示与否
一、用期性任务介绍
任务1:用一个定时器实现这个任务。LED1实现0.3秒取反一次,LED2实现06秒取反一次,LED3实现0.9秒取反一次
数组使用分两步:
1、定义:类型 名称[长度] = {数值};
2、使用:赋值:名称[索引] = 数值
任务2:数组点亮LED,实现流水灯
注意事项:LED是0亮1灭,数组长度要把握好,流水灯移动的时间
变量定义
u8 State = {0x01,0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
核心代码
if(Count_ms>=500)
{
Count_ms = 0;
P0 = ~State; //num 取值0-7
num++;
if(num>7) num=0;
}
任务3:按键1按一下,LED通过数组移动一下
注意事项:按键不能再通过while判断是否按下松开了,可以通过按键按下计数
变量定义
u16 Key_Vol = 0; //按键按下持续时间
核心代码
P0 = ~State; //num 取值0-7
if(Count_ms==1000) //1000ms执行一次
{
Count_ms = 0;
printf("Ai8051u\r\n");
}
if(Count_ms==10) //10ms执行一次
{
Count_ms = 0;
if(P32 == 0) //按键按下
{
Key_Vol ++;
if(Key_Vol==5) //按键按下
{
num ++;
}
}
else
{
Key_Vol = 0;
}
}
二、文件的创建(.c和.h)
创建程序文件三步,把硬件需要的初始化做一个config.c
新建文件并保存,添加到工程,添加到引用路径
一般一个.c和一个.h文件,执行一个外设或者一个任务或功能,这样可以让代码看起来简洁明了
新建XXX.c和XXX.h文件,代表一个功能块,
XXX.h格式:
#ifndef __XXX_H
#define __XXX_H
调用头文件
函数声明,
#endif
XXX.C格式
#inlude “XXX.H”
函数定义
添加文件一定要记得引用路径和添加一工程里。
三、结构体的介绍
四、结构体数组的周期性任务调度
LED1 0.3秒闪一次,
LED2 0.6秒闪一次,
LED3 0.9秒闪一次
1、都有定时器1ms加的变量
2、都有一定设定的计数目标
3、都有需要执行的功能
4、定时时间到了才能执行
typedef struct
{
u8 Run; // 任务状态,Run/Stop
u16 TIMCount; // 定时器计数器
u16 TRITime; // 重载计数器
void (*TaskHook)(void); //任务函数
}TASK_COMPONENTS;
任务4:按键1按一下,LED通过数组移动一下,用虚拟LED显示
思路:
变量定义
u8 State = {0x01,0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}; //事先编排好
u8 Key_Vol = {0, 0};
核心代码
void Key_Task(void)
{
if (P32 == 0)
{
Key_Vol++;
if (Key_Vol==5)
{
//按键按下的任务
P0 = ~State];
Key_Vol++;
}
}
else
{
Key_Vol = 0;
}
}
简易舞台灯光控制系统:
1、按下按钮1,一键启动或关闭LED动作
2、启动后电脑上的LED开始工作
P0口的LED灯循环往下移动
P2口的LED灯循环往上移动
P4口的LED高四位和低四位交替闪烁
P1口的8个LED灯全亮或全灭交替(P1.2的位置用P5.2代替)
在Task.c 中写上如下任务表:
TASK_COMPONENTS Task_Comps[]=
{
//状态计数 重载数 函数
{0, 10, 10, Key_Task}, /* task 1 Period: 10ms */
{0, 100, 100, LED1_Task}, /* task 2 Period: 100ms */
{0, 200, 200, LED2_Task}, /* task 3 Period: 200ms */
{0, 300, 300, LED3_Task}, /* task 4 Period: 300ms */
{0, 400, 400, LED4_Task}, /* task 5 Period: 400ms */
};
在io.c中写上:
//按下按钮1,一键启动或关闭LED动作
u8 Stage1 = 0; //LED的初值,LED可以为P3上的一个口
u8 Key_Vol = 0;
void Key_Task(void)
{
if (P32==0)
{
Key_Vol++;
if (Key_Vol==5)
{
//按键按下的任务
Stage1 = !Stage1;
LED0 = Stage1;
}
}
else
{
Key_Vol = 0;
}
}
// P0口的LED灯循环往下移动
u8 Stage2 = 0; //初值
u8 LED1 = {0x01,0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}
void LED1_Task(void)
{
P0 = ~LED1(Stage2);
Stage2++;
if (Stage2>7)Stage2 = 0;
}
// P2口的LED灯循环往上移动
u8 Stage3 = 0; //初值
u8 LED2 = {0x80,0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01}
void LED2_Task(void)
{
P2 = ~LED2(Stage3);
Stage3++;
if (Stage3>7)Stage3 = 0;
}
// P4口的LED高四位和低四位交替闪烁
u8 Stage4 = 0;//初值
u8 LED4 = {0x0f, 0xf0}
void LED3_Task(void)
{
P4 = LED4(Stage4);
Stage4++;
if (Stage4>1)Stage4 = 0;
}
// P0 P1口的8个LED灯全亮或全灭交替(P1.2的位置用P5.2代替)
u8 Stage5 =0;//初值
u8 LED5 = {0x00,0xff}
void LED4_Task(void)
{
P0 = P1 = LED5(Stage5); //(P1.2的位置用P5.2代替)怎么实现?
Stage5++;
if (Stage5>1)Stage5 = 0;
}
在io.h中写上
void Key_Task(void);
void LED1_Task(void);
void LED2_Task(void);
void LED3_Task(void);
void LED4_Task(void);
这一课的内容很有用,再遇到类似的多任务,只要做好如下3步:
1、在Task.c的列表中按“状态,计数,重载数,函数”的格式加上新的任务,
2、在io.c中写好函数
3、在io.h中登记一下
njslmcc
发表于 2025-9-17 06:03:32
第九集 数码管 学习笔记
擎天柱交流网址:https://www.stcaimcu.com/thread-11902-1-1.html
里面会有注意事项及常见编程错误等信息
一、数码管介绍
本质都是LED,类型分共阴共阳,74HC595
二、数码管显示原理
显示段码,位码,总显示时间不能大于20ms
三、数码管静态显示
任务1:在第一位上显示0
在任务列表中加入
{0,1, 1, Seg_Task},
在io.c中加入代码:
u8 SEG_NUM[]=
{
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*/
0x77, /*'A', 10*/
0x7C, /*'B', 11*/
0x39, /*'C', 12*/
0x5E, /*'D', 13*/
0x79, /*'E', 14*/
0x71, /*'F', 15*/
0x40, /*'-', 16*/
0x00, /*' ', 17*/
0x80, /*'.', 18*/
};
u8 T_NUM =
{
0X01,0X02,0X04,0X08,0X10,0X20,0X40,0X80
};
void Init_595(void)
{
HC595_SER = 0;
HC595_RCK = 0;
HC595_SCK = 0;
}
/************向HC595发送一个字节函数************/
void Send_595(u8 dat)
{
u8 i;
for (i=0;i<8;i++)
{
dat<<=1;
HC595_SER = CY; //先把数据写到引脚上
HC595_SCK = 1; //输出上升沿的时钟信号
HC595_SCK = 0;
}
}
void Display_Seg(u8 HC595_1, u8 HC595_2)
{
Send_595(HC595_1);//数码管段码输出,高电平点亮
Send_595(HC595_2);//数码管位码输出,低电平点亮
HC595_RCK = 1; //数据输出
HC595_RCK = 0;
}
u8 Seg_no = 0;
void Seg_Task(void)
{
Display_Seg( SEG_NUM, ~T_NUM);
}
在io.h中增加如下定义:
#define HC595_SER P34
#define HC595_RCK P35
#define HC595_SCK P32
void Init_595(void);
void Send_595(u8 dat);
void Display_Seg(u8 HC595_1, u8 HC595_2);
void Seg_Task(void);
就可以在第一位显示出“0”了。
四、数码管动态显示
任务2,显示12345678
只要改写下列函数
void Seg_Task(void)
{
Display_Seg( SEG_NUM, ~T_NUM); //刷段码和位码
Seg_no++;
if(Seg_no>7)
Seg_no = 0;
}
任务3,按00-00-00的格式显示时分秒
在任务列表中加入
{0, 1000,1000,TIMECOUNT_Task}, /* task 5 Period: 1000ms */
在io.c中加入代码
u8 shi= 0;
u8 fen= 0;
u8 miao = 0;
void TIMECOUNT_Task(void)
{
miao ++;
if( miao>59 )
{
miao = 0;
fen++;
if( fen>59 )
{
fen = 0;
shi ++;
if( shi>23 )
shi = 0;
}
}
}
void Seg_Task(void)
{
for(i=0;i<8;i++)
{
//算出每一位的值并显示出来
Display_Seg( SEG_NUM, ~T_NUM);
}
Seg_no++;
if(Seg_no>7)
Seg_no = 0;
}
在io.h中增加如下定义:
void TIMECOUNT_Task(void);
就能在LED上显示时分秒了
五、虚拟显示—LED和数码管
(有空的时候可以看一下)
课后小练习
简易10秒免单计数器,
1、在前四位数码管上显示目标时间,即“10.00”表示定时时间10秒钟
2、后四位显示当前的计时00.00,最小单位为10ms
3、按下开始按钮后,每10ms最末尾的数字+1,直到按下结束按钮后停止计数。
在任务表添加两个任务
{0,1, 1, Seg_Task},
{0, 10,10,TIMECOUNT_Task},
在io.c中加入如下代码:
u8 Stage1 = 0;
u16 Key_Vol = {0, 0};
void TIMECOUNT_Task(void)
{
if (P32==0)
{
Key_Vol++;
if (Key_Vol==5)
{
//按键按下的任务
Stage1 = !Stage1;
if(Stage1==0)
Key_Vol++;
else
Key_Vol=0;
}
}
else
{
Key_Vol = 0;
}
}
void Seg_Task(void)
{
if(Seg_no==0)
{
num = 1;
Display_Seg( SEG_NUM, ~T_NUM);
}
else if(Seg_no==1)
{
num = 0;
Display_Seg( SEG_NUM, ~T_NUM);
}
else if(Seg_no==2)
{
num = 0;
Display_Seg( SEG_NUM, ~T_NUM);
}
else if(Seg_no==3)
{
num = 0;
Display_Seg( SEG_NUM, ~T_NUM);
}
else if(Seg_no==5)
{
num = Key_Vol/1000;
Display_Seg( SEG_NUM, ~T_NUM);
}
else if(Seg_no==6)
{
num = (Key_Vol%1000)/100;
Display_Seg( SEG_NUM, ~T_NUM);
}
else if(Seg_no==7)
{
num = (Key_Vol%100)/10;
Display_Seg( SEG_NUM, ~T_NUM);
}
else if(Seg_no==8)
{
num = Key_Vol%10;
Display_Seg( SEG_NUM, ~T_NUM);
}
Seg_no++;
if(Seg_no>7)
Seg_no = 0;
}
njslmcc
发表于 2025-9-18 09:26:44
第十集 虚拟键盘LED和数码管 学习笔记
摘要
一、虚拟显示——LED
1、硬件:擎天柱
2、软件:最新版的ISP
3、参数设置:仿真调试接口,接口设置
4、选择指定的接口和协议:
功能1:控制DIP40的各个管脚上LED的状态
命令格式:4CH 45H 44H 28H x1 x2 px
命令头 数据长度 P0~P5的屏蔽位 P0~P5的状态
库函数声明:void LED40_SendData(BYTE * dat, BYTE size);
功能2:控制DIP40的指定管脚上LED的状态
命令格式:4CH 45H 44H 28H 第5字节 第6字节 第7字节
命令头 数据长度 P0~P5的端口屏蔽位 端口的状态
端口屏蔽位(01H:P0,02H:P1,04H:P2,08H:P3,10H:P4,20H:P5)
库函数声明:void LED40_SetPort(BYTE port, BYTE dat);
其中函数已封装,用起来更方便
port中0代表P0
port中1代表P1
port中2代表P2
port中3代表P3
port中4代表P4
port中5代表P5
功能3:控制DIP40的指定管脚输出高电平
命令格式:4CH 45H 44H 28H 第5字节 第6字节 第7字节
命令头 数据长度 (02H) 固定为00H 端口的状态数据
端口的状态数据
px的bit7,固定为1
px的bit6,bit5,bit4,bit3,指定第几组管脚(0:P0,1:P1,2:P2,。。。。)
px的bit2,bit1,bit0,指定管脚的第几位(0:bit0,1:bit1,。。。。)
库函数声明:void LED40_SetBit(BYTE port, BYTE bt);
功能4:控制DIP40的指定管脚输出低电平
命令格式:4CH 45H 44H 28H第5字节 第6字节 第7字节
命令头 数据长度(02H) 固定为00H 端口的状态数据
端口的状态数据
px的bit7,固定为0
px的bit6,bit5,bit4,bit3,指定第几组管脚(0:P0,1:P1,2:P2,。。。。)
px的bit2,bit1,bit0,指定管脚的第几位(0:bit0,1:bit1,。。。。)
库函数声明:void LED40_ClrBit(BYTE port, BYTE bt);
任务1,P2口流水灯,P10闪烁
把定时器周期性调度任务拷过来,下载最新的USB库函数,头文件更新
1、在task.c中建立一个任务
{0, 500, 500, TASK_1},
2、在io.c中加入如下代码:
u8 P2_State = 0x01;
u8 P10_State = 0;
void TASK_1(void)
{
//-----------p2端口流水灯---------------
LED40_SetPort(2,~P2_State); //点亮P20
P2_State = (P2_State<<1);
if(P2_State == 0)
P2_State = 1;
//-----------p10闪烁---------------
if(P10_State == 0)
LED40_SetBit(1,0);
else
LED40_ClrBit(1,0);
P10_State = !P10_State;
}
3、在io.h中添加定义:
void TASK_1(void);
即可完成任务1。
二、虚拟显示——数码管
功能1:在数码管上显示字符串
命令格式:37H 53H 45H 47H 53H 00H 00H 00H 00H s1 s2 s3 s4
命令头 功能选择保留 字符的ASC码,以’\0’结尾
库函数声明:void SEG7_ShowString(const char *fmt,…….);
库函数调用:SEG7_ShowString(“%08lx”,0x1234abcdL);
功能2:在数码管上显示4字节长整形数
命令格式:37H 53H 45H 47H 4CH x1 x2 00H
命令头 功能选择存储格式0:LE,1:BE 进制 2/10/16保留
后面的d1 d2 d3 d4为四字节长整形数
库函数声明:void SEG7_ShowLong(long n, char radix);//n: 长整形数,rdix:进制
库函数调用:SEG7_ShowLong(0x876432,16);
任务2:左边数码管显示P32按下次数,右边数码管显示P33按下次数
1、在task.c中建立2个任务
{0, 10, 10, Key_Task}, /* task 4 Period: 10ms */
{0, 500, 500, TASK_2},
2、在io.c中加入如下代码:
u16 Key1_Count = 0; //P32按键次数
u16 Key2_Count = 0; //P33按键次数
u32 Key_Count = 0;
void Key_Task(void)
{
//-----------P32按键次数---------------
if (P32 == 0)
{
Key_Vol++;
if (Key_Vol==5)
{
//按键按下的任务
Key1_Count++;
}
}
else
{
Key_Vol = 0;
}
//-----------P33按键次数---------------
if (P33 == 0)
{
Key_Vol_2++;
if (Key_Vol_2==5)
{
//按键按下的任务
Key2_Count++;
}
}
else
{
Key_Vol_2 = 0;
}
}
void TASK_2(void)
{
Key_Count =Key1_Count*10000 +Key2_Count;
SEG7_ShowLong(Key_Count,10);
}
3、在io.h中加上
void Key_Task(void);
void TASK_2(void);
即可完成任务2。
三、虚拟键盘
虚拟键盘接口
功能:在虚拟键盘上按键,然后发送十一日键值到设备
命令格式:4BH 45H 59H50H 00H x1 00H 00H
命令头 保留 按键键值保留
比如收到35H(十进制53),就说明按下了5
任务3:按下数字按键,在数码管显示相应的按键数字
1、在task.c中建立任务
{0, 10, 10, Key_Task}, /* task 4 Period: 10ms */
{0, 500, 500, TASK_3},
2、在io.c中加入代码
u32 Rec_Num = 0;
void TASK_3(void)
{
SEG7_ShowLong(Rec_Num,10);
}
3、在io.h中添加
void Key_Task(void);
void TASK_3(void);
4、在main.c中加入定义
extern u32 Rec_Num;
在主程序循环中加入
if (bUsbOutReady) //如果接收到了数据
{
//USB_SendData(UsbOutBuffer,OutNumber); //发送数据缓冲区,长度(接收数据原样返回, 用于测试)
//printf("");
Rec_Num = UsbOutBuffer;
usb_OUT_done();
}
即可完成任务3。
课后小练
密码锁
1、没有输入时,显示“--------”
2、有输入时,按下一个按键,开始按顺序写入,比如第个按1,显示“1-------”
3、当按下的密码为“12345678”时,数码管显示open的字符,否则,还是显示“--------”
//-------------试做了一下,逻辑也不是很对,后面再探究问题在哪里------------------
1、在task.c中建立任务
{0, 10, 10, Key_Task}, /* task 4 Period: 10ms */
{0, 500, 500, TASK_4},
2、在io.c中加入代码
u32 Show_Str = {-};
u32 Rec_Str = {-};
u32 Rec_Num = 0;
u8 num = 0;
//--------------这一段还要完善-----------
void NumToStr(void)
{
if (Rec_Num是数字)赋值 Rec_Str;
num++;
if (num>7)
{
if ( Rec_Str = "12345678"
{
Show_Str = "--OPEN--";
}
else
{
Show_Str = "--------";
}
}
}
void TASK_4(void)
{
SEG7_ShowString(Show_Str); //
}
3、在io.h中添加
void Key_Task(void);
void TASK_4(void);
void NumToStr(void);
4、在main.c中加入定义
extern u32 Rec_Num;
在主程序循环中加入
if (bUsbOutReady) //如果接收到了数据
{
//USB_SendData(UsbOutBuffer,OutNumber); //发送数据缓冲区,长度(接收数据原样返回, 用于测试)
//printf("");
Rec_Num = UsbOutBuffer;
usb_OUT_done();
}
njslmcc
发表于 2025-9-18 17:54:30
第十一集 矩阵按键 学习笔记
用行和列组合,以减少IO口的使用数量
任务1:数码管显示当前的按键号
把多任务定时器那个目录整个拷过来
在main.c中,加入
Init_595();//在EA = 1;之前
在task.c中建立2个任务
{0, 10, 10, Task_1},
{0, 100, 100, Seg_Task},
在io.c中加入
u8 SEG_NUM[]=
{
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*/
0x77, /*'A', 10*/
0x7C, /*'B', 11*/
0x39, /*'C', 12*/
0x5E, /*'D', 13*/
0x79, /*'E', 14*/
0x71, /*'F', 15*/
0x40, /*'-', 16*/
0x00, /*' ', 17*/
0x80, /*'.', 18*/
};
u8 T_NUM =
{
0X01,0X02,0X04,0X08,0X10,0X20,0X40,0X80
};
void Init_595(void)
{
HC595_SER = 0;
HC595_RCK = 0;
HC595_SCK = 0;
}
/************向HC595发送一个字节函数************/
void Send_595(u8 dat)
{
u8 i;
for (i=0;i<8;i++)
{
dat<<=1;
HC595_SER = CY; //先把数据写到引脚上
HC595_SCK = 1; //输出上升沿的时钟信号
HC595_SCK = 0;
}
}
void Display_Seg(u8 HC595_1, u8 HC595_2)
{
Send_595(HC595_1);//数码管段码输出,高电平点亮
Send_595(HC595_2);//数码管位码输出,低电平点亮
HC595_RCK = 1; //数据输出
HC595_RCK = 0;
}
u8 Seg_no = 0;
void Seg_Task(void)
{
if(key_num == 0xff)
Display_Seg( SEG_NUM, ~T_NUM);
else
Display_Seg( SEG_NUM, ~T_NUM);
}
u8 key_num = 0xff;
//任务1:数码管显示当前的按键号
void Task_1(void)
{
COL1 = 0;
COL2 = 0;
COL3 = 0;
COL4 = 0;
ROW1 = 1;
ROW2 = 1;
if((ROW1 == 0)||(ROW2 == 0))
{
if(( ROW1 == 0 ) && ( ROW2 == 0 ))
{
;
}
else if((( ROW1 == 0 ) && ( ROW2 == 1 )) || (( ROW1 == 1 ) && ( ROW2 == 0 )))
{
if( ROW1 == 0) //判断行起始序号
key_num = 0;
else if( ROW2 ==0)
key_num = 4;
COL1 = 1;
COL2 = 1;
COL3 = 1;
COL4 = 1;
ROW1 = 0;
ROW2 = 0;
if(COL1 == 0) //判断列起始序号
{
key_num += 0;
}
else if(COL2 == 0)
{
key_num += 1;
}
else if(COL3 == 0)
{
key_num += 2;
}
else if(COL4 == 0)
{
key_num += 3;
}
}
COL1 = 0;
COL2 = 0;
COL3 = 0;
COL4 = 0;
ROW1 = 1;
ROW2 = 1;
}
else
{
key_num = 0xff;
}
}
在io.h中加入
#define HC595_SER P34
#define HC595_RCK P35
#define HC595_SCK P32
#define ROW1 P06
#define ROW2 P07
#define COL1 P00
#define COL2 P01
#define COL3 P02
#define COL4 P03
//extern u8 key_num;
void Task_1(void);
void Init_595(void);
void Seg_Task(void);
编译下载,运行OK。
任务2:密码锁
1、没有输入时,显示“--------”
2、有输入时,按下一个按键,开始顺序写入
3、当按下的密码为“12345670”,数码管显示“ 1”
否则,还是显示“--------”
在main.c中,加入
Init_595(); //在 EA = 1;之前
在Task.c中,加入
{0, 10, 10, Task_1},
{0, 1, 1, Seg_Task},
{0, 10, 10, PW_write_Task},
在io.c中,加入
u8 SEG_NUM[]=
{
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*/
0x77, /*'A', 10*/
0x7C, /*'B', 11*/
0x39, /*'C', 12*/
0x5E, /*'D', 13*/
0x79, /*'E', 14*/
0x71, /*'F', 15*/
0x40, /*'-', 16*/
0x00, /*' ', 17*/
0x80, /*'.', 18*/
};
u8 T_NUM =
{
0X01,0X02,0X04,0X08,0X10,0X20,0X40,0X80
};
#define HC595_SER P34
#define HC595_RCK P35
#define HC595_SCK P32
void Init_595(void)
{
HC595_SER = 0;
HC595_RCK = 0;
HC595_SCK = 0;
}
/************向HC595发送一个字节函数************/
void Send_595(u8 dat)
{
u8 i;
for (i=0;i<8;i++)
{
dat<<=1;
HC595_SER = CY; //先把数据写到引脚上
HC595_SCK = 1; //输出上升沿的时钟信号
HC595_SCK = 0;
}
}
void Display_Seg(u8 HC595_1, u8 HC595_2)
{
Send_595(HC595_1);//数码管段码输出,高电平点亮
Send_595(HC595_2);//数码管位码输出,低电平点亮
HC595_RCK = 1; //数据输出
HC595_RCK = 0;
}
u8 Seg_no = 0;
u8 password = {16,16,16,16,16,16,16,16};
void Seg_Task(void)
{
u8 num = 0;
if(Seg_no==0)
{
Display_Seg( SEG_NUM], ~T_NUM);
}
else if(Seg_no==1)
{
Display_Seg( SEG_NUM], ~T_NUM);
}
else if(Seg_no==2)
{
Display_Seg( SEG_NUM], ~T_NUM);
}
else if(Seg_no==3)
{
Display_Seg( SEG_NUM], ~T_NUM);
}
else if(Seg_no==4)
{
Display_Seg( SEG_NUM], ~T_NUM);
}
else if(Seg_no==5)
{
Display_Seg( SEG_NUM], ~T_NUM);
}
else if(Seg_no==6)
{
Display_Seg( SEG_NUM], ~T_NUM);
}
else if(Seg_no==7)
{
Display_Seg( SEG_NUM], ~T_NUM);
}
Seg_no++;
if(Seg_no>7)
Seg_no = 0;
}
u8 Key_Vol3 = 0;
u8 key_no = 0;
void PW_write_Task(void)
{
if (key_num != 0xff)
{
Key_Vol3++;
if (Key_Vol3==5)
{
//按键按下的任务
if( key_no == 0 )
{
password = 16;
password = 16;
password = 16;
password = 16;
password = 16;
password = 16;
password = 16;
password = 16;
}
password = key_num;
key_no++;
//password = 17;
if(key_no == 8) //密码输入完毕
{
if ((password == 1) &&
(password == 2) &&
(password == 3) &&
(password == 4) &&
(password == 5) &&
(password == 6) &&
(password == 7) &&
(password == 0))
{
password = 17;
password = 17;
password = 17;
password = 17;
password = 17;
password = 17;
password = 17;
password = 1;
}
else
{
password = 16;
password = 16;
password = 16;
password = 16;
password = 16;
password = 16;
password = 16;
password = 16;
}
key_no = 0;
}
}
}
else
{
Key_Vol3 = 0;
}
}
u8 key_num = 0xff;
//任务1:数码管显示当前的按键号
void Task_1(void)
{
COL1 = 0;
COL2 = 0;
COL3 = 0;
COL4 = 0;
ROW1 = 1;
ROW2 = 1;
if((ROW1 == 0)||(ROW2 == 0))
{
if(( ROW1 == 0 ) && ( ROW2 == 0 ))
{
;
}
else if((( ROW1 == 0 ) && ( ROW2 == 1 )) || (( ROW1 == 1 ) && ( ROW2 == 0 )))
{
if( ROW1 == 0) //判断行起始序号
key_num = 0;
else if( ROW2 ==0)
key_num = 4;
COL1 = 1;
COL2 = 1;
COL3 = 1;
COL4 = 1;
ROW1 = 0;
ROW2 = 0;
if(COL1 == 0) //判断列起始序号
{
key_num += 0;
}
else if(COL2 == 0)
{
key_num += 1;
}
else if(COL3 == 0)
{
key_num += 2;
}
else if(COL4 == 0)
{
key_num += 3;
}
}
COL1 = 0;
COL2 = 0;
COL3 = 0;
COL4 = 0;
ROW1 = 1;
ROW2 = 1;
}
else
{
key_num = 0xff;
}
}
在io.h中加入
#define HC595_SER P34
#define HC595_RCK P35
#define HC595_SCK P32
#define ROW1 P06
#define ROW2 P07
#define COL1 P00
#define COL2 P01
#define COL3 P02
#define COL4 P03
extern u8 key_num;
void Key_Task(void);
void Task_1(void);
void Init_595(void);
void Seg_Task(void);
void PW_write_Task(void);
编译成功,运行OK
课后练习题目
简易洗衣机面板1、按下开机键后,数码管显示1,表示默认为清洗模式12、用矩阵按键模拟洗衣机的操作面板,前五个按钮模拟1-5的清洗模式按键选择几就显示数字几,表示当前为第几个功能3、按下启动后,按照选择的模式对应的时间开始倒计时,倒计时结束后,数码管熄灭,表示清洗完成,清洗时间任意设定。
刘师傅电路维修
发表于 2025-9-19 06:04:06
你遇到过那家产品很牛的公司,是哪家,我去观摩一番看看对我有没有帮助,遇到问题直接给一个光盘,这点我在感觉上,很是好奇,既然公司很牛,都什么年代了,还用光盘记载资料,资料容量肯定很庞大吧,说所有的开发问题这里面都有答案,一个小问题就要去研读一整张光盘,这对我们普通开发者而言,也太恐怖了,最后只能放弃,就当前的局势来说,光盘的价值重点不在于研读,而是用来收藏,异或是喂给ai模型去学,遇到小问题我会另辟蹊径去解决问题。只有大问题,譬如STC-ISP下载软件bug,我才会考虑发问,我是跳过学习就直接尝试做产品的新人,尽量避免无效的学习。这就好比一些博士,高学历的人,耗尽年华,所学知识,98%一辈子都用不上,一边学一边遗忘,沉默成本太高了,耗散了社会资源,我得出结论,知识也不太值得去学,只要能看得懂就足矣。
njslmcc
发表于 2025-9-21 17:01:32
刘师傅电路维修 发表于 2025-9-19 06:04
你遇到过那家产品很牛的公司,是哪家,我去观摩一番看看对我有没有帮助,遇到问题直接给一个光盘,这点我在 ...
谢谢你的回复,你提出的问题在AI时代更加突出,人类几乎所有学到的知识在AI面前都被秒杀了,在这种情况下,现有的教育方式已经没有未来,下一步连人类继续存在的意义都是个问题。
我们这里只是记录一下自己的学习体会,不用纠结那些问题。
njslmcc
发表于 2025-9-21 17:39:37
第十二集 复位系统 学习笔记
确保系统处于确定状态:
复位操作可以确保单片机在开始工作时处于已知的状态,使其能够正确初始化各个寄存器和外设。
避免不确定行为:
没有进行复位时,内部控制寄存器的内容可能是随机的,这可能导致定时器溢出、中断异常、外设误操作等不确定行为。
初始化系统:
复位操作可以进行系统的初始化,包括清除寄存器、设置默认值、配置时钟等,为系统正常运行做好准备。
保证程序正常开始执行:
复位确保程序从正确的地址开始执行,避免跳转到未知的地址或执行错误的指令
一、硬件复位
1、对复位时间要求不高,可选上电复位。对复位时间要求高,P32 P33 至少一个上拉
2、低压复位,复位电压可选,也可设中断
3、硬件复位,复位脚(第9脚)用做 IO口的勾去掉,接300欧电阻到地,成传统的复位脚
4、看门狗复位,要喂狗
任务1,编写看门狗程序
在main.c中输入如下语句:
在main.c中
while (DeviceState != DEVSTATE_CONFIGURED); //等待USB完成配置
WDT_CONTR = 0X24; //启动看门狗,0.5秒
while(1)
{
if (bUsbOutReady) //如果接收到了数据
{
//USB_SendData(UsbOutBuffer,OutNumber);//发送数据缓冲区,长度(接收数据原样返回, 用于测试)
usb_OUT_done(); //
}
Task_Pro_Handler_Callback(); //执行功能函数
//if(P33)!=0) //用于测试
WDT_CONTR = 0X34; //喂狗
}
在config.c中
void Delay10ms(void) //@24.000MHz
{
unsigned long edata i;
_nop_();
_nop_();
_nop_();
i = 59998UL;
while (i) i--;
}
void USB_Reset_U(void)
{
P3M0 = 0x00;
P3M1 = 0x00;
P3M0 &= ~0x03;
P3M1 |= 0x03;
USBCON = 0X00;
USBCLK = 0X00;
IRC48MCR = 0X00;
Delay10ms();
}
void Sys_init(void)
{
WTST = 0; //设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
EAXFR = 1; //扩展寄存器(XFR)访问使能
CKCON = 0; //提高访问XRAM速度
P0M1 = 0x00; P0M0 = 0x00;
P1M1 = 0x00; P1M0 = 0x00;
P2M1 = 0x00; P2M0 = 0x00;
P3M1 = 0x00; P3M0 = 0x00;
P4M1 = 0x00; P4M0 = 0x00;
P5M1 = 0x00; P5M0 = 0x00;
P6M1 = 0x00; P6M0 = 0x00;
P7M1 = 0x00; P7M0 = 0x00;
USB_Reset_U();
}
在config.h中
void Sys_init(void); //函数声明
void Timer1_Init(void); //1毫秒@24.000MHz
void USB_Reset_U(void);
void Timer10_Init(void); //10毫秒@24.000MHz
二、软件复位
只有IAP_COUTR一个寄存控制器
当IAP_COUTR = 0x60; //进入下载模式,可用P33按钮检测
当IAP_COUTR = 0x20; //软件复位,从头运行用户程序
void KEY_Task(void)
{
if( P33 == 0 )
{
Key_Vol++;
if( Key_Vol==5 )
{
//按键按下的任务
USB_Reset_U();
IAP_CONTR = 0X60; //0x60进入下载模式,0x20进入软件复位
}
}
else
{
Key_Vol = 0;
}
}
课后思考:
密码锁
1.没有输入时,显示“- - - - - - - -”
2.有输入时,按下一个按键,开始按顺序写入
例如,第一个按下1,显示“1 - - - - - - -”
例如,第二个按下3,显示“1 3 - - - - - -”
3.当按下的密码为“ 1 2 3 4 5 6 7 0”时,数码管显示“ 1”的字符,否则,还是显示“- - - - - - - -”
新增:
1.看门狗,超时1秒自动复位
2.增加开机版本号,开机显示三秒的U 1.00 版本号
3.增加手动复位,P33按钮按下时重启(方便查看版本号和清除密码)
思考如下:
1.看门狗,超时1秒自动复位
在main.c中
while (DeviceState != DEVSTATE_CONFIGURED); //等待USB完成配置
WDT_CONTR = 0X25; //启动看门狗,1秒 @24Mhz
while(1)
{
if (bUsbOutReady) //如果接收到了数据
{
//USB_SendData(UsbOutBuffer,OutNumber);//发送数据缓冲区,长度(接收数据原样返回, 用于测试)
usb_OUT_done(); //
}
Task_Pro_Handler_Callback(); //执行功能函数
WDT_CONTR = 0X34; //喂狗
}
config.c和config.h与上面一样
2.增加开机版本号,开机显示三秒的U 1.00 版本号
在Task中增加一个3秒的任务
{0, 3000, 3000, Task_3},
在io.c中
u 8 State3 = 0; //在Task3中State3=1;
void Seg_Task(void)
{
if(State3==0)
password = {17,17,17,17, 1,18,0,0}; //1.00 版本号
else
password = {16,16,16,16,16,16,16,16}; //--------
.....................................
}
3.增加手动复位,P33按钮按下时重启(方便查看版本号和清除密码)
void KEY_Task(void)
{
if( P33 == 0 )
{
Key_Vol++;
if( Key_Vol==5 )
{
//按键按下的任务
USB_Reset_U();
IAP_CONTR = 0X20; //0x60进入下载模式,0x20进入软件复位
}
}
else
{
Key_Vol = 0;
}
}
njslmcc
发表于 2025-9-21 22:09:39
第十三集 中断系统 学习笔记
1、中断系统介绍
当中央处理机 CPU 正在处理某件事的时候外界发生了紧急事件请求,CPU 暂停当前的工作,转而去处理这个紧急事件,处理完以后,再回到原来被中断的地方,继续原来的工作,这样的过程称为中断。实现这种功能的部件称为中断系统。
● CPU 总是先响应优先级别最高的中断请求
● CPU 能够暂停对原来中断源的服务程序,转而去处理优先级更高的中断请求源,处理完以后,再回到原低级中断服务程序,
● 每一个中断源可以用软件独立地控制为开中断或关中断
● 部分中断的优先级别均可用软件设置。高优先级的中断请求可以打断低优先级的中断
当中央处理机 CPU 正在处理某件事的时候外界发生了紧急事件请求,CPU 暂停当前的工作,转而去处理这个紧急事件,处理完以后,再回到原来被中断的地方,继续原来的工作,这样的过程称为中断。实现这种功能的部件称为中断系统。
● CPU 总是先响应优先级别最高的中断请求
● CPU 能够暂停对原来中断源的服务程序,转而去处理优先级更高的中断请求源,处理完以后,再回到原低级中断服务程序,
● 每一个中断源可以用软件独立地控制为开中断或关中断
● 部分中断的优先级别均可用软件设置。高优先级的中断请求可以打断低优先级的中断
2、外部中断介绍
外部中断就是在单片机的一个引脚上(带INT的),由于外部因素导致了一个电平的变化(比如由高变低),而通过捕获这个变化,单片机内部自主运行的程序就会被暂时打断,转而去执行相应的中断处理程序,执行完后又回到原来中断的地方继续执行原来的程序
外部中断0:IT0,IE0,EX0,EA
外部中断1:IT1,IE1,EX1,EA
外部中断x:x=2-4,INTxIF,EXn,EA
3、外部中断用法
任务一、编写外部中断1的程序
在main.c中
INT1_Init(); //外部中断1初始化
EA = 1; //IE |= 0X80;
在io.c中
void INT1_Init(void)
{
IT1 = 1; //下降沿中断
EX1 = 1; //打开中断允许
EA = 1; //打开总中断
}
void INT_ISR(void) interrupt 2
{
P01 = !P01;
}
课后小练
雕刻机保护系统
1.按下矩阵键盘上的按钮1开始工作
2.工作时打开LED0,表示雕刻机电源已打开
3.当外部中断1端口导通时,立刻关闭LED0,
表示切断雕刻机电源,从而实现保护功能。
与任务一同样的:
在main.c中
INT1_Init(); //外部中断1初始化
EA = 1; //IE |= 0X80;
在Task.c中
{0, 10, 10, KEY_Task}, /* task 1 Period: 10ms */
在io.c中
u8 Key_Vol = 0;
void KEY_Task(void)
{
if( P32 == 0 )
{
Key_Vol++;
if( Key_Vol==5 )
{
//按键按下的任务
P01 = 1; //打开电源
}
}
else
{
Key_Vol = 0;
}
}
void INT1_Init(void)
{
IT1 = 1; //下降沿中断
EX1 = 1; //打开中断允许
EA = 1; //打开总中断
}
void INT_ISR(void) interrupt 2
{
P01 = 0; //关闭电源
}
在io.h 中添加定义
void KEY_Task(void);
void INT1_Init(void);
njslmcc
发表于 2025-9-23 08:15:09
第十四集 IO中断(所有普通IO都支持的“外部中断”)
学习笔记
摘要
一、IO中断介绍
相比于外部中断,IO中断有以下优点:
1.支持所有的IO口(外部中断只有特定的外部中断的引脚)
2.可以任意配置上升沿/下降沿/高电平/低电平(外部中断只有上升/下降沿中断)
缺点:
IO中断同时只能支持一种中断模式,外部中断0和1可以同时支持上升/下降沿中断(可以用两个IO端口实现双边沿检测!)。
二、IO中断用法
1、选择合适的中断模式:PnIM0,PnIM1,下降沿,上升沿,低电平,高电平
2、打开端口的中断功能:PnINTE.x,0关1开,n 、x =(0~7),
3、配置IO口的中断:PnINTF.x,0无1有,标志位软件清0
4、中断号处理:装大于31的拓展工具或13号空中断跳转
任务1:编写IO中断的程序
在main.c中写入
P3_IO_Init(); //IO中断初始化
EA = 1;
在io.c中写入
void P3_IO_Init(void)
{
P3IM0 = 0x00; //IO中断模式为下降沿
P3IM1 = 0x00;
P3INTE = 0x08; //打开中断
}
void P3_IO_ISR(void) interrupt 40
{
u8 intf;
intf = P3INTF;
if( intf ) //判断有没有IO中断
{
P3INTF = 0;
If( intf & 0x08) //判断是否是P33按下
{
P01 = !P01;
}
}
}
在io.h中写入
//void INT1_Init(void);
void P3_IO_Init(void);
三、中断优先级的设置
相同优先级,靠前的中断源先执行,执行完之后再执行低中断源,且一个中断源在执行的时候不能被打断。
定时器0和P3中断都是最低优先级,定时器0中断号1,P3中断号40,执行完定时器0,再执行P3,再执行定定时器0,再执行...
PINIPL和PINIPH,00为最低级,11为最高级
任务2:编写P4端口的IO中断 打断 P3低电平中断的的程序(注意优先级)
开显示任务,
平时显示0
P33中断显示1,
P47中断显示2,
低平触发
在main.c中写入
P3_IO_Init(); //IO中断初始化
P4_IO_Init();
EA = 1;
while(1)
{
……..
Task_Pro_Handler_Callback(); //执行功能函数
password = 0;
}
在io.c中写入
void P3_IO_Init(void)
{
P3IM0 = 0x00; //IO中断模式为低电平
P3IM1 = 0xff;
P3INTE = 0x08; //打开中断
}
void P3_IO_ISR(void) interrupt 40
{
u8 intf;
intf = P3INTF;
if( intf ) //判断有没有IO中断
{
P3INTF = 0;
If( intf & 0x08) //判断是否是P33按下
{
password = 1;
}
}
}
void P4_IO_Init(void)
{
P4IM0 = 0x00; //IO中断模式为低电平
P4IM1 = 0xff;
P4INTE = 0x80; //打开中断P4.7
}
void P4_IO_ISR(void) interrupt 41
{
u8 intf;
intf = P4INTF;
if( intf ) //判断有没有IO中断
{
P4INTF = 0;
If( intf & 0x80) //判断是否是P47按下
{
password = 2;
}
}
}
在io.h中写入
//void INT1_Init(void);
extern u8 key_num ;
extern u8 passward;
void P3_IO_Init(void);
void P4_IO_Init(void);
现在就可以看到P3中断的优先级是高于P4中断的优先级的
如果我们要反过来,让P4中断的优先级高于P3中断的优先级
在void P4_IO_Init(void)中增加
PINIPH |=(1<<4); //与入最高优先级
PINIPL |=(1<<4);
在config.c的void Timer0_Init(void)中增加
IPH |= (1<<1);
IP|= (1<<1);
这样P4 优先级就高于P3了
相同优先级:靠前的中断源先执行,执行完之后再执行低中断源,且一个中断源在执行的时候不能被打断。
定时器0和P3中断都是最低优先级,定时器0中断号1,P3中断号40,执行完定时器0再执行P3
课后小练
多路抢答器
1.上电后一位数码管显示0,表示没有按下
2.选择任意三个独立按键作为三个用户,分别
代表“用户1”,“用户2”,“用户3”
3.谁第一个按下按钮,数码管就显示数字几
代表抢答成功
在main.c中写入
P3_IO_Init(); //IO中断初始化
EA = 1;
while(1)
{
……..
Task_Pro_Handler_Callback(); //执行功能函数
password = 0;
}
在io.c中写入
void P3_IO_Init(void)
{
P3IM0 = 0x00; //IO中断模式为低电平
P3IM1 = 0xff;
P3INTE = 0x08; //打开中断
}
void P3_IO_ISR(void) interrupt 40
{
u8 intf;
intf = P3INTF;
if( intf ) //判断有没有IO中断
{
//P3INTF = 0;
if( intf & 0x02) //判断是否是P31按下
{
password = 1;
}
else if( intf & 0x04) //判断是否是P32按下
{
password = 2;
}
else if( intf & 0x08) //判断是否是P33按下
{
password = 3;
}
}
}
njslmcc
发表于 2025-9-26 08:26:15
第十五集 定时器做计数器 学习笔记
一. 计数器的作用
只要输出信号是高低电平变化的传感器,想要计算个数的就可以用计数器的功能。
二. 定时器做为计数器的用法
任务1:编写定时器1计数的的程序
(为了方便计数,10个脉冲中断一次)
新建两个文件,tim.c和tim.h
在main.c中写入
#include “tim.h”
TIM1_Count_Init(); //在EA = 1之前
在tim.c中写入
#include “tim.h”
u32 Count_T1 = 0;
void TIM1_Count_Init(void)
{
//TMOD搜索,找到相关定义
T1_CT = 1; //设置外部计数
T1_M1 = 0; //设置为16位自动重载
T1_M0 = 0;
T0_GATE = 0;
TH1 = (65536-Count_num)>>8 //65526
TL1 = (65536-Count_num);
P3PU |= 0x20; //上拉电阻
TR1 = 1;
ET1 = 1;
}
void Timer1_Isr(void) interrupt 3 //1MS执行一次
{
Count_T1 ++; //T1引脚测到10个脉冲就溢出1次
//不要过于频繁地中断,影响稳定性
}
//因P35 有冲突,故用ISP里的万能虚拟数码管显示
void T1_RunTask(void)
{
u32 count_th_tl = 0;
count_th_tl = ((u16)TH1<<8 + (u16)TL1);
count_th_tl -= 65526;
SEG7_ShowLong(Count_T1 * Count_num + count_ th_tl, 10);
}
在tim.h中写入
#ifndef __TIM_H
#define __TIM_H
#include “config.h”
#define Count_num 10 //累计脉冲单位
void TIM1_Count_Init(void);
void T1_RunTask(void);
#endif
在task.c中写入
#include “tim.h”
增加一个任务
(0,100,100,T1_RunTask),//定时器1的计数功能
时钟选24M,下载,三个勾选,USB不停电下载
打开仿真调试接口,显示按键次数
至此,任务1就完成了。
三.定时器1测量INT1引脚低电平脉冲宽度
任务2:编写INT1测量低电平时间
(由按键模拟信号,100us的计数周期计数!)
ISP定时器计算工具,24MHz,100微秒,使能中断
在main.c中写入
#include “tim.h”
Timer1_Init(); //在EA = 1之前
在tim.c中
u32 Count_T1 = 0;
void Timer1_Isr(void) interrupt 3
{
static u32 count_p33 = 0;
if( P33 == 0 ) //按键按下开始计数
{
count_p33 ++ ;
}
else
{
if( count_p33>0 ) //表示之前按下了这个按键
{
Count_T1 = count_p33;
}
count_p33 = 0;
}
}
void Timer1_Init(void) //100微秒@24.000MHz
{
AUXR &= 0xBF; //定时器时钟12T模式
TMOD &= 0x0F; //设置定时器模式
TL1 = 0x38; //设置定时初始值
TH1 = 0xFF; //设置定时初始值
TF1 = 0; //清除TF1标志
TR1 = 1; //定时器1开始计时
ET1 = 1; //使能定时器1中断
}
void T1_RunTask(void)
{
//SEG7_ShowLong(Count_T1, 10); //显示整数
SEG7_ShowString("%07.01f",((float)Count_T1)/10); //显示1位小数
}
在tim.h中
#define Count_num 10 //累计脉冲单位
//void TIM1_Count_Init(void);
void Timer1_Init(void); //100微秒@24.000MHz
void T1_RunTask(void);
在task.c中写入
#include “tim.h”
有一个任务
(0,100,100,T1_RunTask),//定时器1的计数功能
至此,任务2就完成了。
课后小练
CT计数器:
在设备的出料端口有一个感应器,每次有成品出来就会有一个低电平出来,计算相邻的两个产品出来的时间差来计算CT时间。
1.计算P33引脚的相邻两次按下的时间,精确到100ms(即单个时间)
2.计算按下P33的次数(即总产量)
3.数码管前四位显示单个时间 ,后四位显示次数