This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

CC1312R: Sensor Controller I2C slave implementation

Part Number: CC1312R
Other Parts Discussed in Thread: CC2650, CC2640, CC1350, CC1310, MSP-EXP430F5529LP

I have been working on a slave i2c implementation in sensor controller for most of the day yesterday and today.

I am having difficulty understanding a few things and would like some assistance.

First, I would like to disable the pull-ups on the SDA and SCL, it is not apparent (for me) which bits controls the pull-ups.

I am attempting to have it loop forever to wait for the I2C start condition, something does not appear to like this since the task will time out.  So far I am running in the debugger only, I have not attempted to import this into CCS.

The data that is supposed to appear in R7 always comes out 0xff, I am not sure if the SDA line is being read correctly, but I believe I implemented the necessary logic to wait for SCL return high, read the SDA line, then wait for SCL to return low before returning to the beginning of the loop to wait for the next bit.

At present, I am only attempting to receive a single byte, which will in the end turn out to be the address.

This would then be followed by an arbitrary number of bytes, up to 128, and stored in a buffer until transmission is completed, then notify the main MCU of the transmission completion.

I used the i2c master implementation as a starting point and have adapted the logic from there, therefore it is still under the i2c master module name at present.

I will upload the complete project here, I am using a MSP430 as a i2c master, which is verified to be functioning correctly.  I am not sure of the transmission speed, but since it is software implemented it could not be that fast.

It would be appreciated if someone can provide any feedback on this and what direction I should take.

I am uploading the CCS project, written for a CC1312R LaunchPadXL along with the proc_defs folder that contains the assembly that was written.

i2cslave_CC1312R1_LAUNCHXL_tirtos_ccs.zip

proc_defs.zip

tirtos_builds_CC1312R1_LAUNCHXL_release_ccs.zip

