Routing event from multiple pins to DMA transfer

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

Hello fellow avr people!

 

I'm working on a project which involves 8 digital trigger pins (on PORTC) and 8 analog pins on PORTA. I want to use the xmega event system to trigger a DMA ADC transfer on the corresponding pin on PORTA when a pin on PORTC goes hi. At the end of the DMA transfert, an ISR strobe the corresponding pin on PORTF for 1ms and the process is complete. In memory, I have an array of 8 values of the ADC result according to the proper pin on PORTA which I use for timed processes to create animations on LED strips.

 

As of now in my project, I use the ADC directly in the main loopand try to get a peak value. This is working, but not precise enough to my taste and seems a waste of CPU so I changed my circuit to use a schmitt trigger on PORTC so that the ADC on PORTA is done only when needed. Lastly, the reset pin on PORTF is connected to a transistor which drains the capacitor that helps holding the transient for a good measure on PORTA's external circuitry.

 

First off, does all this makes sense from the microntroller's event and DMA perspective? 

 

Second, should I route the pins to individual event channel or is there a way to route them all in the same event channel? 

I noted that the DMA can only be triggered from event channel 0, 1 and 2. So this setup will probably not work with the DMA:

EVSYS.CH0MUX = EVSYS_CHMUX_PORTC_PIN0_gc;
EVSYS.CH1MUX = EVSYS_CHMUX_PORTC_PIN1_gc;
EVSYS.CH2MUX = EVSYS_CHMUX_PORTC_PIN2_gc;
EVSYS.CH3MUX = EVSYS_CHMUX_PORTC_PIN3_gc;
EVSYS.CH4MUX = EVSYS_CHMUX_PORTC_PIN4_gc;
EVSYS.CH5MUX = EVSYS_CHMUX_PORTC_PIN5_gc;
EVSYS.CH6MUX = EVSYS_CHMUX_PORTC_PIN6_gc;
EVSYS.CH7MUX = EVSYS_CHMUX_PORTC_PIN7_gc;

Here is a quick sequence diagram of what I expect to happen in the end:

 

I know I can do this all using interrupts but the CPU is already running lots of other stuff for animations calculations, so if this can be done using the event system and DMA, that'd be a huge improvement on my current design.

 

Thanks for any feedbacks :)

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

Hi!

 

You didn't mention your chip.

Your configuration is possible for e.g. XMEGA128A1U (which I am using mostly).

 

Shortly: you use 2 ADCs, which have 4 channels each. Say ADCA will be triggered by events 0..3, and ADCB will be triggered by events 4..7. Then you use 2 DMA channels, triggered directly by ADC conversion complete signal. You will need to transfer all 4 channels (8 bytes at a time), so destination should be an array. Destination data will not be damaged since ADC keeps last measurement until next conversion is complete. Last step (1 ms FLASH) should be done in software.

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

I use an ATXMEGA64A3U.

 

Can I use a single channel with MUX settings to use PORTB? And trigger event with something like that:

EVSYS.CH0MUX = EVSYS_CHMUX_PORTC_PIN0_gc | EVSYS.CH0MUX = EVSYS_CHMUX_PORTC_PIN1_gc | etc...

The only thing that bothers me with this is that I need to know which pin triggered the CH0 event in order to flash the proper reset pin. I'm kinda doubting I can have the pin info carried on trough the event system, dma channel then ISR completed to do my 1ms flash.

 

If I would have a way to store the pin triggered in memory to use it in the ISR at the end of the process would be fantastic. Also if I could trig only a single ADC conversion on that particular pin. Having to sample the 8 pins every time one is triggered would not cause any performance issues in my case.

 

In the end would it be possible to MUX 8 pins to the event system CH0, move to the DMA CH0 to sample the pin X, call ISR at the.

 

The reason behind this is because all my PORTA pins are connected to LED output, PORTB pins are ADC input from my sensor, PORTC pins are schmitt trigger input for digital triggering, PORTF pins are the reset pins hooked up to the sensor circuitry to clean up the caps via a transistor. All other ports on the micro are used for LCD, rotary encoder, 4 buttons, 2 LEDS, DMX USART, SD card and PDI. 

 

So I'm restricted to 1 ADC with ADCB to route my events..

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

This is how my pins how used FWI:

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

Kevin Isabelle wrote:

 

Can I use a single channel with MUX settings to use PORTB? And trigger event with something like that:

EVSYS.CH0MUX = EVSYS_CHMUX_PORTC_PIN0_gc | EVSYS.CH0MUX = EVSYS_CHMUX_PORTC_PIN1_gc | etc...

 

No, 1 pin -> 1 event

EVSYS.CH0MUX = EVSYS_CHMUX_PORTC_PIN0_gc;
EVSYS.CH1MUX = EVSYS_CHMUX_PORTC_PIN1_gc;
// ...
EVSYS.CH7MUX = EVSYS_CHMUX_PORTC_PIN7_gc;

 

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

