浅聊软件模拟IIC(用OLED演示)

1.IIC简单的介绍

I²C(Inter-Integrated Circuit),即集成电路总线,也被称为I2C或IIC,在中文里有时称为双线串行总线。它是由飞利浦半导体(现在的NXP半导体)在1982年开发的一种简单双向二线制同步串行数据通信总线,主要用于PC的微控制器和其他模块之间的短距离通信,尤其是在嵌入式系统中。

假如IIC通信没有被发明出来,那么微控制器与被控制芯片之间就需要串口连接,假如只有一个被控制芯片,那还好,假如有四个或者很多个被控制芯片呢,串口连接起来就会很复杂了,于是IIC通信协议产生了

I²C总线设计的主要目的是为了简化电路板上的微控制器与被控芯片之间的连接,通过使用仅有的两根双向信号线(一根为数据线SDA,另一根为时钟线SCL)来实现多个设备间的通信。这种方式减少了布线复杂度,使得电路板的设计更加简洁高效。

I²C的工作原理基于主从模式,其中至少有一个设备充当主机,而其他设备作为从机。主机负责启动和终止数据传输,并且控制时钟信号;而从机则响应主机的命令。每个设备都有一个唯一的地址,主机通过这个地址来选择要通信的具体从机。

2.IIC通信的时序

谈到软件模拟任何一个通信协议时,最不可或缺的就是对他的通信协议的讲解,下面我们将根据图片与文字讲述一下IIC通信协议.根据上段所讲的IIC工作基本原理,在STM32中,主机在大部分情况下就是单片机本身了,那么被控制的芯片就是从机了.

在串口通信中我们用发送线和接收线来进行数据的传递,那么在IIC中我们则是利用SCL(时钟线)和SDA(数据线)来进行数据的发送和接收.如下图所示

那么要通过IIC发送一个数据,首先就产生一个起始信号

起始信号:

在空闲状态下,数据线和时钟线必须保持高电平.产生起始信号时SCL时钟线必须是高电平,而在这时SDA(数据线)则在此时完成由高电平到低电平的跳跃,如图

这样就产生了一个起始信号,那么在软件模拟IIC通信时,起始信该怎么写呢,

首先对GPIO口进行初始化,我们要用的是GPIOB 0和 1分别代表SCL(时钟线)和SDA(数据线)

static void OLED_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;    //定义一个GPIO_InitTypeDef类型的结构体
    
    RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);  //打开GPIOC的外设时钟
    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;  //选择控制的引脚
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;    //设置为通用开漏输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   //设置输出速率为50MHz
    GPIO_Init(GPIOB,&GPIO_InitStructure);   //调用库函数初始化GPIOA
    
    SCL_Bits(); //设B0(SCL)为高电平
    SDA_Bits(); //设PB1(SDA)为高电平
}
​

然后就开始写起始信号了

//IIC起始信号
static void IIC_Star()
{
        SCL_Bits();  //时间总线 高电平  
        SDA_Bits();  //数据总线 高电平
        delay_us(1);
        SDA_Resebits();   //数据总线 低电平
        delay_us(1);
        SCL_Resebits();  //时间总线低电平 方便后面读取数据
        delay_us(1);
}

下面是是一些宏定义方便理解

#define   SCL_Bits()        GPIO_SetBits(GPIOB,GPIO_Pin_0)
#define   SCL_Resebits()  GPIO_ResetBits(GPIOB,GPIO_Pin_0)
#define   SDA_Bits()        GPIO_SetBits(GPIOB,GPIO_Pin_1)
#define   SDA_Resebits()  GPIO_ResetBits(GPIOB,GPIO_Pin_1)
#define   SDA_Read()            GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)//读取SDA的电平
写一个字节的函数

这样一来,我们就大概看懂了IIC的起始信号时如何产生的,起始信号产生后,接下来我们就需要书写一个写一个字节的函数了,那么在SCL(时钟线)高电平的时候,所以说当我们想传输一个数据是,首先拉低SCL(时钟线),然后让SDA(数据线)为高电平或者低电平,然后再将SCL(时钟线)拉高,这样SDA(数据线)上的电平就不能发生变化,假如此时SDA(数据总线)为高电平,这样就完成了,一个字节'1'的传输,,反之则传输'0',如下图

例如我们发送的是这样的数据,他就表示发送的数据为1010000

下面就是代码部分

