`
本帖最后由 MMCU5721167 于 2019-1-22 09:32 编辑
来源 灵动微电MMCU
上一章节中已经教大家如何使用ADC和DMA来采集红外测距模块的数据,本章节将与大家一起配置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
- ");
-
- 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)
- {
- }
- }
复制代码
由于篇幅限制,在本篇文章中字库部分没有提供对应的代码。连接好线路(VCCGNDSCLSDA4线),编译程序下载,我们就可以看到屏幕亮起来了,显示“Hello! MM32 SPIN27”。
图6 OLED显示效果
同样,我们也可以通过逻辑分析仪,分析芯片与模块之间的数据交互,进一步了解I2C通信协议,进一步熟悉I2C的使用。
灵动微电子股份有限公司(股票代码:833448,股票简称:灵动微电)是国内专注于MCU产品与MCU应用方案的领先供应商,是中国工业及信息化部和上海市信息化办公室认定的集成电路设计企业,同时也是上海市认定的高新技术企业。自2011年3月成立至今,灵动微电子已经成功完成数百余MCU产品的设计及推广,灵动微电子目前已批量供货的基于ARMCortex-M0及Cortex-M3 内核的MCU产品包括:针对通用高性能市场的MM32F系列,针对超低功耗及安全应用的MM32L系列,具有多种无线连接功能的MM32W系列,电机驱动及控制专用的MM32SPIN系列,以及针对超小尺寸及超高集成度的MM32P系列等,以满足客户及市场多领域、多层次的丰富应用场景需求。
灵动微电子立足本土,洞悉市场,贴近客户,以为客户提供“保姆式”的全方位支持为特色,坚持“专业、可靠、便捷、高效”的服务理念,贯彻差异最大化,成本最优化的经营策略,不断强化自身生态价值,维护良好产品品牌。公司在销售初期就与客户充分接触,为客户提供产品整体解决方案,从产品功能定义、市场竞争力分析到算法整合、软件驱动、应用例程等都深入参与,为客户提供精准的市场分析和全面的应用方案,帮助客户把握好成功的每一个重要环节。
`
0