#include "hea.h"
void basic_delay(uint32_t timeout) {
do __NOP(); while (--timeout);
}
static uint32_t bootmagic;
const uint32_t BOOTMAGIC=0xDEADBEEF;
static void bootstart() __attribute__((naked,noreturn));
static void bootstart() {
bootmagic=BOOTMAGIC;
SCB->AIRCR = 0x05FA0004;
}
// Im Gegensatz zum PIC16F145x oder ATmegaX8/XUY
// ist der Bootloader nicht vor versehentlichem
// Überschreiben (per Fuse) geschützt. 220120 war er mal weg.
RepF repF={2,{0,1,0,1},1024,900,50,100,elemof(RepT::data),920,0x1698};
RepI repI;
RepT repT;
RepL repL;
byte RepT::sendcount; // Nur für WebUSB = Bulk-Endpoint 2
void RepI::reset() {
__disable_irq();
flags&=0x0F; // Low-Nibble bleibt bestehen
__enable_irq();
}
template<class T, class U>void limit(T&v,U min,U max) {
if (v<min) v=min;
if (v>max) v=max;
}
template<class T>T abs(T v) {return v<0 ? -v : v;}
bool usb::onSetReport(usb::SetupPacket&setup) {
union{
RepF repF;
RepL repL;
byte repid;
operator byte*() {return &repid;}
}recvbuf;
uint16_t len=0;
switch (setup.wValue) {
case 0x0302: len=sizeof(RepF); break;
case 0x0304: len=sizeof(RepL); break;
case 0x022A:
case 0x0363: len=sizeof(RepReboot); break;
}
if (!len) return false; // falsches setup.wValue
if (setup.wLength!=len) return false; // falsche Transaktionslänge
bool ret=usb::recv(0,recvbuf,len);
if (recvbuf.repid!=setup.wValueL) return false; // falsche Report-ID
switch (setup.wValue) {
case 0x022A: if (recvbuf[1]==1) adc::manualtrigger(); break;
case 0x0363: if (recvbuf[1]==1) bootstart(); break;
case 0x0302: {
byte change=0;
if (repF.f.bk!=recvbuf.repF.f.bk) change|=0x10;
if ((repF.f^recvbuf.repF.f)&0xFC) change|=adc::loadF;
repF.f=recvbuf.repF.f;
// Einzelwertweise übertragen
if ((unsigned)recvbuf.repF.brems<4096
&& repF.brems!=recvbuf.repF.brems) {
repF.brems=recvbuf.repF.brems;
change|=0x20;
}
if ((unsigned)recvbuf.repF.tv<4096 // muss >=0 sein!
&& repF.tv!=recvbuf.repF.tv) {repF.tv=recvbuf.repF.tv; change|=adc::loadT;}
if (abs<int>(recvbuf.repF.th)<1024
&& repF.th!=recvbuf.repF.th) {repF.th=recvbuf.repF.th; change|=adc::loadT;}
if ((unsigned)recvbuf.repF.tg<=elemof(repT.data)
&& repF.tg!=recvbuf.repF.tg) {repF.tg=recvbuf.repF.tg/12*12; change|=adc::loadTg;}
if (recvbuf.repF.tp<=repF.tg // hier: beliebig negativ erlaubt
&& repF.tp!=recvbuf.repF.tp) {repF.tp=recvbuf.repF.tp; change|=adc::loadTp;}
if ((unsigned)recvbuf.repF.knull<4096
&& repF.knull!=recvbuf.repF.knull) {
repF.knull=recvbuf.repF.knull; change|=0x40;}
if (change&0x30 && repF.f.bk) pwm::out(repF.brems);
adc::loadFeature(change&0x0F);
}break;
}
return ret;
}
bool usb::onGetReport(usb::SetupPacket&setup) {
switch (setup.wValue) {
case 0x0101: return usb::send0(repI,sizeof repI);
case 0x0302: return usb::send0(repF,sizeof repF);
case 0x0303: return usb::send0(repT,sizeof repT);
case 0x0304: return usb::send0(repL,sizeof repL);
}
return false;
}
static void USB_Renum() {
BB_BIT(GPIOA->CRH,16) = 1; // PA12: Open Drain Output
BB_BIT(GPIOA->ODR,12) = 0; // PA12 nach Low ziehen
basic_delay(0x20000);
BB_BIT(GPIOA->ODR,12) = 1; // PA12 nach High freilassen
BB_BIT(GPIOA->CRH,16) = 0; // PA12: Eingang
}
void set_sysclock() { // auf 72 MHz
uint32_t cr=RCC->CR;
RCC->CR=cr|=1<<16; // 8-MHz-Quarz aktivieren
while (!(RCC->CR&1<<17)); // warten
uint32_t cfgr = 0<<22 // USB-Takt = 72 MHz ÷ 1,5 = 48 MHz (exakt)
| RCC_CFGR_PLLMULL9 // PLL-Takt = 8 MHz × 9 = 72 MHz
| RCC_CFGR_PLLSRC_HSE // PLL als Systemtakt
| RCC_CFGR_ADCPRE_DIV6 // ADC-Takt = 72 MHz ÷ 6 = 12 MHz (14 MHz max.)
| RCC_CFGR_PPRE2_DIV1 // ABP2-Takt = 72 MHz ÷ 1 = 72 MHz (max.)
| RCC_CFGR_PPRE1_DIV2 // ABP1-Takt = 72 MHz ÷ 2 = 36 MHz (max.)
| RCC_CFGR_HPRE_DIV1; // AHB-Takt = 72 MHz ÷ 1 = 72 MHz (max.)
RCC->CFGR=cfgr;
RCC->CR=cr|=1<<24; // PLL aktivieren
while (!(RCC->CR&1<<25)); // warten
FLASH->ACR=0x32; // Flash benötigt für 72 MHz 2 Waitstates
RCC->CFGR=cfgr|=2; // Jetzt PLL als Systemtakt
while (!(RCC->CFGR&1<<3)); // Warten
}
void revert_sysclock() {
BB_BIT(RCC->CFGR,1) = 0;
BB_BIT(RCC->CR,24) = 0;
RCC->CFGR = 0;
BB_BIT(FLASH->ACR,0) = 0;
BB_BIT(RCC->CR,16) = 0;
}
// Aufruf in Hauptschleife aber auch aus usb::poll und usb::send heraus,
// daher unbedingt Rekursionen verhindern
void idle() {
static byte lock; // alles erlaubt
if (!BB_BIT(lock,0)) { // USB EP0
BB_BIT(lock,0)=1;
usb::poll();
BB_BIT(lock,0)=0;
}
if (!BB_BIT(lock,1)) { // USB sonstige Endpoints
BB_BIT(lock,1)=1;
if (repI.flags&0xF0) { // Zuerst der Summary-Report
// Während HID-Reports vom usbhid.sys ausgelesen werden, auch wenn keine Äpp
// sich dafür interessiert …
usb::send(1|usb::onBusyWait,repI,sizeof repI);
// … sammeln sich Bulk-Daten bei WebUSB an, wenn niemand diese abholt.
// Daher alte Daten rotzfrech überschreiben
usb::send(2|usb::onBusySend,repI,sizeof repI);
repI.reset();
}else if (repT.sendcount) { // danach(!) der Trace-Report
debug::isrstart();
// Ein Timeout von 2 ms sorgt zwischen den Paketen für Abbruch,
// wenn sich bspw. die Web-Äpp mittendrin verabschiedet.
usb::send(2|usb::onBusySend,repT,repT.byteLength());
repT.sendcount--; // Auch bei Sende-Fehler dekrementieren!
debug::isrend();
}
BB_BIT(lock,1)=0;
}
static byte tic; // für flotte 10 Hz Datenaktualisierung
byte toc = USB.FNR;
if (byte(toc-tic)>=100) {
tic+=100;
// Da repI.flags auch in ISRs beeinflusst wird,
// über atomare Bitsetzfunktion gehen
BB_BIT(repI.flags,7) = 1; // nach 100 ms Update erzwingen
adc::get_allstat();
// AC-Trigger mit 10 Hz nachführen
if (repF.f.tk && repF.settrigger(repI.os[repF.f.tq].avg)&0x10) {
adc::loadFeature(adc::loadT);
BB_BIT(repI.flags,6) = 1; // Veränderung in repF melden
}
// Potenziometerwert mit 10 Hz einlesen
int q = repI.p.avg; // alter Wert zum Vergleich
auto v=adc::get_poti(repI.p);
q-=v; if (q<0) q=-q;
if (repF.f.bk && q>=16) { // Wenn am Poti gefummelt wurde
repF.f.bk = 0; // Fernsteuerung stehlen
BB_BIT(repI.flags,6) = 1; // Veränderung in repF melden
}
// D/A-Wandler mit 10 Hz aktualisieren
if (!repF.f.bk) pwm::out(v);
// Verdrehtaster mit 10 Hz einlesen
BB_BIT(repI.flags,0) = ~GPIOA->IDR>>6&1;
// Bit 0 reflektiert Zustand des Verdreh-Tasters
}
}
static void meminit() {
// Durch 4 teilbare Adressen werden durch das Linkerskript sichergestellt.
extern uint32_t __ctors,__etext,__data,__bss,__noinit;
auto d=&__data;
_Pragma("GCC diagnostic push")
_Pragma("GCC diagnostic ignored \"-Warray-bounds\"")
auto s=&__etext+2;
// Aus unerfindlichem Grund ist da eine 8-Byte-Lücke im hea.bin!
// Genauso wenn man hea.hex generiert.
// Meine Vermutung: Rechenfehler in objcopy mit dem Vektor-Vorspann.
while (d!=&__bss) *d++=*s++; // initialize data
_Pragma("GCC diagnostic pop")
while (d!=&__noinit) *d++=0; // clear BSS
auto fp=&__ctors; // list of static constructors
while (fp!=&__etext) reinterpret_cast<func_ptr_t>(*fp++)(); // call them
}
func_ptr_t vtbl[16+56] __attribute__((section(".vtbl")));
static_assert(sizeof vtbl==0x120); // initialisierte Daten ab 0x20000520
static void Reset_Handler(uint32_t) __attribute__((naked,noreturn));
static void Reset_Handler(uint32_t) {
if (bootmagic==BOOTMAGIC) {
bootmagic=0;
asm volatile(
" movs r1,#0 \n" // movs ist kürzer als mov!
" ldr r0,[r1] \n" // Stackpointer von Flash-Adresse 0
" msr msp,r0 \n" // einsetzen
" ldr r0,[r1,#4] \n" // Ansprungadresse von Flash-Adresse 4
" bx r0 \n"); // anspringen
}
set_sysclock();
meminit();
SCB->VTOR=uint32_t(vtbl); // Vektortabelle in RAM umsetzen
RCC->AHBENR |= 1; // DMA aktivieren (Register bei Reset nicht Null!)
RCC->APB1ENR = 0x00840007; // USB, USART3 und Timer4:2 takten
RCC->APB2ENR = 0x0000160D; // SPI1, ADC2:1, PortB:A, Alternate Port Function (für ADC)
// Die Ports C und D werden nur gebraucht,
// wenn man am BluePill40/BlackPill34-Board
// an den Quarzen herumlötet und die entsprechenden Pins frei macht.
// So genügen Port A und B.
GPIOA->BSRR = 0x00E0; // Pullups an A7:5 aktiveren
GPIOA->CRL = 0x88809888; // A7:5 auf High ziehen, A3 = PWM-Ausgang
GPIOA->CRH = 0x88854118; // alles ungenutzte auf auf Low ziehen
GPIOB->CRL = 0x88888800; // B1:0 = Analogeingänge
GPIOB->CRH = 0x88864A88; // B12:10 = LED, RxD, TxD aktivieren
// 0 = Analoger Eingang (ADC)
// 1 = Ausgang 10 MHz
// 2 = Ausgang 2 MHz
// 3 = Ausgang 50 MHz
// 4 = Eingang ohne Pullup/Pulldown (USB,RxD)
// 5 = Open-Drain-Ausgang 10 MHz (USB kurzzeitig)
// 6 = Open-Drain-Ausgang 2 MHz (LED)
// 7 = Open-Drain-Ausgang 50 MHz
// 8 = Eingang mit Pullup/Pulldown je nach ODR (Taster, LM393; freie Pins)
// 9 = Alternativer Totempfahl-Ausgang 10 MHz (PWM)
// A = Alternativer Totempfahl-Ausgang 2 MHz (TxD)
// B = Alternativer Totempfahl-Ausgang 50 MHz
// C = undefiniert
// D = Alternativer Open-Drain-Ausgang 10 MHz
// E = Alternativer Open-Drain-Ausgang 2 MHz
// F = Alternativer Open-Drain-Ausgang 50 MHz
repI.repid=1; // Report-Header initialisieren
repT.repid=3; // (nicht in .data für nur 1 Byte pro Struktur)
repL.repid=4;
adc::init();
pwm::init();
caliper::init();
// LED_INIT();
USB_Renum();
usb::init();
for(;;) idle();
}
/* Minimal Flash-based vector table */
extern int32_t __stacktop;
func_ptr_t vectors[] __attribute__((section(".vectors"))) = {
(func_ptr_t)&__stacktop, // Initial stack pointer (MSP)
(func_ptr_t)Reset_Handler, // Initial program counter (PC): Reset handler
};
Detected encoding: UTF-8 | 0
|