打印
[STM8]

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

[复制链接]
1225|23
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主

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

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

#include "stdio.h"
#include "iostm8s105k4.h"
#include "SYSTEM.h"
#include "UART.h"
#include "DELAY.h"
#include "IIC.h"
#include "FONT0805.h"
#include "SH1106.h"
#include "IIC_ROM.h"

#define   AT24C02 160                                   //从机地址

unsigned short A;
unsigned char  A2[]={"0AABBCCDD"};
unsigned char  A3[]={"066778899"};
unsigned char  A4[8];
unsigned char  i;

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

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

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

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

    if(A>=6000) A=0;

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

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

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

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

//-------------------------------------------

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

//-------------------------------------------

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

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

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

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

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

//-------------------------------------------

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

//-------------------------------------------

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

//-------------------------------------------

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

//-------------------------------------------

void IIC_WriteData(u8 addr, u8 x, u8 *dat)               //主机写入多个数据(从机地址,写入数量,待写的数据地址)
{
  u8 i=0;
  IIC_SendAddr(addr,0);                                  //定位从机,写入
  while(i<x)
  {
    IIC_WriteByte(*(dat+i));
    i++;
  }
  IIC_Stop();
}

//-------------------------------------------

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

//-------------------------------------------

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

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

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

#define  IIC_SCL  PB_ODR_ODR4                            //SCL
#define  IIC_SDA  PB_ODR_ODR5                            //SDA

void IIC_Delay(void)
{                        
  asm("nop");
  asm("nop");
}

//IIC初始化
void IIC_Init(void)
{
  PB_CR1_C14=0;                                          //设置PB4开漏输出, 由CR2相应的位做输出摆率控制
  PB_CR1_C15=0;
  
  PB_CR2_C24=1;                                          //设置PB4速度,0=2MHz,1=10MHz
  PB_CR2_C25=1;
  
  PB_DDR_DDR4=1;                                         //设置PB4为输出
  PB_DDR_DDR5=1;
  
  IIC_SCL=1;                                             //时钟数据STOP
  IIC_Delay();
  IIC_SDA=1;
  IIC_Delay();
}

//向接收方写入一个字节
void IIC_WriteByte(u8 dat)
{
  u8 i;
  for(i=0;i<8;i++)
  {
    if(dat&0x80) IIC_SDA=1; else IIC_SDA=0;
    IIC_Delay();
    IIC_SCL=1;
    IIC_Delay();
    IIC_SCL=0;
    IIC_Delay();
    dat<<=1;
  }
  //模拟接收ACK
  IIC_SDA=0;
  IIC_Delay();
  IIC_SCL=1;
  IIC_Delay();
  IIC_SCL=0;
  IIC_Delay();
  IIC_SDA=0;
  IIC_Delay();
}

//写接收方地址与读写方向
void IIC_SendAddr(u8 addr, u8 rw)
{
  IIC_Stop();                                            //加入此句是为了兼容再次START
  
  IIC_SDA=0;                                             //发起Stair信息
  IIC_Delay();
  IIC_SCL=0;
  IIC_Delay();
  
  if(rw) IIC_WriteByte(addr+1); else IIC_WriteByte(addr);  //加载(地址+读写位)
}

//结束信号
void IIC_Stop(void)
{
  IIC_SCL=1;
  IIC_Delay();
  IIC_SDA=1;
  IIC_Delay();
}


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

//
#include "SH1106.h"

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

//光标定位
void OLED_Cursor(unsigned char x, unsigned char y)
{
  unsigned char x2;
  x2=x+2;                                          
  IIC_SendAddr(0x78,0);
  IIC_WriteByte(0x00);
  IIC_WriteByte(0xb0+y);
  IIC_WriteByte(((x2&0xf0)>>4)|0x10);
  IIC_WriteByte((x2&0x0f));
  IIC_Stop();
}             
                                    
//清屏
void OLED_Clear(void)  
{  
  unsigned char i,n;                    
  for(i=0;i<8;i++)  
  {  
    IIC_SendAddr(0x78,0);                          //高7位器件地址+1位R/W
    IIC_WriteByte(0x00);                           //准备写指令
    IIC_WriteByte(0xb0+i);                         //设置页地址(0~7)
    IIC_WriteByte(0x00);                           //设置显示位置—列低地址
    IIC_WriteByte(0x10);                           //设置显示位置—列高地址
    IIC_SendAddr(0x78,0);
    IIC_WriteByte(0x40);                           //准备写数据
    for(n=0;n<132;n++) IIC_WriteByte(0);           //兼容128和132点阵
    IIC_Stop();
  }
}

//=====================================================================================

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

  IIC_SendAddr(0x78,0);
  IIC_WriteByte(0x40);

  while(i<5)                                       //一个字符的字模是5个字节,就是5*8点阵  
  {
    IIC_WriteByte(font0805[No]);
    i++;  
    No++;  
  }  
  IIC_WriteByte(0);                                //每个字符之间空一列  
  IIC_Stop();
}   

