IIC

一,概述

 

 

 

二,信号概念

                      

 

三,24C02

1.地址

              

2,写数据是时序图

 

 

3,读数据时时序图

 

 

#include <stdio.h>
#include "stm32f4xx.h"
#include "sys.h"
#include "string.h"

static GPIO_InitTypeDef   GPIO_InitStructure;

static NVIC_InitTypeDef   NVIC_InitStructure;

static USART_InitTypeDef  USART_InitStructure;



#define SCL            PBout(8)
#define SDA_W        PBout(9)
#define SDA_R        PBin(9)


//重定义fputc函数 
int fputc(int ch, FILE *f)
{     
    USART_SendData(USART1,ch);
    while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);  
    
    return ch;
}   

void delay_us(uint32_t nus)
{        
    uint32_t temp;             
    SysTick->LOAD =SystemCoreClock/8/1000000*nus;     //时间加载               
    SysTick->VAL  =0x00;                            //清空计数器
    SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;         //使能滴答定时器开始倒数      
    do
    {
        temp=SysTick->CTRL;
    }while((temp&0x01)&&!(temp&(1<<16)));            //等待时间到达   
    SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;         //关闭计数器
    SysTick->VAL =0X00;                               //清空计数器 
}

void delay_ms(uint16_t nms)
{                     
    uint32_t temp;           
    SysTick->LOAD=SystemCoreClock/8/1000*nms;        //时间加载(SysTick->LOAD为24bit)
    SysTick->VAL =0x00;                               //清空计数器
    SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;        //能滴答定时器开始倒数 
    do
    {
        temp=SysTick->CTRL;
    }while((temp&0x01)&&!(temp&(1<<16)));            //等待时间到达   
    SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;        //关闭计数器
    SysTick->VAL =0X00;                               //清空计数器              
} 

void USART1_Init(uint32_t baud)
{
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);                             //使能GPIOA时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);                            //使能USART1时钟
 
    //串口1对应引脚复用映射
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);                         //GPIOA9复用为USART1
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);                         //GPIOA10复用为USART1
    
    //USART1端口配置
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;                         //GPIOA9与GPIOA10
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;                                    //复用功能
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;                                //速度50MHz
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;                                     //推挽复用输出
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;                                     //上拉
    GPIO_Init(GPIOA,&GPIO_InitStructure);                                             //初始化PA9,PA10

    //USART1 初始化设置
    USART_InitStructure.USART_BaudRate = baud;                                        //波特率设置
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;                        //字长为8位数据格式
    USART_InitStructure.USART_StopBits = USART_StopBits_1;                            //一个停止位
    USART_InitStructure.USART_Parity = USART_Parity_No;                                //无奇偶校验位
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;    //无硬件数据流控制
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;                    //收发模式
    USART_Init(USART1, &USART_InitStructure);                                         //初始化串口
    
    USART_Cmd(USART1, ENABLE);                                                      //使能串口1 
    
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);                                    //开启相关中断

    //Usart1 NVIC 配置
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;                                //串口1中断通道
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;                            //抢占优先级3
    NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;                                //子优先级3
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                                    //IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);                                                    //根据指定的参数初始化VIC寄存器
}


void i2c_init(void)
{

    //设置PB8与PB9引脚为输出模式
    /*!< Enable GPIO clocks ,使能GPIOB的硬件时钟*/
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
    
    
    /* 配置片选引脚PB8 PB9为输出模式*/
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_Init(GPIOB, &GPIO_InitStructure);    
    
    //设置SCL和SDA引脚为高电平
    SCL=1;
    SDA_W=1;
}

//用于方便切换sda引脚的输入和输出模式
void i2c_sda_mode(uint32_t mode)
{
    /* 配置片选引脚PB9为输入模式*/
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = mode;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_Init(GPIOB, &GPIO_InitStructure);    
}

