I have a working I2C with an accelerometer connected to the dev board.
Communication works after initialization.
After the unit goes to Extended_Sleep and wakes the I2C is no longer working. There is currently only the one device on the I2C. The Power is not disabled.
The task gets stuck at ad_i2c.c:284 OS_EVENT_WAIT(dev_config->bus_data->event, OS_EVENT_FOREVER);
The interrupt fires one time but I cannot clear it because I need to read an address to clear the pin from the accelerometer side. So the accelermeter appears to be still working.
I have one time init of I2C_BUS_INIT(I2C1) and I2C_DEVICE_INIT(ACCEL_KXCJB).
platform_devices.h contains
I2C_BUS(I2C1)
I2C_SLAVE_DEVICE( I2C1, ACCEL_KXCJB, 0x0E,
HW_I2C_ADDRESSING_7B, HW_I2C_SPEED_FAST);
I2C_BUS_END
periph_init() contains (using the HW_GPIO_PINCONFIG macro so the names get extended properly)
hw_gpio_configure_pin (HW_GPIO_PORT_3, HW_GPIO_PIN_0, INPUT, I2C_SDA, false); //IC21 Data
hw_gpio_configure_pin (HW_GPIO_PORT_4, HW_GPIO_PIN_7, OUTPUT, I2C_SCL, false); // I2C1 CLK
hw_gpio_configure_pin (HW_GPIO_PORT_4, HW_GPIO_PIN_3, INPUT, GPIO, false); // accel irq
After the wake up the examples say to reinit the lines. Is there anything else that needs to be done in periph_init() that I am missing for I2C communication to work after extended sleep.
Thanks for your assistance.
I have probed the SCL line and there is no activity on the line after the extended sleep.
Hi Richard Legault,
I dont see anything missing from the configuration that you mention, how do you trigger the reading of the sensor memory etc from the system ? The part of the code that the device stalls is because it waits for an the complete event from the I2C module and apparently nothing occurs.
Also please have a look at the post below, perhaps this will help:
https://support.dialog-semiconductor.com/forums/post/dialog-smartbond-bl...
Thanks MT_dialog
When it comes out of sleep the I2C status is 0 (disabled).
To get the I2C functional again it was not enough to do hw_i2c_enable(HW_I2C1) I had to also init the bus and set the target address.
Thus the code for the I2C periph_init is now:
=================
hw_gpio_configure_pin (HW_GPIO_PORT_3, HW_GPIO_PIN_0, INPUT, I2C_SDA, false); //IC21 Data
hw_gpio_configure_pin (HW_GPIO_PORT_4, HW_GPIO_PIN_7, OUTPUT, I2C_SCL, false); // I2C1 CLK
hw_gpio_configure_pin (HW_GPIO_PORT_4, HW_GPIO_PIN_3, INPUT, GPIO, false); // accel irq
static const i2c_config cfg = {
.speed = HW_I2C_SPEED_FAST,
.mode = HW_I2C_MODE_MASTER,
.addr_mode = HW_I2C_ADDRESSING_7B,
};
hw_i2c_init(HW_I2C1, &cfg);
hw_i2c_set_target_address(HW_I2C1,0x0E); /* appears the adapter does not set the address when only one I2C device is on the bus?? */
hw_i2c_enable(HW_I2C1);
====================
它没有在示例或文档mention that these init steps needs to be done when coming out of sleep. I only discovered this from the link in the previous response.
The question is: is this initialization needed for proper behaviour after a deep sleep or is this a bug in the i2c adapter or hw_i2c code. Does the adapter update the device address even for busses with only one device.
为什么我要当广告设置TargetAddressapter is suppose to do this for me? If I don't set the address in init, then I get a value of 0x07 for all register reads from the device. Is this an artifact of only having one device on the bus.
Hi Richard Legault,
As mentioned as a direct answer on the previous post those initialization are not necessary, since you are using the adapters in order to interact with the sensor, the adapter will re-enable and configure the I2C bus for you, you will just have to place the configuration of your pins in the periph_setup() function. So if you are using the adapters, you will just have to open the connection with the I2C device that you have declared in the platform_devices.h with ad_i2c_open() and start a transaction, with ad_i2c_transact() or ad_i2c_write(), ad_i2c_read() etc. So how do you trigger the transaction ?
Thanks MT_dialog
Yes that is the expected behaviour but the observed behaviour is that I do need to do those 3 steps otherwise I2C does not respond when waking from extended sleep.
Hi Richard
Its the expected and how the system behaves indeed, if you invoke one the functions i ve mentioned above in order to start the interaction the I2C module, the
ad_i2c_bus_aquire() function is invoked and the ad_i2c_bus_apply_config() will configure the I2C module for the current transaction (hw_i2c_init() / hw_i2c_enable()
will be executed for the current transaction the master target address will be also set from the hw_i2c_init()). So if this is the only way that you can achieve
I2C communication perhaps you dont use the adapter in order to trigger a I2C interaction. For example i read a sensor's value periodically from an RTOS timer, so
the system wakes up and with the below function i can read the name of the sensor.
/*Read from sensor with seperate read and write transactions*/
void read_mpu_reg_rw(uint8_t reg_addr)
{
static uint8_t who_i_am_rw = 0;
uint8_t addr = reg_addr;
mpu6050 = ad_i2c_open(MPU_6050);
ad_i2c_write(mpu6050, ®_addr, 1);
ad_i2c_read(mpu6050, &who_i_am_rw, 1);
printf("%02x \n\r", who_i_am_rw);
}
Thanks MT_dialog
Yes I do an open. The transact call never returns as it is waiting for an I2C to complete. I replaced the transact with write and read but the behaviour I have observed has remained the same. The SDK is 1.0.8.1050.1 for the DA14681
Thanks for keeping with this. I am confused as to why I am not seeing the expected behaviour after the extended sleep.
By the way when I do a hw_i2c_get_enable_status() it returns 0 after an extended sleep unless I have the 3 unnecessary steps in the periph_init.
In the I2C_Handler() call the value for i2c->intr_cb is NULL.
I have not changed any of the retention settings but it looks like the memory (state) for the I2C is being lost during extended sleep.
Could there be something there that is causing this behaviour?Do note that we have replaced the 8MBit winbond with a 64MBit winbond Flash part.We have created the new appropriate header file for it.
#define proj_configOPTIMAL_RETRAM (0)
#if !defined(RELEASE_BUILD) && (proj_configOPTIMAL_RETRAM == 1)
/* WARNING: retRAM optimizations are disabled in DEBUG builds! */
#undef proj_configOPTIMAL_RETRAM
#define proj_configOPTIMAL_RETRAM (0)
#elif (dg_configEXEC_MODE != MODE_IS_CACHED)
/* WARNING: retRAM optimizations are not applicable in MIRRORED mode! */
#undef proj_configOPTIMAL_RETRAM
#define proj_configOPTIMAL_RETRAM (0)
#endif
#if (proj_configOPTIMAL_RETRAM == 0)
#define dg_configMEM_RETENTION_MODE (0x1F)
#define dg_configSHUFFLING_MODE (0x3)
Hi Richard,
Can you please let me know, how do you trigger the I2C transaction in the code, where do you place the code in order for the device to start the interaction ?
Thanks MT_dialog
It is within a task that is woken by an interrupt. I showed the call chain to kxcjbDevRegisterGet in a previous response.
Since interrupts cannot use adapters to interact with I2C, the task has to clear the interrupt when it reads the interrupt clearing register via I2C.
Here is the interrupt handler (The clearing of the HW GPIO is done by the WKUP handler I wrote. Tasks register with WKUP to have their handler called when a certain PORT/PIN interrupt is detected.
//the idea is that this is the top Half of the interrupt that it signals to have the bottom half (task) scheduled to run at a later time.
static void kxcjbTapEventInterrupt(void* eventData)
{
//tp_interruptEventData p_eventData = (tp_interruptEventData) eventData;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xTaskNotifyFromISR( accelInterruptTaskHandle, ACCEL_INTR_NOTIFY, eSetBits,
&xHigherPriorityTaskWoken);
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
return;
}
// HERE IS THE TASK //
//the idea is that this is the bottom Half of the interrupt that is scheduled to run at a later time.
static void accelTaskHL( void *pvParameters )
{
/* define variables static to get it off the stack */
static uint8_t accelSource[2];
t_errorCode errCode;
bool st;
bool dataValid = false;
uint32_t notifyValue;
uint8_t data;
const TimerHandle_t accelTimer = xTimerCreate( "accelTaskTimer",
pdMS_TO_TICKS(750),
pdFALSE,
(void *)0,
accelTimerCallback);
while (1)
{
xTaskNotifyWait(0x00, ULONG_MAX, ¬ifyValue, portMAX_DELAY); // WAIT here for the next signal: Interrupt, Timeout, or control message
kxcjbDevRegisterGet(NULL, WHOAMI_ADDR, &data);// Within this function I do the ad_i2c_transact()
if (notifyValue&ACCEL_ENABLE_NOTIFY) {
g_accelState=ACCEL_STATE_0;
kxcjbInterruptEnable();
i2c_device dev = ad_i2c_open(ACCEL_KXCJB);
errCode = kxcjbBitsSet( dev, CTRL_REG1_ADDR, CTRL_REG1_PC1_EN);
assert(errCode==ERRORCODE_OK);
ad_i2c_close(dev);
}
else if (notifyValue&ACCEL_DISABLE_NOTIFY) {
kxcjbInterruptDisable();
i2c_device dev = ad_i2c_open(ACCEL_KXCJB);
errCode =kxcjbBitsClear( dev, CTRL_REG1_ADDR, CTRL_REG1_PC1_EN);
assert(errCode==ERRORCODE_OK);
ad_i2c_close(dev);
g_accelState=ACCEL_STATE_DISABLE;
}
else
{
switch (g_accelState) {
case ACCEL_STATE_DISABLE:
/* do nothing */
break;
case ACCEL_STATE_0:
if (notifyValue & ACCEL_INTR_NOTIFY) {
kxcjbInterruptSrc(NULL, &accelSource[ACCEL_STATE_0]);
/* will handle the case where the timer is already running,
* should not happen
*/
xTimerReset(accelTimer, 0);
g_accelState=ACCEL_STATE_1;
}
/* else we are ignoring the timer interrupt, should not happen */
break;
case ACCEL_STATE_1:
if (notifyValue & ACCEL_INTR_NOTIFY) {
kxcjbInterruptSrc(NULL, &accelSource[ACCEL_STATE_1]);
printf ("0=0x%02x\n\r1=0x%02x\n\r",accelSource[0],accelSource[1]);
fflush(stdout);
/* test if we got source interrupt on the same axis
* as occurred in previous interrupt
*/
if(( (accelSource[ACCEL_STATE_0]&(INT_SOURCE2_XPWU|INT_SOURCE2_XNWU))
&&(accelSource[ACCEL_STATE_1]&(INT_SOURCE2_XPWU|INT_SOURCE2_XNWU))
) ||
( (accelSource[ACCEL_STATE_0]&(INT_SOURCE2_YPWU|INT_SOURCE2_YNWU))
&&(accelSource[ACCEL_STATE_1]&(INT_SOURCE2_YPWU|INT_SOURCE2_YNWU))
) ||
( (accelSource[ACCEL_STATE_0]&(INT_SOURCE2_ZPWU|INT_SOURCE2_ZNWU))
&&(accelSource[ACCEL_STATE_1]&(INT_SOURCE2_ZPWU|INT_SOURCE2_ZNWU))
)
) {
xTimerStop(accelTimer, 0);
if (ACCEL_TASK_DISABLE_ON_TAP_DETECT) {
g_accelState = ACCEL_STATE_DISABLE;
kxcjbInterruptDisable();
}
else {
g_accelState = ACCEL_STATE_0;
}
if (gfp_tapEventCb) {
gfp_tapEventCb(g_appInfo);
}
}
else {
printf("AC:TO\n\r");
fflush(stdout);
xTimerReset(accelTimer, 0);
accelSource[ACCEL_STATE_0]=accelSource[ACCEL_STATE_1];
g_accelState = ACCEL_STATE_1; /* reenter the same state */
}
}
else if (notifyValue & ACCEL_TIMER_NOTIFY) {
g_accelState=ACCEL_STATE_0;
}
else {
assert (0==1);
}
}
}
}
}
// Here is the code that creates the task,
uint32_t status = OS_TASK_CREATE("AccelInterruptHL", /* The text name assigned to the task, for
debug only; not used by the kernel. */
accelTaskHL, /* The System Initialization task. */
( void * ) 0, /* The parameter passed to the task. */
configMINIMAL_STACK_SIZE * OS_STACK_WORD_SIZE * 2,
/* The number of bytes to allocate to the
stack of the task. */
OS_TASK_PRIORITY_HIGHEST, /* The priority assigned to the task. */
accelInterruptTaskHandle ); /* The task handle */
Hi Richard Legault,
So, you have configured the wake up timer in order to trigger sensor readings from the attached sensor, every time the sensor issues an interrupt you notify the task that triggers the reading, and you perform the read of data and clearing of the interrupt. Seems ok, what i dont quite get is the interrupt handler of the wakeup interrupt (i suppose that this is the kxcjbTapEventInterrupt, am i correct), why the handler has an event parameter ? Also its not recommended invoke the portYIELD_FROM_ISR (although i ve tested it and at least on my side it doesn't affect the I2C interaction when you are in sleep mode) and you should not run a task with a highest priority since this might affect other tasks (BLE task, power manager task etc). Anyway i dont think that those will cause any issues to the I2C, so my recomendation would be debug this and check if the code goes through the I2C interaction function, i mean the ad_i2c_read/write etc, also, you ve mentioned that the configuration after sleep is zero, the configuration for the I2C module is applied as soon as the i2c interaction functions are invoked ad_i2c_read/write etc. Also you could write a simpler code in order to test if the interaction with the i2c in simpler project (you could interact with the sensor via a simple timer or via the wake up timer - trigger the interrupt from a gpio) and check if that is working on your custom device then compare with the one that you have now. One last thing i 've noticed that the port that you are using for the I2C pins is PORT4 and PORT 3, be aware that if you are using the WCSP package those ports are not available.
Thanks MT_dialog
It appears to be due to using gcc-arm-none-eabi-6-2017-q1-update ( with this tool chain I have to use the extra steps to get I2C to work).
With the provided gcc tool chain: 4_9-2015q3 the I2C works as advertised.
Thanks for your assistance and perseverance on this.
Hopefully this saves others time, knowing that the tool chain affects the documented behaviour.
Hi Richard,
Thanks for indicating this, glad you have resolved your issue.
Best Regards MT_dialog