

#include "stm32f10x.h"
#include "hw_config.h"
#include "stdbool.h"
#include "usb_lib.h"
#include "usb_pwr.h"
#include "math.h"

#define USE_DMAFILL

#define OPT_DBG  __attribute__((optimize("-O0")))
#define OPT_FAST __attribute__((optimize("-Ofast")))


enum TError
{
  _ErrOk       =  0, // kein Fehler
  _ErrArrArr1  =  1, // ADC-Meßpunktabstand zu gering - ADC braucht 32 Takte
  _ErrArrArr2  =  2, // ADC-Meßpunktabstand zu gering - Triggerzeitpunkt unterschritten
  _ErrArrArr3  =  3, // ArrArr[] Gesamtzeit muss 64us sein (ADC-Meßpunktabstand zu groß?)
};

enum
{
  _CpuMHz      =  72,
  _AdcMHz      = _CpuMHz/2,
  SndCnt       = 4*1024, // Puffer für max. 4ms (=SndCnt/1e6) USB-Dropout
  SndBufMinVal = 100,   // Schwellwert für BufferOverflow-Statistik
  HSyncAmp_mV  = 600,   // Normalwert (ist die Hälfte davon wird als Schwellwert für VSync genommen)
  BurstAmp_mV  = 600,   // Normalwert (20% davon ist der Schwellwert zur Unterscheidung FBAS/BAS)
};

typedef struct
{
  int32_t Init;       // Init
  int32_t ImageBias;  // 0..4095; Schwarzreferenzwert in ADC-Einheiten
  int32_t ImageScale; // 0..65535; bei 4096 wird ein 12 Bit-ADC zu einem 8 Bit-Image[]-Wert skaliert
  int32_t ImageOfs;   // Bildlage in 1/4 Pixel (bei 0 : 10.5µs)
  int32_t Mode;       // 0:1888_576; 1:944_576; 2:472_576
  int32_t dADC2;      // 0: nur ADC1 mit 8 Bit in Image[]; sonst: mit ADC2 um "dADC2"-Takte vor ADC1 mit je 4 Bit in Image[]
} TConfig;

typedef struct
{
  int32_t Flags;  // 0:gültige Daten; 1:VSync(bei Zeile 311 und 624); 2:Extrazeilentakt
  int32_t Error;  // Fehler vom Typ TError
  int32_t VSyncCnt;
  int32_t SubImageOfs;
  int32_t SndBufMinErrCnt;
  int32_t ADC_OverrunCnt;
  int32_t HSync_ErrCnt;    //
  int32_t HSync_Min;       // Low-Wert vom HSync-Pegel in ADC-Einheiten
  int32_t HSync_Threshold; // High/Low-Schwellwert vom HSync-Pegel in ADC-Einheiten
  int32_t HSync_Raw;       // je Zeile angewendete statische Korrektur für die Video-Zeile in CPU-Zyklen
  int32_t HSync_dL16;      // Abweichung einer Video-Zeile in 1/65536 CPU-Zyklen
  int32_t RefBlank; // RefBlank_mV = RefBlank*3300/*4096;
  int32_t BurstAmp; // BurstAmp_mV = sqr(BurstAmp*2/3)/3*3300/4096*2;
  int32_t kk0;
  int32_t kk1;
} TUSBItem_State;

typedef struct
{
  uint8_t  Flags;
  uint8_t  MinMax;
  uint16_t BurstPhi;
  uint8_t  Image[59];
} TUSBItem_Line;

const uint8_t ScanArr_Mode3[] =
{
  30, 16, 18, 24,
  26, 16, 18, 24,
  26, 20, 22, 28,
  30, 20, 22, 28,
};

const uint8_t ScanArr_Mode4[] =
{
  30,  0,  6, 16,
  22,  0,  6, 16,
  22,  8, 14, 24,
  30,  8, 14, 24,
};

const uint8_t ScanArr_Mode5[] =
{
  // 22,  0,  6, 16,
  22, 24, 30, 16,
};

const uint8_t ScanArr_Mode6[] =
{
  // 22,  0,  6, 16,
  22, 24, 30, 16,
};

TConfig _Config;

TUSBItem_State _State;


//
// - Timing einer FBAS-Zeile (https://de.wikipedia.org/wiki/Fernsehsignal)
//    0.000us Zeile - Start
//    0.000us   Synchronimpuls - Start
//    4.700us   Synchronimpuls - Ende
//    4.700us   hintere Schwarzschulter - Start
//    5.800us     Burst - Start
//    8.050us     Burst - Ende
//   10.500us   hintere Schwarzschulter - Ende
//   10.500us   Bild - Start
//   62.500us   Bild - Ende
//   62.500us   vordere Schwarzschulter - Start
//   64.000us   vordere Schwarzschulter - Ende
//   64.000us Zeile - Ende
//


#define us_to_clk(us) (uint8_t)(uint32_t)((us)*_AdcMHz)
#define pix_to_clk(pix) (uint8_t)(uint32_t)(us_to_clk(10.500)+(pix))

