This commit is contained in:
Steven Dan
2025-12-11 09:43:42 +08:00
commit d8b2974133
1822 changed files with 280037 additions and 0 deletions

154
lib_i2c/lib_i2c/.cproject Normal file
View File

@@ -0,0 +1,154 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?fileVersion 4.0.0?><cproject storage_type_id="org.eclipse.cdt.core.XmlProjectDescriptionStorage">
<storageModule moduleId="org.eclipse.cdt.core.settings">
<cconfiguration id="com.xmos.cdt.toolchain.854505739">
<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="com.xmos.cdt.toolchain.854505739" moduleId="org.eclipse.cdt.core.settings" name="Default">
<externalSettings/>
<extensions>
<extension id="com.xmos.cdt.core.XEBinaryParser" point="org.eclipse.cdt.core.BinaryParser"/>
<extension id="org.eclipse.cdt.core.GNU_ELF" point="org.eclipse.cdt.core.BinaryParser"/>
<extension id="com.xmos.cdt.core.XdeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
</extensions>
</storageModule>
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
<configuration buildProperties="" description="" id="com.xmos.cdt.toolchain.854505739" name="Default" parent="org.eclipse.cdt.build.core.emptycfg">
<folderInfo id="com.xmos.cdt.toolchain.854505739.1680930365" name="/" resourcePath="">
<toolChain id="com.xmos.cdt.toolchain.1700184562" name="com.xmos.cdt.toolchain" superClass="com.xmos.cdt.toolchain">
<targetPlatform archList="all" binaryParser="com.xmos.cdt.core.XEBinaryParser;org.eclipse.cdt.core.GNU_ELF" id="com.xmos.cdt.core.platform.482398777" isAbstract="false" osList="linux,win32,macosx" superClass="com.xmos.cdt.core.platform"/>
<builder arguments="-f .makefile" id="com.xmos.cdt.builder.base.621132499" keepEnvironmentInBuildfile="false" managedBuildOn="false" superClass="com.xmos.cdt.builder.base">
<outputEntries>
<entry flags="VALUE_WORKSPACE_PATH" kind="outputPath" name="bin"/>
</outputEntries>
</builder>
<tool id="com.xmos.cdt.xc.compiler.277328952" name="com.xmos.cdt.xc.compiler" superClass="com.xmos.cdt.xc.compiler">
<option id="com.xmos.xc.compiler.option.include.paths.713968690" superClass="com.xmos.xc.compiler.option.include.paths" valueType="includePath">
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/lib_logging/src}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/lib_logging}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/lib_logging/api}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/lib_xassert/src}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/lib_xassert}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/lib_xassert/api}&quot;"/>
</option>
<inputType id="com.xmos.cdt.xc.compiler.input.1007607740" name="XC" superClass="com.xmos.cdt.xc.compiler.input"/>
</tool>
<tool id="com.xmos.cdt.c.compiler.1384555284" name="com.xmos.cdt.c.compiler" superClass="com.xmos.cdt.c.compiler">
<option id="com.xmos.c.compiler.option.include.paths.168966593" superClass="com.xmos.c.compiler.option.include.paths" valueType="includePath">
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/lib_logging/src}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/lib_logging}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/lib_logging/api}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/lib_xassert/src}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/lib_xassert}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/lib_xassert/api}&quot;"/>
</option>
<inputType id="com.xmos.cdt.c.compiler.input.c.25685430" name="C" superClass="com.xmos.cdt.c.compiler.input.c"/>
</tool>
<tool id="com.xmos.cdt.cxx.compiler.163182848" name="com.xmos.cdt.cxx.compiler" superClass="com.xmos.cdt.cxx.compiler">
<option id="com.xmos.cxx.compiler.option.include.paths.1925843647" superClass="com.xmos.cxx.compiler.option.include.paths" valueType="includePath">
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/lib_logging/src}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/lib_logging}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/lib_logging/api}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/lib_xassert/src}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/lib_xassert}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/lib_xassert/api}&quot;"/>
</option>
<inputType id="com.xmos.cdt.cxx.compiler.input.cpp.377092751" name="C++" superClass="com.xmos.cdt.cxx.compiler.input.cpp"/>
</tool>
</toolChain>
</folderInfo>
<sourceEntries>
<entry excluding=".build*" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/>
</sourceEntries>
</configuration>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
</cconfiguration>
</storageModule>
<storageModule moduleId="scannerConfiguration">
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
<profile id="org.eclipse.cdt.make.core.GCCStandardMakePerProjectProfile">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="specsFile">
<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file}" command="gcc" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.make.core.GCCStandardMakePerFileProfile">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="makefileGenerator">
<runAction arguments="-E -P -v -dD" command="" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfile">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="specsFile">
<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file}" command="gcc" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfileCPP">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="specsFile">
<runAction arguments="-E -P -v -dD ${plugin_state_location}/specs.cpp" command="g++" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfileC">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="specsFile">
<runAction arguments="-E -P -v -dD ${plugin_state_location}/specs.c" command="gcc" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.managedbuilder.core.GCCWinManagedMakePerProjectProfile">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="specsFile">
<runAction arguments="-c 'gcc -E -P -v -dD &quot;${plugin_state_location}/${specs_file}&quot;'" command="sh" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.managedbuilder.core.GCCWinManagedMakePerProjectProfileCPP">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="specsFile">
<runAction arguments="-c 'g++ -E -P -v -dD &quot;${plugin_state_location}/specs.cpp&quot;'" command="sh" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
<profile id="org.eclipse.cdt.managedbuilder.core.GCCWinManagedMakePerProjectProfileC">
<buildOutputProvider>
<openAction enabled="true" filePath=""/>
<parser enabled="true"/>
</buildOutputProvider>
<scannerInfoProvider id="specsFile">
<runAction arguments="-c 'gcc -E -P -v -dD &quot;${plugin_state_location}/specs.c&quot;'" command="sh" useDefault="true"/>
<parser enabled="true"/>
</scannerInfoProvider>
</profile>
</storageModule>
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
<project id="lib_i2c.null.793113907" name="lib_i2c"/>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.LanguageSettingsProviders"/>
</cproject>

View File

@@ -0,0 +1,8 @@
all:
@echo "** Module only - only builds as part of application **"
clean:
@echo "** Module only - only builds as part of application **"

105
lib_i2c/lib_i2c/.project Normal file
View File

@@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>lib_i2c</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>com.xmos.cdt.core.LegacyProjectCheckerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.xmos.cdt.core.ProjectCheckerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.xmos.cdt.core.BuildMarkersBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.xmos.cdt.core.ProjectInfoSyncBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.xmos.cdt.core.IncludePathBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.xmos.cdt.core.ModulePathBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name>
<triggers>clean,full,incremental,</triggers>
<arguments>
<dictionary>
<key>?children?</key>
<value>?name?=outputEntries\|?children?=?name?=entry\\\\|\\|\||</value>
</dictionary>
<dictionary>
<key>?name?</key>
<value></value>
</dictionary>
<dictionary>
<key>org.eclipse.cdt.make.core.append_environment</key>
<value>true</value>
</dictionary>
<dictionary>
<key>org.eclipse.cdt.make.core.buildArguments</key>
<value>CONFIG=Debug</value>
</dictionary>
<dictionary>
<key>org.eclipse.cdt.make.core.buildCommand</key>
<value>xmake</value>
</dictionary>
<dictionary>
<key>org.eclipse.cdt.make.core.cleanBuildTarget</key>
<value>clean</value>
</dictionary>
<dictionary>
<key>org.eclipse.cdt.make.core.contents</key>
<value>org.eclipse.cdt.make.core.activeConfigSettings</value>
</dictionary>
<dictionary>
<key>org.eclipse.cdt.make.core.enableAutoBuild</key>
<value>false</value>
</dictionary>
<dictionary>
<key>org.eclipse.cdt.make.core.enableCleanBuild</key>
<value>true</value>
</dictionary>
<dictionary>
<key>org.eclipse.cdt.make.core.enableFullBuild</key>
<value>true</value>
</dictionary>
<dictionary>
<key>org.eclipse.cdt.make.core.stopOnError</key>
<value>true</value>
</dictionary>
<dictionary>
<key>org.eclipse.cdt.make.core.useDefaultBuildCmd</key>
<value>false</value>
</dictionary>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name>
<triggers>full,incremental,</triggers>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.cdt.core.cnature</nature>
<nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
<nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
<nature>com.xmos.cdt.core.XdeProjectNature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<xproject><repository>lib_i2c</repository>
<version>5.0.0dc6fb354094</version>
</xproject>

676
lib_i2c/lib_i2c/api/i2c.h Normal file
View File

