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

#pragma once
#include <stm32f10x.h>
#include "usb.h"
/* hea = Hüftgelenkprothesen-Einschlag-Apparat
Teilprojekt im Virtual-Reality-Projekt HIPS:
Simulation der Chirurgie inklusive Haptik:
- Halten und Führen von Sägen und Raspeln (nicht hier)
- Einschlag der Prothese (hier)

Hardware STM32F103 (vermutlich China-Clone) auf BlackPill-Board:
A3	T2C4	PWM-Ausgang, steuert die Bremskraft
A4	ADC4	Hand-Potenziometer für manuelle Bremskraftvorgabe
A5	SCK1	Messschieber-Takt vom LM393 ¹
A6	(ADC6)	Verdreh-Sensor (Mikrotaster) ¹
A7	MOSI1	Messschieber-Daten vom LM393 ¹
A9	(Tx1)	Debug-Ausgang: ISR-Lastanzeige
A10	(Rx1)	Debug-Ausgang: Idle-Lastanzeige
(A11		USB D-)
(A12		USB D+)
B0	ADC8	Kraftmessdose, ≥ 100 kSa/s
B1	ADC9	Weg-Potenziometer, ≥ 100 kSa/s
B10	TxD3	Interface der Linak-Steuerung
B11	RxD3	Interface der Linak-Steuerung
(B12		Blaue LED, low-aktiv)
¹ erfordert chip-internen Pullup

Verwendung der Peripherie:
ADC1	Kraftmessdose, Weg-Potenziometer (schnell und kontinuierlich)
ADC2	Hand-Potenziometer (gelegentlich)
USART3	Linak-Servo
SPI1	Messschieber
Timer2	PWM-Ausgang
Timer3	Triggerverzögerung für ADC1
Timer4	Endeerkennung für SPI1
DMA1CH1	ADC1 (Kraftmessdose)
DMA1CH2	SPI1 (Messschieber)
USB	HID+WebUSB
*/

/***************************
 * Grundlegende Funktionen *
 ***************************/
void basic_delay(uint32_t);	// Wartet <argument×3>? Takte
void set_sysclock();		// Systemtakt auf 72 MHz (8-MHz-Quarz + PLL ×9) setzen
void revert_sysclock();		// Systemtakt auf ?? (RC-Oszillator) zurücksetzen
void idle();

#define BB_BIT(a,b) (*(volatile uint32_t*)(((uint32_t)&(a)&0xFF000000)+0x02000000+((uint32_t)&(a)&0x00FFFFFFF)*32+(b)*4))

/*************************
 * Debug-Hilfsfunktionen *
 *************************/
namespace debug{
 inline void isrstart() {BB_BIT(GPIOA->ODR,9) = 1;}
 inline void isrend()   {BB_BIT(GPIOA->ODR,9) = 0;}
 inline void workstart(){BB_BIT(GPIOA->ODR,10) = 1;}
 inline void workend()  {BB_BIT(GPIOA->ODR,10) = 0;}
}//namespace

namespace adc{
struct Stat;
}

// Alle Reports verwenden eine Report-ID.
// Ohne Konstruktor.
struct Report{
 byte repid;
 operator byte*() {return &repid;}	// Als Ganzes ansprechen
 operator const byte*() const {return &repid;}
 struct Stat{	// Report::Stat, size: 8
  int16_t
    min,	// Minimum (0..4095)
    max,	// Maximum (0..4095)
    avg,	// Mittelwert (0..4095)
    σ;	// MR610-Varianz = Standardabweichung der Grundgesamtheit
  void operator<<(adc::Stat&);	// „Raubkopie“
 };
};

/*****************************
 * A/D-Wandler und Statistik *
 *****************************/

namespace adc{
void init();
enum{
 loadF=1,	// Triggerquelle, Triggerkopplung, Gesamt-Samplezahl
 loadT=2,	// Triggerschwelle, Hysterese
 loadTp=4,	// Prätrigger
 loadTg=8,	// Gesamt-Samplezahl
};
void loadFeature(byte);	// bei Änderung von repF: Triggerschwellen nachführen
void arm();
void manualtrigger();
unsigned collect(uint16_t*,unsigned);
void get_allstat();
uint16_t get_poti(Report::Stat&);	// liefert min, max, Mittelwert
}

extern struct RepF:public Report{	// size: 16
 struct{byte
  bk:2,	// Bit 1:0 = Benutze Bremskraftvorgabe
	//	00 = Bremskraft vom Potenzoimeter
	//	01 = Bremskraft von <brems>
	//	10 = undefiniert
	//	11 = undefiniert
  tk:2,	// Bit 3:2 = Triggerkopplung
	//	00 = DC
	//	01 = AC (<t1> und <t2> verändern sich laufend)
	//	10 = undefiniert
	//	11 = undefiniert
  tq:2,	// Bit 5:4 = Triggerquelle
	//	00 = PB0 ≡ ADC8 (Kraft)
	//	01 = PB1 ≡ ADC9 (Wegpoti)
	//	10 = PA6 ≡ ADC6 (Verdrehtaster?)
	//	11 = PA1 ≡ ADC1 (frei)
  lk:2;	// Bit 7:6 = letzter Kanal des Oszilloskops, Kanalabtastrate reduzierend
	//	00 = 1 Kanal ADC8
	//	01 = 2 Kanäle (zurzeit fest) ADC8 + ADC9
	//	10 = 3 Kanäle ADC8 + ADC9 + ADC6
	//	11 = 4 Kanäle ADC8 + ADC9 + ADC6 + ADC1
  operator byte&() {return *(byte*)this;}	// im ganzen zugreifbar machen
 }f;		// Flags
 uint16_t brems;// Bremskraftvorgabe, 0..4095
 uint16_t tv;	// Trigger-Vorbereitung, 0..4095, bei AC variabel
  int16_t th;	// Trigger-Hysterese, positiv = steigende Flanke
  int16_t tp;	// Prätrigger (negativ = Posttrigger, max. <tg>)
 uint16_t tg;	// Gesamtzeit in Samples, also 7/6 µs, durch 12 teilbar
 uint16_t knull;// Kraft-Nullwert (wird bei Reset vom A/D-Wandler aktualisiert? Erstmal nicht!)
 uint16_t knib;	// Oszi-ADC-Kanalliste in 4 Nibbles 
// static constexpr int16_t tg_max=500;
 byte settrigger(int);	// Trigger-Vorbereitung anpassen
}repF;

