Quelltext /~heha/mb-iwp/Antriebe/Linak-Servo/hea-fw-230331.zip/adc.cpp

/* A/D-Wandler des STM32F103
 211014	erstellt
*/
#include "hea.h"
#include <cstring>	// memcpy

namespace adc{

static struct Stat{
 unsigned count;
 uint16_t min,max;
 uint32_t sum;
 uint64_t sumq;	// Summe der Quadrate
 void push(uint16_t v) __attribute__((always_inline)) {
  count++;
  if (min>v) min=v;
  if (max<v) max=v;
  sum += v;
  sumq+=(uint32_t)v*v;	// compiliert manchmal(!) zu „ldrd — umlal — strd“
 }
 uint16_t mean() const {return sum/count;}
 uint64_t sfq() const {return sumq-(uint64_t)sum*sum/count;}
 void reset() {min=0xFFFF; sumq = sum = count = max = 0;}
}*statP4[4], *statP, stat[10];

// Aureichend Puffer mit (wohlweislich) ÷24 teilbarer Größe
static uint16_t dmabuf[4296] __attribute__((section(".noinit")));

static_assert(elemof(dmabuf)%24==0); // 2× wegen Doppelpufferung
// Zykluszeit (beide Halbpuffer): 14/12MHz*4284 = 4,998 ms

// DMA-Interrupts werden nur dazu benutzt,
// Statistiken (über Kraft und Weg) 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(uint16_t*d,unsigned len) {
 unsigned idx = sizeof dmabuf/2 - DMA1_Channel1->CNDTR; // Schreibindex in Samples
 const unsigned nk = repF.f.lk+1;
 idx/=nk;	// Schreibindex in Clustern (abrunden)
 idx*=nk;	// Schreibindex in Samples an Clustergrenze
 const unsigned lpc = (uint16_t)repF.tg;
 if (len>lpc) len=lpc;	// Nicht mehr Samples als im Feature-Report vereinbart
 int ri = idx-len;	// Lese-Index in Samples (an Clustergrenze)
 if (ri<0) {	// wrap-around
  ri += elemof(dmabuf);
  int l = elemof(dmabuf)-ri;	// Länge erstes Teilstück in Samples
  memcpy(d,dmabuf+ri,l*sizeof*d);	// hinteres Teilstück zuerst
  d+=l;
  memcpy(d,dmabuf,(len-l)*sizeof*d);	// vorderes Teilstück danach
 }else{		// kein wrap-around
  memcpy(d,dmabuf+ri,len*sizeof*d);	// ganzes Stück
 }
 return len;
}

// ISR, Aufruf nach halber und voller DMA-Puffer-Füllung
// Aufgabe: Statistikwerte sammeln
// Priorität: niedrig (8)
// Aufruffrequenz: f_ADC/1200 = 714 Hz, 1,400 ms.
// Dauer: 700 µs (52% bei always_inline)
static void dma_end() {
// debug::workstart();
 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
 const unsigned lk=repF.f.lk;
 unsigned count=elemof(dmabuf)/2;	// ÷12 teilbar, dadurch glatt
 unsigned index=0;
 do{
  statP4[index]->push(*p++);
  index = index!=lk ? index+1 : 0;
 }while(--count);
// debug::workend();
}

//ISR, Aufruf nach Ablauf einer Zeitspanne nach 2. Trigger
//TODO: Hohe Priorität!
static void watchT() {
// debug::isrstart();
 TIM3->SR=0;
 repT.grab(true);	// Info: 2 Kanäle, mit Trigger
 arm();
// debug::isrend();
}

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;
}

//Wechselt <p> zwischen zwei Puffern des Doppelpuffers <q>
// und liefert den alten Wert von <p> als handliche Referenz.
// Interrupts stören nicht, da der Schreibzugriff atomar ist.
// (Der Inhalt von <p> darf aber nicht irgendwo gecached sein,
// daher nicht ohne Vorkehrungen geeignet für SMP-System.)
template<typename T> T&swap(T*&p, T q[2]) {
 T*r = p;
 p = r == q ? q+1 : q;
 return*r;
}

