SAMD51 DMAC Memory to I2C

Go To Last Post
8 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Been at this for more than one day, can't get it to work. Atmel SAMD51J19 chip. Just sits there and does nothing. Not sure where this is going wrong or at what part. When I debug it it runs without any errors and yet still doesn't appear to do anything. Not sure if it's I2C, DMAC, or what's casing it to not work. If someone can look it over and tell me if I went wrong somewhere I'd be very appreciative.

 

  constexpr var32 sercom5_speed = 12000000;

  static const var8 i2cXfer[] = {
    // IODIRA = All Out
    0x00,
    0x00,

    // IODIRB = All Out
    0x01,
    0x00,

    // GPIOA = All Positive
    0x12,
    0xFF,

    // GPIOB = All Positive
    0x13,
    0xFF,
  };

  constexpr var8 channels = 1;

  DmacDescriptor __attribute__((aligned(128))) dmaBase[channels];
  DmacDescriptor __attribute__((aligned(128))) dmaWrtBk[channels];

  // Enable all priority levels (Don't ignore any descriptor based on priority)
  DMAC->CTRL.reg = DMAC_CTRL_LVLEN0 | DMAC_CTRL_LVLEN1 | DMAC_CTRL_LVLEN2 | DMAC_CTRL_LVLEN3;

  // Allow to run in debug mode
  DMAC->DBGCTRL.bit.DBGRUN = true;

  // Setup priority system
  // A simple setup, all round-robin and priority 0-3 will have highest to lowest latency
  // awareness and elimination

  // For priority 0
  // Round-Robin, Critical Latency Control
  DMAC->PRICTRL0.reg = DMAC_PRICTRL0_RRLVLEN0 | DMAC_PRICTRL0_QOS0_CRITICAL |

  // For priority 1
  // Round-Robin, Medium Latency Control
                      DMAC_PRICTRL0_RRLVLEN1 | DMAC_PRICTRL0_QOS1_SENSITIVE |

  // For priority 2
  // Round-Robin, Some Latency Control
                      DMAC_PRICTRL0_RRLVLEN2 | DMAC_PRICTRL0_QOS2_SHORTAGE |

  // For priority 3
  // Round-Robin, No Latency Control
                      DMAC_PRICTRL0_RRLVLEN3 | DMAC_PRICTRL0_QOS3_REGULAR;

  // Set memory addresses for base and write-back
  DMAC->BASEADDR.bit.BASEADDR = (var32)(dmaBase);
  DMAC->WRBADDR.bit.WRBADDR = (var32)(dmaWrtBk);

  // Enable
  DMAC->CTRL.bit.DMAENABLE = true;

  ////////////////////////
  /// DMAC Descriptor Setup
  ////////////////////////

  // Destination Address Increment Disabled
  DMA::dmaBase[0].BTCTRL.reg = DMAC_BTCTRL_STEPSIZE_X1 | // Address Increment Step Size = 1 byte
                           DMAC_BTCTRL_STEPSEL_SRC | // Step size applies to source address
                           DMAC_BTCTRL_SRCINC | // Increment Src Address by step size
                           DMAC_BTCTRL_BEATSIZE_BYTE | // Beat Size = 1 Byte
                           DMAC_BTCTRL_BLOCKACT_NOACT | // Disable channel at end of transaction
                           DMAC_BTCTRL_EVOSEL_DISABLE | // Do nothing with event output
                           DMAC_BTCTRL_VALID; // This channel is valid and ready for use

  // Number of blocks in a transfer
  var32 s = sizeof(i2cXfer);
  DMA::dmaBase[0].BTCNT.reg = sizeof(i2cXfer);

  DMA::dmaBase[0].SRCADDR.reg = (var32)i2cXfer + sizeof(i2cXfer); // (var32)i2cXfer;
  DMA::dmaBase[0].DSTADDR.reg = (var32)&SERCOM5->I2CM.DATA.reg; // ??? Maybe ??? SERCOM 5
  DMA::dmaBase[0].DESCADDR.reg = 0x00000000; // Transaction has no more descriptors

  ////////////////////////
  /// DMAC Setup
  ////////////////////////

  // Create DMAC Entry for I2C
  DMAC->Channel[0].CHCTRLA.reg = DMAC_CHCTRLA_THRESHOLD_1BEAT | // Write after 1 beat
                                 DMAC_CHCTRLA_BURSTLEN_SINGLE | // Single Beat Burst
                                 DMAC_CHCTRLA_TRIGACT_BURST | // On each trigger write another 1-byte block
                                 DMAC_CHCTRLA_TRIGSRC(0x0F) | // Trigger when SERCOM5 TX Register becomes empty
                                 DMAC_CHCTRLA_RUNSTDBY; // Run in standby

  // No Action ???
  DMAC->Channel[0].CHCTRLB.bit.CMD = DMAC_CHCTRLB_CMD_NOACT;

  // Lowest Priority
  DMAC->Channel[0].CHPRILVL.bit.PRILVL = 0;

  // Don't do anything with event inputs or outputs
  DMAC->Channel[0].CHEVCTRL.reg = 0;

  ////////////////////////
  /// I2C Setup on SERCOM 5
  ////////////////////////

  // Setup SDA & SCL Pins
  // Continuous Sampling, Output Enable default to ground
  // Enable Input and Multiplex to D (Sercom)
  PORT->Group[1].CTRL.bit.SAMPLING |= (1 << 2) | (1 << 3);
  PORT->Group[1].DIRSET.bit.DIRSET = (1 << 2) | (1 << 3);
  PORT->Group[1].OUTCLR.bit.OUTCLR = (1 << 2) | (1 << 3);

  PORT->Group[1].PINCFG[2].bit.INEN = true;
  PORT->Group[1].PINCFG[2].bit.PMUXEN = true;

  PORT->Group[1].PINCFG[3].bit.INEN = true;
  PORT->Group[1].PINCFG[3].bit.PMUXEN = true;

  PORT->Group[1].PMUX[2 / 2].bit.PMUXE = 3;
  PORT->Group[1].PMUX[3 / 2].bit.PMUXO = 3;

  // Use Fast Mode (Fs) For 400KHz Timing

  // Slave SCL Low Extend Time-Out (DISABLED)
  // Master SCL Low Extend Time-Out (DISABLED)
  // SDA Hold Time (DISABLED)
  // Pin Usage (2 PINS)
  // Don't Reset
  // Keep Disabled
  SERCOM5->I2CM.CTRLA.reg = SERCOM_I2CM_CTRLA_INACTOUT(3) | // Inactive Time-Out 20-21 SCL cycle time-out
                            SERCOM_I2CM_CTRLA_SPEED(0) | // Fs (Up to 400KHz)
                            SERCOM_I2CM_CTRLA_RUNSTDBY | // Run in Standby
                            SERCOM_I2CM_CTRLA_MODE(5); // Master Mode

  // Acknowledge Action (Send ACK)
  // CMD (No Action) ??? Used for DMA ???
  // Quick Command (DISABLED)
  SERCOM5->I2CM.CTRLB.reg = SERCOM_I2CM_CTRLB_SMEN; // Smart Mode

  // Data 8-bit
  SERCOM5->I2CM.CTRLC.reg = 0;

  // High Speed BAUD High & Low Disabled
  // Baud High and Low Set to 400KHz
  constexpr var32 i2c_speed = 400000; // 400KHz
  SERCOM5->I2CM.BAUD.reg = SERCOM_I2CM_BAUD_BAUD(sercom5_speed / i2c_speed); // Baud Rate = 400KHz [BAUD=30]

  // Address settings
  // Ten Bit Addressing Disabled
  SERCOM5->I2CM.ADDR.reg = SERCOM_I2CM_ADDR_LEN(1) | // 1 Byte Transactions???
                           SERCOM_I2CM_ADDR_LENEN | // Transaction Length Enable (For DMA)
                           SERCOM_I2CM_ADDR_ADDR((0x20 << 1) | 0); // Address slave 0x20 in write mode (Bit 0 = 0)

  // Run in debug mode
  SERCOM5->I2CM.DBGCTRL.bit.DBGSTOP = false;

  // Enable
  SERCOM5->I2CM.CTRLA.bit.ENABLE = true;

  // Wait for bus to become ready and sync to end
  while(SERCOM5->I2CM.SYNCBUSY.reg);
  while(SERCOM5->I2CM.STATUS.bit.BUSSTATE != 1);

  ////////////////////////
  /// Activate DMAC
  ////////////////////////

  // Enable I2C DMAC
  DMAC->Channel[0].CHCTRLA.bit.ENABLE = true;
  

 

