• 数据的有效性

STM32中I2C通信协议_STM32

  • 起始和停止条件

STM32中I2C通信协议_STM32_02


  • 应答信号

STM32中I2C通信协议_时序图_03

页编程

STM32中I2C通信协议_时序图_04


连续读写时序图

STM32中I2C通信协议_STM32_05

AT24C02读写数据代码
#include "stm32f4xx.h"                  // Device header
#include "stdio.h"
#include "string.h" 
//注意事项:设置成全局变量,不然局部变量影响while(1)循环输出    (只能按复位键,按一下输出一下)

static    GPIO_InitTypeDef      GPIO_InitStructure;

void MY_AT24C02_Init(void);
void MY_SDA_MODE( GPIOMode_TypeDef gpio_Mode);
void IIC_START(void);
int8_t IIC_WAIT_ACK(void);
void IIC_Master_ACK(uint8_t ack);
void IIC_STOP(void);
void IIC_SEND_Byte(uint8_t data);
int8_t AT24C02_WRITE(uint8_t addr,uint8_t *pbuf,uint32_t len);
int32_t AT24C02_READ(uint8_t addr,uint8_t *pbuf,uint32_t len);
void MY_USART1_Init(uint32_t baud);

void delay_us(uint32_t n);
void delay_ms(uint32_t ms);

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

#define PAin(x) *((volatile uint32_t*)(0x42000000+(GPIOA_BASE+0x10-0x40000000)*32+(x)*4))
#define PBin(x) *((volatile uint32_t*)(0x42000000+(GPIOB_BASE+0x10-0x40000000)*32+(x)*4))
#define PFout(x) *((volatile uint32_t*)(0x42000000+(GPIOF_BASE+0x14-0x40000000)*32+(x)*4))
#define PBout(x) *((volatile uint32_t*)(0x42000000+(GPIOB_BASE+0x14-0x40000000)*32+(x)*4))
    
//输出重定向
#pragma import(__use_no_semihosting_swi)//这个声明,使程序遇到文件操作函数不会停在中断处
struct __FILE { int handle; /* Add whatever you need here */ };//标准库需要的支持函数
FILE __stdout;
FILE __stdin;
//重定义fputc函数
int fputc(int ch, FILE *f) 
{
     while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);//检测上一次是否发送完毕
     USART_SendData(USART1,ch);//用哪一个串口打印,直接更改USARTx
     return (ch);
}
//避免使用半主机模式
void _sys_exit(int return_code) {
label: goto label; /* endless loop */
}

int main(void)
{
    int32_t rt=0,i;
    uint8_t buf[8]={0};
    uint8_t buf1[8]={0};
    MY_USART1_Init(9600);
    MY_AT24C02_Init();
    memset(buf,0x67,8);
    rt=AT24C02_WRITE(0,buf,8);
    printf("rt=%d\n",rt);
    for(i=0;i<8;i++)
    {
           printf("buf[%d]=%x\t",i,buf[i]);
        
    }
        printf("\r\n");
    printf("写入数据成功\r\n");
    rt= AT24C02_READ(0,buf1,8);
    if(rt==0)
    {
        for(i=0;i<8;i++)
        {
           printf("buf1[%d]=%x\t",i,buf1[i]);
        
        }
        printf("\r\n");
    
    }
    //主程序的循环
    while(1)
    {
       
        
         
    }


}

void MY_AT24C02_Init(void)
{

    
     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_High_Speed;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOB,&GPIO_InitStructure);
    //初始状态都是拉高
    SCL_W=1;
    SDA_W=1;

}
void MY_SDA_MODE( GPIOMode_TypeDef gpio_Mode)
{
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode =  gpio_Mode;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_Speed    = GPIO_High_Speed;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOB,&GPIO_InitStructure);
}
//启动信号
void IIC_START(void)
{
    //SDA引脚为输出模式
    MY_SDA_MODE(GPIO_Mode_OUT);
    SCL_W=1;
    SDA_W=1;
    delay_us(5);
    SDA_W=0;
    delay_us(5);
    //为了保持占有时钟线,把高电平拉低(钳住总线)
    SCL_W=0;
    delay_us(5);
}
//停止信号
void IIC_STOP(void)
{
    //SDA引脚为输出模式
    MY_SDA_MODE(GPIO_Mode_OUT);
    SCL_W=1;
    SDA_W=0;
    delay_us(5);
    SDA_W=1;
    delay_us(5);

}
//从机应答信号
int8_t IIC_WAIT_ACK(void)
{
    int8_t ack;  
    
    //SDA引脚为输入模式
    MY_SDA_MODE(GPIO_Mode_IN);
    //在高电平的时候获取SDA数据
    SCL_W=1;
    delay_us(5);
    //检测从机应答信号的高低电平
    if(SDA_R)
      ack=1;   //无应答
    else
      ack=0;  //有应答
    SCL_W=0;
    delay_us(5);
    return ack;
}

