打印
[STM32F1]

【转】stm32 i2c通信 操作寄存器

[复制链接]
1312|5
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
联通移不动|  楼主 | 2016-12-4 00:41 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
I2C总线是由NXP(原PHILIPS)公司设计,有十分简洁的物理层定义,其特性如下:
  • 只要求两条总线线路:一条串行数据线SDA,一条串行时钟线SCL;
  • 每个连接到总线的器件都可以通过唯一的地址和一直存在的简单的主机/从机关系软件设定地址,主机可以作为主机发送器或主机接收器;
  • 它是一个真正的多主机总线,如果两个或更多主机同时初始化,数据传输可以通过冲突检测和仲裁防止数据被破坏;
  • 串行的8 位双向数据传输位速率在标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s;
  • 连接到相同总线的IC 数量只受到总线的最大电容400pF 限制。
其典型的接口连线如下:



I2C的协议很简单:

数据的有效性
  在传输数据的时候,SDA线必须在时钟的高电平周期保持稳定,SDA的高或低电平状态只有在SCL 线的时钟信号是低电平时才能改变 。

起始和停止条件
  SCL 线是高电平时,SDA 线从高电平向低电平切换,这个情况表示起始条件;
  SCL 线是高电平时,SDA 线由低电平向高电平切换,这个情况表示停止条件。

字节格式
  发送到SDA 线上的每个字节必须为8 位,每次传输可以发送的字节数量不受限制。每个字节后必须处理一个响应位。

应答响应   
       数据传输必须带响应,相关的响应时钟脉冲由主机产生。在响应的时钟脉冲期间发送器释放SDA 线(高)。   
       在响应的时钟脉冲期间,接收器必须将SDA 线拉低,使它在这个时钟脉冲的高电平期间保持稳定的低电平。
       也就是说主器件发送完一字节数据后要接收一个应答位(低电平),从器件接收完一个字节后要发送一个低电平。

寻址方式(7位地址方式)

  第一个字节的头7 位组成了从机地址,最低位(LSB)是第8 位,它决定了传输的  普通的和带重复开始条件的7位地址格式方向。第一个字节的最低位是
    “0”,表示主机会写信息到被选中的从机;
    “1”表示主机会向从机读信息。
当发送了一个地址后,系统中的每个器件都在起始条件后将头7 位与它自己的地址比较,如果一样,器件会判定它被主机寻址,至于是从机接收器还是从机发送器,都由R/W 位决定。

仲裁

    I2C是所主机总线,每个设备都可以成为主机,但任一时刻只能有一个主机。

    stm32至少有一个I2C接口,提供多主机功能,可以实现所有I2C总线的时序、协议、仲裁和定时功能,支持标准和快速传输两种模式,同时与SMBus 2.0兼容。


    本实验直接操作寄存器实现对I2C总线结构的EEPROM AT24c02的写入和读取。AT24c02相关操作详见 单片机读取EEPROM(AT24C02)

    库函数实现使用stm32的两个I2C模拟I2C设备间的数据收发,并通过串口查看数据交换情况。

直接操作寄存器

首先需要配置I2C接口的时钟,相关寄存器如下:

I2C_CR2寄存器低五位:
    FREQ[5:0]:I2C模块时钟频率 ,必须设置正确的输入时钟频率以产生正确的时序,允许的范围在2~36MHz之间:
    000000:禁用       000001:禁用       000010:2MHz       ...       100100:36MHz       大于100100:禁用。

用于设置I2C设备的输入时钟,本例使用的是PLCK1总线上的时钟所以为36Mhz;

时钟控制寄存器(I2C_CCR)低12位:
CCR[11:0]:快速/标准模式下的时钟控制分频系数(主模式),该分频系数用于设置主模式下的SCL时钟。
在I2C标准模式或SMBus模式下:
        Thigh = CCR ×TPCLK1
        Tlow = CCR ×TPCLK1

        时钟周期为 T = Thigh + Tlow;

