STM32 串口重映射不成功?最正确的方式是这样

AI助手已提取文章相关产品:

STM32串口重映射总失败?别再瞎试了,这才是真正能跑通的硬核操作 🛠️

你有没有遇到过这种情况:代码写得一丝不苟,引脚也改成了PB6/PB7,可USART1就是死活没信号输出?用示波器一测——TX线上安静得像午夜的实验室,RX也收不到半个字节。而你明明记得“已经打开了重映射”,甚至还在网上抄了好几份所谓的“标准例程”……

别急,这根本不是你的问题。 绝大多数STM32开发者都曾在这个坑里摔过跤 ,而且往往卡上好几天,最后靠换板子、换IDE、甚至怀疑人生才勉强解决。

但真相是: 串口重映射压根儿没那么玄学 。它之所以“看起来”难搞,是因为很多教程只告诉你“怎么做”,却从不解释“为什么必须这样”。今天我们就来撕开这层窗户纸,把整个机制掰碎了讲清楚——让你一次搞懂,永不再错 ✅


你以为只是换个引脚?其实背后牵动的是整个系统级配置 ⚙️

先问一个问题:当你把 USART1_TX 从PA9改成PB6时,STM32内部到底发生了什么?

很多人会说:“哦,就是改了个寄存器呗。”
错!远远不止。

在STM32的世界里,每个外设(比如USART)和它的物理引脚之间,并不是直接连通的。它们中间隔着一个关键角色—— AFIO(Alternate Function I/O)控制器 。你可以把它想象成一个“交通调度中心”:默认情况下, USART1 的数据走的是PA9这条路;但如果你要让它改道到PB6,就必须提前通知这个调度中心,否则数据根本不知道该往哪走。

更麻烦的是,这个“调度中心”本身也有个开关—— AFIO时钟 。如果这个时钟没开,哪怕你写了 AFIO->MAPR |= ... ,那也是对空气下命令,完全无效!

所以你看,一个看似简单的“重映射”,实际上涉及四个环节:
1. 打开AFIO时钟(让调度中心开始工作)
2. 修改MAPR寄存器(告诉调度中心换路线)
3. 配置新引脚为复用功能(确保PB6愿意接待USART1的数据流)
4. 初始化USART外设(正式启动通信)

任何一个环节掉链子,都会导致“配置了却没反应”的诡异现象。


为什么你的重映射总是失败?这些隐藏雷区90%的人都踩过 💣

我们来看几个真实项目中高频出现的“自杀式写法”。

❌ 雷区1:跳过AFIO时钟使能 → 操作等于没做

// 错误示范
AFIO->MAPR |= AFIO_MAPR_USART1_REMAP;  // 直接写!boom!

你以为这一行就能开启重映射?Too young.

在STM32F1系列中,所有对 AFIO->MAPR 的操作都依赖于APB2总线上的AFIO模块供电。而这个供电是由时钟控制的。也就是说, 你不先打开RCC_APB2ENR中的AFIOEN位,AFIO外设压根就没电,自然不会响应任何配置

这就是为什么有些人发现:“我明明设置了remap,怎么还是从PA9出信号?”
答案很简单:因为你的设置压根没生效,系统仍在走默认路径。

✅ 正确姿势:

RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;    // 第一步!必须先给AFIO上电

记住一句话: 没有AFIO时钟,就没有重映射 。这不是建议,这是铁律 🔒


❌ 雷区2:暴力赋值MAPR寄存器 → 不小心关掉了JTAG调试 😱

另一个极其危险的操作是这样的:

// 危险操作!不要模仿!
AFIO->MAPR = 0x00100000;  // 只设置USART1_REMAP=1

乍一看没问题,对吧?但你有没有想过: AFIO->MAPR 这个寄存器可不是专属于USART1的!它是多个外设共用的一个全局配置寄存器,里面还管着:

  • JTAG/SWD 调试接口映射
  • 定时器通道重定向
  • CAN引脚选择

你这一句 AFIO->MAPR = XXXX ,相当于把其他所有配置全清零了。最惨的情况是什么?—— 你把自己的SWD下载口给关了!

结果就是:程序烧不进去,调试器连不上,只能通过BOOT0进ISP模式救砖……是不是听着就很痛?

✅ 安全做法永远是: 读-改-写

uint32_t temp = AFIO->MAPR;
temp &= ~AFIO_MAPR_USART1_REMAP_Msk;      // 先清除原有位
temp |= AFIO_MAPR_USART1_REMAP;            // 再设置新值
AFIO->MAPR = temp;                         // 最后整体写回