void IIC_SEND_Byte(uint8_t data)
{
    int8_t i=0;
    //SDA引脚为输出模式
    MY_SDA_MODE(GPIO_Mode_OUT);
    SCL_W=0;
    SDA_W=0;
    delay_us(5);
    for(i=7;i>=0;i--)
    {
        if(data&(1<<i))
         SDA_W=1;
        else
         SDA_W=0;
        delay_us(5);
          //在时钟线为高电平的时候,SDA获取数据
        SCL_W=1;
        delay_us(5);
        //在时钟线为低电平的时候,SDA切换电平
        SCL_W=0;
        delay_us(5);
    }
    
}


int8_t AT24C02_WRITE(uint8_t addr,uint8_t *pbuf,uint32_t len)
{
    int8_t ack=0;
    uint8_t *p=pbuf;
    //发送启动信号
    IIC_START();
    //设备开始寻址
    IIC_SEND_Byte(0xA0);//写地址
    //检测从机是否应答
    ack=IIC_WAIT_ACK();
    printf("ack=%d",ack);
    if(ack)
    {
        printf("device address error\n");
        return -1;
    }
    printf("device address success\n");
    //发送你要写入数据的地址
    IIC_SEND_Byte(addr);
    //检测从机是否应答
    ack=IIC_WAIT_ACK();
    if(ack)
    {
        printf("word address error\n");
        return -2;
    }
    printf("word address success\n");
    //连续写入数据
    while(len--)
    {
       IIC_SEND_Byte(*p++);
       ack=IIC_WAIT_ACK();
        if(ack)
        {
            printf("write error\n");
            return -3;
        }
    }
    //停止信号
    IIC_STOP();
    return 0;
    
}




//主机应答
void IIC_Master_ACK(uint8_t ack) //由主机读数据,主机应答(发数据0,1)  
{

    //SDA引脚为输出模式
    MY_SDA_MODE(GPIO_Mode_OUT);
    
    SCL_W=0;
    SDA_W=0;
    delay_us(5);
    if(ack)
        SDA_W=1;
    else
        SDA_W=0;
    delay_us(5);
    
    SCL_W=1;        //时钟线在高电平的时候,SDA获取数据
    delay_us(5);
    
    SCL_W=0;        //时钟线在低电平的时候,SDA切换电平
    delay_us(5);
    
    
}
//接收数据
uint8_t I2c_Recv_Byte(void)
{
    int32_t i=0;
    uint8_t    data=0;
    //SDA引脚为输入模式
    MY_SDA_MODE(GPIO_Mode_IN);

    for(i=7;i>=0;i--)
    {
        SCL_W=1;        //时钟线在高电平的时候,SDA获取数据
        delay_us(5);
        //检测从机应答
        if(SDA_R)
            data|=1<<i;
        
        SCL_W=0;         //时钟线在低电平的时候,SDA切换电平
        delay_us(5);   
    }
    return data;
}
//读数据
int32_t AT24C02_READ(uint8_t addr,uint8_t *pbuf,uint32_t len)
{
    
    uint8_t ack=0;
    uint8_t *p=pbuf;
    //发送起始信号
    IIC_START();
    //设备寻址
    IIC_SEND_Byte(0xA0);//写地址
    //检测从机是否应答
     ack=IIC_WAIT_ACK();
    if(ack)
    {
        printf("device address error\r\n");
        return -1;
    }
    //发送要读取数据的地址
    IIC_SEND_Byte(addr);
    
    ack=IIC_WAIT_ACK();
    if(ack)
    {
        printf("word address error\r\n");
        return -2;
    }
    //发送起始信号
    IIC_START();
    //设备寻址
    IIC_SEND_Byte(0xA1);//读地址
    ack=IIC_WAIT_ACK();
     if(ack)
    {
        printf("advice address error\r\n");
        return -3;
    }
    len=len-1;
    while(len--)
    {
        *p++=I2c_Recv_Byte();
        //printf("rcv:%x",*(p-1));
        IIC_Master_ACK(0);
    }
    //获取最后一个数据
    *p=I2c_Recv_Byte();
    IIC_Master_ACK(1);
    //停止信号
    IIC_STOP();
    return 0;
    
}

