45 STM32 IIC主机、从机通信实例(ma51t12b触摸按键芯片)

45.1引言

最近在研究触摸按键板的通信,按键板主要用到的通信协议为IIC,事实上IIC的例子有很多,但大都都是模拟IIC的主机(引用某网友的评论),从机这边的内容还是比较少。

早在两三年前就跟着原子哥的学习资料,学习并操作过IIC的eeprom器件,但是依葫芦画瓢,虽然看了IIC的协议,但是还不算很透!!!只能说熟悉。最近接到这个项目,用到IIC通信,原本很快就搞定了,但是遇到了一些问题,发现是从机地址写错了(感觉是芯片厂商故意在文档里面写错的),不过换算挺好的,借此机会算是把IIC较为透彻的跑一遍。

45.2 实验环境

本次IIC实验环境使用了两块stm32f103C8t6最小开发板,一个做IIC主机,一个做IIC从机,使用的是标准库完成。

硬件环境:

IIC主机:使用了模拟IIC;

IIC从机:使用STM32自带的标准库;

还有逻辑分析仪!!(因为IIC学会了使用这家伙,感觉还不错)

45.3 IIC知识

(0)IIC支持一主多从,属于半双工,

 

主机跟从机通信,由直接先发目标从机的地址,目标从机匹配好自己地址后会回复主机一个ACK,然后根据从机定义的读写协议进行通信。

(1)IIC总线在空闲状态SDA和SCL都是高电平;

(2)IIC的起始信号和结束信号;

在使用模拟IIC的时候,按照时序图来就好,延时us级延时,根据原子哥那个延时应该没啥问题。

 

(3)IIC应答信号

 

应答信号,是数据传输第8位完成后,第九位需要应答,如果SDA在第九个时钟被拉低,表示发生了应答信号,否则没有应答。

(4)数据时序

传输数据的时候,在时钟跳变的时候,SDA必须保持稳定,否则数据错乱。

(7)IIC主从交互的时候,从机如何判断主机是要写?还是要读呢?

本来以前我没怎么关注这个问题,因为一些外设器件已经设计好了,提供了相关的驱动demo,在用的时候不会深究每一个细节,但当这次用stm32模拟从机的时候,我就遇到了这个问题,因为我要读取从机的数据。

好了,从机如何判断主机要读?还是写?是基于主机发送从机地址的第1位,是高还是低,来判断的!!!如下图所示。

主机写时序:

主机读时序:

结合上面两张图,就可以看到就是从机地址的第1位,是否为0或1来判断的。

注:上述的IIC从机设备的地址只有高7个字节,所以第1位是用来判断,主机到底是要写还是要读的。如果地址是其它位数的,地址的第1位,作用也是一样的功能,就是用来判断是否读写!!

(6)IIC完整的数据时序示例

45.4 STM32F103C8T6 IIC模拟主机代码

IIC头文件代码:

#ifndef __MYIIC_H
#define __MYIIC_H

#include "sys.h"
#include "stm32f10x.h"

//IO操作函数	 
#define IIC_SCL    PBout(8) //SCL
#define IIC_SDA    PBout(9) //SDA	 
#define READ_SDA   PBin(9)  //输入SDA 

//IIC所有操作函数
void IIC_Init(void);                //初始化IIC的IO口				 
void IIC_Start(void);				//发送IIC开始信号
void IIC_Stop(void);	  			//发送IIC停止信号
void IIC_Send_Byte(u8 txd);			//IIC发送一个字节
u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
u8 IIC_Wait_Ack(void); 				//IIC等待ACK信号
void IIC_Ack(void);					//IIC发送ACK信号
void IIC_NAck(void);				//IIC不发送ACK信号

void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);
u8 IIC_Read_One_Byte(u8 daddr,u8 addr);	  
#endif

IIC源文件代码:

#include "myiic.h"
#include "delay.h"


//
//初始化IIC
void IIC_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE); // 使能PB端口时钟	

	
	//PB8-SCL  PB9-SDA
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;	//选择对应的引脚
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;		 
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);	//初始化PC端口
	GPIO_SetBits(GPIOB, GPIO_Pin_8);   // 关闭所有LED
	GPIO_SetBits(GPIOB, GPIO_Pin_9);   // 关闭所有LED

	
}

//IO方向设置
void SDA_IN(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
	
	//PB9-SDA
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;	//选择对应的引脚
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;	   
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
}

void SDA_OUT(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
	
	//PB9-SDA
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;	//选择对应的引脚
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	   
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
}


