I found a very interesting I2C master implementation written in assembly language for the ESP8266 (https://github.com/pasko-zh/brzo_i2c). However, as the author mentions, the library is written for the Arduino toolchain.
I made some slight changes in order to compile it with the newest SDK from espressif: ESP8266_NONOS_SDK_V2.0.0_16_07_19.
asm volatile (
// Disable all interrupts, i.e. interrupts up to the highest interrupt level of 15
// the current level is saved in %[r_temp1] but we will not use that value again,
// instead we will just enable all interrupt levels at the end of this routine
"RSIL %[r_temp1], 15;"
"MOVI %[r_set], 0x60000304;"
// Check if bus is free and send START
"OR %[r_temp1], %[r_sda_bitmask], %[r_scl_bitmask];"
"L16UI %[r_in_value], %[r_set], 20;" // offset is 20d = 14h = > in: 0x60000318
"MEMW;"
"MOVI.N %[r_error], 1;"
// If either SDA or SCL is low, then bus is not free and thus jump to l_exit
"BNALL %[r_in_value], %[r_temp1], l_exit;"
// Bus is free, so we can send START
"MOVI.N %[r_error], 0;"
"MOV.N %[r_temp1], %[r_iteration_scl_halfcycle];"
// Set SCL = 1
"S16I %[r_scl_bitmask], %[r_set], 0;"
"MEMW;"
// Set SDA = 0
// Delay for tHD;STA >= 4.0 usec for standard mode, 0.6 usec for fast or 0.26 usec fast mode plus
// => a delay of one half cycle is enough to meet those timings
"S16I %[r_sda_bitmask], %[r_set], 4;" // clear: 0x60000308
"l_w01:"
"ADDI.N %[r_temp1], %[r_temp1], -1;"
"NOP;"
"BNEZ %[r_temp1], l_w01;"
// Post Condition: SDA = 0, SCL = 1
// The outer loop, sending 1...n data bytes
"l_send_byte:"
// select the MSB of byte_to_send
"MOVI %[r_bit_index], 128;"
// The inner loop, sending 1...8 bits
"l_send_bit:"
"MOV.N %[r_temp1], %[r_iteration_scl_halfcycle];"
// check if the bit of byte_to_send at bit_index is 0 or 1
"BALL %[r_byte_to_send], %[r_bit_index], l_sda1_scl0;"
// SDA = 0, SCL = 0
"S16I %[r_scl_bitmask], %[r_set], 4;" // clear: 0x60000308
"MEMW;"
"S16I %[r_sda_bitmask], %[r_set], 4;" // clear: 0x60000308
"l_w02:"
"ADDI.N %[r_temp1], %[r_temp1], -1;"
"NOP;"
"BNEZ %[r_temp1], l_w02;"
"j l_sdax_scl1;"
"l_sda1_scl0:"
// SDA = 1, SCL = 0
"S16I %[r_scl_bitmask], %[r_set], 4;" // clear: 0x60000308
"MEMW;"
"S16I %[r_sda_bitmask], %[r_set], 0;"
"l_w03:"
"ADDI.N %[r_temp1], %[r_temp1], -1;"
"NOP;"
"BNEZ %[r_temp1], l_w03;"
"l_sdax_scl1:"
// SDA = leave unchanged and set SCL = 1
// Check for clock stretching
// Delay is little bit shorter, i.e. half_cycle - delta
"ADDI %[r_temp1], %[r_iteration_scl_halfcycle], -5;"
// Let SCL raise
"S16I %[r_scl_bitmask], %[r_set], 0;"
"MEMW;"
"l_w04:"
"ADDI.N %[r_temp1], %[r_temp1], -1;"
"NOP;"
// Explicitly BGEZ instead of BNEZ
"BGEZ %[r_temp1], l_w04;"
// Sample SCL value
"L16UI %[r_in_value], %[r_set], 20;" // offset is 20d = 14h = > in: 0x60000318
"MEMW;"
// r_temp1 holds the number of iterations for clock stretch timeout
"MOV.N %[r_temp1], %[r_iteration_scl_clock_stretch];"
// Branch if SCL = 1, i.e. no stretching
"BALL %[r_in_value], %[r_scl_bitmask], l_no_stretch;"
// SCL = 0, i.e. stretching by the slave, i.e. it pulls SCL low
"l_stretch:"
// Sample SCL value
"L16UI %[r_in_value], %[r_set], 20;" // offset is 20d = 14h = > in: 0x60000318
"MEMW;"
// Branch if SCL = 1, i.e. no more stretching
"BALL %[r_in_value], %[r_scl_bitmask], l_scl_high_by_slave;"
// SCL is still low
"ADDI.N %[r_temp1], %[r_temp1], -1;"
// Did we reach the clock stretch timeout?
// Branch if we have not yet reached the timeout
"BNEZ %[r_temp1], l_stretch;"
// We have reached the clock stretch timeout, i.e. SCL is still pulled low by the slave
// Error: Bus is not free, since SCL is still low AND clock stretch timeout reached
"MOVI.N %[r_error], 8;"
// We explicitly do not send a STOP instead we exit, i.e. jump to l_exit and not to l_send_stop
"j l_exit;"
"l_scl_high_by_slave:"
// SCL was set high by the slave
// We have to make sure that SCL = 1 for a complete half cycle
"MOV.N %[r_temp1], %[r_iteration_scl_halfcycle];"
"l_w041:"
"ADDI.N %[r_temp1], %[r_temp1], -1;"
"NOP;"
"BNEZ %[r_temp1], l_w041;"
"l_no_stretch:"
// Postcondition: SCL = 1 for a half cycle
// Are there bits left that we need to send?
"SRLI %[r_bit_index], %[r_bit_index], 1;"
// When the LSB of the byte_to_send was sent, i.e. bit index was 1 before SRLI, it will now be zero
// As long as the LSB was not sent keep on sending bits, i.e. jump
"BNEZ %[r_bit_index], l_send_bit;"
// we have sent 8 Bits
// check for ACK by slave
// Precondition
// SDA = LSB (i.e. SDA = 0, since we have an i2c write), SCL = 1
// SCL = 0
// Spike reducing waits here
"S16I %[r_scl_bitmask], %[r_set], 4;" // clear : 0x60000308
"MOV.N %[r_temp1], %[r_iteration_minimize_spike];"
"l_w05:"
"ADDI.N %[r_temp1], %[r_temp1], -1;"
"NOP;"
"BNEZ %[r_temp1], l_w05;"
// Reduce number of iterations by the ones we've already used
"SUB %[r_temp1], %[r_iteration_scl_halfcycle], %[r_iteration_minimize_spike];"
// Now we let SDA raise.
// In case of an ACK the i2c slave is pulling SDA down
// In case of an NACK, SDA raises
"S16I %[r_sda_bitmask], %[r_set], 0;"
"MEMW;"
"l_w06:"
"ADDI.N %[r_temp1], %[r_temp1], -1;"
"NOP;"
"BGEZ %[r_temp1], l_w06;"
// Delay is little bit shorter, i.e. half_cycle - delta
// Because we will have a L16UI after in this half cycle
"ADDI %[r_temp1], %[r_iteration_scl_halfcycle], -5;"
// Set SCL = 1, i.e. start of the second half cycle of the 9th SCL cycle
"S16I %[r_scl_bitmask], %[r_set], 0;"
"MEMW;"
// Delay for the second half cycle of the 9th SCL cycle
"l_w07:"
"ADDI.N %[r_temp1], %[r_temp1], -1;"
"NOP;"
"BGEZ %[r_temp1], l_w07;"
// Sample SDA at the end of the 9th clock cycle
// In the case of an NACK we want to leave enough time that SDA can raise
// If sda_value AND sda_bitmask == 0 => ACK else we have an NACK
"L16UI %[r_in_value], %[r_set], 20;" // offset is 20d = 14h = > in: 0x60000318
"BNALL %[r_in_value], %[r_sda_bitmask], l_slave_ack;"
"MOVI.N %[r_error], 2;"
// NACK by slave
// Postcondition:
// SDA = 1 (NACK) and SCL = 1
// 9th Clock Cycle is finished
"j l_send_stop;"
"l_slave_ack:"
// ACK
// Precondition: SDA = 0 (still pulled low by the slave) and SCL = 1
// The slave will pull SDA low as long as SCL = 1
// We have to set SDA = 0 by the master
// clear : 0x60000308
"S16I %[r_sda_bitmask], %[r_set], 4;"
// Postcondition:
// SDA = 0 and SCL = 1
// 9th Clock Cycle is finished
"BEQZ %[r_no_of_bytes], l_send_stop;"
// Branch if there are no more Data Bytes to send
// Load the corresponding element of array data[.] into byte_to_send
"L8UI %[r_byte_to_send], %[r_adr_array_element], 0;"
// Move the pointer to the next array element (since we have an array of bytes, the increment is 1)
"ADDI.N %[r_adr_array_element], %[r_adr_array_element], 1;"
// Decrement the number of bytes to send
"ADDI.N %[r_no_of_bytes], %[r_no_of_bytes], -1;"
"j l_send_byte;"
"l_send_stop:"
// Send Stop
// We have to make sure that SDA = 0 and SCL = 1, before we send the STOP sequence,
// i.e. "A LOW to HIGH transition on the SDA line while SCL is HIGH"
// In order to achieve this econdition, we have to distinguish between
// 1) NACK: SDA = 1, SCL = 1
// 2) ACK: SDA = 0, SCL = 1
// SDA is still pulled low by the slave, so we have to signal the slave to release it.
// We will do this by letting SCL go low.
"MOV.N %[r_temp1], %[r_iteration_scl_halfcycle];"
// if we had a NACK then r_error = 2
// if we had an ACK then r_error = 0
"BNEZ.N %[r_error], l_stop_after_NACK;"
// Send stop after ACK
// Precondition: SDA = 0, SCL = 1
// We are at the beginning of the 10th cycle (if there was no clock stretching)
// Set SCL = 0
// During the first half cycle the slave should release SDA...
"S16I %[r_scl_bitmask], %[r_set], 4;" // clear : 0x60000308
"MEMW;"
"l_w08:"
"ADDI.N %[r_temp1], %[r_temp1], -1;"
"NOP;"
"BNEZ %[r_temp1], l_w08;"
// Check for a repeated start
// Branch if r_repeated is 0, i.e. is no repeated start, just send stop
"BEQZ.N %[r_repeated], l_no_repeated_start;"
// Make sure that the precondition for the next command (i.e. the start) will be met
// Currently, SCL = 0 and SDA is starting to raise, since the slave has released it
// To be on the safe side, we set both SCL = 1 _and SDA = 1
// SDA = 1
"S16I %[r_sda_bitmask], %[r_set], 0;"
"MEMW;"
// SCL = 1;
"S16I %[r_scl_bitmask], %[r_set], 0;"
// Postcondition: SCL = 1 and SDA = 1, now the next i2c command send start
"j l_exit;"
"l_no_repeated_start:"
// For the second half cycle, we set SDA = 0, SCL = 1
"MOV.N %[r_temp1], %[r_iteration_scl_halfcycle];"
"S16I %[r_sda_bitmask], %[r_set], 4;" // clear : 0x60000308
"MEMW;"
"S16I %[r_scl_bitmask], %[r_set], 0;"
"l_w09:"
"ADDI.N %[r_temp1], %[r_temp1], -1;"
"NOP;"
"BNEZ %[r_temp1], l_w09;"
// For the first half cycle of the 11th cycle, we set SDA = 1 and leave SCL = 1
"MOV.N %[r_temp1], %[r_iteration_scl_halfcycle];"
// SDA = 1 (SCL is already high, we don't need to change it)
"S16I %[r_sda_bitmask], %[r_set], 0;"
"MEMW;"
"l_w10:"
"ADDI.N %[r_temp1], %[r_temp1], -1;"
"NOP;"
"BNEZ %[r_temp1], l_w10;"
"j l_exit;"
"l_stop_after_NACK:"
// Send stop after NACK
// Precondition: SDA = 1, SCL = 1
// SDA = 0
// SCL = 1 : In "normal" cycles we woud set SCL to 0
"S16I %[r_sda_bitmask], %[r_set], 4;" // clear: 0x60000308
"S16I %[r_scl_bitmask], %[r_set], 0;"
// Delay for the first half cycle of 10th cycle
"MEMW;"
"l_w11:"
"ADDI.N %[r_temp1], %[r_temp1], -1;"
"NOP;"
"BNEZ %[r_temp1], l_w11;"
// Postcondition: SDA = 0 and SCL = 1
// Now we set SDA = 1 and leave SCL = 1 : This ist the STOP condition,
// i.e. the "A LOW to HIGH transition on the SDA line while SCL is HIGH"
"MOV.N %[r_temp1], %[r_iteration_scl_halfcycle];"
"S16I %[r_sda_bitmask], %[r_set], 0;"
// SDA = 1 (SCL is already high, we don't need to change it)
"MEMW;"
"l_w12:"
"ADDI.N %[r_temp1], %[r_temp1], -1;"
"NOP;"
"BNEZ %[r_temp1], l_w12;"
"l_exit:"
// Enable all interrupts again, i.e. interrupts with interrupt level >= 1
"RSIL %[r_temp1], 0;"
: [r_set] "+r" (a_set), [r_repeated] "+r" (a_repeated), [r_temp1] "+r" (a_temp1), [r_in_value] "+r" (a_in_value), [r_error] "+r" (i2c_error), [r_bit_index] "+r" (a_bit_index), [r_adr_array_element] "+r" (&data[0]), [r_byte_to_send] "+r" (byte_to_send), [r_no_of_bytes] "+r" (no_of_bytes)
: [r_sda_bitmask] "r" (sda_bitmask), [r_scl_bitmask] "r" (scl_bitmask), [r_iteration_scl_halfcycle] "r" (iteration_scl_halfcycle), [r_iteration_minimize_spike] "r" (iteration_remove_spike), [r_iteration_scl_clock_stretch] "r" (iteration_scl_clock_stretch)
: "memory"
);
I would really appreciate if you could help me understand what it is that is going wrong and what options there are to solve it. Thanks in advance.
I found a very interesting I2C master implementation written in assembly language for the ESP8266 (https://github.com/pasko-zh/brzo_i2c). However, as the author mentions, the library is written for the Arduino toolchain.
I made some slight changes in order to compile it with the newest SDK from espressif: ESP8266_NONOS_SDK_V2.0.0_16_07_19.
However, I haven't been able to get past this compiler error:
The CFLAG variable in my Makefile looks like this:
This is the inline assembler code that it points to:
I would really appreciate if you could help me understand what it is that is going wrong and what options there are to solve it. Thanks in advance.