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號