MM32SPIN2x 电机专用MCU功能特色 —— I2C功能

demi的头像
demi 发布于:周一, 10/28/2019 - 10:02 ,关键词:

本章节将与大家一起配置I2C接口来控制一个OLED模块。

I2C介绍

IIC 即Inter-Integrated Circuit(集成电路总线),是由飞利浦半导体公司在八十年代初设计出来的,主要是用来连接整体电路(ICS) ,IIC是一种多向控制总线,也就是说多个芯片可以连接到同一总线结构下,同时每个芯片都可以作为实时数据传输的控制源。这种方式简化了信号传输总线接口。


图1 I2C主设备与从设备

I2C总线采用一条数据线(SDA),加一条时钟线(SCL)来完成数据的传输及外围器件的扩展。每个器件都有一个唯一的地址识别,而且都可以作为一个发送或接收器。除了发送器和接收器外,器件在执行数据传输时也可以被看做是主机或者从机。主机是初始化总线的数据传输并产生允许传输的时钟信号的器件。此时,任何被寻址的器件都被认为是从机。

在SPIN27上的I2C拥有如下特性:

• 半双工同步操作
• 支持主从模式
• 支持 7 位地址和 10位地址
• 支持标准模式 100Kbps,快速模式 400Kbps
• 产生 Start、 Stop、重新发 Start、应答 Acknowledge信号检测
• 在主模式下只支持一个主机
• 分别有 2字节的发送和接收缓冲
• 在 SCL和 SDA上增加了无毛刺电路
• 支持 DMA 操作
• 支持中断和查询操作

I2C协议

在I2C中,当总线处于空闲状态时,SCL和SDA同时被外部上拉电阻拉为高电平。在SCL线是高电平时,SDA线从高电平向低电平切换表示起始条件。当主机结束传输时要发送停止条件。在SCL线是高电平,SDA线由低电平向高电平切换表示停止条件。下图显示了起始和停止条件的时序图。


图2 起始和停止条件

I2C总线的数据都是以字节(8位)的方式传送的,每个数据字节在传送时都是高位(MSB)在前。数据传输过程中,当SCL为1时,SDA必须保持稳定。发送器件每发送一个字节之后,在时钟的第9个脉冲期间释放数据总线,由接收器发送一个ACK(把数据总线的电平拉低)来表示数据成功接收;接收器不拉低数据总线表示一个NACK,NACK有两种用途:在主机发送从机接收时表示未成功接收数据字节,在从机发送主机接收时表示传送数据结束。

下面是主机对从机的读写过程:

写通讯过程:

1、主机在检测到总线空闲的状况下,首先发送一个START信号掌管总线;
2、发送一个地址字节(包括7位地址码和一位R/W);
3、当从机检测到主机发送的地址与自己的地址相同时发送一个应答信号(ACK);
4、主机收到ACK后开始发送第一个数据字节;
5、从机收到数据字节后发送一个ACK表示继续传送数据,发送NACK表示传送数据结束
6、主机发送完全部数据后,发送一个停止位STOP,结束整个通讯并且释放总线;

读通讯过程:

1、主机在检测到总线空闲的状况下,首先发送一个START信号掌管总线;
2、发送一个地址字节(包括7位地址码和一位R/W);
3、当从机检测到主控发送的地址与自己的地址相同时发送一个应答信号(ACK);
4、主机收到ACK后释放数据总线,开始接收第一个数据字节;
5、主机收到数据后发送ACK表示继续传送数据,发送NACK表示传送数据结束;
6、主机发送完全部数据后,发送一个停止位STOP,结束整个通讯并且释放总线;

I2C有两种地址格式: 7位的地址格式和 10位的地址格式。

7位的地址格式:

在起始条件(S)后发送的一个字节的前7位(bit 7:1)为从机地址,最低位(bit 0)是数据方向位,当bit 0为0,表示主机写数据到从机,1表示主机从从机读数据。


图3 7位的地址格式

10 位的地址格式:

在 10 位的地址格式中,发送2个字节来传输 10 位地址。发送的第一个字节的位的描述如下:第一个5位(bit 7:3)用于告示从机接下来是10位的传输。第一个字节的后两个字节(bit 2:1)位从机地址的bit 9:8,最低位(bit 0)是数据方向位(R/W)。传输的第二个字节为10位地址的低八位。


图4 10位的地址格式

下面我们一起来配置MM32SPIN27的I2C模块进行OLED屏的显示功能:

I2C配置:

void I2CInitMasterMode()

{

I2C_InitTypeDef I2C_InitStructure;

GPIO_InitTypeDef  GPIO_InitStructure;

NVIC_InitTypeDef NVIC_InitStructure;

 

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB,ENABLE); //开启GPIO时钟

RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE); //开启I2C1时钟

 

GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_8 | GPIO_Pin_9;  //I2C1重映射IO口