static void Write_IIC_Byte(unsigned char IIC_Byte)
{
    unsigned char i;  //定义变量,一个字节有八位数据
    for(i = 0;i < 8;i++)
    {
        SCL_Resebits(); //时钟线置低,为传输数据做准备
        delay_us(1);
        if(IIC_Byte & 0x80)//获取最高位
        {
             SDA_Bits();  //高电平
        }
        else{
            SDA_Resebits();//低电平
        }
        IIC_Byte<< = 1;  //左移一位,将下一位放到最高位,以便单独提取出来
        delay_us(1);
        SCL_Bits(); //时钟线置高,产生上升沿,把数据发送出去
        delay_us(1);
    }
        SCL_Resebits(); //时钟线置低
    delay_us(1);
    while(IIC_Wait_Ack());  //从机应答,这个就是接下来讲的
​
}
从机应答

当我们发送完一个数据后,从机得回答一下我们主机一下,收到,由此产生了从机应答信号,从机应答的时候,根据I2C硬件通信协议,我们也要将SCL(时钟线)拉低,因为要将SDA线拉高(此时我们就是第九个脉冲),只有在SCL低电平时才能改变SDA的电平,然后再将SCL拉高,然后开始读取数据,

//模拟IIC从机的应答信号
static uint8_t IIC_Wait_Ack(void)
{
        uint8_t ack;
        SCL_Resebits();//拉低SCL
        delay_us(1);
        SDA_Bits(); //释放SDA  就是将SDA置1
        delay_us(1);
        SCL_Bits();//拉高SCL 读取SDA上面的数据
​
        if(SDA_Read() )  //读取SDA的输入信号
        {
        ack = 1;
        }
        else{
    
            ack = 0;
    
        }
        
        SCL_Resebits();  //拉低SCL 为下一个时钟周期或数据传输做准备
        delay_us(1);
        return ack;
​
}
终止信号

我们再根据协议写一个终止指令,告诉单机读取结束了,这个就比较简单了,

static void IIC_Stop()
{       
    
        SDA_Resebits();   //数据总线 低电平
        delay_us(1);
        SCL_Bits();  //时间总线 高电平
        delay_us(1);
        SDA_Bits();  //数据总线 高电平
     delay_us(1);
}

根据IIC协议,我们书写了一个驱动OLED的程序,供大家参考

