bool rtc_read(uint8_t addr, uint8_t *data, uint8_t count) { // fail gracefully if count is zero if (count == 0) return true; // write chip address SERCOM3->I2CM.ADDR.reg = RTC_ADDRESS_WRITE; while(SERCOM3->I2CM.STATUS.bit.SYNCBUSY); while(SERCOM3->I2CM.INTFLAG.bit.MB == 0); if (SERCOM3->I2CM.STATUS.reg & (SERCOM_I2CM_STATUS_RXNACK|SERCOM_I2CM_STATUS_BUSERR|SERCOM_I2CM_STATUS_ARBLOST)) { goto rtc_read_fail; } // write register address SERCOM3->I2CM.DATA.reg = addr; while(SERCOM3->I2CM.STATUS.bit.SYNCBUSY); while(SERCOM3->I2CM.INTFLAG.bit.MB == 0); if (SERCOM3->I2CM.STATUS.reg & (SERCOM_I2CM_STATUS_RXNACK|SERCOM_I2CM_STATUS_BUSERR|SERCOM_I2CM_STATUS_ARBLOST)) { goto rtc_read_fail; } // writing RTC_ADDRESS_READ sends repeated start, // sends address and receives first byte. SERCOM3->I2CM.ADDR.reg = RTC_ADDRESS_READ; while(SERCOM3->I2CM.STATUS.bit.SYNCBUSY); while(count--) { // wait for data byte to arrive while(SERCOM3->I2CM.INTFLAG.bit.SB == 0); // grab data *data++ = SERCOM3->I2CM.DATA.reg; if (count) { // if we have another byte to receive, ACK this transmission and start another xfer SERCOM3->I2CM.CTRLB.reg = (0 << SERCOM_I2CM_CTRLB_ACKACT_Pos) | SERCOM_I2CM_CTRLB_CMD(2); while(SERCOM3->I2CM.STATUS.bit.SYNCBUSY); } else { // we're done receiving, so NACK and STOP. SERCOM3->I2CM.CTRLB.reg = (1 << SERCOM_I2CM_CTRLB_ACKACT_Pos) | SERCOM_I2CM_CTRLB_CMD(3); while(SERCOM3->I2CM.STATUS.bit.SYNCBUSY); } } return true; rtc_read_fail: SERCOM3->I2CM.CTRLB.reg = (1 << SERCOM_I2CM_CTRLB_ACKACT_Pos) | SERCOM_I2CM_CTRLB_CMD(3); while(SERCOM3->I2CM.STATUS.bit.SYNCBUSY); return false; }
/** * \internal * Address response. Called when address is answered or timed out. * * \param[in,out] module Pointer to software module structure * * \return Status of address response. * \retval STATUS_OK No error has occurred * \retval STATUS_ERR_DENIED If error on bus * \retval STATUS_ERR_PACKET_COLLISION If arbitration is lost * \retval STATUS_ERR_BAD_ADDRESS If slave is busy, or no slave * acknowledged the address */ static enum status_code _i2c_master_address_response( struct i2c_master_module *const module) { /* Sanity check arguments */ Assert(module); Assert(module->hw); SercomI2cm *const i2c_module = &(module->hw->I2CM); /* Check for error and ignore bus-error; workaround for BUSSTATE stuck in * BUSY */ if (i2c_module->INTFLAG.reg & SERCOM_I2CM_INTFLAG_SB) { /* Clear write interrupt flag */ i2c_module->INTFLAG.reg = SERCOM_I2CM_INTFLAG_SB; /* Check arbitration. */ if (i2c_module->STATUS.reg & SERCOM_I2CM_STATUS_ARBLOST) { /* Return packet collision. */ return STATUS_ERR_PACKET_COLLISION; } /* Check that slave responded with ack. */ } else if (i2c_module->STATUS.reg & SERCOM_I2CM_STATUS_RXNACK) { /* Slave busy. Issue ack and stop command. */ i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3); /* Return bad address value. */ return STATUS_ERR_BAD_ADDRESS; } return STATUS_OK; }
/** * \internal * Read next data. Used by interrupt handler to get next data byte from slave. * * \param[in,out] module Pointer to software module structure */ static void _i2c_master_read( struct i2c_master_module *const module) { /* Sanity check arguments. */ Assert(module); Assert(module->hw); SercomI2cm *const i2c_module = &(module->hw->I2CM); /* Find index to save next value in buffer */ uint16_t buffer_index = module->buffer_length - module->buffer_remaining; module->buffer_remaining--; if (!module->buffer_remaining) { /* Send nack */ i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_ACKACT; if (module->send_stop) { /* Send stop condition */ _i2c_master_wait_for_sync(module); i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3); } } else { i2c_module->CTRLB.reg &= ~SERCOM_I2CM_CTRLB_ACKACT; } /* Read byte from slave and put in buffer */ _i2c_master_wait_for_sync(module); module->buffer[buffer_index] = i2c_module->DATA.reg; }
static inline void _stop(SercomI2cm *dev) { /* Wait for hardware module to sync */ while (dev->SYNCBUSY.reg & SERCOM_I2CM_SYNCBUSY_MASK) {} /* Stop command */ dev->CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3); DEBUG("Stop sent\n"); }
/** * \brief Sends stop condition on bus * * Sends a stop condition on bus. * * \note This function can only be used after the * \ref i2c_master_write_packet_wait_no_stop function. If a stop condition * is to be sent after a read, the \ref i2c_master_read_packet_wait * function must be used. * * \param[in] module Pointer to the software instance struct */ void i2c_master_send_stop(struct i2c_master_module *const module) { /* Sanity check */ Assert(module); Assert(module->hw); SercomI2cm *const i2c_module = &(module->hw->I2CM); /* Send stop command */ _i2c_master_wait_for_sync(module); i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3); }
/** * \internal * Acts on slave address response. Checks for errors concerning master->slave * handshake. * * \param[in,out] module Pointer to software module structure */ static void _i2c_master_async_address_response( struct i2c_master_module *const module) { /* Sanity check arguments. */ Assert(module); Assert(module->hw); SercomI2cm *const i2c_module = &(module->hw->I2CM); /* Check for error. Ignore bus-error; workaround for bus state stuck in * BUSY. */ if (i2c_module->INTFLAG.reg & SERCOM_I2CM_INTFLAG_MB) { /* Clear write interrupt flag */ i2c_module->INTFLAG.reg = SERCOM_I2CM_INTENCLR_MB; /* Check arbitration */ if (i2c_module->STATUS.reg & SERCOM_I2CM_STATUS_ARBLOST) { /* Return busy */ module->status = STATUS_ERR_PACKET_COLLISION; } } if (i2c_module->STATUS.reg & SERCOM_I2CM_STATUS_RXNACK) { /* Return bad address value */ module->status = STATUS_ERR_BAD_ADDRESS; module->buffer_remaining = 0; if (module->send_stop) { /* Send stop condition */ _i2c_master_wait_for_sync(module); i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3); } } module->buffer_length = module->buffer_remaining; /* Check for status OK. */ if (module->status == STATUS_BUSY) { /* Call function based on transfer direction. */ if (module->transfer_direction == I2C_TRANSFER_WRITE) { _i2c_master_write(module); } else { _i2c_master_read(module); } } }
static int _start(SercomI2cm *dev, uint16_t addr) { /* Wait for hardware module to sync */ DEBUG("Wait for device to be ready\n"); while (dev->SYNCBUSY.reg & SERCOM_I2CM_SYNCBUSY_MASK) {} /* Set action to ACK. */ dev->CTRLB.reg &= ~SERCOM_I2CM_CTRLB_ACKACT; /* Send Start | Address | Write/Read */ DEBUG("Generate start condition by sending address\n"); dev->ADDR.reg = (addr) | (0 << SERCOM_I2CM_ADDR_HS_Pos); /* Wait for response on bus. */ if (addr & I2C_READ) { /* Some devices (e.g. SHT2x) can hold the bus while preparing the reply */ if (_wait_for_response(dev, 100 * SAMD21_I2C_TIMEOUT) < 0) return -ETIMEDOUT; } else { if (_wait_for_response(dev, SAMD21_I2C_TIMEOUT) < 0) return -ETIMEDOUT; } /* Check for address response error unless previous error is detected. */ /* Check for error and ignore bus-error; workaround for BUSSTATE * stuck in BUSY */ if (dev->INTFLAG.reg & SERCOM_I2CM_INTFLAG_SB) { /* Clear write interrupt flag */ dev->INTFLAG.reg = SERCOM_I2CM_INTFLAG_SB; /* Check arbitration. */ if (dev->STATUS.reg & SERCOM_I2CM_STATUS_ARBLOST) { DEBUG("STATUS_ERR_PACKET_COLLISION\n"); return -EAGAIN; } } /* Check that slave responded with ack. */ else if (dev->STATUS.reg & SERCOM_I2CM_STATUS_RXNACK) { /* Slave busy. Issue ack and stop command. */ dev->CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3); DEBUG("STATUS_ERR_BAD_ADDRESS\n"); return -ENXIO; } return 0; }
bool rtc_write(uint8_t addr, uint8_t *data, uint8_t count) { bool retval = true; SERCOM3->I2CM.ADDR.reg = RTC_ADDRESS_WRITE; while(SERCOM3->I2CM.STATUS.bit.SYNCBUSY); while(SERCOM3->I2CM.INTFLAG.bit.MB == 0); if (SERCOM3->I2CM.STATUS.reg & (SERCOM_I2CM_STATUS_RXNACK|SERCOM_I2CM_STATUS_BUSERR|SERCOM_I2CM_STATUS_ARBLOST)) { retval = false; goto rtc_write_fail; } // write register address SERCOM3->I2CM.DATA.reg = addr; while(SERCOM3->I2CM.STATUS.bit.SYNCBUSY); while(SERCOM3->I2CM.INTFLAG.bit.MB == 0); if (SERCOM3->I2CM.STATUS.reg & (SERCOM_I2CM_STATUS_RXNACK|SERCOM_I2CM_STATUS_BUSERR|SERCOM_I2CM_STATUS_ARBLOST)) { retval = false; goto rtc_write_fail; } // write data while(count--) { SERCOM3->I2CM.DATA.reg = *data++; while(SERCOM3->I2CM.STATUS.bit.SYNCBUSY); while(SERCOM3->I2CM.INTFLAG.bit.MB == 0); if (SERCOM3->I2CM.STATUS.reg & (SERCOM_I2CM_STATUS_RXNACK|SERCOM_I2CM_STATUS_BUSERR|SERCOM_I2CM_STATUS_ARBLOST)) { retval = false; break; } } rtc_write_fail: // issue STOP SERCOM3->I2CM.CTRLB.reg = SERCOM_I2CM_CTRLB_CMD(3); while(SERCOM3->I2CM.STATUS.bit.SYNCBUSY); return retval; }
/** * \internal * Read next data. Used by interrupt handler to get next data byte from slave. * * \param[in,out] module Pointer to software module structure */ static void _i2c_master_read( struct i2c_master_module *const module) { /* Sanity check arguments. */ Assert(module); Assert(module->hw); SercomI2cm *const i2c_module = &(module->hw->I2CM); bool sclsm_flag = i2c_module->CTRLA.bit.SCLSM; /* Find index to save next value in buffer */ uint16_t buffer_index = module->buffer_length; buffer_index -= module->buffer_remaining; module->buffer_remaining--; if (sclsm_flag) { if (module->send_nack && module->buffer_remaining == 1) { /* Set action to NACK. */ i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_ACKACT; } } else { if (module->send_nack && module->buffer_remaining == 0) { /* Set action to NACK. */ i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_ACKACT; } } if (module->buffer_remaining == 0) { if (module->send_stop) { /* Send stop condition */ _i2c_master_wait_for_sync(module); i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3); } } /* Read byte from slave and put in buffer */ _i2c_master_wait_for_sync(module); module->buffer[buffer_index] = i2c_module->DATA.reg; }
/** * \internal * Starts blocking write operation. * * \param[in,out] module Pointer to software module struct. * \param[in,out] packet Pointer to I<SUP>2</SUP>C packet to transfer. * * \return Status of reading packet. * \retval STATUS_OK The packet was read successfully * \retval STATUS_ERR_TIMEOUT If no response was given within * specified timeout period * \retval STATUS_ERR_DENIED If error on bus * \retval STATUS_ERR_PACKET_COLLISION If arbitration is lost * \retval STATUS_ERR_BAD_ADDRESS If slave is busy, or no slave * acknowledged the address */ static enum status_code _i2c_master_write_packet( struct i2c_master_module *const module, struct i2c_master_packet *const packet) { SercomI2cm *const i2c_module = &(module->hw->I2CM); /* Return value. */ enum status_code tmp_status; uint16_t tmp_data_length = packet->data_length; _i2c_master_wait_for_sync(module); /* Set address and direction bit. Will send start command on bus. */ i2c_module->ADDR.reg = (packet->address << 1) | I2C_TRANSFER_WRITE; /* Wait for response on bus. */ tmp_status = _i2c_master_wait_for_bus(module); /* Check for address response error unless previous error is * detected. */ if (tmp_status == STATUS_OK) { tmp_status = _i2c_master_address_response(module); } /* Check that no error has occurred. */ if (tmp_status == STATUS_OK) { /* Buffer counter. */ uint16_t buffer_counter = 0; /* Write data buffer. */ while (tmp_data_length--) { /* Check that bus ownership is not lost. */ if (!(i2c_module->STATUS.reg & SERCOM_I2CM_STATUS_BUSSTATE(2))) { return STATUS_ERR_PACKET_COLLISION; } /* Write byte to slave. */ _i2c_master_wait_for_sync(module); i2c_module->DATA.reg = packet->data[buffer_counter++]; /* Wait for response. */ tmp_status = _i2c_master_wait_for_bus(module); /* Check for error. */ if (tmp_status != STATUS_OK) { break; } /* Check for NACK from slave. */ if (i2c_module->STATUS.reg & SERCOM_I2CM_STATUS_RXNACK) { /* Return bad data value. */ tmp_status = STATUS_ERR_OVERFLOW; break; } } if (module->send_stop) { /* Stop command */ _i2c_master_wait_for_sync(module); i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3); } } return tmp_status; }
/** * \internal * Starts blocking read operation. * * \param[in,out] module Pointer to software module struct. * \param[in,out] packet Pointer to I<SUP>2</SUP>C packet to transfer. * * \return Status of reading packet. * \retval STATUS_OK The packet was read successfully * \retval STATUS_ERR_TIMEOUT If no response was given within * specified timeout period * \retval STATUS_ERR_DENIED If error on bus * \retval STATUS_ERR_PACKET_COLLISION If arbitration is lost * \retval STATUS_ERR_BAD_ADDRESS If slave is busy, or no slave * acknowledged the address * */ static enum status_code _i2c_master_read_packet( struct i2c_master_module *const module, struct i2c_master_packet *const packet) { /* Sanity check arguments */ Assert(module); Assert(module->hw); Assert(packet); SercomI2cm *const i2c_module = &(module->hw->I2CM); /* Return value. */ enum status_code tmp_status; uint16_t tmp_data_length = packet->data_length; /* Written buffer counter. */ uint16_t counter = 0; /* Set address and direction bit. Will send start command on bus. */ i2c_module->ADDR.reg = (packet->address << 1) | I2C_TRANSFER_READ; /* Wait for response on bus. */ tmp_status = _i2c_master_wait_for_bus(module); /* Set action to ack. */ i2c_module->CTRLB.reg &= ~SERCOM_I2CM_CTRLB_ACKACT; /* Check for address response error unless previous error is * detected. */ if (tmp_status == STATUS_OK) { tmp_status = _i2c_master_address_response(module); } /* Check that no error has occurred. */ if (tmp_status == STATUS_OK) { /* Read data buffer. */ while (tmp_data_length--) { /* Check that bus ownership is not lost. */ if (!(i2c_module->STATUS.reg & SERCOM_I2CM_STATUS_BUSSTATE(2))) { return STATUS_ERR_PACKET_COLLISION; } if (tmp_data_length == 0) { /* Set action to NACK */ i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_ACKACT; } else { /* Save data to buffer. */ _i2c_master_wait_for_sync(module); packet->data[counter++] = i2c_module->DATA.reg; /* Wait for response. */ tmp_status = _i2c_master_wait_for_bus(module); } /* Check for error. */ if (tmp_status != STATUS_OK) { break; } } if (module->send_stop) { /* Send stop command unless arbitration is lost. */ _i2c_master_wait_for_sync(module); i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3); } /* Save last data to buffer. */ _i2c_master_wait_for_sync(module); packet->data[counter] = i2c_module->DATA.reg; } return tmp_status; }
/** * \internal * Interrupt handler for I<SUP>2</SUP>C master. * * \param[in] instance SERCOM instance that triggered the interrupt */ void _i2c_master_interrupt_handler( uint8_t instance) { /* Get software module for callback handling */ struct i2c_master_module *module = (struct i2c_master_module*)_sercom_instances[instance]; Assert(module); SercomI2cm *const i2c_module = &(module->hw->I2CM); /* Combine callback registered and enabled masks */ uint8_t callback_mask = module->enabled_callback & module->registered_callback; /* Check if the module should respond to address ack */ if ((module->buffer_length <= 0) && (module->buffer_remaining > 0)) { /* Call function for address response */ _i2c_master_async_address_response(module); /* Check if buffer write is done */ } else if ((module->buffer_length > 0) && (module->buffer_remaining <= 0) && (module->status == STATUS_BUSY) && (module->transfer_direction == I2C_TRANSFER_WRITE)) { /* Stop packet operation */ i2c_module->INTENCLR.reg = SERCOM_I2CM_INTENCLR_MB | SERCOM_I2CM_INTENCLR_SB; module->buffer_length = 0; module->status = STATUS_OK; if (module->send_stop) { /* Send stop condition */ _i2c_master_wait_for_sync(module); i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3); } else { /* Clear write interrupt flag */ i2c_module->INTFLAG.reg = SERCOM_I2CM_INTENCLR_MB; } if (callback_mask & (1 << I2C_MASTER_CALLBACK_WRITE_COMPLETE)) { module->callbacks[I2C_MASTER_CALLBACK_WRITE_COMPLETE](module); } /* Continue buffer write/read */ } else if ((module->buffer_length > 0) && (module->buffer_remaining > 0)){ /* Check that bus ownership is not lost */ if (!(i2c_module->STATUS.reg & SERCOM_I2CM_STATUS_BUSSTATE(2))) { module->status = STATUS_ERR_PACKET_COLLISION; } else if (module->transfer_direction == I2C_TRANSFER_WRITE) { _i2c_master_write(module); } else { _i2c_master_read(module); } } /* Check if read buffer transfer is complete */ if ((module->buffer_length > 0) && (module->buffer_remaining <= 0) && (module->status == STATUS_BUSY) && (module->transfer_direction == I2C_TRANSFER_READ)) { /* Stop packet operation */ i2c_module->INTENCLR.reg = SERCOM_I2CM_INTENCLR_MB | SERCOM_I2CM_INTENCLR_SB; module->buffer_length = 0; module->status = STATUS_OK; /* Call appropriate callback if enabled and registered */ if ((callback_mask & (1 << I2C_MASTER_CALLBACK_READ_COMPLETE)) && (module->transfer_direction == I2C_TRANSFER_READ)) { module->callbacks[I2C_MASTER_CALLBACK_READ_COMPLETE](module); } else if ((callback_mask & (1 << I2C_MASTER_CALLBACK_WRITE_COMPLETE)) && (module->transfer_direction == I2C_TRANSFER_WRITE)) { module->callbacks[I2C_MASTER_CALLBACK_WRITE_COMPLETE](module); } } /* Check for error */ if ((module->status != STATUS_BUSY) && (module->status != STATUS_OK)) { /* Stop packet operation */ i2c_module->INTENCLR.reg = SERCOM_I2CM_INTENCLR_MB | SERCOM_I2CM_INTENCLR_SB; module->buffer_length = 0; module->buffer_remaining = 0; /* Send nack and stop command unless arbitration is lost */ if ((module->status != STATUS_ERR_PACKET_COLLISION) && module->send_stop) { _i2c_master_wait_for_sync(module); i2c_module->CTRLB.reg |= SERCOM_I2CM_CTRLB_ACKACT | SERCOM_I2CM_CTRLB_CMD(3); } /* Call error callback if enabled and registered */ if (callback_mask & (1 << I2C_MASTER_CALLBACK_ERROR)) { module->callbacks[I2C_MASTER_CALLBACK_ERROR](module); } } }
static void i2c_stop(SercomI2cm *si) { si->CTRLB.reg = SERCOM_I2CM_CTRLB_CMD(3); }