个人分享
登录

12m晶振 串口有输出

71
  • admin
    管理员
    /*
     * STM32F407VGT6 - Arduino (STM32duino) 三口每秒输出 "st"
     * ------------------------------------------------------------
     * CH340   : Serial1  (PA9/PA10)        115200
     * 外接串口 : Serial2  (PA2/PA3)        115200  (需自行实例化)
     * 485     : 软件串口  PB6(TX)+PB5(EN)  9600  (只发不收)
     *
     * 说明: PA9/PA10 与 PB6/PB7 都是 USART1(AF7),不能并存,
     *       所以 485 用软件串口 bit-bang 在 PB6 上发送,PB5 控制方向。
     * 注意: 本核心是 ST 官方 STM32 核心,串口类是 Uart (非抽象基类 HardwareSerial)。
     *       Serial1 由变体 HAVE_HWSERIAL1 提供,但 HAVE_HWSERIAL2 未定义,
     *       核心不提供 Serial2 对象,必须自行定义;构造函数会按 USART2 自动取
     *       PA2(TX)/PA3(RX)。
     */
    
    // 本变体未定义 HAVE_HWSERIAL2,核心没提供 Serial2,需自行定义
    Uart Serial2(USART2);   // 外接串口 PA2(TX)/PA3(RX)
    
    /* ============================================================
     * 12MHz 外接晶振专用时钟配置
     * 核心 F407 变体默认 PLL_M=8 (按 8MHz 设计),12MHz 下 VCO=504MHz 超上限,
     * 必须覆盖 WEAK 的 SystemClock_Config,改 PLL_M=12 -> 168MHz。
     * 注意: 本函数仅修正 PLL 倍频;HSE_VALUE=12000000 仍需在编译参数里设置
     *       (boards.local.txt 加 build.extra_flags=-DHSE_VALUE=12000000),
     *       否则 HAL 算出的 SystemCoreClock/波特率仍按默认 25MHz 算,会偏。
     * ============================================================ */
    extern "C" void SystemClock_Config(void)
    {
      RCC_OscInitTypeDef RCC_OscInitStruct = {};
      RCC_ClkInitTypeDef RCC_ClkInitStruct = {};
    
      __HAL_RCC_PWR_CLK_ENABLE();
      __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
    
      RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
      RCC_OscInitStruct.HSEState = RCC_HSE_ON;
      RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
      RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
      RCC_OscInitStruct.PLL.PLLM = 12;             // 12MHz / 12 = 1MHz VCO 输入
      RCC_OscInitStruct.PLL.PLLN = 336;            // VCO = 336MHz
      RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;   // SYSCLK = 168MHz
      RCC_OscInitStruct.PLL.PLLQ = 7;              // 48MHz (USB)
      if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
        Error_Handler();
      }
    
      RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK |
                                     RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2);
      RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
      RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
      RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;  // PCLK1 = 42MHz
      RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;  // PCLK2 = 84MHz
      if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK) {
        Error_Handler();
      }
    
      /* 关键: 若编译期未正确设置 HSE_VALUE=12000000,HAL 算出的 SystemCoreClock
       * 会按错误的 HSE 频率计算,导致 delayMicroseconds 与串口波特率全部偏差。
       * 此处显式声明内核频率为 168MHz,使软件计时与硬件 PLL(168MHz) 一致,
       * 不依赖 boards.local.txt。仍建议同时设置 HSE_VALUE 以作双保险。 */
      SystemCoreClock = 168000000UL;
    }
    
    #define RS485_TX   PB6
    #define RS485_EN   PB5
    #define RS485_BAUD 9600UL
    #define RS485_BIT_US (1000000UL / RS485_BAUD)   // 约 104us
    
    /* 软件串口: 写一个比特 (LSB 先发由调用方处理) */
    static void rs485WriteBit(uint8_t b) {
      digitalWrite(RS485_TX, b ? HIGH : LOW);
      delayMicroseconds(RS485_BIT_US);
    }
    
    /* 软件串口: 写一个字节 (起始位 + 8数据 + 停止位) */
    static void rs485WriteByte(uint8_t data) {
      rs485WriteBit(0);                       // 起始位 低
      for (uint8_t i = 0; i < 8; i++) {
        rs485WriteBit((data >> i) & 0x01);    // 数据位 LSB 先
      }
      rs485WriteBit(1);                       // 停止位 高
    }
    
    /* 软件串口: 发送字符串 */
    static void rs485Print(const char *s) {
      noInterrupts();                         // 禁止中断,避免 ISR 拉长位时间造成帧抖动
      digitalWrite(RS485_EN, HIGH);           // EN 拉高 -> 发送方向
      delayMicroseconds(20);
      while (*s) rs485WriteByte((uint8_t)(*s++));
      delayMicroseconds(20);
      digitalWrite(RS485_EN, LOW);            // 回到接收/空闲
      interrupts();
    }
    
    void setup() {
      pinMode(RS485_TX, OUTPUT);
      pinMode(RS485_EN, OUTPUT);
      digitalWrite(RS485_TX, HIGH);           // 空闲态高
      digitalWrite(RS485_EN,   LOW);
    
      Serial1.begin(115200);                  // CH340
      Serial2.begin(115200);                  // 外接串口
    }
    
    void loop() {
      Serial1.print("st");                    // CH340
      Serial2.print("st");                    // 外接串口
      rs485Print("st");                       // 485
      delay(1000);                            // 每秒一次
    }
  • admin
    管理员
    #1
    /*
     * STM32F407VGT6 - 485 <-> Type-C(PA9/PA10) 透明转发桥
     * ------------------------------------------------------------
     * Type-C  : 软件串口 PA9(TX)/PA10(RX)  115200  (bit-bang, DWT 定时)
     * 485     : 硬件串口 Serial1 / PB6(TX)+PB7(RX) 9600, EN=PB5 控方向
     * 外接串口 : Serial2 (PA2/PA3) 115200  每秒广播 "st" (不变)
     *
     * 转发逻辑:
     *   485 收到数据          -> 转发到 Type-C (PA9)
     *   Type-C(PA10) 收到数据 -> 转发到 485
     *
     * 注意: 485 为半双工,EN 高=发送 / 低=接收。须由 EN 同时控制 DE 与 RE
     *       (标准 MAX485 模块 DE//RE 共接, 高有效),发送时接收关闭,避免自发自收回环。
     *       若你的模块 RE 被单独拉低(接收常开),则 485 会收到自己发出的回环,
     *       需在模块端把 RE 也接到 EN。
     *
     * 软件串口 RX 关键说明:
     *   115200 下每字节间隔仅 ~87us。若用"进中断后忙等整帧(~107us)"的 ISR,
     *   ISR 比字节周期还长 -> 下一字节起始位被迟到采样 -> 末字节读成 FF(全1=空闲)。
     *   故 RX 改用 TIM3 定时器以 3x 波特率(345600Hz)采样,ISR 仅 ~2.9us,
     *   永不阻塞下一字节,从根本上解决末字节乱码。
     */
    
    // 外接串口需自行定义(核心未提供 Serial2)
    Uart Serial2(USART2);   // PA2(TX)/PA3(RX)
    
    /* ============================================================
     * 12MHz 外接晶振专用时钟配置
     * 核心 F407 变体默认 PLL_M=8(按 8MHz 设计),12MHz 下 VCO=504MHz 超上限,
     * 覆盖 WEAK 的 SystemClock_Config,改 PLL_M=12 -> 168MHz,
     * 并显式声明 SystemCoreClock=168MHz,保证软件串口/DWT 定时与硬件串口波特率准确。
     * ============================================================ */
    extern "C" void SystemClock_Config(void)
    {
      RCC_OscInitTypeDef RCC_OscInitStruct = {};
      RCC_ClkInitTypeDef RCC_ClkInitStruct = {};
    
      __HAL_RCC_PWR_CLK_ENABLE();
      __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
    
      RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
      RCC_OscInitStruct.HSEState = RCC_HSE_ON;
      RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
      RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
      RCC_OscInitStruct.PLL.PLLM = 12;             // 12MHz / 12 = 1MHz VCO 输入
      RCC_OscInitStruct.PLL.PLLN = 336;            // VCO = 336MHz
      RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;   // SYSCLK = 168MHz
      RCC_OscInitStruct.PLL.PLLQ = 7;              // 48MHz (USB)
      if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
        Error_Handler();
      }
    
      RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK |
                                     RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2);
      RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
      RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
      RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;  // PCLK1 = 42MHz
      RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;  // PCLK2 = 84MHz
      if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK) {
        Error_Handler();
      }
    
      // 显式声明内核时钟(覆盖 HAL 基于 HSE_VALUE 的计算),供软件串口/DWT 使用
      SystemCoreClock = 168000000UL;
    }
    
    /* ============================================================
     * 软件串口:PA9 发 / PA10 收,接 CH340 (Type-C),115200
     *  TX(PA9): DWT 周期计数器 bit-bang (noInterrupts 保证位时序)
     *  RX(PA10): TIM3 定时器 3x 过采样,状态机解帧(见 rxTimerISR)
     * ============================================================ */
    #define SOFT_TX     PA9
    #define SOFT_RX     PA10
    #define SOFT_BAUD   115200UL
    static uint32_t g_softBitCycles;   // = SystemCoreClock / SOFT_BAUD
    
    /* --- TX (PA9) --- */
    static void softWriteBit(uint8_t b) {
      uint32_t t = DWT->CYCCNT;
      digitalWrite(SOFT_TX, b ? HIGH : LOW);
      while ((uint32_t)(DWT->CYCCNT - t) < g_softBitCycles) { }
    }
    
    static void softWrite(uint8_t d) {
      noInterrupts();                       // 关中断,避免 ISR 拉长位时间
      softWriteBit(0);                      // 起始位 低
      for (uint8_t i = 0; i < 8; i++) {
        softWriteBit((d >> i) & 0x01);      // 数据位 LSB 先
      }
      softWriteBit(1);                      // 停止位 高
      uint32_t t = DWT->CYCCNT;
      while ((uint32_t)(DWT->CYCCNT - t) < g_softBitCycles) { }  // 字节间空闲 1 bit
      interrupts();
    }
    
    /* --- RX (PA10):TIM3 定时器采样 --- */
    #include <HardwareTimer.h>
    #define SOFT_RX_BUF_SIZE  64
    static volatile uint8_t g_rxBuf[SOFT_RX_BUF_SIZE];
    static volatile uint8_t g_rxHead = 0, g_rxTail = 0;
    
    static HardwareTimer g_rxTimer(TIM3);
    static volatile int     g_rxPhase = -1;   // -1=空闲;>=0 自起始沿起计 tick
    static volatile uint8_t g_rxCur   = 0;
    
    // TIM3 溢出频率 = 3 * 波特率;每 tick ≈ 2.89us,每个数据位采样 3 次
    static void rxTimerISR(void) {
      static uint8_t prev = 1;
      uint8_t line = (GPIOA->IDR & (1u << 10)) ? 1 : 0;   // PA10 直读(比 digitalRead 快得多)
    
      if (g_rxPhase < 0) {
        if (prev == 1 && line == 0) {   // 下降沿 = 起始位
          g_rxPhase = 0;
          g_rxCur = 0;
        }
        prev = line;
        return;
      }
    
      g_rxPhase++;
      // 数据位在 phase 4,7,10,...,25 采样(每 bit 中心附近)
      if (g_rxPhase >= 4 && g_rxPhase <= 25 && ((g_rxPhase - 4) % 3) == 0) {
        uint8_t k = (uint8_t)((g_rxPhase - 4) / 3);   // 0..7
        if (k == 0) g_rxCur = 0;
        g_rxCur |= (line << k);
      }
      if (g_rxPhase == 28) {            // 停止位中心
        if (line == 1) {                // 停止位为高 -> 帧有效,入队
          uint8_t next = (uint8_t)((g_rxHead + 1) % SOFT_RX_BUF_SIZE);
          if (next != g_rxTail) {
            g_rxBuf[g_rxHead] = g_rxCur;
            g_rxHead = next;
          }
        }
        g_rxPhase = -1;                 // 回到空闲
      }
    }
    
    /* ============================================================
     * 485 硬件串口:Serial1 @ PB6(TX)/PB7(RX),EN=PB5 控方向
     * ============================================================ */
    #define RS485_EN   PB5
    // 485 TX = PB6(USART1_TX, AF7),RX = PB7(USART1_RX),复用原 485 接线,无需改板
    
    void setup() {
      // 启用 DWT 周期计数器(供软件串口 TX 精确延时)
      CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
      DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
      DWT->CYCCNT = 0;
    
      g_softBitCycles = SystemCoreClock / SOFT_BAUD;   // 168M / 115200 = 1458
    
      // 软件串口 Type-C
      pinMode(SOFT_TX, OUTPUT);
      digitalWrite(SOFT_TX, HIGH);                     // 空闲态高
      pinMode(SOFT_RX, INPUT_PULLUP);
      // RX 用 TIM3 采样,不占用下降沿中断(避免"忙等整帧"超字节周期)
      g_rxTimer.setOverflow((uint32_t)SOFT_BAUD * 3, HERTZ_FORMAT);  // 345600 Hz
      g_rxTimer.attachInterrupt(rxTimerISR);
      g_rxTimer.resume();
    
      // 485 方向控制(默认接收态)
      pinMode(RS485_EN, OUTPUT);
      digitalWrite(RS485_EN, LOW);
    
      // 485:硬件 USART1,TX/RX 用 PB6/PB7
      Serial1.setTx(PB6);
      Serial1.setRx(PB7);
      Serial1.begin(9600);
    
      // 外接串口:硬件 USART2,PA2/PA3
      Serial2.begin(115200);
    }
    
    void loop() {
      // 1) 485 -> Type-C(PA9)
      while (Serial1.available()) {
        softWrite((uint8_t)Serial1.read());
      }
    
      // 2) Type-C(PA10) -> 485(硬件 USART1/PB6,EN 控方向)
      //    关键: 必须等 TC(最后一字节停止位移出)后才能拉低 EN,否则末字节被截断 -> 收端报帧错(常显 0xFF)
      if (g_rxHead != g_rxTail) {
        delayMicroseconds(1000);               // 收集窗口: 把一帧数据尽量到齐(115200 下几字节<1ms)
    
        // 原子拷贝当前缓冲到临时帧
        uint8_t frame[SOFT_RX_BUF_SIZE];
        uint8_t n = 0;
        noInterrupts();
        while (g_rxHead != g_rxTail && n < sizeof(frame)) {
          frame[n++] = g_rxBuf[g_rxTail];
          g_rxTail = (uint8_t)((g_rxTail + 1) % SOFT_RX_BUF_SIZE);
        }
        interrupts();
    
        if (n > 0) {
          digitalWrite(RS485_EN, HIGH);                 // 切到发送
          delayMicroseconds(20);                        // 等 DE 建立
          Serial1.write(frame, n);                      // 整帧一次发出
          Serial1.flush();                              // 等软件缓冲排空(最后字节进 DR)
          while (!(USART1->SR & USART_SR_TC)) {}        // 等 TC: 最后停止位移出完毕再关驱动
          delayMicroseconds(20);                        // 余量
          digitalWrite(RS485_EN, LOW);                  // 回到接收态
        }
      }
    
      // 3) 外接串口心跳(不变):每秒广播 "st"
      static uint32_t lastSt = 0;
      if ((uint32_t)(millis() - lastSt) >= 1000) {
        lastSt = millis();
        Serial2.print("st");
      }
    }

发表回复

登录后回复