uint16_t get_poti(Report::Stat&p) {
 p << swap(statP,stat+8);	// Zeiger tauschen, umrechnen mit Quadratwurzel
 return p.avg;
}

void get_allstat() {
 unsigned const lk = repF.f.lk;	// lk = letzter Kanal
 Report::Stat*const p = repI.os;
 for (unsigned i=0; i<=lk; i++)
   p[i] << swap(statP4[i],stat+i*2);	// Zeiger tauschen, umrechnen mit Quadratwurzel
}	// und bereitmachen für nächstes swap()

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

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

//ISR: Sammelinterrupt für ADC1: Watchdog und ADC2: Konversionsende
// Priorität: mittel (4)
// Aufruffrequenz: 48 kHz, 20 µs
// Dauer: 1,14 µs (5,7%)
static void check_adc() {
 if (ADC1->SR&ADC_SR_AWD) {	// Watchdog = Triggergrenzenüberschreitung?
//  debug::isrstart();
  advancetrigger();		// Zweite Triggergrenze (Phase 2) oder keine (Phase 3)
  byte f = repI.flags = repI.flags+4;	// auf Phase 2 oder 3;
  if ((f&0x0C)==0x0C) future(watchT,repF.tg-repF.tp);	// Phase 3 erreicht
//  debug::isrend();
 }
 if (ADC2->SR&ADC_SR_EOC) {
  statP->push(ADC2->DR);	// der Lesezugriff löscht ADC_SR_EOC
 }
}
//ISR, Flankentrigger scharfmachen, Aufruf wenn Prätrigger-Zeit abgelaufen
// Priorität oberhalb DMA-Ende
static void edgearm() {
// debug::isrstart();
 TIM3->SR=0;			// Timer3 erledigt
 advancetrigger();
 repI.flags = repI.flags+4;	// Zustandsbits 3:2 auf Phase 1
// debug::isrend();
}

static void unarm() {
 BB_BIT(ADC1->CR1,6/*AWDIE*/) = 0;
 ADC1->HTR=0xFFF;
 ADC1->LTR=0;			// Triggerbedingungen beseitigen
}

static int16_t limitH(int16_t v) {return v<=4095 ? v : 4095;}
static int16_t limitL(int16_t v) {return v>=0 ? v : 0;}

static void tr_fill() {
 if (repF.th>=0) {	// steigende Flanke suchen
  tr[0]=0xFFF;
  tr[1]=repF.tv;		// Unterschreitung feststellen lassen
  tr[2]=limitH(repF.tv+repF.th);// Überschreitung feststellen lassen
  tr[3]=0;
 }else{				// fallende Flanke suchen
  tr[0]=repF.tv;		// Überschreitung feststellen lassen
  tr[1]=0;
  tr[2]=0xFFF;
  tr[3]=limitL(repF.tv+repF.th);// Unterschreitung feststellen lassen
 }
 tr[4]=0xFFF;
 tr[5]=0;
}

void arm() {
 repI.flags = repI.flags&~0x0C;	// Zustandsbits 3:2 auf Phase 0
 tr_fill();
 trp=tr;
 future(edgearm,repF.tp);
 BB_BIT(ADC1->SR,0/*AWD*/) = 0;
 BB_BIT(ADC1->CR1,6/*AWDIE*/) = 1;
}

void manualtrigger() {
// Darf nicht von (langsamer, CPU fressender) DMA-Routine unterbrochen weden.
// Eine funktionierende IRQL habe ich nicht gefunden. #230313
 __disable_irq();
 unarm();
 repT.grab(false);	// Info: 2 Kanäle, ungetriggert
 arm();		// Erneut scharfmachen (theoretisch: repT in Queue, aber zu wenig RAM)
 __enable_irq();
}

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