Last Edited: Fri. Nov 15, 2019 - 07:26 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If that's all code then you have not enabled SERCOM5 and it has no clock, i.e., missing

    MCLK->APBDMASK.bit.SERCOM5_ = 1;
    GCLK->PCHCTRL[SERCOM5_GCLK_ID_CORE].bit.GEN = 0;
    GCLK->PCHCTRL[SERCOM5_GCLK_ID_CORE].bit.CHEN = 1;

Doing this

  // Address settings
  // Ten Bit Addressing Disabled
  SERCOM5->I2CM.ADDR.reg = SERCOM_I2CM_ADDR_LEN(1) | // 1 Byte Transactions???
                           SERCOM_I2CM_ADDR_LENEN | // Transaction Length Enable (For DMA)
                           SERCOM_I2CM_ADDR_ADDR((0x20 << 1) | 0); // Address slave 0x20 in write mode (Bit 0 = 0)

before 

  // Enable
  SERCOM5->I2CM.CTRLA.bit.ENABLE = true;

  // Wait for bus to become ready and sync to end
  while(SERCOM5->I2CM.SYNCBUSY.reg);
  while(SERCOM5->I2CM.STATUS.bit.BUSSTATE != 1);

 

 

seems wrong. And SERCOM_I2CM_ADDR_LEN(1) there, it should be sizeof(i2cXfer) and not 1 right?

