[STM8] STM8硬件I2C_IIC驱动OLED_AT24C02(防卡死)实现单主机多从机通讯

[复制链接]
 楼主| panxiaoyi 发表于 2021-10-9 11:45 | 显示全部楼层 |阅读模式

STM8 硬件 I2C IIC 驱动 OLED AT24C02 实现单主机多从机通讯
本例最大的特点就是主机带防卡死功能,即使人为的干扰总线,主机也还能正常工作

  1. /*
  2. //STM8S105_IAR_V3.11
  3. //本例由panxiaoyi(QQ68354248)原创,并在21ic.com首发,转载请保留,谢谢!
  4. //本例使用硬件IIC读写AT24C02,并驱动IIC接口的OLED显示屏,适用于单主机,多从机通讯
  5. //本例的主机发送和主机接收均使用软件查询方式
  6. //本例在每次等待总线响应时,都加入了计数递减功能,当计数到零时就强制退出等待来防止主机被卡死
  7. //本例没有错误重发数据功能,遇到错误后则IIC重新复位
  8. */

  9. #include "stdio.h"
  10. #include "iostm8s105k4.h"
  11. #include "SYSTEM.h"
  12. #include "UART.h"
  13. #include "DELAY.h"
  14. #include "IIC.h"
  15. #include "FONT0805.h"
  16. #include "SH1106.h"
  17. #include "IIC_ROM.h"

  18. #define   AT24C02 160                                   //从机地址

  19. unsigned short A;
  20. unsigned char  A2[]={"0AABBCCDD"};
  21. unsigned char  A3[]={"066778899"};
  22. unsigned char  A4[8];
  23. unsigned char  i;

  24. int main( void )
  25. {
  26.   SYSTEM_Init();                                        //系统初始化
  27.   UART2_Init();                                         //串口2初始化
  28.   IIC_Init();                                           //IIC初始化
  29.   
  30.   asm("RIM");                                           //插入汇编,使能全局中断
  31.   
  32.   printf("QQ68354248\n");                               //使用该语句前必须包含stdio.h文件并重定义putchar函数
  33.   
  34.   OLED_Init();
  35.   OLED_Clear();                                         //清屏
  36.   OLED_Cursor(11,1);                                    //光标列行定位
  37.   OLED_ShowString("STM8S105 => SH1106");                //显示字符串
  38.   OLED_Cursor(53,3);
  39.   OLED_ShowU16(A);                                      //以十进制显示U16变量
  40.   OLED_Cursor(65,5);
  41.   OLED_ShowChar('H');                                   //显示字符

  42.   A2[0]=24;                                             //把AT24C02的ROM地址放在数组成员[0]里面
  43.   A3[0]=24;
  44.   
  45.   while(1)
  46.   {
  47.     A++;
  48.     OLED_Cursor(53,3);
  49.     OLED_ShowU16(A);
  50.    
  51.     if(A==3000) IIC_WriteData(AT24C02,9,A2);            //主机写入(从机地址,写入数量,待写数据)
  52.     if(A==6000) IIC_WriteData(AT24C02,9,A3);            //写入地址后,再写ROM地址,再写需要保存的数组
  53.    
  54.     //DelayMs(2);                                       //AT24C02内部写操作时,对寻址不响应,建议延时操作

  55.     IIC_ROM_Addr(AT24C02,24);                           //要读AT24C02的数据时,先写人ROM的地址(伪写)
  56.     IIC_ReadData(AT24C02,8,A4);                         //主机读取(从机地址,读取数量,保存地址)

  57.     OLED_Cursor(47,7);
  58.     for(i=0;i<8;i++) OLED_ShowChar(*(A4+i));            //显示读取的数据

  59.     if(A>=6000) A=0;

  60.     //下面是测试语句,如果IIC卡死,则LED不会闪烁
  61.    
  62.     PE_DDR_DDR5=1;                                      //输入=0, 输出=1
  63.     PE_CR1_C15=1;                                       //如果是输出,0开漏,1推换; 如果是输入,0高阻抗,1上拉
  64.     if((A%100)==0) PE_ODR_ODR5=~PE_ODR_ODR5;            //翻转电平驱动LED
  65.   }
  66. }

  1. /*
  2. //STM8S105_IAR_V3.11
  3. //本例由panxiaoyi(QQ68354248)原创,并在21ic.com首发,转载请保留,谢谢!
  4. //本例使用硬件IIC读写AT24C02,并驱动IIC接口的OLED显示屏,适用于单主机,多从机通讯
  5. //本例的主机发送和主机接收均使用软件查询方式
  6. //本例在每次等待总线响应时,都加入了计数递减功能,当计数到零时就强制退出等待来防止主机被卡死
  7. //本例没有错误重发数据功能,遇到错误后则IIC重新复位
  8. */

  9. #include "IIC.h"
  10. #define   IIC_COUNT 64                                   //设定计数初值,CPU越快,通讯速度越慢,则此数值应更大

  11. u8 iic_count=IIC_COUNT;                                  //计数防卡死,如果CPU时钟远大于16M,则建议使用U16变量
  12. u8 *count=&iic_count;

  13. //-------------------------------------------

  14. #pragma vector=19+2                                      //STM8S的IIC中断向量就一个,请留意中断标记是否有清零
  15. __interrupt void I2C_IRQHandler(void)                    //本例没有用到该中断函数
  16. {
  17.   I2C_ITR_ITEVTEN=0;                                     //关闭事件中断
  18.   I2C_ITR_ITBUFEN=0;                                     //关闭从机的接收缓冲未读/发送缓冲空中断,主机模式时无效!
  19.   I2C_ITR_ITERREN=0;                                     //关闭错误与仲裁失败中断
  20. }

  21. //-------------------------------------------

  22. void IIC_Init(void)
  23. {
  24.   CLK_PCKENR1 |= 1;                                      //外设IIC时钟门控使能
  25.   I2C_CR2_SWRST=1;                                       //软件复位IIC,复位后SWRST自动清零
  26.   I2C_CR1=0;
  27.   I2C_CR2=0;
  28.   I2C_CR1_NOSTRETCH=0;                                   //时钟延展使能(默认)
  29.   I2C_CR1_ENGC=1;                                        //响应广播地址0x00
  30.   I2C_CR2_ACK=1;                                         //回复ACK使能

  31.   //下面是100K通讯速度的设置(设置高电平宽度)
  32.   //输出时钟的电平的宽度比 L:H=1:1 (固定值)
  33.   //要求总线SCL电平的上升时间最大为1000ns

  34.   I2C_FREQR=16;                                          //请填写驱动IIC的时钟,如16则表示16MHz
  35.   I2C_CCRH=0;
  36.   I2C_CCRH_F_S=0;                                        //标准模式
  37.   I2C_CCRL=80;                                           //设置H电平=80 则L电平=80 速度16M/(80+80)=100K
  38.   I2C_TRISER_TRISE=17;                                   //设置SCL的最大上升值=1000/62.5+1=17

  39.   //下面是400K通讯速度的设置(设置高电平宽度)
  40.   //输出时钟的电平的宽度比 L:H=2:1 (默认值)
  41.   //要求总线SCL电平的上升时间最大为300ns
  42.   
  43.   I2C_FREQR=16;                                          //请填写驱动IIC的时钟,如16则表示16MHz
  44.   I2C_CCRH=0;
  45.   I2C_CCRH_F_S=1;                                        //快速模式,其实就是改变L:H的比值
  46.   I2C_CCRL=14;                                           //设置H电平=14 则L电平=28 速度16M/(14+28)=380K
  47.   I2C_TRISER_TRISE=6;                                    //设置SCL的最大上升值=300/62.5+1=6

  48.   I2C_CR1_PE=1;                                          //使能IIC
  49.   I2C_CR2_ACK=1;                                         //回复ACK使能,只有在使能IIC后,本语句才能生效
  50. }

  51. //-------------------------------------------

  52. void IIC_SendAddr(u8 addr, u8 rw)                        //主机发送地址与读写指令(高7位器件地址+1位R/W)
  53. {
  54.   if(!iic_count) return;                                 //如果曾经出现过卡死就退出
  55.   I2C_CR2_START=1;                                       //发送START
  56.   iic_count=IIC_COUNT;                                   //计数赋初始值
  57.   while(!I2C_SR1_SB) if(!(*count)--) break;              //判断等待START完成,如果卡死就退出循环
  58.   if(!iic_count) return;
  59.   if(rw) I2C_DR=addr+1; else I2C_DR=addr;                //加载(地址+读写位,SB即被硬件自动清零)
  60.   iic_count=IIC_COUNT;
  61.   while(!I2C_SR1_ADDR) if(!(*count)--) break;            //判断等待地址发送完成,完成后,数据寄存器空置位
  62.   I2C_SR3;                                               //读取SR1后再读写SR3,ADDR标记位清零
  63.   
  64. }

  65. //-------------------------------------------

  66. void IIC_ReadData(u8 addr, u8 x, u8 *dat)                //主机读取多个数据(从机地址,读取数量,保存地址)
  67. {
  68.   u8 i=0;
  69.   IIC_SendAddr(addr,1);                                  //定位从机,读取
  70.   while(i<x)
  71.   {
  72.     if(!iic_count) break;
  73.     iic_count=IIC_COUNT;
  74.     while(!I2C_SR1_RXNE) if(!(*count)--) break;          //等待收到数据
  75.     if(i==(x-2)) I2C_CR2_ACK=0;                          //准备读最后一个字节之前关闭ACK
  76.     *(dat+i)=I2C_DR;                                     //读写DR即可清零RXNE
  77.     i++;
  78.   }
  79.   I2C_CR2_ACK=1;                                         //回复ACK使能
  80.   IIC_Stop();
  81. }

  82. //-------------------------------------------

  83. void IIC_WriteByte(u8 dat)                               //主机写单个字节
  84. {
  85.   if(!iic_count) return;
  86.   I2C_DR=dat;
  87.   iic_count=IIC_COUNT;
  88.   while(!I2C_SR1_TXE) if(!(*count)--) break;             //判断等待缓冲寄存器空
  89. }

  90. //-------------------------------------------

  91. void IIC_WriteData(u8 addr, u8 x, u8 *dat)               //主机写入多个数据(从机地址,写入数量,待写的数据地址)
  92. {
  93.   u8 i=0;
  94.   IIC_SendAddr(addr,0);                                  //定位从机,写入
  95.   while(i<x)
  96.   {
  97.     IIC_WriteByte(*(dat+i));
  98.     i++;
  99.   }
  100.   IIC_Stop();
  101. }

  102. //-------------------------------------------

  103. void IIC_Stop(void)                                      //主机发送STOP后,设备就进入了从模式,从机发送STOP则是释放总线
  104. {
  105.   I2C_CR2_STOP=1;                                        //发送STOP信号(只有当前字节接收或者发送完成后,STOP才开始)
  106.   iic_count=IIC_COUNT;
  107.   while(I2C_SR3_MSL) if(!(*count)--) break;              //判断等待STOP发送完成
  108.   iic_count=IIC_COUNT;                                   //计数重新赋值
  109.   I2C_SR2_AF=0;                                          //清零各种错误
  110.   I2C_SR2_ARLO=0;
  111.   I2C_SR2_BERR=0;
  112. }

  113. //-------------------------------------------

  114. void IIC_Off(void)
  115. {
  116.   I2C_CR1_PE=0;                                          //先关闭IIC
  117.   CLK_PCKENR1 &= (~1);                                   //再关闭IIC输入时钟
  118. }

