
STM32G431CBU6
简介
一块STM32G431CBU6开发板,板载MPU6050,AHT20传感器,一个用户按键,一个用户LED,一个大头拨码开关。
简介:一块STM32G431CBU6开发板,板载MPU6050,AHT20传感器,一个用户按键,一个用户LED,一个大头拨码开关。开源协议
:MIT License
(未经作者授权,禁止转载)描述
项目简介
某天逛X宝的时候,忽然发现STM32G431CBU6这个芯片不到5¥(1/10 KFC)就可以拿下!加之还是UFQFPN封装,之前虽然画了很多板子,焊接了很多,但从未试过这个封装,本着想要“精进”一下焊接技术的念头,故“斥巨资”直接拿下!

复刻成本
4.65¥(主控)+7.40¥(屏幕)+0.8¥(拨码开关)+ 2.63¥(MPU6050)+1.5¥(ATH20)+0.3¥(TLV62569DBVR)=17.28¥+10¥(阻容连接件)=27.28¥,粗略估计30¥即可复刻这块开发板。
由于库存还有一部分模块处于闲置状态,故打算稍微整合一下,做一个稍微“进阶”的学习开发板。
项目功能
暂时就只是将AHT20和MPU6050传感器的读取到的外界参数显示到ST7789-1.47 inch显示屏上。
提供一些思路以供继续扩展当前项目。
- 结合大头开关和显示模块,做一个UI项目,当然了,肯定会涉及到显示板载这两个传感器的读取情况。
- 单纯继续调优校准
MPU6050,榨干板子和传感器的性能,继续验证RUST嵌入式的潜力(当然了,你用C/C++开发也可以。)。
当前完成的效果。

- 实时读取
AHT20温湿度 +MPU6050姿态角,ST7789屏幕分区显示,温度超 30°C 自动点亮 PC13 LED (每一秒更新一次状态,全屏刷新,这里也可以继续优化)。 - 项目使用
Rust+Embassy编写,得益于crates.io上大佬们贡献的crate,完成所有测试(USB偷懒没测)仅用了差不多十分钟。以下是已经测试无误的模块。- ST7789
- MPU6050
- AHT20
- LED
项目参数
- ATH20读取温湿度。
- MPU6050读取温湿度、Pitch、Roll。(随意找的一个库,没有Yaw,这里之后还可以继续优化)。
- ST7789 1.47英寸屏幕显示传感器值,PC13引脚作为用户LED灯,会在AHT20的温度超过阈值(代码里设置的30°)时点亮。
原理解析(硬件说明)
整个项目很简单,完全是从初学者的角度来设计的原理图,和绘制的PCB。
- 电源部分
- 采用
TLV62569DBVR,将5V转为3V3,一般来说用个LDO(经典的AMS1117和RT9013都可以),但是库存只有TLV62569DBVR也就没有更换(还要重新买,伤不起哇:)),电路部分的Layout完全按照数据手册进行。
- USB部分
- 这部分很简单,就只加了一个
BST236A054U作为防反接,按理来说应该在差分线上再串两个22R的电阻,但是,一切从简(又不是不能用,又不是造飞机大炮)。两个5K1的电路下拉CC引脚。 - Type-C用的是1.6mm 16pin沉板的型号,可能比普通的16pin稍微贵一点,但是这是库存,价格敏感的话,改一下原理图里的型号,重新Layout一下就行。(我还是比较推荐就用沉板的,问就是方便焊接!)
- 复位电路
- 很经典的套公式电路,RC的参数也是直接套用即可,没什么好讲的,唯一要注意的是,这个按键是6x6x4.5的,按起来不费劲,太小了我按起来费劲。(自己学习用就迁就自己吧,怎么舒服怎么来:))。
- 晶振电路
- 低速部分用作
RTC - 高速部分代替内置的晶振,提供更稳定的时钟源,减少误差,提高稳定性。
- MPU6050
- 照着数据手册抄就行,但是代码里并没有用到
INT,后续想要进阶的话这里做了兼容。
- LCD
- 直接找X宝的客服,问客服要一份设计参考就行,这里用的8pin
FPC座子连接(我是真的不想焊这个座子,对我来说太难了,好在最后完美焊接上了,成功显示)。
- AHT20
- 通信引脚记得用4K7电阻上拉即可。
- 调试接口
- 额外用一个4pin弯排针引出,就不用再去对着一排排引脚找了。
- 引脚引出
- 考虑对称和美观以及复用,最大程度上将不会产生干扰的引脚都引出了。(每种不足的是丝印部分,工程里我用了
反相,实际打出来的板子有些看不太清,字体太小了)
焊接完成效果
屏幕在右侧未进入镜头,方便展示板载元件。