/Lars

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Not sure if it's I2C, DMAC, or what's casing it to not work.

When you write that I sure hope you already have a version without DMAC that works.

/Lars

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
  1. I commented out DMAC and focused on I2C re-building it to work manually because I did get ahead of myself trying to get too much working at the same time.
  2. I already had the clocks enabled in another part of the code, apologies for forgetting to post it.
  3. I tend to put everything not important to set after the peripheral has been enabled before enabling it to avoid running into "syncing" and "enable protections" if it wasn't needed after enabling such as when initially setting up the component. However "ADDR" is one example that is required after enabling.

 

Essentially I found out that there's literally no point in having an ADDR setup at all before enabling because for some odd reason the act of setting the ADDR tells it to "Start Working" after enabling it So you'd have to set it twice in my example above. Out of all the ways to ask it to start or re-start, writing or re-writing the ADDR register is interesting. I changed it to this.

SERCOM5->I2CM.ADDR.reg = 0;

and then this after enabling

 

  // Enable
  SERCOM5->I2CM.CTRLA.bit.ENABLE = true;

  // Wait for bus to become ready and sync to end
  while(SERCOM5->I2CM.SYNCBUSY.reg);
  while(SERCOM5->I2CM.STATUS.bit.BUSSTATE != 1);

  // Address slave 0x20 in write mode (Bit 0 = 0)
  SERCOM5->I2CM.ADDR.bit.ADDR = (0x20 << 1) | 0; // <----------

I discovered this kick starts the I2C process

 

After that I discovered much of the options I had set were redundant, having no effect, so I removed them.

 

// This more minimal code works for manual mode