当然,本例也具有软件模拟IIC功能,用来驱动OLED的效果也是非常好的(只能是主机单写功能)

  1. //软件模拟IIC主机发送
  2. //在时钟16M下,速度相当于硬件IIC的400K速率

  3. #define  IIC_SCL  PB_ODR_ODR4                            //SCL
  4. #define  IIC_SDA  PB_ODR_ODR5                            //SDA

  5. void IIC_Delay(void)
  6. {                        
  7.   asm("nop");
  8.   asm("nop");
  9. }

  10. //IIC初始化
  11. void IIC_Init(void)
  12. {
  13.   PB_CR1_C14=0;                                          //设置PB4开漏输出, 由CR2相应的位做输出摆率控制
  14.   PB_CR1_C15=0;
  15.   
  16.   PB_CR2_C24=1;                                          //设置PB4速度,0=2MHz,1=10MHz
  17.   PB_CR2_C25=1;
  18.   
  19.   PB_DDR_DDR4=1;                                         //设置PB4为输出
  20.   PB_DDR_DDR5=1;
  21.   
  22.   IIC_SCL=1;                                             //时钟数据STOP
  23.   IIC_Delay();
  24.   IIC_SDA=1;
  25.   IIC_Delay();
  26. }

  27. //向接收方写入一个字节
  28. void IIC_WriteByte(u8 dat)
  29. {
  30.   u8 i;
  31.   for(i=0;i<8;i++)
  32.   {
  33.     if(dat&0x80) IIC_SDA=1; else IIC_SDA=0;
  34.     IIC_Delay();
  35.     IIC_SCL=1;
  36.     IIC_Delay();
  37.     IIC_SCL=0;
  38.     IIC_Delay();
  39.     dat<<=1;
  40.   }
  41.   //模拟接收ACK
  42.   IIC_SDA=0;
  43.   IIC_Delay();
  44.   IIC_SCL=1;
  45.   IIC_Delay();
  46.   IIC_SCL=0;
  47.   IIC_Delay();
  48.   IIC_SDA=0;
  49.   IIC_Delay();
  50. }

  51. //写接收方地址与读写方向
  52. void IIC_SendAddr(u8 addr, u8 rw)
  53. {
  54.   IIC_Stop();                                            //加入此句是为了兼容再次START
  55.   
  56.   IIC_SDA=0;                                             //发起Stair信息
  57.   IIC_Delay();
  58.   IIC_SCL=0;
  59.   IIC_Delay();
  60.   
  61.   if(rw) IIC_WriteByte(addr+1); else IIC_WriteByte(addr);  //加载(地址+读写位)
  62. }

  63. //结束信号
  64. void IIC_Stop(void)
  65. {
  66.   IIC_SCL=1;
  67.   IIC_Delay();
  68.   IIC_SDA=1;
  69.   IIC_Delay();
  70. }