//产生IIC起始信号
void IIC_Start(void)
{
	SDA_OUT();     //sda线输出
	IIC_SDA=1;	  	  
	IIC_SCL=1;
	delay_us(4);
 	IIC_SDA=0;//START:when CLK is high,DATA change form high to low 
	delay_us(4);
	IIC_SCL=0;//钳住I2C总线,准备发送或接收数据 
}	  

//产生IIC停止信号
void IIC_Stop(void)
{
	SDA_OUT();//sda线输出
	IIC_SCL=1;
	IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
 	delay_us(4);
	IIC_SCL=1; 
	IIC_SDA=1;//发送I2C总线结束信号
	delay_us(4);							   	
}

//等待应答信号到来
//返回值:1,接收应答失败
//        0,接收应答成功
u8 IIC_Wait_Ack(void)
{
	u8 ucErrTime=0;
	SDA_IN();      //SDA设置为输入  
	IIC_SDA=1;delay_us(1);	   
	IIC_SCL=1;delay_us(1);	 
	while(READ_SDA)
	{
		ucErrTime++;
		if(ucErrTime>250)
		{
			IIC_Stop();
			return 1;
		}
	}
	IIC_SCL=0;//时钟输出0 	   
	return 0;  
} 

//产生ACK应答
void IIC_Ack(void)
{
	IIC_SCL=0;
	SDA_OUT();
	IIC_SDA=0;
	delay_us(2);
	IIC_SCL=1;
	delay_us(2);
	IIC_SCL=0;
}

//不产生ACK应答		    
void IIC_NAck(void)
{
	IIC_SCL=0;
	SDA_OUT();
	IIC_SDA=1;
	delay_us(2);
	IIC_SCL=1;
	delay_us(2);
	IIC_SCL=0;
}

//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答			  
void IIC_Send_Byte(u8 txd)
{                        
    u8 t;   
	SDA_OUT(); 	    
    IIC_SCL=0;//拉低时钟开始数据传输
    for(t=0;t<8;t++)
    {              
        IIC_SDA=(txd&0x80)>>7;
        txd<<=1; 	  
		delay_us(2);   //对TEA5767这三个延时都是必须的
		IIC_SCL=1;
		delay_us(2); 
		IIC_SCL=0;	
		delay_us(2);
    }	 
}

//读1个字节,ack=1时,发送ACK,ack=0,发送nACK   
u8 IIC_Read_Byte(unsigned char ack)
{
	unsigned char i,receive=0;
	SDA_IN();//SDA设置为输入
    for(i=0;i<8;i++ )
	{
        IIC_SCL=0; 
        delay_us(2);
		IIC_SCL=1;
        receive<<=1;
        if(READ_SDA)receive++;   
		delay_us(1); 
    }					 
    if (!ack)
        IIC_NAck();//发送nACK
    else
        IIC_Ack(); //发送ACK   
    return receive;
}

45.5 STM32F103C8T6 IIC硬件从机代码

说到IIC的从机代码,网上找了挺多的,但好像标准库的比较少,导致要自己翻了 较多的博客才搞定,现在共享出来,为了大伙更好的学习吧。

IIC头文件代码:

#ifndef __MYIIC_H
#define __MYIIC_H

#include "sys.h"
#include "stm32f10x.h"

void I2C1_Init(void);


#endif

IIC源文件代码:

#include <stdio.h>
#include <stdint.h>
#include "myiic.h"
#include "delay.h"


/*---------IIC1---------------*/
u8 I2C1_ADDRESS = 0x68; //7 位 I2C 地址


vu8 I2C_Status = 0;

vu8 I2C1_Buffer_Tx[8] = {0};
vu8 I2C1_Buffer_Rx[8] = {0};

vu32 Tx_Counter = 0;
vu32 Rx_Counter = 0;
//

void I2C1_Init(void)
{
	GPIO_InitTypeDef  	GPIO_InitStructure;
	I2C_InitTypeDef 	I2C_InitStructure;
	NVIC_InitTypeDef 	NVIC_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); 

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	GPIO_PinRemapConfig(GPIO_Remap_I2C1,ENABLE);


	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);

	I2C_DeInit(I2C1);
	I2C_InitStructure.I2C_Mode 			= I2C_Mode_I2C;
	I2C_InitStructure.I2C_DutyCycle 	= I2C_DutyCycle_2;
	I2C_InitStructure.I2C_OwnAddress1 	= I2C1_ADDRESS; 	//从机地址,一定要设置正确
	I2C_InitStructure.I2C_Ack 			= I2C_Ack_Enable;
	I2C_InitStructure.I2C_AcknowledgedAddress= I2C_AcknowledgedAddress_7bit;
	I2C_InitStructure.I2C_ClockSpeed 	= 100000;
	I2C_Init(I2C1, &I2C_InitStructure);

	NVIC_InitStructure.NVIC_IRQChannel 						= I2C1_EV_IRQn;//事件中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 	= 2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority 			= 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd					= ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	NVIC_InitStructure.NVIC_IRQChannel 						= I2C1_ER_IRQn;//错误中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 	= 2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority 			= 1;				   
	NVIC_InitStructure.NVIC_IRQChannelCmd					= ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	I2C_ITConfig(I2C1, I2C_IT_BUF | I2C_IT_EVT |I2C_IT_ERR, ENABLE);   
	I2C_Cmd(I2C1, ENABLE);	 
	/*允许1字节1应答模式*/
    I2C_AcknowledgeConfig(I2C1, ENABLE);    
	
}


