mechray 发表于 6 天前

第十一期(第11集《矩阵按键》)

普中开发板上的是4X4的矩阵按键,代码编写如下。写的不是很优雅,但也算好用。


u8 matrix_key(void)
{
      u8 key_num=0;
      P13=0;P12=1;P11=1;P10=1;
      if(P17==0)
      {
                key_num=1;
      }else if(P16==0)
      {
                key_num=2;
      }else if(P15==0)
      {
                key_num=3;
      }else if(P14==0)
      {
                key_num=4;
      }      
      P13=1;P12=0;P11=1;P10=1;
      if(P17==0)
      {
                key_num=5;
      }else if(P16==0)
      {
                key_num=6;
      }else if(P15==0)
      {
                key_num=7;
      }else if(P14==0)
      {
                key_num=8;
      }
      P13=1;P12=1;P11=0;P10=1;
      if(P17==0)
      {
                key_num=9;
      }else if(P16==0)
      {
                key_num=10;
      }else if(P15==0)
      {
                key_num=11;
      }else if(P14==0)
      {
                key_num=12;
      }
      P13=1;P12=1;P11=1;P10=0;
      if(P17==0)
      {
                key_num=13;
      }else if(P16==0)
      {
                key_num=14;
      }else if(P15==0)
      {
                key_num=15;
      }else if(P14==0)
      {
                key_num=16;
      }
      return key_num;
}

mechray 发表于 6 天前

第十二期(第12集《复位系统》)

这集视频比较全面地介绍了复位系统。注意到:
89C52的复位脚是高电平复位,而AI8051U是低电平复位,所以普中开发板上的复位按键不起作用,但好在擎天柱上的复位按键是可以用的。

另外,USB复位代码很重要,不然按复位之后,串口通讯将失效,printf打印不出东西来!

//复位USB 很重要
        P3M0 &=~0x03;
        P3M1 |=0x03;       
       
        USBCON=0x00;
        USBCLK=0x00;
        IRC48MCR=0x00;
        delay_ms(10);

mechray 发表于 5 天前

第十三期(第13集《外部中断》)

本来外部中断的难点是不记得哪些引脚支持,以及对应的中断向量号。不过幸好有ISP工具自动生成代码,所以用起来还比较顺。

mechray 发表于 5 天前

第十四期(第14集《IO中断》)

由于Keil的中断向量号最高只能支持到31,所以需要下载插件来进一步拓展,或者通过保留的13号中断来路由。由于后者需要用到汇编语言(完全不懂),所以貌似还是前者更方便。
;P3的13号中断
      ;CSEG AT 0143H
      ;JMP P3INT_SR
;P3INT_SR:
      ;JMP 006BH
      ;END


mechray 发表于 4 天前

第十五期(第15集《定时器做计数器》)

定时器如果切换为计数器,就可以利用特定引脚计量输入的脉冲数,实现编码器测速等功能。

#define COUNTER 10

void Timer1_Init(void)                //计数器
{
      AUXR |= 0x40;                        //定时器时钟1T模式
      TMOD &= 0x0F;                        //设置定时器模式为计数器
      TMOD |= 0x40;
      TL1 = (65536-COUNTER);                              //设置定时初始值
      TH1 = (65536-COUNTER)>>8;                              //设置定时初始值
      TF1 = 0;                              //清除TF1标志
      
      P3PU |=0x20;         //打开P3.5上拉电阻
      
      TR1 = 1;                              //定时器1开始计时
      ET1 = 1;                              //使能定时器1中断
}

mechray 发表于 4 天前

第十六期(第16集《DS18B20测温》)

这节课温习了一下单总线协议的写法。视频讲得很细,挺好的。
#include "18B20.h"
#include "io.h"

u32 DS18B20_Temp=0;
u8 minus=0;

void Delay480us(void)      //@40.000MHz
{
      unsigned long edata i;

      _nop_();
      _nop_();
      _nop_();
      i = 4798UL;
      while (i) i--;
}

void Delay60us(void)      //@40.000MHz
{
      unsigned long edata i;

      _nop_();
      _nop_();
      _nop_();
      i = 598UL;
      while (i) i--;
}

void Delay1us(void)      //@40.000MHz
{
      unsigned long edata i;

      _nop_();
      _nop_();
      _nop_();
      i = 8UL;
      while (i) i--;
}