GPIO_InitStructure.GPIO_Speed =GPIO_Speed_2MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//保持总线空闲即CLK&DATA为高

GPIO_Init(GPIOB, &GPIO_InitStructure);   

 

I2C_InitStructure.I2C_Mode = I2C_Mode_MASTER; //主模式

I2C_InitStructure.I2C_OwnAddress =FLASH_DEVICE_ADDR; //从机地址

I2C_InitStructure.I2C_Speed =I2C_Speed_STANDARD;  //标准速率

I2C_InitStructure.I2C_ClockSpeed = 100000; //100K

I2C_Init(I2C1, &I2C_InitStructure);

 

NVIC_InitStructure.NVIC_IRQChannel = I2C1_IRQn;//I2C中断设置

NVIC_InitStructure.NVIC_IRQChannelPriority = 1;

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&NVIC_InitStructure);

 

I2C_ClearITPendingBit(I2C1, I2C_IT_RX_FULL |I2C_IT_TX_EMPTY);//请中断标志位

I2C_ITConfig(I2C1,I2C_IT_RX_FULL|I2C_IT_TX_EMPTY, ENABLE);//开启I2C中断

 

I2C_Cmd(I2C1, ENABLE);    //使能I2C

 

GPIO_InitStructure.GPIO_Speed =GPIO_Speed_2MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;  //需要外加上拉

GPIO_Init(GPIOB, &GPIO_InitStructure);

 

GPIO_PinAFConfig(GPIOB,GPIO_PinSource8,GPIO_AF_1);//设置GPIO的复用功能

GPIO_PinAFConfig(GPIOB,GPIO_PinSource9,GPIO_AF_1);

}

目标地址设置函数:

void I2CSetDeviceAddr(unsigned chardeviceaddr) 

{

GPIO_InitTypeDef  GPIO_InitStructure;

   

GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_8 | GPIO_Pin_9;  //I2C1重映射IO口

GPIO_InitStructure.GPIO_Speed =GPIO_Speed_2MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//保持总线空闲即CLK&DATA为高

GPIO_Init(GPIOB, &GPIO_InitStructure);   

   

I2C_Cmd(I2C1,DISABLE);

I2C_Send7bitAddress(I2C1, deviceaddr ,I2C_Direction_Transmitter);//设置从机地址

I2C_Cmd(I2C1, ENABLE);

   

GPIO_InitStructure.GPIO_Speed =GPIO_Speed_2MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; 

GPIO_Init(GPIOB, &GPIO_InitStructure);

}

发送与接收检查:

void I2CTXEmptyCheck(I2C_TypeDef *I2Cx)

{

while(1)

if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_TX_EMPTY))

break;

}

void I2CRXFullCheck(I2C_TypeDef *I2Cx)

{

while(1)

if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_RX_FULL))

break;

}

库函数中还有一些常用的操作函数,如发送函数I2C_SendData()和停止函数I2C_GenerateSTOP()等,下面我们连接上一个I2C从机,使用上面几个函数发送几个数据,从逻辑分析仪,可以得到下图所示波形


图5 数据发送

可以看到,图中,从机的地址为0x78(包含读/写位),发送的几个数据也得到了ACK回应。

I2C的简单配置就完成了。在实际应用中我们需要根据不同器件,对数据进行处理,以实现所需的功能。

下面,我们以市面上常见的基于SSD1303的OLED显示模块为例,介绍一个SPIN27的I2C接口应用。

OLED应用

SSD1303是一个带有单芯片的单色OLED显示模块,支持最大显示分辨率132x64。

模块内嵌了对比度控制、显存和振荡器,减少了外部组件的数量和功耗。它适用于许多小型便携式应用,例如手机子显示屏、计算器、MP3播放器等显示类应用。

I2C接口应用代码:

void WriteCmdStart()

{

I2C_SendData(I2C1,0x80);//寄存器地址

I2CTXEmptyCheck(I2C1);

}

 

void WriteDataStart()

{

I2C_SendData(I2C1,0x40);//寄存器地址

I2CTXEmptyCheck(I2C1);

}

 

void WriteEnd()

{

delay_us(10);

I2C_GenerateSTOP( I2C1, ENABLE );

}

 

void WriteCmd(u8 command)

{

I2C_SendData(I2C1,command);

I2CTXEmptyCheck(I2C1);

}

 

void WriteData(u8 data)

{

I2C_SendData(I2C1,data);

I2CTXEmptyCheck(I2C1);

}

 

void OLED_Init(void)