// Array für die Samplezeitpunkte
uint8_t ArrArr[] =
{
  us_to_clk( 0.000), // #00 fallende Flanke vom horizontalen Synchronimpuls
  us_to_clk( 1.175), // #01 HSync-Testpunkt
  us_to_clk( 2.350), // #02 Synchronimpuls - Ende bei kurzem HSync (nur während VSync)
  us_to_clk( 3.525), // #03 VSync (Unterscheidung vom kurzen- / langen HSync)
  // us_to_clk( 4.100), // #04 ExtraSample - mit Musterbildung (warum:?)
  pix_to_clk( -166), // #04 ExtraSample - sonst leichte Musterbildung (warum:?)
  pix_to_clk( -147), // #05 Burst Messpunkt#1 = BurstMitte-2.333*PAL-Perioden = 6.944us-1*0.528us
  pix_to_clk( -128), // #06 Burst Messpunkt#2 = BurstMitte+0.000*PAL-Perioden = 6.944us+0*0.528us
  pix_to_clk( -109), // #07 Burst Messpunkt#3 = BurstMitte+2.333*PAL-Perioden = 6.944us+1*0.528us
  pix_to_clk(  -64), // #08 Bildverschiebung Startwert (Wert wird für jede Zeile gepatcht)
  pix_to_clk( 0*32), // #09 10.500µs Bildpixel #0 (Bildbereich 10.500µs bis 62.500µs)
  pix_to_clk( 1*32), // #10 11.389µs Bildpixel #32
  pix_to_clk( 2*32), // #11 12.278µs Bildpixel #64
  pix_to_clk( 3*32), // #12 ...
  pix_to_clk( 4*32), //
  pix_to_clk( 5*32), //
  pix_to_clk( 6*32), //
  pix_to_clk( 7*32), //
  pix_to_clk( 8*32), //
  pix_to_clk( 9*32), //
  pix_to_clk(10*32), // #18 19.389µs Bildpixel #320
  pix_to_clk(11*32), //
  pix_to_clk(12*32), //
  pix_to_clk(13*32), //
  pix_to_clk(14*32), //
  pix_to_clk(15*32), //
  pix_to_clk(16*32), //
  pix_to_clk(17*32), //
  pix_to_clk(18*32), //
  pix_to_clk(19*32), //
  pix_to_clk(20*32), // #28 28.278µs Bildpixel #640
  pix_to_clk(21*32), //
  pix_to_clk(22*32), //
  pix_to_clk(23*32), //
  pix_to_clk(24*32), //
  pix_to_clk(25*32), //
  pix_to_clk(26*32), //
  pix_to_clk(27*32), //
  pix_to_clk(28*32), //
  pix_to_clk(29*32), //
  pix_to_clk(30*32), // #38 37.167µs Bildpixel #960
  pix_to_clk(31*32), //
  pix_to_clk(32*32), //
  pix_to_clk(33*32), //
  pix_to_clk(34*32), //
  pix_to_clk(35*32), //
  pix_to_clk(36*32), //
  pix_to_clk(37*32), //
  pix_to_clk(38*32), //
  pix_to_clk(39*32), //
  pix_to_clk(40*32), // #38 46.056µs Bildpixel #1280
  pix_to_clk(41*32), //
  pix_to_clk(42*32), //
  pix_to_clk(43*32), //
  pix_to_clk(44*32), //
  pix_to_clk(45*32), //
  pix_to_clk(46*32), //
  pix_to_clk(47*32), //
  pix_to_clk(48*32), //
  pix_to_clk(49*32), //
  pix_to_clk(50*32), // #58 54.944µs Bildpixel #1600
  pix_to_clk(51*32), //
  pix_to_clk(52*32), //
  pix_to_clk(53*32), //
  pix_to_clk(54*32), //
  pix_to_clk(55*32), //
  pix_to_clk(56*32), //
  pix_to_clk(57*32), //
  pix_to_clk(58*32), // #64 62.056µs Bildpixel #1856 (Wert wird für jede Zeile gepatcht)
  us_to_clk(64.000), // Zeilenende
};

enum
{
  AdcLineCnt = 3,
  AdcSamplesPerRow = sizeof(ArrArr)-1
};

// Offset bestimmter ADC-Werte in AdcArr[] (entsprechend zu "ArrArr[]")
enum TAdcArrConst
{
  AdcVal_HSyncEdge  =  0,
  AdcVal_HSyncTest  =  1,
  AdcVal_VSync      =  3,
  AdcVal_Burst1     =  5,
  AdcVal_Burst2     =  6,
  AdcVal_Burst3     =  7,
  AdcVal_ImageFirst =  9,
  AdcVal_ImageLast  = AdcSamplesPerRow-1,
};

// Offset bestimmter Timing-Werte in ArrArr[]
enum TArrArrConst
{
  ArrVal_SubStart = 8,
  ArrVal_SubStop  = AdcSamplesPerRow-1,
};

typedef struct
{
  int16_t Adc1;
  int16_t Adc2;
} TAdcItem;

TAdcItem AdcArr[AdcLineCnt*AdcSamplesPerRow];

uint8_t  SndArr[SndCnt];

volatile uint32_t SndPos0 = 0;
volatile uint32_t SndPos1 = 0;