本OLED驱动具有较高的通讯效率

  1. //
  2. #include "SH1106.h"

  3. //SSD1306(12864)的列显示从列地址0开始
  4. // SH1106(13264)的列显示从列地址2开始

  5. //光标定位
  6. void OLED_Cursor(unsigned char x, unsigned char y)
  7. {
  8.   unsigned char x2;
  9.   x2=x+2;                                          
  10.   IIC_SendAddr(0x78,0);
  11.   IIC_WriteByte(0x00);
  12.   IIC_WriteByte(0xb0+y);
  13.   IIC_WriteByte(((x2&0xf0)>>4)|0x10);
  14.   IIC_WriteByte((x2&0x0f));
  15.   IIC_Stop();
  16. }             
  17.                                     
  18. //清屏
  19. void OLED_Clear(void)  
  20. {  
  21.   unsigned char i,n;                    
  22.   for(i=0;i<8;i++)  
  23.   {  
  24.     IIC_SendAddr(0x78,0);                          //高7位器件地址+1位R/W
  25.     IIC_WriteByte(0x00);                           //准备写指令
  26.     IIC_WriteByte(0xb0+i);                         //设置页地址(0~7)
  27.     IIC_WriteByte(0x00);                           //设置显示位置—列低地址
  28.     IIC_WriteByte(0x10);                           //设置显示位置—列高地址
  29.     IIC_SendAddr(0x78,0);
  30.     IIC_WriteByte(0x40);                           //准备写数据
  31.     for(n=0;n<132;n++) IIC_WriteByte(0);           //兼容128和132点阵
  32.     IIC_Stop();
  33.   }
  34. }

  35. //=====================================================================================

  36. void OLED_ShowChar(unsigned char dat)              //显示字符
  37. {     
  38.   unsigned char i=0;  
  39.   unsigned int No;  
  40.   No=dat-32;                                       //字模数据是由空格开始,空格字符的ASCII的值就是32  
  41.   No=No*5;                                         //每个字符的字模是5个字节  

  42.   IIC_SendAddr(0x78,0);
  43.   IIC_WriteByte(0x40);

  44.   while(i<5)                                       //一个字符的字模是5个字节,就是5*8点阵  
  45.   {
  46.     IIC_WriteByte(font0805[No]);
  47.     i++;  
  48.     No++;  
  49.   }  
  50.   IIC_WriteByte(0);                                //每个字符之间空一列  
  51.   IIC_Stop();
  52. }   

  53. //=====================================================================================  

  54. void OLED_ShowString(unsigned char *s)             //显示字符串,C编译器会在字符串后面加\0
  55. {
  56.   while(*s)                                        //检测字符串结束符
  57.   {
  58.     OLED_ShowChar(*s++);
  59.   }
  60. }      

  61. //=======================================================================================  

  62. void OLED_ShowU16(unsigned int dat)                //显示U16变量
  63. {
  64.   unsigned int i;
  65.   for(i=10000; i>=1; i=i/10)
  66.   {
  67.     OLED_ShowChar(dat/i%10+0x30);
  68.   }
  69. }   

  70. //初始化                                    
  71. void OLED_Init(void)                               //下列参数参考OLED厂家的设置
  72. {
  73.   DelayMs(99);
  74.   IIC_SendAddr(0x78,0);                            //总线OLED地址
  75.   IIC_WriteByte(0x00);                             //准备写指令
  76.   IIC_WriteByte(0xae);                             //关显示
  77.   IIC_WriteByte(0xd5);                             //晶振频率
  78.   IIC_WriteByte(0x80);
  79.   IIC_WriteByte(0xa8);                             //duty 设置
  80.   IIC_WriteByte(0x3f);                             //duty=1/64
  81.   IIC_WriteByte(0xd3);                             //显示偏移
  82.   IIC_WriteByte(0x00);
  83.   IIC_WriteByte(0x40);                             //起始行
  84.   IIC_WriteByte(0x8d);                             //升压允许
  85.   IIC_WriteByte(0x14);
  86.   IIC_WriteByte(0x20);                             //page address mode
  87.   IIC_WriteByte(0x02);
  88.   IIC_WriteByte(0xc8);                             //行扫描顺序:从上到下
  89.   IIC_WriteByte(0xa1);                             //列扫描顺序:从左到右
  90.   IIC_WriteByte(0xda);                             //sequential configuration
  91.   IIC_WriteByte(0x12);
  92.   IIC_WriteByte(0x81);                             //微调对比度,本指令的 0x81 不要改动,改下面的值
  93.   IIC_WriteByte(0x01);                             //微调对比度的值,可设置范围 0x00~0xff
  94.   IIC_WriteByte(0xd9);                             //Set Pre-Charge Period
  95.   IIC_WriteByte(0xf1);
  96.   IIC_WriteByte(0xdb);                             //Set VCOMH Deselect Level
  97.   IIC_WriteByte(0x40);
  98.   IIC_WriteByte(0xaf);                             //开显示
  99.   IIC_Stop();
  100. }


