/* * ============================================================================== * ЧЕСТНЫЙ 1-КАНАЛЬНЫЙ ОСЦИЛЛОГРАФ НА СВЕРХСКОРОСТНОМ АЦП + DMA (СТРОГО 2 MSPS) * ИСПРАВЛЕНО: Полный скетч с жестким сбросом времени выборки АЦП до 1.5 циклов * Плата: STM32F103 (Blue Pill), Экран: 1.3" SH1106 (SCL=PB6, SDA=PB7) * Распиновка органов: CLK=PB5, DT=PB8, SW=PB9, CONFIRM=PB3, BACK=PB4 * ============================================================================== */ #include #include #include // РОДНОЙ КОНСТРУКТОР ДЛЯ ТВОЕГО ЭКРАНА 1.3" НА ПЛАТЕ С ЭНКОДЕРОМ U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, 0x3C); // Твоя оригинальная карта пинов #define PIN_CONFIRM PB3 #define PIN_BACK PB4 #define ENC_CLK PB5 #define ENC_DT PB8 #define ENC_SW PB9 // Кнопка HOLD (Заморозка) #define PIN_ADC_IN PA0 // Вход замера (Для 2 MSPS аппаратно необходим PA0!) // Размер буфера увеличен в 2 раза для работы триггера #define BUFFER_SIZE 256 #define DISPLAY_SIZE 128 alignas(4) volatile uint16_t adc_buffer[BUFFER_SIZE]; // Аппаратный буфер DMA uint8_t scope_buffer[DISPLAY_SIZE]; // Буфер экрана (128 точек) volatile bool dma_transfer_done = false; bool isHoldActive = false; // Таймбаза: такты для TIM3 (ARR значения для частоты 72 МГц шины таймера) // Индексы: 0.5us, 1.0us, 2.0us, 5.0us, 10us, 20us, 50us, 100us на одну точку const uint16_t timebase_presets[] = {36, 72, 144, 360, 720, 1440, 3600, 7200}; const char* const timebase_names[] = {"0.5us", "1.0us", "2.0us", "5.0us", "10us", "20us", "50us", "100us"}; int timebase_idx = 0; // По умолчанию максимальный разгон (2 MSPS) int lastClkState; unsigned long last_enc_scan = 0; // Прерывание окончания передачи DMA1 Channel 1 extern "C" void DMA1_Channel1_IRQHandler(void) { if (DMA1->ISR & DMA_ISR_TCIF1) { DMA1->IFCR = DMA_IFCR_CTCIF1; dma_transfer_done = true; } } // УЛЬТИМАТИВНАЯ НАСТРОЙКА АЦП НА НАСТОЯЩИЕ 2 MSPS void setup_ultra_adc() { // 1. Включаем тактирование АЦП, DMA1 и таймера TIM3 RCC->AHBENR |= RCC_AHBENR_DMA1EN; RCC->APB2ENR |= RCC_APB2ENR_ADC1EN | RCC_APB2ENR_ADC2EN | RCC_APB2ENR_IOPAEN; RCC->APB1ENR |= RCC_APB1ENR_TIM3EN; pinMode(PIN_ADC_IN, INPUT_ANALOG); // 2. Настройка таймера TIM3 (Аппаратный пинатель АЦП) TIM3->PSC = 0; TIM3->ARR = timebase_presets[timebase_idx]; TIM3->CR2 |= TIM_CR2_MMS_1; // Выход TRGO по Update // 3. Настройка DMA1 Канал 1 DMA1_Channel1->CPAR = (uint32_t)&(ADC1->DR); DMA1_Channel1->CMAR = (uint32_t)adc_buffer; DMA1_Channel1->CNDTR = BUFFER_SIZE; // 16 бит, инкремент памяти, прерывание по окончанию (TCIE) DMA1_Channel1->CCR = DMA_CCR_MSIZE_0 | DMA_CCR_PSIZE_0 | DMA_CCR_MINC | DMA_CCR_TCIE; NVIC_EnableIRQ(DMA1_Channel1_IRQn); NVIC_SetPriority(DMA1_Channel1_IRQn, 0); // 4. Настройка спаренного режима работы АЦП (Dual Interleaved) ADC1->CR1 = (7 << ADC_CR1_DUALMOD_Pos); ADC2->CR1 = (7 << ADC_CR1_DUALMOD_Pos); // ЖЕСТКИЙ СБРОС НАСТРОЕК АРДУИНЫ: Выставляем время выборки в 1.5 цикла (код 000) // Очищаем биты SMP0 (Канал 0 / PA0) в регистрах выборки обоих АЦП ADC1->SMPR2 &= ~(ADC_SMPR2_SMP0); ADC2->SMPR2 &= ~(ADC_SMPR2_SMP0); // Привязываем запуск АЦП1 к триггеру TIM3 TRGO и разрешаем DMA режим ADC1->CR2 &= ~ADC_CR2_EXTSEL; ADC1->CR2 |= ADC_CR2_EXTTRIG | (4 << ADC_CR2_EXTSEL_Pos) | ADC_CR2_DMA; ADC1->SQR3 = 0; // Канал 0 (PA0) ADC2->SQR3 = 0; // Канал 0 (PA0) // Включение питания АЦП и принудительная калибровка мостов ADC1->CR2 |= ADC_CR2_ADON; ADC2->CR2 |= ADC_CR2_ADON; delay(1); ADC1->CR2 |= ADC_CR2_CAL; ADC2->CR2 |= ADC_CR2_CAL; delay(2); // Поехали: запускаем поток DMA и включаем таймер тактов DMA1_Channel1->CCR |= DMA_CCR_EN; TIM3->CR1 |= TIM_CR1_CEN; } void restart_dma() { DMA1_Channel1->CCR &= ~DMA_CCR_EN; DMA1_Channel1->CNDTR = BUFFER_SIZE; dma_transfer_done = false; DMA1_Channel1->CCR |= DMA_CCR_EN; } void setup() { // Отключение JTAG для освобождения пинов кнопок PB3 и PB4 RCC->APB2ENR |= RCC_APB2ENR_AFIOEN; AFIO->MAPR = (AFIO->MAPR & ~AFIO_MAPR_SWJ_CFG) | AFIO_MAPR_SWJ_CFG_JTAGDISABLE; // Инициализация твоих пинов управления pinMode(PIN_CONFIRM, INPUT_PULLUP); pinMode(PIN_BACK, INPUT_PULLUP); pinMode(ENC_CLK, INPUT_PULLUP); pinMode(ENC_DT, INPUT_PULLUP); pinMode(ENC_SW, INPUT_PULLUP); // Настройка твоего экрана SH1106 u8g2.setI2CAddress(0x3C * 2); u8g2.begin(); u8g2.setFont(u8g2_font_6x10_tf); setup_ultra_adc(); lastClkState = digitalRead(ENC_CLK); } void loop() { uint32_t ct = millis(); // Опрос энкодера для изменения времени развертки if (ct - last_enc_scan >= 1) { last_enc_scan = ct; int currentClkState = digitalRead(ENC_CLK); if (currentClkState != lastClkState && currentClkState == LOW) { if (digitalRead(ENC_DT) != currentClkState) { if (timebase_idx < 7) timebase_idx++; } else { if (timebase_idx > 0) timebase_idx--; } TIM3->CR1 &= ~TIM_CR1_CEN; TIM3->ARR = timebase_presets[timebase_idx]; TIM3->CR1 |= TIM_CR1_CEN; restart_dma(); } lastClkState = currentClkState; } // Опрос кнопки HOLD (Заморозка кадра на PB9) static int last_sw_state = HIGH; int current_sw_state = digitalRead(ENC_SW); if (last_sw_state == HIGH && current_sw_state == LOW) { isHoldActive = !isHoldActive; delay(150); } last_sw_state = current_sw_state; // Обработка данных АЦП, триггер и вывод графики if (dma_transfer_done && !isHoldActive) { // Алгоритм программного ТРИГГЕРА по восходящему фронту (уровень 32 — центр экрана) int trigger_level = 32; int trigger_offset = 0; // Сканируем буфер в поисках пересечения средней линии снизу вверх for (int i = 1; i < (BUFFER_SIZE - DISPLAY_SIZE); i++) { int val_prev = (adc_buffer[i-1] & 0x0FFF) >> 6; int val_curr = (adc_buffer[i] & 0x0FFF) >> 6; if (val_prev < trigger_level && val_curr >= trigger_level) { trigger_offset = i; // Нашли точку синхронизации кадра break; } } // Заполняем буфер экрана со смещением от найденной триггерной точки for (int i = 0; i < DISPLAY_SIZE; i++) { uint16_t sample = adc_buffer[i + trigger_offset] & 0x0FFF; scope_buffer[i] = sample >> 6; // Масштабируем 12-битное АЦП под 64 пикселя высоты } restart_dma(); // Немедленный перезапуск аппаратного цикла сбора данных АЦП // Отрисовка графического интерфейса u8g2.clearBuffer(); // Рисуем сетку for(int i = 0; i < 128; i += 16) { for(int j = 0; j < 64; j += 8) u8g2.drawPixel(i, j); } // Рисуем непрерывную линию луча сигнала for (int i = 0; i < DISPLAY_SIZE - 1; i++) { u8g2.drawLine(i, 63 - scope_buffer[i], i+1, 63 - scope_buffer[i+1]); } // Текстовый статус-бар u8g2.setCursor(2, 10); u8g2.print("TRUE 2MSPS | T: "); u8g2.print(timebase_names[timebase_idx]); u8g2.sendBuffer(); } else if (isHoldActive) { // В режиме HOLD просто циклически перерисовываем старый буфер кадра u8g2.clearBuffer(); for(int i = 0; i < 128; i += 16) { for(int j = 0; j < 64; j += 8) u8g2.drawPixel(i, j); } for (int i = 0; i < DISPLAY_SIZE - 1; i++) { u8g2.drawLine(i, 63 - scope_buffer[i], i+1, 63 - scope_buffer[i+1]); } u8g2.setCursor(2, 10); u8g2.print("[ HOLD ACTIVE ]"); u8g2.sendBuffer(); delay(50); } }