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);
    }
}

image
image
image
image

posted @ 2025-08-12 13:35  huh&uh  阅读(108)  评论(0)    收藏  举报