OLED程序
#include "stm32f10x.h"
#include "OLED.h"
#include "Systick.h"
#include "picture.h"
​
​
​
//OLED端口初始化
static void OLED_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;    //定义一个GPIO_InitTypeDef类型的结构体
    
    RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);  //打开GPIOC的外设时钟
    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;  //选择控制的引脚
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;    //设置为通用开漏输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   //设置输出速率为50MHz
    GPIO_Init(GPIOB,&GPIO_InitStructure);   //调用库函数初始化GPIOA
    
    SCL_Bits(); //设B0(SCL)为高电平
    SDA_Bits(); //设PB1(SDA)为高电平
}
​
​
​
​
//IIC起始信号
static void IIC_Star()
{
        SCL_Bits();  //时间总线 高电平
        SDA_Bits();  //数据总线 高电平
        delay_us(1);
        SDA_Resebits();   //数据总线 低电平
        delay_us(1);
        SCL_Resebits();  //时间总线低电平 方便后面读取数据
     delay_us(1);
}
​
​
//IIC停止信号
static void IIC_Stop()
{       
    
        SDA_Resebits();   //数据总线 低电平
        delay_us(1);
        SCL_Bits();  //时间总线 高电平
        delay_us(1);
        SDA_Bits();  //数据总线 高电平
     delay_us(1);
}
​
​
//模拟IIC从机的应答信号
static uint8_t IIC_Wait_Ack(void)
{
        uint8_t ack;
        SCL_Resebits();//拉低SCL
        delay_us(1);
        SDA_Bits(); //释放SDA  就是将SDA置1
        delay_us(1);
        SCL_Bits();//拉高SCL 读取SDA上面的数据
​
        if(SDA_Read() )  //读取SDA的输入信号
        {
        ack = 1;
        }
        else{
    
            ack = 0;
    
        }
        
        SCL_Resebits();  //拉低SCL 为下一个时钟周期或数据传输做准备
        delay_us(1);
        return ack;
​
}
​
​
//写字节
static void Write_IIC_Byte(unsigned char IIC_Byte)
{
​
        unsigned char i;  //定义变量
    for(i=0;i<8;i++) //for循环8次
    {
        SCL_Resebits(); //时钟线置低,为传输数据做准备
         delay_us(1);
​
        if(IIC_Byte & 0x80) //读取最高位
            SDA_Bits();//最高位为1
        else
            SDA_Resebits(); //最高位为0
​
        IIC_Byte <<= 1;  //数据左移1位
         delay_us(1);
        SCL_Bits(); //时钟线置高,产生上升沿,把数据发送出去
         delay_us(1);
    }
    SCL_Resebits(); //时钟线置低
    delay_us(1);
​
    while(IIC_Wait_Ack());  //从机应答
​
}
​
​
​
​
//写命令
 static void Write_IIC_Command(unsigned char IIC_Command)
{
    
   IIC_Star();
   Write_IIC_Byte(0x78);//写入从机地址,SD0 = 0
   Write_IIC_Byte(0x00);//写入命令
   Write_IIC_Byte(IIC_Command);//数据
   IIC_Stop();  //发送停止信号
    
}
​
//写数据
static void Write_IIC_data(unsigned char IIC_Data)
{
   IIC_Star();
   Write_IIC_Byte(0x78);    //写入从机地址,SD0 = 0
   Write_IIC_Byte(0x40);    //写入数据
   Write_IIC_Byte(IIC_Data);//数据
   IIC_Stop();      //发送停止信号
}
​
​
//
void OLED_WR_Byte(unsigned char dat,unsigned char cmd)
{
    if(cmd) 
      {
       Write_IIC_data(dat); //写入数据
      }
    else {
       Write_IIC_Command(dat); //写入命令
    }
}
​
//设置起始位置
void OLED_Set_Pos(unsigned char x, unsigned char y) 
{
        OLED_WR_Byte(0xb0+y,OLED_CMD);
​
        OLED_WR_Byte((x & 0x0f) ,OLED_CMD);
​
        OLED_WR_Byte(((x & 0xf0) >>4 )| 0x10,OLED_CMD);
​
}
​
//开启
void OLED_Display_On(void)
{
    OLED_WR_Byte(0X8D,OLED_CMD);  //设置OLED电荷泵
    OLED_WR_Byte(0X14,OLED_CMD);  //使能,开
    OLED_WR_Byte(0XAF,OLED_CMD);  //开显示
}
​
//关闭
void OLED_Display_Off(void)
{
    OLED_WR_Byte(0XAE,OLED_CMD);  //关显示
    OLED_WR_Byte(0X8D,OLED_CMD);  //设置OLED电荷泵
    OLED_WR_Byte(0X10,OLED_CMD);  //失能,关
}                    
​
void OLED_Clear(void)  
{
        uint8_t i = 0,j = 0;
        for(j = 0;j<8;j++)
    {
            OLED_WR_Byte (0xb0+j,OLED_CMD);    //从0~7页依次写入
            OLED_WR_Byte (0x00,OLED_CMD);      //列低地址
            OLED_WR_Byte (0x10,OLED_CMD);      //列高地址  
            for(i = 0;i<128;i++)
            {
​
                OLED_WR_Byte(0,OLED_DATA);
                
            }
    }
​
}
​
​
​
void OLED_ShouChar(unsigned char x,unsigned char y,unsigned char chr,uint8_t size)
{
        
            uint8_t ch = 0,i = 0;
            ch  = chr - 32;
​
            if(x>128)
            {
            
            x = 0;
            y = y+2;
            }
            if(size == 16)
            {
                
                OLED_Set_Pos(x,y); //从x,y开始画点
                for(i=0;i<8;i++)
                OLED_WR_Byte(OLED_F8x16[ch][i],OLED_DATA);//找出字符c的数组位数,先把第一列画完
                OLED_Set_Pos(x,y+1); //从x,y开始画点
                for(i=0;i<8;i++)
                OLED_WR_Byte(OLED_F8x16[ch][i+8],OLED_DATA);//找出字符c的数组位数,画出第二列,我这个是二维数组
​
            }
        else{
​
            OLED_Set_Pos(x,y); //从x,y开始画点
            for(i=0;i<6;i++)
            OLED_WR_Byte(OLED_F6x8[ch][i],OLED_DATA);//找出字符c的数组位数,先把第一列画完
​
​
        }
​
    
    
}
​
​
//显示字符串
void OLED_Show_String(uint8_t x,uint8_t y,uint8_t ch[],uint8_t s)
{
​
    uint8_t i = 0;
    
​
    while(ch[i]!='\0')
    {
    
    
    OLED_ShouChar(x,y,ch[i],s);
    i++;
    if(s == 16)
    {
        x = x+8;
    
    }
    else
    {
        x = x+6;
    
    }
        
    }
​
}
​
​
//计算平方的函数,为了提取出单个数
uint32_t oled_pow(uint8_t m,uint8_t n)
{
    unsigned int  result = 1;
    while(n--)
    {
    
    result = result* m;
    
    }
    
    return result;
​
}
​
​
void oled_Num(uint8_t x,uint8_t y,uint32_t i,uint8_t len,uint8_t size)
{
        
    uint8_t t = 0,temp = 0;
    
    for(i = 0;i < len;i++)
    {
    
            temp = oled_pow(i,len) ;
    
    }
​
​
​
​
}
​
//显示数字的
void OLED_ShowNum(unsigned char x,unsigned char y,unsigned int num,unsigned char len,unsigned char size)
{           
    unsigned char t,temp;  //定义变量
    uint8_t enshow = 0;
​
    for(t=0;t<len;t++)
    {
        temp=(num/oled_pow(10,len-1-t))%10;//取出输入数的每个位,由高到低
        if(temp == 0 && enshow == 0)
        {
        
                OLED_ShouChar(x,y,' ',size);
​
            continue;
        }
        
        OLED_ShouChar(x,y,temp+'0',size);
        enshow = 1;
        if(size == 16)
        {
                x = x+8;
        }
        else{
        
        x = x+6;
        }
​
    }
}
​
​
​
​
//显示中文
void OLED_ShowCHinese(unsigned char x,unsigned char y,unsigned char no)
{
​
    uint8_t i = 0;
    OLED_Set_Pos(x,y);
    for(i = 0;i < 16;i++)
    {
    OLED_WR_Byte(Hzk[no*2][i],OLED_DATA);
    
    }
    OLED_Set_Pos(x,y+1);
    for(i = 0;i < 16;i++)
    {
    OLED_WR_Byte(Hzk[no*2 +1][i],OLED_DATA);
    
    }   
    
}
​
//显示图片的函数,x0,y0是 左下角的 起始点坐标位置   x1,y1是右下角坐标结束位置
​
void OLED_ShowDraw(uint8_t x0,uint8_t y0,uint8_t x1,uint8_t y1,uint8_t BMP[])
{
        uint8_t x ,y ;
        unsigned int i = 0;
        if(y1%8 == 0)
        y= y1/8;
        else
        y= y1/8 + 1;
        
        for(y = y0;y<y1;y++)
        {       
                //因为x0 y0是左上角 起始位置坐标 
                OLED_Set_Pos(x0,y);
​
                    for(x = x0;x<x1;x++)
                {
​
                    Write_IIC_data(BMP[i++]);
                
                }
​
        }
    
​
}
​
void OLED_Init(void)
{
    OLED_GPIO_Init();   //GPIO口初始化
 
    delay_ms(200);  //延迟,由于单片机上电初始化比OLED快,所以必须加上延迟,等待OLED上复位完成
​
    OLED_WR_Byte(0xAE,OLED_CMD);    //关闭显示
​
    OLED_WR_Byte(0x00,OLED_CMD);    //设置低列地址
    OLED_WR_Byte(0x10,OLED_CMD);    //设置高列地址
    OLED_WR_Byte(0x40,OLED_CMD);    //设置起始行地址
    OLED_WR_Byte(0xB0,OLED_CMD);    //设置页地址
​
    OLED_WR_Byte(0x81,OLED_CMD);    // 对比度设置,可设置亮度
    OLED_WR_Byte(0xFF,OLED_CMD);    //  265  
​
    OLED_WR_Byte(0xA1,OLED_CMD);    //设置段(SEG)的起始映射地址;column的127地址是SEG0的地址
    OLED_WR_Byte(0xA6,OLED_CMD);    //正常显示;0xa7逆显示
​
    OLED_WR_Byte(0xA8,OLED_CMD);    //设置驱动路数(16~64)
    OLED_WR_Byte(0x3F,OLED_CMD);    //64duty
    
    OLED_WR_Byte(0xC8,OLED_CMD);    //重映射模式,COM[N-1]~COM0扫描
​
    OLED_WR_Byte(0xD3,OLED_CMD);    //设置显示偏移
    OLED_WR_Byte(0x00,OLED_CMD);    //无偏移
    
    OLED_WR_Byte(0xD5,OLED_CMD);    //设置震荡器分频
    OLED_WR_Byte(0x80,OLED_CMD);    //使用默认值
    
    OLED_WR_Byte(0xD9,OLED_CMD);    //设置 Pre-Charge Period
    OLED_WR_Byte(0xF1,OLED_CMD);    //使用官方推荐值
    
    OLED_WR_Byte(0xDA,OLED_CMD);    //设置 com pin configuartion
    OLED_WR_Byte(0x12,OLED_CMD);    //使用默认值
    
    OLED_WR_Byte(0xDB,OLED_CMD);    //设置 Vcomh,可调节亮度(默认)
    OLED_WR_Byte(0x40,OLED_CMD);    使用官方推荐值
    
    OLED_WR_Byte(0x8D,OLED_CMD);    //设置OLED电荷泵
    OLED_WR_Byte(0x14,OLED_CMD);    //开显示
    
    OLED_WR_Byte(0xAF,OLED_CMD);    //开启OLED面板显示
    OLED_Clear();        //清屏
    OLED_Set_Pos(0,0);   //设置数据写入的起始行、列
} 

