IIC驱动代码_2
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;
}
逐句对应如下:
| 语句 | SCL | SDA | 含义 |
|---|---|---|---|
| IIC_SCL=1 | 1 | 未确定 | 释放时钟线 |
| IIC_SDA=1 | 1 | 1 | 两线进入空闲状态 |
| Delay5us() | 1 | 1 | 满足总线空闲/建立时间 |
| IIC_SDA=0 | 1 | 0 | 在 SCL 高时产生 START |
| Delay5us() | 1 | 0 | 保持起始条件 |
| IIC_SCL=0 | 0 | 0 | 进入可准备首个数据位的低电平阶段 |
代码时间 →
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 后两线是否都为高。