int32_t DbgArr[128];
int32_t DbgOfs = 0;


//################################################### EP3_IN_Callback
void EP3_IN_Callback(void)
{
  #ifdef USE_DMAFILL
  {
    _State.kk0++;
    if(DMA1_Channel3->CNDTR!=0)
    {
      _State.kk1++;
      while(DMA1_Channel3->CNDTR!=0)
      {
      }
    }
  }
  #endif
  ToggleDTOG_RX(ENDP3); // Buffer 0 wird jetzt gesendet
  SetEPTxCount((ENDP3 & 0x7F), 64);
  SetEPTxValid(ENDP3);  // Buffer 1 wird jetzt gesendet
  int iBuf = (GetENDPOINT(ENDP3)&EP_DTOG_RX)!=0 ? 1 : 0; // ==0 -> Buffer0 definieren; sonst Buffer1
  int oBuf = iBuf!=0 ? GetEPDblBuf1Addr(EP3_IN&0x7F) : GetEPDblBuf0Addr(EP3_IN&0x7F);
  uint16_t* z = (uint16_t*)(PMAAddr+2*oBuf);

  int n = SndPos1-SndPos0;
  n &= SndCnt-1;

  if(n>=64)
      {
        uint16_t* q = (uint16_t*)(SndArr+(SndPos0&(SndCnt-1)));
        #ifdef USE_DMAFILL
        {
          DMA1_Channel3->CCR = 0;
          DMA1_Channel3->CNDTR = 32;
          DMA1_Channel3->CPAR  = (uint32_t)q;
          DMA1_Channel3->CMAR  = (uint32_t)z;
          DMA1_Channel3->CCR = (DMA_CCR1_MEM2MEM *1) |
                               (DMA_CCR1_PL_0    *0) |
                               (DMA_CCR1_MSIZE_0 *2) |
                               (DMA_CCR1_PSIZE_0 *1) |
                               (DMA_CCR1_MINC    *1) |
                               (DMA_CCR1_PINC    *1) |
                               (DMA_CCR1_CIRC    *0) |
                               (DMA_CCR1_DIR     *0) |
                               (DMA_CCR1_TEIE    *0) |
                               (DMA_CCR1_HTIE    *0) |
                               (DMA_CCR1_TCIE    *0) |
                               (DMA_CCR1_EN      *1);
        }
        #else
        {
          for(int i=0; i<32; i++)
          {
            z[2*i] = q[i];
          }
        }
        #endif
        SndPos0 += 64;
      }
    else
      {
        // leeren Block senden
        z[0*4+0] = 0x0000;
      }

  int d = (SndCnt+SndPos0)-SndPos1;
  if(d<SndBufMinVal)
  {
    SndPos0 = SndPos1&(~(64-1));
    _State.SndBufMinErrCnt++;
  }
}

//################################################### Data_Func123
uint8_t* Data_Func123(uint16_t Length)
{
  if(Length==0)
  {
    pInformation->Ctrl_Info.Usb_wLength = sizeof(_Config);
    return NULL;
  }
  return (uint8_t*)&_Config;
}

//################################################### MUSB_Data_Setup
RESULT MUSB_Data_Setup(uint8_t RequestNo)
{
  uint8_t *(*CopyRoutine)(uint16_t);

  CopyRoutine = NULL;

  // usb_control_msg "USB_ENDPOINT_OUT, 123, 77, 88, Arr, sizeof(Arr)"
  if(RequestNo==123)
  {
    if(Type_Recipient==(CLASS_REQUEST | INTERFACE_RECIPIENT))
    {
      if(pInformation->USBwValue0==77 && pInformation->USBwIndex0==88)
      {
        EP3_IN_Callback(); // Init BULK-Endpoint
        CopyRoutine = Data_Func123;
      }
    }
  }

  if(CopyRoutine==NULL)
  {
    return USB_UNSUPPORT;
  }

  pInformation->Ctrl_Info.CopyData = CopyRoutine;
  pInformation->Ctrl_Info.Usb_wOffset = 0;
  (*CopyRoutine)(0);
  return USB_SUCCESS;
}