.h文件

#ifndef __OLED_H
#define __OLED_H
​
​
​
#define   SCL_Bits()        GPIO_SetBits(GPIOB,GPIO_Pin_0)
#define   SCL_Resebits()  GPIO_ResetBits(GPIOB,GPIO_Pin_0)
#define   SDA_Bits()        GPIO_SetBits(GPIOB,GPIO_Pin_1)
#define   SDA_Resebits()  GPIO_ResetBits(GPIOB,GPIO_Pin_1)
#define   SDA_Read()            GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)//读取SDA的电平
​
​
#define IIC_ACK  0  //应答
#define IIC_NO_ACK  1  //不应答
#define  OLED_CMD 0 //写命令
#define  OLED_DATA  1 //写数据
#define Max_Column  128     //最大列数
​
​
​
​
#include "stm32f10x.h"
​
void OLED_Init(void);
​
static void OLED_GPIO_Init(void);
​
static void IIC_Star();  //开始信号
​
static void IIC_Stop();   //停止信号
​
static uint8_t IIC_Wait_Ack(void);//模拟IIC应答
​
static void Write_IIC_Byte(unsigned char IIC_Byte);  //写字节函数
​
static void Write_IIC_Command(unsigned char IIC_Command);//写命令
​
static void Write_IIC_data(unsigned char IIC_Data);//写数据
 