@@ -0,0 +1,676 @@
// Copyright 2014-2021 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#ifndef _i2c_h_
#define _i2c_h_
#include <stddef.h>
#include <stdint.h>
/** This type is used in I2C functions to report back on whether the
* slave performed an ACK or NACK on the last piece of data sent
* to it.
*/
typedef enum {
I2C_NACK, ///< the slave has NACKed the last byte
I2C_ACK, ///< the slave has ACKed the last byte
} i2c_res_t;
#ifdef __XC__
#define BIT_TIME(KBITS_PER_SEC) ((XS1_TIMER_MHZ * 1000) / KBITS_PER_SEC)
#define BIT_MASK(BIT_POS) (1 << BIT_POS)
/** This interface is used to communication with an I2C master component.
* It provides facilities for reading and writing to the bus.
*
*/
typedef interface i2c_master_if {
/** Write data to an I2C bus.
*
* \param device_addr the address of the slave device to write to.
* \param buf the buffer containing data to write.
* \param n the number of bytes to write.
* \param num_bytes_sent the function will set this value to the
* number of bytes actually sent. On success, this
* will be equal to ``n`` but it will be less if the
* slave sends an early NACK on the bus and the
* transaction fails.
* \param send_stop_bit if this is non-zero then a stop bit
* will be sent on the bus after the transaction.
* This is usually required for normal operation. If
* this parameter is zero then no stop bit will
* be omitted. In this case, no other task can use
* the component until a stop bit has been sent.
*
* \returns ``I2C_ACK`` if the write was acknowledged by the slave
* device, otherwise ``I2C_NACK``.
*/
[[guarded]]
i2c_res_t write(uint8_t device_addr, uint8_t buf[n], size_t n,
size_t &num_bytes_sent, int send_stop_bit);
/** Read data from an I2C bus.
*
* \param device_addr the address of the slave device to read from
* \param buf the buffer to fill with data
* \param n the number of bytes to read
* \param send_stop_bit if this is non-zero then a stop bit
* will be sent on the bus after the transaction.
* This is usually required for normal operation. If
* this parameter is zero then no stop bit will
* be omitted. In this case, no other task can use
* the component until a stop bit has been sent.
*
* \returns ``I2C_ACK`` if the read was acknowledged by the slave
* device, otherwise ``I2C_NACK``.
*/
[[guarded]]
i2c_res_t read(uint8_t device_addr, uint8_t buf[n], size_t n,
int send_stop_bit);
/** Send a stop bit.
*
* This function will cause a stop bit to be sent on the bus. It should
* be used to complete/abort a transaction if the ``send_stop_bit`` argument
* was not set when calling the read() or write() functions.
*/
void send_stop_bit(void);
/** Shutdown the I2C component.
*
* This function will cause the I2C task to shutdown and return.
*/
void shutdown();
} i2c_master_if;
/** This type is used by the supplementary I2C register read/write functions to
* report back on whether the operation was a success or not.
*/
typedef enum {
I2C_REGOP_SUCCESS, ///< the operation was successful
I2C_REGOP_DEVICE_NACK, ///< the operation was NACKed when sending the device address, so either the device is missing or busy
I2C_REGOP_INCOMPLETE ///< the operation was NACKed halfway through by the slave
} i2c_regop_res_t;
extends client interface i2c_master_if : {
/** Read an 8-bit register on a slave device.
*
* This function reads an 8-bit addressed, 8-bit register from the i2c
* bus. The function reads data by
* transmitting the register addr and then reading the data from the slave
* device.
*
* Note that no stop bit is transmitted between the write and the read.
* The operation is performed as one transaction using a repeated start.
*
* \param i the interface to the I2C master
* \param device_addr the address of the slave device to read from
* \param reg the address of the register to read
* \param result indicates whether the read completed successfully. Will
* be set to ``I2C_REGOP_DEVICE_NACK`` if the slave NACKed,
* and ``I2C_REGOP_SUCCESS`` on successful completion of the
* read.
*
* \returns the value of the register
*/
inline uint8_t read_reg(client interface i2c_master_if i,
uint8_t device_addr, uint8_t reg,
i2c_regop_res_t &result) {
uint8_t a_reg[1] = {reg};
uint8_t data[1] = {0};
size_t n;
i2c_res_t res;
res = i.write(device_addr, a_reg, 1, n, 0);
if (n != 1) {
result = I2C_REGOP_DEVICE_NACK;
i.send_stop_bit();
return 0;
}
res = i.read(device_addr, data, 1, 1);
if (res == I2C_ACK) {
result = I2C_REGOP_SUCCESS;
} else {
result = I2C_REGOP_DEVICE_NACK;
}
return data[0];
}
/** Write an 8-bit register on a slave device.
*
* This function writes an 8-bit addressed, 8-bit register from the i2c
* bus. The function writes data by
* transmitting the register addr and then
* transmitting the data to the slave device.
*
* \param i the interface to the I2C master
* \param device_addr the address of the slave device to write to
* \param reg the address of the register to write
* \param data the 8-bit value to write
*/
inline i2c_regop_res_t write_reg(client interface i2c_master_if i,
uint8_t device_addr, uint8_t reg, uint8_t data)
{
uint8_t a_data[2] = {reg, data};
size_t n;
i.write(device_addr, a_data, 2, n, 1);
if (n == 0) {
return I2C_REGOP_DEVICE_NACK;
}
if (n < 2) {
return I2C_REGOP_INCOMPLETE;
}
return I2C_REGOP_SUCCESS;
}
/** Read an 8-bit register on a slave device from a 16-bit register address.
*
* This function reads a 16-bit addressed, 8-bit register from the i2c
* bus. The function reads data by
* transmitting the register addr and then reading the data from the slave
* device.
*
* Note that no stop bit is transmitted between the write and the read.
* The operation is performed as one transaction using a repeated start.
*
* \param i the interface to the I2C master
* \param device_addr the address of the slave device to read from
* \param reg the 16-bit address of the register to read
* (most significant byte first)
* \param result indicates whether the read completed successfully. Will
* be set to ``I2C_REGOP_DEVICE_NACK`` if the slave NACKed,
* and ``I2C_REGOP_SUCCESS`` on successful completion of the
* read.
*
* \returns the value of the register
*/
inline uint8_t read_reg8_addr16(client interface i2c_master_if i,
uint8_t device_addr, uint16_t reg,
i2c_regop_res_t &result)
{
uint8_t a_reg[2] = {reg >> 8, reg};
uint8_t data[1];
size_t n;
i2c_res_t res;
i.write(device_addr, a_reg, 2, n, 0);
if (n != 2) {
result = I2C_REGOP_DEVICE_NACK;
i.send_stop_bit();
return 0;
}
res = i.read(device_addr, data, 1, 1);
if (res == I2C_NACK) {
result = I2C_REGOP_DEVICE_NACK;
} else {
result = I2C_REGOP_SUCCESS;
}
return data[0];
}
/** Write an 8-bit register on a slave device from a 16-bit register address.
*
* This function writes a 16-bit addressed, 8-bit register from the i2c
* bus. The function writes data by
* transmitting the register addr and then
* transmitting the data to the slave device.
*
* \param i the interface to the I2C master
* \param device_addr the address of the slave device to write to
* \param reg the 16-bit address of the register to write
* (most significant byte first)
* \param data the 8-bit value to write
*/
inline i2c_regop_res_t write_reg8_addr16(client interface i2c_master_if i,
uint8_t device_addr, uint16_t reg,
uint8_t data) {
uint8_t a_data[3] = {reg >> 8, reg, data};
size_t n;
i.write(device_addr, a_data, 3, n, 1);
if (n == 0) {
return I2C_REGOP_DEVICE_NACK;
}
if (n < 3) {
return I2C_REGOP_INCOMPLETE;
}
return I2C_REGOP_SUCCESS;
}
/** Read an 16-bit register on a slave device from a 16-bit register address.
*
* This function reads a 16-bit addressed, 16-bit register from the i2c
* bus. The function reads data by transmitting the register addr and then
* reading the data from the slave device. It is assumed the data is returned
* most significant byte first on the bus.
*
* Note that no stop bit is transmitted between the write and the read.
* The operation is performed as one transaction using a repeated start.
*
* \param i the interface to the I2C master
* \param device_addr the address of the slave device to read from
* \param reg the address of the register to read (most
* significant byte first)
* \param result indicates whether the read completed successfully. Will
* be set to ``I2C_REGOP_DEVICE_NACK`` if the slave NACKed,
* and ``I2C_REGOP_SUCCESS`` on successful completion of the
* read.
*
* \returns the 16-bit value of the register
*/
inline uint16_t read_reg16(client interface i2c_master_if i,
uint8_t device_addr, uint16_t reg,
i2c_regop_res_t &result)
{
uint8_t a_reg[2] = {reg >> 8, reg};
uint8_t data[2];
size_t n;
i2c_res_t res;
i.write(device_addr, a_reg, 2, n, 0);
if (n != 2) {
result = I2C_REGOP_DEVICE_NACK;
i.send_stop_bit();
return 0;
}
res = i.read(device_addr, data, 2, 1);
if (res == I2C_NACK) {
result = I2C_REGOP_DEVICE_NACK;
} else {
result = I2C_REGOP_SUCCESS;
}
return ((uint16_t) data[0] << 8) | data[1];
}
/** Write an 16-bit register on a slave device from a 16-bit register address.
*
* This function writes a 16-bit addressed, 16-bit register from the i2c
* bus. The function writes data by transmitting the register addr and then
* transmitting the data to the slave device.
*
* \param i the interface to the I2C master
* \param device_addr the address of the slave device to write to
* \param reg the 16-bit address of the register to write
* (most significant byte first)
* \param data the 16-bit value to write (most significant
* byte first)
*
* \returns ``I2C_REGOP_DEVICE_NACK`` if the address is NACKed,
* ``I2C_REGOP_INCOMPLETE`` if not all data was ACKed and
* ``I2C_REGOP_SUCCESS`` on successful completion of the
* write with every byte being ACKed.
*/
inline i2c_regop_res_t write_reg16(client interface i2c_master_if i,
uint8_t device_addr, uint16_t reg,
uint16_t data) {
uint8_t a_data[4] = {reg >> 8, reg, data >> 8, data};
size_t n;
i.write(device_addr, a_data, 4, n, 1);
if (n == 0) {
return I2C_REGOP_DEVICE_NACK;
}
if (n < 4) {
return I2C_REGOP_INCOMPLETE;
}
return I2C_REGOP_SUCCESS;
}
/** Read an 16-bit register on a slave device from a 8-bit register address.
*
* This function reads a 8-bit addressed, 16-bit register from the i2c
* bus. The function reads data by transmitting the register addr and
* then reading the data from the slave device. It is assumed that the data
* is return most significant byte first on the bus.
*
* Note that no stop bit is transmitted between the write and the read.
* The operation is performed as one transaction using a repeated start.
*
* \param i the interface to the I2C master
* \param device_addr the address of the slave device to read from
* \param reg the address of the register to read
* \param result indicates whether the read completed successfully. Will
* be set to ``I2C_REGOP_DEVICE_NACK`` if the slave NACKed,
* and ``I2C_REGOP_SUCCESS`` on successful completion of the
* read.
*
* \returns the 16-bit value of the register
*/
inline uint16_t read_reg16_addr8(client interface i2c_master_if i,
uint8_t device_addr, uint8_t reg,
i2c_regop_res_t &result)
{
uint8_t a_reg[1] = {reg};
uint8_t data[2];
size_t n;
i2c_res_t res;
i.write(device_addr, a_reg, 1, n, 0);
if (n != 1) {
result = I2C_REGOP_DEVICE_NACK;
i.send_stop_bit();
return 0;
}
res = i.read(device_addr, data, 2, 1);
if (res == I2C_NACK) {
result = I2C_REGOP_DEVICE_NACK;
} else {
result = I2C_REGOP_SUCCESS;
}
return ((uint16_t) data[0] << 8) | data[1];
}
/** Write an 16-bit register on a slave device from a 8-bit register address.
*
* This function writes a 8-bit addressed, 16-bit register from the i2c
* bus. The function writes data by transmitting the register addr and then
* transmitting the data to the slave device.
*
* \param i the interface to the I2C master
* \param device_addr the address of the slave device to write to
* \param reg the address of the register to write
* \param data the 16-bit value to write (most significant byte first)
*
* \returns ``I2C_REGOP_DEVICE_NACK`` if the address is NACKed,
* ``I2C_REGOP_INCOMPLETE`` if not all data was ACKed and
* ``I2C_REGOP_SUCCESS`` on successful completion of the
* write with every byte being ACKed.
*/
inline i2c_regop_res_t write_reg16_addr8(client interface i2c_master_if i,
uint8_t device_addr, uint8_t reg,
uint16_t data) {
uint8_t a_data[3] = {reg, data >> 8, data};
size_t n;
i.write(device_addr, a_data, 3, n, 1);
if (n == 0) {
return I2C_REGOP_DEVICE_NACK;
}
if (n < 3) {
return I2C_REGOP_INCOMPLETE;
}
return I2C_REGOP_SUCCESS;
}
}
/** Implements I2C on the i2c_master_if interface using two ports.
*
* \param i an array of server interface connections for clients
* to connect to
* \param n the number of clients connected
* \param p_scl the SCL port of the I2C bus
* \param p_sda the SDA port of the I2C bus
* \param kbits_per_second the speed of the I2C bus
**/
[[distributable]] void i2c_master(server interface i2c_master_if i[n],
size_t n,
port p_scl, port p_sda,
static const unsigned kbits_per_second);
#if (defined(__XS2A__) || defined(__XS3A__) || defined(__DOXYGEN__))
/** Implements I2C on a single multi-bit port.
*
* This function implements an I2C master bus using a single port. It is only
* supported on xCORE-200 devices.
*
* \param c an array of server interface connections for clients
* to connect to
* \param n the number of clients connected
* \param p_i2c the multi-bit port containing both SCL and SDA.
* the bit positions of SDA and SCL are configured using the
* ``sda_bit_position`` and ``scl_bit_position`` arguments.
* \param kbits_per_second the speed of the I2C bus
* \param sda_bit_position the bit of the SDA line on the port
* \param scl_bit_position the bit of the SCL line on the port
* \param other_bits_mask a value that is ORed into the port value driven
* to ``p_i2c``. The SDA and SCL bit values for this
* variable must be set to 0. Note that ``p_i2c`` is
* configured with set_port_drive_low() and
* therefore external pullup resistors are required
* to produce a value 1 on a bit.
*/
[[distributable]]
void i2c_master_single_port(server interface i2c_master_if c[n], static const size_t n,
port p_i2c, static const unsigned kbits_per_second,
static const unsigned scl_bit_position,
static const unsigned sda_bit_position,
static const unsigned other_bits_mask);
#endif
/** This interface is used to communicate with an I2C master component
* asynchronously.
* It provides facilities for reading and writing to the bus.
*
*/
typedef interface i2c_master_async_if {
/** Initialize a write to an I2C bus.
*
* \param device_addr the address of the slave device to write to
* \param buf the buffer containing data to write
* \param n the number of bytes to write
* \param send_stop_bit if this is non-zero then a stop bit
* will be sent on the bus after the transaction.
* This is usually required for normal operation. If
* this parameter is zero then no stop bit will
* be omitted. In this case, no other task can use
* the component until a stop bit has been sent.
*/
[[guarded]]
void write(uint8_t device_addr, uint8_t buf[n], size_t n,
int send_stop_bit);
/** Initialize a read to an I2C bus.
*
* \param device_addr the address of the slave device to read from.
* \param n the number of bytes to read.
* \param send_stop_bit if this is non-zero then a stop bit
* will be sent on the bus after the transaction.
* This is usually required for normal operation. If
* this parameter is zero then no stop bit will
* be omitted. In this case, no other task can use
* the component until a stop bit has been sent.
*/
[[guarded]]
void read(uint8_t device_addr, size_t n, int send_stop_bit);
/** Completed operation notification.
*
* This notification will fire when a read or write is completed.
*/
[[notification]]
slave void operation_complete(void);
/** Get write result.
*
* This function should be called after a write has completed.
*
* \param num_bytes_sent the function will set this value to the
* number of bytes actually sent. On success, this
* will be equal to ``n`` but it will be less if the
* slave sends an early NACK on the bus and the
* transaction fails.
*
* \returns ``I2C_ACK`` if the write was acknowledged by the slave
* device, otherwise ``I2C_NACK``.
*/
[[clears_notification]]
i2c_res_t get_write_result(size_t &num_bytes_sent);
/** Get read result.
*
* This function should be called after a read has completed.
*
* \param buf the buffer to fill with data.
* \param n the number of bytes to read, this should be the same
* as the number of bytes specified in read(),
* otherwise the behavior is undefined.
*
* \returns ``I2C_ACK`` if the write was acknowledged by the slave
* device, otherwise ``I2C_NACK``.
*/
[[clears_notification]]
i2c_res_t get_read_data(uint8_t buf[n], size_t n);
/** Send a stop bit.
*
* This function will cause a stop bit to be sent on the bus. It should
* be used to complete/abort a transaction if the ``send_stop_bit`` argument
* was not set when calling the read() or write() functions.
*/
void send_stop_bit(void);
/** Shutdown the I2C component.
*
* This function will cause the I2C task to shutdown and return.
*/
void shutdown();
} i2c_master_async_if;
/** I2C master component (asynchronous API).
*
* This function implements I2C and allows clients to asynchronously
* perform operations on the bus.
*
* \param i the interfaces to connect the component to its clients
* \param n the number of clients connected to the component
* \param p_scl the SCL port of the I2C bus
* \param p_sda the SDA port of the I2C bus
* \param kbits_per_second the speed of the I2C bus
* \param max_transaction_size the size of the local buffer in bytes. Any
* transactions exceeding this size will cause a
* run-time exception.
*
*/
void i2c_master_async(server interface i2c_master_async_if i[n],
size_t n,
port p_scl, port p_sda,
static const unsigned kbits_per_second,
static const size_t max_transaction_size);
/** I2C master component (asynchronous API, combinable).
*
* This function implements I2C and allows clients to asynchronously
* perform operations on the bus.
* Note that this component can be run on the same logical core as other
* tasks (i.e. it is [[combinable]]). However, care must be taken that the
* other tasks do not take too long in their select cases otherwise this
* component may miss I2C transactions.
*
* \param i the interfaces to connect the component to its clients
* \param n the number of clients connected to the component
* \param p_scl the SCL port of the I2C bus
* \param p_sda the SDA port of the I2C bus
* \param kbits_per_second the speed of the I2C bus
* \param max_transaction_size the size of the local buffer in bytes. Any
* transactions exceeding this size will cause a
* run-time exception.
*/
[[combinable]]
void i2c_master_async_comb(server interface i2c_master_async_if i[n],
size_t n,
port p_scl, port p_sda,
static const unsigned kbits_per_second,
static const size_t max_transaction_size);
typedef enum i2c_slave_ack_t {
I2C_SLAVE_ACK,
I2C_SLAVE_NACK,
} i2c_slave_ack_t;
/** This interface is used to communicate with an I2C slave component.
* It provides facilities for reading and writing to the bus. The I2C slave
* component acts a *client* to this interface. So the application must
* respond to these calls (i.e. the members of the interface are callbacks
* to the application).
*
*/
typedef interface i2c_slave_callback_if {
/** Master has requested a read.
*
* This callback function is called by the component
* if the bus master requests a read from this slave device.
*
* At this point the slave can choose to accept the request (and
* drive an ACK signal back to the master) or not (and drive a NACK
* signal).
*
* \returns the callback must return either ``I2C_SLAVE_ACK`` or
* ``I2C_SLAVE_NACK``.
*/
[[guarded]]
i2c_slave_ack_t ack_read_request(void);
/** Master has requested a write.
*
* This callback function is called by the component
* if the bus master requests a write from this slave device.
*
* At this point the slave can choose to accept the request (and
* drive an ACK signal back to the master) or not (and drive a NACK
* signal).
*
* \returns the callback must return either ``I2C_SLAVE_ACK`` or
* ``I2C_SLAVE_NACK``.
*/
[[guarded]]
i2c_slave_ack_t ack_write_request(void);
/** Master requires data.
*
* This callback function will be called when the I2C master requires data
* from the slave.
*
* \return the data to pass to the master.
*/
[[guarded]]
uint8_t master_requires_data();
/** Master has sent some data.
*
* This callback function will be called when the I2C master has transferred
* a byte of data to the slave.
*/
[[guarded]]
i2c_slave_ack_t master_sent_data(uint8_t data);
/** Stop bit.
*
* This callback function will be called by the component when a stop bit
* is sent by the master.
*/
void stop_bit(void);
/** Shutdown the I2C component.
*
* This function will cause the I2C slave task to shutdown and return.
*/
[[notification]] slave void shutdown();
} i2c_slave_callback_if;
/** I2C slave task.
*
* This function instantiates an i2c_slave component.
*
* \param i the client end of the i2c_slave_callback_if interface. The component
* takes the client end and will make calls on the interface when
* the master performs reads or writes.
* \param p_scl the SCL port of the I2C bus
* \param p_sda the SDA port of the I2C bus
* \param device_addr the address of the slave device
*
*/
[[combinable]]
void i2c_slave(client i2c_slave_callback_if i,
port p_scl, port p_sda,
uint8_t device_addr);
#endif // __XC__
#endif