PORT->Group[1].PINCFG[3].reg = PORT_PINCFG_PMUXEN | PORT_PINCFG_INEN;
PORT->Group[1].PMUX[3 / 2].bit.PMUXO = 3;

PORT->Group[1].PINCFG[2].reg = PORT_PINCFG_PMUXEN | PORT_PINCFG_INEN;
PORT->Group[1].PMUX[2 / 2].bit.PMUXE = 3;

SERCOM5->I2CM.CTRLA.reg = SERCOM_I2CM_CTRLA_INACTOUT(3) | // Inactive Time-Out 20-21 SCL cycle time-out
    SERCOM_I2CM_CTRLA_SPEED(0) | // Fs (Up to 400KHz)
    SERCOM_I2CM_CTRLA_RUNSTDBY | // Run in Standby
    SERCOM_I2CM_CTRLA_MODE(5); // Master Mode
    
SERCOM5->I2CM.CTRLB.reg = SERCOM_I2CM_CTRLB_SMEN; // Smart Mode

SERCOM5->I2CM.CTRLC.reg = 0;

constexpr var32 i2c_speed = 400000; // 400KHz
SERCOM5->I2CM.BAUD.reg = SERCOM_I2CM_BAUD_BAUD(sercom5_speed / i2c_speed); // Baud Rate = 400KHz [BAUD=30]
  
SERCOM5->I2CM.ADDR.reg = 0;

SERCOM5->I2CM.DBGCTRL.bit.DBGSTOP = false;

SERCOM5->I2CM.INTENSET.reg = SERCOM_I2CM_INTENSET_ERROR | // set when any error is detected
                             SERCOM_I2CM_INTENSET_MB; // when a byte is transmitted with or without error and/or loss

NVIC_EnableIRQ(SERCOM5_0_IRQn); // MB
NVIC_EnableIRQ(SERCOM5_3_IRQn); // Error

SERCOM5->I2CM.CTRLA.bit.ENABLE = true;

while(SERCOM5->I2CM.SYNCBUSY.reg);
while(SERCOM5->I2CM.STATUS.bit.BUSSTATE != 1);

SERCOM5->I2CM.ADDR.bit.ADDR = (0x20 << 1) | 0;

So now that I have I2C setup, I'll re-tackle DMAC and see what I can figure out. Did the DMAC code look ok or did anything appear wrong there?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Also, to add

 

SERCOM_I2CM_ADDR_LEN(1) there, it should be sizeof(i2cXfer) and not 1 right?

After playing around with SERCOM_I2CM_ADDR_LEN in manual mode and adding all different numbers up to and including 0, it made no difference on the outcome. That's why above I just removed it it entirely from the new setup and it still worked as expected. I'm sure this option does something but all I know now is everything works normally no matter what number I placed there.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes, that is for DMAC. this is very clear in the datasheet, you have read it right?

/Lars

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hostile much? I've been reading the data sheet literally every single day for the past 3 months, no joke, I read it so often at least 3 copies are open 24/7 in my browser along with a permanent bookmark, forgive me if I overlooked a single detail in mass of information contained in the datasheet. Also I will do everything in my power to figure it out myself, including scanning through the data sheet, before asking for help online because I generally have a 1 out of a million chance of receiving useful help when I ask any question online anywhere on the internet.

 

Anyways the DMAC is not working and after spending the past few hours on it my theory is that while I have the DMAC listening for the SERCOM 5 TX register being ready for new data, my theory is SERCOM 5 is not communicating with DMAC. No matter what I do, when I try to use DMAC it only outputs 0xFFFFFFFF on the I2C bus to the correct address. It also does this when I've disabled DMAC. After poking around on the data sheet I found a reference saying SERCOM must be setup to output the TX signal that certain peripherals like DMAC need when listening to it.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
 before asking for help online because I generally have a 1 out of a million chance of receiving useful help when I ask any question online anywhere on the internet.

True, so I advice you don't call people trying to help hostile. 

/Lars