嵌入式学习--江协51单片机day6

[复制链接]
发表于 2025-8-31 15:15:54 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
更新一放学习方式,之前一直都是跟着老师敲代码,但是这样会导致中间明白不了,而且有些老师背面改了,是直接在视频中添的字,导致背面查抄不出错误。以是,将会以明白为主,代码直接从源码粘贴,上课就主要是明白了。
然后总共是过三遍,上课看视频明白一边,每两个模块会写一篇博客,周末回顾会照着源码敲注释
(最近的视频大多都是1小时多一集,大概一天看一个模块,看4个小时左右)
AT24C02和DS18B20
这两个一个是I2C总线一个是单总线
AT24C02

AT24C02是E2PROM,一种存储器,使用I2C总线
重要的原理是I2C总线的时序结构,背面的代码就是模拟实现
(这里主机从机,一般是单片机做主机,模块做从机)
为了差别模块的衔接,在各个模块中会有多余的赋值操作
这里发送和接受都是以字节为单位(在吸收或者是读取数据时,要读取的数据已经位于线上,例如,这里读取时数据已经位于SDA线上)
(&常用于获取某一位,|常用于赋值某一位)
(这里的释放是指置一)
  1. #include <REGX52.H>
  2. //引脚定义
  3. sbit I2C_SCL=P2^1;
  4. sbit I2C_SDA=P2^0;
  5. /**
  6.   * @brief  I2C开始
  7.   * @param  无
  8.   * @retval 无
  9.   */
  10. void I2C_Start(void)
  11. {
  12.         I2C_SDA=1;
  13.         I2C_SCL=1;
  14.         I2C_SDA=0;
  15.         I2C_SCL=0;
  16. }
  17. /**
  18.   * @brief  I2C停止
  19.   * @param  无
  20.   * @retval 无
  21.   */
  22. void I2C_Stop(void)
  23. {
  24.         I2C_SDA=0;
  25.         I2C_SCL=1;
  26.         I2C_SDA=1;
  27. }
  28. /**
  29.   * @brief  I2C发送一个字节
  30.   * @param  Byte 要发送的字节
  31.   * @retval 无
  32.   */
  33. void I2C_SendByte(unsigned char Byte)
  34. {
  35.         unsigned char i;
  36.         for(i=0;i<8;i++)
  37.         {
  38.                 I2C_SDA=Byte&(0x80>>i);
  39.                 I2C_SCL=1;
  40.                 I2C_SCL=0;
  41.         }
  42. }
  43. /**
  44.   * @brief  I2C接收一个字节
  45.   * @param  无
  46.   * @retval 接收到的一个字节数据
  47.   */
  48. unsigned char I2C_ReceiveByte(void)
  49. {
  50.         unsigned char i,Byte=0x00;
  51.         I2C_SDA=1;
  52.         for(i=0;i<8;i++)
  53.         {
  54.                 I2C_SCL=1;
  55.                 if(I2C_SDA){Byte|=(0x80>>i);}
  56.                 I2C_SCL=0;
  57.         }
  58.         return Byte;
  59. }
  60. /**
  61.   * @brief  I2C发送应答
  62.   * @param  AckBit 应答位,0为应答,1为非应答
  63.   * @retval 无
  64.   */
  65. void I2C_SendAck(unsigned char AckBit)
  66. {
  67.         I2C_SDA=AckBit;
  68.         I2C_SCL=1;
  69.         I2C_SCL=0;
  70. }
  71. /**
  72.   * @brief  I2C接收应答位
  73.   * @param  无
  74.   * @retval 接收到的应答位,0为应答,1为非应答
  75.   */
  76. unsigned char I2C_ReceiveAck(void)
  77. {
  78.         unsigned char AckBit;
  79.         I2C_SDA=1;
  80.         I2C_SCL=1;
  81.         AckBit=I2C_SDA;
  82.         I2C_SCL=0;
  83.         return AckBit;
  84. }