软件代码
#![no_std]
#![no_main]
use defmt::{error, info};
use embassy_executor::Spawner;
use embassy_stm32::gpio::{Level, Output, Speed};
use embassy_stm32::i2c::{Config as I2cConfig, I2c};
use embassy_stm32::spi::{Config as SpiConfig, Spi};
use embassy_stm32::time::Hertz;
use embassy_time::{Delay, Timer};
use {defmt_rtt as _, panic_probe as _};
// Sensor drivers
use aht20_driver::AHT20;
use mpu6050::Mpu6050;
// Display drivers
use display_interface_spi::SPIInterface;
use embedded_graphics::{
mono_font::{MonoTextStyle, ascii::FONT_10X20},
pixelcolor::Rgb565,
prelude::*,
primitives::{Line, PrimitiveStyle, Rectangle},
text::Text,
};
use st7789::{BacklightState, Orientation, ST7789};
// For no_std string formatting
use core::fmt::Write;
use heapless::String;
use micromath::F32Ext;
const SCREEN_WIDTH: i32 = 320;
const SCREEN_HEIGHT: i32 = 172;
// 分区常量
const LEFT_X: i32 = 10;
const RIGHT_X: i32 = 170;
const DIVIDER_X: i32 = 160;
const START_Y: i32 = 60;
const LINE_HEIGHT: i32 = 30;
// ============ Helper Function: Format f32 to "X.XX" ============
fn format_f32_2dec(buffer: &mut String<32>, value: f32) {
let abs_val = value.abs();
let integer_part = abs_val as u32;
let mut decimal_part = ((abs_val - integer_part as f32) * 100.0 + 0.5).floor() as u32;
if decimal_part >= 100 {
decimal_part = 0;
}
let _ = if value < 0.0 {
write!(buffer, "-{}.{:02}", integer_part, decimal_part)
} else {
write!(buffer, "{}.{:02}", integer_part, decimal_part)
};
}
#[embassy_executor::main]
async fn main(_spawner: Spawner) -> ! {
let mut config = embassy_stm32::Config::default();
{
use embassy_stm32::rcc::*;
config.rcc.hse = Some(Hse {
freq: Hertz(24_000_000),
mode: HseMode::Oscillator,
});
config.rcc.pll = Some(Pll {
source: PllSource::HSE,
prediv: PllPreDiv::DIV6,
mul: PllMul::MUL85,
divp: None,
divq: Some(PllQDiv::DIV8),
divr: Some(PllRDiv::DIV2),
});
config.rcc.mux.fdcansel = mux::Fdcansel::PLL1_Q;
config.rcc.sys = Sysclk::PLL1_R;
}
let p = embassy_stm32::init(config);
// Initialize I2C for AHT20 sensor (I2C1: PA15 for SCL, PB7 for SDA)
let i2c_aht20 = I2c::new_blocking(p.I2C1, p.PA15, p.PB7, Hertz(400_000), I2cConfig::default());
// Initialize I2C for MPU6050 sensor (I2C3: PA8 for SCL, PC11 for SDA)
let i2c_mpu6050 =
I2c::new_blocking(p.I2C3, p.PA8, p.PC11, Hertz(400_000), I2cConfig::default());
// Initialize AHT20 sensor
let mut aht20_uninit = AHT20::new(i2c_aht20, aht20_driver::SENSOR_ADDRESS);
let mut delay = Delay;
let mut aht20 = aht20_uninit.init(&mut Delay).unwrap();
// Initialize MPU6050 sensor
let mut imu = Mpu6050::new(i2c_mpu6050);
match imu.init(&mut delay) {
Ok(_) => info!("IMU Initialized successfully!"),
Err(_) => info!("Bad things happened, check your wire connections!"),
}
// Configure SPI for display
let mut spi_config = SpiConfig::default();
spi_config.frequency = Hertz(20_000_000);
spi_config.mode = embassy_stm32::spi::Mode {
polarity: embassy_stm32::spi::Polarity::IdleHigh,
phase: embassy_stm32::spi::Phase::CaptureOnSecondTransition,
};
// SPI pins for display (SPI1)
let spi = Spi::new_txonly(p.SPI1, p.PA5, p.PA7, p.DMA2_CH2, spi_config);
// Display control pins
let cs = Output::new(p.PA4, Level::High, Speed::VeryHigh);
let dc = Output::new(p.PA6, Level::Low, Speed::VeryHigh);
let rst = Output::new(p.PC4, Level::High, Speed::VeryHigh);
let bl = Output::new(p.PB1, Level::Low, Speed::VeryHigh);
// Create display interface
let di = SPIInterface::new(spi, dc, cs);
// Create ST7789 display driver instance
let mut display = ST7789::new(
di,
Some(rst),
Some(bl),
SCREEN_WIDTH as u16,
SCREEN_HEIGHT as u16,
);
// Initialize PC13 as output for temperature alert LED
let mut led_alert = Output::new(p.PC13, Level::Low, Speed::VeryHigh);
// Initialize display
info!("Initializing ST7789...");
match display.init(&mut delay) {
Ok(_) => info!("Display initialized successfully"),
Err(_) => {
error!("Failed to initialize ST7789");
loop {}
}
}
// Set display orientation
if let Err(_) = display.set_orientation(Orientation::Landscape) {
error!("Failed to set display orientation");
} else {
info!("Display orientation set to landscape");
}
// Turn on backlight
if let Err(_) = display.set_backlight(BacklightState::On, &mut delay) {
error!("Failed to turn on backlight");
} else {
info!("Backlight turned on");
}
// Clear display
display.clear(Rgb565::BLACK).unwrap();
// ============ 样式定义 ============
let title_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE);
let label_style = MonoTextStyle::new(&FONT_10X20, Rgb565::CYAN); // 传感器名称用青色
let value_style = MonoTextStyle::new(&FONT_10X20, Rgb565::YELLOW); // 数值用黄色
// Draw title (居中)
Text::new("Sensor Data Display", Point::new(60, 20), title_style)
.draw(&mut display)
.unwrap();
loop {
// Read sensor data
let aht20_measurement = aht20.measure(&mut Delay).unwrap();
let temperature_aht20 = aht20_measurement.temperature;
let humidity_aht20 = aht20_measurement.humidity;
// >>>>>>>>>>>>>>>>>> 新增:温度超过28°C点亮PC13 <<<<<<<<<<<<<<<<<<
if temperature_aht20 > 28.0 {
led_alert.set_high();
} else {
led_alert.set_low();
}
let acc_angles = imu.get_acc_angles();
let temp_mpu6050 = imu.get_temp();
// === 清除数据区域 (Y=40 以下) ===
Rectangle::new(
Point::new(0, 40),
Size::new(SCREEN_WIDTH as u32, (SCREEN_HEIGHT - 40) as u32),
)
.into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK))
.draw(&mut display)
.unwrap();
// === 绘制分隔竖线 ===
Line::new(
Point::new(DIVIDER_X, 40),
Point::new(DIVIDER_X, SCREEN_HEIGHT),
)
.into_styled(PrimitiveStyle::with_stroke(Rgb565::BLUE, 1))
.draw(&mut display)
.unwrap();
// ============ LEFT SIDE: AHT20 ============
let mut y = START_Y;
Text::new("AHT20", Point::new(LEFT_X, y), label_style)
.draw(&mut display)
.unwrap();
y += LINE_HEIGHT;
{
let mut buffer: String<32> = String::new();
buffer.push_str("Temp: ").ok();
format_f32_2dec(&mut buffer, temperature_aht20);
buffer.push_str(" C").ok();
Text::new(&buffer, Point::new(LEFT_X, y), value_style)
.draw(&mut display)
.unwrap();
}
y += LINE_HEIGHT;
{
let mut buffer: String<32> = String::new();
buffer.push_str("Humi: ").ok();
format_f32_2dec(&mut buffer, humidity_aht20);
buffer.push_str(" %").ok();
Text::new(&buffer, Point::new(LEFT_X, y), value_style)
.draw(&mut display)
.unwrap();
}
// ============ RIGHT SIDE: MPU6050 ============
let mut y = START_Y;
Text::new("MPU6050", Point::new(RIGHT_X, y), label_style)
.draw(&mut display)
.unwrap();
y += LINE_HEIGHT;
match temp_mpu6050 {
Ok(temp) => {
let mut buffer: String<32> = String::new();
buffer.push_str("Temp: ").ok();
format_f32_2dec(&mut buffer, temp);
buffer.push_str(" C").ok();
Text::new(&buffer, Point::new(RIGHT_X, y), value_style)
.draw(&mut display)
.unwrap();
}
Err(_) => {
Text::new("Temp: Error", Point::new(RIGHT_X, y), value_style)
.draw(&mut display)
.unwrap();
}
}
y += LINE_HEIGHT;
match acc_angles {
Ok(v) => {
let pitch_deg = v[0].to_degrees();
let roll_deg = v[1].to_degrees();
{
let mut buffer: String<32> = String::new();
buffer.push_str("Pitch: ").ok();
format_f32_2dec(&mut buffer, pitch_deg);
buffer.push_str("deg").ok();
Text::new(&buffer, Point::new(RIGHT_X, y), value_style)
.draw(&mut display)
.unwrap();
}
y += LINE_HEIGHT;
{
let mut buffer: String<32> = String::new();
buffer.push_str("Roll: ").ok();
format_f32_2dec(&mut buffer, roll_deg);
buffer.push_str("deg").ok();
Text::new(&buffer, Point::new(RIGHT_X, y), value_style)
.draw(&mut display)
.unwrap();
}
}
Err(_) => {
Text::new("Angles: Error", Point::new(RIGHT_X, y), value_style)
.draw(&mut display)
.unwrap();
}
}
// Wait for 1 second before updating
Timer::after_millis(1000).await;
}
}
注意事项
测试代码使用RUST编写,C/C++用户还请自行进行测试。
组装流程
由于屏幕是FPC连接的,所以不想用的时候就可以拔下来,也方便多个板子之间借用,只是要注意,若直接放置接触到板子,若是用手按压屏幕,可能导致短路,重启等等。自然连接放置没有任何问题,你也可以考虑涂一层UV胶在板子上。
设计图
未生成预览图,请在编辑器重新保存一次BOM
暂无BOM
克隆工程知识产权声明&复刻说明
本项目为开源硬件项目,其相关的知识产权归创作者所有。创作者在本平台上传该硬件项目仅供平台用户用于学习交流及研究,不包括任何商业性使用,请勿用于商业售卖或其他盈利性的用途;如您认为本项目涉嫌侵犯了您的相关权益,请点击上方“侵权投诉”按钮,我们将按照嘉立创《侵权投诉与申诉规则》进行处理。
请在进行项目复刻时自行验证电路的可行性,并自行辨别该项目是否对您适用。您对复刻项目的任何后果负责,无论何种情况,本平台将不对您在复刻项目时,遇到的任何因开源项目电路设计问题所导致的直接、间接等损害负责。









