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,估计还差时序调节的问题吧。
标签:ma51t12b,void,45,STM32,InitStructure,IIC,GPIO,I2C,I2C1 来源: https://blog.csdn.net/Chasing_Chasing/article/details/115237850