乐高 发表于 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


页: 1 2 3 4 [5] 6
查看完整版本: AI8051U教学视频课后小练 | 高质量打卡