View File

@@ -0,0 +1,7 @@
set(LIB_NAME lib_i2c)
set(LIB_VERSION 6.2.0)
set(LIB_INCLUDES api)
set(LIB_DEPENDENT_MODULES "lib_xassert(4.2.0)")
set(LIB_COMPILER_FLAGS -Os)
XMOS_REGISTER_MODULE()

View File

@@ -0,0 +1,14 @@
VERSION = 6.2.0
DEPENDENT_MODULES = lib_xassert(>=4.2.0)
MODULE_XCC_FLAGS = $(XCC_FLAGS) \
-Os
OPTIONAL_HEADERS +=
EXPORT_INCLUDE_DIRS = api
INCLUDE_DIRS = $(EXPORT_INCLUDE_DIRS)
SOURCE_DIRS = src

View File

@@ -0,0 +1,294 @@
// Copyright 2011-2021 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include <i2c.h>
#include <xs1.h>
#include <xclib.h>
#include <timer.h>
#include "xassert.h"
/* NOTE: the kbits_per_second needs to be passed around due to the fact that the
* compiler won't compute a new static const from a static const.
*/
/** Return the number of 10ns timer ticks required to meet the timing as defined
* in the standards.
*/
static const unsigned inline compute_low_period_ticks(
static const unsigned kbits_per_second)
{
unsigned ticks = 0;
if (kbits_per_second <= 100) {
const unsigned four_point_seven_micro_seconds_in_ticks = 470;
ticks = four_point_seven_micro_seconds_in_ticks;
} else if (kbits_per_second <= 400) {
const unsigned one_point_three_micro_seconds_in_ticks = 130;
ticks = one_point_three_micro_seconds_in_ticks;
} else {
fail("Fast-mode Plus not implemented");
}
// There is some jitter on the falling edges of the clock. In order to ensure
// that the low period is respected we need to extend the minimum low period.
const unsigned jitter_ticks = 3;
return ticks + jitter_ticks;
}
static const unsigned inline compute_bus_off_ticks(
static const unsigned kbits_per_second)
{
const unsigned bit_time = BIT_TIME(kbits_per_second);
// Ensure the bus off time is respected. This is just over 1/2 bit time in
// the case of the Fast-mode I2C so adding bit_time/16 ensures the timing
// will be enforced
return bit_time/2 + bit_time/16;
}
/** Releases the SCL line, reads it back and waits until it goes high (in
* case the slave is clock stretching).
* Since the line going high may be delayed, the fall_time value may
* need to be adjusted
*/
static void release_clock_and_wait(
port p_scl,
unsigned &fall_time,
unsigned delay)
{
p_scl when pinseq(1) :> void;
timer tmr;
unsigned time;
tmr when timerafter(fall_time + delay) :> time;
// Adjust timing due to support clock stretching without clock drift in the
// normal case.
// If the time is beyond the time it takes simply to wake up and start
// executing then the clock needs to be adjusted
const int wake_up_ticks = 10;
if (time > fall_time + delay + wake_up_ticks) {
fall_time = time - delay - wake_up_ticks;
}
}
/** 'Pulse' the clock line high and in the middle of the high period
* sample the data line (if required). Timing is done via the fall_time
* reference and bit_time period supplied.
*/
[[always_inline]]
static int inline high_pulse_sample(
port p_scl,
port p_sda,
static const unsigned kbits_per_second,
unsigned &fall_time)
{
const unsigned bit_time = BIT_TIME(kbits_per_second);
int sample_value = 0;
timer tmr;
p_sda :> int _;
tmr when timerafter(fall_time + compute_low_period_ticks(kbits_per_second)) :> void;
release_clock_and_wait(p_scl, fall_time, (bit_time * 3) / 4);
p_sda :> sample_value;
fall_time = fall_time + bit_time;
tmr when timerafter(fall_time) :> void;
p_scl <: 0;
// Mask off all but lowest bit of input - allows use of bit[0] of multibit port
return sample_value & 1;
}
/** 'Pulse' the clock line high. Timing is done via the fall_time
* reference and bit_time period supplied.
*/
[[always_inline]]
static void inline high_pulse(
port p_scl,
static const unsigned kbits_per_second,
unsigned &fall_time)
{
const unsigned bit_time = BIT_TIME(kbits_per_second);
timer tmr;
tmr when timerafter(fall_time + compute_low_period_ticks(kbits_per_second)) :> void;
release_clock_and_wait(p_scl, fall_time, (bit_time * 3) / 4);
fall_time = fall_time + bit_time;
tmr when timerafter(fall_time) :> void;
p_scl <: 0;
}
/** Output a start bit. The function returns the 'fall time' i.e. the
* reference clock time when the SCL line transitions to low.
*/
static void start_bit(
port p_scl,
port p_sda,
static const unsigned kbits_per_second,
unsigned &fall_time,
int stopped)
{
const unsigned bit_time = BIT_TIME(kbits_per_second);
timer tmr;
if (!stopped) {
fall_time += compute_low_period_ticks(kbits_per_second);
tmr when timerafter(fall_time) :> fall_time;
release_clock_and_wait(p_scl, fall_time, compute_bus_off_ticks(kbits_per_second));
}
// Drive SDA low
p_sda <: 0;
delay_ticks(bit_time / 2);
// Drive SCL low
p_scl <: 0;
// Record
tmr :> fall_time;
}
/** Output a stop bit.
*/
static void stop_bit(
port p_scl,
port p_sda,
static const unsigned kbits_per_second,
unsigned &fall_time)
{
const unsigned bit_time = BIT_TIME(kbits_per_second);
timer tmr;
p_sda <: 0;
tmr when timerafter(fall_time + compute_low_period_ticks(kbits_per_second)) :> void;
release_clock_and_wait(p_scl, fall_time, bit_time);
p_sda :> void;
delay_ticks(compute_bus_off_ticks(kbits_per_second));
}
/** Transmit 8 bits of data, then read the ack back from the slave and return
* that value.
*/
static int tx8(
port p_scl,
port p_sda,
unsigned data,
static const unsigned kbits_per_second,
unsigned &fall_time)
{
// Data is transmitted MSB first
data = bitrev(data) >> 24;
for (int i = 8; i != 0; i--) {
p_sda <: data & 0x1;
data >>= 1;
high_pulse(p_scl, kbits_per_second, fall_time);
}
return high_pulse_sample(p_scl, p_sda, kbits_per_second, fall_time);
}
[[distributable]]
void i2c_master(
server interface i2c_master_if c[n],
size_t n,
port p_scl,
port p_sda,
static const unsigned kbits_per_second)
{
const unsigned bit_time = BIT_TIME(kbits_per_second);
unsigned last_fall_time = 0;
unsigned locked_client = -1;
p_scl :> void;
p_sda :> void;
while (1) {
select {
case (size_t i =0; i < n; i++)
(n == 1 || (locked_client == -1 || i == locked_client)) =>
c[i].read(uint8_t device, uint8_t buf[m], size_t m,
int send_stop_bit) -> i2c_res_t result:
const int stopped = locked_client == -1;
unsigned fall_time = last_fall_time;
start_bit(p_scl, p_sda, kbits_per_second, fall_time, stopped);
int ack = tx8(p_scl, p_sda, (device << 1) | 1, kbits_per_second, fall_time);
if (ack == 0) {
for (int j = 0; j < m; j++){
unsigned char data = 0;
timer tmr;
for (int i = 8; i != 0; i--) {
int temp = high_pulse_sample(p_scl, p_sda, kbits_per_second, fall_time);
data = (data << 1) | temp;
}
buf[j] = data;
tmr when timerafter(fall_time + bit_time/4) :> void;
// ACK after every read byte until the final byte then NACK.
if (j == m-1)
p_sda :> void;
else {
p_sda <: 0;
}
// High pulse but make sure SDA is not driving before lowering SCL
tmr when timerafter(fall_time + compute_low_period_ticks(kbits_per_second)) :> void;
high_pulse(p_scl, kbits_per_second, fall_time);
p_sda :> void;
}
}
if (send_stop_bit) {
stop_bit(p_scl, p_sda, kbits_per_second, fall_time);
locked_client = -1;
}
else {
locked_client = i;
}
result = (ack == 0) ? I2C_ACK : I2C_NACK;
// Remember the last fall time to ensure the next start bit is valid
last_fall_time = fall_time;
break;
case (size_t i = 0; i < n; i++)
(n == 1 || (locked_client == -1 || i == locked_client)) =>
c[i].write(uint8_t device, uint8_t buf[n], size_t n,
size_t &num_bytes_sent,
int send_stop_bit) -> i2c_res_t result:
unsigned fall_time = last_fall_time;
const int stopped = locked_client == -1;
start_bit(p_scl, p_sda, kbits_per_second, fall_time, stopped);
int ack = tx8(p_scl, p_sda, (device << 1), kbits_per_second, fall_time);
int j = 0;
for (; j < n; j++) {
if (ack != 0) {
break;
}
ack = tx8(p_scl, p_sda, buf[j], kbits_per_second, fall_time);
}
if (send_stop_bit) {
stop_bit(p_scl, p_sda, kbits_per_second, fall_time);
locked_client = -1;
} else {
locked_client = i;
}
num_bytes_sent = j;
result = (ack == 0) ? I2C_ACK : I2C_NACK;
// Remember the last fall time to ensure the next start bit is valid
last_fall_time = fall_time;
break;
case c[int i].send_stop_bit(void):
timer tmr;
unsigned fall_time;
tmr :> fall_time;
stop_bit(p_scl, p_sda, kbits_per_second, fall_time);
locked_client = -1;
break;
case c[int i].shutdown(void):
return;
}
}
}