void DS18B20_reset(void)
{
      u8 flag=1;
      while(flag)
      {
                DQ=0;
                Delay480us();
                DQ=1;
                Delay60us();
                flag=DQ;
                Delay480us();
      }
}

void DS18B20_write_0(void)
{
      DQ=0;
      Delay60us();
      DQ=1;
      Delay1us();
      Delay1us();
}

void DS18B20_write_1(void)
{
      DQ=0;
      Delay1us();
      Delay1us();
      DQ=1;
      Delay60us();
}

bit DS18B20_read(void)
{
      bit b=0;
      DQ=0;
      Delay1us();
      Delay1us();
      DQ=1;
      Delay1us();
      Delay1us();
      b=DQ;
      Delay60us();
      return b;
}

void DS18B20_write_byte(u8 dat)
{
      u8 i;
      for(i=0;i<8;i++)
      {
                if((dat>>i) & 0x01)
                        DS18B20_write_1();
                else
                        DS18B20_write_0();
      }
}

u8 DS18B20_read_byte(void)
{
      u8 dat=0;
      u8 i;
      for(i=0;i<8;i++)
      {
                dat >>= 1;
                if(DS18B20_read())
                        dat |= 0x80;
      }
      return dat;
}

void DS18B20_ReadTemp(void)
{
      u8 datH=0, datL=0;
      u16 temp=0;
/* 发送检测命令 */      
      DS18B20_reset();
      DS18B20_write_byte(0xCC); //跳过ROM命令
      DS18B20_write_byte(0x44); //开始转化
      while(!DQ);
/* 发送读取命令 */      
      DS18B20_reset();
      DS18B20_write_byte(0xCC); //跳过ROM命令
      DS18B20_write_byte(0xBE); //开始读取
      datL=DS18B20_read_byte();
      datH=DS18B20_read_byte();
      
      if(datH & 0x80)
      {
                minus=1;
                temp=((u16)datH<<8)|(u16)datL;
                temp =~temp+1; //负数取补码
                DS18B20_Temp=(u32)temp*625; //放大1万倍
      }
      else
      {
                minus=0;
                temp=((u16)datH<<8)|(u16)datL;
                DS18B20_Temp=(u32)temp*625; //放大1万倍
      }
}

void DS18B20_ShowTemp(u8 digit)
{
      u8 i;
      u32 new_temp;
      if(digit>4) digit=4;//最多4为小数
      new_temp=DS18B20_Temp/(u32)pow(10,4-digit);
      for(i=0;i<digit+2;i++)
      {      
                if(i==digit)
                        digit_show((u8)(new_temp/(u32)pow(10,i)%10),(u8)(i+1),1);
                else
                        digit_show((u8)(new_temp/(u32)pow(10,i)%10),(u8)(i+1),0);
      }
}

mechray 发表于 前天 10:07

第十七期(第17集《串口的简单应用》)
第十八期(第18集《串口高级应用》)

利用串口超时中断接收不定长数据非常实用。不过因为计算合适的超时时长还是比较复杂,所以干脆可以把时长设长一点。我一开始用AiCube自动配置的10个串口波特率时长会触发两次超时中断,延长到255个就好了。

//<<AICUBE_USER_HEADER_REMARK_BEGIN>>
////////////////////////////////////////
// 在此添加用户文件头说明信息
// 文件名称: uart.c
// 文件描述:
// 文件版本: V1.0
// 修改记录:
//   1. (2025-04-30) 创建文件
////////////////////////////////////////
//<<AICUBE_USER_HEADER_REMARK_END>>


#include "config.h"


//<<AICUBE_USER_INCLUDE_BEGIN>>
// 在此添加用户头文件包含
//<<AICUBE_USER_INCLUDE_END>>


//<<AICUBE_USER_GLOBAL_DEFINE_BEGIN>>
u8 Rec_Dat;
u8 Rec_Num=0;
bit B_TX2_Busy=0;
bit B_TX2_OK=0;
//<<AICUBE_USER_GLOBAL_DEFINE_END>>



