I'm coming back to you since I got some pbs when associating the TTC0 code above to my previous designed sketches (RTC and LCD working both on I2C). I'm also using millis() to display the time each 5 sec etc...
This is my code (joined piece).
In fact it works as TCC0 capture, but both serialUSB and RTC seem to be blocked by TCC interruptions.
When I comment TCC0 initialisation in the void setup, serialUSB and RTC are running well again !!
Could you help ?
I fear I2C and/or RTC as non compatible with TCC0 ?
If you're using a 40kHz input, then I think it's most probably because your input frequency is too high for the SAMD21 with the additional RTC and I2C code.
Your options as I see it are as follows:
If possible use a lower input frequency.
If you don't need both pulse-width and period data then disable the interrupt for the one you don't need.
Use the SAMD21's Direct Memory Access Controller (DMAC) to trigger on the TCC0 input, get it to read the pulse-width/period and move this data to a memory location that can be accessed by your main program.
Rather than using the TCC0_Handler() interrupt service routine, it instead uses the SAMD21's Direct Memory Access Controller (DMAC). The DMAC shoulders the burden of receiving the period and pulse width data from the TCC0 timer, thereby freeing the CPU from this task.
The code uses DMAC channels 0 and 1 to continuously read the period and pulse width data from TCC0's registers and moves this information to the variables "dmacPeriod" and "dmacPulsewidth".
In the loop() portion of your sketch the CPU only has to read these variables. This should free up the CPU, giving it time to run your RTC and I2C routines.
Here's the code:
// Setup TCC0 to capture pulse-width and period with DMAC transfer
volatile uint32_t dmacPeriod;
volatile uint32_t dmacPulsewidth;
typedef struct // DMAC descriptor structure
{
uint16_t btctrl;
uint16_t btcnt;
uint32_t srcaddr;
uint32_t dstaddr;
uint32_t descaddr;
} dmacdescriptor ;
volatile dmacdescriptor wrb[12] __attribute__ ((aligned (16))); // Write-back DMAC descriptors
dmacdescriptor descriptor_section[12] __attribute__ ((aligned (16))); // DMAC channel descriptors
dmacdescriptor descriptor __attribute__ ((aligned (16))); // Place holder descriptor
void setup()
{
SerialUSB.begin(115200); // Send data back on the Zero's native port
while(!SerialUSB); // Wait for the SerialUSB port to be ready
DMAC->BASEADDR.reg = (uint32_t)descriptor_section; // Set the descriptor section base address
DMAC->WRBADDR.reg = (uint32_t)wrb; // Set the write-back descriptor base adddress
DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf); // Enable the DMAC and priority levels
DMAC->CHID.reg = DMAC_CHID_ID(0); // Select DMAC channel 0
// Set DMAC channel 1 to priority level 0 (lowest), to trigger on TCC0 match compare 1 and to trigger every beat
DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(0) | DMAC_CHCTRLB_TRIGSRC(TCC0_DMAC_ID_MC_0) | DMAC_CHCTRLB_TRIGACT_BEAT;
//DMAC->CHINTENSET.reg = DMAC_CHINTENSET_MASK ; // Enable all DMAC interrupts
descriptor.descaddr = (uint32_t)&descriptor_section[0]; // Set up a circular descriptor
descriptor.srcaddr = (uint32_t)&TCC0->CC[0].reg; // Take the contents of the TCC0 counter comapare 0 register
descriptor.dstaddr = (uint32_t)&dmacPeriod; // Copy it to the "dmacPeriod" variable
descriptor.btcnt = 1; // This takes one beat
descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD | DMAC_BTCTRL_VALID; // Copy 16-bits (HWORD) and flag the descriptor as valid
memcpy(&descriptor_section[0], &descriptor, sizeof(dmacdescriptor)); // Copy to the channel 0 descriptor
DMAC->CHID.reg = DMAC_CHID_ID(1); // Select DMAC channel 1
// Set DMAC channel 1 to priority level 0 (lowest), to trigger on TCC0 match compare 1 and to trigger every beat
DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(0) | DMAC_CHCTRLB_TRIGSRC(TCC0_DMAC_ID_MC_1) | DMAC_CHCTRLB_TRIGACT_BEAT;
//DMAC->CHINTENSET.reg = DMAC_CHINTENSET_MASK ; // Enable all DMAC interrupts
descriptor.descaddr = (uint32_t)&descriptor_section[1]; // Set up a circular descriptor
descriptor.srcaddr = (uint32_t)&TCC0->CC[1].reg; // Take the contents of the TCC0 counter comapare 1 register
descriptor.dstaddr = (uint32_t)&dmacPulsewidth; // Copy it to the "dmacPulseWidth" variable
descriptor.btcnt = 1; // This takes 1 beat
descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD | DMAC_BTCTRL_VALID; // Copy 16-bits (HWORD) and flag the descriptor as valid
memcpy(&descriptor_section[1], &descriptor, sizeof(dmacdescriptor)); // Copy to the channel 1 descriptor
REG_PM_APBCMASK |= PM_APBCMASK_EVSYS; // Switch on the event system peripheral
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) | // Divide the 48MHz system clock by 1 = 48MHz
GCLK_GENDIV_ID(5); // Set division on Generic Clock Generator (GCLK) 5
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK 5
GCLK_GENCTRL_SRC_DFLL48M | // Set the clock source to 48MHz
GCLK_GENCTRL_ID(5); // Set clock source on GCLK 5
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization*/
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable the generic clock...
GCLK_CLKCTRL_GEN_GCLK5 | // ....on GCLK5
GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed the GCLK5 to TCC0 and TCC1
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
// Enable the port multiplexer on digital pin D12
PORT->Group[g_APinDescription[12].ulPort].PINCFG[g_APinDescription[12].ulPin].bit.PMUXEN = 1;
// Set-up the pin as an EIC (interrupt) peripheral on D12
PORT->Group[g_APinDescription[12].ulPort].PMUX[g_APinDescription[12].ulPin >> 1].reg |= PORT_PMUX_PMUXO_A;
//attachInterrupt(12, NULL, HIGH); // Attach interrupts to digital pin 12 (external interrupt 3)
REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3; // Enable event output on external interrupt 3
REG_EIC_CONFIG0 |= EIC_CONFIG_SENSE3_HIGH; // Set event detecting a HIGH level
REG_EIC_CTRL |= EIC_CTRL_ENABLE; // Enable EIC peripheral
while (EIC->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_EVSYS_USER = EVSYS_USER_CHANNEL(1) | // Attach the event user (receiver) to channel 0 (n + 1)
EVSYS_USER_USER(EVSYS_ID_USER_TCC0_EV_1); // Set the event user (receiver) as timer TCC0, event 1
REG_EVSYS_CHANNEL = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT | // No event edge detection
EVSYS_CHANNEL_PATH_ASYNCHRONOUS | // Set event path as asynchronous
EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_3) | // Set event generator (sender) as external interrupt 3
EVSYS_CHANNEL_CHANNEL(0); // Attach the generator (sender) to channel 0
REG_TCC0_EVCTRL |= TCC_EVCTRL_MCEI1 | // Enable the match or capture channel 1 event input
TCC_EVCTRL_MCEI0 | //.Enable the match or capture channel 0 event input
TCC_EVCTRL_TCEI1 | // Enable the TCC event 1 input
/*TCC_EVCTRL_TCINV1 |*/ // Invert the event 1 input
TCC_EVCTRL_EVACT1_PPW; // Set up the timer for capture: CC0 period, CC1 pulsewidth
REG_TCC0_CTRLA |= TCC_CTRLA_CPTEN1 | // Enable capture on CC1
TCC_CTRLA_CPTEN0 | // Enable capture on CC0
TCC_CTRLA_PRESCALER_DIV1 | // Set prescaler to 1, 48MHz/1 = 48MHz
TCC_CTRLA_ENABLE; // Enable TCC0
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
DMAC->CHID.reg = DMAC_CHID_ID(0); // Select DMAC channel 0
DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; // Enable DMAC channel 0
DMAC->CHID.reg = DMAC_CHID_ID(1); // Select DMAC channel 1
DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; // Enable DMAC channel 1
}
void loop()
{
SerialUSB.print(dmacPeriod); // Output the results
SerialUSB.print(F(" "));
SerialUSB.println(dmacPulsewidth);
}
1 / Has AttachInterrupt with NULL being repaired in the IDE 1.8.5 ? If not I need to put a callback routine as previously isn't it ?
2/ How can I know that a new period has been measured in your sketch, since there is no more "void TCC0_Handler()" ?
3/ I also need "Count" data for my application. Can I declare isrCount variable and put isrCount++; in the void loop, but as said in 2/ above, how can I increment it at the end of each period measurement ?
I am facing a problem to set an event (the purpose of this will be to make them work like Arduino Due's function of clock chaining):
I want to use a pair of TC4 - TC5 working in 32 bit mode.
I read that to set an event 2 parameters are important EVGEN (Event Generator) and USER (Event user).
So TC4 should act as a event generator and TC5 as a user.
which isTC4 should count upto the maximum value and then it should trigger TC5 to start counting i.e clock
chaining.
1 / Has AttachInterrupt with NULL being repaired in the IDE 1.8.5 ? If not I need to put a callback routine as previously isn't it ?
I raised the issue with the Arduino SAMD core development team and they've flagged it as a bug, so I expect it might be fixed in a subsequent Arduino Zero build.
In the DMAC example code above, I commented out attachInterrupt() function and replaced it by setting the interrupt registers directly.
2/ How can I know that a new period has been measured in your sketch, since there is no more "void TCC0_Handler()" ?
The "dmacPeriod" and "dmacPulsewidth" variables now hold the latest measurements. The DMAC is reading them instead of the CPU having service the TCC0_Handler() each time. The DMAC is effectively handling the TCC0 timer for the CPU.
3/ I also need "Count" data for my application. Can I declare isrCount variable and put isrCount++; in the void loop, but as said in 2/ above, how can I increment it at the end of each period measurement ?
I'll have to think about this one....
It might be possible to use the event system to get another of the SAMD21's timers to count the number of TCC0 periods that have elapsed.
I am facing a problem to set an event (the purpose of this will be to make them work like Arduino Due's function of clock chaining):
I want to use a pair of TC4 - TC5 working in 32 bit mode.
I read that to set an event 2 parameters are important EVGEN (Event Generator) and USER (Event user).
So TC4 should act as a event generator and TC5 as a user.
which isTC4 should count upto the maximum value and then it should trigger TC5 to start counting i.e clock
chaining.
The problem is to setup this connection. What all registers should be initialised to make it work
and how ?
Looks like your issue is similar to the Daniel's requirement in point (3) above. I'll see if I can come up with a solution using the TC timers in 32-bit mode.
I've added the counter using the TC3 and TC4 timers in 32-bit mode. In this mode, TC4 acts as the master and TC3 the slave, this just means that the registers are accessed through TC4's interface.
The TC4 timer in 32-bit mode is set-up to increment each time it receives an event from interrupt pin on the event system.
Using 32-bits with a 40kHz input frequency will allow the TC timer to count for almost 30 hours before rolling over back to 0.
If you require longer than this then it'll be necessary to get TC4 to generate an overflow event to the 16-bit TC5 timer. Using TC5 as well will give you 223 years before it rolls over! Probably enough for most projects, in any case we won't be around when this happens.
Please find the code attached. Unfortunately it exceeds the 9000 character limit, so I can't display it in this message.
Here's a cut down version of the above code, that only counts the number of incoming interrupt pulses on D12 using timer TC4 in 32-bit mode, (but doesn't use the DMAC or TCC0):
// Setup TC4 in 32-bit mode to count interrupt pulses using the event system on digital pin D12
void setup()
{
SerialUSB.begin(115200); // Send data back on the Zero's native port
while(!SerialUSB); // Wait for the SerialUSB port to be ready
REG_PM_APBCMASK |= PM_APBCMASK_EVSYS; // Switch on the event system peripheral
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) | // Divide the 48MHz system clock by 1 = 48MHz
GCLK_GENDIV_ID(5); // Set division on Generic Clock Generator (GCLK) 5
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK 5
GCLK_GENCTRL_SRC_DFLL48M | // Set the clock source to 48MHz
GCLK_GENCTRL_ID(5); // Set clock source on GCLK 5
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization*/
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable the generic clock...
GCLK_CLKCTRL_GEN_GCLK5 | // ....on GCLK5
GCLK_CLKCTRL_ID_TCC2_TC3; // Feed the GCLK5 to TCC2 and TC3
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable the generic clock...
GCLK_CLKCTRL_GEN_GCLK5 | // ....on GCLK5
GCLK_CLKCTRL_ID_TC4_TC5; // Feed the GCLK5 to TC4 and TC5
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
// Enable the port multiplexer on digital pin D12
PORT->Group[g_APinDescription[12].ulPort].PINCFG[g_APinDescription[12].ulPin].bit.PMUXEN = 1;
// Set-up the pin as an EIC (interrupt) peripheral on D12
PORT->Group[g_APinDescription[12].ulPort].PMUX[g_APinDescription[12].ulPin >> 1].reg |= PORT_PMUX_PMUXO_A;
//attachInterrupt(12, NULL, HIGH); // Attach interrupts to digital pin 12 (external interrupt 3)
REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3; // Enable event output on external interrupt 3
REG_EIC_CONFIG0 |= EIC_CONFIG_SENSE3_HIGH; // Set event detecting a HIGH level
REG_EIC_CTRL |= EIC_CTRL_ENABLE; // Enable EIC peripheral
while (EIC->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_EVSYS_USER = EVSYS_USER_CHANNEL(1) | // Attach the event user (receiver) to channel 0 (n + 1)
EVSYS_USER_USER(EVSYS_ID_USER_TC4_EVU); // Set the event user (receiver) as timer TC4
REG_EVSYS_CHANNEL = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT | // No event edge detection
EVSYS_CHANNEL_PATH_ASYNCHRONOUS | // Set event path as asynchronous
EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_3) | // Set event generator (sender) as external interrupt 3
EVSYS_CHANNEL_CHANNEL(0); // Attach the generator (sender) to channel 0
REG_TCC0_EVCTRL |= TCC_EVCTRL_MCEI1 | // Enable the match or capture channel 1 event input
TCC_EVCTRL_MCEI0 | //.Enable the match or capture channel 0 event input
TCC_EVCTRL_TCEI1 | // Enable the TCC event 1 input
/*TCC_EVCTRL_TCINV1 |*/ // Invert the event 1 input
TCC_EVCTRL_EVACT1_PPW; // Set up the timer for capture: CC0 period, CC1 pulsewidth
REG_TC4_EVCTRL |= TC_EVCTRL_TCEI | // Enable asynchronous events on the TC timer
TC_EVCTRL_EVACT_COUNT; // Increment the TC timer each time an event is received
REG_TC4_CTRLA |= TC_CTRLA_PRESCALER_DIV1 | // Set prescaler to 1, 48MHz/1 = 48MHz
TC_CTRLA_MODE_COUNT32 | // Set the TC4 timer to 32-bit mode in conjuction with timer TC3
TC_CTRLA_ENABLE; // Enable TC4
while (TC4->COUNT32.STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_TC4_READREQ = TC_READREQ_RCONT | // Enable a continuous read request
TC_READREQ_ADDR(0x10); // Offset of the 32-bit COUNT register
while (TC4->COUNT32.STATUS.bit.SYNCBUSY); // Wait for (read) synchronization
}
void loop()
{
SerialUSB.println(REG_TC4_COUNT32_COUNT); // Output the results
}
Thank you so much.
The only part where I have a problem is this :
// Enable the port multiplexer on digital pin D12
PORT->Group[g_APinDescription[12].ulPort].PINCFG[g_APinDescription[12].ulPin].bit.PMUXEN = 1;
// Set-up the pin as an EIC (interrupt) peripheral on D12
PORT->Group[g_APinDescription[12].ulPort].PMUX[g_APinDescription[12].ulPin >> 1].reg |= PORT_PMUX_PMUXO_A;
//attachInterrupt(12, NULL, HIGH); // Attach interrupts to digital pin 12 (external interrupt 3)
REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3; // Enable event output on external interrupt 3
REG_EIC_CONFIG0 |= EIC_CONFIG_SENSE3_HIGH; // Set event detecting a HIGH level
REG_EIC_CTRL |= EIC_CTRL_ENABLE; // Enable EIC peripheral
while (EIC->STATUS.bit.SYNCBUSY); // Wait for synchronization
Why is it necessary to attach an external interrupt, cant it be done using internal interrupt ?
In simple words when the TC4 counts to its maximum count it displays the count and again starts over .
In the earlier builds of the Arduino core code, it was possible to set-up a pin as an interrupt by using the attachInterrupt() function and passing a NULL argument as the function callback pointer:
attachInterrupt(12, NULL, HIGH); // Attach interrupts to digital pin 12 (external interrupt 3)
However, in the later builds this was changed so that attachInterrupt() would skip pin set-up if a NULL pointer was detected.
This means at the moment it's necessary to either use attachInterrupt() with a dummy (empty) callback function, or set-up the pin using regsiter manipulation.
I've raised the isse on Github with the Arduino SAMD core developers, as this changes reduces the flexibility of this function.
The first section of code just switches the pin from GPIO to the peripheral multiplexer and then selects the peripheral as the External Interrupt Controller (EIC):
// Enable the port multiplexer on digital pin D12
PORT->Group[g_APinDescription[12].ulPort].PINCFG[g_APinDescription[12].ulPin].bit.PMUXEN = 1;
// Set-up the pin as an EIC (interrupt) peripheral on D12
PORT->Group[g_APinDescription[12].ulPort].PMUX[g_APinDescription[12].ulPin >> 1].reg |= PORT_PMUX_PMUXO_A;
The following lines then just activate the interrupt on D12, which is on EIC channel 3 and set the pin sensing to interrupt on a HIGH level:
REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3; // Enable event output on external interrupt 3
REG_EIC_CONFIG0 |= EIC_CONFIG_SENSE3_HIGH; // Set event detecting a HIGH level
REG_EIC_CTRL |= EIC_CTRL_ENABLE; // Enable EIC peripheral
while (EIC->STATUS.bit.SYNCBUSY); // Wait for synchronization
I think I probably got the wrong end of the stick with regard to your requirements, it's just that this thread was dealing with capturing an external signal and for this it's necessary to use an external interrupt.
If you're application doesn't require an external interrupt then it's possible to just use TC timer in 32-bit mode with internal interrupts and events.
Setting the TC4 timer to 32-bit mode automatically feeds one timer from another. Essentially it turns timer TC4 into a 32-bit timer. The SAMD21's hardware does this for you, chaining TC4 to TC3 in the background.
Reading the TC4's 32-bit count register gives you a seamless 32-bit result.
It's also possible to set-up CCx counter compare and overflow interrupts in 32-bits.
Well I did that and it shows TC4 value is zero on the serial monitor.
That's because with this code the TC timer is set up to read a high speed external interrupt.
How the TC timer is initialised depends on what you're trying to achieve. It can be set up in number of different ways depending on your requirement.
If you're just requiring a regular internal interrupt then the normal (NFRQ) or match frequency (MFRQ) modes are suitable. If however you require an external PWM output signal then normal (NPWM) or match (MPWM) PWM modes are probably best. There's also the event system, where one peripheral can be used to trigger another without CPU intervention.
TcCount32* TC = (TcCount32*) TC4; // The type cast must fit with the selected timer mode
TC->CTRLA.reg |= TC_CTRLA_MODE_COUNT32; // Set Timer counter Mode to 32 bits
TC->CTRLA.reg |= TC_CTRLA_WAVEGEN_NFRQ; // Set TC as normal Frequency
TC->CTRLA.reg |= TC_CTRLA_PRESCALER_DIV256; // Set perscaler
TC->CTRLA.reg |= TC_CTRLA_ENABLE; // Enable TC
REG_TC4_READREQ = TC_READREQ_RCONT | // Enable a continuous read request
TC_READREQ_ADDR(0x10); // Offset of the 16 bit COUNT register
while (TC4->COUNT32.STATUS.bit.SYNCBUSY); // Wait for synchronization
}
void loop() {
TcCount32* TC = (TcCount32*) TC4; // The type cast must fit with the selected timer mode TcCount32* TC = (TcCount32*) TC3; // The type cast must fit with the selected timer mode
Now I would just like the counter (when rolls over to zero ) it should go to TC5 from TC4 and should not start counting itself from zero. I know I am asking the same thing but I am still there. so,if you can help how can this connection be achieved.
This sounds to be my solution , but I need help in initialization :
**There's also the event system, where one peripheral can be used to trigger another without CPU intervention.**
EVGEN(Event Generator) TC3_MC0 should generate an event to the USER_TC4
No problem, hopefully I can help with the initialization.
First of all I need to know the frequency you intend to clock the timers. I notice in your code you're using a 256 prescaler divider.
My questions are:
Are you intending to clock the timers at 187.5kHz (48MHz/256)?
Do you wish the timers to free run and overflow at their maximum (2^32)?
Regarding the TC timer chaining, setting the TC timers to 32-bit mode is effectively the same as TC timer chaining on the Arduino Due, like in ard_newbie's code:
TC0->TC_BMR = TC_BMR_TC1XC1S_TIOA0; // Timer Counter 0 channel 1 is internally clocked by TIOA0
TC0->TC_CHANNEL[1].TC_CMR = TC_CMR_TCCLKS_XC1 // External clock XC1 selected
| TC_CMR_WAVE // Waveform mode
| TC_CMR_WAVSEL_UP_RC // UP mode with automatic trigger on RC Compare
| TC_CMR_ACPA_CLEAR // Clear TIOA1 on RA compare match
| TC_CMR_ACPC_SET; // Set TIOA1 on RC compare match
It's just that the SAMD21 requires less input from the programmer, as its internal hardware handles this for you. It means that you don't have to use the event system or interrupts to chain the timers.
What I need is to "restart" the TCCount timer (TC4 & slave TC3) to 0 when the value of REG_TC4_COUNT32_COUNT rises a dedicated number (i.e lenght of bit measurement in my application).
Then how can I reset the TC timer to 0 and let it count again ?