void MY_USART1_Init(uint32_t baud)
{
        
        GPIO_InitTypeDef GPIO_InitStructure;
        USART_InitTypeDef USART_InitStructure;
        NVIC_InitTypeDef NVIC_InitStructure;
    
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
        //1.使能时钟
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
        
        //2.将PA9,PA10引脚复用映射为串口1
         GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);//USART1_TX
         GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);//USART1_RX
        //3.配置引脚
        GPIO_InitStructure.GPIO_Pin= GPIO_Pin_9|GPIO_Pin_10;
        GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF;
        GPIO_InitStructure.GPIO_Speed= GPIO_High_Speed;
        GPIO_InitStructure.GPIO_OType= GPIO_OType_PP;
        GPIO_InitStructure.GPIO_PuPd= GPIO_PuPd_NOPULL;
        GPIO_Init(GPIOA, &GPIO_InitStructure);
    
        //4.串口初始化
        USART_InitStructure.USART_BaudRate=baud;//波特率
        USART_InitStructure.USART_WordLength=USART_WordLength_8b;//8位数据位
        USART_InitStructure.USART_StopBits=USART_StopBits_1;//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);
        //5、使能串口1的接收中断--触发方式
        USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
        
        //6、配置抢占优先级和响应优先级,打开中断请求通道给NVIC
        NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;
        NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
        NVIC_Init(&NVIC_InitStructure);
        
        //7、使能串口
        USART_Cmd(USART1,ENABLE);
}
void delay_us(uint32_t n)
{
    
        SysTick->CTRL = 0; //关闭系统定时器才能进行配置
        //时钟源21MHz
        //实现21000 000次计数就是1s的时间到达
        //实现21次计数就是1us的时间到达
        SysTick->LOAD = (21*n)-1; // 1us的定时
        SysTick->VAL = 0; // 清空当前计数值也会清空count_flag(计数标志位)
        SysTick->CTRL = 1; //使能系统定时器工作,并且时钟源选择21MHz的参考时钟001
        while ((SysTick->CTRL & 0x00010000)==0);//等待count flag标志位为1
        SysTick->CTRL = 0; //关闭系统定时器,不再进行定时

} 
void delay_ms(uint32_t ms)
{
    #if 0
    while(ms --)
    {
        SysTick->CTRL = 0;             // 关闭系统定时器后才能配置寄存器
        SysTick->LOAD = 21000-1;             // 设置计数值,用于设置定时的时间
        SysTick->VAL = 0;             // 清空当前值还有计数标志位
        SysTick->CTRL = 1;             // 使能系统定时器工作,且时钟源为系统时钟的8分频(168MHz/8=21MHz)
        while ((SysTick->CTRL & (1<<16))==0);    // 等待系统定时器计数完毕
        SysTick->CTRL = 0;             // 关闭系统定时器    
    }
    #else 
    while(ms --)
    {
        SysTick->CTRL = 0;             // 关闭系统定时器后才能配置寄存器
        SysTick->LOAD = 168000-1;             // 设置计数值,用于设置定时的时间
        SysTick->VAL = 0;             // 清空当前值还有计数标志位
        SysTick->CTRL = 5;             // 使能系统定时器工作,且时钟源为系统时钟168MHz
        while ((SysTick->CTRL & (1<<16))==0);    // 等待系统定时器计数完毕
        SysTick->CTRL = 0;             // 关闭系统定时器    
    }
    #endif
}