View File

@@ -0,0 +1,526 @@
// Copyright 2015-2021 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include <i2c.h>
#include <xs1.h>
#include <string.h>
#include <print.h>
#include <syscall.h>
#include <xassert.h>
enum i2c_async_master_state_t {
IDLE,
REPEATED_START_CLOCK_LOW,
REPEATED_START_WAIT_FOR_CLOCK_HIGH,
REPEATED_START_HOLD_CLOCK_HIGH,
START_BIT_0,
START_BIT_1,
WRITE_0,
WRITE_1,
WRITE_ACK_0,
WRITE_ACK_1,
WRITE_ACK_2,
READ_0,
READ_1,
READ_2,
READ_ACK_0,
READ_ACK_1,
STOP_BIT_0,
STOP_BIT_1,
STOP_BIT_2,
STOP_BIT_3,
STOP_BIT_4,
DONE_NO_STOP,
};
enum optype_t {
WRITE = 0, READ = 1, SEND_STOP_BIT = 2
};
enum ack_t {
ACKED = 0, NACKED = 1,
};
void i2c_master_async_aux(
server interface i2c_master_async_if i[n],
size_t n,
client interface i2c_master_if i2c,
static const size_t max_transaction_size)
{
uint8_t buf[max_transaction_size];
uint8_t device_addr;
size_t num_bytes, num_bytes_sent;
int send_stop_bit = 0;
int optype = 0;
int cur_client = -1;
i2c_res_t res = I2C_ACK;
while (1) {
select {
case i[int j].write(uint8_t addr, uint8_t buf0[n], size_t n,
int ssb):
device_addr = addr;
send_stop_bit = ssb;
optype = WRITE;
num_bytes = n;
memcpy(buf, buf0, n);
cur_client = j;
break;
case i[int j].read(uint8_t addr, size_t n, int ssb):
device_addr = addr;
send_stop_bit = ssb;
optype = READ;
num_bytes = n;
cur_client = j;
break;
case i[int j].send_stop_bit():
optype = WRITE;
cur_client = j;
break;
case i[int j].shutdown():
return;
case i[int j].get_write_result(size_t &nbs) -> i2c_res_t result:
// ERROR
break;
case i[int j].get_read_data(uint8_t buf0[n], size_t n) -> i2c_res_t result:
// ERROR
break;
}
switch (optype) {
case WRITE:
res = i2c.write(device_addr, buf, num_bytes, num_bytes_sent,
send_stop_bit);
break;
case READ:
res = i2c.read(device_addr, buf, num_bytes, send_stop_bit);
break;
case SEND_STOP_BIT:
i2c.send_stop_bit();
break;
}
i[cur_client].operation_complete();
select {
case i[int j].get_write_result(size_t &nbs) -> i2c_res_t result:
nbs = num_bytes_sent;
result = res;
break;
case i[int j].get_read_data(uint8_t buf0[n], size_t n) -> i2c_res_t result:
memcpy(buf0, buf, n);
result = res;
break;
case i[int j].shutdown():
return;
case i[int j].send_stop_bit():
break;
}
}
}
void i2c_master_async(
server interface i2c_master_async_if i[n],
size_t n,
port p_scl, port p_sda,
static const unsigned kbits_per_second,
static const size_t max_transaction_size)
{
i2c_master_if i2c_dist[1];
par {
i2c_master(i2c_dist, 1, p_scl, p_sda, kbits_per_second);
// Disable 'slices interface preventing analysis' warning
#pragma warning disable
i2c_master_async_aux(i, n, i2c_dist[0], max_transaction_size);
#pragma warning enable
}
}
/* Adjust for time slip.
*
* All timings of the state machine in i2c_master_async_comb are made
* w.r.t the measured fall_time of the SCL line.
* If the task keeps up then we will get the speed requested. However,
* if the task falls behind due to other processing on the core then
* this function will adjust both the next event time and the fall time
* reference to slip so that subsequent timings are correct.
*/
static void inline adjust_for_slip(
int now,
int &event_time,
int &?fall_time)
{
// This value is the minimum number of timer ticks we estimate we can
// get to a new event to from now.
const int SLIP_THRESHOLD = 100;
if (event_time - now < SLIP_THRESHOLD) {
int new_event_time = now + SLIP_THRESHOLD;
if (!isnull(fall_time)) {
fall_time += new_event_time - event_time;
}
event_time = new_event_time;
}
}
static int inline adjust_fall(
int event_time,
int now,
int fall_time)
{
const int SLIP_THRESHOLD = 100;
if (now - event_time > SLIP_THRESHOLD) {
fall_time = fall_time + (now - event_time);
}
return fall_time;
}
[[combinable]]
void i2c_master_async_comb(
server interface i2c_master_async_if i[n],
size_t n,
port p_scl, port p_sda,
static const unsigned kbits_per_second,
static const size_t max_transaction_size)
{
const unsigned bit_time = BIT_TIME(kbits_per_second);
uint8_t buf[max_transaction_size] = {};
timer tmr;
int state = IDLE;
int waiting_for_clock_release = 0, timer_enabled = 0;
int optype = 0;
int fall_time = 0;
int data = 0;
int bitnum = 0;
int bytes_sent = 0;
int num_bytes = 0;
int event_time = 0;
int cur_client = -1;
int send_stop_bit = 0;
int stopped = 1;
i2c_res_t res = I2C_ACK;
/* These select cases represent the main state machine for the I2C master
component. The state machine will change state based on a timer event to
progress the transaction or on an event from the SCL line when waiting
for the clock to be released (supporting clock stretching). */
while (1) {
select {
case waiting_for_clock_release => p_scl when pinseq(1) :> void:
int now;
tmr :> now;
switch (state) {
case WRITE_0:
case WRITE_ACK_0:
case READ_ACK_0:
case STOP_BIT_0:
case REPEATED_START_WAIT_FOR_CLOCK_HIGH:
case READ_0:
fall_time = fall_time + bit_time;
event_time = fall_time;
adjust_for_slip(now, event_time, fall_time);
break;
case WRITE_ACK_1:
fall_time = fall_time + bit_time;
event_time = fall_time - bit_time / 4;
adjust_for_slip(now, event_time, fall_time);
state = WRITE_ACK_2;
break;
case STOP_BIT_2:
event_time = now + bit_time / 2;
adjust_for_slip(now, event_time, null);
state = STOP_BIT_3;
break;
case READ_1:
fall_time = fall_time + bit_time;
event_time = fall_time - bit_time / 4;
adjust_for_slip(now, event_time, fall_time);
state = READ_2;
break;
}
waiting_for_clock_release = 0;
timer_enabled = 1;
break;
case timer_enabled => tmr when timerafter(event_time) :> int now:
switch (state) {
case REPEATED_START_CLOCK_LOW:
// The operation is finished, but no stop bit is being written
p_scl <: 0;
event_time = now + bit_time / 2;
state = REPEATED_START_WAIT_FOR_CLOCK_HIGH;
break;
case REPEATED_START_WAIT_FOR_CLOCK_HIGH:
p_scl :> void;
timer_enabled = 0;
waiting_for_clock_release = 1;
state = REPEATED_START_HOLD_CLOCK_HIGH;
break;
case REPEATED_START_HOLD_CLOCK_HIGH:
event_time = now + bit_time / 2;
state = START_BIT_0;
break;
case START_BIT_0:
p_sda <: 0;
event_time = now + bit_time / 2;
state = START_BIT_1;
break;
#pragma fallthrough
case START_BIT_1:
fall_time = now;
// Fallthrough to WRITE_0 state
case WRITE_0:
p_scl <: 0;
fall_time = adjust_fall(event_time, now, fall_time);
p_sda <: data >> 7;
data <<= 1;
bitnum++;
state = WRITE_1;
event_time = fall_time + bit_time / 2 + bit_time / 32;
adjust_for_slip(now, event_time, fall_time);
break;
case WRITE_1:
p_scl :> void;
timer_enabled = 0;
waiting_for_clock_release = 1;
if (bitnum == 8) {
state = WRITE_ACK_0;
} else {
state = WRITE_0;
}
break;
case WRITE_ACK_0:
p_scl <: 0;
p_sda :> void;
fall_time = adjust_fall(event_time, now, fall_time);
event_time = fall_time + bit_time / 2 + bit_time / 32;
adjust_for_slip(now, event_time, fall_time);
state = WRITE_ACK_1;
break;
case WRITE_ACK_1:
p_scl :> void;
timer_enabled = 0;
waiting_for_clock_release = 1;
break;
case WRITE_ACK_2:
int ack;
p_sda :> ack;
event_time = fall_time;
adjust_for_slip(now, event_time, fall_time);
if (ack == ACKED && optype == WRITE) {
bytes_sent++;
int all_data_sent = (bytes_sent == num_bytes);
if (all_data_sent) {
// The master and slave disagree since the slave should nack
// the last byte.
res = I2C_ACK;
if (send_stop_bit) {
state = STOP_BIT_0;
} else {
state = DONE_NO_STOP;
}
} else {
// get next byte of data.
data = buf[bytes_sent];
bitnum = 0;
// Now go back to the transmitting
state = WRITE_0;
}
} else if (ack == NACKED && optype == WRITE) {
bytes_sent++;
int all_data_sent = (bytes_sent == num_bytes);
if (all_data_sent) {
// The master and slave agree that this is the end of the operation.
res = I2C_NACK;
if (send_stop_bit) {
state = STOP_BIT_0;
} else {
state = DONE_NO_STOP;
}
} else {
// The slave has aborted the operation.
res = I2C_NACK;
if (send_stop_bit) {
state = STOP_BIT_0;
} else {
state = DONE_NO_STOP;
}
}
} else if (ack == ACKED && optype == READ) {
// The slave has acked the addr, we can go ahead with the operation.
data = 0;
bitnum = 0;
bytes_sent++;
state = READ_0;
res = I2C_ACK;
} else if (ack == NACKED && optype == READ) {
// The slave has nacked the addr (or the slave isn't there). Abort.
res = I2C_NACK;
if (send_stop_bit) {
state = STOP_BIT_0;
} else {
state = DONE_NO_STOP;
}
}
break;
case READ_0:
p_scl <: 0;
p_sda :> void;
fall_time = adjust_fall(event_time, now, fall_time);
bitnum++;
state = READ_1;
event_time = fall_time + bit_time / 2 + bit_time / 32;
adjust_for_slip(now, event_time, fall_time);
break;
case READ_1:
p_scl :> void;
timer_enabled = 0;
waiting_for_clock_release = 1;
break;
case READ_2:
int bit;
p_sda :> bit;
data <<= 1;
data += bit&1;
event_time = fall_time;
adjust_for_slip(now, event_time, fall_time);
if (bitnum == 8) {
buf[bytes_sent] = data;
bytes_sent++;
state = READ_ACK_0;
} else {
state = READ_0;
}
break;
case READ_ACK_0:
p_scl <: 0;
if (bytes_sent == num_bytes) {
p_sda :> void;
} else {
p_sda <: 0;
}
fall_time = adjust_fall(event_time, now, fall_time);
state = READ_ACK_1;
event_time = fall_time + bit_time / 2 + bit_time / 32;
adjust_for_slip(now, event_time, fall_time);
break;
case READ_ACK_1:
p_scl :> void;
timer_enabled = 0;
waiting_for_clock_release = 1;
if (bytes_sent == num_bytes) {
if (send_stop_bit) {
state = STOP_BIT_0;
} else {
state = DONE_NO_STOP;
}
} else {
data = 0;
bitnum = 0;
state = READ_0;
}
break;
case STOP_BIT_0:
p_scl <: 0;
fall_time = adjust_fall(event_time, now, fall_time);
event_time = fall_time + bit_time / 4;
adjust_for_slip(now, event_time, fall_time);
state = STOP_BIT_1;
break;
case STOP_BIT_1:
p_sda <: 0;
event_time = fall_time + bit_time / 2;
adjust_for_slip(now, event_time, fall_time);
state = STOP_BIT_2;
break;
case STOP_BIT_2:
p_scl :> void;
timer_enabled = 0;
waiting_for_clock_release = 1;
break;
case STOP_BIT_3:
p_sda :> void;
event_time = now + bit_time/4;
state = STOP_BIT_4;
// Know that the next transaction can start from the stopped state
stopped = 1;
break;
#pragma fallthrough
case DONE_NO_STOP:
// Know that the next transaction needs to create a repeated start
stopped = 0;
// Fallthrough to STOP_BIT_4 code
case STOP_BIT_4:
i[cur_client].operation_complete();
cur_client = -1;
timer_enabled = 0;
state = IDLE;
break;
case IDLE:
fail("");
break;
}
break;
case i[int j].write(uint8_t device_addr, uint8_t buf0[n], size_t n,
int _send_stop_bit):
data = (device_addr << 1) | 0;
bitnum = 0;
optype = WRITE;
num_bytes = n;
send_stop_bit = _send_stop_bit;
memcpy(buf, buf0, n);
// The 'bytes_sent' variable gets increment after every byte *including*
// the addr bytes. So we set it to -1 to be 0 after the addr byte.
bytes_sent = -1;
timer_enabled = 1;
cur_client = j;
tmr :> event_time;
if (!stopped) {
state = REPEATED_START_CLOCK_LOW;
} else {
state = START_BIT_0;
}
break;
case i[int j].read(uint8_t device_addr, size_t n, int _send_stop_bit):
data = (device_addr << 1) | 1;
bitnum = 0;
optype = READ;
num_bytes = n;
send_stop_bit = _send_stop_bit;
// The 'bytes_sent' variable gets increment after every byte *including*
// the addr bytes. So we set it to -1 to be 0 after the addr byte.
bytes_sent = -1;
timer_enabled = 1;
cur_client = j;
tmr :> event_time;
if (!stopped) {
state = REPEATED_START_CLOCK_LOW;
} else {
state = START_BIT_0;
}
break;
case i[int j].send_stop_bit():
break;
case i[int j].get_write_result(size_t &num_bytes_sent) -> i2c_res_t result:
num_bytes_sent = bytes_sent;
result = res;
break;
case i[int j].get_read_data(uint8_t buf0[n], size_t n) -> i2c_res_t result:
memcpy(buf0, buf, n);
result = res;
break;
case i[int j].shutdown():
return;
}
}
}

