乐高
发表于 2025-3-2 09:55:44
第18集串口高级应用课后小练
本人愚钝,MODBUS-RTU 花了整整一个星期的时间才初步完成了此集课后小练,而且还是照搬了梁工的示例程序。
8H/8G/8C/8A系列4组串口同时通信的程序,Modbus 演示程序;可共享T2做波特率发生器 - 串行口,DMA支持的4组串口,RS232,RS485,Modbus, CRC16 国芯技术交流网站 - AI32位8051交流社区
本程序使用USB不间断下载接口,频率选择24M。实验箱J18跳线帽切换到C, D。串口助手更多设置选择“CRC-16 MODBUS协议,发送的数据包结尾增加校验数据”,串口发送接收都选择HEX模式。
u8 MODBUS_RTU(void)
{
u8 i,j,k;
u16 reg_addr; //寄存器地址
u8 reg_len; //写入寄存器个数
u16 crc;
if(RX1_Buffer == 0x10) //写多寄存器
{
if(RX1_cnt < 9) return 0x91; //命令长度错误
if((RX1_Buffer != 0) || ((RX1_Buffer *2) != RX1_Buffer)) return 0x92; //写入寄存器个数与字节数错误
if((RX1_Buffer==0) || (RX1_Buffer > REG_LENGTH)) return 0x92; //写入寄存器个数错误
reg_addr = ((u16)RX1_Buffer << 8) + RX1_Buffer; //寄存器地址
reg_len = RX1_Buffer; //写入寄存器个数
if((reg_addr+(u16)RX1_Buffer) > (REG_ADDRESS1+REG_LENGTH)) return 0x93; //寄存器地址错误
if(reg_addr < REG_ADDRESS1) return 0x93; //寄存器地址错误
if((reg_len*2+7) != RX1_cnt) return 0x91; //命令长度错误
j = reg_addr - REG_ADDRESS1; //寄存器数据下标
for(k=7, i=0; i<reg_len; i++,j++)
{
modbus_reg = ((u16)RX1_Buffer << 8) + RX1_Buffer; //写入数据, 大端模式
k += 2;
}
if(RX1_Buffer != 0) //非广播地址则应答
{
for(i=0; i<6; i++) TX1_Buffer = RX1_Buffer; //要返回的应答
crc = MODBUS_CRC16(TX1_Buffer, 6);
TX1_Buffer = (u8)crc; //CRC是小端模式, 先发低字节,后发高字节。
TX1_Buffer = (u8)(crc>>8);
B_TX1_Busy = 1; //标志发送忙
TX1_cnt = 0; //发送字节计数
TX1_number = 8; //要发送的字节数
TI = 1; //启动发送
}
}
else if(RX1_Buffer == 0x06) //写单寄存器
{
if(RX1_cnt < 6) return 0x91; //命令长度错误
//if((RX1_Buffer != 0) || ((RX1_Buffer *2) != RX1_Buffer)) return 0x92; //写入寄存器个数与字节数错误
//if((RX1_Buffer==0) || (RX1_Buffer > REG_LENGTH)) return 0x92; //写入寄存器个数错误
reg_addr = ((u16)RX1_Buffer << 8) + RX1_Buffer; //寄存器地址
//reg_len = RX1_Buffer; //写入寄存器个数
//if((reg_addr+(u16)RX1_Buffer) > (REG_ADDRESS+REG_LENGTH)) return 0x93; //寄存器地址错误
//if(reg_addr != REG_ADDRESS) return 0x93; //寄存器地址错误
if( RX1_cnt !=6) return 0x91; //命令长度错误
j = reg_addr - REG_ADDRESS; //寄存器数据下标
modbus_reg = ((u16)RX1_Buffer<<8)+ RX1_Buffer; //写入数据, 大端模式
num = modbus_reg;
if(RX1_Buffer != 0) //非广播地址则应答
{
for(i=0; i<6; i++) TX1_Buffer = RX1_Buffer; //要返回的应答
crc = MODBUS_CRC16(TX1_Buffer, 6);
TX1_Buffer = (u8)crc; //CRC是小端模式, 先发低字节,后发高字节。
TX1_Buffer = (u8)(crc>>8);
B_TX1_Busy = 1; //标志发送忙
TX1_cnt = 0; //发送字节计数
TX1_number = 8; //要发送的字节数
TI = 1; //启动发送
}
}
else if(RX1_Buffer == 0x03) //读多寄存器
{
if(RX1_Buffer != 0) //非广播地址则应答
{
if(RX1_cnt != 6) return 0x91; //命令长度错误
if(RX1_Buffer != 0) return 0x92; //读出寄存器个数错误
if((RX1_Buffer==0) || (RX1_Buffer > REG_LENGTH)) return 0x92; //读出寄存器个数错误
reg_addr = ((u16)RX1_Buffer << 8) + RX1_Buffer; //寄存器地址
reg_len = RX1_Buffer; //读出寄存器个数
if((reg_addr+(u16)RX1_Buffer) > (REG_ADDRESS1+REG_LENGTH)) return 0x93; //寄存器地址错误
//if(reg_addr < REG_ADDRESS1) return 0x93; //寄存器地址错误
j = reg_addr - REG_ADDRESS1; //寄存器数据下标
TX1_Buffer = SL_ADDR; //站号地址
TX1_Buffer = 0x03; //读功能码
TX1_Buffer = reg_len*2; //返回字节数
for(k=3, i=0; i<reg_len; i++,j++)
{
TX1_Buffer = (u8)(modbus_reg >> 8); //数据为大端模式
TX1_Buffer = (u8)modbus_reg;
}
crc = MODBUS_CRC16(TX1_Buffer, k);
TX1_Buffer = (u8)crc; //CRC是小端模式, 先发低字节,后发高字节。
TX1_Buffer = (u8)(crc>>8);
B_TX1_Busy = 1; //标志发送忙
TX1_cnt = 0; //发送字节计数
TX1_number = k; //要发送的字节数
TI = 1; //启动发送
}
}
else return 0x90; //功能码错误
return 0; //解析正确
}示例代码把数码管数据前的无效零位消隐了。
633
乐高
发表于 2025-3-8 14:22:48
第十九集ADC 课间思考
冲哥的意思是不是要求按下按键松开后,数码管显示键值保持不变,直到下一次按键按下?我的理解是这样的。
程序中增加KEY_Task(void)函数。
void KEY_Task(void)
{
u8 temp;
temp =ADC_KEY_READ(ADC_Read(0));
if(temp)
{
Key_Vol++;
if( Key_Vol==5 )
{
key = temp;
}
}
else
{
Key_Vol = 0;
}
}
634
乐高
发表于 2025-3-9 12:04:43
第十九集ADC课后小练
定义P10引脚电压就是ADC按键端的电压为V_key,按键端ADC读取数值为value,MCU的电源电压为VCC(来自ME6231C33M5G稳压管,输出电压3.3V),MCU内部参考信号为BGV
根据反推工作电压计算公式得到:VCC=4096*BGV/ADC_Read(15);VCC/4096=V_key/value;V_key=VCC*value/4096。
u16 BGV_Read(void)
{
u16 bgv = 0;
bgv = (((u16)CHIPID7) << 8) + (CHIPID8); //获取内部参考信号源值
return bgv;
}void KEY_Task(void)
{
u8 temp;
temp =ADC_KEY_READ(ADC_Read(0));
if(temp)
{
Key_Vol++;
if( Key_Vol==5 )
{
key = temp;
value = ADC_Read(0);
}
}
else
{
Key_Vol = 0;
}
VCC = 4096l*BGV_Read()/ADC_Read(15);
V_key = (u32)VCC*value/4096;
SEG_Show_U32( key,V_key);
//SEG_Show_U32( key,VCC);
//SEG_Show_U32( key,value);
//SEG_Show_U32( key,ADC_Read(15));
//SEG_Show_U32( key,BGV_Read());
} 635
乐高
发表于 2025-3-9 20:25:36
第十九集ADC课后小练 程序修正
在上一个程序中,数码管显示的P10端口电压一直在跳动,排查发现是ADC_Read(15)的数值一直在变化,如果电源电压稳定,这应该是一个相对固定的值。
我考虑在程序开始的时候就把它读出来,采用连续读取20次求取平均值。修改后的结果数码管显示稳定。程序中4096后面是小写的“L”,不是数字“1”,用来转换后面的U16为U32长整型。
/*鉴于前所写程序中ADC_Read(15)的数值一直在跳动,而这个数值应该是一个相对固定值,
所以在程序运行的开始就把它读取出来,连续读取20次,求其平均值*/
Delay10ms();
Delay10ms();
for( i=0;i<20;i++)
{
BGV_ADC_Read += ADC_Read(15);
Delay10ms();
}
BGV_ADC_Read =BGV_ADC_Read/20;
VCC = 4096l*BGV_Read()/BGV_ADC_Read;637
乐高
发表于 2025-3-12 16:04:42
第20集ADC_NTC测温课后小练
NTC温度读取采用冲哥STC32G视频教程第19集NTC温度采集程序。 程序下载运行IRC频率24M,插上测温跳帽JUP2。
void KEY_Task(void)
{
u8 temp;
temp = ADC_KEY_READ(ADC_Read(0));
if(temp)
{
Key_Vol++;
if( Key_Vol==5 )
{
key = temp;
if(key==8) //“设置”按键
Set_t = 1; //按下设置键,标志位Set_t置一
if(key==16) //“确认退出”按键
Set_t = 0;
if(Set_t)
{
switch(key)
{
//温度增减按键
case 1: if(baojing<9)baojing++; break;
case 2: if(baojing<9)baojing++; break;
case 3: if(baojing<9)baojing++; break;
case 4: if(baojing<9)baojing++; break;
case 9: if(baojing>0)baojing--; break;
case 10:if(baojing>0)baojing--; break;
case 11:if(baojing>0)baojing--; break;
case 12:if(baojing>0)baojing--; break;
default: break;
}
}
}
}
else
{
Key_Vol = 0;
}
BJ_wendu = baojing*1000+baojing*100+baojing*10+baojing; //报警温度放在数组baojing[]内
}
void NTC_Task(void) //间隔500ms读取一次NTC值,兼作后四位数码管闪烁计时来源
{
NTC_wendu = Temp_Cal( ADC_Read(3)); //NTC温度读取采用冲哥STC32G视频教程第19集NTC温度采集程序
VCC = 4096l*BGV_Read()/BGV_ADC_Read; //MCU电压计算
SEG_Show(VCC,NTC_wendu); //数码管显示,前四位显示电压,后四位显示NTC温度
if(Set_t) //按下设置键,标志位Set_t置一,后四位数码管闪烁
blink = ~blink;
}void SEG_Task(void)
{
if( Seg_no ==0 )
{
Display_Seg( SEG_NUM]+0x80 , ~T_NUM); //+0x80是为了添加小数点,编译会出现警告C188,可忽略
}
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 )
{
if(passward==0)
Display_Seg( SEG_NUM , ~T_NUM); //第一位如果是零,则消隐
else
Display_Seg( SEG_NUM], ~T_NUM);
}
else if( Seg_no ==5 )
{
Display_Seg( SEG_NUM] , ~T_NUM);
}
else if( Seg_no ==6 )
{
if(Set_t ) //按下设置键,后四位数码管闪烁
{
if(!blink)
Display_Seg( 0 , ~T_NUM);
else
Display_Seg( SEG_NUM]+0x80 , ~T_NUM);
}
else if(NTC_wendu < BJ_wendu )
Display_Seg( SEG_NUM]+0x80 , ~T_NUM);
else
Display_Seg( SEG_NUM] , ~T_NUM);
}
else if( Seg_no ==7 )
{
Display_Seg( SEG_NUM] , ~T_NUM);
}
else
{
}
Seg_no ++;
if( Seg_no>7 )
Seg_no=0;
}
void SEG_Show(u16 vol,u16 wendu)
{
u8 i;
for(i=0;i<4;i++)
{
if(Set_t) //按下设置键,后四位数码管闪烁
{
if(blink)
passward = 17;
else
passward = baojing;
}
else
{
if(wendu > BJ_wendu) //如果温度高于预警温度值,则显示“----”
passward = 16;
else
{
passward = wendu%10;;
wendu /= 10;
}
}
}
for(i=4;i<8;i++)
{
passward = vol%10;
vol /= 10;
}
}642
643
乐高
发表于 2025-3-16 22:28:50
第二十一集 Flash模拟EEPROM 课后小练
虽然看上去只有几句要求,但做起来还是有点麻烦。按键又增加了一个,单独用矩阵键盘是不够用了。要用到ADC键盘,我把这两个键盘都用上了,可以同时工作,类似于笔记本电脑的大小键盘{:4_256:}。
下载程序的时候需要注意一点:不选择“下次下载用户程序时擦除用户EEPROM区”,不然保存在EEPROM 的数据,经过一次下载就没了,还不知道问题出在哪里。当然,如你需要,可以选择擦除。
void main(void)
{
Sys_init(); //系统初始化
usb_init(); //USB CDC 接口配置
IE2 |= 0x80; //使能USB中断
Timer0_Init(); //定时器初始化
ADC_Init();
EA = 1; //IE |= 0X80;
WDT_CONTR = 0x25; //使能看门狗,溢出时间约1.05S
//while (DeviceState != DEVSTATE_CONFIGURED); //等待USB完成配置
//P40 = 0 ;
Parm_Init();
while(1)
{
if( blink_stop) //新密码输入完成,再次按下“#”键,这里是ADC键盘F号键
if(ADC_KEY_READ(ADC_Read(0))==15)
{
admin = 0 ;blink_stop = 0;
EEPROM_SectorErase(E_ADDR); //清空地址里的数据,擦除扇区
memcpy(dat,cod,8);
dat = SYS_Run_times;
dat = (u8)(dat+dat+dat+dat+dat+dat+dat+dat+dat);
EEPROM_write_n(E_ADDR,dat,10);
}
Task_Pro_Handler_Callback(); //执行功能函数
WDT_CONTR = 0x35; //清看门狗
}
}u8 SYS_Run_times = 0;
u8 dat; //0-7:8位密码 8:开机次数 9:校验码(累加校验)
/*任务1:实现手册的EEPROM的基本操作,并用数码管显示当前的数值。并实现每次重新开机数值+1*/
void Parm_Init(void)
{
EEPROM_read_n( E_ADDR ,dat,10 );
if( dat == 0xff ) //如果读取到的数据是0xff,就是第一次上电,此时可以初始化
{
EEPROM_SectorErase(E_ADDR); //清空地址里的数据,擦除扇区
memcpy(dat,cod2,8);
dat = 1;
dat = (u8)(dat+dat+dat+dat+dat+dat+dat+dat+dat);
EEPROM_write_n(E_ADDR,dat,10);
//SYS_Run_times = 1;
}
else //第二次及其之后的上电
{
if( dat == (u8)(dat+dat+dat+dat+dat+dat+dat+dat+dat) ) //校验通过,数据有效
{
SYS_Run_times = dat;
SYS_Run_times +=1;
memcpy(cod2,dat,8);
}
else
{
//如果数据校验出错,执行什么
}
}
}sbit COL1 = P0^0; //矩阵键盘的数据口端口定义
sbit COL2 = P0^1;
sbit COL3 = P0^2;
sbit COL4 = P0^3;
sbit ROW1 = P0^6;
sbit ROW2 = P0^7;
//矩阵端口数据处理
void Matrixkey_W(u8 m) //向6个管脚写入数据
{
COL1 = m & (0x01<<0);
COL2 = m & (0x01<<1);
COL3 = m & (0x01<<2);
COL4 = m & (0x01<<3);
ROW1 = m & (0x01<<4);
ROW2 = m & (0x01<<5);
// ROW3 = m & (0x01<<6);
// ROW4 = m & (0x01<<7);
}
u8 Matrixkey_R() //从6个管脚读出数据
{
u8 n =0;
// n&= (u8)COL1;
if(COL1)
n |= 0x01;
if(COL2)
n |= 0x02;
if(COL3)
n|= 0x04;
if(COL4)
n |= 0x08;
if(ROW1)
n |= 0x10;
if(ROW2)
n |= 0x20;
// if(ROW3)
// n |= 0x40;
// if(ROW4)
// n |= 0x80;
return n;
}
void KEY_Task(void) //2*4矩阵键盘扫描
{
u8 key_temp;
u8 key1,key2;
Matrixkey_W(0x30);
key1 = Matrixkey_R();
if(key1 != 0x30 )
{
key1 = Matrixkey_R()&0x30; //确定哪一行的按键按下
Matrixkey_W(0x0f);
key2 = Matrixkey_R();
if(key2 != 0x0f)
key2 = Matrixkey_R()&0x0f; //确定哪一列的按键按下
key_temp=key1|key2; //确定按键位置
}
else
key_temp = key_no;
switch(key_temp) //当确定按键按下后,列举所有的按键情况
{
case 0x2e: key_value=0;break;
case 0x2d: key_value=1;break;
case 0x2b: key_value=2;break;
case 0x27: key_value=3;break;
case 0x1e: key_value=4;break;
case 0x1d: key_value=5;break;
case 0x1b: key_value=6;break;
case 0x17: key_value=7;break;
case 0xff: key_value=255;break;
}
}void PW_write_Task(void)
{
u8 key ;
if(ADC_KEY_READ(ADC_Read(0))==15 & open) //开锁后长按“#”号按键,这里设置ADC15号F按键为“#”号按键
t_count++;
if(t_count>300) //长按“#”号按键3秒,进入管理员模式,admin置一
{
admin = 1;
memcpy(cod ,cod0,8); //将数组cod0的值复制给cod
t_count = 0;
}
if(key_value<11) //只要矩阵键盘或者ADC键盘有按键按下,就把键值读入。
key = key_value; //实现两组按键同时起作用
else
key = ADC_KEY_READ(ADC_Read(0));
if( key< 11 )
{
Key_num ++ ;
if(Key_num ==6)
{
j++;
if(open ==0 )
{
cod = cod1; //把收到的数字逐一存入cod[]中
if(j==8) //如果按了8个数字
{
if(!memcmp(cod,cod2,8)) //比较cod与cod2两数组是否相等,若相等显示“--OPEN--”,不等显示初始值“--------”
open = 1;
else
{
memcpy(cod,cod0,8); //将初始值cod0复制给cod,回到起始状态
j = 0;
}
}
}
if(admin)
{
cod = cod1; //进入管理员模式,把新的密码数字逐一存入cod[]中
if(j==8) //如果按了8个数字,停止闪烁,正常显示新的密码
{
blink_stop = 1;
}
}
if( key ==10)
{
j = 0;
open =0;
}
if((open ==0)&&(j==0))
memcpy(cod,cod0,8); //将初始值cod0复制给cod,回到起始状态
}
}
else
Key_num = 0;
}648
乐高
发表于 2025-3-17 08:27:16
第二十一集 Flash模拟EEPROM 课后小练补充
这次我在程序中加入了查看开锁密码和开机次数的功能,把程序版本号改为了U 2.00。
设置的查看开锁密码的数组是“31415926”,查看开机次数的数组是“88889999”。
MCU运行频率24M。
if(open ==0 )
{
cod = cod1; //把收到的数字逐一存入cod[]中
if(j==8) //如果按了8个数字
{
if(!memcmp(cod,cod2,8)) //比较cod与cod2两数组是否相等,若相等显示“--OPEN--”
open = 1;
else if(!memcmp(cod,cod4,8)) //比较cod与cod4两数组是否相等,若相等则显示开锁密码
memcpy(cod,cod2,8); //将初始值cod2复制给cod
else if(!memcmp(cod,cod5,8)) //比较cod与cod5两数组是否相等,若相等则显示开机次数
{
cod = 0;
cod = 0;
cod = 0;
cod = 0;
cod = 0;
cod = SEG_NUM;
cod = SEG_NUM;
cod = SEG_NUM;
}
else //不等显示初始值“--------”
{
memcpy(cod,cod0,8); //将初始值cod0复制给cod,回到起始状态
j = 0;
}
}
}
651
乐高
发表于 2025-3-19 11:06:18
触摸按键演示板
void main(void)
{
u8 i,j;
char xdata dat;
P_SW2 |=0x80;
UART1_config(1); // 选择波特率, 2: 使用Timer2做波特率, 其它值: 使用Timer1做波特率.
//1.需要用到的触摸引脚设置为高阻输入
P1M0 = 0x00; P1M1 = 0xf8; //P1.3-P1.7
P3M0 = 0x00; P3M1 = 0x0c; //P3.2 、 P3.3,(P3.0 、 P3.1 作为串口1通讯口)
// P3M0 = 0x00; P3M1 = 0xfc;
P5M0 = 0x00; P5M1 = 0x10; //P5.4
//2.配置是否需要同时使用触摸LED指示和触摸检测
TSRT = 0; //不需要使用LED
//3.需要用到的触摸通道使能
TSCHEN1 = 0xfc;
TSCHEN2 = 0x0c;
//4.设置触摸按键的开关频率,参考电压和放电时间
TSCFG1 = (7<<4) + 6; //工作频率设为最低,3 时钟为1000个系统时钟,6时钟为5000个系统时钟
TSCFG2 = 2; //参考电压为 5/8 AVCC
//5. 开启触摸
TSCTRL = 0xa1 ;
//6.开启触摸中断并保存数据
IE2 |= 0x80;
EA = 1;
}
661
663
667
程序(1)修改了一下LED数组操作序号,这样更合理一些。
乐高
发表于 2025-3-27 16:22:36
第二十二集比较器 课后小练
首先让我们对照电路图看程序,内部比较器的一端CMP-输入脚接内部1.19V参考电压;另一端CMP+接P4.6,P4.6接在USB+5V下面的可调电阻W1调节端。
CMPEXCFG |= 0x04; //内部1.19V参考电压为CMP-输入脚
CMPEXCFG &= ~0x03; //P4.6为CMP+输入脚
INVCMPO = 0; //比较器正向输出
CMPO_S = 0; //选择P4.5作为比较器输出脚,P4.5在LCD12864模块接口的第四位。因为设定了比较器正向输出 ,所以当CMP+电压大于CMP-电压的时候,P4.5为高电平输出。
P42 = ~CMPRES; //中断方式读取比较器比较结果,CMPRESS=0,LED11灭;CMPRESS=1,LED11亮。原程序是P42 = CMPRES; 为了节省断电后的电流,我把它取反了。
//在P4.5端口接上一个发光二极管,以及从实验箱上的LED11都可以观察比较器的输出结果,我把它们接成同相了。
在程序测试时,首先需要调节W1的阻值,使得P4.6端口的电压大于1.19V且不太多,这样可以使得掉电后,MCU有足够的时间去保存数据,而且不用在MCU两端加大容量电容。
679
void PW_write_Task(void)
{
u8 key ;
if(ADC_KEY_READ(ADC_Read(0))==15 & open) //开锁后长按“#”号按键,这里设置ADC15号F按键为“#”号按键
t_count++;
if(t_count>300) //长按“#”号按键3秒,进入管理员模式,admin置一
{
admin = 1;
memcpy(cod ,cod0,8); //将数组cod0的值复制给cod
t_count = 0;
}
if(key_value<11) //只要矩阵键盘或者ADC键盘有按键按下,就把键值读入。
key = key_value; //实现两组按键同时起作用
else
key = ADC_KEY_READ(ADC_Read(0));
if( key< 11 )
{
Key_num ++ ;
if(Key_num ==6)
{
j++;
if(open ==0 )
{
cod = cod1; //把收到的数字逐一存入cod[]中
if(j==8) //如果按了8个数字
{
if(!memcmp(cod,cod2,8)) //比较cod与cod2两数组是否相等,若相等显示“--OPEN--”,不等显示初始值“--------”
{
open = 1;
open_t++; //开门次数加一
}
else if(!memcmp(cod,cod4,8)) //比较cod与cod4两数组是否相等,若相等则显示开锁密码
memcpy(cod,cod2,8); //将初始值cod2复制给cod
else if(!memcmp(cod,cod5,8)) //比较cod与cod5两数组是否相等,若相等则显示开机次数
{
/*依次显示3位错输次数,3位开门次数,2位进管理员模式次数*/
cod = SEG_NUM;
cod = SEG_NUM;
cod = SEG_NUM;
cod = SEG_NUM;
cod = SEG_NUM;
cod = SEG_NUM;
cod = SEG_NUM;
cod = SEG_NUM;
}
else
{
err_t++; //输入错误,错输次数加一
memcpy(cod,cod0,8); //将初始值cod0复制给cod,回到起始状态
j = 0;
}
}
}
if(admin)
{
cod = cod1; //进入管理员模式,把新的密码数字逐一存入cod[]中
if(j==8) //如果按了8个数字,停止闪烁,正常显示新的密码
{
blink_stop = 1;
adm_t++; //完成一次修改密码,进管理员次数加一
}
}
if( key ==10)
{
j = 0;
open =0;
if(admin)
{
adm_t++; //中途退出,进管理员次数也加一次
admin = 0;
}
}
if((open ==0)&&(j==0))
memcpy(cod,cod0,8); //将初始值cod0复制给cod,回到起始状态
}
}
else
Key_num = 0;
dat = err_t;
dat = open_t;
dat = adm_t;
dat = (u8)(dat+dat+dat+dat+dat+dat+dat+dat+dat+dat+dat);
}
乐高
发表于 2025-3-27 16:57:44
第二十二集 比较器课后小练(续)
我在二十一集程序的基础上稍作了一些增删。仍然采用输入31415926来读出开门密码,输入88889999来显示(一次8位同时显示)1、密码输入错误次数(前3位),2、开门次数(中3位),3、进入管理员模式次数(后2位)。如下图:密码输入错误6次,开门5次,进入管理员模式3次。
//========================================================================
// 函数: void CMP_config(void)
// 描述: 比较器初始化函数。
// 参数: 无.
// 返回: 无.
// 版本: V1.0, 2020-6-10
//========================================================================
void CMP_config(void)
{
CMPEXCFG = 0x00;
//CMPEXCFG |= 0x40; //比较器DC迟滞输入选择,0:0mV; 0x40:10mV; 0x80:20mV; 0xc0:30mV
//CMPEXCFG &= ~0x04; //P4.4为CMP-输入脚
CMPEXCFG |= 0x04; //内部1.19V参考电压为CMP-输入脚
CMPEXCFG &= ~0x03; //P4.6为CMP+输入脚
//CMPEXCFG |= 0x01; //P5.0为CMP+输入脚
//CMPEXCFG |= 0x02; //P5.1为CMP+输入脚
//CMPEXCFG |= 0x03; //ADC输入脚为CMP+输入脚
CMPCR2 = 0x00;
INVCMPO = 0; //比较器正向输出
//INVCMPO = 1; //比较器反向输出
DISFLT = 0; //使能0.1us滤波
//DISFLT = 1; //禁止0.1us滤波
//CMPCR2 &= ~0x3f; //比较器结果直接输出
CMPCR2 |= 0x10; //比较器结果经过16个去抖时钟后输出
CMPCR1 = 0x00;
//PIE = 0; //禁止比较器上升沿中断
PIE = 1; //使能比较器上升沿中断
//NIE = 0; //禁止比较器下降沿中断
NIE = 1; //使能比较器下降沿中断
//CMPOE = 0; //禁止比较器输出
CMPOE = 1; //使能比较器输出
CMPO_S = 0; //选择P4.5作为比较器输出脚
//CMPO_S = 1; //选择P4.1作为比较器输出脚
CMPEN = 1; //使能比较器模块
}
u8 dat; //0-7:8位开门密码 8:错输次数 9:开门次数10:进管理员模式次数 11:校验码(累加校验)
void Parm_Init(void)
{
EEPROM_read_n( E_ADDR ,dat,12 );
if( dat == 0xff ) //如果读取到的数据是0xff,就是第一次上电,此时可以初始化
{
memcpy(dat,cod2,8);
}
else
{
if( dat == (u8)(dat+dat+dat+dat+dat+dat+dat+dat+dat+dat+dat) ) //校验通过,数据有效
{
EEPROM_SectorErase(E_ADDR); //清空地址里的数据,擦除扇区
memcpy(cod2,dat,8);
err_t = dat;
open_t = dat;
adm_t = dat;
}
}
}
/******************* 比较器中断函数 ********************/
void CMP_Isr() interrupt 21
{
// u8i;
CMPIF = 0; //清中断标志
P42 = ~CMPRES; //中断方式读取比较器比较结果,CMPRESS=0,LED11灭;CMPRESS=1,LED11亮。
if(CMPRES)
{
if(LowVolFlag)
{
LowVolFlag = 0; //清除低电压标志
memcpy(cod,cod0,8); //点亮数码管
// if(Test_cnt != Temp_cnt)
// {
// EEPROM_read_n(E_ADDR,tmp,2); //读出2字节
// Test_cnt = ((u16)tmp << 8) + tmp; //秒计数
// if(Test_cnt > 10000) Test_cnt = 0; //秒计数范围为0~10000
// Temp_cnt = Test_cnt;
// }
}
}
else
{
//Display_Seg(0,0);
memcpy(cod,cod6,8); //熄灭数码管
if(!LowVolFlag)
{
LowVolFlag = 1; //设置低电压标志
// dat = err_t;
// dat = open_t;
// dat = adm_t;
// dat = (u8)(dat+dat+dat+dat+dat+dat+dat+dat+dat+dat+dat);
EEPROM_write_n(E_ADDR,dat,12);
// if(Test_cnt != Temp_cnt)
// {
// Temp_cnt = Test_cnt;
// EEPROM_SectorErase(EE_ADDRESS); //擦除扇区
// tmp = (u8)(Temp_cnt >> 8);
// tmp = (u8)Temp_cnt;
// EEPROM_write_n(EE_ADDRESS,tmp,2);
// }
}
}
}680