//################################################### Sample_Init
void Sample_Init()
{
  RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;

  // Reset durchführen
  {
    RCC->APB2RSTR |= RCC_APB2RSTR_TIM1RST;
    RCC->APB2RSTR &= ~RCC_APB2RSTR_TIM1RST;
    RCC->APB2RSTR |= RCC_APB2RSTR_ADC1RST;
    RCC->APB2RSTR &= ~RCC_APB2RSTR_ADC1RST;
    RCC->APB2RSTR |= RCC_APB2RSTR_ADC2RST;
    RCC->APB2RSTR &= ~RCC_APB2RSTR_ADC2RST;
    DMA1_Channel1->CCR = 0;
    DMA1_Channel6->CCR = 0;
  }

  // Init TIM1
  {
    RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;
    // Trigger ADC1
    TIM1->PSC = 2-1;
    TIM1->EGR = TIM_EGR_UG;
    TIM1->CCR1 = 18; // Sample-Zeitpunkt für ADC1
    TIM1->CCER |= TIM_CCER_CC1E;
    TIM1->CCMR1 |= TIM_CCMR1_OC1M_0*7;
    // Trigger ADC2
    // Der ADC2 hat kein eigenen DMA-Transfer. Stattdessen wird er
    // beim  DMA-Transfer vom ADC1 nebenbei mit übertragen. Der
    // TIM1->CCR2-Wert sollte sinnvollerweise deshalb vor dem
    // TIM1->CCR2-Wert liegen.
    TIM1->CCR2 = TIM1->CCR1-2; // Sample-Zeitpunkt für ADC2 (kleiner TIM1->CCR1)
    TIM1->CCER |= TIM_CCER_CC2E;
    TIM1->CCMR1 |= TIM_CCMR1_OC2M_0*7;
    // DMA1_Channel6-Trigger zum Patchen von TIM1->ARR
    TIM1->CCR3 = 1;
    TIM1->DIER |= TIM_DIER_CC3DE;
    // Enable Output
    TIM1->BDTR |= TIM_BDTR_MOE;
  }

  // Init Periodendauer der ADC-Sample
  {
    int32_t Sum = 0;
    for(int i=0; i<AdcSamplesPerRow; i++)
    {
      int dn = (uint8_t)(ArrArr[i+1]-ArrArr[i]);
      if(dn<16) _State.Error=_ErrArrArr1; // die Mindesttaktzahl für den ADC
      if(dn<TIM1->CCR1) _State.Error=_ErrArrArr2;
      ArrArr[i] = dn-1; // "-1" da ein "TIM->ARR"-Wert
      Sum += dn;
    }
    if(Sum!=64*_AdcMHz) _State.Error=_ErrArrArr3; // FBAS-Zeile hat 64µs
  }

  // Init ADC 36MHz-Takt
  // 42ns Sample&Hold (=1.5Takte*1*2/72MHz)
  {
    RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
    RCC->APB2ENR |= RCC_APB2ENR_ADC2EN;
    RCC->CFGR   |= RCC_CFGR_ADCPRE_0*0; // 0 -> PCLK2/2 -> fADC=36 MHz (=72MHz/2)
    for(int iADC=0; iADC<2; iADC++)
    {
      ADC_TypeDef* CurAdc = iADC==0 ? ADC1 : ADC2;
      {
        // Calibrate ADC
        CurAdc->CR2   |= ADC_CR2_ADON;        // ADC ON (0->1)
        CurAdc->CR2   |= ADC_CR2_ADON;        // start (single) conversion (1->1)
        while((CurAdc->SR&ADC_SR_EOC)==0);    // wait for the end of conversion
        CurAdc->CR2   |= ADC_CR2_RSTCAL;      // reset calibration
        while((CurAdc->CR2&ADC_CR2_RSTCAL));  // wait for reset
        CurAdc->CR2   |= ADC_CR2_CAL;         // start calibration
        while((CurAdc->CR2&ADC_CR2_CAL)!=0);  // wait till calibration is done
        CurAdc->CR2   &= ~ADC_CR2_ADON;       // ADC OFF
      }
      CurAdc->CR2   |= ADC_CR2_DMA;         // DMA Enabled
      CurAdc->CR2   |= ADC_CR2_EXTTRIG;     // External trigger
      ADC2->CR2   |= ADC_CR2_EXTSEL_0*iADC; // 0 -> Trigger TIM1_CC1; 1 -> Trigger TIM1_CC2
      CurAdc->SQR1  |= ADC_SQR1_L_0*(1-1);  // 0 -> 1 Kanal
      CurAdc->SQR3  |= ADC_SQR3_SQ1_0*7;    // 7 -> PA7
      CurAdc->SMPR2 |= ADC_SMPR2_SMP7_0*0;  // 0 -> 14 Takte (T0+Tx=12.5+1.5) für PA7
    }
    ADC1->CR2 |= ADC_CR2_ADON;  // ADC1 ein
  }

  // Init DMA_CH1 zum Sampeln der ADC-Daten
  {
    RCC->AHBENR |= RCC_AHBENR_DMA1EN;
    DMA1_Channel1->CPAR  = (uint32_t)&ADC1->DR;
    DMA1_Channel1->CMAR  = (uint32_t)AdcArr;
    DMA1_Channel1->CNDTR = AdcLineCnt*AdcSamplesPerRow;
    DMA1_Channel1->CCR   = (DMA_CCR1_MEM2MEM *0) |
                           (DMA_CCR1_PL_0    *1) | // höhere Priorität als DMA1_Channel3!
                           (DMA_CCR1_MSIZE_0 *2) |
                           (DMA_CCR1_PSIZE_0 *2) |
                           (DMA_CCR1_MINC    *1) |
                           (DMA_CCR1_PINC    *0) |
                           (DMA_CCR1_CIRC    *1) |
                           (DMA_CCR1_DIR     *0) |
                           (DMA_CCR1_TEIE    *0) |
                           (DMA_CCR1_HTIE    *0) |
                           (DMA_CCR1_TCIE    *0) |
                           (DMA_CCR1_EN      *1);
  }

  // Init DMA_CH5 für "TIM1->ARR"-Patch
  {
    RCC->AHBENR |= RCC_AHBENR_DMA1EN;
    DMA1_Channel6->CPAR  = (uint32_t)&TIM1->ARR;
    DMA1_Channel6->CMAR  = (uint32_t)ArrArr;
    DMA1_Channel6->CNDTR = 1*AdcSamplesPerRow;;
    DMA1_Channel6->CCR   = (DMA_CCR1_MEM2MEM *0) |
                           (DMA_CCR1_PL_0    *1) | // höhere Priorität als DMA1_Channel3!
                           (DMA_CCR1_MSIZE_0 *0) |
                           (DMA_CCR1_PSIZE_0 *1) |
                           (DMA_CCR1_MINC    *1) |
                           (DMA_CCR1_PINC    *0) |
                           (DMA_CCR1_CIRC    *1) |
                           (DMA_CCR1_DIR     *1) |
                           (DMA_CCR1_TEIE    *0) |
                           (DMA_CCR1_HTIE    *0) |
                           (DMA_CCR1_TCIE    *0) |
                           (DMA_CCR1_EN      *1);
  }

  // Start!
  TIM1->CR1 |= TIM_CR1_CEN;
}