static void setScanChannels() {
 ADC1->SQR3 = (repF.knib>>0&15)<<0	// ADC8
	    | (repF.knib>>4&15)<<5	// ADC9
	    | (repF.knib>>8&15)<<10
	    | (repF.knib>>12&15)<<15;
 ADC1->SQR1 = repF.f.lk<<20;	// zunächst 2 Kanäle
}
static void setTriggerSource() {
 ADC1->CR1 = ADC_CR1_AWDEN
	   | ADC_CR1_AWDSGL	// Nur 1 Kanal überwachen zum Triggern
	   | ADC_CR1_SCAN
//	   | ADC_CR1_AWDIE
	   | repF.knib>>(repF.f.tq<<2)&15;	// ADC8 oder ADC9 überwachen
}

void loadFeature(byte change) {
 if (change&loadTg) return arm();
  		// wenn die DMA-Puffergröße bleibt, nichts weiter tun
 __disable_irq();
 if (change&loadF) {	// Hier nur Triggerquelle
  if (ADC1->SQR1>>20 != repF.f.lk) {
   BB_BIT(ADC1->CR2,0/*ADON*/)=0;
   BB_BIT(DMA1_Channel1->CCR,0/*EN*/)=0;
   setScanChannels();
   setTriggerSource();
   BB_BIT(DMA1_Channel1->CCR,0/*EN*/)=1;
   BB_BIT(ADC1->CR2,0/*ADON*/)=1;
  }else{
   setTriggerSource();	// Funktioniert nicht so recht!!
  }
 }			// (Wird bei laufendem Scan-Modus nicht wirksam??)
 if (change&(loadF|loadT)) {
  if (repF.f.tk) repF.settrigger(repI.os[repF.f.tq].avg);
  tr_fill();
  uint32_t *p = trp;
  if (p!=tr) {		// In Suchphase?
   ADC1->HTR = p[-2];	// Grenzen ändern
   ADC1->LTR = p[-1];	// (In Posttriggerphase passiert nichts)
  }
 }
 if (change&loadF) arm();
 __enable_irq();
}

uint32_t getTriggerState() {
 return (trp-tr)>>1;	// 0..3
}

static void dmaInit() {
 vtbl[16+DMA1_Channel1_IRQn]=dma_end;
 NVIC_SetPriority(DMA1_Channel1_IRQn,8);	// niedrige Priorität (aus 0..15)
 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 = sizeof dmabuf/2;
 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() {
 for (auto s:stat) s.reset();
 statP4[0] = stat+0;	// Anfangs gerade Indizes
 statP4[1] = stat+2;
 statP4[2] = stat+4;
 statP4[3] = stat+6;
 statP = stat+8;

 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)
 basic_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 Hand-Potenziometer im Schneckentempo.
 ADC2->SQR3 = 4<<0;	// ADC-Kanäle der Normalsequenz: Nur ADC4
 ADC2->SQR1 = 1-1<<20;
 ADC2->SMPR2 = 7<<(4*3);// Maximale Abtastzeit für ADC4 (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 Kraftmessdose und Wegpoti
// mit minimaler Abtastzeit.
// Wandlungszeit: 1,5 + 12,5 = 14 ADC-Takte ≈ 1,2 µs ≈ 857 kSa/s
 setScanChannels();
// 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.
 setTriggerSource();
				// 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_SetPriority(ADC1_2_IRQn,4);	// mittlere Priorität (aus 0..15)
 NVIC_EnableIRQ(ADC1_2_IRQn);	// Ob ADC1 oder ADC2 wird in der ISR verzweigt

 ADC2->CR2 = ADC_CR2_CONT
	   | ADC_CR2_ADON;	// loslegen: Alle 20 µs ein Interrupt = 1 Sample ADC4, dazu Analog-Watchdog
 ADC1->CR2 = ADC_CR2_DMA
	   | ADC_CR2_CONT
	   | ADC_CR2_ADON;	// loslegen: Alle 700 µs ein Interrupt = 600 Samples = 300 ADC8 + 300 ADC9
 arm();
}

}//namespace