// I2C1 作为从机,用于中断接收从机数据
void I2C1_EV_IRQHandler(void)
{
	uint8_t ch = 0;

	switch(I2C_GetLastEvent(I2C1))
	{
		// 收到匹配的地址数据
		case I2C_EVENT_SLAVE_TRANSMITTER_ADDRESS_MATCHED:
		{
			I2C_Status = 1;
			//I2C_GenerateSTOP(I2C1, DISABLE);
			break;
		}
		// 收到一个字节数据
		case I2C_EVENT_SLAVE_BYTE_RECEIVED:
		{
			I2C_Status = 2;
			
			//I2C_ClearITPendingBit(I2C1, I2C_IT_RXNE);		  
			ch = I2C_ReceiveData(I2C1);
			I2C1_Buffer_Rx[2] = ch;
			break;
		}
		case I2C_EVENT_SLAVE_BYTE_TRANSMITTING: //发送数据
		{
			I2C_Status = 3;
			
			I2C_SendData(I2C1, 0x77);
			//I2C_ClearITPendingBit(I2C1, I2C_IT_TXE);		  
			break;
		}
		//发送数据,要发送,不然锁死,不过 master 没收到
		case I2C_EVENT_SLAVE_BYTE_TRANSMITTED:
		{
			I2C_Status = 4;

			I2C_SendData(I2C1, 0x66);
			//I2C_ClearITPendingBit(I2C1, I2C_IT_TXE);		  

			break;
		}
		case I2C_EVENT_SLAVE_STOP_DETECTED: //收到结束条件
		{
			I2C_Status = 5;
			//I2C_GenerateSTOP(I2C1, ENABLE);			
			I2C_Cmd(I2C1, ENABLE); 
			break;
		}
		default: 
			break;
	}

	
}


void  I2C1_ER_IRQHandler(void)
{
	/* Check on I2C1 AF flag and clear it */ 
	if (I2C_GetITStatus(I2C1, I2C_IT_AF)) 
	{ 
		I2C_ClearITPendingBit(I2C1, I2C_IT_AF); 
	} 

	/* Check on I2C1 AF flag and clear it */ 
	if (I2C_GetITStatus(I2C1, I2C_IT_BERR))  
	{ 
		I2C_ClearITPendingBit(I2C1, I2C_IT_BERR);
	}
}

45.6 ma51t12b触摸按键芯片IIC通信实例

本次实验刚好用到这块ma51t12b触摸芯片,这里要吐槽一下他们的datasheet,从机的地址跟文档写的是不一样的,搞的我浪费了一天时间找问题,最后他们联系上厂商,他们发了一个51单片机的IIC读取demo,也不是stm32的,然后一对比,发现地址居然跟文档写的不一样,真的好气啊。

不过现在搞定了就好了,按照下面的时序进行读写操作。

废话不多说,还是上代码把,因为我现在要吃午饭了。

ma51t12b.h 头文件

#ifndef __MA51T12B_H__
#define __MA51T12B_H__

#define 	MA51T12B_DEVICE_ADDR			0xD0

#define		MA51T12B_MID_SENS_VALUE			((1<<7)|(8<<4)|(1<<3)|(8<<0))



/register addr//
#define 	MA51T12B_CH1_2_SEN_ADDR			0x02
#define 	MA51T12B_CH3_4_SEN_ADDR			0x03
#define 	MA51T12B_CH5_6_SEN_ADDR			0x04
#define 	MA51T12B_CH7_8_SEN_ADDR			0x05
#define 	MA51T12B_CH9_10_SEN_ADDR		0x06
#define 	MA51T12B_CH11_12_SEN_ADDR		0x07

#define 	MA51T12B_INT_MOD_ADDR			0x08
#define 	MA51T12B_SYS_CON_ADDR			0x09
#define 	MA51T12B_CH1_8_PD_ADDR			0x0C
#define 	MA51T12B_CH9_12_PD_ADDR			0x0D