View File

@@ -0,0 +1,57 @@
// Copyright 2014-2021 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include <i2c.h>
/* This file provides external definitions for the inline functions declared in
i2c.h (using C99 inlining semantics) */
extends client interface i2c_master_if : {
extern inline uint8_t read_reg_8_8(client interface i2c_master_if i,
uint8_t device_addr, uint8_t reg,
i2c_regop_res_t &res);
extern inline uint8_t read_reg(client interface i2c_master_if i,
uint8_t device_addr, uint8_t reg,
i2c_regop_res_t &res);
extern inline i2c_regop_res_t write_reg_n_m(client interface i2c_master_if i,
uint8_t device_addr,
uint8_t reg[m],
size_t m,
uint8_t data[n],
size_t n);
extern inline i2c_regop_res_t write_reg_8_8(client interface i2c_master_if i,
uint8_t device_addr, uint8_t reg,
uint8_t data);
extern inline i2c_regop_res_t write_reg(client interface i2c_master_if i,
uint8_t device_addr,
uint8_t reg, uint8_t data);
extern inline uint8_t read_reg8_addr16(client interface i2c_master_if i,
uint8_t device_addr, uint16_t reg,
i2c_regop_res_t &res);
extern inline i2c_regop_res_t write_reg8_addr16(client interface i2c_master_if i,
uint8_t device_addr, uint16_t reg,
uint8_t data);
extern inline uint16_t read_reg16(client interface i2c_master_if i,
uint8_t device_addr, uint16_t reg,
i2c_regop_res_t &res);
extern inline i2c_regop_res_t write_reg16(client interface i2c_master_if i,
uint8_t device_addr, uint16_t reg,
uint16_t data);
extern inline i2c_regop_res_t write_reg16_addr8(client interface i2c_master_if i,
uint8_t device_addr, uint8_t reg,
uint16_t data);
extern inline uint16_t read_reg16_addr8(client interface i2c_master_if i,
uint8_t device_addr, uint8_t reg,
i2c_regop_res_t &result);
}