////////////////////////////////////////
// 串口2中断服务程序
// 入口参数: 无
// 函数返回: 无
////////////////////////////////////////
void UART2_ISR(void) interrupt UART2_VECTOR
{
    //<<AICUBE_USER_UART2_ISR_CODE1_BEGIN>>
    // 在此添加中断函数用户代码
    if (UART2_CheckTxFlag())            //判断串口发送中断
    {
      UART2_ClearTxFlag();            //清除串口发送中断标志
                B_TX2_Busy=0;
    }

    if (UART2_CheckRxFlag())            //判断串口接收中断
    {
      UART2_ClearRxFlag();            //清除串口接收中断标志
               
                UR2TOCR = 0xe0;
               
                Rec_Dat=S2BUF;
    }
      
      if(UR2TOSR & 0x01)
      {
                B_TX2_OK = 1;
                UR2TOSR = 0;
                UR2TOCR = 0x00;
      }
    //<<AICUBE_USER_UART2_ISR_CODE1_END>>
}

////////////////////////////////////////
// 串口2初始化函数
// 入口参数: 无
// 函数返回: 无
////////////////////////////////////////
void UART2_Init(void)
{
#ifdef BAUDRATE
#undef BAUDRATE
#endif
#define BAUDRATE      (115200)
#define T2_RELOAD       (65536 - (SYSCLK / BAUDRATE + 2) / 4)

    UART2_SwitchP4243();                //设置串口数据端口: RxD2 (P4.2), TxD2 (P4.3)

//UART2_Timer2BRT();                  //串口2固定选择定时器2作为波特率发生器
    TIMER2_TimerMode();               //设置定时器2为定时模式
    TIMER2_1TMode();                  //设置定时器2为1T模式
    TIMER2_SetPrescale(0);            //设置定时器2的8位预分频
    TIMER2_SetReload16(T2_RELOAD);      //设置定时器2的16位重载值
    TIMER2_Run();                     //定时器2开始运行

    UART2_EnableRx();                   //使能串口2接收数据
    UART2_Mode3();                      //设置串口2为模式3 (9位数据可变波特率)
    UART2_OddParity();                  //串口2硬件自动奇校验
    UART2_SetIntPriority(0);            //设置中断为最低优先级
    UART2_EnableInt();                  //使能串口2中断

    UART2_TimeoutScale_BRT();         //设置串口波特率为接收超时时间基准
    UART2_SetTimeoutInterval(0x0000ff); //设置接收超时时间长度
    UART2_EnableTimeoutInt();         //使能串口接收超时中断
    UART2_EnableTimeout();            //使能串口接收超时功能

    //<<AICUBE_USER_UART2_INITIAL_BEGIN>>
    // 在此添加用户初始化代码
    //<<AICUBE_USER_UART2_INITIAL_END>>
}


//<<AICUBE_USER_FUNCTION_IMPLEMENT_BEGIN>>
void uart_receive(void)
{
      if(B_TX2_OK)
      {
                B_TX2_OK=0;
                uart_send("receive finish\r\n");
      }
      if(Rec_Num>=6)
      {
                if(Rec_Dat=='\n' && Rec_Dat=='\r')
                {
                        if(Rec_Dat=='o' && Rec_Dat=='p'&& Rec_Dat=='e' && Rec_Dat=='n')
                        {
                              P00=0;
                              uart_send("open\r\n");
                        }
                        if(Rec_Dat=='c' && Rec_Dat=='l' && Rec_Dat=='o'&& Rec_Dat=='s' && Rec_Dat=='e')
                        {
                              P00=1;
                              uart_send("closed\r\n");
                        }
                        Rec_Num=0;
                }
      }
}

void uart_send(u8 *dat)
{
      for(;*dat !=0; dat++)
      {
                S2BUF=*dat;
                B_TX2_Busy=1;
                while(B_TX2_Busy);
      }
}
//<<AICUBE_USER_FUNCTION_IMPLEMENT_END>>





mechray 发表于 昨天 15:19

第十九期(第19集《ADC》)

让人兴奋的是:
1.申请的Ai8951U实验箱收到了,我的设备又一次更新了
2.ISP6.95N更新了AiCube,现在可以在用户自定义区编写代码且刷新工程后不会被擦除了,越来越接近CubeMX了。(不过好像还有bug,例如刷新工程后,原来添加到keil的自定义文件会被移除,需要重新添加到目录中)
这个五一可以好好研究一下了。

今天学习了ADC章节。发现自己写的ADC按键代码有问题,虽然按下按键后能够正常显示按键值,但是一旦松开,按键值便归零。理解是之前的值无法存在全局/静态变量中。研究了一晚上发现,如果将ADC转换函数加入到定时器中断,使其间隔100ms运行一次就好了(至少要间隔50ms)。猜想是ADC转换需要一定的时间,如果两次转换函数间隔时间太近,会对全局变量存储结果产生影响,导致其无法存储上一次按下的值。(但仅仅是猜想,不知道准确的原因如何描述)


