/* 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“
}
Detected encoding: UTF-8 | 0
|