I missed the point - XMEGA64A3U has 2 ADCs, so why can't you use both?

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

Once again - ADC has 4 independant channels, that can be triggered indivdually by events.

So if you use both ADCs, you can measure 8 lines triggered by 8 edges on PORTC, routed via event system. End of conversion on any ADC channel can trigger DMA transfer to move result to some memory area. Till this point all task is performed in hardware.

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

This document explains how to use PORTB for ADCA and ADCB

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

Haaa that is totally true. ADCA is not "tied" to PORTA. My bad. Thanks for the clarification.

 

So, all this part is pretty neat but how do I know which pin trigged the process so I can flash the proper RST pin in the DMA completion ISR?

 

ISR(DMA_CH0_vect){ // same function for DMA_CH1_vect for ADCB
	
	int8_t resetPin = 0; // How do I know that?
	
	RESET_TRIG_PORT.OUTSET |= resetPin;
	
	delay_ms(1);
	
	RESET_TRIG_PORT.OUTCLR |= resetPin;
}

This would be the last piece I need to understand to make the whole thing work.

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

Ok I think I understand. I don't put my ISR on DMA vector, I put it on the ADC channel conversion vector. This way I have one ISR per channel (8 in total). Then, I can know exactly which reset pin to flash.

 

ISR(ADCA_CH0_vect){
	flashResetPin(RESET_PIN1);
}

ISR(ADCA_CH1_vect){
	flashResetPin(RESET_PIN2);
}

ISR(ADCA_CH2_vect){
	flashResetPin(RESET_PIN3);
}

ISR(ADCA_CH3_vect){
	flashResetPin(RESET_PIN4);
}

ISR(ADCB_CH0_vect){
	flashResetPin(RESET_PIN5);
}

ISR(ADCB_CH1_vect){
	flashResetPin(RESET_PIN6);
}

ISR(ADCB_CH2_vect){
	flashResetPin(RESET_PIN7);
}

ISR(ADCB_CH3_vect){
	flashResetPin(RESET_PIN8);
}

But just to confirm, does anyone know if the ADC ISR is triggered when the conversion is done via a DMA transfer?

 

BTW, I'm waiting for new PCBs revisions to start testing this implementation. I'll post my tests and solutions in this thread as soon as I get new development.

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

Every DMA channel has its own ISR as well. You will need to check and clear ADCA.INTFLAGS/ADCB.IN TFLAGS if you need to know which ADC channel was working.

Last Edited: Sat. Jan 13, 2018 - 09:56 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ADC ISR is triggered when ADC conversion is complete :) There is no DMA influence here. You can use DMA to transfer this data somewhere and there is DMA ISR which can be triggered when data transfer is complete.

Since your are using ADC ISR for every channel you no longer need DMA, simply write result to destination address in this ISR.

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

Ok guys, received my new part and started coding. Finally got it working and i'd say, it is pretty good! Here is a summary of what I did:

 

1) Here is my function to configure all pins and ports for Schmitt trigger, ADC and reset pins

