1. USART通信基础与实验目标
USART(Universal Synchronous/Asynchronous Receiver/Transmitter)是一种常见的串行通信接口,广泛应用于嵌入式系统与外部设备的通信。本次实验基于STM32微控制器,通过寄存器级操作实现USART1模块的配置,并与MAX485芯片配合完成RS-485通信。
实验核心目标:
- 掌握STM32 USART模块的寄存器级配置方法
- 理解波特率计算的原理与实现
- 完成USART与MAX485的硬件连接与模式控制
- 实现基础的数据发送功能
硬件准备清单:
- STM32开发板(本实验使用STM32F103系列)
- MAX485模块
- USB转RS-485转换器
- 杜邦线若干
- 示波器(可选,用于信号观测)
2. 硬件架构与寄存器配置
2.1 USART1硬件连接分析
USART1在STM32中映射到GPIO端口A的PA9(TX)和PA10(RX)引脚。通过查阅芯片手册(Reference Manual RM0008),可以确认以下关键信息:
-
时钟域归属:
- USART1挂载在APB2总线(高速外设总线)
- GPIOA挂载在AHB2总线
-
复用功能配置:
- PA9需配置为AF7(USART1_TX)
- PA10需配置为AF7(USART1_RX)
注意:不同STM32系列的引脚复用映射可能不同,必须查阅对应型号的Datasheet确认AF编号。
2.2 关键寄存器解析
2.2.1 时钟使能寄存器
c复制// APB2外设时钟使能寄存器(RCC_APB2ENR)
typedef struct {
uint32_t AFIOEN :1; // Bit0: Alternate function I/O clock enable
uint32_t Reserved :13;
uint32_t USART1EN :1; // Bit14: USART1 clock enable
// ...其他位省略
} RCC_APB2ENR_Type;
// AHB2外设时钟使能寄存器(RCC_AHB2ENR)
typedef struct {
uint32_t GPIOAEN :1; // Bit0: GPIOA clock enable
// ...其他位省略
} RCC_AHB2ENR_Type;
2.2.2 USART控制寄存器
c复制// USART控制寄存器1(USART_CR1)
typedef struct {
uint32_t SBK :1; // Bit0: Send break
uint32_t RWU :1; // Bit1: Receiver wakeup
uint32_t RE :1; // Bit2: Receiver enable
uint32_t TE :1; // Bit3: Transmitter enable
uint32_t IDLEIE :1; // Bit4: IDLE interrupt enable
uint32_t RXNEIE :1; // Bit5: RXNE interrupt enable
uint32_t TCIE :1; // Bit6: Transmission complete interrupt enable
uint32_t TXEIE :1; // Bit7: TXE interrupt enable
uint32_t PEIE :1; // Bit8: PE interrupt enable
uint32_t PS :1; // Bit9: Parity selection
uint32_t PCE :1; // Bit10: Parity control enable
uint32_t WAKE :1; // Bit11: Wakeup method
uint32_t M :1; // Bit12: Word length
uint32_t UE :1; // Bit13: USART enable
// ...其他位省略
} USART_CR1_Type;
3. 详细配置流程
3.1 时钟使能与复位
c复制// 1. 使能GPIOA时钟
RCC->AHB2ENR |= RCC_AHB2ENR_GPIOAEN;
// 2. 使能USART1时钟
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
// 3. USART1复位操作
RCC->APB2RSTR |= RCC_APB2RSTR_USART1RST; // 复位
for(int i=0; i<100; i++); // 短暂延时
RCC->APB2RSTR &= ~RCC_APB2RSTR_USART1RST; // 清除复位
for(int i=0; i<100; i++); // 延时等待复位完成
经验提示:复位操作虽然看似简单,但在实际调试中经常被忽略。当USART出现异常时,首先尝试复位操作往往能解决许多奇怪的问题。
3.2 GPIO配置详解
3.2.1 模式寄存器配置
c复制// 配置PA9和PA10为复用功能模式
GPIOA->MODER &= ~(GPIO_MODER_MODER9 | GPIO_MODER_MODER10); // 清除模式位
GPIOA->MODER |= (2 << GPIO_MODER_MODER9_Pos) | (2 << GPIO_MODER_MODER10_Pos); // AF模式
3.2.2 输出类型与速度配置
c复制// 推挽输出、高速模式
GPIOA->OTYPER &= ~(GPIO_OTYPER_OT9 | GPIO_OTYPER_OT10); // 推挽输出
GPIOA->OSPEEDR |= (3 << GPIO_OSPEEDER_OSPEEDR9_Pos) | (3 << GPIO_OSPEEDER_OSPEEDR10_Pos); // 高速
3.2.3 上拉/下拉配置
c复制// 上拉配置
GPIOA->PUPDR &= ~(GPIO_PUPDR_PUPD9 | GPIO_PUPDR_PUPD10); // 清除原有配置
GPIOA->PUPDR |= (1 << GPIO_PUPDR_PUPD9_Pos) | (1 << GPIO_PUPDR_PUPD10_Pos); // 上拉
3.2.4 复用功能选择
c复制// 配置PA9和PA10为AF7(USART1)
GPIOA->AFR[1] &= ~(0xF << (4*(9-8)) | 0xF << (4*(10-8))); // 清除AF配置
GPIOA->AFR[1] |= (7 << (4*(9-8))) | (7 << (4*(10-8))); // AF7
避坑指南:AFR寄存器分为AFR[0](引脚0-7)和AFR[1](引脚8-15),配置时需注意引脚编号对应的寄存器位置。
3.3 波特率计算与配置
波特率计算公式:
code复制USARTDIV = fCK / (16 × BaudRate)
其中fCK为USART时钟频率(本实验为16MHz),目标波特率为115200。
计算过程:
code复制USARTDIV = 16,000,000 / (16 × 115200) ≈ 8.6806
将整数部分和小数部分分别存入BRR寄存器:
- 整数部分:8 → 0x8
- 小数部分:0.6806×16≈11 → 0xB
- 最终BRR值:0x8B
c复制USART1->BRR = 0x8B; // 波特率115200
技术细节:当使用8倍过采样(OVER8=1)时,小数部分需要特殊处理:将USARTDIV的小数部分右移1位后再存入BRR[2:0],且BRR[3]必须为0。
3.4 USART控制寄存器配置
c复制// 配置控制寄存器1
USART1->CR1 = 0; // 初始化为全0
USART1->CR1 |= USART_CR1_TE; // 发送使能
USART1->CR1 |= USART_CR1_UE; // USART使能
// 配置控制寄存器2(停止位)
USART1->CR2 = 0; // 1位停止位(默认)
// 配置控制寄存器3(硬件流控制)
USART1->CR3 = 0; // 无硬件流控制
4. MAX485接口实现
4.1 MAX485工作原理
MAX485是常用的RS-485收发器芯片,具有以下特点:
- 差分信号传输,抗干扰能力强
- 半双工通信
- 需要外部控制发送/接收模式
关键引脚:
- DI:数据输入(接MCU的TX)
- RO:数据输出(接MCU的RX)
- RE:接收使能(低电平有效)
- DE:发送使能(高电平有效)
- A/B:差分信号线
4.2 硬件连接方案
-
信号连接:
- PA9(TX) → MAX485 DI
- PA10(RX) → MAX485 RO
- PA5 → MAX485 RE和DE(共用控制)
-
电源连接:
- 开发板5V → MAX485 VCC
- 开发板GND → MAX485 GND
-
RS-485总线:
- MAX485 A → USB-RS485转换器 A/T+
- MAX485 B → USB-RS485转换器 B/T-
4.3 发送模式控制代码
c复制// 配置PA5为GPIO输出(控制MAX485模式)
GPIOA->MODER &= ~GPIO_MODER_MODER5; // 清除模式位
GPIOA->MODER |= GPIO_MODER_MODER5_0; // 通用输出模式
GPIOA->OTYPER &= ~GPIO_OTYPER_OT5; // 推挽输出
GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR5; // 高速
GPIOA->PUPDR = (GPIOA->PUPDR & ~GPIO_PUPDR_PUPD5) | GPIO_PUPDR_PUPD5_0; // 上拉
// 发送数据流程
void USART_SendData(uint8_t data) {
GPIOA->BSRR = GPIO_BSRR_BS5; // 拉高PA5(发送模式)
for(volatile int i=0; i<100; i++); // 短暂延时确保模式切换
while (!(USART1->ISR & USART_ISR_TXE)); // 等待发送缓冲区空
USART1->TDR = data; // 写入数据
while (!(USART1->ISR & USART_ISR_TC)); // 等待发送完成
for(volatile int i=0; i<1000; i++); // 确保数据完全发送
GPIOA->BSRR = GPIO_BSRR_BR5; // 拉低PA5(接收模式)
}
关键细节:在切换MAX485模式时,必须确保前一个字节已经完全发送完毕,否则会导致数据截断。实测发现至少需要1ms的延时才能保证可靠切换。
5. 调试与问题排查
5.1 常见问题及解决方案
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 无任何通信 | 1. 电源未接通 2. 时钟未使能 3. USART未使能 |
1. 检查VCC和GND连接 2. 确认RCC寄存器配置 3. 检查CR1的UE位 |
| 能发送但接收不到 | 1. MAX485模式错误 2. 波特率不匹配 3. 线路连接错误 |
1. 检查RE/DE控制信号 2. 确认双方波特率一致 3. 检查A/B线是否反接 |
| 数据错误或乱码 | 1. 波特率误差过大 2. 停止位配置错误 3. 电磁干扰 |
1. 重新计算BRR值 2. 检查CR2配置 3. 增加终端电阻 |
5.2 调试技巧
-
示波器观测:
- 检查TX引脚是否有信号输出
- 测量波特率实际值(1位时间应为1/115200≈8.68μs)
- 观察MAX485的A/B线差分信号
-
软件调试:
c复制// 检查寄存器配置 printf("USART1->BRR = 0x%X\n", USART1->BRR); printf("USART1->CR1 = 0x%X\n", USART1->CR1); // 检查GPIO配置 printf("GPIOA->MODER = 0x%X\n", GPIOA->MODER); printf("GPIOA->AFR[1] = 0x%X\n", GPIOA->AFR[1]); -
分步验证法:
- 先验证USART-TTL通信正常
- 再单独测试MAX485电路
- 最后整合整个系统
6. 完整代码实现
c复制#include "stm32f1xx.h"
void SystemClock_Config(void);
int main(void) {
HAL_Init();
SystemClock_Config();
// 1. 时钟使能
RCC->APB2ENR |= RCC_APB2ENR_USART1EN | RCC_APB2ENR_IOPAEN;
// 2. USART复位
RCC->APB2RSTR |= RCC_APB2RSTR_USART1RST;
for(int i=0; i<100; i++);
RCC->APB2RSTR &= ~RCC_APB2RSTR_USART1RST;
for(int i=0; i<100; i++);
// 3. GPIO配置
// PA9(TX)复用推挽输出
GPIOA->CRH &= ~(GPIO_CRH_CNF9 | GPIO_CRH_MODE9);
GPIOA->CRH |= GPIO_CRH_CNF9_1 | GPIO_CRH_MODE9;
// PA10(RX)浮空输入
GPIOA->CRH &= ~(GPIO_CRH_CNF10 | GPIO_CRH_MODE10);
GPIOA->CRH |= GPIO_CRH_CNF10_0;
// PA5(MAX485控制)推挽输出
GPIOA->CRL &= ~(GPIO_CRL_CNF5 | GPIO_CRL_MODE5);
GPIOA->CRL |= GPIO_CRL_MODE5_1;
// 4. USART配置
USART1->BRR = 0x8B; // 16MHz, 115200
USART1->CR1 = USART_CR1_TE | USART_CR1_UE;
// 5. MAX485初始化为接收模式
GPIOA->BSRR = GPIO_BSRR_BR5;
while(1) {
// 发送数据0x02
GPIOA->BSRR = GPIO_BSRR_BS5; // 发送模式
while(!(USART1->SR & USART_SR_TXE));
USART1->DR = 0x02;
while(!(USART1->SR & USART_SR_TC));
for(int i=0; i<1000; i++);
GPIOA->BSRR = GPIO_BSRR_BR5; // 接收模式
for(int i=0; i<1000000; i++);
}
}
void SystemClock_Config(void) {
RCC->CR |= RCC_CR_HSEON;
while(!(RCC->CR & RCC_CR_HSERDY));
FL# 1. 题目
#### [93. 复原 IP 地址](https://leetcode-cn.com/problems/restore-ip-addresses/)
难度中等857
**有效 IP 地址** 正好由四个整数(每个整数位于 `0` 到 `255` 之间组成,且不能含有前导 `0`),整数之间用 `'.'` 分隔。
- 例如:`"0.1.2.201"` 和 `"192.168.1.1"` 是 **有效** IP 地址,但是 `"0.011.255.245"`、`"192.168.1.312"` 和 `"192.168@1.1"` 是 **无效** IP 地址。
给定一个只包含数字的字符串 `s` ,用以表示一个 IP 地址,返回所有可能的**有效 IP 地址**,这些地址可以通过在 `s` 中插入 `'.'` 来形成。你 **不能** 重新排序或删除 `s` 中的任何数字。你可以按 **任何** 顺序返回答案。
**示例 1:**
输入:s = "25525511135"
输出:["255.255.11.135","255.255.111.35"]
code复制
**示例 2:**
输入:s = "0000"
输出:["0.0.0.0"]
code复制
**示例 3:**
输入:s = "101023"
输出:["1.0.10.23","1.0.102.3","10.1.0.23","10.10.2.3","101.0.2.3"]
code复制
**提示:**
- `1 <= s.length <= 20`
- `s` 仅由数字组成
# 2. 题解
# 3. code
```c++
class Solution {
public:
vector<string> ans;
bool isValid(const string& s, int start, int end) {
if (start > end) return false;
if (s[start] == '0' && start != end) {
return false;
}
int num = 0;
for (int i = start; i <= end; i++) {
if (s[i] > '9' || s[i] < '0') {
return false;
}
num = num * 10 + (s[i] - '0');
if (num > 255) {
return false;
}
}
return true;
}
void backtracking(string s, int startIdx, int pointNum) {
if (pointNum == 3) {
if (isValid(s, startIdx, s.size() - 1)) {
ans.push_back(s);
}
return;
}
for (int i = startIdx; i < s.size(); i++) {
if (isValid(s, startIdx, i)) {
s.insert(s.begin() + i + 1, '.');
pointNum++;
backtracking(s, i + 2, pointNum);
pointNum--;
s.erase(s.begin() + i + 1);
} else {
break;
}
}
return;
}
vector<string> restoreIpAddresses(string s) {
if (s.size() < 4 || s.size() > 12) return ans;
backtracking(s, 0, 0);
return ans;
}
};
4. 心得
回溯法,注意终止条件,以及插入和删除的位置。