void i2c_start(void)
{
    //sda引脚为输出模式
    i2c_sda_mode(GPIO_Mode_OUT);
    
    //SCL与SDA引脚默认是高电平
    SCL=1;
    SDA_W=1;
    
    //延时5us
    delay_us(5);
    
    
    //SDA输出低电平
    SDA_W=0;
    
    //延时5us
    delay_us(5);
    

    //SCL引脚输出低电平
    SCL=0;


}


void i2c_stop(void)
{
    //sda引脚为输出模式
    i2c_sda_mode(GPIO_Mode_OUT);
    
    //SCL与SDA引脚默认是低电平
    SCL=0;
    SDA_W=0;
    
    
    //延时5us
    delay_us(5);
    
    //SCL输出高电平
    SCL=1;
    
    
    //延时5us
    delay_us(5);

    //SDA输出高电平    
    SDA_W=1;
    
    //延时5us
    delay_us(5);    
}


void i2c_send_byte(uint8_t txd)
{
    uint32_t i=0;
    
    //sda引脚为输出模式
    i2c_sda_mode(GPIO_Mode_OUT);
    
    //保证SCL引脚为低电平,允许SDA引脚电平发生改变
    SCL=0;
    
    //延时5us
    delay_us(5);
    
    //发送txd这个参数,分8个bit进行发送,MSB格式
    
    for(i=0; i<8; i++)
    {
        //MSB,最高有效位发送数据
        if(txd & (1<<(7-i)))
            SDA_W =1;
        else
            SDA_W =0;
        
        //因为SDA线比SCL引脚电平提前变化,需要延时
        delay_us(5);
        
        //设置SCL高电平
        SCL=1;
        delay_us(5);
    
        //设置SCL低电平
        SCL=0;
        delay_us(5);
    
    }    
    
}
uint8_t i2c_recv_byte(void)
{
    uint32_t i=0;
    uint8_t  rxd=0;
    
    //保证SDA引脚为输出模式
    i2c_sda_mode(GPIO_Mode_IN);

    //保证SCL引脚开始的时候为低电平,允许数据的改变
    SCL =0;
    delay_us(5);
    
    //连续接收8个bit,采用最高有效位优先进行接收
    for(i=0; i<8; i++)
    {

        
        delay_us(5);
        
        //锁存数据
        SCL=1;
        delay_us(5);
        
        if(SDA_R)
            rxd|=1<<(7-i);        
        
        //允许改变数据
        SCL=0;
        delay_us(5);        
    
    }
    
    return rxd;

}


void i2c_ack(uint8_t ack)
{

    //保证SDA引脚为输出模式
    i2c_sda_mode(GPIO_Mode_OUT);

    //保证SCL引脚开始的时候为低电平,允许数据的改变
    SCL =0;
    delay_us(5);
    

    if(ack)
        SDA_W=1;
    else
        SDA_W=0;
    
    delay_us(5);
    
    //锁存数据,让从机进行识别
    SCL=1;
    delay_us(5);
    
    //允许改变数据,从机无视该数据
    SCL=0;
    delay_us(5);        

}

uint8_t i2c_wait_ack(void)
{
    uint8_t ack=0;


    //sda引脚为输入模式
    i2c_sda_mode(GPIO_Mode_IN);
    
    
    //SCL输出高电平
    SCL=1;
    
    
    //延时5us
    delay_us(5);
    
    //判断SDA引脚的电平,如果为1,就返回1,就是无应答;若为0,就返回0,意味着是有应答
    if(SDA_R)
    {
        
        //调用该函数,将SCL和SDA引脚恢复到原来高电平的状态
        i2c_stop();
        
        ack=1;
    
    }
    else    
        ack=0;
    
    //SCL输出低电平,保持占用i2c总线
    SCL=0;
    
    //延时5us
    delay_us(5);
    
    return ack;

}