Edit: I have corrected one issue with not using fwSwitchOutputBuffer(); to allow the main MCU access to the buffered data.  After fixing this all the data received is coming back 0x0000.

  • Hi Allan,

    Could you maybe elaborate a bit on which files is the interesting one in your test case? As for IO pull, this is typically set in the ".red" file found in the "resource_def" folder. This contains information on all the data structures, IOs (and their configuration when in uninitialized/initialized) connected to a specific proc resource such as "I2C Master". 

    As for the timeout, I assume this is in the "Task Testing" view? This is expected in a case where you have a longer loop as it is set to expect the task to report "done" within a given time. This is really no issue, it should bring up the "instruction view" and you can then press play again and it will run for ever. This is also nice as this view (you can enter it before running by pressing the small "document" in the toolbar to the right of play/pause/stop) as it allows you to set breakpoints as well as stepping the application. You could use this to debug your behavior to see if it reacts to events in the manner you expect:

    1) Set breakpoint to see if it finds "START"

    2) Set breakpoint on first bit to see if it evaluated correctly

    3) Set breakpoint to see if "STOP" is found as expected.

  • Hi M-W,

    Thank you for the reply.

    I am going to rewrite this again from the ground up and going to take the approach of one transaction per run task.

    I would like to use an interrupt on the SCL line to start the task, but, it looks like I can only wait and pull it either in assembly, or with the gpioWaitForLevel(AUXIO_I_SCL, 1); instruction.

    Also, I have a few questions concerning some clarification on some of the documentation.

    iobtst      #(AUXIO_I2C_SCL & 0x7), [#(IOP_AIODIO0_GPIODIN + (AUXIO_I2C_SCL >> 3))]
    biob1       /done

    1. This appears to be bit test, followed by branch if set.  In the documentation the command is as follows iobclr #imm, [#addr], can you provide more of explanation of what #imm and #addr are?

    2. Same with biob0 is branch if zero I believe.  biob0 and biob1 don't appear to be documented, or I missed them.


    iobset #(AUXIO_I2C_SDA & 0x7), [#(IOP_AIODIO0_GPIODOUT + (AUXIO_I2C_SDA >> 3))]

    3. The above appears to set a bit, in this case set AUXIO_I2C_SDA to input, followed by pull-up on AUXIO_I2C_SDA. How can I  set AUXIO_I2C_SDA to input but without pull-up.

    5. One other command I am looking for is how to rotate bits in the task (by C code, not assembly, similar to assembly instruction lsl), and how to bit test (by C code, not assembly).

    I have thought about creating the I2C slave completely in C, just as an exercise to get the logic down before finalizing it in assembly.

    6. Are instructions such as gpioWaitForLevel(AUXIO_I_SCL, 1) going to be too slow to run in C to keep up with the I2C speed?

    7. A couple other assembly commands I would like to know, branch if Z (zero) set/clear, branch if C (carry) set/clear.  It is not clear in the help file with these, or I missed them.  Basically how can I bit test the STATUS register?  What address is it at, or what is it called, and what the bits are in the STATUS register?

    8. How can I set C (carry) manually, if I want to rotate (using lsl)in a bit from C?

    9. What is the efficiency difference (number of clock cycles) of using a command such as compare (cmp) then branch if not zero (bneq) vs doing a subtract and checking for Z (zero)? Is beq (branch if equal) also valid?

    10. If the assembly is similar to the CC1312, perhaps I can use the CC1312 reference, or is this a custom more limited instruction set?

    11. Can I read/write the PC (program counter)?  Useful for branching based off a table or returning values from a look up table.

    12. From other documentation it appears the stack can only support calls three levels deep, is this correct?  Will a stack overrun/underrun be caught, or undefined behavior?

    If you can help answer the above questions it would be very helpful, if what I asked is in the help file do please let me know and I will review it again in more detail.

  • Hi Allan,

    Please see answers below:

    1) The answer is really in the "operation" column in the help section. The "iob" instructions is "single bit input/output" operations and #imm is the bit to access while #addr is the address to the register you want to target.

    It only support 8-bit depth which is why you see the #imm being masked with 0x7. Most registers, such as AUXIO registers, are divided up in 8-bit mappings which means the IO access to all 32 pins are divided over 4 registers with 8 IOs in each. This is why there is a number in IOP_AIODIO0_GPIODIN, this ranges from 0-3 depending on the IO you are going for. 

    The registers are mapped in memory next to each other which is why this statement works for any IO:

    #(IOP_AIODIO0_GPIODIN + (AUXIO_I2C_SCL >> 3))

    Basically, if AUXIO_I2C_SCL would be 8 (just choose this as out of the blue, could be higher), the result would be #(IOP_AIODIO0_GPIODIN + 1) = IOP_AIODIO1_GPIODIN which is where the IO is found.

    2) They are, but as they are a branch operation, you need to look first in the "Program Flow Control" section, three you will find b<cc> which is "Branch relative if condition is met". The "cc" is the condition and these are in their own table. Here you will find for example:

    "geu / iob0 - Greater or equal, unsigned / Tested I/O bit = 0"

    Putting these together, you get b<iob0> (or biob0 as you actually write it) which would mean "Branch relative if Tested I/O bit = 0"

    3) IOP_AIODIO0_GPIODIN and IOP_AIODIO0_GPIODOUT are input and output registers, they do not set the mode of the IO, they simply let you read/set the value of one.

    While you can control if an AUXIO is to be input or output (using the IOP_AIODIO0_GPIOMODEL and IOP_AIODIO0_GPIOMODEH regsiters, I recommend looking at the register map for a description of these registers), pull resistance live in the IOC which the SCE can not reach. This is why you setup the IO pull configuration in the ".red" file (found in resource_defs) that specifies the interface, this to allow the ARM controller to later call "scifInitIo()" and configure this up. 

    While running debugging, the debugger tools in SCS will take care of this for you assuming the .red file is setup as you want.

    5) There is multiple approaches to this, in SCE there is not good instructions for rotating registers. What I tend to do in these situations, when reading in bit-banged data with LSB first using the SCE, is to or them in backwards. For example, in case of 8 bits, I would do something along these lines:

    ; bit OR register
    ld  R0, #0x80
    
    ; Actual result register
    ld  R1, #0
    
    ; Loop for 8 bits
    /start:
    
    iobtst #(MY_IO & 0x7), [some_register]
    
    ; If bit was 0, skip or operation
    biob0 /was0:
    
    ; Bit was 1, set it in R1
    or  R1, R0
    
    /was0:
    
    ; Move R0 for next bit
    lsr R0, #1
    
    ; When zero, we have all 8-bits done
    beq R0, /done
    
    ; If not, start over
    bra /start
    
    /done:
    ...

    6) This is a hard one to answer, it depends on how you implement the equivalent and what state the system is in. Assuming for example an interrupt, the responds could be quick if the system is active and slow if you are in standby.

    7) You can't test the STATUS register (that I'm aware of) in this case. The flags you are looking for is found in the "Condition" column in table with the same name. For example, branch if zero:

    <cc>     Description    Condition

    eq / z | Equal / Zero | Z

    If you look at the other instruction tables, you will find the column "Z/N/C/V", this shows you if the instruction will set, clear or leave the condition flag alone.

    8) As the special registers are not mapped (as far as I'm aware), the only way to "set" the carry flag is to perform a operation that would set it (for example a iobtst in a bit that is 1)

    9) They are the same. Each instruction takes 2 cycles (which means the efficient "instruction rate" is half of the SCE clock rate) unless they end up being prefixed. In that case they will require additional cycles (typically they act as a double instruction). Which commands that could be prefixed and why is found int he "prefix" column in the asm documentation.

    There is also some special cases that involve AON access or if you are using the MAC. MAC instructions typically take longer time to finish so you could get gated if running them after each other. The AON domain is clocked by the 32 kHz clock which means you need to wait for it to sync up.

    10) CC1352, CC2652, CC2642 and CC1312 all share the same instruction set, it is the same SCE. The CC1310, CC1350, CC2650 and CC2640 have slightly different instruction set. 

    11) Reading it not possible, but you "write" it by performing a jmp R0 instruction where R0 is the new PC value. If you use the subroutine jumps you get PC pushed to the internal stack. You can however only nest 2 levels deep as the SCE only support three stack pushes (and one is used initially by the setup code).

    12) Yes. Also, as I mentioned in 11), you only really have two levels free to use due to the first level being used by the framework (when you call your routine from inside SCS). The behavior is undefined.

  • Hi M-W,

    Thank you very much for the answers, it is very useful and good insight into how the sensor controller works.

    I am in the process of rethinking the logic for the i2c slave implementation and will reply back soon in this topic about the progress or if something comes up.

  • Hi M-W,

    I believe I have been able to implement the START detect, and would like to ask for feedback if you have any.

    The .red file is nearly a direct copy from the i2c.red file.

    I believe, since I need to retrieve one (or two) bytes at a time, the best course would be to separate the code in SCE.

    Psudo code would go as follows.

    // Wait for i2c START
    i2cSlaveWaitStart();
    
    // Receive device address
    U16 result;
    i2cSlaveRxByte8(result);
    
    // Check device address matches
    // If no match end task
    
    // Receive register address
    i2cSlaveRxByte8(result);
    output.buffer[0] = result;
    
    // Receive bytes until STOP
    // Since receiving a string of characters the number of bytes is undetermined, up to 128 bytes
    n = 1;
    while(!stop) {
      i2cSlaveRxByte8(result);
      output.buffer[0] = result;
      n += 1;
    }
    
    // Pass buffer to main MCU
    fwSwitchOutputBuffer();
    
    // Schedule the next execution
    fwScheduleTask(1);

    Following is the function for detecting START.  It appears to be working correctly, any suggestions are appreciated.

    I am looking for a 20ms window where SDA and SCL stay high so the middle of a transaction is not recognized as a START condition.

    I am concerned the timing is not correct for 20ms, I am following the i2c master timing loop, same clock cycles I believe, does this look correct?

    I do not have an oscilloscope to check the timing with at present.

    ; CLOBBERS:
    ;     R5
    I2cSlaveWaitStart:
                            ; SDA = pull-up
                            iobset      #(AUXIO_I2C_SDA & 0x7), [#(IOP_AIODIO0_GPIODOUT + (AUXIO_I2C_SDA >> 3))]
    
                            ; SCL = pull-up
                            iobset      #(AUXIO_I2C_SCL & 0x7), [#(IOP_AIODIO0_GPIODOUT + (AUXIO_I2C_SCL >> 3))]
    
    /waitLoopSdaHigh:
                                ; If SDA is high, we're done
                                iobtst      #(AUXIO_I2C_SDA & 0x7), [#(IOP_AIODIO0_GPIODIN + (AUXIO_I2C_SDA >> 3))]
                                biob1       /waitLoopSdaHighDone
                                ; Unroll the loop to 12 instructions = 1 per loop for faster response
                                iobtst      #(AUXIO_I2C_SDA & 0x7), [#(IOP_AIODIO0_GPIODIN + (AUXIO_I2C_SDA >> 3))]
                                biob1       /waitLoopSdaHighDone
                                iobtst      #(AUXIO_I2C_SDA & 0x7), [#(IOP_AIODIO0_GPIODIN + (AUXIO_I2C_SDA >> 3))]
                                biob1       /waitLoopSdaHighDone
                                iobtst      #(AUXIO_I2C_SDA & 0x7), [#(IOP_AIODIO0_GPIODIN + (AUXIO_I2C_SDA >> 3))]
                                biob1       /waitLoopSdaHighDone
                                iobtst      #(AUXIO_I2C_SDA & 0x7), [#(IOP_AIODIO0_GPIODIN + (AUXIO_I2C_SDA >> 3))]
                                biob1       /waitLoopSdaHighDone
    
                            jmp         /waitLoopSdaHigh
    
    /waitLoopSdaHighDone:
    /waitLoopScl:
                                ; If SCL is high, we're done
                                iobtst      #(AUXIO_I2C_SCL & 0x7), [#(IOP_AIODIO0_GPIODIN + (AUXIO_I2C_SCL >> 3))]
                                biob1       /waitLoopSclDone
                                ; Unroll the loop to 12 instructions = 1 per loop for faster response
                                iobtst      #(AUXIO_I2C_SCL & 0x7), [#(IOP_AIODIO0_GPIODIN + (AUXIO_I2C_SCL >> 3))]
                                biob1       /waitLoopSclDone
                                iobtst      #(AUXIO_I2C_SCL & 0x7), [#(IOP_AIODIO0_GPIODIN + (AUXIO_I2C_SCL >> 3))]
                                biob1       /waitLoopSclDone
                                iobtst      #(AUXIO_I2C_SCL & 0x7), [#(IOP_AIODIO0_GPIODIN + (AUXIO_I2C_SCL >> 3))]
                                biob1       /waitLoopSclDone
                                iobtst      #(AUXIO_I2C_SCL & 0x7), [#(IOP_AIODIO0_GPIODIN + (AUXIO_I2C_SCL >> 3))]
                                biob1       /waitLoopSclDone
    
                            jmp         /waitLoopScl
    
    /waitLoopSclDone:
                            ; Make sure SDA and SCL are still high for 20 ms, if not we go back to the start and retry
                            ; Load the timeout (up to 20 ms) loop counter = configured number of us
                            ld          R5, #(-I2C_STRETCH_TIMEOUT_US)
    /waitLoopSclSda20ms:
                                ; If SCL is low, we restart
                                iobtst      #(AUXIO_I2C_SCL & 0x7), [#(IOP_AIODIO0_GPIODIN + (AUXIO_I2C_SCL >> 3))]
                                biob0       /waitLoopSdaHigh
    
                                ; If SDA is low, we restart
                                iobtst      #(AUXIO_I2C_SDA & 0x7), [#(IOP_AIODIO0_GPIODIN + (AUXIO_I2C_SDA >> 3))]
                                biob0       /waitLoopSdaHigh
    
                                ; Timing adjustments, 12 instructions total per loop
                                nop;
                                nop;
                                nop;
                                nop;
                                nop;
                                nop;
    
                            add         R5, #1;
                            bnz         /waitLoopSclSda20ms
                            jmp         /waitSdaLow
    
                            ; Timeout has occurred, so SDA and SCL have been high for 20ms
    
    /waitSdaLow:
                            ; wait SDA goes low, this indicates start
    
                                ; If SDA is low, we're done
                                iobtst      #(AUXIO_I2C_SDA & 0x7), [#(IOP_AIODIO0_GPIODIN + (AUXIO_I2C_SDA >> 3))]
                                biob0       /waitSdaLowDone
                                ; Unroll the loop to 12 instructions = 1 per loop for faster response
                                iobtst      #(AUXIO_I2C_SDA & 0x7), [#(IOP_AIODIO0_GPIODIN + (AUXIO_I2C_SDA >> 3))]
                                biob0       /waitSdaLowDone
                                iobtst      #(AUXIO_I2C_SDA & 0x7), [#(IOP_AIODIO0_GPIODIN + (AUXIO_I2C_SDA >> 3))]
                                biob0       /waitSdaLowDone
                                iobtst      #(AUXIO_I2C_SDA & 0x7), [#(IOP_AIODIO0_GPIODIN + (AUXIO_I2C_SDA >> 3))]
                                biob0       /waitSdaLowDone
                                iobtst      #(AUXIO_I2C_SDA & 0x7), [#(IOP_AIODIO0_GPIODIN + (AUXIO_I2C_SDA >> 3))]
                                biob0       /waitSdaLowDone
    
                            jmp         /waitSdaLow
    
    /waitSdaLowDone:
    /checkSclHigh:
                            ; SCL should still be high, if not we restart
                                iobtst      #(AUXIO_I2C_SCL & 0x7), [#(IOP_AIODIO0_GPIODIN + (AUXIO_I2C_SCL >> 3))]
                                biob1       /done
                            jmp          /waitLoopSdaHigh
    
    /done:                  ; Done
                            rts

  • Hi Allan, 

    A few notes you could consider based on what you have:

    1) How do you configure the IOs in code? I would assume you want to setup the IOs in open-drain mode as you expect external I2C pull-ups in most cases. Maybe this is what you are doing?

    2) Should you implement "stretch" on ACK? How will you handle control of ACK and NACK? Should "Wait for start" maybe be a "WaitOnDeviceAddress" kind of API which means it waits for start AND check the configured device address (and decide to NACK/ACK this)?

    3) I would assume you are looking for a SDA falling edge while SCLK is high, maybe something like this for START detection? It is maybe a bit to simple and you likely need to beef it up a bit to make it robust but I do not think you need to worry about first finding a "idle" time before detecting START. typically you should never have a 1 -> 0 during a high SCLK anyway.

    /findStart:
        ; Wait for SDA to be high
        iobtst SDA, [SDA ...]
        biob0 /findStart
    /findFallingSDAEdge:
        iobtst SDA, [SDA ...]
        ; Still not pulled low
        biob1 /findFallingSDAEdge
        ; The SDA went low, check SCLK
        iobtst SCLK, [SCLK ...]
        biob1 /startFound
        ; Not a START condition
        bra /findStart
    /startFound:
        ; Sample bits and what not. You likely want to align with the SCLK at this pointer
        iobtst SCLK, [SCLK ...]
        biob1 /startFound
        ; Clock went LOW, wait for first bits
    /waitForBit0:
        iobtst SCLK, [SCLK ...]
        biob0 /waitForBit0
        ; Clock went high, sample bit
        iobtst SDA, [SDA ...]
        biob0 /bit0Zero
        ; Was 1, or in backwards
        or RESULT_REG, #0x80
        ; Wait for falling SCLK edge
        bra /waitForBit1
    /bit0Zero:
        ; Need to check for early STOP in this case
        iobtst SCLK, [SCLK ...]
        biob0 /waitForBit1
        ; Did SDA go high while SCLK is high?
        iobtst SDA, [SDA ...]
        biob1 /possibleSTOPFound
        ; Still low, go back and wait for falling edge of SCLK
        bra /bit0Zero
    
    /waitForBit1:
        iobtst SCLK, [SCLK ...]
        biob1 /waitForBit1
        .....
    

  • Hi M-W,

    Thank you for your suggestion, I have been able to receive data through the I2C and forward it to the main MCU.

    I am planning to rewrite it again once it is functioning taking your suggestions into account.

    However, there are a few issues that came up, I was able to use the oscilloscope at the office and attempted to use DI015 connected to one of the channels in order to keep track of where the code was at in execution.

    I had a lot of trouble initially as I could not get the pullup to work, or maybe the pullup does not exist on DI015, I also tried DI021.

    I needed to add an external pull up resistor of 1.5k between the GPIO and 3V3, but it does work.  I am wondering why the internal pull-up is not enabling as expected, or maybe it does not exist on that GPIO pin.

    Here are the commands I am using, I am copying the .red file code and the assembly that I am using.

    I have omitted non-relevant parts of code to not clutter the post.

        <!-- I/O function references -->
        <io_func_ref name="Digital GPIO" label_format="I2C SCL" const_format="AUXIO_I2C_SCL" attr="SCL pin"/>
        <io_func_ref name="Digital GPIO" label_format="I2C SDA" const_format="AUXIO_I2C_SDA" attr="SDA pin"/>
        <io_func_ref name="Digital GPIO" label_format="I2C TEST" const_format="AUXIO_I2C_TEST" attr="TEST pin"/>
        <io_array_size value="0"/>
        <io_usage_count min="1" max="1"/>
    
        <!-- Attributes -->
        <rattr name="I2C_OP_WRITE"            type="dec"  content="const"  scope="task" desc="Bit 0 of the I2C address byte of an I2C write operation">0</rattr>
        <rattr name="I2C_OP_READ"             type="dec"  content="const"  scope="task" desc="Bit 0 of the I2C address byte of an I2C read operation">1</rattr>
        <rattr name="BV_I2C_STATUS_TX_NACK"   type="hex"  content="const"  scope="task" desc="Status bit: Acknowledgment value for the last i2cTx() call (0 = ACK, 1 = NACK)">0x0001</rattr>
        <rattr name="BV_I2C_STATUS_TIMEOUT"   type="hex"  content="const"  scope="task" desc="Status bit: Clock stretching timeout has occurred">0x0002</rattr>
        <rattr name="I2C_BASE_DELAY"          type="expr" content="const"  scope="task" desc="Protocol base delay, in instruction cycles">(12 - 3) + ('SCL frequency' * 46)</rattr>
        <rattr name="SCL frequency"           type="dec"  content="cfg"    scope="task" min="0"   max="1"     list="400 kHz,100 kHz">0</rattr>
        <rattr name="SCL stretch timeout"     type="dec"  content="cfg"    scope="task" min="1"   max="20000" unit="1 us">1</rattr>
        <rattr name="state.i2cStatus"         type="hex"  content="struct" scope="task" min="0x0" max="0x7"   desc="I2C master status">0x0</rattr>
    
        <!-- Assembly code, preventing use of the resource in multiple tasks -->
        <asm_code usage="defines">
            <![CDATA[
                .define AUXIO_I2C_SCL           'SCL pin'
                .define AUXIO_I2C_SDA           'SDA pin'
                .define AUXIO_I2C_TEST           'TEST pin'
                .define I2C_BASE_DELAY          'I2C_BASE_DELAY'
                .define I2C_EXT_DELAY           4
                .define I2C_STRETCH_TIMEOUT_US  'SCL stretch timeout'
                .define I2C_WAIT_STRETCH_DELAY  5
            ]]>
        </asm_code>
    
        <!-- Driver code -->
        <driver_code usage="init" scope="io_instance">
            <execute>
                <![CDATA[
                    scifInitIo('SCL pin', AUXIOMODE_OPEN_DRAIN_WITH_INPUT, -1, 1);
                    HWREG((IOC_BASE + IOC_O_IOCFG0) + pAuxIoIndexToMcuIocfgOffsetLut['SCL pin']) |= IOC_IOCFG0_HYST_EN_M;
                    scifInitIo('SDA pin', AUXIOMODE_OPEN_DRAIN_WITH_INPUT, -1, 1);
                    HWREG((IOC_BASE + IOC_O_IOCFG0) + pAuxIoIndexToMcuIocfgOffsetLut['SDA pin']) |= IOC_IOCFG0_HYST_EN_M;
                    scifInitIo('TEST pin', AUXIOMODE_OPEN_DRAIN_WITH_INPUT, -1, 1);
                    HWREG((IOC_BASE + IOC_O_IOCFG0) + pAuxIoIndexToMcuIocfgOffsetLut['TEST pin']) |= IOC_IOCFG0_HYST_EN_M;
                ]]>
            </execute>
        </driver_code>
        <driver_code usage="reinit_io" scope="io_instance">
            <execute>
                <![CDATA[
                    scifReinitIo('SCL pin', -1, 0);
                    HWREG((IOC_BASE + IOC_O_IOCFG0) + pAuxIoIndexToMcuIocfgOffsetLut['SCL pin']) |= IOC_IOCFG0_HYST_EN_M;
                    scifReinitIo('SDA pin', -1, 0);
                    HWREG((IOC_BASE + IOC_O_IOCFG0) + pAuxIoIndexToMcuIocfgOffsetLut['SDA pin']) |= IOC_IOCFG0_HYST_EN_M;
                    scifReinitIo('TEST pin', -1, 0);
                    HWREG((IOC_BASE + IOC_O_IOCFG0) + pAuxIoIndexToMcuIocfgOffsetLut['TEST pin']) |= IOC_IOCFG0_HYST_EN_M;
                ]]>
            </execute>
        </driver_code>
        <driver_code usage="uninit" scope="io_instance">
            <execute>
                <![CDATA[
                    scifUninitIo('SCL pin', -1);
                    scifUninitIo('SDA pin', -1);
                    scifUninitIo('TEST pin', -1);
                ]]>
            </execute>
        </driver_code>
    

    In the .asm file

    ; TEST = pull-up
    ;iobset      #(AUXIO_I2C_TEST & 0x7), [#(IOP_AIODIO0_GPIODOUT + (AUXIO_I2C_TEST >> 3))]
    
    ; TEST signal, set low
    iobclr      #(AUXIO_I2C_TEST & 0x7), [#(IOP_AIODIO0_GPIODOUT + (AUXIO_I2C_TEST >> 3))]
    
    ; TEST signal, set high
    iobset      #(AUXIO_I2C_TEST & 0x7), [#(IOP_AIODIO0_GPIODOUT + (AUXIO_I2C_TEST >> 3))]
    

    Again thank you for your code suggestions, I will be working on this more tonight and implementing the device address check code along with STOP detection.

    At first I am interested mostly in writing at the moment, but would like to be able to have the main MCU set data for later I2C read, such as the IEEE address of the CC1312, etc.

    If you could please look into the issue with the pull-up resistor, maybe something that I overlooked.

  • Hi M-W,

    Let me answer the questions before I move forward too far.

    1) How do you configure the IOs in code? I would assume you want to setup the IOs in open-drain mode as you expect external I2C pull-ups in most cases. Maybe this is what you are doing?

    I am using the same method as in the I2C master implementation provided in SCE.
    The following commands are used to pull-down and release the SDA and SCL lines.

    ; SCL = pull-up
    iobset      #(AUXIO_I2C_SCL & 0x7), [#(IOP_AIODIO0_GPIODOUT + (AUXIO_I2C_SCL >> 3))]
    
    ; SDA = pull-up
    iobset      #(AUXIO_I2C_SDA & 0x7), [#(IOP_AIODIO0_GPIODOUT + (AUXIO_I2C_SDA >> 3))]
    
    ; SCL = pull-down
    iobclr      #(AUXIO_I2C_SCL & 0x7), [#(IOP_AIODIO0_GPIODOUT + (AUXIO_I2C_SCL >> 3))]
    
    ; SDA = pull-down
    iobclr      #(AUXIO_I2C_SDA & 0x7), [#(IOP_AIODIO0_GPIODOUT + (AUXIO_I2C_SDA >> 3))]
    

    You can also see in the above post how they are setup in the .red file.

    I would like to use external pull-ups, as this should be the responsibility of the master device.
    What is the best way to disable the internal pull-ups?  I believe I hinted at this before, but the answer was not clear.
    I guess what I am asking is how I modify the above instructions to not enable the pull-up but retain the ability to easily pull down the SDA and SCL lines as needed?

    2) Should you implement "stretch" on ACK? How will you handle control of ACK and NACK? Should "Wait for start" maybe be a "WaitOnDeviceAddress" kind of API which means it waits for start AND check the configured device address (and decide to NACK/ACK this)?

    Yes, the plan is to implement this and will come up really soon as soon as soon as get the STOP code working since the number of bytes in the transmission is arbitrary I will need to be able to detect the STOP to know transmission is finished.

    3) I would assume you are looking for a SDA falling edge while SCLK is high, maybe something like this for START detection? It is maybe a bit to simple and you likely need to beef it up a bit to make it robust but I do not think you need to worry about first finding a "idle" time before detecting START. typically you should never have a 1 -> 0 during a high SCLK anyway.

    Yes, as seen from the (a little overcomplicated) code above I am attempting to wait for idle time so I don't get shifted on bits on received data if for some reason the middle of the transmission is caught and bits might get mixed up.  Maybe this is overthinking a little, and will also force a delay if another transmission begins soon after the present one finishes.

    One thing I have a question about is the I2C master implementation that I have based some of this code on.
    The following code is used to detect rising or falling edges and is it redundant to pull it multiple times in the loop?
    Does this actually provide a faster response as stated in the comments?

    /waitSclAck2Low:
                                ; Wait for SCL to go low
                                ; Indicates master is done reading SDA
    
                                    ; If SCL is low, we're done
                                    iobtst      #(AUXIO_I2C_SCL & 0x7), [#(IOP_AIODIO0_GPIODIN + (AUXIO_I2C_SCL >> 3))]
                                    biob0       /waitSclAck2LowDone
                                    ; Unroll the loop to 12 instructions = 1 per loop for faster response
                                    iobtst      #(AUXIO_I2C_SCL & 0x7), [#(IOP_AIODIO0_GPIODIN + (AUXIO_I2C_SCL >> 3))]
                                    biob0       /waitSclAck2LowDone
                                    iobtst      #(AUXIO_I2C_SCL & 0x7), [#(IOP_AIODIO0_GPIODIN + (AUXIO_I2C_SCL >> 3))]
                                    biob0       /waitSclAck2LowDone
                                    iobtst      #(AUXIO_I2C_SCL & 0x7), [#(IOP_AIODIO0_GPIODIN + (AUXIO_I2C_SCL >> 3))]
                                    biob0       /waitSclAck2LowDone
                                    iobtst      #(AUXIO_I2C_SCL & 0x7), [#(IOP_AIODIO0_GPIODIN + (AUXIO_I2C_SCL >> 3))]
                                    biob0       /waitSclAck2LowDone
    
                                jmp         /waitSclAck2Low

    Thank you again for your time and support with this!

  • Hi Allan,

    If you look inte .red file you find:

    scifInitIo('SCL pin', AUXIOMODE_OPEN_DRAIN_WITH_INPUT, -1, 1);
    The "-1" here is "pull resistors disabled" so you should be fine in that regard. As I briefly explained before, the sensor controller can't control the pull resistors, this is why it is part of the initialization code from the ARM side. I think what is confusing is the comment about "pull-up" and "pull-down", what it refers to is close/open the drain. In other words, set -> drain open -> line is pulled up and vise verse.
    As for the responds, it all depends on code structuring, consider the following code:
    /waitSclAck2Low:
    ; Wait for SCL to go low
    ; Indicates master is done reading SDA
    
        ; If SCL is low, we're done
        iobtst      #(AUXIO_I2C_SCL & 0x7), [#(IOP_AIODIO0_GPIODIN + (AUXIO_I2C_SCL >> 3))]
        biob0       /waitSclAck2LowDone
    
    jmp         /waitSclAck2Low
    
    /waitSclAck2LowDone:

    As you depend on the "jmp" to loop back, the responds is faster as iobtst + biob0 + jmp = 3 while iobtst + biob0 = 2 (which is why it was repeated. This way you also have a 2 instruction "responds" to waitSclAck2LowDone seen from the "tst".

    However, if you structure it this way:

    /waitSclAck2Low:
    ; Wait for SCL to go low
    ; Indicates master is done reading SDA
    
        ; If SCL is low, we're done
        iobtst      #(AUXIO_I2C_SCL & 0x7), [#(IOP_AIODIO0_GPIODIN + (AUXIO_I2C_SCL >> 3))]
        biob1       /waitSclAck2Low
    
    /waitSclAck2LowDone:

    You would have something similar, right? It is just that you inverse the test so the "loop" is still iobtst + biob1 (true) = 2 and iobtst + biob1 (false) = 2. 

  • Hi M-W,

    Thank you for your responses.  It was not straight forward to understand that the "-1" was controlling the pull-up resistors, thank you for the explanation!

    Ah, this is why the pull up resistor was not enabled as expected.  At present the CC1312 is not able to pull the SDA line down as low as expected, I think I saw a topic concerning how to set the current higher so I will search for that.

    I will modify the loop structure it as you suggested to reduce the code size and be more compact.

    Thank you for the suggestions, much appreciated!  I will be working on cleaning up the logic and adding the address checking, read/write bit checking, etc.

    Based off your suggestions I believe I have been able to implement the STOP checking logic correctly (checking for STOP when bit0 is 0).

  • Hi M-W,

    I have spent time working on this over the past several days and believe I have come up with something that appears to be functioning correctly, although there are some areas that could use some more development.  As far as master to slave communication goes, I believe this was the easiest and should be, for the most part, working correctly.  I am a little concerned about STOP and repeated START conditions.

    As for slave to master communication the reSTART was the most difficult to deal with and let me explain the issues I had, then ask for advice about if the methods I chose to implement are reasonable.

    The problem lies in that I consider one interaction of the TASK to be a START ended by a STOP, or a timeout.  Some problems arise since the TASK takes time to be called again due to the RTC ticks I assume.  I resolved this by "stretching" the SCL line and holding it down until the task can begin again.  I don't know if this is correct usage of clock stretching, but it appears to work.

    I may have to upload some oscilloscope images to describe in more detail but when data is read from the slave (CC1312R) the master will first send the the device address, followed by a register address, then will do a START or reSTART.  At the time I detect this (SDA changes when SCL is high) I pull SCL low and hold it low and set a bit in state.i2cStatus (as this data survives task restarts).  When the task restarts the master has already sent the START so it is already ready to send the data and is just waiting for SCL to be released.  Then waiting for START Is skipped and immediately the first SCL high is the first bit of the device address that the master is sending and SCL is already high (since I released it).  I believe I stretch SCL as the first bit of the device address is coming through (after the reSTART).

    1) Can you provide me any feedback on how this is implemented?

    2) A little discussion about your recommendation quoted below, I chose to retain the multiple calls for the sake of timing since the timeout timing is based on how many instructions are executed.  I have the choice of either keeping the multiple IOBSTS BIOB1 instructions or replacing them with NOP to preserve the timeout timings.  I have attempted to implement timeouts in every part of the code where a loop happens waiting for SCL or SDA to change.

    M-W said:

    Hi Allan,

    If you look inte .red file you find:

    scifInitIo('SCL pin', AUXIOMODE_OPEN_DRAIN_WITH_INPUT, -1, 1);
    The "-1" here is "pull resistors disabled" so you should be fine in that regard. As I briefly explained before, the sensor controller can't control the pull resistors, this is why it is part of the initialization code from the ARM side. I think what is confusing is the comment about "pull-up" and "pull-down", what it refers to is close/open the drain. In other words, set -> drain open -> line is pulled up and vise verse.
    As for the responds, it all depends on code structuring, consider the following code:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /waitSclAck2Low:
    ; Wait for SCL to go low
    ; Indicates master is done reading SDA
        ; If SCL is low, we're done
        iobtst      #(AUXIO_I2C_SCL & 0x7), [#(IOP_AIODIO0_GPIODIN + (AUXIO_I2C_SCL >> 3))]
        biob0       /waitSclAck2LowDone
    jmp         /waitSclAck2Low
    /waitSclAck2LowDone:

    As you depend on the "jmp" to loop back, the responds is faster as iobtst + biob0 + jmp = 3 while iobtst + biob0 = 2 (which is why it was repeated. This way you also have a 2 instruction "responds" to waitSclAck2LowDone seen from the "tst".

    However, if you structure it this way:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    /waitSclAck2Low:
    ; Wait for SCL to go low
    ; Indicates master is done reading SDA
        ; If SCL is low, we're done
        iobtst      #(AUXIO_I2C_SCL & 0x7), [#(IOP_AIODIO0_GPIODIN + (AUXIO_I2C_SCL >> 3))]
        biob1       /waitSclAck2Low
    /waitSclAck2LowDone:

    You would have something similar, right? It is just that you inverse the test so the "loop" is still iobtst + biob1 (true) = 2 and iobtst + biob1 (false) = 2. 

    3) In question 1) above I talked about the problems I noticed when the task took time to restart, by default (or the sample that I used) the RTC was set to 1Hz, which I believe means that after the task finishes it is 1 second before it is schedule again for restart.  When a reSTART is necessary for an i2c READ the task needs to restart as soon as possible to catch the data, until the task is restarted I am holding SCL low (stretch I believe it is called).  Therefore I have changed some things in the CC1312 code as shown below.  This is a separate task based on an sample project I located in the sensor controller tutorials (great tutorial by the way).

    /*
     *  ======== tirtosScThread ========
     */
    void *tirtosScThread(void *arg0)
    {
        // SWI Initialization
        Swi_Params swiParams;
        Swi_Params_init(&swiParams);
        swiParams.priority = 3;
        Swi_construct(&swiTaskAlert, swiTaskAlertFxn, &swiParams, NULL);
        hSwiTaskAlert = Swi_handle(&swiTaskAlert);
    
    
        // Initialize the Sensor Controller
        scifOsalInit();
        scifOsalRegisterCtrlReadyCallback(scCtrlReadyCallback);
        scifOsalRegisterTaskAlertCallback(scTaskAlertCallback);
        scifInit(&scifDriverSetup);
    
        // Set the Sensor Controller task tick interval to 1 second
        uint32_t rtc_Hz = 1000;  // 1000Hz RTC
        scifStartRtcTicksNow(0x00010000 / rtc_Hz);
    
        // Configure Sensor Controller tasks
    
        // Start Sensor Controller task
        scifStartTasksNbl(BV(SCIF_I2C_SLAVE_TASK_ID));
    
        while (1) {
    
        }
    }
    

    As you can see I changed the Hz to 1000, what is the limit to this?  How fast can the task tick interval be?  I would like to restart the task as soon as possible after the present one is done to wait for the next i2c communication.

    4) Related to question 3) as I increase the task frequency what is the impact on current consumption?  I have not done any measurements yet but having the task wait in a loop waiting for SCL or SDA to go low for a START condition seems like it may not be the most efficient use of MCU time, but, what other choice is there?

    Things I have not implemented yet is setting data in the sensor controller from the main MCU (registers for the i2c master to read) and a higher current pull on the SDA/SCL lines.  Right now the CC1312R is not pulling them down as low as it should be but I think its a simple setting but have not found it yet.

    5) Can you briefly explain the last parameter on the following code?  I have not been able to locate a good explanation for it as of yet.

    scifInitIo('SCL pin', AUXIOMODE_OPEN_DRAIN_WITH_INPUT, -1, 1);

    The second parameter "-1" is no pull-up, 1 appears to set pull-up, what does 0 do?
    What is the purpose of the third parameter?  Is this also how I can set the the GPIO current setting? I believe default is 2mA, but 3mA or 4mA might be better for i2c.

    6) Based on how I have chosen to implement some of the program I am using R7, R6, R5, R4, and R3.  Are there any concerns with using R3?  I didn't run into any example code that was using R3 yet.  What registers are safe to use?  Due to some tricky things concerning R0 usage so I have left that register alone now.  Does R0 act as the working register (WREG on some MCUs)?  Sometimes I would like to do an XOR or something into WREG, but I end up needing to use a different register, such as R3, when I want to compare R7 and R5, but not effect what is stored in R7 or R5.  I have to copy the contents to R3 to do that comparison.  Is there a more efficient way of doing an XOR to compare two registers and effect the Z status bit but not change the contents of the two registers?

    7) One final concern is the size of the code that I have made, it looks like the sensor controller about 58% or so filled, which is ok but have not researched what the maximum code size is for the sensor controller yet, I am assuming I don't have anything to worry about running out of program memory yet.

    EDIT: 8) After some testing today without the sensor controller running I can achieve about 12uA consumption during idle (for the 6lowpan project that I am incorporating this into).  If I add in the sensor controller this jumps to about 770uA idle.  Any recommendations on this? I think this is due to waiting in a loop for i2c START.

    Thank you for taking the time to help out with this project and the help is really appreciated.

    I am uploading the complete CCS10 project and the SCE files.  The SCE zip file contains all the folders from the "C:\Users\user\Documents\Texas Instruments\Sensor Controller Studio" folder, where the custom assembly I have been working on resides.  This should allow anyone to reconstruct the development environment that I am using.

    For hardware I am using a CC1312R LaunchPad, RevD (revision E silicon I believe)
    The SCL and SDA pins are the ones common to other CC1312R sample code, DI04 and DI05.
    Pull-up resistors to 3V3 are needed on the pins by the i2c master.

    For testing I have been using a few i2c masters, MSP-EXP430F5529LP, Arduino based ATMEGA328P, ATTINY85.
    MSP430 sends 'F', '4', '1', '9', '2', 'B' to the CC1312R.  The MSP430 has a tendency to hang if the slave doesn't release the SDA or SCL correctly, something unrelated but I should look into.
    Arduino is easy to see in the code, which I will also upload.  The "rx" appended schetches are testing i2c read (slave to master transmission).
    The final project could use any of the above as i2c masters.  I am also uploading the MSP430 code as I think you may have that hardware readily available.

    While you can "stack" the MSP-EXP430F5529LP directly on the CC1312R I would recommend against it as the RESET lines also get connected and makes things act funny as you are debugging things.  All that needs to be connected is 3V3, GND, P4.1, and P4.2 to their corresponding places on the CC1312R LaunchPad.  I program the MSP430 then disconnect the microUSB from it as it is powered from the CC1312R.  Also of concern is the TMS, TCK, TDO, TDI, and SDW programming lines that maybe shouldn't be all tied together so the jumpers on the MSP430 would have to be removed on the SBWTDIO and SBWTCK at minimum, RS232 RTS, CTS, RXD, and TXD is not a bad idea either to avoid merging the signals on the two MCUs.

    The version of the tools that I am using in Windows 7 64-bit are as follows.
    Code Composer Studio Version: 10.1.0.00010.
    SDK SimpleLink 13x2 26x2 4.10.0.78
    Sensor Controller Studio Version: 2.6.0.32 Patches 1, 2, and 3.>br />Arduino 1.8.9 (this is on MacOSX)

    Although my primary development environment is MacOSX, I use Parallels Desktop to run Windows 7 64-bit for applications like SCE that do not yet have a MacOSX port.  Have not had USB connectivity issues with Parallels Desktop.
    VirtualBOX does not have good USB pass through support and is not usable for this, if anyone is curious about using VirtualBox with SCE or CCS.

    Updating the XDS110 firmware in Parallels Desktop can be tricky as the USB device re-enumerates multiple times during the firmware update.
    A little off topic, but some information for others thinking about a virtual development environment.

    File names should be self explanatory.  Rather then copy-paste a lot of code in the thread it probably better to upload the files.

    6278.Sensor Controller Studio_20200628.zip

    7875.i2cslave_CC1312R1_LAUNCHXL_tirtos_ccs_20200628.zip

    8867.tirtos_builds_CC1312R1_LAUNCHXL_release_ccs.zip

    4135.MSP430F55xx_usci_i2c_standard_master_20200628.zip

    0216.arduino_20200628.zip

  • Hi AllanD,

    1) It seems you got it to work so that is good. On the Task part of it, I would advice to try to implement it in such a way that the execution/event code does not return unless you are going to be inactive. In other words, guarding your calls inside a "while active" type of loop. There should be no need to depend on RTC ticks for you to run your task.

    3) Interval could in theory be 1 RTC tick so 31 us, more reasonable is 3-4 ticks taking some special requirements into consideration. I think you should try to keep it to running the task once only and the task not returning until you set a variable telling it to end (have a look on how the UART emulator works).

    4) This depends on the power level of the SC, if it runs at 24 MHz or 2 MHz. Typically you could count with SC consuming ~500uA in 24 MHz while active. It would consume a lot less in 2 MHz mode (but you get less instructions to work with). 

    Current pull etc is part of teh ARM side configuration and thus the .red file. It is nothing you can do from the SC.

    5) It is the initial output value of the IO (when configured as output). You can control the drive strength with the second parameter to or in:

    MODE | ((0 "low" / 1 "medium" / 2 "high") << BI_AUXIOMODE_OUTPUT_DRIVE_STRENGTH)

    6) As long as you only use our own APIs (and do not mix with other SCS APIs) you could use any register. R0 and R1 have some special usage together with some instructions, but it is only as "input" and they are not used as working registers (so what you put in them are safe sort of).

    There is really no way of comparing without alerting but the "tst" and "cmp" instructions. 

    7) You got around 4 KB of AUX RAM, so about 2000 words worth of code and buffers. To this there is 10-20% "boiler plate" code you are not getting rid of so you have about 80% free to play with.

    8) That sounds reasonable. What you would need to do in order to save power here is to allow the device to enter into ow-power mode by waiting for an IO event (like the START falling edge). This is however a bit tricky as you are limited to ~150us start-up time if using the 24 MHz clock (it takes only 32us to wake up to 2 MHz mode). This means you likely would need to implement some "wake-up" condition of the slave code so that it can be allowed to sleep and start working only when the master is active. 

    There is some approaches to this but I suggest you look into the "GPIO Event Trigger" API in the SC and how to use that to wait for a GPIO event.

  • Hi M-W,

    Thank you very much for your responses prior to vacation.

    I thought a little about current consumption and think the best approach is to use a GPIO and either wait for falling edge or keep checking it every task iteration.  If I do this, I will use one more GPIO to signal the master that the sensor controller is ready to receive data.  This should allow it to sleep, and allow the master to be able to send data (with some delay waiting for wake up).  I think this might be the best approach to solve the current consumption issue.

    At this time I will go ahead and mark this thread as solved and when another question arrises I will open a new thread referencing this one.

    Thank you for all your assistance and suggestions.

    Best regards,

    Allan

  • Sounds like a good plan!

    Thank you and good luck on the future the implementation work :)