mechray 发表于 昨天 15:42

第二十期(第20集《ADC_NTC测温》)

之前普中A2的板子因为用的是89C52,不带ADC,所以开发板上是通过一颗ET2046芯片实现ADC功能的。这次给板子插上了擎天柱,顺便也向客服问到了普中上板载的热敏电阻和光敏电阻型号,在普中和实验箱上同时做了实验。

普中上的电阻参数记录如下:
1.NTC:MF52/10K/3950/1%
2.光敏:5549/5MM/50K-100K
void show_temperature(void)
{
      u16 adc_val=Adc_read(0x00);
      double ntc_val;
      double temperature;
      u16 tempX10;
      
      ntc_val=4096.0*10000/adc_val-10000;
      temperature=1/(log(ntc_val/10000)/3950+1/(273.15+25))-273.15;
      tempX10=(u16)(temperature*10);

      digit_show((u8)(tempX10/100%10),3,0);
      delay_ms(5);
      digit_show((u8)(tempX10/10%10),2,1);
      delay_ms(5);
      digit_show((u8)(tempX10%10),1,0);
      delay_ms(5);
}

mechray 发表于 昨天 16:53

第二十一期(第21集《Flash模拟EEPROM》)

这集貌似跟着移植代码就好了,比较顺利。


#include "eeprom.h"

//========================================================================
// 函数: void DisableEEPROM(void)
// 描述: 禁止EEPROM.
// 参数: none.
// 返回: none.
// 版本: V1.0, 2014-6-30
//========================================================================
void DisableEEPROM(void)      //禁止访问EEPROM
{
    IAP_CONTR = 0;          //关闭 IAP 功能
    IAP_CMD = 0;            //清除命令寄存器
    IAP_TRIG = 0;         //清除触发寄存器
    IAP_ADDRE = 0xff;       //将地址设置到非 IAP 区域
    IAP_ADDRH = 0xff;       //将地址设置到非 IAP 区域
    IAP_ADDRL = 0xff;
}

//========================================================================
// 函数: void EEPROM_Trig(void)
// 描述: 触发EEPROM操作.
// 参数: none.
// 返回: none.
// 版本: V1.0, 2014-6-30
//========================================================================
void EEPROM_Trig(void)
{
    F0 = EA;    //保存全局中断
    EA = 0;   //禁止中断, 避免触发命令无效
    IAP_TRIG = 0x5A;
    IAP_TRIG = 0xA5;    //先送5AH,再送A5H到IAP触发寄存器,每次都需要如此
                        //送完A5H后,IAP命令立即被触发启动
                        //CPU等待IAP完成后,才会继续执行程序。
    _nop_();   //多级流水线的指令系统,触发命令后建议加4个NOP,保证IAP_DATA的数据完成准备
    _nop_();
    _nop_();
    _nop_();
    EA = F0;    //恢复全局中断
}

//========================================================================
// 函数: void EEPROM_SectorErase(u32 EE_address)
// 描述: 擦除一个扇区.
// 参数: EE_address:要擦除的EEPROM的扇区中的一个字节地址.
// 返回: none.
// 版本: V1.0, 2014-6-30
//========================================================================
void EEPROM_SectorErase(u32 EE_address)
{
    IAP_ENABLE();                     //设置等待时间,允许IAP操作,送一次就够
    IAP_ERASE();                        //宏调用, 送扇区擦除命令,命令不需改变时,不需重新送命令
                                        //只有扇区擦除,没有字节擦除,512字节/扇区。
                                        //扇区中任意一个字节地址都是扇区地址。
    IAP_ADDRE = (u8)(EE_address >> 16); //送扇区地址高字节(地址需要改变时才需重新送地址)
    IAP_ADDRH = (u8)(EE_address >> 8);//送扇区地址中字节(地址需要改变时才需重新送地址)
    IAP_ADDRL = (u8)EE_address;         //送扇区地址低字节(地址需要改变时才需重新送地址)
    EEPROM_Trig();                      //触发EEPROM操作
    DisableEEPROM();                  //禁止EEPROM操作
}