static void init_Triggers(void) {
	
	TRIGGER_PORT.DIRCLR =  PIN0_bm | PIN1_bm | PIN2_bm | PIN3_bm | PIN4_bm | PIN5_bm | PIN6_bm | PIN7_bm;
	SCHMITT_TRIG_PORT.DIRCLR = PIN0_bm | PIN1_bm | PIN2_bm | PIN3_bm | PIN4_bm | PIN5_bm | PIN6_bm | PIN7_bm;
	RESET_TRIG_PORT.DIRSET = PIN0_bm | PIN1_bm | PIN2_bm | PIN3_bm | PIN4_bm | PIN5_bm | PIN6_bm | PIN7_bm;
	RESET_TRIG_PORT.OUTCLR = PIN0_bm | PIN1_bm | PIN2_bm | PIN3_bm | PIN4_bm | PIN5_bm | PIN6_bm | PIN7_bm;
	
	TRIGGER_PORT.PIN0CTRL = PORT_OPC_PULLDOWN_gc;
	TRIGGER_PORT.PIN1CTRL = PORT_OPC_PULLDOWN_gc;
	TRIGGER_PORT.PIN2CTRL = PORT_OPC_PULLDOWN_gc;
	TRIGGER_PORT.PIN3CTRL = PORT_OPC_PULLDOWN_gc;
	TRIGGER_PORT.PIN4CTRL = PORT_OPC_PULLDOWN_gc;
	TRIGGER_PORT.PIN5CTRL = PORT_OPC_PULLDOWN_gc;
	TRIGGER_PORT.PIN6CTRL = PORT_OPC_PULLDOWN_gc;
	TRIGGER_PORT.PIN7CTRL = PORT_OPC_PULLDOWN_gc;
	
	SCHMITT_TRIG_PORT.PIN0CTRL = PORT_ISC_RISING_gc | PORT_OPC_PULLDOWN_gc;
	SCHMITT_TRIG_PORT.PIN1CTRL = PORT_ISC_RISING_gc | PORT_OPC_PULLDOWN_gc;
	SCHMITT_TRIG_PORT.PIN2CTRL = PORT_ISC_RISING_gc | PORT_OPC_PULLDOWN_gc;
	SCHMITT_TRIG_PORT.PIN3CTRL = PORT_ISC_RISING_gc | PORT_OPC_PULLDOWN_gc;
	SCHMITT_TRIG_PORT.PIN4CTRL = PORT_ISC_RISING_gc | PORT_OPC_PULLDOWN_gc;
	SCHMITT_TRIG_PORT.PIN5CTRL = PORT_ISC_RISING_gc | PORT_OPC_PULLDOWN_gc;
	SCHMITT_TRIG_PORT.PIN6CTRL = PORT_ISC_RISING_gc | PORT_OPC_PULLDOWN_gc;
	SCHMITT_TRIG_PORT.PIN7CTRL = PORT_ISC_RISING_gc | PORT_OPC_PULLDOWN_gc;
	
	SCHMITT_TRIG_PORT.INT0MASK = PIN0_bm | PIN1_bm | PIN2_bm | PIN3_bm | PIN4_bm | PIN5_bm | PIN6_bm | PIN7_bm;
	SCHMITT_TRIG_PORT.INTCTRL = PORT_INT0LVL_HI_gc;

	ADCB.CALL = ReADCBlibrationByte(PRODSIGNATURES_ADCBCAL0);
	ADCB.CALH = ReADCBlibrationByte(PRODSIGNATURES_ADCBCAL1);
	ADCB.CTRLA = ADC_ENABLE_bm;

	ADCB.CTRLB = ADC_RESOLUTION_12BIT_gc;
	ADCB.CTRLB &= ~ADC_CONMODE_bm;
	ADCB.PRESCALER = ADC_PRESCALER_DIV128_gc;
	ADCB.REFCTRL = ADC_REFSEL_INTVCC_gc;
	ADCB.EVCTRL = ADC_EVACT_NONE_gc;
	ADCB.CH0.INTCTRL = 0;
	ADCB.CH0.CTRL = ADC_CH_INPUTMODE_SINGLEENDED_gc | ADC_CH_GAIN_1X_gc;
	
	_delay_us(400); // Wait at least 25 clocks


}

2) The interrupt that process the schmitt trigger 

ISR(STRIGGER_TRIGGED){ // PORTC_INT0_vect
	
	uint8_t changedbits;

	// ADC predelay loop for get an accurate reading
	for(int i=0; i<TRIGGER_PREDELAY_LOOP;i++){
		
	}

	changedbits = SCHMITT_TRIG_PORT.IN ^ portbhistory;
	portbhistory = SCHMITT_TRIG_PORT.IN;
	
	for (int bitNb = 0; bitNb<=7; bitNb++){
		if(changedbits & (1 << bitNb ))
		{
			processTriggerAction(bitNb);
		}
	}
}

3) Now the processTriggerAction function 

static void processTriggerAction(int triggerIndex) {

	ADCB.CH0.MUXCTRL = ((triggerIndex)<<3);	//Analog Input: ADC1 Pin (on PORTA.0)
	ADCB.CH0.INTCTRL = 0 ;
	ADCB.CH0.CTRL |= ADC_CH_START_bm;		//Start conversion on ADCB1 (bm = bitmask)
	while(ADCB.CH0.INTFLAGS==0);			//Wait for conversion to complete
	
	ADCB.CH0.INTFLAGS = ADCB.CH0.INTFLAGS;
	
	int result = ADCB.CH0.RES ; //Get the conversion result
	
	if (result > TRIGGER_STATES[triggerIndex]){
		TRIGGER_STATES[triggerIndex] = clipValue(result, triggerIndex);
	}
	
	portbhistory &= ~(1 << triggerIndex);
	flashResetPin(triggerIndex);
	
}

4) In the flashResetPin function, I flash until the capacitor is fully unloaded by checking the state of the schmitt trigger input. This makes that I don't have to wait a fixed amount of time and does it optimally. Got pretty good result with it looking at the scope:

RESET_TRIG_PORT.OUTSET |= pin;
while (SCHMITT_TRIG_PORT.IN & (1 << trig)){ }
RESET_TRIG_PORT.OUTCLR |= pin;

Using the portbhistory variable to know which trigger was hit is pretty cool, can't remember where I found this technique on the internet.

 

So that's it. I promised I would come back and post my result. Here they are :) If you have questions, hit me up.