// Triggerschwellen anpassen
// Wird neu bei „AC-Triggerkopplung“ aufgerufen, bei neuem Average für Trigger-Kanal
// Triggerschwellen werden im gültigen Bereich (0..4095) gehalten
byte RepF::settrigger(int avg) {
 byte ret=0;
// <avg> so eingrenzen, dass die Hysterese eingehalten wird
// (optional, es reicht auch die Limitierung bei tr_fill()
 if (th>=0) {	// Steigende Flanke
  if (avg>4095-th) {avg=4095-th; ret|=1;}
 }else{		// Fallende Flanke
  if (avg<-th) {avg=-th; ret|=2;}
 }
 if (tv!=avg) {
  tv=avg;
  ret|=16;
 }
 return ret;
}

// Einschlag-Apparat-spezifische Routine (nicht für Oszi-Anwendung):
// Kraft (des Oszillogramms) integrieren und durch Anzahl teilen,
// Maximum ermitteln (beides nur über die erste Millisekunde),
// Wegdifferenz ermitteln (über gesamtes Oszillogramm).
void RepT::integrate(int mean) const{
 const unsigned nk = info.nk;
 unsigned sum = 0, max = 0, count = fill;
// Nur über die erste Millisekunde integrieren! Rest ist eh' Rauschen.
 const unsigned sapms = 12000/14*nk;	// Bei nk==2: 428 Samples
 if (count>sapms) count=sapms;
 const uint16_t*p = data;	// Kanal 0 = Kraft adressieren
 do{
  int summand = *p-mean;
  if (summand>0) {
   sum+=summand;	// Nur positives zählen
   if (max<(unsigned)summand) max=summand;
  }
  p+=nk;
 }while(--count);
 repI.ks = sum/sapms;	// das passt wieder in int16_t
 repI.km = max;
// Wegdifferenz aus Poti-Mittel der ersten und letzten 8 Samples ermitteln
 if (nk>1) {
  const unsigned meansamp = 8;
  sum = 0; count = meansamp; p = data;
  do{
   sum+=p[1];
   p+=nk;
  }while(--count);	// sum = am Anfang
  max=0; count = meansamp; p = data+fill*nk;
  do{
   p-=nk;
   max+=p[1];
  }while(--count);	// max = am Ende
  repI.Δw = (max-sum)/meansamp;
 }
}

// Integer-Wurzelzieh-Algorithmus mit Rest im High-Teil
// https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Binary_numeral_system_.28base_2.29
uint64_t ullsqrt(uint64_t n) {
 uint64_t c = 0, d = 1uLL << 62;
 while (d>n) d>>=2;	// Erst mal „schnell“ Startbitposition finden
 while (d) {
  auto k = c+d;		// Addition Bit + linksverschobenes Ergebnis
  c >>= 1;
  if (n >= k) {
   n -= k;
   c += d;		// Bit setzen
  }
  d >>= 2;
 }
 return n<<32 | c;	// Hier sollte <c> und <n> nur noch 32 Bit groß sein.
}

static uint32_t usqrt_r(uint64_t n) {
 uint64_t c = ullsqrt(n);
 uint32_t r = c;
 if ((uint32_t)(c>>32) > r) ++r;	// Ergebnis runden
 return r;
}

void Report::Stat::operator<<(adc::Stat&stat) {
 min = stat.min;
 max = stat.max;
 avg = stat.count ? stat.mean() : 0;
 σ = stat.count ? usqrt_r(stat.sfq()/stat.count) : 0;
 stat.reset();	// „Datendiebstahl erfolgreich beendet“
}
Vorgefundene Kodierung: UTF-80