Source file: /~heha/mb-iwp/Antriebe/Linak-Servo/hips-210406.zip/adc.cpp

/* A/D-Wandler des STM32F103
 211014	erstellt
*/
#include "hips.h"

namespace adc{
static uint16_t dmabuf[256] __attribute__((section(".noinit")));
// DMA-Interrupts werden nur dazu benutzt,
// Statistiken über die Kraft zu erfassen.
// Die Oszi-Funktion verlässt sich auf den Analog-Watchdog,
// und von da ab wird die Historie ausgelesen.
// Letzteres schreit nach einem Speicher-zu-Speicher-DMA-Transfer
// und/oder nach einer Pufferumschaltung.

unsigned collect(int16_t*d,unsigned l) {
 unsigned constexpr n=elemof(dmabuf);
 unsigned idx=n-DMA1_Channel1->CNDTR;
 if (l>n) l=n;
 else if (l<n) {
  idx+=n-l;
  if (idx>=n) idx-=n;		// unnötige ältere Einträge ignorieren
 }
 for (unsigned i=0; i<l; i++) {
  d[i]=dmabuf[idx];
  if (++idx>=n) idx=0;
 }
 return l;
}

// ISR, Aufruf nach halber und voller DMA-Puffer-Füllung
// Einzige Aufgabe: Statistikwerte sammeln
static void dma_end() {
 const uint32_t isr=DMA1->ISR;
 DMA1->IFCR=1;		// Alle Arten von DMA-Interrupts Kanal 1 bestätigen
 uint16_t const*p=dmabuf;	// DMA zur Hälfte
 if (isr&2) p+=elemof(dmabuf)/2;// DMA komplett
 auto&r=repI;		// Input-Report (aus später mehreren)
 if (r.n==65535) return;	// Nichts tun wenn Puffer fertig
 r.k.add(p[0]);		// Nur den 1. Wert wegen Rechenlast!
// r.k.add(p[32]);
 if (++r.n>=65000) r.flags|=0xC0;
	// Voll genug, Überlauf droht (weil Messschieber fehlt)
}

//ISR, Aufruf nach Ablauf einer Zeitspanne nach 2. Trigger
//TODO: Hohe Priorität!
static void watchT() {
 TIM3->SR=0;
 repStoß.collect();
}

static void future(func_ptr_t cb,int16_t to) {
 vtbl[16+TIM3_IRQn]=cb;
 TIM3->ARR = to>0 ? to-1 : 0;	// Abzuwartende Samples
 TIM3->CR1 = TIM_CR1_OPM|TIM_CR1_CEN;
}

static Simplestat poti;
static uint16_t poti_n;

uint16_t get_poti(Simplestat&p) {
 ADC2->CR1 = ADC_CR1_SCAN;		// Hier darf kein Interrupt vom ADC2 dazwischenfunken
 p=poti;		// memcpy
 if (poti_n) p.sum/=poti_n;
 else if (repI.flags&0x20) p.sum>>=16;
 poti.reset();
 poti_n=0;
 ADC2->CR1 = ADC_CR1_SCAN|ADC_CR1_EOCIE;	// Interruptfreigabe
 return p.sum;
}

static uint32_t tr[6] __attribute__((section(".noinit")));
static uint32_t*trp;		// Laufzeiger in obige Liste

static void advancetrigger() {
 ADC1->SR=0;			// Flags löschen
 ADC1->HTR=*trp++;		// Grenzen umaktivieren
 ADC1->LTR=*trp++;
}

//ISR: Sammelinterrupt für ADC1: Watchdog und ADC2: Konversionsende
//TODO: Mittlere Priorität!
static void check_adc() {
 if (ADC1->SR&ADC_SR_AWD) {	// Watchdog = Triggergrenzenüberschreitung?
  advancetrigger();		// Zweite Triggergrenze (Phase 2) oder keine (Phase 3)
  byte f = repI.flags = repI.flags+4 | 0x80;	// auf Phase 2 oder 3;
  if ((f&0x0C)==0x0C) future(watchT,repF.tg-repF.tp);	// Phase 3 erreicht
 }
 if (ADC2->SR&ADC_SR_EOC) {
  uint16_t v = ADC2->DR;	// der Lesezugriff löscht ADC_SR_EOC
  auto&r=repI;
  if (!(r.flags&0x20)) {
   poti.add(v);
   if (!++poti_n) r.flags|=0x20;
  }
 }
}
//ISR, Flankentrigger scharfmachen, Aufruf wenn Prätrigger-Zeit abgelaufen
//TODO: Hohe Priorität!
static void edgearm() {
 TIM3->SR=0;			// Timer3 erledigt
 advancetrigger();
 repI.flags = repI.flags+4 | 0x80;	// Zustandsbits 3:2 auf Phase 1
}

void arm() {
 repI.flags = repI.flags&~0x0C | 0x80;	// Zustandsbits 3:2 auf Phase 0
 if (repF.k1<=repF.k2) {	// steigende Flanke suchen
  tr[0]=0xFFF;
  tr[1]=repF.k1;		// Unterschreitung feststellen lassen
  tr[2]=repF.k2;		// Überschreitung feststellen lassen
  tr[3]=0;
 }else{
  tr[0]=repF.k2;		// Überschreitung feststellen lassen
  tr[1]=0;
  tr[2]=0xFFF;
  tr[3]=repF.k1;		// Unterschreitung feststellen lassen
 }
 tr[4]=0xFFF;
 tr[5]=0;
 trp=tr;
 future(edgearm,repF.tp);
}

void manualtrigger() {
//PROBLEM!!
// if (NVIC->ISER[TIM3_IRQn>>5]&1<<(TIM3_IRQn&31)) return;
 ADC1->HTR=0xFFF;
 ADC1->LTR=0;			// Triggerbedingungen beseitigen
 repStoß.collect();
}

// Timer2: Periode 13 Hz, Periodendauer 76 ms
static void timerInit() {
 TIM3->PSC = 6*14-1;		// = 83, entsprechend 1,16 µs (Abtastrate von AIN8)
 TIM3->DIER = TIM_DIER_UIE;	// Interrupt wenn CNT==ARR -> CNT==0
 NVIC_EnableIRQ(TIM3_IRQn);
}

void reloadFeature(byte change) {
 if (change) arm();		// wenn die DMA-Puffergröße bleibt, nichts weiter tun
}
uint32_t getTriggerState() {
 return (trp-tr)>>1;	// 0..3
}

static void dmaInit() {
 vtbl[16+DMA1_Channel1_IRQn]=dma_end;
 NVIC_EnableIRQ(DMA1_Channel1_IRQn);
//ADC1 ist fest mit DMA1Ch1 verknüpft!
 DMA1_Channel1->CPAR = uint32_t(&ADC1->DR);
 DMA1_Channel1->CMAR = uint32_t(dmabuf);
 DMA1_Channel1->CNDTR = elemof(dmabuf);
 DMA1_Channel1->CCR = 1<<10	// 16 Bit Speicher
		|2<<8		// 32 Bit Peripherie (Seite 237: „must be accessed by words“)
		|1<<7		// Speicher-Inkrement
		|1<<5		// zirkulär
		|1<<2		// Interrupt bei halber Transferlänge
		|1<<1		// Interrupt bei voller Transferlänge
		|1<<0;		// Freigabe
}

// Die Portpins und RCC sind bereits initialisiert!
void init() {
 ADC1->CR2 = ADC_CR2_ADON;	// einschalten
 ADC2->CR2 = ADC_CR2_ADON;

// Beide A/D-Wandler zuerst „kalibrieren“ (mit „genau“ hat das Ergebnis kaum zu tun)
 delay(8);			// 2 ADC-Takte warten: (48MHz / 12MHz) * 2 = 8 => 8 Nops
 ADC1->CR2 = ADC_CR2_CAL|ADC_CR2_ADON;	// Kalibrierung beider ADCs starten
 ADC2->CR2 = ADC_CR2_CAL|ADC_CR2_ADON;
 while ((ADC1->CR2|ADC2->CR2)&ADC_CR2_CAL);	// Warten bis beide ADCs fertig

// ADC2 konvertiert ausschließlich das Potenziometer im Schneckentempo.
 ADC2->SQR3 = 9<<0;	// ADC-Kanäle der Normalsequenz: Nur ADC9
// ADC2->SQR1 = 1-1<<20;
 ADC2->SMPR2 = 7<<27;	// Maximale Abtastzeit für ADC9 (Potenziometer)
// Wandlungszeit: 239,5 + 12,5 = 252 ADC-Takte ≈ 48 kSa/s
 ADC2->CR1 = ADC_CR1_SCAN|ADC_CR1_EOCIE;
 ADC2->CR2 = ADC_CR2_CONT|ADC_CR2_ADON;
 ADC2->SR = 0;	// Eventuelle Interruptanforderungen löschen

// ADC1 konvertiert oszilloskopartig die Kraftmessdose mit minimaler Abtastzeit
// Wandlungszeit: 1,5 + 12,5 = 14 ADC-Takte ≈ 1,2 µs ≈ 857 kSa/s
 ADC1->SQR3 = 8<<0;
// ADC1->SQR1 = 1-1<<20;
// Interrupt bei Analog-Watchdog, auf 1 Kanal bezogen.
// Die Schaltgrenzen (LTR, HTR) werden per Software gesteuert
// Nach Reset sind sie so eingestellt, dass keine Interrupts ausgelöst werden.
 ADC1->CR1 = ADC_CR1_AWDEN|ADC_CR1_AWDSGL|ADC_CR1_SCAN|ADC_CR1_AWDIE|8;
				// Scan mit Watchdog auf Takt; Dual-Modus: 6 = Regulär Simultan
 ADC1->CR2 = ADC_CR2_DMA|ADC_CR2_CONT|ADC_CR2_ADON;	// DMA, kontinuierlich, noch nicht loslegen
 ADC1->SR = 0;	// Eventuelle Interruptanforderungen löschen

 dmaInit();
 timerInit();
 vtbl[16+ADC1_2_IRQn]=check_adc;
 NVIC_EnableIRQ(ADC1_2_IRQn);	// Ob ADC1 oder ADC2 wird in der ISR verzweigt

 ADC2->CR2 = ADC_CR2_CONT|ADC_CR2_ADON;
 ADC1->CR2 = ADC_CR2_DMA|ADC_CR2_CONT|ADC_CR2_ADON;	// loslegen
 arm();
}

}//namespace
Detected encoding: UTF-80