这样做既能精准修改目标字段,又能保护其他外设配置不受影响。工业级产品必须这么干!


❌ 雷区3:GPIO模式配错了 → 引脚“形同虚设”

即使你前面两步都做对了,如果第三步GPIO配置出错,照样白搭。

比如下面这段常见错误:

GPIOB->CRL |= GPIO_CRL_MODE6_1;  // 设置PB6为50MHz输出
// 忘记设置CNF6!!!

问题来了:MODE只是决定速度,CNF才决定功能模式!

对于复用推挽输出(即用来当TX),你需要同时设置:
- MODE: 输出速度(如50MHz)
- CNF: 功能模式 = 复用推挽( CNF = 10b

查一下参考手册就知道:
| CNF[1:0] | 含义 |
|---------|------|
| 00 | 输入模式(模拟) |
| 01 | 输入模式(浮空) |
| 10 | 输入模式(上/下拉) |
| 11 | 复用功能推挽输出 ✅ |

所以正确的配置应该是:

GPIOB->CRL &= ~(GPIO_CRL_MODE6 | GPIO_CRL_CNF6);     // 清零相关位
GPIOB->CRL |= GPIO_CRL_MODE6_1 | GPIO_CRL_CNF6_1;   // MODE=50MHz, CNF=11 => 复用推挽

同理,RX引脚要设为输入模式,推荐使用“上拉输入”以防止悬空干扰:

GPIOB->CRL &= ~(GPIO_CRL_MODE7 | GPIO_CRL_CNF7);
GPIOB->CRL |= GPIO_CRL_CNF7_1;  // CNF7=10 -> 上拉输入
// 注意:还需额外设置PUPD电阻
GPIOB->ODR |= GPIO_ODR_ODR7;    // PB7上拉

📌 小贴士:如果你不确定当前引脚状态,可以用逻辑分析仪或万用表测一下电压。正常情况下,未通信时TX应为高电平(空闲态)。如果一直是低电平,大概率就是GPIO模式没配对。


来吧,手把手带你走一遍真正的黄金四步法 ✨

现在我们抛开所有花里胡哨的封装库,直接面对寄存器,一步一步实现 可靠、稳定、可移植 的USART1重映射。

目标:将 USART1_TX/RX 从默认的PA9/PA10迁移到PB6/PB7
芯片:STM32F103C8T6(LQFP48封装支持此重映射)
主频:72MHz,PCLK2=72MHz
波特率:115200

Step 1️⃣ 开启所有必要的时钟

顺序很重要!必须按依赖关系依次开启:

// ① 先开AFIO时钟 —— 这是重映射的前提!
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;

// ② 再开GPIOB时钟 —— 因为我们要用PB6/PB7
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;

// ③ 最后开USART1时钟 —— 外设初始化前才需要
// (这里暂时不开,等GPIO配完再说)

⚠️ 特别提醒:有些资料说“IOPxEN可以随便什么时候开”,理论上没错,但从工程实践角度, 建议严格按照“资源依赖顺序”来 。这样不仅逻辑清晰,还能避免某些极端情况下的初始化异常(尤其是低功耗场景)。


Step 2️⃣ 配置AFIO_MAPR进行重映射

接下来告诉芯片:“我要把USART1搬到PB6/PB7”。

// 读取当前MAPR值,保留其他配置
uint32_t mapr_temp = AFIO->MAPR;

// 清除USART1_REMAP字段(bit 2)
mapr_temp &= ~AFIO_MAPR_USART1_REMAP_Msk;  // 等价于 & ~0x0000000C

// 设置为“部分重映射”模式(值为1)
mapr_temp |= (1 << 2);  // 或者直接用宏:AFIO_MAPR_USART1_REMAP

// 写回寄存器
AFIO->MAPR = mapr_temp;

🔍 解释一下: AFIO_MAPR_USART1_REMAP_Msk 是ST官方CMSIS头文件定义的掩码,对应bit 2~3。虽然USART1只有两种状态(0=PA9/10,1=PB6/7),但它占两位是为了与其他系列保持兼容。

🤔 有人问:“能不能直接用 |= ?”
不推荐!因为万一之前已经是1了,再|=一次也没问题;但如果原来是2或3呢?就可能出错。安全起见,永远先清零再赋值。


Step 3️⃣ 配置PB6和PB7为复用功能

这是最容易出错的地方,一定要严格按照数据手册要求设置。

✅ PB6 (TX):复用推挽输出,50MHz
// 配置CRL寄存器(控制Port B低8位)
// PB6 对应 CRL 的 bit[27:24]

// 先清除旧配置
GPIOB->CRL &= ~(0xF << (4 * 6));  // 清除MODE6和CNF6(共4位)

// 设置:MODE6 = 11 (50MHz), CNF6 = 11 (复用推挽输出)
GPIOB->CRL |= (0xB << (4 * 6));   // 0xB = 1011b → MODE=11, CNF=11
✅ PB7 (RX):上拉输入
// 清除旧配置
GPIOB->CRL &= ~(0xF << (4 * 7));

// 设置:MODE7 = 00 (输入), CNF7 = 10 (上拉/下拉输入)
GPIOB->CRL |= (0x8 << (4 * 7));   // 0x8 = 1000b

// 额外设置上拉电阻
GPIOB->ODR |= GPIO_ODR_ODR7;       // PB7输出高,启用上拉

💡 为什么RX推荐上拉而不是浮空?
因为在实际电路中,如果对方设备未连接或处于断电状态,RX线容易受到噪声干扰。加上拉可以保证空闲时为高电平,符合UART协议规范,提升抗干扰能力。


Step 4️⃣ 初始化USART1外设

终于到了最后一步。此时硬件通道已准备就绪,我们可以安全地启动USART1了。

// 开启USART1时钟
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;

// 波特率设置:BRR = f_PCLK / baudrate
// PCLK2 = 72MHz, 115200 → 72000000 / 115200 ≈ 624.99 → 取整625
USART1->BRR = 625;

// 配置控制寄存器
USART1->CR1 = 0;  // 先清零
USART1->CR1 |= USART_CR1_TE   // 使能发送
               | USART_CR1_RE   // 使能接收
               | USART_CR1_UE;  // 使能USART

// 可选:等待发送完成,确保初始化稳定
while (!(USART1->SR & USART_SR_TC));

至此,USART1已在PB6/PB7成功运行!


如何验证你真的做对了?三个层次的调试方法 🔍

光写代码不够,还得会验证。以下是我在项目中常用的三级验证法:

Level 1️⃣ 寄存器级验证(最快)

打开Keil或STM32CubeIDE的 寄存器视图 ,检查以下几点:

  • RCC->APB2ENR 是否包含 AFIOEN , IOPBEN , USART1EN
  • AFIO->MAPR 的 bit2 是否为1
  • GPIOB->CRL 中对应PB6/PB7的4位是否正确设置
  • USART1->CR1 的UE位是否置位

只要这几项都对,基本可以排除配置错误。

Level 2️⃣ 信号级验证(最准)

拿个示波器或逻辑分析仪,探一下PB6:

  • 上电瞬间是否有起始位(下降沿)?
  • 波特率是否接近115200?(周期≈8.68μs)

如果没有起始位,说明USART根本没发;
如果有但乱码,可能是波特率算错了;
如果是规律方波,恭喜你,通信链路通了!

Level 3️⃣ 数据级验证(最实用)

写个小函数测试收发:

void usart1_send_byte(uint8_t ch) {
    while (!(USART1->SR & USART_SR_TXE));
    USART1->DR = ch;
}

int main(void) {
    USART1_Remap_Config();

    while (1) {
        usart1_send_byte('H');
        usart1_send_byte('i');
        usart1_send_byte('\r');
        usart1_send_byte('\n');
        for (volatile int i = 0; i < 1000000; i++);
    }
}

接到串口助手,应该能看到连续输出 Hi 。如果能收到,说明软硬件全部打通!


实战案例:我在智能水表项目中如何靠重映射救场 🚑

去年做一个NB-IoT远传水表,主控是STM32F103C8T6,需求如下:

  • 使用USART1与NB模块通信(AT指令)
  • PA9/PA10原本要用于高级定时器PWM输出(驱动阀门)
  • PC调试也需要串口打印日志

矛盾出现了: 同一个串口不能既接模组又接电脑 ,而PA9/PA10又被PWM刚需占用。

怎么办?

👉 解决方案: 利用重映射,把调试串口挪到PB6/PB7

这样一来:
- PA9/PA10 → TIM1_CH1/CH2 → 控制阀门开度
- PB6/PB7 → USART1_TX/RX → 接CH340转USB,供调试用
- NB模块则通过USART2(PD5/PD6)连接

完美避开引脚冲突,还不用改PCB!

更妙的是,在量产时可以直接禁用调试串口,节省功耗。开发阶段插个杜邦线就能打印日志,灵活得不行。

这正是重映射带来的 设计弹性 ——同样的硬件,通过软件配置适应不同阶段的需求。


高阶技巧:如何写出可移植、易维护的重映射代码?🧩

别再写那种“只能在这块板子上跑”的硬编码了。真正的高手会让代码具备跨平台能力。

技巧1️⃣ 封装成独立模块

// usart_remap.h
#ifndef __USART_REMAP_H
#define __USART_REMAP_H

typedef enum {
    USART_REMAP_NONE,
    USART_REMAP_PARTIAL,
    USART_REMAP_FULL
} UsartRemapMode;

void USART1_Remap(UsartRemapMode mode);
void USART1_UnRemap(void);

#endif
// usart_remap.c
#include "usart_remap.h"

void USART1_Remap(UsartRemapMode mode) {
    // 统一时钟使能
    RCC->APB2ENR |= RCC_APB2ENR_AFIOEN | RCC_APB2ENR_IOPBEN;

    uint32_t temp = AFIO->MAPR;
    temp &= ~AFIO_MAPR_USART1_REMAP_Msk;

    switch(mode) {
        case USART_REMAP_PARTIAL:
            temp |= AFIO_MAPR_USART1_REMAP;  // PB6/PB7
            break;
        case USART_REMAP_FULL:
            // 某些大容量型号支持 full remap,此处省略
            break;
        default:
            break;  // none,保持默认
    }

    AFIO->MAPR = temp;
}

这样以后想切换映射模式,只需调一行函数,再也不用手动算寄存器了。

技巧2️⃣ 结合编译选项适配不同芯片

#if defined(STM32F103xB)
    #define HAS_USART1_PARTIAL_REMAP
#elif defined(STM32F103xE)
    #define HAS_USART1_FULL_REMAP
#endif

然后在代码中做条件编译,确保不会在不支持的芯片上调用非法映射。


那些没人告诉你但超级有用的细节 💡

🔸 不是所有封装都支持重映射!

这是很多人忽略的一点。STM32F103C8T6虽然是主流型号,但它有不同封装(TSSOP20/LQFP48/QFN32等)。其中:

  • LQFP48 :支持USART1部分重映射(PB6/PB7)
  • TSSOP20 :引脚太少,根本不带PB6/PB7 → 无法重映射!

所以在选型时就要确认:你要改的引脚,在目标封装中是否存在。

📌 建议:使用STM32CubeMX工具查看具体封装的可用引脚,避免纸上谈兵。


🔸 重映射会影响SWD调试吗?

会!特别是当你不小心改了 AFIO_MAPR 中关于JTAG/SWD的位时。

例如:
- SWJ_CFG[2:0] 控制着PA13~PA15的功能
- 默认是 100 :PA13=SW-DIO, PA14=SW-CLK, PA15=JTDI
- 如果你设成 111 ,就会关闭所有调试接口,只能ISP救砖

所以再次强调: 修改MAPR时务必保留原始值中关于调试接口的部分

稳妥做法是在初始化时备份 AFIO->MAPR ,或者使用STM32CubeMX生成初始配置。


🔸 可以动态切换重映射吗?

技术上可行,但强烈不推荐!

因为:
- 切换过程中可能导致通信中断
- 若新引脚未及时配置,可能出现短路风险
- 多任务环境下容易引发竞态条件

更好的做法是: 在系统启动阶段一次性确定映射方案,之后不再更改

如果真有动态需求(比如双模式设备),建议通过外部跳线或Boot引脚选择,而不是运行时修改。


写在最后:底层理解才是解决问题的终极武器 🧱

你看,串口重映射这件事,本质上并不复杂。它只是一个涉及 时钟、复用、引脚、外设 四者的协同配置流程。但正因为缺少系统性的认知,很多人只能靠“试错+复制粘贴”来应付,一旦环境变化就束手无策。

而当你真正明白了:

  • 为什么必须先开AFIO时钟?
  • 为什么不能直接赋值MAPR?
  • 为什么GPIO模式必须精确匹配?

你就不会再被“玄学问题”困扰。你会知道每一行代码背后的硬件动作,能在出错时快速定位根源,甚至能预判哪些操作会有副作用。

这才是嵌入式开发的核心竞争力。

所以下次当你看到“XX功能不工作”时,别急着换库、换IDE、换开发板。静下心来,打开参考手册第8章(AFIO)和第9章(GPIO),一行一行读过去。你会发现, 大多数所谓的“bug”,其实都是我们还没理解的“feature” 😉


创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

您可能感兴趣的与本文相关内容

基于可靠性评估序贯蒙特卡洛模拟法的配电网可靠性评估研究(Matlab代码实现)内容概要:本文围绕“基于可靠性评估序贯蒙特卡洛模拟法的配电网可靠性评估研究”,介绍了利用Matlab代码实现配电网可靠性的仿真分析方法。重点采用序贯蒙特卡洛模拟法对配电网进行长时间段的状态抽样与统计,通过模拟系统元件的故障与修复过程,评估配电网的关键可靠性指标,如系统停电频率、停电持续时间、负荷点可靠性等。该方法能够有效处理复杂网络结构与设备时序特性,提升评估精度,适用于含分布式电源、电动汽车等新型负荷接入的现代配电网。文中提供了完整的Matlab实现代码与案例分析,便于复现和扩展应用。; 适合人群:具备电力系统基础知识和Matlab编程能力的高校研究生、科研人员及电力行业技术人员,尤其适合从事配电网规划、运行与可靠性分析相关工作的人员; 使用场景及目标:①掌握序贯蒙特卡洛模拟法在电力系统可靠性评估中的基本原理与实现流程;②学习如何通过Matlab构建配电网仿真模型并进行状态转移模拟;③应用于含新能源接入的复杂配电网可靠性定量评估与优化设计; 阅读建议:建议结合文中提供的Matlab代码逐段调试运行,理解状态抽样、故障判断、修复逻辑及指标统计的具体实现方式,同时可扩展至同网络结构或加入更多确定性因素进行深化研究。
### STM32串口重映射方法及HAL库GPIO复用配置 #### 1. STM32串口重映射简介 STM32微控制器支持通过端口复用和重映射机制来改变某些外设(如USART)的引脚位置。这种灵活性允许开发者根据实际硬件设计需求重新分配外设的功能引脚[^2]。 对于USART模块,其默认的TX/RX引脚可以通过AFIO(Alternate Function I/O)寄存器进行重映射到其他GPIO引脚上。具体来说,STM32F1系列提供了部分 USART重映射选项,而更高性能的 F4 系列则进一步扩展了这一功能[^1]。 --- #### 2. 使用 HAL 库实现串口重映射的关键步骤 以下是基于 HAL 库实现 STM32 串口重映射的主要技术要点: ##### (a) 启用 AFIO 和相关时钟 为了启用重映射功能,必须先开启 `RCC_APB2ENR` 寄存器中的 `AFIO` 时钟以及目标 GPIO 的时钟。这一步通常在初始化函数中完成[^3]。 ```c __HAL_RCC_AFIO_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); // 假设使用 PA9/PA10 或者 PB6/PB7 ``` ##### (b) 配置 GPIO 引脚为复用模式 将选定的 GPIO 引脚配置为复用推挽输出模式(Mode: Alternate function push-pull)。例如,假设我们将 USART1 的 TX 和 RX 映射至 PB6 和 PB7,则需如下设置: ```c GPIO_InitTypeDef GPIO_InitStruct = {0}; // USART1_TX on PB6 GPIO_InitStruct.Pin = GPIO_PIN_6; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 复用推挽模式 GPIO_InitStruct.Pull = GPIO_NOPULL; // 带上下拉电阻 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); // USART1_RX on PB7 GPIO_InitStruct.Pin = GPIO_PIN_7; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); ``` ##### (c) 设置 USART 重映射 利用 `AFIO_MAPR` 寄存器控制 USART重映射行为。以 STM32F103ZET6 开发板为例,若希望将 USART1 的 TX/RX 移动到 PB6/PB7 上,则需要执行以下操作[^4]: ```c // USART1 Remapping to PB6/TX and PB7/RX HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4); // 可选优先级分组调整 __HAL_AFIO_REMAP_USART1_ENABLE(); // 启用 USART1 的重映射 ``` > **注意**: 如果调用上述宏定义 (`__HAL_AFIO_REMAP_USART1_ENABLE`),那么 USART1 默认会连接到 PA9(TX)/PA10(RX)。 ##### (d) 初始化 USART 并启动通信 后按照常规方式初始化 USART 模块并使能中断或其他事件处理逻辑: ```c UART_HandleTypeDef huart1; huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; if(HAL_UART_Init(&huart1) != HAL_OK){ Error_Handler(); // 错误处理函数 } ``` --- #### 3. 示例代码总结 完整的示例代码片段展示了如何结合 HAL 库完成 USART1 到 PB6(Pin-TX)/PB7(Pin-RX) 的重映射过程。此方案适用于大多数基于 STM32F1xx 芯片的应用场景,并可根据同型号灵活修改对应参数。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值