IIC驱动代码_2

Tutorial: 电子电路 Category: 嵌入式 Published: 2026-08-29 08:09:36 Views: 0 Likes: 0 Comments: 0

066.9.5 IIC驱动代码(2)

/* ==================== main.c ==================== */
#include "reg52.h"
#include <intrins.h>
#include "delay.h"
#include "main.h"
#include "uart.h"

uchar recv;

void main()
{
//	UART_Init();
	while(1)
  {

		Delay_xms(1000);
  }
}


/* ==================== main.h ==================== */
#ifndef _MAIN_H_
#define _MAIN_H_

#include "reg52.h"

typedef unsigned char uchar;
typedef unsigned int uint;

extern uchar recv;

sbit LED1 = P1^0;
sbit LED2 = P1^1;
sbit LED3 = P1^2;
sbit LED4 = P1^3;
sbit BEEP = P1^6;
sbit JDQ1 = P2^0;

sbit IIC_SCL = P1^5;
sbit IIC_SDA = P1^4;

#endif



/* ==================== Delay\delay.c ==================== */
#include "delay.h"
#include <intrins.h>

void Delay_xms(uint xms)	//@11.0592MHz
{
	uchar data i, j;
	while(xms)
	{
		_nop_();
		i = 2;
		j = 199;
		do
		{
			while (--j);
		} while (--i);
    xms--;		
	}
}

void Delay5us(void)	//@11.0592MHz
{
}

/* ==================== Delay\delay.h ==================== */
#ifndef _DELAY_H_
#define _DELAY_H_
#include "main.h"

void Delay_xms(uint xms);
void Delay5us(void);

#endif

/* ==================== IIC\iic.c ==================== */
#include "iic.h"
#include "main.h"
#include "delay.h"

void IIC_Start(void)
{
	IIC_SCL = 1;
	IIC_SDA = 1;
	Delay5us();
	IIC_SDA = 0;
	Delay5us();
	IIC_SCL = 0;
}

void IIC_Stop(void)
{
	IIC_SCL = 1;
	IIC_SDA = 0;
	Delay5us();
	IIC_SDA = 1;
}

void IIC_Send_Byte(unsigned char IIC_Byte)
{
	char i;
	for(i = 0; i<8; i++)
	{		
		IIC_SDA = IIC_Byte & (0x80 >> i);
		IIC_SCL = 1;
		Delay5us();
		IIC_SCL = 0;
		Delay5us();		
	}
}

unsigned char IIC_Recv_Ack(void)
{
	unsigned char ack;
	IIC_SDA = 1;
	IIC_SCL = 1;
	Delay5us();
	ack = IIC_SDA;
	IIC_SCL = 0;
	Delay5us();	
	
	return ack;
}

/* ==================== IIC\iic.h ==================== */
#ifndef _IIC_H_
#define _IIC_H_

void IIC_Start(void);
void IIC_Stop(void);
void IIC_Send_Byte(unsigned char IIC_Byte);
unsigned char IIC_Recv_Ack(void);

#endif



/* ==================== Uart\uart.c ==================== */
#include "uart.h"
#include "main.h"

//串口发送一个字节
void UART_Send_Byte(uchar send_byte)
{
	SBUF = send_byte;
	while(!TI);
	TI=0;
}

//串口发送字符串
void UART_Send_Str(uchar *send_str)
{	
	while(*send_str != '\0') //判断字符串结束符,如果识别到‘\0',则跳出while,结束发送
	{
		UART_Send_Byte(*send_str++);	//本质是一个字节一个字节的发送,
		//发送完一个字节,地址+1,等待发送下一个字节,直到检测到字符串结束符为止
	}	
} 

void UART_Init(void)
{
	SCON = 0x50; 	//串口配成工作方式1
	PCON &= 0x7F; //波特率不加倍
	TMOD &= 0x0f;	
	TMOD |= 0x20;	//定时器1,模式二,自动重装初值,串口按照一定速率发数据,需要用的定时器
	TH1 = 0xFD;	
	TL1 = 0xFD; //@11.0592MH时钟,9600比特率定时器初值
	TR1 = 1; //定时器开始运行   
	ES = 1;  //串口中断打开
	EA=1;					//总中断打开
	recv = 0;
}

/* ==================== Uart\uart.h ==================== */
#ifndef _UART_H_
#define _UART_H_
#include "main.h"

void UART_Send_Byte(uchar send_byte);
void UART_Send_Str(uchar *send_str);
void UART_Init(void);

#endif

一、任务信息资源导图

本节只解决两个总线条件:START 和 STOP。观察任何 I²C 波形时,先认准:

  • SCL 是时钟线
  • SDA 是双向数据线
  • SCL 高电平期间的 SDA 跳变具有特殊含义;
  • SCL 低电平期间改变 SDA,通常是在准备下一位普通数据。