最后是源代码和图片


QQ图片20211009113201.jpg

【模板】STM8S105_UART2_IIC_RW.rar

928.6 KB, 下载次数: 21

xiaoqizi 发表于 2021-11-2 12:08 | 显示全部楼层
防卡死的原理是什么呢
木木guainv 发表于 2021-11-2 12:14 | 显示全部楼层
地址需要一个一个的进行处理定义吗
tpgf 发表于 2021-11-2 12:16 | 显示全部楼层
会不会出现时序混乱的问题
磨砂 发表于 2021-11-2 12:37 | 显示全部楼层
就是重发机制吗
晓伍 发表于 2021-11-2 12:39 | 显示全部楼层
可以按照这个方式是会看
perseverance51 发表于 2023-3-29 11:01 | 显示全部楼层
I2C接 PB4和PB5怎么屏幕点不亮
bqyj 发表于 2023-3-29 20:10 | 显示全部楼层
即使人为的干扰总线,主机也还能正常工作
gongche 发表于 2023-3-29 20:14 | 显示全部楼层
用来驱动OLED的效果也是非常好
gongche 发表于 2023-3-29 20:19 | 显示全部楼层
OLED驱动具有较高的通讯效率
gongche 发表于 2023-3-29 20:23 | 显示全部楼层
地址需要一个一个的进行处理定义吗
gongche 发表于 2023-3-29 20:32 | 显示全部楼层
I2C接 PB4和PB5怎么屏幕点不亮
 楼主| panxiaoyi 发表于 2023-4-2 17:59 | 显示全部楼层
很久没有玩STM8了,这个代码是经过反复测试的,记得切换端口是需要配置字的,忘记了,现在转战国产芯片了
MessageRing 发表于 2023-4-5 23:14 | 显示全部楼层
防止卡死就是卡了 的时候自动复位
supernan 发表于 2023-7-5 19:11 | 显示全部楼层
防卡死的原理是什么呢
xxrs 发表于 2023-7-5 19:12 | 显示全部楼层
地址需要一个一个的进行处理定义吗
dengdc 发表于 2023-7-5 19:14 | 显示全部楼层
会不会出现时序混乱的问题
heweibig 发表于 2023-7-5 19:15 | 显示全部楼层

就是重发机制吗
wuhany 发表于 2023-7-5 19:17 | 显示全部楼层
可以按照这个方式是会看
huangchui 发表于 2023-7-5 19:19 | 显示全部楼层
I2C接 PB4和PB5怎么屏幕点不亮
您需要登录后才可以回帖 登录 | 注册

本版积分规则

53

主题

414

帖子

2

粉丝
快速回复 在线客服 返回列表 返回顶部

53

主题

414

帖子

2

粉丝
快速回复 在线客服 返回列表 返回顶部