#define 	MA51T12B_CH1_4_OUT_ADDR			0x10
#define 	MA51T12B_CH5_8_OUT_ADDR			0x11
#define 	MA51T12B_CH9_12_OUT_ADDR		0x12
///

void MA51TXX_WriteOneByte(uint8_t addr, uint8_t nByte);
uint8_t MA51TXX_ReadOneByte(uint8_t addr);

void MA51TXX_Init(void);

#endif

ma51t12b.c源文件

#include <stdio.h>
#include <stdint.h>
#include "sys.h"
#include "stm32f10x.h"
#include "ma51t12b.h"
#include "myiic.h"


uint8_t		keyNumValue 	= 0xff;
uint8_t		keyASCIIValue 	= 'N';
uint8_t		nCnt = 0;


void MA51TXX_WriteOneByte(uint8_t addr, uint8_t nByte)
{
	uint8_t res = 0;
	
	//起始信号
	IIC_Start();
	
	//写从机地址
	IIC_Send_Byte(MA51T12B_DEVICE_ADDR);
	
	//等待应答
	res = IIC_Wait_Ack();
	if(res ==1)
	{
		printf("===>falied at (%d) line\r\n",__LINE__);
		return;
	}
	
	//写寄存器地址
	IIC_Send_Byte(addr);
	
	//等待应答
	res = IIC_Wait_Ack();
	if(res ==1)
	{
		printf("===>falied at (%d) line\r\n",__LINE__);
		return;
	}
	
	//写数据
	IIC_Send_Byte(nByte);
	
	//等待应答
	res = IIC_Wait_Ack();
	if(res ==1)
	{
		printf("===>falied at (%d) line\r\n",__LINE__);
		return;
	}

	//t
	IIC_Stop();

	delay_ms(10);
}

uint8_t MA51TXX_ReadOneByte(uint8_t addr)
{
	uint8_t res = 0x0;
	
	//起始信号
	IIC_Start();
	
	//写从机地址
	IIC_Send_Byte(MA51T12B_DEVICE_ADDR);
	
	//等待应答
	res = IIC_Wait_Ack();
	if(res ==1)
	{
		printf("===>falied at (%d) line\r\n",__LINE__);
		return;
	}
	
	//写寄存器地址
	IIC_Send_Byte(addr);
	
	//等待应答
	res = IIC_Wait_Ack();
	if(res ==1)
	{
		printf("===>falied at (%d) line\r\n",__LINE__);
		return;
	}
	IIC_Stop();

	delay_us(50);
	
	//起始信号
	IIC_Start();

	//写从机地址
	IIC_Send_Byte(MA51T12B_DEVICE_ADDR | 0x01);

	//等待应答
	res = IIC_Wait_Ack();
	if(res ==1)
	{
		printf("===>falied at (%d) line\r\n",__LINE__);
		return;
	}

	res = IIC_Read_Byte(0);

	IIC_Stop();

	delay_ms(10);

	return res;
}

//初始化硬件
void MA51TXX_Init(void)
{	
	IIC_Init();
	delay_ms(500);

	//配置工作模式 0-正常模式 1-低功耗模式
	MA51TXX_WriteOneByte(MA51T12B_SYS_CON_ADDR,0x03);

	//配置中断输出-中灵敏度-低灵敏度值
	MA51TXX_WriteOneByte(MA51T12B_INT_MOD_ADDR,0x10);

	//设置按键灵敏度
	MA51TXX_WriteOneByte(MA51T12B_CH1_2_SEN_ADDR,MA51T12B_MID_SENS_VALUE);
	MA51TXX_WriteOneByte(MA51T12B_CH3_4_SEN_ADDR,MA51T12B_MID_SENS_VALUE);
	MA51TXX_WriteOneByte(MA51T12B_CH5_6_SEN_ADDR,MA51T12B_MID_SENS_VALUE);
	MA51TXX_WriteOneByte(MA51T12B_CH7_8_SEN_ADDR,MA51T12B_MID_SENS_VALUE);
	MA51TXX_WriteOneByte(MA51T12B_CH9_10_SEN_ADDR,MA51T12B_MID_SENS_VALUE);
	MA51TXX_WriteOneByte(MA51T12B_CH11_12_SEN_ADDR,MA51T12B_MID_SENS_VALUE);

	//使能1-12按键
	MA51TXX_WriteOneByte(MA51T12B_CH1_8_PD_ADDR,0x00);
	MA51TXX_WriteOneByte(MA51T12B_CH9_12_PD_ADDR,0x00);
	
}

 好了,到此结束了!通过本次实验,算是较为完整的熟悉了IIC,估计还差时序调节的问题吧。

猜你喜欢

转载自blog.csdn.net/Chasing_Chasing/article/details/115237850