例如:在标准模式下,FREQR = 36 即36Mhz,产生200kHz的SCL的频率

            时钟控制分频系数  = Freqr /2/f    f 为想得到的频率

配置好时钟,还需要配置本机地址,I2C支持7位地址和10位地址,这里用的是7位地址:
自身地址寄存器1(I2C_OAR1)[7:1]:接口地址,地址的7~1位。







沙发
联通移不动|  楼主 | 2016-12-4 00:42 | 只看该作者
其他相关操作参见代码,有详细注释:(system.h 和 stm32f10x_it.h 等相关代码参照 stm32 直接操作寄存器开发环境配置

User/main.c
[cpp] view plain copy


  • #include <stm32f10x_lib.h>      
  • #include "system.h"  
  • #include "usart.h"   
  • #include "i2c.h"  
  •   
  • #define LED1 PAout(4)  
  • #define LED2 PAout(5)  
  • #define LED3 PAout(6)  
  • #define LED4 PAout(7)  
  •   
  • void Gpio_Init(void);  
  •   
  • int main(void)  
  • {               
  •   
  •     Rcc_Init(9);                          //系统时钟设置  
  •   
  •     Usart1_Init(72,9600);  
  •   
  •     Nvic_Init(1,0,I2C1_EV_IRQChannel,4);      //设置抢占优先级为1,响应优先级为0,中断分组为4  
  •   
  •     Nvic_Init(0,0,I2C1_ER_IRQChannel,4);      //设置I2C错误中断抢占优先级为0  
  •   
  •     Gpio_Init();  
  •   
  •     I2c_Init(0x30);                           //设置I2C1地址为0x30                     
  •   
  •     I2c_Start();  
  •   
  •     while(1);         
  • }  
  •   
  •   
  • void Gpio_Init(void)  
  • {  
  •     RCC->APB2ENR |= 1<<2;          //使能PORTA时钟      
  •     RCC->APB2ENR |= 1<<3;          //使能PORTB时钟;      
  •   
  •   
  •     GPIOA->CRL &= 0x0000FFFF;        // PA0~3设置为浮空输入,PA4~7设置为推挽输出  
  •     GPIOA->CRL |= 0x33334444;   
  •   
  •   
  •     GPIOB->CRL &= 0x00FFFFFF;        //PB6 I2C1_SCL ,PB7  I2C1_SDL  
  •     GPIOB->CRL |= 0xFF000000;        //复用开漏输出  
  •       
  •     //USART1 串口I/O设置  
  •   
  •     GPIOA -> CRH &= 0xFFFFF00F;      //设置USART1 的Tx(PA.9)为第二功能推挽,50MHz;Rx(PA.10)为浮空输入  
  •     GPIOA -> CRH |= 0x000008B0;      
  •   
  • }  

User/stm32f10x_it.c
[cpp] view plain copy


  • #include "stm32f10x_it.h"  
  • #include "system.h"  
  • #include "stdio.h"  
  • #include "i2c.h"  
  •   
  • #define LED1 PAout(4)  
  • #define LED2 PAout(5)  
  • #define LED3 PAout(6)  
  • #define LED4 PAout(7)  
  •   
  • #define  ADDRS_R  0xA1    //读操作地址  
  • #define  ADDRS_W  0xA0    //写操作地址  
  •   
  • u8  go = 0;               //操作步骤标记  
  •   
  • void I2C1_EV_IRQHandler(void)     //I2C1 Event Interrupt   
  • {  
  •     u16 clear = 0;  
  •   
  •     if(I2C1 -> SR1 & 1<<0 )          //已发送起始条件,写数据寄存器的操作将清除该位  
  •     {  
  •         printf("\r\n I2C1 Start .. \r\n");  
  •   
  •         switch(go)  
  •         {  
  •             case 0:{   
  •                 I2c_Write(ADDRS_W);        //写入从机地址,写指令操作地址  
  •                 break;  
  •             }  
  •             case 1:{  
  •                 I2c_Write(ADDRS_W);        //写入从机地址,写指令操作地址  
  •                 break;  
  •             }  
  •             case 2:{  
  •                 I2c_Write(ADDRS_R);        //写入从机地址,读数据操作地址  
  •                 break;  
  •            }  
  •         }  
  •   
  •     }  
  •   
  •     if(I2C1 -> SR1 & 1<<1 )        //从机地址已发送  
  •     {  
  •         printf("\r\n I2C1 has send address .. \r\n");  
  •         clear = I2C1 -> SR2; //读取SR2可以清除该位中断  
  •   
  •         switch(go)  
  •         {  
  •             case 0:{   
  •                 I2c_Write(0x01);    //写入待写入的EEPROM单元地址  
  •                 break;  
  •             }  
  •   
  •             case 1:{  
  •                 I2c_Write(0x01);    //写入待写入的EEPROM单元地址  
  •                 break;  
  •             }  
  •             case 2:{  
  •                 delay(100000);  
  •                 printf("\r\n Read 0x%X from At24c02 ,Address 0x01 ..  \r\n",I2c_Read());  
  •                 I2c_Stop();  
  •                 break;  
  •            }  
  •         }  
  •   
  •     }  
  •   
  •     if(I2C1 -> SR1 & 1<<2 )        //字节发送结束  发送地址字节时,不触发此中断  
  •     {  
  •          
  •         //printf("\r\n I2C1 send byte success .. \r\n");  
  •         switch(go)  
  •         {  
  •             case 0:{   
  •                 I2c_Write(0x86);            //写入数据  
  •                 printf("\r\n Write 0x%X to At24c02 ,Address 0x01 ..  \r\n",0x86);            
  •                 //I2c_Stop();  
  •       
  •                 delay(10000);  
  •                 go = 1;  
  •                 I2c_Start();   
  •                 break;  
  •             }  
  •   
  •             case 1:{  
  •   
  •                 delay(10000);  
  •                 go = 2;  
  •                 I2c_Start();  
  •                 break;  
  •             }  
  •             case 2:{  
  •   
  •                 break;  
  •            }  
  •         }  
  •   
  •     }  
  •   
  •     delay(100000);  
  •     LED3 = 1;  
  •   
  •     //I2C1 -> CR2 &= ~(1<<9);          //事件中断关闭  
  • }  
  •   
  • void I2C1_ER_IRQHandler(void)       //I2C1 Error Interrupt   
  • {  
  •     delay(100000);  
  •     LED4 = 1;     
  •   
  •     if(I2C1->SR1 & 1<<10)          //应答失败  
  •     {  
  •         printf("\r\n ACK ERROR .. \r\n");  
  •   
  •         I2C1->SR1 &=~(1<<10);      //清除中断  
  •     }  
  •   
  •     if(I2C1->SR1 & 1<<14)          //超时  
  •     {  
  •         printf("\r\n Timeout .. \r\n");  
  •   
  •         I2C1->SR1 &=~(1<<14);      //清除中断  
  •     }  
  •   
  •     if(I2C1->SR1 & 1<<11)          //过载/欠载  
  •     {  
  •         printf("\r\n Overrun/Underrun .. \r\n");  
  •         I2C1->SR1 &=~(1<<11);      //清除中断  
  •     }  
  •   
  •     if(I2C1->SR1 & 1<<9)           //仲裁丢失  
  •     {  
  •         printf("\r\n Arbitration lost .. \r\n");  
  •         I2C1->SR1 &=~(1<<9);       //清除中断  
  •     }  
  •   
  •     if(I2C1->SR1 & 1<<8)           //总线出错  
  •     {  
  •         printf("\r\n Bus error .. \r\n");  
  •         I2C1->SR1 &=~(1<<8);       //清除中断  
  •     }  
  •   
  •   
  • }  




使用特权

评论回复
板凳
联通移不动|  楼主 | 2016-12-4 00:45 | 只看该作者
Library/src/i2c.c
[cpp] view plain copy


  • #include "i2c.h"   
  •   
  • void I2c_Init(u16 Addr )  
  • {  
  •   
  •     RCC -> APB1ENR |= 1<<21;           //打开I2C1时钟  
  •     //RCC -> APB1ENR |= 1<<22;         //打开I2C2时钟  
  •   
  •     RCC->APB1RSTR  |= 1<<21;           //复位I2C1  
  •     RCC->APB1RSTR  &= ~(1<<21);            //复位结束I2C1  
  •     //RCC->APB1RSTR  |= 1<<22;         //复位I2C2  
  •   
  •     //I2C1 -> CR1 |=  1<<15;               //复位寄存器  
  •   
  •     //I2C模块时钟频率,2~36MHz之间  
  •     I2C1 -> CR2 |=   36 ;                //000000:禁用 000001:禁用 000010:2MHz ... 100100:36MHz  
  •   
  •   
  •     I2C1 -> CCR |= 0<<15;              //I2C主模式  0:标准模式的I2C    1:快速模式的I2C  
  •     //I2C1 -> CCR |= 1<<14;                //快速模式时的占空比 0 Tlow/Thigh = 2    1   Tlow/Thigh = 16/9  
  •   
  •     //得到200kHz频率  
  •     I2C1 -> CCR |= 90<<0;              //时钟控制分频系数  = PCLK1 /2/f    f 为想得到的频率  
  •   
  •     //主模式最大上升时间  
  •     I2C1 -> TRISE |= 37;             //最大允许SCL上升时间为1000ns,故TRISE[5:0]中必须写入(1us/(1/36)us = 36+1)。  
  •   
  •     I2C1 -> CR1 |=  1<<10;             //打开ACK应答,在接收到一个字节后返回一个应答  
  •     I2C1 -> CR1 |= 1<<6;               //广播呼叫使能  
  •   
  •     I2C1 -> OAR1 |= 0<<15;             //寻址模式   1 响应10位地址  0  响应7位地址     
  •   
  •     I2C1 -> OAR1 |= 1<<14;             //必须始终由软件保持为 1  
  •   
  •     I2C1 -> OAR1 |=  Addr <<1 ;            //设置接口地址的 7~1位  
  •   
  •     //I2C1 -> OAR1 |=  0 ;           //设置10位地址模式时地址第0位   
  •     //I2C1 -> OAR1 |= 0<<8;                //设置10位地址模式时地址第9~8位  
  •   
  •     //I2C1 -> CR2 |=  1<<10;               //缓冲器中断使能  
  •     I2C1 -> CR2 |=  1<<9;              //事件中断使能  
  •     I2C1 -> CR2 |=  1<<8;              //出错中断使能  
  •   
  •     I2C1 -> CR1 |=   1<<0;             //开启I2C1  
  • }  
  •   
  •   
  • void  I2c_Start()  
  • {  
  •   
  •     I2C1 -> CR1 |=   1<<8;             //I2C1产生起始条件  
  • }  
  •   
  • void  I2c_Stop()  
  • {  
  •     I2C1 -> CR1 |=   1<<9;             //I2C1产生停止条件  
  • }  
  •   
  •   
  • void  I2c_Write(u8 data)  
  • {  
  •     I2C1 -> DR = data;  
  • }  
  •   
  • u8  I2c_Read()  
  • {  
  •     while(!(I2C1 -> SR1 & 1<<6));      //接收到数据标志位  
  •   
  •     return I2C1 -> DR;  
  • }  
  •   
  • void  I2c_End()                         //关闭I2C  
  • {  
  •     I2C1 -> CR1 &=   ~(1<<0);        
  • }  

Library/inc/i2c.h
[cpp] view plain copy


  • #include <stm32f10x_lib.h>  
  •   
  • void I2c_Init(u16 Addr );     
  •   
  • void  I2c_Start(void);  
  • void  I2c_Stop(void);  
  • void  I2c_Write(u8 data);  
  • u8    I2c_Read(void);  
  • void  I2c_End(void);  




使用特权

评论回复
地板
mintspring| | 2016-12-4 11:48 | 只看该作者
我来贴个
#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "lcd.h"
#include "usart.h"
#include "usmart.h"         
#include "24cxx.h"         


                                        
//要写入到24c02的字符串数组
const u8 TEXT_Buffer[]={"WarShipSTM32 IIC TEST"};
#define SIZE sizeof(TEXT_Buffer)
int main(void)
{         
        u8 key;
        u16 i=0;
        u8 datatemp[SIZE];
        delay_init();                     //延时函数初始化          
        NVIC_Configuration();          //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
        uart_init(9600);                 //串口初始化为9600
        KEY_Init();
        LED_Init();                             //LED端口初始化
        LCD_Init();       
        usmart_dev.init(72);        //初始化USMART                                
        AT24CXX_Init();                        //IIC初始化

        POINT_COLOR=RED;//设置字体为红色
        LCD_ShowString(60,50,200,16,16,"WarShip STM32");       
        LCD_ShowString(60,70,200,16,16,"IIC TEST");       
        LCD_ShowString(60,90,200,16,16,"ATOM@ALIENTEK");
        LCD_ShowString(60,110,200,16,16,"2012/9/9");       
        LCD_ShowString(60,130,200,16,16,"WK_UP:Write  KEY1:Read");        //显示提示信息                       
        while(AT24CXX_Check())//检测不到24c02
        {
                LCD_ShowString(60,150,200,16,16,"24C02 Check Failed!");
                delay_ms(500);
                LCD_ShowString(60,150,200,16,16,"Please Check!      ");
                delay_ms(500);
                LED0=!LED0;//DS0闪烁
        }
        LCD_ShowString(60,150,200,16,16,"24C02 Ready!");
                                                                          
        POINT_COLOR=BLUE;//设置字体为蓝色          
        while(1)
        {
                key=KEY_Scan(0);
                if(key==KEY_UP)//KEY_UP按下,写入24C02
                {
                        LCD_Fill(0,170,239,319,WHITE);//清除半屏   
                        LCD_ShowString(60,170,200,16,16,"Start Write 24C02....");
                        AT24CXX_Write(0,(u8*)TEXT_Buffer,SIZE);
                        LCD_ShowString(60,170,200,16,16,"24C02 Write Finished!");//提示传送完成
                }
                if(key==KEY_DOWN)//KEY_DOWN按下,读取字符串并显示
                {
                        LCD_ShowString(60,170,200,16,16,"Start Read 24C02.... ");
                        AT24CXX_Read(0,datatemp,SIZE);
                        LCD_ShowString(60,170,200,16,16,"The Data Readed Is:  ");//提示传送完成
                        LCD_ShowString(60,190,200,16,16,datatemp);//显示读到的字符串
                }
                i++;
                delay_ms(10);
                if(i==20)
                {
                        LED0=!LED0;//提示系统正在运行       
                        i=0;
                }                  
        }
}


使用特权

评论回复
5
mintspring| | 2016-12-4 11:50 | 只看该作者
IIC(Inter-Integrated Circuit)总线是一种由 PHILIPS 公司开发的两线式串行总线,用于连接
微控制器及其外围设备。它是由数据线 SDA 和时钟 SCL 构成的串行总线,可发送和接收数据。
在 CPU 与被控 IC 之间、IC 与 IC 之间进行双向传送,高速 IIC 总线一般可达 400kbps 以上。
I2C 总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号、结束信号和应答
信号。
开始信号:SCL 为高电平时,SDA 由高电平向低电平跳变,开始传送数据。
结束信号:SCL 为高电平时,SDA 由低电平向高电平跳变,结束传送数据。
应答信号:接收数据的 IC 在接收到 8bit 数据后,向发送数据的 IC 发出特定的低电平脉冲,
表示已收到数据。CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU 接
收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为
受控单元出现故障。
这些信号中,起始信号是必需的,结束信号和应答信号,都可以不要。

使用特权

评论回复
6
豆腐111| | 2016-12-4 22:02 | 只看该作者
厉害

使用特权

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

本版积分规则

67

主题

127

帖子

0

粉丝