//################################################### MinMax_Single
uint8_t OPT_FAST MinMax_Single(TAdcItem* q, int n)
{
  uint16_t Min = 4095;
  uint16_t Max =    0;
  for(int i=0; i<n; i++)
  {
    uint16_t v = q[i].Adc1;
    if(v<Min) Min=v;
    if(v>Max) Max=v;
  }
  Min = (Min+  0)>>8;
  Max = (Max+255)>>8;
  return Min|((Max-Min)<<4);
}

//################################################### MinMax_Dual
uint8_t OPT_FAST MinMax_Dual(TAdcItem* q, int n, int iLine)
{
  static uint16_t PrevMin = 0;
  static uint16_t PrevMax = 0;
  uint16_t nNow = ((n+3)/4)*2;
  if((iLine&1)!=0)
  {
    // ein MinMax über eine Zeile auf zwei Zeilen verteilen (sonst zu langsam)
    q = q+n-nNow;
  }
  uint16_t Min = 4095;
  uint16_t Max =    0;
  for(int i=0; i<nNow; i+=2)
  {
    // Code etwas entrollen (sonst zu langsam)
    uint16_t v0;
    v0 = q[i+0].Adc1;
    if(v0<Min) Min=v0;
    if(v0>Max) Max=v0;
    v0 = q[i+0].Adc2;
    if(v0<Min) Min=v0;
    if(v0>Max) Max=v0;
    v0 = q[i+1].Adc1;
    if(v0<Min) Min=v0;
    if(v0>Max) Max=v0;
    v0 = q[i+1].Adc2;
    if(v0<Min) Min=v0;
    if(v0>Max) Max=v0;
  }
  uint16_t TmpMin = Min<PrevMin ? Min : PrevMin;
  uint16_t TmpMax = Max>PrevMax ? Max : PrevMax;
  PrevMin = Min;
  PrevMax = Max;
  Min = (TmpMin+  0)>>8;
  Max = (TmpMax+255)>>8;
  return Min|((Max-Min)<<4);
}

//################################################### Conv_12_to_8_Bit
void OPT_FAST Conv_12_to_8_Bit(uint8_t* z, TAdcItem* q, int n, int Bias, int Scale)
{
  for(int i=0; i<n; i++)
  {
    int v = q[i].Adc1;
    v = ((v-Bias)*Scale)>>16;
    if(v<  0) v=  0;
    if(v>255) v=255;
    v = v^i; // Daten verrauschen um "USB-Bit-Stuffing"-Wahrscheinlichkeit zu mindern
    z[i] = v;
  }
}

//################################################### Conv_24_to_8_Bit
void OPT_FAST Conv_24_to_8_Bit(uint8_t* z, TAdcItem* q, int n, int Bias, int Scale)
{
  for(int i=0; i<n; i++)
  {
    int v0 = q[i].Adc1;
    v0 = ((v0-Bias)*Scale)>>16;
    if(v0< 0) v0= 0;
    if(v0>15) v0=15;
    int v1 = q[i].Adc2;
    v1 = ((v1-Bias)*Scale)>>16;
    if(v1< 0) v1= 0;
    if(v1>15) v1=15;
    uint8_t v = (v1<<4)|v0;
    v = v^i; // Daten verrauschen um "USB-Bit-Stuffing"-Wahrscheinlichkeit zu mindern
    z[i] = v;
  }
}