//========================================================================
// 函数: void EEPROM_read_n(u32 EE_address,u8 *DataAddress,u8 lenth)
// 描述: 读N个字节函数.
// 参数: EE_address:要读出的EEPROM的首地址.
//       DataAddress: 要读出数据的指针.
//       length:      要读出的长度
// 返回: 0: 写入正确.1: 写入长度为0错误.2: 写入数据错误.
// 版本: V1.0, 2014-6-30
//========================================================================
void EEPROM_read_n(u32 EE_address,u8 *DataAddress,u8 length)
{
    IAP_ENABLE();                           //设置等待时间,允许IAP操作,送一次就够
    IAP_READ();                           //送字节读命令,命令不需改变时,不需重新送命令
    do
    {
      IAP_ADDRE = (u8)(EE_address >> 16); //送地址高字节(地址需要改变时才需重新送地址)
      IAP_ADDRH = (u8)(EE_address >> 8);//送地址中字节(地址需要改变时才需重新送地址)
      IAP_ADDRL = (u8)EE_address;         //送地址低字节(地址需要改变时才需重新送地址)
      EEPROM_Trig();                      //触发EEPROM操作
      *DataAddress = IAP_DATA;            //读出的数据送往
      EE_address++;
      DataAddress++;
    }while(--length);

    DisableEEPROM();
}


//========================================================================
// 函数: u8 EEPROM_write_n(u32 EE_address,u8 *DataAddress,u8 length)
// 描述: 写N个字节函数.
// 参数: EE_address:要写入的EEPROM的首地址.
//       DataAddress: 要写入数据的指针.
//       length:      要写入的长度
// 返回: 0: 写入正确.1: 写入长度为0错误.2: 写入数据错误.
// 版本: V1.0, 2014-6-30
//========================================================================
u8 EEPROM_write_n(u32 EE_address,u8 *DataAddress,u8 length)
{
    u8i;
    u16 j;
    u8*p;
   
    if(length == 0) return 1;   //长度为0错误

    IAP_ENABLE();                     //设置等待时间,允许IAP操作,送一次就够
    i = length;
    j = EE_address;
    p = DataAddress;
    IAP_WRITE();                            //宏调用, 送字节写命令
    do
    {
      IAP_ADDRE = (u8)(EE_address >> 16); //送地址高字节(地址需要改变时才需重新送地址)
      IAP_ADDRH = (u8)(EE_address >> 8);//送地址中字节(地址需要改变时才需重新送地址)
      IAP_ADDRL = (u8)EE_address;         //送地址低字节(地址需要改变时才需重新送地址)
      IAP_DATA= *DataAddress;         //送数据到IAP_DATA,只有数据改变时才需重新送
      EEPROM_Trig();                      //触发EEPROM操作
      EE_address++;                     //下一个地址
      DataAddress++;                      //下一个数据
    }while(--length);                     //直到结束

    EE_address = j;
    length = i;
    DataAddress = p;
    i = 0;
    IAP_READ();                           //读N个字节并比较
    do
    {
      IAP_ADDRE = (u8)(EE_address >> 16); //送地址高字节(地址需要改变时才需重新送地址)
      IAP_ADDRH = (u8)(EE_address >> 8);//送地址中字节(地址需要改变时才需重新送地址)
      IAP_ADDRL = (u8)EE_address;         //送地址低字节(地址需要改变时才需重新送地址)
      EEPROM_Trig();                      //触发EEPROM操作
      if(*DataAddress != IAP_DATA)      //读出的数据与源数据比较
      {
            i = 2;
            break;
      }
      EE_address++;
      DataAddress++;
    }while(--length);

    DisableEEPROM();
    return i;
}

#define START_ADDR 0
#define PWD 0xfe
u8 SYS_RUNTIME=0;
void EEPROM_Init(void)
{
        u8 dat; //0:操作码 1:开机次数 2:校验码
        EEPROM_read_n(START_ADDR,dat,3);
        if(dat!=PWD)
        {
                EEPROM_SectorErase(START_ADDR);
                dat=PWD;
                dat=0;
                dat=dat+dat;
                EEPROM_write_n(START_ADDR,dat,3);
                SYS_RUNTIME=0;
        }
        else{
                if(dat==(u8)(dat+dat)){
                        SYS_RUNTIME=dat;
                        SYS_RUNTIME++;
                        EEPROM_SectorErase(START_ADDR);
                        dat=PWD;
                        dat=SYS_RUNTIME;
                        dat=dat+dat;
                        EEPROM_write_n(START_ADDR,dat,3);
                }
        }
}


页: 1 [2]
查看完整版本: 利用普中A2开发板学习AI8051U的心得