void at24c02_write(uint8_t addr,uint8_t *pbuf,uint32_t len)
{
    uint8_t ack=0;
    
    //发送起始信号
    i2c_start();
    
    //发送寻址地址0xA0
    i2c_send_byte(0xA0);
    
    //等待应答
    ack=i2c_wait_ack();
    
    if(ack)
    {
    
        printf("24c02 ack device address fail\r\n");
        
        return ;
    }
    
    printf("24c02 is online\r\n");
    
    
    
    //发送数据存储地址
    i2c_send_byte(addr);
    
    //等待应答
    ack=i2c_wait_ack();
    
    if(ack)
    {
    
        printf("24c02 ack word address fail\r\n");
        
        return ;
    }
    
    printf("24c02 word address ok\r\n");
    
    //发送数据内容
    while(len--)
    {
        //发送数据内容
        i2c_send_byte(*pbuf++);
        
        //等待应答
        ack=i2c_wait_ack();
        
        if(ack)
        {
        
            printf("24c02 ack data fail\r\n");
            
            return ;
        }        
    
    
    }
    
    //发送停止信号
    i2c_stop();
}
void at24c02_read(uint8_t addr,uint8_t *pbuf,uint8_t len)
{
    uint8_t ack=0;
    
    //发送启动信号
    i2c_start();
    
    //发送寻址地址为0xA0,写访问操作
    i2c_send_byte(0xA0);
    
    //等待应答
    ack = i2c_wait_ack();
    
    if(ack)
    {
        printf("24c02 ack device address fail\r\n");
        
        return;
    
    }

    printf("24c02 is online\r\n");

    //发送数据存储地址
    i2c_send_byte(addr);
    
    //等待应答
    ack = i2c_wait_ack();
    
    if(ack)
    {
        printf("24c02 ack word address 1 fail\r\n");
        
        return;
    
    }    
    
    printf("24c02 word address ok\r\n");    
    
    //重新发送启动信号
    i2c_start();
    
    //发送寻址地址为0xA1,读访问操作
    i2c_send_byte(0xA1);
    
    //等待应答
    ack = i2c_wait_ack();
    
    if(ack)
    {
        printf("24c02 ack device address 2 fail\r\n");
        
        return;
    
    }
    
    len=len-1;
    
    while(len--)
    {
        //接收数据
        *pbuf++=i2c_recv_byte();
        
        //主动发送应答给从机
        i2c_ack(0);
    }
    
    //接收数据
    *pbuf=i2c_recv_byte();
    
    //主动发送无应答给从机
    i2c_ack(1);    
    
    //发送停止信号,整个通信过程结束
    i2c_stop();
    
    printf("24c02 read ok\r\n");

}

int main(void)
{
    uint16_t w25qxx_id=0;
    uint8_t buf_wr[64]={0};
    uint8_t buf_rd[64]={0};    
    uint32_t i=0;

    //系统定时器初始化,时钟源来自HCLK,且进行8分频,
    //系统定时器时钟频率=168MHz/8=21MHz
    SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); 
        
    //设置中断优先级分组2
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    
    //串口1,波特率115200bps,开启接收中断
    USART1_Init(115200);

    i2c_init();
    
    //写入数据
    memset(buf_wr,1,sizeof buf_wr);
    printf("at24c02_write all is 1\r\n");
    at24c02_write(0,buf_wr,8);
    
    //读写之间必须得加延时,如果写完之后立即读取就会产生读取失败
    delay_ms(500);
    
    //读取数据
    memset(buf_rd,0,sizeof buf_rd);
    
    
    printf("at24c02_read\r\n");
    at24c02_read(0,buf_rd,8);

    for(i=0; i<8; i++)
    {
        printf("%02X ",buf_rd[i]);
    
    }
    
    printf("\r\n");
    
    
    while(1)
    {
        

    }

}
main.c

 

思考题1:OLED屏的设备地址是多少?

回答:设备地址为0x78.

 

思考题2:在24c02控制时钟的时候,为什么要进行5us的延时,小于5us延时或许大于5us的延时是否可以,示例代码如下:

//设置SCL高电平
SCL=1;
delay_us(5);
    
//设置SCL低电平
SCL=0;
delay_us(5);

回答:大于5us是可以的,如果使用5ms也是可以的。但是延时是不能低于1.2us,详细描述如下图。

 

posted @ 2019-05-29 00:05  狂奔~  阅读(522)  评论(0编辑  收藏  举报