//=====================================================================================  

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

//=======================================================================================  

void OLED_ShowU16(unsigned int dat)                //显示U16变量
{
  unsigned int i;
  for(i=10000; i>=1; i=i/10)
  {
    OLED_ShowChar(dat/i%10+0x30);
  }
}   

//初始化                                    
void OLED_Init(void)                               //下列参数参考OLED厂家的设置
{
  DelayMs(99);
  IIC_SendAddr(0x78,0);                            //总线OLED地址
  IIC_WriteByte(0x00);                             //准备写指令
  IIC_WriteByte(0xae);                             //关显示
  IIC_WriteByte(0xd5);                             //晶振频率
  IIC_WriteByte(0x80);
  IIC_WriteByte(0xa8);                             //duty 设置
  IIC_WriteByte(0x3f);                             //duty=1/64
  IIC_WriteByte(0xd3);                             //显示偏移
  IIC_WriteByte(0x00);
  IIC_WriteByte(0x40);                             //起始行
  IIC_WriteByte(0x8d);                             //升压允许
  IIC_WriteByte(0x14);
  IIC_WriteByte(0x20);                             //page address mode
  IIC_WriteByte(0x02);
  IIC_WriteByte(0xc8);                             //行扫描顺序:从上到下
  IIC_WriteByte(0xa1);                             //列扫描顺序:从左到右
  IIC_WriteByte(0xda);                             //sequential configuration
  IIC_WriteByte(0x12);
  IIC_WriteByte(0x81);                             //微调对比度,本指令的 0x81 不要改动,改下面的值
  IIC_WriteByte(0x01);                             //微调对比度的值,可设置范围 0x00~0xff
  IIC_WriteByte(0xd9);                             //Set Pre-Charge Period
  IIC_WriteByte(0xf1);
  IIC_WriteByte(0xdb);                             //Set VCOMH Deselect Level
  IIC_WriteByte(0x40);
  IIC_WriteByte(0xaf);                             //开显示
  IIC_Stop();
}


最后是源代码和图片


QQ图片20211009113201.jpg (398.98 KB )

QQ图片20211009113201.jpg

【模板】STM8S105_UART2_IIC_RW.rar

928.6 KB

使用特权

评论回复
沙发
xiaoqizi| | 2021-11-2 12:08 | 只看该作者
防卡死的原理是什么呢

使用特权

评论回复
板凳
木木guainv| | 2021-11-2 12:14 | 只看该作者
地址需要一个一个的进行处理定义吗

使用特权

评论回复
地板
tpgf| | 2021-11-2 12:16 | 只看该作者
会不会出现时序混乱的问题

使用特权

评论回复
5
磨砂| | 2021-11-2 12:37 | 只看该作者
就是重发机制吗

使用特权

评论回复
6
晓伍| | 2021-11-2 12:39 | 只看该作者
可以按照这个方式是会看

使用特权

评论回复
7
perseverance51| | 2023-3-29 11:01 | 只看该作者
I2C接 PB4和PB5怎么屏幕点不亮

使用特权

评论回复
8
bqyj| | 2023-3-29 20:10 | 只看该作者
即使人为的干扰总线,主机也还能正常工作

使用特权

评论回复
9
gongche| | 2023-3-29 20:14 | 只看该作者
用来驱动OLED的效果也是非常好

使用特权

评论回复
10
gongche| | 2023-3-29 20:19 | 只看该作者
OLED驱动具有较高的通讯效率

使用特权

评论回复
11
gongche| | 2023-3-29 20:23 | 只看该作者
地址需要一个一个的进行处理定义吗

使用特权

评论回复
12
gongche| | 2023-3-29 20:32 | 只看该作者
I2C接 PB4和PB5怎么屏幕点不亮

使用特权

评论回复
13
panxiaoyi|  楼主 | 2023-4-2 17:59 | 只看该作者
很久没有玩STM8了,这个代码是经过反复测试的,记得切换端口是需要配置字的,忘记了,现在转战国产芯片了

使用特权

评论回复
14
MessageRing| | 2023-4-5 23:14 | 只看该作者
防止卡死就是卡了 的时候自动复位

使用特权

评论回复
15
supernan| | 2023-7-5 19:11 | 只看该作者
防卡死的原理是什么呢

使用特权

评论回复
16
xxrs| | 2023-7-5 19:12 | 只看该作者
地址需要一个一个的进行处理定义吗

使用特权

评论回复
17
dengdc| | 2023-7-5 19:14 | 只看该作者
会不会出现时序混乱的问题

使用特权

评论回复
18
heweibig| | 2023-7-5 19:15 | 只看该作者

就是重发机制吗

使用特权

评论回复
19
wuhany| | 2023-7-5 19:17 | 只看该作者
可以按照这个方式是会看

使用特权

评论回复
20
huangchui| | 2023-7-5 19:19 | 只看该作者
I2C接 PB4和PB5怎么屏幕点不亮

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

47

主题

384

帖子

2

粉丝