stm32最小核心板 串口篇
「进串口」系统 Bootloader 烧录(可选)
如果你想 通过串口直接下载程序(不是用 ST-Link),F103 的系统引导支持串口 ISP:
拉高 BOOT0(接 3.3V),BOOT1 置 0(接 GND;很多小板 BOOT1 固定为 0)。
复位/上电,芯片进入 System Memory (内置 bootloader)。
用 STM32CubeProgrammer(或老的 Flash Loader Demonstrator)选择 UART,波特率 115200,连上你的 USB-TTL(仍是 TX↔RX 交叉、GND 共地)。
识别到芯片后,选择要烧的 .hex/.bin 下载。
烧完把 BOOT0 拉回 0,再复位,才会从 用户 Flash 运行你的应用。
如果连不上:检查波特率、接线、是否真的把 BOOT0 拉到 1;某些小板需要按住复位再松开。
0x00目标外设与引脚
- USART1:PA9(TX), PA10(RX) @ 115200, 8N1,中断收
- I2C1:PB6(SCL), PB7(SDA) @ 100 kHz(先稳后快)
- 上拉:SCL/SDA 建议各接 4.7k–10kΩ 到 3.3V(若暂时没有,见文末“临时上拉”)
0x01) 时钟(RCC → Clock Configuration)
HSE: Crystal/Ceramic Resonator(8 MHz 常见)
PLL: ON, PLL MUL = x9
SYSCLK = 72 MHz
AHB = 72 MHz, APB1 = 36 MHz, APB2 = 72 MHz
Flash Latency: 2 WS
SysTick: 1ms(默认)
这套时钟能保证 USART 波特率精准、I2C稳定。
0x02) GPIO(Pinout & Configuration)
无需手动给 I2C/USART 分配模式,CubeMX 勾外设会自动配置:
-
USART1_TX → PA9(AF Push-Pull)
-
USART1_RX → PA10(Input Floating)
-
I2C1_SCL → PB6(AF Open-Drain)
-
I2C1_SDA → PB7(AF Open-Drain)
如果你暂时没有外部上拉,见第 6 节“临时内部上拉”。
0x03) I2C1 配置(Peripherals → I2C1)
Mode: I2C
Timing/Speed: Standard Mode (100 kHz)(跑通后再改 400 kHz)
Own Address1: 0
Addressing: 7-bit
Stretch Mode: Enabled(默认)
NVIC: 都不勾(用阻塞/简单 HAL 调用即可;你的 SSD1306 驱动都是阻塞发)
你的代码用 HAL_I2C_Master_Transmit(&hi2c1, 0x78, ...),这是8位地址(0x3C<<1)。保持即可。
0x04) USART1 配置(Peripherals → USART1)
Mode: Asynchronous
Baud Rate: 115200
Word Length: 8 Bits
Parity: None
Stop Bits: 1
OverSampling: 16
NVIC:勾选 USART1 global interrupt(开启中断)
DMA:暂不启用(先中断版稳定后再上 DMA)
生成后在 main() 里调用一次HAL_UART_Receive_IT(&huart1, &rx_byte, 1);开始中断收。
0x06) (可选)“临时内部上拉”方案
不建议长期使用,仅在你手上没电阻、线很短(<5cm)、速度 100 kHz 时凑合测通电:
在 MX_I2C1_Init() 之前,手动给 PB6/PB7 加上拉(F1 的 I2C 引脚内部上拉弱):
在 MX_I2C1_Init() 之前,手动给 PB6/PB7 加上拉(F1 的 I2C 引脚内部上拉弱):
// 在 MX_I2C1_Init(); 调用之前插入:
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
GPIO_InitStruct.Pull = GPIO_PULLUP; // 临时内部上拉(弱)
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
若出现 NACK/卡死/乱码,立刻改用外部 4.7k–10kΩ上拉。
点击查看代码
#include "main.h"
#include "gpio.h"
#include "i2c.h"
#include "usart.h"
#include "oled.h" // 你提供的
#include <string.h>
#include <ctype.h>
#define OLED_COLS 16 // 128/8 = 16 列(你的 8x16 字库)
#define OLED_LINES 4 // 64/16 = 4 行
// ====== 串口中断接收相关 ======
static uint8_t rx_byte; // 单字节中断接收
static char line_buf[64]; // 输入缓冲(足够放一行及回车)
static uint8_t line_len = 0;
// 保持 4 行窗口滚动显示
static char scr[OLED_LINES][OLED_COLS+1]; // 每行16列 + '\0'
static void SCR_ClearAll(void) {
for (int r=0; r<OLED_LINES; ++r) {
memset(scr[r], ' ', OLED_COLS);
scr[r][OLED_COLS] = '\0';
}
}
static void SCR_Render(void) {
// 将 scr[0..3] 渲染到 OLED 第 1..4 行
for (int r=0; r<OLED_LINES; ++r) {
OLED_ShowString(r+1, 1, scr[r]);
}
}
// 将一段字符串分块(<=16列)写入屏幕,自动滚动
static void SCR_PushText(const char* s) {
size_t n = strlen(s);
size_t idx = 0;
while (idx < n) {
char line[OLED_COLS+1];
size_t chunk = (n - idx > OLED_COLS) ? OLED_COLS : (n - idx);
memcpy(line, s + idx, chunk);
// 填空格到16列
if (chunk < OLED_COLS) memset(line + chunk, ' ', OLED_COLS - chunk);
line[OLED_COLS] = '\0';
// 滚动:0<-1<-2<-3,最后一行写新内容
for (int r=0; r<OLED_LINES-1; ++r) {
memcpy(scr[r], scr[r+1], OLED_COLS+1);
}
memcpy(scr[OLED_LINES-1], line, OLED_COLS+1);
idx += chunk;
}
SCR_Render();
}
// 显示提示或系统消息(前缀 ">> ",便于区分)
static void SCR_PushSys(const char* s) {
char msg[64];
snprintf(msg, sizeof(msg), ">> %s", s);
SCR_PushText(msg);
}
// 在收到一行(以 \n 结束)时调用:把 line_buf 显示出来(忽略控制字符)
static void Handle_OneLine(const char* in) {
char cleaned[sizeof(line_buf)];
size_t w = 0;
for (size_t i=0; in[i] && w < sizeof(cleaned)-1; ++i) {
char c = in[i];
if (c == '\r' || c == '\n') continue;
// 仅显示可打印 ASCII(0x20~0x7E)
if (isprint((unsigned char)c)) cleaned[w++] = c;
}
cleaned[w] = '\0';
if (w == 0) return;
SCR_PushText(cleaned);
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C1_Init();
MX_USART1_UART_Init();
// OLED 初始化 & 清屏
OLED_Init();
SCR_ClearAll();
OLED_Clear();
SCR_PushSys("UART@115200 8N1");
SCR_PushSys("Send text to show");
// 启动一次串口中断接收
HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
while (1) {
HAL_Delay(50);
}
}
// 串口中断回调:逐字接收,遇到 '\n' 视为一行
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1) {
uint8_t c = rx_byte;
// 回显(可选):把收到的字节发回 PC
HAL_UART_Transmit(&huart1, &c, 1, 10);
if (c == '\n') {
// 结束一行
line_buf[line_len] = '\0';
Handle_OneLine(line_buf);
line_len = 0;
} else if (c != '\r') {
// 累积到缓冲(防溢出)
if (line_len < sizeof(line_buf) - 1) {
line_buf[line_len++] = (char)c;
} else {
// 太长则直接做成一行显示并清空
line_buf[line_len] = '\0';
Handle_OneLine(line_buf);
line_len = 0;
}
}
// 继续挂下一次接收
HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
}
}





浙公网安备 33010602011771号