News:

Let's find out together what makes a PIC Tick!

Main Menu

I2C in Timer1 ISR possible?

Started by basparky, Aug 04, 2021, 08:36 AM

Previous topic - Next topic

basparky

Hi,

I would like to setup I2C in a way it can be used in 'background'. Is this possible? i cannot find much info in the datasheet yet:(
This for a hobby servo project using a PCA9685 PWM driver. I would like to do position control for 8 servo's sort off simultaneous moving at different speeds to different positions.

Currently i'am using I2Cout command in a main loop called every 20ms but having troubles doing other stuff while i need to send the new posdata for the servo's over I2C. This makes it hard to nicely control the 8 positions.

The pic i'm using is a dsPIc33.

Many thanks for any suggestions!

david

Hi,
I'm not clear why you feel the I2C needs to be in the background as it's only used when needed and shouldn't be intrusive in any way.
I've not used the PWM chip you mention but I assume that every 20mS frame period you send the data needed for each of the 8 channels which should only be a short burst of I2C.  Perhaps if you can explain the payload required I may appreciated your situation better.
Another approach is to output all channels simultaneously rather than sequentially and this also overcomes the typical 8 channel limit fitting in to the 20mS frame rate.  All channels are turned on every 20mS and are turned off when their individual channel values are exceeded by a background timer. This is done by the micro itself and a PWM chip is not required. See the attached snippet.   Please note the first line is decoding Flysky's serial iBus protocol as would be directly output from an iBus receiver.  It really is that simple to decode.  So the channel data transfer rate is 115.2kbaud and all outputs should be low after about 2mS so you should have 18mS to do other tasks before you need to update again.
There are also commercial products that can do iBus to PWM conversion-
http://store.fpvbox.com/index.php?route=product/product&product_id=100
You would then just have to send serial iBus data to it - 115.2kbuad, 8N1, 2 start bits followed by (up to) 14x word values of the channel uS required sent little endian, 2 byte checksum/stop bit.
If you think you're getting close with the PWM chip then stick with it but we may need a little more detail to figure out how best to help.

Cheers,
David

     HRSIn Wait($20,$40),Ch1,Ch2,Ch3,Ch4,Ch5,Ch6,Ch7,Ch8   
     T1CON=%00110001     'start timer
     DelayUS 10          'fudge factor for loop delay.
     PORTB=255            'set 8 outputs high
         
chloop:
      Time=TMR1L+(TMR1H<<8)        'fix the time
      If Time>Ch1 Then PORTB.0=0   'compare channel
      If Time>Ch2 Then PORTB.1=0
      If Time>Ch3 Then PORTB.2=0
      If Time>Ch4 Then PORTB.3=0
      If Time>Ch5 Then PORTB.4=0   'compare channel
      If Time>Ch6 Then PORTB.5=0
      If Time>Ch7 Then PORTB.6=0
      If Time>Ch8 Then PORTB.7=0
      If TMR1H>42 Then GoTo begin    'Approx TMR1=11000
      GoTo chloop
 

basparky

Thanks for thinking with me.

The PCA9685 PWM driver is a great driver to control 16 channels. Need only to send pwm value over I2C.

In this case would like to send 12 bytes every 20ms this to archive smooth movement at different speeds. I'm struggling to create smooth movements over time with 'cheap' hobby servo's i'm using...
Currently i'm calling this Proc every 20ms. Meanwhile i'm also using UART and doing other stuff. this influences the 20ms interval.

I was hoping its possible to use a dedicated interrupt routine to hande the I2C bus in the background. This just like U1RX or U1TX Interrupt.

I noticed from the datasheet that I2CTRN register is the I2C byte which will be transmitted. I assume this can be used like SPI, right?
I hope this makes any sence.





Proc PCA9685Loop(BrilServo As Word,OgenServo As Word,MondServo As Word,HoofdServo As Word,KnikServo As Word)
Dim ServoOn As Word
ServoOn = 0
    'I2Cout all servopositions starting at adrress 0 (6)
    'Loop this every 20ms = 50Hz.   
    I2COut SDA, SCL, I2CAddress,6,[ServoOn.LowByte,ServoOn.HighByte,BrilServo.LowByte,BrilServo.HighByte,OgenServo.LowByte,OgenServo.HighByte,MondServo.LowByte,MondServo.HighByte,HoofdServo.LowByte,HoofdServo.HighByte,KnikServo.LowByte,KnikServo.HighByte]
   
EndProc

david

Hi,
Looks like a good chip to use but I see you are bit bashing the I2C and I would have thought the micro you are using would likely have a hardware I2C.  It may allow you to run a faster bus but it's probably academic. Note that with the I2COut command you can work with Word size variables (as per your first line) rather than using high byte and low byte.  It makes things easier to follow but won't really change things as I2C is byte oriented. It also depends on the source of your servo channel data but this can also be Word size variables too.
If you want to try running without an interrupt you would need to use a 20mS loop time and ensure all processes within the loop have a fairly constant execution time such that the 20mS loop remains roughly constant under all conditions.

Loop:
Update PWM chip
Serial In or Out
Calculations
Dwell time
Repeat

If you think this is not possible you will be forced to look at a background timer and interrupt routine for the PWM update.  By most standards this would be a long interrupt.
Cheap hobby servos will have about 8-10uS of deadband but are fine for just moving to a new position.  In normal use they are repeatedly given a new, fixed target value and they just move as fast as they can to get there.  If you want them to move slowly you need to send incrementing target positions such that they only move a small amount in each 20mS frame.  Obviously with a 10uS deadband there is little point in sending increments less than this value.  If you were wanting to move 1mS of PWM duty cycle (1000uS - 2000uS) then you would need 100x 10uS incrementing commands to get to the new position and this would take 100x 20mS =2 seconds.  You could try 5uS but the fine movement may be a little jerky when viewed closely.  Fully digital servos would greatly reduce the deadband but may be prone to hunting around the neutral point.
Sorry - I'm not familiar with I2CTRN in my smaller PICs but do you need to get down to this register level?  Hardware I2C is very simple to use and easier than even serial.

Cheers,
David

basparky

Thanks..
I have tried the hardware i2C but without success. Meanwhile i made the following but haven't got any pulses on the lines yet. Hoping to get this working, enabling the interrupt for this and run it in background.

Anyone experience with this?

I2C1CONbits_I2CEN   = 1

Proc I2C_WriteConfig(pAddress As Byte, pRegister As Byte,pValue As Byte),Byte
'Registers on page 277 from datasheet Dspic33EP256GP502

    'write address:
    I2C1CONbits_SEN = 1                              'Send I2C start condition
    While I2C1STATbits_TRSTAT = 1 : Wend             'transmit status bit
    I2C1TRN = pAddress
    While I2C1STATbits_TRSTAT = 1 : Wend             'transmit status bit
    If I2C1STATbits_ACKSTAT = 1 Then                 'Acknowledge Status 0 = Acknoledge received from slave
        I2C1CONbits_PEN = 1                          'Stop Condition Enable bit (when operating as I2C master)
        While I2C1CONbits_PEN = 1 : Wend
        Result = I2CError
    EndIf


   
    'write Register number:
    I2C1TRN = pRegister
    While I2C1STATbits_TRSTAT = 1 : Wend             'transmit status bit
    If I2C1STATbits_ACKSTAT = 1 Then                 'Acknowledge Status 0 = Acknoledge received from slave
        I2C1CONbits_PEN = 1                          'Stop Condition Enable bit (when operating as I2C master)
        While I2C1CONbits_PEN = 1 : Wend
        Result = I2CError
    EndIf 
   

   
    'LowWord value:
    I2C1TRN = pValue
    While I2C1STATbits_TRSTAT = 1 : Wend             'transmit status bit
    If I2C1STATbits_ACKSTAT = 1 Then                 'Acknowledge Status 0 = Acknoledge received from slave
        I2C1CONbits_PEN = 1                          'Stop Condition Enable bit (when operating as I2C master)
        While I2C1CONbits_PEN = 1 : Wend
        Result = I2CError
    EndIf
 

   
    I2C1CONbits_PEN = 1                          'Stop Condition Enable bit (when operating as I2C master)
    While I2C1CONbits_PEN = 1 : Wend
    Result = I2CSendOk
   
EndProc

david

#5
Hi,
Sorry to hear you're still having a hard time of it but I wonder if you're not overthinking the process.
You should be able to use just the two commands - I2CIn and I2COut for bit bashed or Hbusin and Hbusout for devices with a SSP.
Determine device address +Write bit
Send initial device set up data
Send PWM update data  (repeating)

I've not fully read the datasheet on the PWM chip but it looks like each channel requires 2x 12 bit values (0-4096) i.e. 3 bytes.  The first 3 nibbles define the time to the start of the duty cycle and the latter 3 define the time to the end of the duty cycle.  Not the most convenient arrangement but workable.  Please correct me if my understanding is wrong.  You also have auto increment so all updates can be addressed to register 6 followed by 24 bytes of data (8ch x 3 bytes)
I think you were on to it with your line-
I2COut SDA, SCL, I2CAddress,6,[ServoOn.LowByte,ServoOn.HighByte,BrilServo.LowByte,BrilServo.HighByte..etc...]
provided the initial set up was done correctly and that it can accept 2 bytes per channel (??)  I would have thought the auto increment would get confused and allocate 3 consecutive bytes to the one output.  As I say - I've not fully read the datasheet so could well be wrong.  Good luck.

Cheers,
David

John Drew

#6
Hi,
Hardware I2C works really well. Unfortunately some of the newer PICs have radically changed things which is why Les is looking at not supporting HBusout in the newer PICs.
That aside, if you are not using one of the latest chips using HBUS is a "piece of cake".
Set the declares then send the data like this:
Proc SendToLCD(DataOut as byte)        'in this case SendToLCD is the address of the device             
       HBusOut I2CPrintAddress,[Dataout]
       Repeat
       Until SSPSTAT.0 = 0  'check if the buffer is empty
EndProc

The trick is to check that the buffer is empty before sending more data otherwise you'll overwrite the buffer. Which might have been where you were going wrong.
Because of this hardware buffer in effect you are speeding up your I2C send by 1 byte.
e.g. if you are sending two bytes inside an interrupt the second byte does not take any significant interrupt time as far as the sending is concerned as it happens in the background. There is no such advantage with bit bashed where you have to wait for both bytes to be sent.

If you are sending 8 bytes then you save 12% time inside the interrupt by using hardware.

If HBusout isn't supported then it's no big deal to setup the registers and then use a procedure to write directly to the buffer.

John

Edit: SSPSTAT may have to be SSPSTAT1 or SSPSTAT2 if your device has two MSSP ports.