extern struct RepI:public Report{	// size: 50
 byte flags;	// Bits:
		// 7 = Neues <p>,<os> nach 100 ms *
		// 6 = RepF geändert *
		// 5 = Neues vom Messschieber, neues <ms> *
		// 4 = RepT geändert, neues <ks>,<Δw> nach Triggerereignis *
		// 3:2 = Phase der Triggerung:
		//	0 = Abwarten von <repF.tp> Samples (nur wenn repF.tp>0)
		//	1 = Warten auf erste Triggerbedingung (Unterschreitung <repF.k1>)
		//	2 = Warten auf zweite Triggerbedingung (Überschreitung <repF.k2>)
		//	3 = Abwarten von <repF.tg-repF.tp> Samples
		// 1 = frei
		// 0 = Zustand des Verdreh-Tasters
 void reset();	// * = Dieses Bits lösen Interrupt-Transfer aus und werden danach gelöscht
 Stat p;	// ADC2-Statistik: Hand-Poti
 Stat os[4];	// ADC1-Statistik (Oszi-Statistik):
		// [0] Kraft, [1] Wegmess-Poti, [2] Verdrehsensor, [3] frei
 int16_t ms;	// Messschieber
 int16_t ks;	// Gemessener Kraftstoß (Integral über „Huckel“, t = 1 ms)
 int16_t km;	// Kraft-Maximum bei der Integration
 int16_t Δw;	// Wegdifferenz vom Wegmess-Poti beim Kraftstoß (nach 4 ms)
}repI;

extern struct RepL:public Report{	// size: 4
 byte addr;	// Bit 7 = lesen
 uint16_t value;
}repL;

extern struct RepT:public Report{	// size:
 struct Info{
  byte	nk:3,	// Anzahl Kanäle (1..4)
	tt:1,	// TrueTrigger: echt (true), von Hand (false)
	nr:4;	// Laufende Oszillogramm-Nummer
//  operator byte&() {return *(byte*)this;}	// im ganzen zugreifbar machen
 }info;
 uint16_t fill;	// Anzahl Samples (pro Kanal, typ. voll)
 uint16_t data[3432];	// Trace-Daten, verzahnt (3432 ≈ 4 ms)
// Der Report ist immerhin knapp 7 KByte groß!
 void reset() {adc::arm();}
 void grab(bool tt) {
  const unsigned nk = repF.f.lk+1;
  info.nk= nk;
  info.tt = tt;
  info.nr++;
  fill = adc::collect(data,elemof(data))/nk;
// Nach verzögertem Trigger: Daten aus DMA-Puffer einsammeln  
  integrate(repF.f.tk?repI.os[0].avg:repF.knull);
  BB_BIT(repI.flags,4) = 1;	// außerdem normaler 30-Byte-Report
  sendcount++;
 }
 unsigned byteLength() const {return 4+info.nk*sizeof*data*fill;}
 void integrate(int mean) const;	// verändert repI
 static byte sendcount;
}repT;

static_assert(elemof(RepT::data)%12 == 0);

struct RepReboot:public Report{
 byte doit;	// Nur wenn 1
};

/* Simple function pointer type to call user program */
typedef void (*func_ptr_t)();

extern func_ptr_t vtbl[16+56];

namespace usb{
extern byte DevStatus;	// Bit 0: SelfPower, Bit 1: RemoteWakeup
extern byte DevConfig;
void shutdown();
void init();
// Diese Routinen können blockieren und rufen dann idle()!
// Rekursion ist zu vermeiden! Ausnahme: sendXx() ist innerhalb poll() erlaubt.
void poll();
bool send0(const byte*,unsigned);
bool send(byte,const byte*,unsigned);
// Der letzte Parameter steuert ob Short-Packets okay sind, und ist ein Zeiger
// auf die empfangenen Bytes.
// Diese Routine blockiert und ruft idle()!
bool recv(byte,byte*,unsigned,unsigned* =nullptr);
enum{
 //onBusySkip=0x00,	// nach 5 ms raus mit false
 onBusyWait=0x10,	// nach 100 ms raus mit false (nur 1. Paket)
 onBusySend=0x20,	// nach 5 ms überschreiben (nur 1. Paket)
 noZlp=0x40,	// Kein Null-Byte-Paket am Ende senden
 partial=0x80,	// short-packet nicht absenden sondern (beim nächsten Aufruf) fortsetzen
};

// Callbacks
bool onSetReport(SetupPacket&);
bool onGetReport(SetupPacket&);
}

#define LED_INIT()	BB_BIT(GPIOB->CRH,16) = 1	// output open drain
#define LED		BB_BIT(GPIOB->ODR,12)		// 0 = on, 1 = off

namespace caliper{
 void init();	// SPI1 und DMA1CH2 aktivieren
}

namespace pwm{
 void init();
 inline void out(uint16_t v) {TIM2->CCR4=v;}
}
Vorgefundene Kodierung: UTF-80