//################################################### CalcBurstPhi
uint16_t CalcBurstPhi(TAdcItem* Burst)
{
  // - die Phase vom Burst bezogen auf den Zeitpunkt vom mittleren Burst-ADC-Sample berechnen
  // - gibt dessen atan2()-Parameter in gepackter Form zurück (weniger USB-Datenvolumen)
  // - bei fehlendem BurstSignal wird 0xffff zurückgegeben
  int32_t Ofs = Burst[0].Adc1+Burst[1].Adc1+Burst[2].Adc1;
  int32_t Amp0 = 3*Burst[0].Adc1-Ofs;
  int32_t Amp1 = 3*Burst[1].Adc1-Ofs;
  int32_t Amp2 = 3*Burst[2].Adc1-Ofs;

  // Burst-Signal vorhanden?
  {
    // bool NoBurst = (2*sqrt((Amp0*Amp0+Amp1*Amp1+Amp2*Amp2)*2/3)/3*3300/4096) < (20%*Burst_mV);"
    int32_t AmpAmp = Amp0*Amp0+Amp1*Amp1+Amp2*Amp2;
    const int32_t Threshold = (int32_t)(3*exp(2*log((0.20*BurstAmp_mV*3/2*4096/3300)))/2);
    if(AmpAmp<Threshold) return 0xffff;
    _State.BurstAmp = AmpAmp;
  }

  int32_t c0 = Amp1*(int32_t)(65536*sqrt(3))/65536; // Amp1*sqrt(3);
  int32_t c1 = Amp2-Amp0;
  // Das Ergebnis ist "phi_burst = atan2(c0, c1)".
  // Nun c0 und c1 packen...
  uint16_t v = 0;
  if(c0<0)
  {
    c0 = -c0;
    v |= 0x8000;
  }
  if(c1<0)
  {
    c1 = -c1;
    v |= 0x4000;
  }
  if(c0>c1)
  {
    int32_t Tmp = c0;
    c0 = c1;
    c1 = Tmp;
    v |= 0x2000;
  }
  if(c1>0)
  {
    c0 = c0*8191/c1;
  }
  v |= c0;
  return v;
}