View File

@@ -0,0 +1,324 @@
// Copyright 2013-2021 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#if (defined(__XS2A__) || defined(__XS3A__))
#include "i2c.h"
#include <xs1.h>
#include <xclib.h>
#include <stdio.h>
#include <timer.h>
#include "xassert.h"
#define SDA_LOW 0
#define SCL_LOW 0
/* NOTE: the kbits_per_second needs to be passed around due to the fact that the
* compiler won't compute a new static const from a static const.
*/
/** Return the number of 10ns timer ticks required to meet the timing as defined
* in the standards.
*/
static const unsigned inline compute_low_period_ticks(
static const unsigned kbits_per_second)
{
unsigned ticks = 0;
if (kbits_per_second <= 100) {
const unsigned four_point_seven_micro_seconds_in_ticks = 470;
ticks = four_point_seven_micro_seconds_in_ticks;
} else if (kbits_per_second <= 400) {
const unsigned one_point_three_micro_seconds_in_ticks = 130;
ticks = one_point_three_micro_seconds_in_ticks;
} else {
fail("Fast-mode Plus not implemented");
}
// There is some jitter on the falling edges of the clock. In order to ensure
// that the low period is respected we need to extend the minimum low period.
const unsigned jitter_ticks = 3;
return ticks + jitter_ticks;
}
static const unsigned inline compute_bus_off_ticks(
static const unsigned kbits_per_second)
{
const unsigned bit_time = BIT_TIME(kbits_per_second);
// Ensure the bus off time is respected. This is just over 1/2 bit time in
// the case of the Fast-mode I2C so adding bit_time/16 ensures the timing
// will be enforced
return bit_time/2 + bit_time/16;
}
/** Reads back the SCL line, waiting until it goes high (in
* case the slave is clock stretching). It is assumed that the clock
* line has been release (driven high) before calling this function.
* Since the line going high may be delayed, the fall_time value may
* need to be adjusted
*/
static void wait_for_clock_high(
port p_i2c,
static const unsigned scl_bit_position,
unsigned &fall_time,
unsigned delay)
{
const unsigned SCL_HIGH = BIT_MASK(scl_bit_position);
unsigned val = peek(p_i2c);
while (!(val & SCL_HIGH)) {
val = peek(p_i2c);
}
timer tmr;
unsigned time;
tmr when timerafter(fall_time + delay) :> time;
// Adjust timing due to support clock stretching without clock drift in the
// normal case.
// If the time is beyond the time it takes simply to wake up and start
// executing then the clock needs to be adjusted
const int wake_up_ticks = 10;
if (time > fall_time + delay + wake_up_ticks) {
fall_time = time - delay - wake_up_ticks;
}
}
static void high_pulse_drive(
port p_i2c,
int sdaValue,
static const unsigned kbits_per_second,
static const unsigned scl_bit_position,
static const unsigned sda_bit_position,
static const unsigned other_bits_mask,
unsigned &fall_time)
{
const unsigned bit_time = BIT_TIME(kbits_per_second);
const unsigned SCL_HIGH = BIT_MASK(scl_bit_position);
const unsigned SDA_HIGH = BIT_MASK(sda_bit_position);
timer tmr;
sdaValue = sdaValue ? SDA_HIGH : SDA_LOW;
p_i2c <: SCL_LOW | sdaValue | other_bits_mask;
tmr when timerafter(fall_time + compute_low_period_ticks(kbits_per_second)) :> void;
p_i2c <: SCL_HIGH | sdaValue | other_bits_mask;
wait_for_clock_high(p_i2c, scl_bit_position, fall_time, (bit_time * 3) / 4);
fall_time = fall_time + bit_time;
tmr when timerafter(fall_time) :> void;
p_i2c <: SCL_LOW | sdaValue | other_bits_mask;
}
static int high_pulse_sample(
port p_i2c,
static const unsigned kbits_per_second,
static const unsigned scl_bit_position,
static const unsigned sda_bit_position,
static const unsigned other_bits_mask,
unsigned &fall_time)
{
const unsigned bit_time = BIT_TIME(kbits_per_second);
const unsigned SCL_HIGH = BIT_MASK(scl_bit_position);
const unsigned SDA_HIGH = BIT_MASK(sda_bit_position);
timer tmr;
p_i2c <: SCL_LOW | SDA_HIGH | other_bits_mask;
tmr when timerafter(fall_time + compute_low_period_ticks(kbits_per_second)) :> void;
p_i2c <: SCL_HIGH | SDA_HIGH | other_bits_mask;
wait_for_clock_high(p_i2c, scl_bit_position, fall_time, (bit_time * 3) / 4);
int sample_value = peek(p_i2c);
if (sample_value & SDA_HIGH)
sample_value = 1;
else
sample_value = 0;
fall_time = fall_time + bit_time;
tmr when timerafter(fall_time) :> void;
p_i2c <: SCL_LOW | SDA_HIGH | other_bits_mask;
return sample_value;
}
static void start_bit(
port p_i2c,
static const unsigned kbits_per_second,
static const unsigned scl_bit_position,
static const unsigned sda_bit_position,
static const unsigned other_bits_mask,
unsigned &fall_time,
int stopped)
{
const unsigned bit_time = BIT_TIME(kbits_per_second);
const unsigned SCL_HIGH = BIT_MASK(scl_bit_position);
const unsigned SDA_HIGH = BIT_MASK(sda_bit_position);
timer tmr;
if (!stopped) {
fall_time += compute_low_period_ticks(kbits_per_second);
tmr when timerafter(fall_time) :> fall_time;
p_i2c <: SCL_HIGH | SDA_HIGH | other_bits_mask;
wait_for_clock_high(p_i2c, scl_bit_position, fall_time, compute_bus_off_ticks(kbits_per_second));
}
p_i2c <: SCL_HIGH | SDA_LOW | other_bits_mask;
delay_ticks(bit_time / 2);
p_i2c <: SCL_LOW | SDA_LOW | other_bits_mask;
tmr :> fall_time;
}
static void stop_bit(
port p_i2c,
static const unsigned kbits_per_second,
static const unsigned scl_bit_position,
static const unsigned sda_bit_position,
static const unsigned other_bits_mask,
unsigned fall_time)
{
const unsigned bit_time = BIT_TIME(kbits_per_second);
const unsigned SCL_HIGH = BIT_MASK(scl_bit_position);
const unsigned SDA_HIGH = BIT_MASK(sda_bit_position);
timer tmr;
p_i2c <: SCL_LOW | SDA_LOW | other_bits_mask;
tmr when timerafter(fall_time + compute_low_period_ticks(kbits_per_second)) :> void;
p_i2c <: SCL_HIGH | SDA_LOW | other_bits_mask;
wait_for_clock_high(p_i2c, scl_bit_position, fall_time, bit_time);
p_i2c <: SCL_HIGH | SDA_HIGH | other_bits_mask;
delay_ticks(compute_bus_off_ticks(kbits_per_second));
}
static int tx8(
port p_i2c, unsigned data,
static const unsigned kbits_per_second,
static const unsigned scl_bit_position,
static const unsigned sda_bit_position,
static const unsigned other_bits_mask,
unsigned &fall_time)
{
unsigned bit_rev_data = ((unsigned) bitrev(data)) >> 24;
for (int i = 8; i != 0; i--) {
high_pulse_drive(p_i2c, bit_rev_data & 1, kbits_per_second, scl_bit_position, sda_bit_position, other_bits_mask, fall_time);
bit_rev_data >>= 1;
}
return high_pulse_sample(p_i2c, kbits_per_second, scl_bit_position, sda_bit_position, other_bits_mask, fall_time);
}
[[distributable]]
void i2c_master_single_port(
server interface i2c_master_if c[n],
static const size_t n,
port p_i2c,
static const unsigned kbits_per_second,
static const unsigned scl_bit_position,
static const unsigned sda_bit_position,
static const unsigned other_bits_mask)
{
const unsigned bit_time = BIT_TIME(kbits_per_second);
const unsigned SCL_HIGH = BIT_MASK(scl_bit_position);
const unsigned SDA_HIGH = BIT_MASK(sda_bit_position);
unsigned last_fall_time = 0;
unsigned locked_client = -1;
set_port_drive_low(p_i2c);
p_i2c <: SCL_HIGH | SDA_HIGH | other_bits_mask;
while (1) {
select {
case (size_t i =0; i < n; i++)
(n == 1 || (locked_client == -1 || i == locked_client)) =>
c[i].read(uint8_t device, uint8_t buf[m], size_t m,
int send_stop_bit) -> i2c_res_t result:
const int stopped = locked_client == -1;
unsigned fall_time = last_fall_time;
start_bit(p_i2c, kbits_per_second, scl_bit_position, sda_bit_position, other_bits_mask, fall_time, stopped);
int ack = tx8(p_i2c, (device << 1) | 1, kbits_per_second, scl_bit_position, sda_bit_position, other_bits_mask, fall_time);
if (ack == 0) {
for (int j = 0; j < m; j++){
unsigned char data = 0;
timer tmr;
for (int i = 8; i != 0; i--) {
int temp = high_pulse_sample(p_i2c, kbits_per_second, scl_bit_position, sda_bit_position, other_bits_mask, fall_time);
data = (data << 1) | temp;
}
buf[j] = data;
// ACK after every read byte until the final byte then NACK.
unsigned sda = SDA_LOW;
if (j == m-1) {
sda = SDA_HIGH;
}
p_i2c <: SCL_LOW | sda | other_bits_mask;
tmr when timerafter(fall_time + compute_low_period_ticks(kbits_per_second)) :> void;
p_i2c <: SCL_HIGH | sda | other_bits_mask;
wait_for_clock_high(p_i2c, scl_bit_position, fall_time, (bit_time * 3) / 4);
fall_time = fall_time + bit_time;
tmr when timerafter(fall_time) :> void;
// Release the data bus
p_i2c <: SCL_LOW | SDA_HIGH | other_bits_mask;
}
}
if (send_stop_bit) {
stop_bit(p_i2c, kbits_per_second, scl_bit_position, sda_bit_position, other_bits_mask, fall_time);
locked_client = -1;
} else {
locked_client = i;
}
result = (ack == 0) ? I2C_ACK : I2C_NACK;
// Remember the last fall time to ensure the next start bit is valid
last_fall_time = fall_time;
break;
case (size_t i = 0; i < n; i++)
(n == 1 || (locked_client == -1 || i == locked_client)) =>
c[i].write(uint8_t device, uint8_t buf[n], size_t n,
size_t &num_bytes_sent,
int send_stop_bit) -> i2c_res_t result:
const int stopped = locked_client == -1;
unsigned fall_time = last_fall_time;
start_bit(p_i2c, kbits_per_second, scl_bit_position, sda_bit_position, other_bits_mask, fall_time, stopped);
int ack = tx8(p_i2c, device<<1, kbits_per_second, scl_bit_position, sda_bit_position, other_bits_mask, fall_time);
int j = 0;
for (; j < n; j++) {
if (ack != 0)
break;
ack = tx8(p_i2c, buf[j], kbits_per_second, scl_bit_position, sda_bit_position, other_bits_mask, fall_time);
}
if (send_stop_bit) {
stop_bit(p_i2c, kbits_per_second, scl_bit_position, sda_bit_position, other_bits_mask, fall_time);
locked_client = -1;
} else {
locked_client = i;
}
num_bytes_sent = j;
result = (ack == 0) ? I2C_ACK : I2C_NACK;
// Remember the last fall time to ensure the next start bit is valid
last_fall_time = fall_time;
break;
case c[int i].send_stop_bit(void):
timer tmr;
unsigned fall_time;
tmr :> fall_time;
stop_bit(p_i2c, kbits_per_second, scl_bit_position, sda_bit_position, other_bits_mask, fall_time);
locked_client = -1;
break;
case c[int i].shutdown():
return;
}
}
}
#endif

