12m晶振 串口有输出
71
- admin管理员4小时前
/* * 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); // 每秒一次 } /* * 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"); } }