复制代码
在了解了基础模块后,我们就可以发送数据帧
  1. #include <REGX52.H>
  2. #include "I2C.h"
  3. #define AT24C02_ADDRESS                0xA0
  4. /**
  5.   * @brief  AT24C02写入一个字节
  6.   * @param  WordAddress 要写入字节的地址
  7.   * @param  Data 要写入的数据
  8.   * @retval 无
  9.   */
  10. void AT24C02_WriteByte(unsigned char WordAddress,Data)
  11. {
  12.         I2C_Start();
  13.         I2C_SendByte(AT24C02_ADDRESS);
  14.         I2C_ReceiveAck();
  15.         I2C_SendByte(WordAddress);
  16.         I2C_ReceiveAck();
  17.         I2C_SendByte(Data);
  18.         I2C_ReceiveAck();
  19.         I2C_Stop();
  20. }
  21. /**
  22.   * @brief  AT24C02读取一个字节
  23.   * @param  WordAddress 要读出字节的地址
  24.   * @retval 读出的数据
  25.   */
  26. unsigned char AT24C02_ReadByte(unsigned char WordAddress)
  27. {
  28.         unsigned char Data;
  29.         I2C_Start();
  30.         I2C_SendByte(AT24C02_ADDRESS);
  31.         I2C_ReceiveAck();
  32.         I2C_SendByte(WordAddress);
  33.         I2C_ReceiveAck();
  34.         I2C_Start();
  35.         I2C_SendByte(AT24C02_ADDRESS|0x01);
  36.         I2C_ReceiveAck();
  37.         Data=I2C_ReceiveByte();
  38.         I2C_SendAck(1);
  39.         I2C_Stop();
  40.         return Data;
  41. }
复制代码
AT24C02数据存储

在实现了各个模块后,数据存储就变得简单了
注意在写入时,要delay一段时间,在手册中规定了最短时间
int在C51中是16位,我们无论是发送还是接受都是8位,以是要给他分开
  1. #include <REGX52.H>
  2. #include "LCD1602.h"
  3. #include "Key.h"
  4. #include "AT24C02.h"
  5. #include "Delay.h"
  6. unsigned char KeyNum;
  7. unsigned int Num;
  8. void main()
  9. {
  10.         LCD_Init();
  11.         LCD_ShowNum(1,1,Num,5);
  12.         while(1)
  13.         {
  14.                 KeyNum=Key();
  15.                 if(KeyNum==1)        //K1按键,Num自增
  16.                 {
  17.                         Num++;
  18.                         LCD_ShowNum(1,1,Num,5);
  19.                 }
  20.                 if(KeyNum==2)        //K2按键,Num自减
  21.                 {
  22.                         Num--;
  23.                         LCD_ShowNum(1,1,Num,5);
  24.                 }
  25.                 if(KeyNum==3)        //K3按键,向AT24C02写入数据
  26.                 {
  27.                         AT24C02_WriteByte(0,Num%256);
  28.                         Delay(5);
  29.                         AT24C02_WriteByte(1,Num/256);
  30.                         Delay(5);
  31.                         LCD_ShowString(2,1,"Write OK");
  32.                         Delay(1000);
  33.                         LCD_ShowString(2,1,"        ");
  34.                 }
  35.                 if(KeyNum==4)        //K4按键,从AT24C02读取数据
  36.                 {
  37.                         Num=AT24C02_ReadByte(0);
  38.                         Num|=AT24C02_ReadByte(1)<<8;
  39.                         LCD_ShowNum(1,1,Num,5);
  40.                         LCD_ShowString(2,1,"Read OK ");
  41.                         Delay(1000);
  42.                         LCD_ShowString(2,1,"        ");
  43.                 }
  44.         }
  45. }
复制代码
秒表(定时器扫描数码管)