{

delay_ms(100); //延时

 

WriteCmdStart();

WriteCmd(0xAE); //display off

WriteCmd(0x20); //Set Memory AddressingMode   

WriteCmd(0x10); //00,Horizontal AddressingMode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid

WriteCmd(0xb0); //Set Page Start Address forPage Addressing Mode,0-7

WriteCmd(0xc8); //Set COM Output Scan Direction

WriteCmd(0x00); //---set low column address

WriteCmd(0x10); //---set high column address

WriteCmd(0x40); //--set start line address

WriteCmd(0x81); //--set contrast controlregister

WriteCmd(0xff); //亮度调节 0x00~0xff

WriteCmd(0xa1); //--set segment re-map 0 to 127

WriteCmd(0xa6); //--set normal display

WriteCmd(0xa8); //--set multiplex ratio(1 to64)

WriteCmd(0x3F); //

WriteCmd(0xa4); //0xa4,Output follows RAMcontent;0xa5,Output ignores RAM content

WriteCmd(0xd3); //-set display offset

WriteCmd(0x00); //-not offset

WriteCmd(0xd5); //--set display clock divideratio/oscillator frequency

WriteCmd(0xf0); //--set divide ratio

WriteCmd(0xd9); //--set pre-charge period

WriteCmd(0x22); //

WriteCmd(0xda); //--set com pins hardwareconfiguration

WriteCmd(0x12);

WriteCmd(0xdb); //--set vcomh

WriteCmd(0x20); //0x20,0.77xVcc

WriteCmd(0x8d); //--set DC-DC enable

WriteCmd(0x14); //

WriteCmd(0xaf); //--turn on oled panel

WriteEnd();

}

 

void OLED_ON(void)

{

WriteCmdStart();

WriteCmd(0X8D); //设置电荷泵

WriteCmd(0X14); //开启电荷泵

WriteCmd(0XAF); //OLED唤醒

WriteEnd();

}

void OLED_Fill(unsigned char fill_Data)//全屏填充

{

unsigned char m,n;

for(m=0;m<8;m++)

{

WriteCmdStart();

WriteCmd(0xb0+m);       //page0-page1

WriteCmd(0x00);     //low column start address

WriteCmd(0x10);     //high column start address

WriteEnd();

WriteDataStart();

for(n=0;n<128;n++){

WriteData(fill_Data);

}

WriteEnd();    

}

}

 

void OLED_CLS(void)//清屏

{

OLED_Fill(0x00);

}

int OLED_ShowStr(unsigned char x, unsigned chary, unsigned char ch[], unsigned char TextSize)

{

unsigned char c = 0,i = 0,j = 0;

switch(TextSize){

case 1:

{

while(ch[j] != '\0'){

c = ch[j] - 32;

OLED_SetPos(x,y);

WriteDataStart();

for(i=0;i<8;i++)

WriteData(Font_6x8_h[c*8+i]);

WriteEnd();    

y--;

j++;

}

}break;

 

case 2:

{

while(ch[j] != '\0'){

c = ch[j] - 32;

OLED_SetPos(x,y);

WriteDataStart();

for(i=0;i<12;i++)

WriteData(Font_8x12_h[c*12+i]);

WriteEnd();    

y--;

j++;

}

}break;

case 3:

{

while(ch[j] != '\0'){

c = ch[j] - 32;

OLED_SetPos(x,y);

WriteDataStart();

for(i=0;i<16;i++)

WriteData(Font_8x16_h[c*16+i]);

WriteEnd();    

y--;

j++;

}

}break;

 

default:

return 1;

}

return 0;

}

void OLED_SetPos(unsigned char x, unsigned chary) //设置起始点坐标

{

WriteCmdStart();

WriteCmd(0xb0+(y&0x0f));

WriteEnd();    

WriteCmdStart();

WriteCmd(((x&0xf0)>>4)|0x10);

WriteEnd();    

WriteCmdStart();

WriteCmd((x&0x0f)|0x01);

WriteEnd();    

}

//Main函数:

int main(void)

{

unsigned char str1[]="Hello!";

unsigned char str2[]="MM32";

unsigned char str3[]="SPIN27";

delay_init();//启动外部晶振

uart_initwBaudRate(115200);

printf("uart ok\r\n");

 

I2CInitMasterMode() ;

I2CSetDeviceAddr(OLED_DEVICE_ADDR);

OLED_Init();//关闭显示并初始化

OLED_ON();//OLED唤醒

OLED_Fill(0xFF);//点亮所有像素点

OLED_CLS();//清屏

OLED_ShowStr(5,6,str1,3);

OLED_ShowStr(21,5,str2,3);

OLED_ShowStr(37,6,str3,3);

while(1)             

{

}

}

由于篇幅限制,在本篇文章中字库部分没有提供对应的代码。连接好线路(VCC\GND\SCL\SDA4线),编译程序下载,我们就可以看到屏幕亮起来了,显示“Hello! MM32 SPIN27”。


图6 OLED显示效果

同样,我们也可以通过逻辑分析仪,分析芯片与模块之间的数据交互,进一步了解I2C通信协议,进一步熟悉I2C的使用。

来源:灵动MM32MCU

围观 135