View File

@@ -0,0 +1,265 @@
// Copyright 2014-2021 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include <i2c.h>
#include <xs1.h>
#include <xclib.h>
enum i2c_slave_state {
WAITING_FOR_START_OR_STOP,
READING_ADDR,
ACK_ADDR,
ACK_WAIT_HIGH,
ACK_WAIT_LOW,
IGNORE_ACK,
MASTER_WRITE,
MASTER_READ
};
static inline void ensure_setup_time()
{
// The I2C spec requires a 100ns setup time
delay_ticks(10);
}
[[combinable]]
void i2c_slave(client i2c_slave_callback_if i,
port p_scl, port p_sda,
uint8_t device_addr)
{
enum i2c_slave_state state = WAITING_FOR_START_OR_STOP;
enum i2c_slave_state next_state = WAITING_FOR_START_OR_STOP;
int sda_val = 0;
int scl_val;
int bitnum = 0;
int data;
int rw = 0;
int stop_bit_check = 0;
int ignore_stop_bit = 1;
p_sda when pinseq(1) :> void;
while (1) {
select {
case i.shutdown():
return;
case state != WAITING_FOR_START_OR_STOP => p_scl when pinseq(scl_val) :> void:
switch (state) {
case READING_ADDR:
// If clock has gone low, wait for it to go high before doing anything
if (scl_val == 0) {
scl_val = 1;
break;
}
int bit;
p_sda :> bit;
if (bitnum < 7) {
data = (data << 1) | bit;
bitnum++;
scl_val = 0;
break;
}
// We have gathered the whole device address sent by the master
if (data != device_addr) {
state = IGNORE_ACK;
} else {
state = ACK_ADDR;
rw = bit;
}
scl_val = 0;
break;
case IGNORE_ACK:
// This request is not for us, ignore the ACK
next_state = WAITING_FOR_START_OR_STOP;
scl_val = 1;
state = ACK_WAIT_HIGH;
break;
case ACK_ADDR:
// Stretch clock (hold low) while application code is called
p_scl <: 0;
// Callback to the application to determine whether to ACK
// or NACK the address.
int ack;
if (rw) {
ack = i.ack_read_request();
} else {
ack = i.ack_write_request();
}
ignore_stop_bit = 0;
if (ack == I2C_SLAVE_NACK) {
// Release the data line so that it is pulled high
p_sda :> void;
next_state = WAITING_FOR_START_OR_STOP;
} else {
// Drive the ACK low
p_sda <: 0;
if (rw) {
next_state = MASTER_READ;
} else {
next_state = MASTER_WRITE;
}
}
scl_val = 1;
state = ACK_WAIT_HIGH;
ensure_setup_time();
// Release the clock
p_scl :> void;
break;
case ACK_WAIT_HIGH:
// Rising edge of clock, hold ack to the falling edge
state = ACK_WAIT_LOW;
scl_val = 0;
break;
case ACK_WAIT_LOW:
// ACK done, release the data line
p_sda :> void;
if (next_state == MASTER_READ) {
scl_val = 0;
} else if (next_state == MASTER_WRITE) {
data = 0;
scl_val = 1;
} else { // WAITING_FOR_START_OR_STOP
sda_val = 0;
}
state = next_state;
bitnum = 0;
break;
case MASTER_READ:
if (scl_val == 1) {
// Rising edge
if (bitnum == 8) {
// Sample ack from master
int bit;
p_sda :> bit;
if (bit) {
// Master has NACKed so the transaction is finished
state = WAITING_FOR_START_OR_STOP;
sda_val = 0;
} else {
bitnum = 0;
scl_val = 0;
}
} else {
// Wait for next falling edge
scl_val = 0;
bitnum++;
}
} else {
// Falling edge, drive data
if (bitnum < 8) {
if (bitnum == 0) {
// Stretch clock (hold low) while application code is called
p_scl <: 0;
data = i.master_requires_data();
// Data is transmitted MSB first
data = bitrev(data) >> 24;
// Send first bit of data
p_sda <: data & 0x1;
ensure_setup_time();
// Release the clock
p_scl :> void;
} else {
p_sda <: data & 0x1;
}
data >>= 1;
} else {
// Release the bus for the master to be able to ACK/NACK
p_sda :> void;
}
scl_val = 1;
}
break;
case MASTER_WRITE:
if (scl_val == 1) {
// Rising edge
int bit;
p_sda :> bit;
data = (data << 1) | (bit & 0x1);
if (bitnum == 0) {
if (bit) {
sda_val = 0;
} else {
sda_val = 1;
}
// First bit could be a start or stop bit
stop_bit_check = 1;
}
scl_val = 0;
bitnum++;
} else {
// Falling edge
// Not a start or stop bit
stop_bit_check = 0;
if (bitnum == 8) {
// Stretch clock (hold low) while application code is called
p_scl <: 0;
int ack = i.master_sent_data(data);
if (ack == I2C_SLAVE_NACK) {
// Release the data bus so it is pulled high to signal NACK
p_sda :> void;
} else {
// Drive data bus low to signal ACK
p_sda <: 0;
}
state = ACK_WAIT_HIGH;
ensure_setup_time();
// Release the clock
p_scl :> void;
}
scl_val = 1;
}
break;
}
break;
case (state == WAITING_FOR_START_OR_STOP) || stop_bit_check =>
p_sda when pinseq(sda_val) :> void:
if (sda_val == 1) {
// SDA has transitioned from low to high, if SCL is high
// then it is a stop bit.
int val;
p_scl :> val;
if (val) {
if (!ignore_stop_bit) {
i.stop_bit();
}
state = WAITING_FOR_START_OR_STOP;
ignore_stop_bit = 1;
stop_bit_check = 0;
}
sda_val = 0;
} else {
// SDA has transitioned from high to low, if SCL is high
// then it is a start bit.
int val;
p_scl :> val;
if (val == 1) {
state = READING_ADDR;
bitnum = 0;
data = 0;
scl_val = 0;
stop_bit_check = 0;
} else {
sda_val = 1;
}
}
break;
}
}
}