void OLED_WR_Byte(unsigned char dat,unsigned char cmd);  //写命令或者 数据
​
void OLED_Set_Pos(unsigned char x, unsigned char y);// 设置位置
​
void OLED_Display_On(void); //开启屏幕
​
void OLED_Display_Off(void);//关闭屏幕
​
void OLED_Clear(void);  //清屏函数
​
void OLED_ShouChar(unsigned char x,unsigned char y,unsigned char chr,uint8_t size);  //显示字符的函数
​
void OLED_Show_String(uint8_t x,uint8_t y,uint8_t ch[],uint8_t s);//显示字符串的函数
​
void OLED_ShowNum(unsigned char x,unsigned char y,unsigned int num,unsigned char len,unsigned char size);  //显示数字
​
void OLED_ShowCHinese(unsigned char x,unsigned char y,unsigned char no);  //显示中文
​
void OLED_ShowDraw(uint8_t x0,uint8_t y0,uint8_t x1,uint8_t y1,uint8_t BMP[]);
​
#endif

main

#include "stm32f10x.h"
#include "main.h"
#include "LED.h"
#include "Bear.h"
#include "key.h"
#include "Realy.h"
#include "usart.h"
#include "shake.h"
#include "exti.h"
#include "time.h"
#include "pwm.h"
#include "HC-SR04.h"
#include "Systick.h"
#include "OLED.h"
#include "DHT11.h"
​
extern const unsigned char gImage_oled[];
​
 uint8_t GARM[1024];
​
int  main()
{
    OLED_Init();
    OLED_Clear();
    
    uint16_t a ;
    
    for(a= 0;a<1023;a++)
    {
    GARM[a] = 0xff;
        
    OLED_WR_Byte(GARM[a],OLED_DATA);
    //OLED_Set_Pos(0,1);
    
    }
    while(1)
    {
    
    }
} 
​
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值