基于复现油炸鸡的智能手表的过程(1)
一、实现软件IIC功能
1 相关头文件介绍
1.1 一个通用的IIC接口,通过设置一个IIC控制结构体实现通用接口
typedef struct
{
GPIO_TypeDef* IIC_SDA_PORT;
GPIO_TypeDef* IIC_SCL_PORT;
uint32_t IIC_SDA_PIN;
uint32_t IIC_SCL_PIN;
// void (*CLK_ENABLE)(void);
}iic_bus_t;//IIC控制器
包含了两个IO_PORT指针可直接访问GPIO_TypeDef 这个结构体类型
例如 IIC_SDA_PORT->BSRR = (*IIC_SDA_PORT).BSRR.
1.2一些要实现的IIC通信协议接口
void IICStart(iic_bus_t *bus);
void IICStop(iic_bus_t *bus);
uint8_t IICWaitAck(iic_bus_t *bus);
void IICSendAck(iic_bus_t *bus);
void IICSendNotAck(iic_bus_t *bus);
void IICSendByte(iic_bus_t *bus, uint8_t cSendByte);
uint8_t IICReceiveByte(iic_bus_t *bus);
void IICInit(iic_bus_t *bus);
uint8_t IIC_Write_One_Byte(iic_bus_t *bus, uint8_t daddr,uint8_t reg,uint8_t data);
uint8_t IIC_Write_Multi_Byte(iic_bus_t *bus, uint8_t daddr,uint8_t reg,uint8_t length,uint8_t buff[]);
uint8_t IIC_Read_One_Byte(iic_bus_t *bus, uint8_t daddr,uint8_t reg);
uint8_t IIC_Read_Multi_Byte(iic_bus_t *bus, uint8_t daddr, uint8_t reg, uint8_t length, uint8_t buff[]);
其中包含开始信号 、停止信号、等待应答(等从机拉低总线SDA)、给应答(主机拉低总线SDA)、非应答(主机释放总线SDA),发送一个字节(高位在前)、接受一个字节(高位在前)、IIC硬件驱动初始化(电气属性)、往寄存器写字节、往寄存器写多个字节、向寄存器读一个字节、向寄存器读多个字节。这些基本涵盖了所有IIC通信协议的操作
1.3 头文件
#ifndef __IIC_HAL_H
#define __IIC_HAL_H
#include "stm32f4xx_hal.h"
typedef struct
{
GPIO_TypeDef* IIC_SDA_PORT;
GPIO_TypeDef* IIC_SCL_PORT;
uint32_t IIC_SDA_PIN;
uint32_t IIC_SCL_PIN;
// void (*CLK_ENABLE)(void);
}iic_bus_t;//IIC控制器
void IICStart(iic_bus_t *bus);
void IICStop(iic_bus_t *bus);
uint8_t IICWaitAck(iic_bus_t *bus);
void IICSendAck(iic_bus_t *bus);
void IICSendNotAck(iic_bus_t *bus);
void IICSendByte(iic_bus_t *bus, uint8_t cSendByte);
uint8_t IICReceiveByte(iic_bus_t *bus);
void IICInit(iic_bus_t *bus);
uint8_t IIC_Write_One_Byte(iic_bus_t *bus, uint8_t daddr,uint8_t reg,uint8_t data);
uint8_t IIC_Write_Multi_Byte(iic_bus_t *bus, uint8_t daddr,uint8_t reg,uint8_t length,uint8_t buff[]);
uint8_t IIC_Read_One_Byte(iic_bus_t *bus, uint8_t daddr,uint8_t reg);
uint8_t IIC_Read_Multi_Byte(iic_bus_t *bus, uint8_t daddr, uint8_t reg, uint8_t length, uint8_t buff[]);
#endif
2 IIC通用接口的C文件实现
2.1 SDA的输出输入模式
我现在还依稀记得 IIC 是低电平放数据 高电平稳定了不允许改变数据 并可以读取数据
输入模式就是为了读取IIC 的SDA总线上的数据
当然在学正点原子的时候 设置的是开漏模式 ,开漏模式不需要切换到输入模式就可以进行读
此刻SDA总线如果没有被从机操作,会由于上拉电阻处于高电平。
输出模式也是只有输出0才有效,因为开漏模式不支持输出高电平,它会假输出高电平,这个高电平是上拉电阻起的作用。
当然也可以用输出输入模式切换的形式实现读与写,在学51的时候我们是这么做的。
这里在配置电气属性的时候,我建议直接用CubeMX生成,因为我操作的时候结构体成员选错一个导致后面IIC启动失败,一直在找协议的问题,谁知道是配置电气属性出现的问题。
//SCL 低电平期间 SDA可以任意改变电位 高电平不允许改动 因为要采样
// SDA输入模式
void SDA_Input_Mode(iic_bus_t* bus)
{
GPIO_InitTypeDef GPIO_InitStructure = {0};
GPIO_InitStructure.Pin = bus->IIC_SDA_PIN;
GPIO_InitStructure.Pull = GPIO_MODE_INPUT;
GPIO_InitStructure.Pull = GPIO_PULLUP;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(bus->IIC_SDA_PORT,&GPIO_InitStructure);
}
// SDA输出模式
void SDA_Output_Mode(iic_bus_t* bus)
{
GPIO_InitTypeDef GPIO_InitStructure = {0};
GPIO_InitStructure.Pin = bus->IIC_SDA_PIN;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;//下次不要这样搞,还是用MX来配置后复制,不然容易错
GPIO_InitStructure.Pull = GPIO_NOPULL;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(bus->IIC_SDA_PORT,&GPIO_InitStructure);
}
2.2 SDA与SCL总线的读写操作
这里就很简单,直接调用HAL库的API就行,当然也可以操作寄存器。
//SDA输出一个位
void SDA_Output(iic_bus_t *bus, uint8_t val)
{
if(val)
HAL_GPIO_WritePin(bus->IIC_SDA_PORT,bus->IIC_SDA_PIN,GPIO_PIN_SET);
else
HAL_GPIO_WritePin(bus->IIC_SDA_PORT,bus->IIC_SDA_PIN,GPIO_PIN_RESET);
}
//SCL输出一个位
void SCL_Output(iic_bus_t *bus, uint8_t val)
{
if(val)
HAL_GPIO_WritePin(bus->IIC_SCL_PORT,bus->IIC_SCL_PIN,GPIO_PIN_SET);
else
HAL_GPIO_WritePin(bus->IIC_SCL_PORT,bus->IIC_SCL_PIN,GPIO_PIN_RESET);
}
//SDA输入一位 读一位
uint8_t SDA_Input(iic_bus_t *bus)
{
//我觉得这里可以直接返回读到得值 不用判断了
return (uint8_t)HAL_GPIO_ReadPin(bus->IIC_SDA_PORT,bus->IIC_SDA_PIN) ;
}
2.3 开始信号与停止信号
一般来说,开始信号就是在SCL总线高电平期间,SDA总线由高电平向低电平跳变,但为了后续时序的连贯性,会加上再将SCL总线拉低的操作,同时也避免了导致触发停止信号的过程
在停止信号的时候是在SCL总线高电平期间,SDA总线实现从低电平到高电平的跳变。同理在检查应答信号内部调用了如果超时主机未收到应答调用停止信号的过程,所以要在开始将SCL总线拉低。一般SCL总线是在结尾拉低。
//从这个严格定义的角度来说,最后将 SCL 拉低这一步不属于起始信号本身的部分
//将 SCL 拉低是为了按照 I2C 通信的正确时序,
//把总线状态调整到适合后续数据传输的起始位置
void IICStart(iic_bus_t *bus)
{
//当 SCL 为高电平时,SDA 从高电平向低电平跳变
SDA_Output(bus,1);
delay_us(2);
SCL_Output(bus,1);
delay_us(1);
SDA_Output(bus,0);
delay_us(1);
SCL_Output(bus,0);
delay_us(1); //
}
//严格来说,I2C 协议中定义的停止信号是当 SCL 为高电平时,
//SDA 从低电平向高电平跳变
//为了确保正确的时序过渡:在 I2C 通信中,
//数据的传输和各种信号的产生都是按照严格的时序进行的
void IICStop(iic_bus_t *bus)
{
//当 SCL 为高电平时,SDA 从低电平向高电平跳变
SCL_Output(bus,0);
delay_us(2);
SDA_Output(bus,0);
delay_us(1);
SCL_Output(bus,1);
delay_us(1); //释放总线
SDA_Output(bus,1);
delay_us(1);
}
2.4 等待应答 应答与非应答
等待应答 其实就是读字节其中的一步,读的操作就是SCL总线在高电平期间 主机读取SDA总线上的数据,因为此时数据是稳定的。并且等待应答在发送字节这个操作的后面,发送字节操作的结尾会把SCL电平拉低,所以我们不用二次拉低SCL电平
超时机制就是 连读5次如果都是非应答 就退出 将SDA改为输出模式,以便下次使用。
只有在主机读的时候才会把SDA设置为输入模式。
应答就是与发送字节的时序一致,在SCL总线为低电平的时候 将应答位放入SDA总线上,SCL总线高电平数据稳定并由从机读取。当然我们会在后面看见读字节操作的末尾会把SCL总线电平拉低的。
uint8_t IICWaitAck(iic_bus_t *bus)
{
uint8_t cErrTime = 5;
SDA_Input_Mode(bus);//将数据线变成输入模式 接受从机的应答
SCL_Output(bus,1);// 拉高时钟线
while(SDA_Input(bus))//读到的数一直是1的话就是没有应答
{
cErrTime--;//读5次都没有结果就是没有应答 表示接受结束
delay_us(1);
if(0 == cErrTime)
{
SDA_Output_Mode(bus);
IICStop(bus);
return ERROR;
}
}
//如果给了应答 就是读到了0
SDA_Output_Mode(bus);//转换成输出模式
SCL_Output(bus,0);//拉低时钟线 为下一个时序做准备
delay_us(2);
return SUCCESS;
}
void IICSendAck(iic_bus_t *bus)
{
SDA_Output(bus,0);//我记得是低电平放上数据
delay_us(1);
SCL_Output(bus,1);
delay_us(1);
SCL_Output(bus,0);
delay_us(2);
}
void IICSendNotAck(iic_bus_t *bus)
{
//我记得是低电平放上数据等时钟线高电平稳定采样 且此时不允许修改数据
SDA_Output(bus,1);
delay_us(1);
SCL_Output(bus,1);
delay_us(1);
SCL_Output(bus,0);
delay_us(2);
}
2.5 发送一个字节 与接受一个字节
仔细看它的时序 跟应答和接受应答很像吧。
然后接受一个字节是高位先接受
通过移位的操作一次取一位,同理接受的时候也是高位先接受在通过或操作把每一位都填上
void IICSendByte(iic_bus_t *bus, uint8_t cSendByte)
{
uint8_t i;
for(i=0;i<8;i++)
{
SCL_Output(bus,0);
delay_us(2);
SDA_Output(bus,((cSendByte >> (7 - i)) & 0x01));
delay_us(1);
SCL_Output(bus,1);
delay_us(1);
}
SCL_Output(bus,0);
delay_us(2);
}
uint8_t IICReceiveByte(iic_bus_t *bus)
{
uint8_t i, cR_Byte=0;
uint8_t bit=0;
SDA_Input_Mode(bus);//接受字节 改成输入模式
for(i=0;i<8;i++)
{
SCL_Output(bus,0); //拉低时钟线 等待从机放数据
delay_us(2);
SCL_Output(bus,1);//拉高时钟线 可以采样了
delay_us(1);
bit = SDA_Input(bus);
cR_Byte |= bit << (7-i);
}
SCL_Output(bus,0);//拉低时钟线
SDA_Output_Mode(bus);//改变为输出模式 只有读数据才是输入模式
return cR_Byte;
2.6 IIC电气属性初始化(不包括时钟,在BSP驱动会初始化IIC实例并开启时钟)
void IICInit(iic_bus_t *bus)
{
GPIO_InitTypeDef GPIO_InitStructure = {0};
//bus->CLK_ENABLE();
GPIO_InitStructure.Pin = bus->IIC_SDA_PIN ;
//上面给的是开漏 这里变成推挽搞不懂 ,推挽就不用变输入模式了
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStructure.Pull = GPIO_NOPULL;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(bus->IIC_SDA_PORT, &GPIO_InitStructure);
GPIO_InitStructure.Pin = bus->IIC_SCL_PIN ;
HAL_GPIO_Init(bus->IIC_SCL_PORT, &GPIO_InitStructure);
}
2.7 向寄存器写字节 收字节 写多个字节 收多个字节
写字节完成后 ,向寄存器写一个字节 就是按照IIC协议往里填内容就可以了。比如先给一个开始信号,然后写一个从机地址(7位地址)左移1位表示写,从机响应。然后主机在写寄存器的地址,从机再响应,主机再写数据,从机响应,发送停止信号,表示一次写寄存器字节结束。写多个字节的时候就是不发送停止信号,连续发送数据就好,因为寄存器地址会自增的。
读寄存器也是同理 先写从机地址(7位地址 )左移一位表示要进行写操作,应答后写入要读的寄存器地址 再次发起开始信号这次表示要在那个寄存器地址进行写 ,写从机地址 左移一位+1 表示要读 直接读数据就可以了。
IICSendByte(bus,*dataPtr++);
主要还是这个操作挺好的,定义了一个与输入的参数同类型指针,当然传参进来也是将那个地址作为指针 复制一份。右++是先操作再进行自增。解引用后就得到对应元素的值。自增是指针移向限一个元素,如果是32位的元素,那指针其实移动的地址为4个字节。这里要注意传进来的参数类型。
主机不想要了,就发送非应答,从机就不会再发送了。再给个停止信号,这次读寄存器就算结束了
//返回1表示失败
uint8_t IIC_Write_One_Byte(iic_bus_t *bus, uint8_t daddr,uint8_t reg,uint8_t data)
{
IICStart(bus);//开始信号
IICSendByte(bus,(daddr << 1));//写从机地址
if(IICWaitAck(bus)) //无应答停止传输
{
IICStop(bus);
//printf("%d\r\n",IICWaitAck(bus)); 第一次初始化器件的时候会非应答
return 1;
}
IICSendByte(bus,reg);
IICWaitAck(bus);
IICSendByte(bus,data);
IICWaitAck(bus);
IICStop(bus);
delay_us(1);
return 0;
}
uint8_t IIC_Write_Multi_Byte(iic_bus_t *bus, uint8_t daddr,uint8_t reg,uint8_t length,uint8_t buff[])
{
uint8_t i;
uint8_t* dataPtr = buff;
IICStart(bus);//开始信号
IICSendByte(bus,(daddr << 1));//写从机地址
if(IICWaitAck(bus)) //无应答停止传输
{
IICStop(bus);
return 1;
}
IICSendByte(bus,reg);
IICWaitAck(bus);
for(i=0;i<length;i++)
{
//IICSendByte(bus,buff[i]);
IICSendByte(bus,*dataPtr++);//利用指针指向缓存区 先解引用给首个元素赋值 再指向下一个字节
IICWaitAck(bus);
}
IICStop(bus);
delay_us(1);
return 0;
}
uint8_t IIC_Read_One_Byte(iic_bus_t *bus, uint8_t daddr,uint8_t reg)
{
uint8_t cR_Byte=0;
IICStart(bus);//开始信号
IICSendByte(bus,(daddr << 1));//写从机地址
IICWaitAck(bus);
IICSendByte(bus,reg);
IICWaitAck(bus);
IICStart(bus);//开始信号
IICSendByte(bus,(daddr << 1)| 1);//读操作
IICWaitAck(bus);
cR_Byte = IICReceiveByte(bus);
IICSendNotAck(bus);
IICStop(bus);
return cR_Byte;
}
uint8_t IIC_Read_Multi_Byte(iic_bus_t *bus, uint8_t daddr, uint8_t reg, uint8_t length, uint8_t buff[])
{
uint8_t i;
uint8_t* dataPtr = buff;
IICStart(bus);//开始信号
IICSendByte(bus,(daddr << 1)&0xFE );//写从机地址
IICWaitAck(bus);
IICSendByte(bus,reg);
IICWaitAck(bus);
IICStart(bus);//开始信号
IICSendByte(bus,(daddr << 1)| 1);//读操作
IICWaitAck(bus);
for(i=0;i<length;i++)
{
//buff[i] = IICReceiveByte(bus);
*dataPtr++ = IICReceiveByte(bus);//右++ 先操作后自加
if(i<length-1)
IICSendAck(bus);
else
IICSendNotAck(bus);
}
IICStop(bus);
return 0;
}
2.8 整个C文件
#include "iic_hal.h"
#include <stdio.h>
#include "delay.h"
//SCL 低电平期间 SDA可以任意改变电位 高电平不允许改动 因为要采样
// SDA输入模式
void SDA_Input_Mode(iic_bus_t* bus)
{
GPIO_InitTypeDef GPIO_InitStructure = {0};
GPIO_InitStructure.Pin = bus->IIC_SDA_PIN;
GPIO_InitStructure.Pull = GPIO_MODE_INPUT;
GPIO_InitStructure.Pull = GPIO_PULLUP;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(bus->IIC_SDA_PORT,&GPIO_InitStructure);
}
// SDA输出模式
void SDA_Output_Mode(iic_bus_t* bus)
{
GPIO_InitTypeDef GPIO_InitStructure = {0};
GPIO_InitStructure.Pin = bus->IIC_SDA_PIN;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;//下次不要这样搞,还是用MX来配置后复制,不然容易错
GPIO_InitStructure.Pull = GPIO_NOPULL;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(bus->IIC_SDA_PORT,&GPIO_InitStructure);
}
//SDA输出一个位
void SDA_Output(iic_bus_t *bus, uint8_t val)
{
if(val)
HAL_GPIO_WritePin(bus->IIC_SDA_PORT,bus->IIC_SDA_PIN,GPIO_PIN_SET);
else
HAL_GPIO_WritePin(bus->IIC_SDA_PORT,bus->IIC_SDA_PIN,GPIO_PIN_RESET);
}
//SCL输出一个位
void SCL_Output(iic_bus_t *bus, uint8_t val)
{
if(val)
HAL_GPIO_WritePin(bus->IIC_SCL_PORT,bus->IIC_SCL_PIN,GPIO_PIN_SET);
else
HAL_GPIO_WritePin(bus->IIC_SCL_PORT,bus->IIC_SCL_PIN,GPIO_PIN_RESET);
}
//SDA输入一位 读一位
uint8_t SDA_Input(iic_bus_t *bus)
{
//我觉得这里可以直接返回读到得值 不用判断了
return (uint8_t)HAL_GPIO_ReadPin(bus->IIC_SDA_PORT,bus->IIC_SDA_PIN) ;
}
//从这个严格定义的角度来说,最后将 SCL 拉低这一步不属于起始信号本身的部分
//将 SCL 拉低是为了按照 I2C 通信的正确时序,
//把总线状态调整到适合后续数据传输的起始位置
void IICStart(iic_bus_t *bus)
{
//当 SCL 为高电平时,SDA 从高电平向低电平跳变
SDA_Output(bus,1);
delay_us(2);
SCL_Output(bus,1);
delay_us(1);
SDA_Output(bus,0);
delay_us(1);
SCL_Output(bus,0);
delay_us(1); //
}
//严格来说,I2C 协议中定义的停止信号是当 SCL 为高电平时,
//SDA 从低电平向高电平跳变
//为了确保正确的时序过渡:在 I2C 通信中,
//数据的传输和各种信号的产生都是按照严格的时序进行的
void IICStop(iic_bus_t *bus)
{
//当 SCL 为高电平时,SDA 从低电平向高电平跳变
SCL_Output(bus,0);
delay_us(2);
SDA_Output(bus,0);
delay_us(1);
SCL_Output(bus,1);
delay_us(1); //释放总线
SDA_Output(bus,1);
delay_us(1);
}
uint8_t IICWaitAck(iic_bus_t *bus)
{
uint8_t cErrTime = 5;
SDA_Input_Mode(bus);//将数据线变成输入模式 接受从机的应答
SCL_Output(bus,1);// 拉高时钟线
while(SDA_Input(bus))//读到的数一直是1的话就是没有应答
{
cErrTime--;//读5次都没有结果就是没有应答 表示接受结束
delay_us(1);
if(0 == cErrTime)
{
SDA_Output_Mode(bus);
IICStop(bus);
return ERROR;
}
}
//如果给了应答 就是读到了0
SDA_Output_Mode(bus);//转换成输出模式
SCL_Output(bus,0);//拉低时钟线 为下一个时序做准备
delay_us(2);
return SUCCESS;
}
void IICSendAck(iic_bus_t *bus)
{
SDA_Output(bus,0);//我记得是低电平放上数据
delay_us(1);
SCL_Output(bus,1);
delay_us(1);
SCL_Output(bus,0);
delay_us(2);
}
void IICSendNotAck(iic_bus_t *bus)
{
//我记得是低电平放上数据等时钟线高电平稳定采样 且此时不允许修改数据
SDA_Output(bus,1);
delay_us(1);
SCL_Output(bus,1);
delay_us(1);
SCL_Output(bus,0);
delay_us(2);
}
void IICSendByte(iic_bus_t *bus, uint8_t cSendByte)
{
uint8_t i;
for(i=0;i<8;i++)
{
SCL_Output(bus,0);
delay_us(2);
SDA_Output(bus,((cSendByte >> (7 - i)) & 0x01));
delay_us(1);
SCL_Output(bus,1);
delay_us(1);
}
SCL_Output(bus,0);
delay_us(2);
}
uint8_t IICReceiveByte(iic_bus_t *bus)
{
uint8_t i, cR_Byte=0;
uint8_t bit=0;
SDA_Input_Mode(bus);//接受字节 改成输入模式
for(i=0;i<8;i++)
{
SCL_Output(bus,0); //拉低时钟线 等待从机放数据
delay_us(2);
SCL_Output(bus,1);//拉高时钟线 可以采样了
delay_us(1);
bit = SDA_Input(bus);
cR_Byte |= bit << (7-i);
}
SCL_Output(bus,0);//拉低时钟线
SDA_Output_Mode(bus);//改变为输出模式 只有读数据才是输入模式
return cR_Byte;
}
void IICInit(iic_bus_t *bus)
{
GPIO_InitTypeDef GPIO_InitStructure = {0};
//bus->CLK_ENABLE();
GPIO_InitStructure.Pin = bus->IIC_SDA_PIN ;
//上面给的是开漏 这里变成推挽搞不懂 ,推挽就不用变输入模式了
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStructure.Pull = GPIO_NOPULL;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(bus->IIC_SDA_PORT, &GPIO_InitStructure);
GPIO_InitStructure.Pin = bus->IIC_SCL_PIN ;
HAL_GPIO_Init(bus->IIC_SCL_PORT, &GPIO_InitStructure);
}
//返回1表示失败
uint8_t IIC_Write_One_Byte(iic_bus_t *bus, uint8_t daddr,uint8_t reg,uint8_t data)
{
IICStart(bus);//开始信号
IICSendByte(bus,(daddr << 1));//写从机地址
if(IICWaitAck(bus)) //无应答停止传输
{
IICStop(bus);
//printf("%d\r\n",IICWaitAck(bus)); 第一次初始化器件的时候会非应答
return 1;
}
IICSendByte(bus,reg);
IICWaitAck(bus);
IICSendByte(bus,data);
IICWaitAck(bus);
IICStop(bus);
delay_us(1);
return 0;
}
uint8_t IIC_Write_Multi_Byte(iic_bus_t *bus, uint8_t daddr,uint8_t reg,uint8_t length,uint8_t buff[])
{
uint8_t i;
uint8_t* dataPtr = buff;
IICStart(bus);//开始信号
IICSendByte(bus,(daddr << 1));//写从机地址
if(IICWaitAck(bus)) //无应答停止传输
{
IICStop(bus);
return 1;
}
IICSendByte(bus,reg);
IICWaitAck(bus);
for(i=0;i<length;i++)
{
//IICSendByte(bus,buff[i]);
IICSendByte(bus,*dataPtr++);//利用指针指向缓存区 先解引用给首个元素赋值 再指向下一个字节
IICWaitAck(bus);
}
IICStop(bus);
delay_us(1);
return 0;
}
uint8_t IIC_Read_One_Byte(iic_bus_t *bus, uint8_t daddr,uint8_t reg)
{
uint8_t cR_Byte=0;
IICStart(bus);//开始信号
IICSendByte(bus,(daddr << 1));//写从机地址
IICWaitAck(bus);
IICSendByte(bus,reg);
IICWaitAck(bus);
IICStart(bus);//开始信号
IICSendByte(bus,(daddr << 1)| 1);//读操作
IICWaitAck(bus);
cR_Byte = IICReceiveByte(bus);
IICSendNotAck(bus);
IICStop(bus);
return cR_Byte;
}
uint8_t IIC_Read_Multi_Byte(iic_bus_t *bus, uint8_t daddr, uint8_t reg, uint8_t length, uint8_t buff[])
{
uint8_t i;
uint8_t* dataPtr = buff;
IICStart(bus);//开始信号
IICSendByte(bus,(daddr << 1)&0xFE );//写从机地址
IICWaitAck(bus);
IICSendByte(bus,reg);
IICWaitAck(bus);
IICStart(bus);//开始信号
IICSendByte(bus,(daddr << 1)| 1);//读操作
IICWaitAck(bus);
for(i=0;i<length;i++)
{
//buff[i] = IICReceiveByte(bus);
*dataPtr++ = IICReceiveByte(bus);//右++ 先操作后自加
if(i<length-1)
IICSendAck(bus);
else
IICSendNotAck(bus);
}
IICStop(bus);
return 0;
}
原文地址:https://blog.csdn.net/NEWEVA__zzera22/article/details/143737136
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!