这两天要写一下板子上这个芯片的驱动,虽然现在写好了,但是感觉还是有必要把整个挣扎痛苦的过程记录下来,积累经验。
首先由于之前并没用过配置寄存器的方式实现主机模式的I2C通信,故想吃一次“螃蟹”。然后从网上找了一些资料做准备工作,初始化IO、配置I2C、实现读写函数,感觉还挺顺手的。接着马上下载到板子上开始调试,然后辨出现了意料之中的事:程序挂了。。。然后开始了各种怀疑。参考网上的例程和其他资料,好不靠谱啊。比对了好久,还是不能确定我写的程序到底对不对。然后小小地纠结了一下,唉,还是用模拟I2C写驱动吧,起码以前还成功用过。
随后一狠心将之前研究了半天的代码(突然感觉好悲催)给删除了 。好吧,重新整理下思路,调整下心情,开始模拟I2C。
配IO、找相关控制寄存器做上宏定义、写函数( start、stop、read、write四个函数 ),测试函数写完,带着略有兴奋的心情,又一次将 程序下载到板子里。这一次的结果可是出乎意料的:虽然程序没挂,但是读写程序都报错了。。。好失败啊。。可是尽管这样我还是比较相信我的代码的(因为之前这可是好用着呢)。于是去问度娘了,看看,有悲惨遭遇的小伙伴还不少(多少有点安慰)。
首先由于之前并没用过配置寄存器的方式实现主机模式的I2C通信,故想吃一次“螃蟹”。然后从网上找了一些资料做准备工作,初始化IO、配置I2C、实现读写函数,感觉还挺顺手的。接着马上下载到板子上开始调试,然后辨出现了意料之中的事:程序挂了。。。然后开始了各种怀疑。参考网上的例程和其他资料,好不靠谱啊。比对了好久,还是不能确定我写的程序到底对不对。然后小小地纠结了一下,唉,还是用模拟I2C写驱动吧,起码以前还成功用过。
随后一狠心将之前研究了半天的代码(突然感觉好悲催)给删除了 。好吧,重新整理下思路,调整下心情,开始模拟I2C。
配IO、找相关控制寄存器做上宏定义、写函数( start、stop、read、write四个函数 ),测试函数写完,带着略有兴奋的心情,又一次将 程序下载到板子里。这一次的结果可是出乎意料的:虽然程序没挂,但是读写程序都报错了。。。好失败啊。。可是尽管这样我还是比较相信我的代码的(因为之前这可是好用着呢)。于是去问度娘了,看看,有悲惨遭遇的小伙伴还不少(多少有点安慰)。
找了半天罗列了一些:地址不对?延时不对?然后一个一个尝试,先找地址,手册上是这样描述的:
额,我的器件是PCF8574TS,没有。。。 虽然原理图上写的是0x40,但还是谨慎一点为好(看到网上好多骂奸商坑人的文章)。
可是。。。这两种地址都做了尝试,都不行啊。。都快崩溃了。周日晚上加班到八点半,没搞定,灰溜溜地回去了。。
第二天,就是今天啦,虚心请教了大神同事。也是各种怀疑(我都快怀疑人生了),然后检查呗,都准备亮出必杀技(量波形)了。唉,上午就这么快过了,下午大神同事说,看看供电吧。赶紧拿个万用表,看准原理图比对元件,一戳,0V,额没供电。。。。
突然感觉上边那么多话都成废话了。。。
请教了硬件工程师之后,飞了根线,供电供上了。马上下载程序(当时用了地址0x40),调试了一下,哟,进去了,成功了!哎呀,万事开头难,这下好了,可以正常一点了。接下来就简单了,先贴两个图吧:
看了半天,感觉不对劲,①写时序中没有stop信号。。。②读时序中stop信号之前有个1。。。
第一个问题又去请教大神同事(不耻下问嘛),说stop信号必须有。
第二个问题额,1不就是NACK么(好蠢,还是经同事提醒想起来的)。
贴上I2C标准时序:
想通之后,重新修改整理代码,调试通过,哎呀,完美了。。好了,贴代码了。
i2c.h 文件
#ifndef _I2C_H_
#define _I2C_H_
#include "config.h"
#define I2C_NOT_LAST 0x00
#define I2C_LAST_BYTE 0x01
#define I2C_READ 0x01
#define I2C_WRITE 0x00
#define PCF8574TS 0x40
/*!
* 定义I2C1的IO访问操作
* 配置I2C1引脚:SCL:PB6(92) SDA:PB7(93)
*/
#define I2C1_SDA_IDR (GPIOB_BASE+0x10 - PERIPH_BASE) //!<输入数据寄存器(选择B端口)
#define I2C1_SDA_ODR (GPIOB_BASE+0x14 - PERIPH_BASE) //!<输出数据寄存器(选择B端口)
#define I2C1_SCL_ODR (GPIOB_BASE+0x14 - PERIPH_BASE) //!<输出数据寄存器(选择B端口)
/*定义I2C1 SDA地址*/
#define I2C1_SDA_NUM 7
#define I2C1_SDA_OUA (PERIPH_BB_BASE + (I2C1_SDA_ODR * 32) + (I2C1_SDA_NUM * 4))
#define I2C1_SDA_ODA *(__IO uint32_t *)I2C1_SDA_OUA //!<数据寄存器输出地址
#define I2C1_SDA_INA (PERIPH_BB_BASE + (I2C1_SDA_IDR * 32) + (I2C1_SDA_NUM * 4))
#define I2C1_SDA_IDA *(__IO uint32_t *)I2C1_SDA_INA //!<数据寄存器输入地址(时钟线不需要读输入寄存器数据)
#define I2C1_SDA_MCA *(__IO uint32_t *)(GPIOB_BASE+0) //!<控制寄存器
/*定义I2C1 SCL地址*/
#define I2C1_SCL_NUM 6
#define I2C1_SCL_OUA (PERIPH_BB_BASE + (I2C1_SCL_ODR * 32) + (I2C1_SCL_NUM * 4))
#define I2C1_SCL_ODA *(__IO uint32_t *)I2C1_SCL_OUA
#define I2C1_SCL_MCA *(__IO uint32_t *)(GPIOB_BASE+0) //!<控制寄存器
/*定义I2C1 SDA IO操作*/
#define I2C1_SDA_OUT() I2C1_SDA_MCA = (((I2C1_SDA_MCA) & 0xFFFF3FFF) | 0x00004000) //!<数据线配置为输出(PB7)
#define I2C1_SDA_H() I2C1_SDA_ODA = 1 //!<数据线拉高
#define I2C1_SDA_L() I2C1_SDA_ODA = 0 //!<数据线拉低
#define I2C1_SDA_IN() I2C1_SDA_MCA = (((I2C1_SDA_MCA) & 0xFFFF3FFF) | 0x00000000) //!<数据线配置为输入(PB7)
#define I2C1_SDA_READ() I2C1_SDA_IDA //!<读取数据线上电平高低值
/*定义I2C1 SCL IO操作*/
#define I2C1_SCL_OUT() I2C1_SCL_MCA = (((I2C1_SCL_MCA) & 0xFFFFCFFF) | 0x00001000) //!<时钟线配置为输出(PB6)
#define I2C1_SCL_H() I2C1_SCL_ODA = 1 //!<时钟线拉高
#define I2C1_SCL_L() I2C1_SCL_ODA = 0 //!<时钟线拉低
void I2CSysCtlDelay(unsigned long ulCount);
void I2C1_Start(void);
void I2C1_Stop(void);
uint8 I2C1_Receive_byte(uint8 flag);
uint8 I2C1_Send_byte(uint8 data);
#endif
i2c.c 文件
#include "i2c.h"
#define I2C_DELAY() I2CSysCtlDelay(30)
/*
* @brief SysCtlDelay
* @param ulCount 延时值,必须大于0
* @retval None
*/
void I2CSysCtlDelay(unsigned long ulCount)
{
__asm(" subs r0, #1\n"
" bne.n I2CSysCtlDelay\n"
" bx lr");
}
/*!
* @brief I2C1 GPIO初始化
* @param none
* @return NONE
* @note PB6-SCL,PB7-SDA
*/
void I2C1_IO_init(void)
{
GPIO_InitTypeDef GPIO_Init_s;
GPIO_Init_s.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 ;
GPIO_Init_s.GPIO_Mode = GPIO_Mode_OUT; //!<输出模式
GPIO_Init_s.GPIO_OType = GPIO_OType_OD; //!<开漏
GPIO_Init_s.GPIO_Speed = GPIO_Speed_50MHz; //!<快速
GPIO_Init_s.GPIO_PuPd = GPIO_PuPd_UP;//GPIO_PuPd_NOPULL; //无上拉
GPIO_Init(GPIOB, &GPIO_Init_s);
}
/*!
* @brief I2C1起始信号
* @param none
* @return none
* @note 数据:D 时钟:C 高:H 低:L 输出:O 输入:I 延时:_
* @note DOCO_DHCH_DL__CL
*/
void I2C1_Start(void)
{
I2C1_SDA_OUT();
I2C1_SCL_OUT();
I2C_DELAY();
I2C1_SDA_H();
I2C1_SCL_H();
I2C_DELAY();
I2C1_SDA_L();
I2C_DELAY();
I2C_DELAY();
I2C1_SCL_L();
}
/*!
* @brief I2C1结束信号
* @param none
* @return none
* @note 数据:D 时钟:C 高:H 低:L 输出:O 输入:I 延时:_
* @note DOCO_DLCL_CH___DH__
*/
void I2C1_Stop(void)
{
I2C1_SDA_OUT();
I2C1_SCL_OUT();
I2C1_SDA_L();
I2C1_SCL_L();
I2C_DELAY();
I2C1_SCL_H();
I2C_DELAY();
I2C_DELAY();
I2C_DELAY();
I2C1_SDA_H();
I2C_DELAY();
I2C_DELAY();
}
/*!
* @brief 主机向I2C1总线发送一个字节
* @param data:数据
* @return 0:失败 1:成功
* @note 数据:D 时钟:C 高:H 低:L 输出:O 输入:I 延时:_ 读取:R
* @note (_DH/L_CH__CL)*8_DHDI_CH_DR_CL_DO
*/
uint8 I2C1_Send_byte(uint8 data)
{
uint8 k;
for(k=0;k<8;k++){//!<发送8bit数据
I2C_DELAY();
if(data&0x80){
I2C1_SDA_H();
}else{
I2C1_SDA_L();
}
data=data<<1;
I2C_DELAY();
I2C1_SCL_H();
I2C_DELAY();
I2C_DELAY();
I2C1_SCL_L();
}
I2C_DELAY();//!<延时读取ACK响应
I2C1_SDA_H();
I2C1_SDA_IN();//!<设为输入
I2C_DELAY();
I2C1_SCL_H();
I2C_DELAY();
k=I2C1_SDA_READ();//读取ACK/NACK
I2C_DELAY();
I2C1_SCL_L();
I2C_DELAY();
I2C1_SDA_OUT();
if(k){ NACK响应
return 0;
}
return 1;
}
/*!
* @brief 主机从I2C1总线上接收一个字节
* @param flag:是否为读取的最后一个字节
* @return data:读取的数值
* @note 数据:D 时钟:C 高:H 低:L 输出:O 输入:I 延时:_ 读取:R
* @note DI(_CH_DR_CL_)*8DODL_CH__CL_DO
*/
uint8 I2C1_Receive_byte(uint8 flag)
{
uint32 k,data;
I2C1_SDA_IN();//!<置为输入线
data=0;
for(k=0;k<8;k++){ //!<接收8bit数据
I2C_DELAY();
I2C1_SCL_H();
I2C_DELAY();
data=data|I2C1_SDA_READ();//!<读数据
data=data<<1;
I2C_DELAY();
I2C1_SCL_L();
I2C_DELAY();
}
data=data>>1; //!<往回移动1次
I2C1_SDA_OUT(); //!<置为输出线
if(flag){
I2C1_SDA_H(); //!<拉高为NACK
}else{
I2C1_SDA_L(); //!<拉低为ACK
}
I2C_DELAY();
I2C1_SCL_H();
I2C_DELAY();
I2C_DELAY();
I2C1_SCL_L();
I2C_DELAY();
I2C1_SDA_OUT();
return (uint8)data; //!<返回读取的数据
}
i2c_PCF8574TS.c 文件
#include "i2c.h"
/*!
* @brief 主机从I2C1总线上的PCF8574TS设备中读取一个字节数据
* @param data:数据回传指针
* @return 0:失败 1:成功
* @note 获取的数据为8个IO口状态
*/
uint8 i2c_EIO_RD(uint8* data)
{
uint8 state,ret;
I2C1_Start(); //!<启动I2C总线
state = I2C1_Send_byte(PCF8574TS|I2C_READ); //!<发送器件地址,主机读
if(state == 1){
*data = I2C1_Receive_byte(I2C_LAST_BYTE); //!<读取数据
ret = 1;
}else{
ret = 0;
}
I2C1_Stop();
return ret;
}
/*!
* @brief 主机向I2C1总线上的PCF8574TS设备中写入一个字节数据
* @param data:写入的数据
* @return 0:失败 1:成功
* @note none
*/
uint8 i2c_EIO_WD(uint8 data)
{
uint8 state,ret;
I2C1_Start(); //!<启动I2C总线
state = I2C1_Send_byte(PCF8574TS|I2C_WRITE); //!<发送器件地址,主机写
if(state==1){
state = I2C1_Send_byte(data);
if(state==1){
ret = 1;
}else{
ret = 0;
}
}else{
ret = 0;
}
I2C1_Stop();
return ret;
}
以上仅做记录,望以后少走弯路!但总的来说,虽然很痛苦,可谁叫自己阅历不丰富咧。