//################################################### Sample_Do
// OPT_DBG
void Sample_Do()
{
  // TIM1->CCR4 = 5; // Sample-Zeitpunkt für ADC1
  // TIM1->CCER |= TIM_CCER_CC4E;
  // TIM1->CCMR2 |= TIM_CCMR2_OC4M_0*7;
  // GPIOA->CRH = (GPIOA->CRH&0xfffffff0)|0x0000000b;

  int8_t  nSubCycles = 0;
  uint8_t ArrOrgVal_SubStart = ArrArr[ArrVal_SubStart];
  uint8_t ArrOrgVal_SubStop  = ArrArr[ArrVal_SubStop];
  int32_t PrevSyncLine = 0;
  int32_t iLine = 0;

  int32_t AdcLineOfs = 0;

  int32_t HSync_MinSum = 0;
  int32_t HSync_MinCnt = 0;

  int32_t RefBlankSum  = 0;
  int32_t RefBlankLine = 0;

  for(uint32_t iTotalLine=0; ; iTotalLine++)
  {
    // auf nächste Zeile warten
    {
      AdcLineOfs++;
      if(AdcLineOfs==AdcLineCnt) AdcLineOfs=0;
      bool Overrun = true;
      while(true)
      {
        int iADC = AdcLineCnt*AdcSamplesPerRow-DMA1_Channel1->CNDTR;
        iADC -= AdcLineOfs*AdcSamplesPerRow;
        if(iADC<0 || iADC>=AdcSamplesPerRow) break;
        Overrun = false;
      }
      if(Overrun) _State.ADC_OverrunCnt++;
    }
      DWT->CTRL = 0x40000001;
      DWT->CYCCNT = 0x00000000;

    TAdcItem* AdcRow = AdcArr+AdcLineOfs*AdcSamplesPerRow;

    TUSBItem_Line* USBItem = (TUSBItem_Line*)(SndArr+(SndPos1&(SndCnt-1)));

    USBItem->Flags = 1<<0; // gültiger Datenblock

    // vertikale Synchronisierung
    {
      // Eigentlich kann der Bildwechsel durch Messung der HSync-Impulslänge
      // erkannt werden. Dort ist Länge (laut Wikipedia) 2.35µs statt 4.7µs.
      // Beim VC20 sind kurze Impulse eher 3.4µs lang -> deshalb besser
      // Erkennung an anderer Stelle, der hinteren Schwarzschulter. Dort
      // muss aber erst der Burst entfernt werden.
      int RefBlank = + AdcRow[AdcVal_Burst1].Adc1
                     + AdcRow[AdcVal_Burst2].Adc1
                     + AdcRow[AdcVal_Burst3].Adc1; // Farbträgerfilter
      RefBlankSum += RefBlank;
      if(RefBlank<3*_State.HSync_Threshold)
      {

        // Während des Bildwechsel sind Synchronimpule statt 4.7µs
        // nur 2.35µs lang. Der erste kurze Impuls liegt abwechselnd
        // bei Zeile 1 oder bei Zeile 314.
        int dn = iTotalLine-PrevSyncLine;
        if(dn>50)
        {
          PrevSyncLine = iTotalLine;
          if(dn==312 || dn==313)
          {
            iLine = 0;
            _State.VSyncCnt++;
            if(dn==313) // Beginn vom zweiten Halbbild (bzw. Ende vom ersten Halbbild)
            {
              _State.VSyncCnt |= 1;
            }
            _State.RefBlank = (RefBlankSum-RefBlank)/(iTotalLine-RefBlankLine)/3;
          }
        }
        RefBlankSum = 0;
        RefBlankLine = iTotalLine;
      }
    }

    // Statuszeile (1. Zeile) "senden"
    if(iLine==0)
    {
      USBItem->Flags |= 1<<1; // VSync
      _State.Flags = USBItem->Flags;
      if(_Config.Init!=0)
      {
        _Config.Init = 0;
        if(_Config.dADC2==0)
          {
            ADC2->CR2 &= ~ADC_CR2_ADON;
          }
        else
          {
            ADC2->CR2 |= ADC_CR2_ADON;
            TIM1->CCR2 = TIM1->CCR1-_Config.dADC2;
          }
      }
      if(_Config.Mode==0)
      {
        // SubImageOfs für aufeinanderfolgende Halbbilder
        // 0  0  1  1
        // 0  0  1  1
        // 2  2  3  3
        // 2  2  3  3
        // 4  4  5  5
        // ...
        int v = _State.VSyncCnt;
        v = (v>>1);
        v = (((v>>2)<<1)+((v&1)<<0))&0x1f;
        _State.SubImageOfs = v;
      }
      if(_Config.Mode==1)
      {
        // 0  0 16 16
        // 0  0 16 16
        // 2  2 18 18
        // 2  2 18 18
        // 4  4 20 20
        // ...
        int v = _State.VSyncCnt;
        v = (v>>1);
        v = (((v>>2)<<1)+((v&1)<<4))&0x1f;
        _State.SubImageOfs = v;
      }
      if(_Config.Mode==2)
      {
        // 0  0  8  8
        // 0  0  8  8
        // 2  2 10 10
        // 2  2 10 10
        // 4  4 12 12
        // ...
        int v = _State.VSyncCnt;
        v = (v>>1);
        v = (((v>>2)<<1)+((v&1)<<3))&0x1f;
        _State.SubImageOfs = v;
      }
      if(_Config.Mode==3)
      {
        _State.SubImageOfs = ScanArr_Mode3[_State.VSyncCnt%sizeof(ScanArr_Mode3)];
      }
      if(_Config.Mode==4)
      {
        _State.SubImageOfs = ScanArr_Mode4[_State.VSyncCnt%sizeof(ScanArr_Mode4)];
      }
      if(_Config.Mode==5)
      {
        _State.SubImageOfs = ScanArr_Mode5[_State.VSyncCnt%sizeof(ScanArr_Mode5)];
      }
      if(_Config.Mode==6)
      {
        _State.SubImageOfs = ScanArr_Mode6[_State.VSyncCnt%sizeof(ScanArr_Mode6)];
      }
      for(int i=0; i<(sizeof(_State)+3)/4; i++)
      {
        ((uint32_t*)USBItem)[i] = ((uint32_t*)&_State)[i];
      }
      // *(TUSBItem_State*)USBItem = _State;
    }

    // normale Bildzeile "senden"
    if(iLine!=0)
    {
      // ADC-Werte zum USB-Sendepuffer übertragen
      USBItem->BurstPhi = CalcBurstPhi(AdcRow+AdcVal_Burst1);
      USBItem->MinMax = 0x00;
      int Bias  = _Config.ImageBias;
      int Scale = _Config.ImageScale;
      TAdcItem* SendAdcRow = AdcRow;
      if(Scale==0) // "AutoMode?"
      {
        // optimale Verstärkung für die aktuelle Zeile bestimmen
        if(_Config.dADC2!=0)
        {
          USBItem->MinMax = MinMax_Dual(SendAdcRow+AdcVal_ImageFirst+1,
                                        AdcVal_ImageLast-AdcVal_ImageFirst+1-2,
                                        iLine);
          Bias  = (USBItem->MinMax&0xf)<<8;
          Scale = (65536/16)/(USBItem->MinMax>>4);
        }
        else
        {
          USBItem->MinMax = MinMax_Single(SendAdcRow+AdcVal_ImageFirst+1,
                                          AdcVal_ImageLast-AdcVal_ImageFirst+1-2);
          Bias  = (USBItem->MinMax&0xf)<<8;
          Scale = 65536/(USBItem->MinMax>>4);
        }
      }
      if(_Config.dADC2!=0)
      {
        Conv_24_to_8_Bit(USBItem->Image,
                         SendAdcRow+AdcVal_ImageFirst,
                         AdcVal_ImageLast-AdcVal_ImageFirst+1,
                         Bias,
                         Scale);
      }
      else
      {
        Conv_12_to_8_Bit(USBItem->Image,
                         SendAdcRow+AdcVal_ImageFirst,
                         AdcVal_ImageLast-AdcVal_ImageFirst+1,
                         Bias,
                         Scale);
      }
    }

    // HSync-Schwellwert bestimmen
    {
      // Es reichen zwei um 25% verschobene Testpunkte um den Minimalpegel ohne Wissem
      // von der Position vom HSync-Impuls sicher zu finden.
      if(AdcRow[0*AdcSamplesPerRow/4].Adc1<HSync_MinSum) HSync_MinSum=AdcRow[0*AdcSamplesPerRow/4].Adc1;
      if(AdcRow[1*AdcSamplesPerRow/4].Adc1<HSync_MinSum) HSync_MinSum=AdcRow[1*AdcSamplesPerRow/4].Adc1;
      HSync_MinCnt++;
      if(HSync_MinCnt==313)
      {
        _State.HSync_Threshold = HSync_MinSum+(HSyncAmp_mV*4096/3300/2);
        _State.HSync_Min = HSync_MinSum;
        HSync_MinSum = 4096;
        HSync_MinCnt = 0;
      }
    }

    // horizontale Synchronisierung
    {
      // warten bis ADC-Wandler hinter ImgOfs sampelt
      // (dadurch kann ein HSync-ADC-Wert ohne Verzögerung bereits bei
      // der nächsten Zeile berüchsichtigt werden.)
      int CurAdcLineOfs = AdcLineOfs+1;
      if(CurAdcLineOfs==AdcLineCnt) CurAdcLineOfs=0;
      while(true)
      {
        int iADC = AdcLineCnt*AdcSamplesPerRow-DMA1_Channel1->CNDTR;
        iADC -= CurAdcLineOfs*AdcSamplesPerRow;
        if(iADC>ArrVal_SubStart) break;
      }
      TAdcItem* CurAdcRow = AdcArr+CurAdcLineOfs*AdcSamplesPerRow;
      // alle 20% bis 80% der Zeilen einen Korrekturwert vom 2 Takte einfügen
      int32_t dnHSync = (CurAdcRow[AdcVal_HSyncEdge].Adc1)>_State.HSync_Threshold ? +1 : -1;
      USBItem->Flags |= (dnHSync>0 ? 1 : 0)<<2; // Extratakt
      // eine Grundabweichung hinzufügen
      // - damit der Wert praktisch konstant (über Stunden) bleibt,
      //   eine Min-/Max-Toleranz  erlauben
      int32_t dn = _State.HSync_dL16;
      int32_t MinOfs = (dn+0xa000)>>16;
      int32_t MaxOfs = (dn+0xc000)>>16;
      if(_State.HSync_Raw<MinOfs) _State.HSync_Raw=MinOfs;
      if(_State.HSync_Raw>MaxOfs) _State.HSync_Raw=MaxOfs;
      dnHSync += _State.HSync_Raw;
      // bei Verlust der Synchronisation grob regeln (+-5 Takte)
      // (die Synchronisation ist dann verloren, wenn mehr als 10% der Werte
      // nicht passen)
      static int32_t nErr = 0;
      if(nErr>0) nErr--;
      if(CurAdcRow[AdcVal_VSync].Adc1>_State.HSync_Threshold)
      {
        if(nErr<200) nErr+=10;
        if(nErr>100)
        {
          dnHSync = CurAdcRow[AdcVal_HSyncEdge].Adc1>_State.HSync_Threshold ? +5 : -5;
          _State.HSync_ErrCnt++;
        }
      }
      dn += (dnHSync*65536-dn)/(15625/10); // Tau = 1/10s
      if(dn<-4*0x10000) dn=-4*0x10000; // 4 -> 1700 PPM erlaubte Abweichung (=4/_AdcMHz*15625))
      if(dn>+4*0x10000) dn=+4*0x10000;
      _State.HSync_dL16 = dn;
      ArrArr[ArrVal_SubStop] = + ArrOrgVal_SubStop
                               - nSubCycles // Bildverschiebung wieder aufheben
                               + dnHSync;
    }

    // Bildverschiebung für die nächste Zeile initialisieren
    // (0..15 Pixel  bzw. 0..15 Viertel-Farbträgerperioden)
    nSubCycles = _Config.ImageOfs+_State.SubImageOfs;
    if((USBItem->Flags&(1<<2))==0) nSubCycles++;
    ArrArr[ArrVal_SubStart] = ArrOrgVal_SubStart+nSubCycles;

    iLine++;
    if(iLine==320) iLine=0; // (tritt nur bei Sync-Verlust ein)

    // neue Daten für EP3_IN_Callback() sichtbar machen
    SndPos1 += 64;

      DbgArr[DbgOfs++&0x7f] = DWT->CYCCNT/72;
  }

}

//################################################### main
int main(void)
{
  // Timer usw. beim Debug-Stop anhalten
  DBGMCU->CR = 0xffffffff;

  // Dbg: USB-Reset beim Host (-> zuverlässige Geräteerkennung auch beim Debuggen)
  if(1==2)
  {
    // USB-Plus Leitung kurz auf Low -> Reset (Pfusch!)
    // - für einen USB-Reset müsste sonst VCC
    //   kurzzeitig auf 0V liegen
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
    GPIOA->CRH = (GPIOA->CRH&0xfff0ffff)|0x00060000; // 2MHz OPENDRAIN
    RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;
    TIM1->CR1 = TIM_CR1_CEN;
    while(TIM1->CNT<48*100) // 100µs
    {
    }
    GPIOA->CRH = (GPIOA->CRH&0xfff0ffff)|0x00040000; // default
  }

  Sample_Init();

  Set_System();

  USB_Interrupts_Config();

  Set_USBClock();

  USB_Init();

  Sample_Do();

  return 0;
}