在这里老师更新了按键的实现,之前使用delay加while死循环过滤按键的震动,会出现按下后主函数无法进行(卡在死循环),这里使用定时器扫描按键就制止了这种情况,同时可以选择实现按下释放还是松开释放
  1. #include <REGX52.H>
  2. #include "Delay.h"
  3. unsigned char Key_KeyNumber;
  4. /**
  5.   * @brief  获取按键键码
  6.   * @param  无
  7.   * @retval 按下按键的键码,范围:0,1~4,0表示无按键按下
  8.   */
  9. unsigned char Key(void)
  10. {
  11.         unsigned char Temp=0;
  12.         Temp=Key_KeyNumber;
  13.         Key_KeyNumber=0;
  14.         return Temp;
  15. }
  16. /**
  17.   * @brief  获取当前按键的状态,无消抖及松手检测
  18.   * @param  无
  19.   * @retval 按下按键的键码,范围:0,1~4,0表示无按键按下
  20.   */
  21. unsigned char Key_GetState()
  22. {
  23.         unsigned char KeyNumber=0;
  24.        
  25.         if(P3_1==0){KeyNumber=1;}
  26.         if(P3_0==0){KeyNumber=2;}
  27.         if(P3_2==0){KeyNumber=3;}
  28.         if(P3_3==0){KeyNumber=4;}
  29.        
  30.         return KeyNumber;
  31. }
  32. /**
  33.   * @brief  按键驱动函数,在中断中调用
  34.   * @param  无
  35.   * @retval 无
  36.   */
  37. void Key_Loop(void)
  38. {
  39.         static unsigned char NowState,LastState;
  40.         LastState=NowState;                                //按键状态更新
  41.         NowState=Key_GetState();                //获取当前按键状态
  42.         //如果上个时间点按键按下,这个时间点未按下,则是松手瞬间,以此避免消抖和松手检测
  43.         if(LastState==1 && NowState==0)
  44.         {
  45.                 Key_KeyNumber=1;
  46.         }
  47.         if(LastState==2 && NowState==0)
  48.         {
  49.                 Key_KeyNumber=2;
  50.         }
  51.         if(LastState==3 && NowState==0)
  52.         {
  53.                 Key_KeyNumber=3;
  54.         }
  55.         if(LastState==4 && NowState==0)
  56.         {
  57.                 Key_KeyNumber=4;
  58.         }
  59. }
复制代码
可能有人对于Key的实现有疑问,为什么不直接返回KeyNumber?由于我们需要给它恢复0的状态,以是需要额外设置变量
数码管也进行了更新
添加数组Buf是为了背面体现用的,表示第i位上的数字
  1. #include <REGX52.H>
  2. #include "Delay.h"
  3. //数码管显示缓存区
  4. unsigned char Nixie_Buf[9]={0,10,10,10,10,10,10,10,10};
  5. //数码管段码表
  6. unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x00,0x40};
  7. /**
  8.   * @brief  设置显示缓存区
  9.   * @param  Location 要设置的位置,范围:1~8
  10.   * @param  Number 要设置的数字,范围:段码表索引范围
  11.   * @retval 无
  12.   */
  13. void Nixie_SetBuf(unsigned char Location,Number)
  14. {
  15.         Nixie_Buf[Location]=Number;
  16. }
  17. /**
  18.   * @brief  数码管扫描显示
  19.   * @param  Location 要显示的位置,范围:1~8
  20.   * @param  Number 要显示的数字,范围:段码表索引范围
  21.   * @retval 无
  22.   */
  23. void Nixie_Scan(unsigned char Location,Number)
  24. {
  25.         P0=0x00;                                //段码清0,消影
  26.         switch(Location)                //位码输出
  27.         {
  28.                 case 1:P2_4=1;P2_3=1;P2_2=1;break;
  29.                 case 2:P2_4=1;P2_3=1;P2_2=0;break;
  30.                 case 3:P2_4=1;P2_3=0;P2_2=1;break;
  31.                 case 4:P2_4=1;P2_3=0;P2_2=0;break;
  32.                 case 5:P2_4=0;P2_3=1;P2_2=1;break;
  33.                 case 6:P2_4=0;P2_3=1;P2_2=0;break;
  34.                 case 7:P2_4=0;P2_3=0;P2_2=1;break;
  35.                 case 8:P2_4=0;P2_3=0;P2_2=0;break;
  36.         }
  37.         P0=NixieTable[Number];        //段码输出
  38. }
  39. /**
  40.   * @brief  数码管驱动函数,在中断中调用
  41.   * @param  无
  42.   * @retval 无
  43.   */
  44. void Nixie_Loop(void)
  45. {
  46.         static unsigned char i=1;
  47.         Nixie_Scan(i,Nixie_Buf[i]);
  48.         i++;
  49.         if(i>=9){i=1;}
  50. }
