这篇讲解软件IIC和一些基础,硬件IIC在下一篇章,添加链接描述
https://blog.csdn.net/qq_46187594/article/details/141643049
第二章-IIC协议
2.1-协议概述
说IIC特点
IIC时序图例子
或者仿真图
2.2-软件模式IIC
2.2.1-STM32模拟I2C
SDA必须设置开漏输出+硬件上拉电阻
SCL可以开漏输出+硬件上拉电阻 也可以推挽输出
如何通过STM32 io 模拟出I2C时序
我们这里还使用第0章的工程
设置上拉输入开漏输出、然后硬件也要上拉电阻
防止优化,我们要关闭优化选项
IIC.c 文件
#include "MyApp.h"
//讲IIC通信引脚设置为开漏上拉输出,引脚还要连接上拉电阻
//SDA电平设置
#define SDA_High HAL_GPIO_WritePin(GY30_SDA_GPIO_Port,GY30_SDA_Pin,GPIO_PIN_SET)
#define SDA_Low HAL_GPIO_WritePin(GY30_SDA_GPIO_Port,GY30_SDA_Pin,GPIO_PIN_RESET)
//SCL电平设置
#define SCL_Hige HAL_GPIO_WritePin(GY30_SCL_GPIO_Port,GY30_SCL_Pin,GPIO_PIN_SET)
#define SCL_Low HAL_GPIO_WritePin(GY30_SCL_GPIO_Port,GY30_SCL_Pin,GPIO_PIN_RESET)
//STM32在开漏输出模式可以直接读GPIO 电平
#define SDA_Read HAL_GPIO_ReadPin(GY30_SDA_GPIO_Port, GY30_SDA_Pin)
/* Private variables----------------------------------------------------------*/
/* Public variables-----------------------------------------------------------*/
/* Private function prototypes------------------------------------------------*/
/*******************
* @brief 这是us 级延时函数
* @param
* @return
*
*******************/
void I2C_Delay_us(uint8_t us)
{
uint8_t i = 0;
while(us --)
{
for(i = 0;i<7;i++);
}
}
/*******************
* @brief 起始信号S
* @param
* @return
*
*******************/
void I2C_Start(void)
{
SCL_Hige;
SDA_High;
I2C_Delay_us(5);
SDA_Low;
I2C_Delay_us(5);
SCL_Low;
}
/*******************
* @brief 停止信号
* @param
* @return
*
*******************/
void I2C_Stop(void)
{
SDA_Low;
I2C_Delay_us(5);
SCL_Hige;
I2C_Delay_us(5);
SDA_High;
}
/*******************
* @brief 读是否有应答
* @param
* @return 1-没有收到应答位,0-收到应答位
*
*******************/
uint8_t I2C_Read_Ack(void)
{
uint8_t AckTime = 0;//超时计数
SCL_Hige;//先拉高SCL、SCL为高的时候检测SDA
I2C_Delay_us(5);
while(SDA_Read){
if(++AckTime > 250){//超时判断 如果长时间没有应答
I2C_Stop();//发送停止信号
return 1;//退出
}
}
SCL_Low;
I2C_Delay_us(4);
return 0; //
}
/*******************
* @brief 写一个字节数据
* @param 要写的一个字节
* @return
*
*******************/
void I2C_Write_Byte(uint8_t Data)
{
SCL_Low;//设置SCL低电平 ,允许SDA改变
I2C_Delay_us(4);
for(uint8_t i = 0;i<8;i++){
if((Data << i) & 0x80) SDA_High;//循环发送,先发送高位 ,然后发低位
else SDA_Low;
SCL_Hige;//设置SCL为高,后面要保持SDA不变
I2C_Delay_us(4);
SCL_Low;
I2C_Delay_us(4);
}
}
/*******************
* @brief 读一个字节数据
* @param
* @return 返回读的数据
*
*******************/
uint8_t I2C_Read_Data(void)
{
uint8_t Data;//暂存读的数据
for(uint8_t i=0;i<8;i++){
SCL_Hige;//设置SCL高,延时后SDA稳定 从SDA读数据
I2C_Delay_us(4);
Data <<= 1; //收的数据先存低位,然后用这个左移8次
if(SDA_Read){//如果SDA为高电平就置位
Data |= 0x01;//就置为最后一位
}
SCL_Low;//拉低SCL,期间SDA可能变化
I2C_Delay_us(4);
}
return Data;//返回读的数据
}
/*******************
* @brief 发送应答或者非应答信号
* @param 1- 非应答、0- 应答
* @return 无
*
*******************/
void Sende_Ack(uint8_t Ack)
{
if(Ack)
SDA_High;//如果输入1 SDA设置为高
else
SDA_Low;
SCL_Hige;//SCL设置为高保持4毫秒
I2C_Delay_us(4);
SCL_Low;//然后释放掉
I2C_Delay_us(4);
}
/********************************************************
End Of File
********************************************************/
I2C.h
#ifndef __I2C_H__
#define __I2C_H__
#include "MyApp.h"
/* extern variables-----------------------------------------------------------*/
/* extern function prototypes-------------------------------------------------*/
void I2C_Delay_us(uint8_t us);
/*******************
* @brief 起始信号S
* @param
* @return
*
*******************/
void I2C_Start(void);
/*******************
* @brief 停止信号
* @param
* @return
*
*******************/
void I2C_Stop(void);
/*******************
* @brief 读是否有应答
* @param
* @return 1-没有收到应答位,0-收到应答位
*
*******************/
uint8_t I2C_Read_Ack(void);
/*******************
* @brief 写一个字节数据
* @param 要写的一个字节
* @return
*
*******************/
void I2C_Write_Byte(uint8_t Data);
/*******************
* @brief 读一个字节数据
* @param
* @return 返回读的数据
*
*******************/
uint8_t I2C_Read_Data(void);
/*******************
* @brief 发送应答或者非应答信号
* @param 1- 非应答、0- 应答
* @return 无
*
*******************/
void Sende_Ack(uint8_t Ack);
#endif
/********************************************************
End Of File
********************************************************/
2.2.2模拟I2C驱动传感器
光照传感器
GY30.c
#include "MyApp.h"
#define GY30 0xB8
//ADDR接高地址为OxB8(1011100)然后写就是最后一位0 就是0xB8 如果读就最后一位1 就是0xB9
//ADDR接低地址为0x46(0100011)然后写操作最后一位是0 就是0x46 如果是读最后一位1 就是0x47
/* Private variables----------------------------------------------------------*/
/* Public variables-----------------------------------------------------------*/
uint8_t DATA[2];
/* Private function prototypes------------------------------------------------*/
/*******************
* @brief 写一个Byte
* @param
* @return
*
*******************/
void GY30_Write_Byte(uint8_t Byte){
I2C_Start();//主机发送起始信号
I2C_Write_Byte(GY30|0x00);//发送地址+0表示写 0xB8
I2C_Read_Ack();//主机等待收到应答信号
I2C_Write_Byte(Byte);//主机写数据过去
I2C_Read_Ack();//主机等待从机的应答信号
I2C_Stop();//主机发送停止信号
}
/*******************
* @brief 读光照数据
* @param
* @return
*
*******************/
void GY30_Read_Byte(void){
I2C_Start();//发送起始信号
I2C_Write_Byte(GY30|0x01);//0xB9发送过去,表示地址0XB8和1(读数据)
I2C_Read_Ack();//主机等待收到应答信号
for(uint8_t i = 0;i<2;i++){
DATA[i] = I2C_Read_Data();//读数据保持
if(i == 2) ///觉得这里有问题,
I2C_Sende_Ack(1);//如果读两个数据 主机发送不应答
else
I2C_Sende_Ack(0);
}
I2C_Stop();//主机发送停止信号
HAL_Delay(5);//延时5ms 时间
}
/********************************************************
End Of File
********************************************************/
GY30.h
#ifndef __GY30_H__
#define __GY30_H__
#include "MyApp.h"
/* extern variables-----------------------------------------------------------*/
/* extern function prototypes-------------------------------------------------*/
/*******************
* @brief 写一个Byte
* @param
* @return
*
*******************/
void GY30_Write_Byte(uint8_t Byte);
/*******************
* @brief 读光照数据
* @param
* @return
*
*******************/
void GY30_Read_Byte(void);
#endif
补充一个点
添加一下串口的初始化
增加映射
/**
* @brief 重定向printf (重定向fputc),
使用时候记得勾选上魔法棒->Target->UseMicro LIB
可能需要在C文件加typedef struct __FILE FILE;
包含这个文件#include "stdio.h"
* @param
* @return
*/
int fputc(int ch,FILE *stream)
{
HAL_UART_Transmit(&huart1,( uint8_t *)&ch,1,0xFFFF);
return ch;
}
勾选微库
调试之后发现无法读出光照数据、光照高位和低位都是0 ,然后我们猜测是延时函数不准确,下面测试延时函数、
把延时函数调整一下
这里接接好传感器还是不能读出数据
我们烧录一下历程,然后看一下例程的波形
三个部分的波形
第一部分波形
波形就是:起始位+从机地址(0xB8)+从机ACK+通电指令(0X01)+从机ACK+SP停止
然后下一个:起始位+从机地址(0XB8)+从机ACK+通电指令(0X10)+从机ACK+SP停止
第二部分
起始信号+0xB9(地址0xB9+1这个表示读)+光照高八位+从机ACK+光照低八位+从机ACK
我们把没有问题的程序数据和有问题的都采集到逻辑分析仪上
这是有问题的波形
这是没有问题的波形
2.2.3-切换状态解决问题
还是没有发现问题,我们把IO设置推挽输出,然后在需要设置输入的时候再切换模式
设置GPIO状态
代码增加了SDA状态改变,我把所有代码贴出来了
iic.c
#include "MyApp.h"
//讲IIC通信引脚设置为开漏上拉输出,引脚还要连接上拉电阻
//SDA电平设置
#define SDA_High HAL_GPIO_WritePin(GY30_SDA_GPIO_Port,GY30_SDA_Pin,GPIO_PIN_SET)
#define SDA_Low HAL_GPIO_WritePin(GY30_SDA_GPIO_Port,GY30_SDA_Pin,GPIO_PIN_RESET)
//SCL电平设置
#define SCL_Hige HAL_GPIO_WritePin(GY30_SCL_GPIO_Port,GY30_SCL_Pin,GPIO_PIN_SET)
#define SCL_Low HAL_GPIO_WritePin(GY30_SCL_GPIO_Port,GY30_SCL_Pin,GPIO_PIN_RESET)
//STM32在开漏输出模式可以直接读GPIO 电平
#define SDA_Read HAL_GPIO_ReadPin(GY30_SDA_GPIO_Port, GY30_SDA_Pin)
/* Private variables----------------------------------------------------------*/
/* Public variables-----------------------------------------------------------*/
/* Private function prototypes------------------------------------------------*/
//软件先设置GPIO为推挽输出、然后SDA读的时候设置
/*******************
* @brief 设置IIC的模式
* @param
* @return
*
*******************/
void I2C_SDA_Mode(uint8_t Addr)
{
GPIO_InitTypeDef GPIO_InitStruct;
if(Addr){ // 1 设置输出模式
GPIO_InitStruct.Pin = GY30_SDA_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GY30_SDA_GPIO_Port, &GPIO_InitStruct);
}
else{//0 设置输入模式
GPIO_InitStruct.Pin = GY30_SDA_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GY30_SDA_GPIO_Port, &GPIO_InitStruct);
}
}
/*******************
* @brief 这是us 级延时函数
* @param
* @return
*
*******************/
void I2C_Delay_us(uint8_t us)
{
uint8_t i = 0;
while(us --)
{
for(i = 0;i<8;i++);
}
}
/*******************
* @brief 起始信号S
* @param
* @return
*
*******************/
void I2C_Start(void)
{
I2C_SDA_Mode(OUT);
SCL_Hige;
SDA_High;
I2C_Delay_us(5);
SDA_Low;
I2C_Delay_us(5);
SCL_Low;
}
/*******************
* @brief 停止信号
* @param
* @return
*
*******************/
void I2C_Stop(void)
{
I2C_SDA_Mode(OUT);
SDA_Low;
// I2C_Delay_us(5);//调整
SCL_Hige;
I2C_Delay_us(5);
SDA_High;
I2C_Delay_us(5);//调整
}
/*******************
* @brief 读是否有应答
* @param
* @return 1-没有收到应答位,0-收到应答位
*
*******************/
uint8_t I2C_Read_Ack(void)
{
uint8_t AckTime ;//超时计数
I2C_SDA_Mode(INPUT);//设置输入模式
SCL_Hige;//先拉高SCL、SCL为高的时候检测SDA
I2C_Delay_us(4);
while(SDA_Read == GPIO_PIN_SET){
if(++AckTime > 250){//超时判断 如果长时间没有应答
I2C_Stop();//发送停止信号
return 1;//退出
}
}
SCL_Low;
I2C_Delay_us(4);
return 0; //
}
/*******************
* @brief 写一个字节数据
* @param 要写的一个字节
* @return
*
*******************/
void I2C_Write_Byte(uint8_t Data)
{
SCL_Low;//设置SCL低电平 ,允许SDA改变
I2C_Delay_us(4);
for(uint8_t i = 0;i<8;i++){
I2C_SDA_Mode(OUT);//设置输出
if((Data << i) & 0x80) SDA_High;//循环发送,先发送高位 ,然后发低位
else SDA_Low;
SCL_Hige;//设置SCL为高,后面要保持SDA不变
I2C_Delay_us(4);
SCL_Low;
I2C_Delay_us(4);
}
}
/*******************
* @brief 读一个字节数据
* @param
* @return 返回读的数据
*
*******************/
uint8_t I2C_Read_Data(void)
{
uint8_t Data;//暂存读的数据
for(uint8_t i=0;i<8;i++){
I2C_SDA_Mode(INPUT);//设置输入
SCL_Hige;//设置SCL高,延时后SDA稳定 从SDA读数据
I2C_Delay_us(4);
Data <<= 1; //收的数据先存低位,然后用这个左移8次
if(SDA_Read == GPIO_PIN_SET){//如果SDA为高电平就置位
Data |= 0x01;//就置为最后一位
}
SCL_Low;//拉低SCL,期间SDA可能变化
I2C_Delay_us(4);
}
return Data;//返回读的数据
}
/*******************
* @brief 发送应答或者非应答信号
* @param 1- 非应答、0- 应答
* @return 无
*
*******************/
void I2C_Sende_Ack(uint8_t Ack)
{
I2C_SDA_Mode(OUT);//设置输出
if(Ack)
SDA_High;//如果输入1 SDA设置为高
else
SDA_Low;
SCL_Hige;//SCL设置为高保持4毫秒
I2C_Delay_us(4);
SCL_Low;//然后释放掉
I2C_Delay_us(4);
}
/********************************************************
End Of File
********************************************************/
iic.h
#ifndef __I2C_H__
#define __I2C_H__
#include "MyApp.h"
/* extern variables-----------------------------------------------------------*/
#define OUT 1
#define INPUT 0
/* extern function prototypes-------------------------------------------------*/
void I2C_Delay_us(uint8_t us);
/*******************
* @brief 起始信号S
* @param
* @return
*
*******************/
void I2C_Start(void);
/*******************
* @brief 停止信号
* @param
* @return
*
*******************/
void I2C_Stop(void);
/*******************
* @brief 读是否有应答
* @param
* @return 1-没有收到应答位,0-收到应答位
*
*******************/
uint8_t I2C_Read_Ack(void);
/*******************
* @brief 写一个字节数据
* @param 要写的一个字节
* @return
*
*******************/
void I2C_Write_Byte(uint8_t Data);
/*******************
* @brief 读一个字节数据
* @param
* @return 返回读的数据
*
*******************/
uint8_t I2C_Read_Data(void);
/*******************
* @brief 发送应答或者非应答信号
* @param 1- 非应答、0- 应答
* @return 无
*
*******************/
void I2C_Sende_Ack(uint8_t Ack);
#endif
/********************************************************
End Of File
********************************************************/
GY30.c
#include "MyApp.h"
//#define GY30 0XB8
#define GY30 0X46
//ADDR接高地址为OxB8(1011100)然后写就是最后一位0 就是0xB8 如果读就最后一位1 就是0xB9
//ADDR接低地址为0x46(0100011)然后写操作最后一位是0 就是0x46 如果是读最后一位1 就是0x47
/* Private variables----------------------------------------------------------*/
/* Public variables-----------------------------------------------------------*/
uint8_t DATA[2];
/* Private function prototypes------------------------------------------------*/
/*******************
* @brief 写一个Byte
* @param
* @return
*
*******************/
void GY30_Write_Byte(uint8_t Byte){
I2C_Start();//主机发送起始信号
I2C_Write_Byte(GY30|0x00);//发送地址+0表示写 0xB8
I2C_Read_Ack();//主机等待收到应答信号
I2C_Write_Byte(Byte);//主机写数据过去
I2C_Read_Ack();//主机等待从机的应答信号
I2C_Stop();//主机发送停止信号
}
/*******************
* @brief 读光照数据
* @param
* @return
*
*******************/
void GY30_Read_Byte(void){
I2C_Start();//发送起始信号
I2C_Write_Byte(GY30|0x01);//0xB9发送过去,表示地址0XB8和1(读数据)
I2C_Read_Ack();//主机等待收到应答信号
for(uint8_t i = 0;i<2;i++){
DATA[i] = I2C_Read_Data();//读数据保持
if(i == 2) ///觉得这里有问题,
I2C_Sende_Ack(1);//如果读两个数据 主机发送不应答
else
I2C_Sende_Ack(0);
}
I2C_Stop();//主机发送停止信号
HAL_Delay(5);//延时5ms 时间
}
/********************************************************
End Of File
********************************************************/
GY30.h
#ifndef __GY30_H__
#define __GY30_H__
#include "MyApp.h"
/* extern variables-----------------------------------------------------------*/
/* extern function prototypes-------------------------------------------------*/
/*******************
* @brief 写一个Byte
* @param
* @return
*
*******************/
void GY30_Write_Byte(uint8_t Byte);
/*******************
* @brief 读光照数据
* @param
* @return
*
*******************/
void GY30_Read_Byte(void);
#endif
然后主函数还是那样的
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
//这个测试设置两个都是推挽,然后SDA读的时候设置上拉输入
GY30_Write_Byte(0x01);//通电指令
GY30_Write_Byte(0x10);//连续高分辨模式120ms
HAL_Delay(120);//延时
GY30_Read_Byte();//读数据
printf("数值:%d\r\n%d\r\n",DATA[0],DATA[1]);
HAL_Delay(500);
HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);
// DATA[0] //这就是高位和低位了
// DATA[1]
}
2.3-硬件I2C
硬件IIC我们下一篇再讲解