课程画面中波形的颜色只适用于当时的软件显示,不能把“黄线”“蓝线”当成固定标准。逻辑分析仪通道颜色可以自由设置,应依据通道标签和接线判断。

二、IIC总线时序分析

空闲、起始、数据阶段和停止之间的关系如下:

阶段       空闲          START            数据阶段             STOP       空闲
SCL   ‾‾‾‾‾‾‾‾‾‾    ‾‾‾‾‾\__    _/‾\_/‾\_/‾\_       __/‾‾‾‾‾‾‾‾‾
SDA   ‾‾‾‾‾‾‾\___    1 → 0       <每位在高电平稳定>   ____/‾‾‾‾‾‾‾
                ↑                                         ↑
         SCL高时下降                                 SCL高时上升

START 后,主机通常把 SCL 拉低,以便在低电平阶段准备地址的最高位。STOP 后,两线都应被释放,由上拉电阻保持高电平。

软件模拟时不要只看每根线各自的变化,还要把两根线放在同一时间轴上。相同的 SDA 下降沿,如果 SCL 为高就是 START,如果 SCL 为低只是普通数据准备。

三、IIC驱动代码实现

完整配套工程位于标题下方。引脚在 main.h 中用 C51 的 sbit 语法定义,而不是普通标准 C 的 #define:

sbit IIC_SCL = P1^5;
sbit IIC_SDA = P1^4;

sbit 和 P1^5 是 Keil C51 扩展语法,移植到 GCC 或其他 MCU 时需要改成对应的 GPIO 操作。

3.1 IC_Start函数

这里的函数名是 IIC_Start()。配套实现为:

void IIC_Start(void)
{
    IIC_SCL = 1;
    IIC_SDA = 1;
    Delay5us();
    IIC_SDA = 0;
    Delay5us();
    IIC_SCL = 0;
}

逐句对应如下:

语句SCLSDA含义
IIC_SCL=11未确定释放时钟线
IIC_SDA=111两线进入空闲状态
Delay5us()11满足总线空闲/建立时间
IIC_SDA=010在 SCL 高时产生 START
Delay5us()10保持起始条件
IIC_SCL=000进入可准备首个数据位的低电平阶段
代码时间 →
SCL:‾‾‾‾‾‾‾‾‾‾‾\____
SDA:‾‾‾‾‾\____________
              ↑ START

延时和 100 kHz 的关系需要准确理解。 100 kHz 对应 10 µs 周期,但 START 本身不是一个完整、周期性的 SCL 脉冲,不能要求 IIC_Start() 每两句恰好拼成 10 µs。规范规定的是 SCL 高低时间、起始保持时间、数据建立时间等最小值;满足最小值后,频率可以更低。

配套 Delay5us() 的函数体是空的:

void Delay5us(void)  //@11.0592MHz
{
}

它利用函数调用和返回所消耗的指令时间近似延时。这种方式高度依赖 11.0592 MHz 晶振、STC89 的机器周期、C51 编译器、优化等级以及函数是否被内联,不能把函数名当成精确的 5 µs。改变晶振或编译设置后,应重新计算并用示波器/逻辑分析仪验证。

普通 C 语句执行时间也不是固定“每行约 5 µs”。一行源码可能编译成零条、一条或多条机器指令;精确时间必须看反汇编和芯片机器周期。

3.2 IC_Stop函数

标准 STOP 的目标是:先保证 SDA 为低,在 SCL 上升并稳定为高后,让 SDA 由低变高。

推荐的软件顺序为:

void IIC_Stop(void)
{
    IIC_SCL = 0;       // 先进入允许改变数据的阶段
    IIC_SDA = 0;       // 准备SDA低电平
    Delay5us();

    IIC_SCL = 1;       // 释放SCL为高
    Delay5us();

    IIC_SDA = 1;       // SCL高时释放SDA,形成STOP
    Delay5us();
}

对应波形为:

SCL:______/‾‾‾‾‾‾‾‾‾
SDA:___________/‾‾‾‾‾
                 ↑ STOP

配套源码的顺序是先 IIC_SCL=1,再 IIC_SDA=0。若进入函数时 SDA 已经为 0,它能得到预期 STOP;但若 SDA 为 1,就会在 SCL 高时先出现一次下降沿,产生额外 START,然后再产生 STOP。因此总线函数不应过度依赖调用前的隐含 SDA 状态,先在 SCL 低电平阶段准备 SDA 更稳妥。

STOP 完成后,软件写 1 的本质应是释放线路,高电平由上拉电阻建立:

VPU
 │
 Rpull-up
 │
 ├──────── SDA/SCL
 │
 └──[器件开漏晶体管]──GND
       关断:线路被上拉为高
       导通:线路被拉为低

空闲为高不是“没有任何电流、电压”,而是没有器件主动拉低。若线路短路、上拉缺失、上拉电压错误或某器件一直拉低,总线就无法回到空闲状态。排障时可先测 STOP 后两线是否都为高。