复制代码
主函数中中断函数的使用也很巧妙,这个只能是个人体悟了吧
  1. #include <REGX52.H>
  2. #include "Timer0.h"
  3. #include "Key.h"
  4. #include "Nixie.h"
  5. #include "Delay.h"
  6. #include "AT24C02.h"
  7. unsigned char KeyNum;
  8. unsigned char Min,Sec,MiniSec;
  9. unsigned char RunFlag;
  10. void main()
  11. {
  12.         Timer0_Init();
  13.         while(1)
  14.         {
  15.                 KeyNum=Key();
  16.                 if(KeyNum==1)                        //K1按键按下
  17.                 {
  18.                         RunFlag=!RunFlag;        //启动标志位翻转
  19.                 }
  20.                 if(KeyNum==2)                        //K2按键按下
  21.                 {
  22.                         Min=0;                                //分秒清0
  23.                         Sec=0;
  24.                         MiniSec=0;
  25.                 }
  26.                 if(KeyNum==3)                        //K3按键按下
  27.                 {
  28.                         AT24C02_WriteByte(0,Min);        //将分秒写入AT24C02
  29.                         Delay(5);
  30.                         AT24C02_WriteByte(1,Sec);
  31.                         Delay(5);
  32.                         AT24C02_WriteByte(2,MiniSec);
  33.                         Delay(5);
  34.                 }
  35.                 if(KeyNum==4)                        //K4按键按下
  36.                 {
  37.                         Min=AT24C02_ReadByte(0);        //读出AT24C02数据
  38.                         Sec=AT24C02_ReadByte(1);
  39.                         MiniSec=AT24C02_ReadByte(2);
  40.                 }
  41.                 Nixie_SetBuf(1,Min/10);        //设置显示缓存,显示数据
  42.                 Nixie_SetBuf(2,Min%10);
  43.                 Nixie_SetBuf(3,11);
  44.                 Nixie_SetBuf(4,Sec/10);
  45.                 Nixie_SetBuf(5,Sec%10);
  46.                 Nixie_SetBuf(6,11);
  47.                 Nixie_SetBuf(7,MiniSec/10);
  48.                 Nixie_SetBuf(8,MiniSec%10);
  49.         }
  50. }
  51. /**
  52.   * @brief  秒表驱动函数,在中断中调用
  53.   * @param  无
  54.   * @retval 无
  55.   */
  56. void Sec_Loop(void)
  57. {
  58.         if(RunFlag)
  59.         {
  60.                 MiniSec++;
  61.                 if(MiniSec>=100)
  62.                 {
  63.                         MiniSec=0;
  64.                         Sec++;
  65.                         if(Sec>=60)
  66.                         {
  67.                                 Sec=0;
  68.                                 Min++;
  69.                                 if(Min>=60)
  70.                                 {
  71.                                         Min=0;
  72.                                 }
  73.                         }
  74.                 }
  75.         }
  76. }
  77. void Timer0_Routine() interrupt 1
  78. {
  79.         static unsigned int T0Count1,T0Count2,T0Count3;
  80.         TL0 = 0x18;                //设置定时初值
  81.         TH0 = 0xFC;                //设置定时初值
  82.         T0Count1++;
  83.         if(T0Count1>=20)
  84.         {
  85.                 T0Count1=0;
  86.                 Key_Loop();        //20ms调用一次按键驱动函数
  87.         }
  88.         T0Count2++;
  89.         if(T0Count2>=2)
  90.         {
  91.                 T0Count2=0;
  92.                 Nixie_Loop();//2ms调用一次数码管驱动函数
  93.         }
  94.         T0Count3++;
  95.         if(T0Count3>=10)
  96.         {
  97.                 T0Count3=0;
  98.                 Sec_Loop();        //10ms调用一次数秒表驱动函数
  99.         }
  100. }
复制代码
就到这里结束了,温度传感器下一篇再写了,太长了....
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表