为什么手搓i2c
本来合宙官方的固件中已经包含了硬件i2c和模拟i2c两个模块,完全没必要再自己写一个。但是i2c是一个很重要的通信协议,我想通过自己手搓的方式去更加的熟悉这个协议。然后呢,百度发现网上没有关于合宙手搓模拟i2c的例子,毕竟官方都做好了,直接用就好了,很少有人再自己折腾。
i2c需要实现的功能
- 启动
- 停止
- 写入数据
- 读取数据
- 发送ACK/NACK
- 接受ACK
这部分希望能看得懂,不懂可以去百度i2c协议,毕竟不是面对i2c小白。
i2c示例代码
代码里边加入了一些测试用的代码,主要是查看i2c协议是否是正常工作的。因为没有逻辑分析仪,只能读取引脚状态然后打印出来。
local I2c = {}
-- 软件模型i2c
sys = require("sys")
-- 引脚变量,下面好操作一点
pin_sda = pin.PA4
pin_sck = pin.PA1
-- 定义引脚功能,这样对引脚的控制方便一点
function sck_0() gpio.set(pin_sck,0) end
function sck_1() gpio.set(pin_sck,1) end
function sda_0() gpio.set(pin_sda,0) end
function sda_1() gpio.set(pin_sda,1) end
function sda_read() return gpio.get(pin_sda) end -- 读取sda引脚
-- 延时函数
function Delay()
sys.wait(2)
end
-- sda引脚输入或输出模式
-- 在开始,发送数据,ACK的时候,引脚需要设置为输出模式。
-- 读取的时候需要设置为输入模式
function pin_SdaMode(mode)
if mode == 1 then
Sda = gpio.setup(pin_sda,nil,gpio.PULLDOWN) -- 输入模式
else
Sda = gpio.setup(pin_sda,1,gpio.PULLUP) -- 输出模式
end
end
-- 初始化引脚
function I2c.GPIO_init()
Sck = gpio.setup(pin_sck,1,gpio.PULLUP)
pin_SdaMode(0)
-- I2c.io() -- 查看引脚状态,测试用
end
-- i2c开始
function I2c.start()
pin_SdaMode(0) -- 输出模式
sda_1()
sck_1()
Delay()
sda_0() Delay()
sck_0() Delay()
end
-- i2c结束
function I2c.stop()
pin_SdaMode(0)
sda_0()
sck_1() Delay()
sda_1() Delay()
end
-- ACK响应
function ACK()
pin_SdaMode(0)
sda_0() Delay()
sck_1() Delay()
sck_0() Delay()
sda_1() -- 释放总线
end
-- NACK响应
function NACK()
sda_1() Delay()
sck_1() Delay()
sck_0() Delay()
end
-- 读取从Ack,如果发送数据给从设备没有响应,后面的数据将没有意义
function I2c.ReadAck()
pin_SdaMode(1)
Ack = nil
sck_1() Delay()
if gpio.get(pin_sda) == 0 then
Ack = 1
else
Ack = 0
end
sck_0() Delay()
return Ack
end
-- 给从设备写入数据
function I2c.Write(data)
pin_SdaMode(0)
for i=0,7 do
if (data & 0x80)>0 then
sda_1()
-- print("1")
else
sda_0()
-- print("0")
end
Delay()
sck_1() Delay()
sck_0() Delay()
data = data<<1
end
sda_1() -- 释放总线
Delay()
end
-- 从 从机读取数据
function I2c.Read(ack)
pin_SdaMode(1)
local Data = 0
for i=0,7 do
Data = Data<<1
sck_1() Delay()
if gpio.get(pin_sda)==1 then
Data=Data+1
end
sck_0() Delay()
end
if ack == 1 then -- 是否发送响应
ACK()
else
NACK()
end
return Data
end
-- 读取引脚状态,查看引脚的电平变化,没有逻辑分析仪的无奈之举
function I2c.io()
print("sck",gpio.get(pin_sck),"sda",gpio.get(pin_sda))
end
-- 给模块发送地址,地址正确返回1
function I2c.CheckDevice(addr)
I2c.start()
I2c.Write(addr<<1)
local ack = I2c.ReadAck()
-- print("ack",ack)
I2c.stop()
end
return I2c
使用bh1750测试i2c协议
local bh1750_soft = {}
i2c = require "I2c_soft"
-- BH1750 命令
local BH1750_POWER_DOWN = 0x00 -- 断电
local BH1750_POWER_ON = 0x01 -- 通电
local BH1750_RESET = 0x07 -- 重置寄存器
local BH1750_CON_H_RES_MODE = 0x10 -- 连续高分辨率模式
local BH1750_CON_H_RES_MODE2 = 0x11 -- 连续高分辨率模式2
local BH1750_CON_L_RES_MODE = 0x13 -- 连续低分辨率
local BH1750_ONE_H_RES_MODE = 0x20 -- 一次高分辨率 测量后断电模式
local BH1750_ONE_H_RES_MODE2 = 0x21 -- 一次高分辨率2
local BH1750_ONE_L_RES_MODE = 0x23 -- 一次高分辨率
-- 模块初始化
function bh1750_soft.init()
i2c.GPIO_init()
i2c.start()
i2c.Write(0x23<<1) -- 设备寻址
i2c.ReadAck()
i2c.Write(BH1750_POWER_ON) -- 启动模块
i2c.ReadAck()
i2c.stop()
end
-- 读取亮度信息
function bh1750_soft.readData ()
i2c.start()
i2c.Write(0x23<<1)
i2c.ReadAck()
i2c.Write(BH1750_CON_H_RES_MODE2) -- 读取模式
i2c.ReadAck()
sys.wait(180) -- 测量需要120ms
i2c.start()
i2c.Write((0x23<<1)+1)
i2c.ReadAck()
local data1 = i2c.Read(1)
local data2 = i2c.Read(0)
-- print("data1 data2",data1,data2)
local data = (data1<<8) + data2
data = data/1.2
-- print("data",data)
i2c.stop()
return data
end
return bh1750_soft
实验效果
结语
这个程序并不完美,但我测试并没有问题,如果有很好的解决方案可以提出哈。同时,很多地方没有很好的去解释,希望能看懂。