AI智能棋盘中基于WCH32F103的传感器采集与串口通信实践
在AI与物联网加速融合的当下,传统棋类设备正悄然经历一场智能化变革。无论是围棋、国际象棋还是五子棋,越来越多的教学系统开始引入“看得见”、“会思考”的智能棋盘——它们能自动识别落子位置,实时同步到手机App或云端AI引擎,甚至提供走法建议和复盘分析。
这类系统的底层逻辑看似简单:感知棋子 → 上传数据 → 分析决策。但真正实现时,挑战往往藏在细节里。尤其是如何以低成本、高可靠性的方式,在毫秒级时间内完成64个(8×8)乃至更多检测点的状态采集,并稳定传输给上位机?这正是嵌入式开发者必须面对的核心问题。
南京沁恒微电子推出的WCH32F103系列MCU,作为国产化替代STM32F103的优选方案,凭借其高性能Cortex-M3内核、丰富的外设资源以及极具竞争力的价格,在这一领域展现出显著优势。结合矩阵扫描技术和串口通信机制,它为构建高效、可调试的智能棋盘提供了坚实基础。
为什么选择WCH32F103?
先来看一组对比:
| 指标 | 51单片机 | Arduino UNO (ATmega328P) | WCH32F103 |
|---|---|---|---|
| 主频 | ≤24MHz | 16MHz | 72MHz |
| SRAM | <1KB | 2KB | 20KB |
| Flash | 4–8KB | 32KB | 64KB |
| USART接口数量 | 通常1个 | 1个 | 最多3个 |
| GPIO数量(LQFP48封装) | 极有限 | 约14个可用 | 多达37个通用IO |
从资源角度看,51和AVR平台在处理大规模传感器阵列时捉襟见肘:RAM不足难以缓存整盘状态,主频偏低导致扫描延迟明显,且缺乏DMA、定时器触发ADC等高级功能。而WCH32F103不仅完全兼容STM32标准库开发流程,还具备完整的APB总线架构,支持硬件USART、SPI、I2C、ADC、定时器中断等多种外设协同工作,非常适合需要多任务响应的场景。
更关键的是,其批量采购单价已低于2元人民币,远优于进口芯片的成本与供货稳定性,对消费级产品极具吸引力。
如何用16个IO管理64个传感器?
设想一个8×8的棋盘,每个交叉点下方都安装了一个传感器——如果采用独立连接方式,将消耗64个GPIO引脚,显然不现实。因此, 矩阵扫描法 成为主流解决方案。
以霍尔传感器配合磁性棋子为例,原理如下:
- 将所有传感器按行共地、列接输入的方式布线;
- MCU依次拉低每一行(Row),其余行保持高阻态;
- 在当前行激活期间,读取8位列(Col)的电平状态;
- 若某列为低,则表示该位置有棋子落下(磁场触发导通);
- 扫完8行即获得全盘状态,整个过程可在几毫秒内完成。
这种方式将64个检测点压缩至仅需16个IO(8行输出 + 8列输入),极大节省了MCU资源。对于更大尺寸如19×19的围棋盘,还可通过IO扩展芯片(如PCF8574)或级联移位寄存器进一步拓展。
当然,不同类型的传感器有不同的接入策略:
- 数字开关型(如薄膜按键、红外对管) :直接连接GPIO,配置为上拉输入,检测电平跳变;
- 模拟输出型(如SS49E线性霍尔) :需使用ADC采样电压值,设定阈值判断是否有棋子;
- 电容式触摸传感器 :可通过TTP223等专用IC或STM32自带的电容感应功能实现。
无论哪种类型,核心思想一致: 周期性扫描 + 差分检测 + 软件滤波 。
实战代码解析:从硬件初始化到数据上报
下面是一段典型的WCH32F103驱动8×8传感器阵列并通过串口上报变化的C语言实现。虽然基于标准库编写,但逻辑清晰,易于移植。
#include "stm32f10x.h"
// 行输出端口(PA0~PA7),列输入端口(PB8~PB15)
#define ROW_PORT GPIOA
#define COL_PORT GPIOB
uint16_t row_pins[8] = {GPIO_Pin_0, GPIO_Pin_1, GPIO_Pin_2, GPIO_Pin_3,
GPIO_Pin_4, GPIO_Pin_5, GPIO_Pin_6, GPIO_Pin_7};
uint16_t col_pins[8] = {GPIO_Pin_8, GPIO_Pin_9, GPIO_Pin_10, GPIO_Pin_11,
GPIO_Pin_12, GPIO_Pin_13, GPIO_Pin_14, GPIO_Pin_15};
uint8_t chessboard[8][8]; // 当前状态
uint8_t last_chessboard[8][8]; // 上次状态,用于差分检测
void RCC_Configuration(void) {
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_USART1, ENABLE);
}
void GPIO_Configuration(void) {
GPIO_InitTypeDef GPIO_InitStructure;
// 配置行引脚为推挽输出
GPIO_InitStructure.GPIO_Pin = 0xFF;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(ROW_PORT, &GPIO_InitStructure);
// 配置列引脚为浮空输入
GPIO_InitStructure.GPIO_Pin = 0xFF00;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(COL_PORT, &GPIO_InitStructure);
// USART1 TX (PA9)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
这里完成了基本的时钟使能与GPIO配置。值得注意的是,列输入采用了
GPIO_Mode_IN_FLOATING
,适用于外部已有上拉电阻的情况;若无上拉,应改为
GPIO_Mode_IPU
内部上拉模式。
接下来是串口初始化:
void USART1_Configuration(void) {
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
USART_Cmd(USART1, ENABLE);
}
设置波特率为115200,满足大多数PC串口助手和蓝牙模块的接收要求。实际项目中可根据通信距离和干扰情况调整至9600或57600以提升稳定性。
发送函数采用轮询方式实现,简洁可靠:
void send_uart_char(uint8_t ch) {
while (!USART_GetFlagStatus(USART1, USART_FLAG_TXE));
USART_SendData(USART1, ch);
}
void send_position_change(int x, int y, int status) {
char buf[16];
sprintf((char*)buf, "X%02dY%02dS%d\r\n", x, y, status);
for (int i = 0; buf[i] != '\0'; i++) {
send_uart_char(buf[i]);
}
}
协议设计为文本格式
"X03Y05S1"
,表示第3行第5列放置了一枚棋子(S=1),空出则S=0。这种格式便于调试时用串口助手直接观察,也容易被Python脚本解析。
核心扫描逻辑如下:
void scan_chessboard(void) {
for (int r = 0; r < 8; r++) {
GPIO_ResetBits(ROW_PORT, 0xFF); // 先全部拉高
GPIO_SetBits(ROW_PORT, row_pins[r]); // 再拉低当前行
Delay_ms(1); // 给信号稳定时间
for (int c = 0; c < 8; c++) {
uint8_t current = !GPIO_ReadInputDataBit(COL_PORT, col_pins[c]);
uint8_t prev = last_chessboard[r][c];
chessboard[r][c] = current;
if (current != prev) {
send_position_change(r, c, current);
last_chessboard[r][c] = current;
}
}
}
}
每次只激活一行,逐列读取状态,并与上次结果比较。只有发生变化的位置才触发上报,大幅减少冗余通信量。
主循环中每50ms执行一次扫描:
int main(void) {
RCC_Configuration();
GPIO_Configuration();
USART1_Configuration();
memset(chessboard, 0, sizeof(chessboard));
memset(last_chessboard, 0, sizeof(last_chessboard));
while (1) {
scan_chessboard();
Delay_ms(50);
}
}
Delay_ms()
可通过SysTick定时器实现,避免阻塞中断服务。
提示:在更高要求的应用中,可改用定时器中断触发扫描任务,配合DMA+ADC实现模拟传感器的全自动采集,进一步释放CPU负载。
应对真实世界的挑战:去抖、抗干扰与功耗优化
理论很美好,现实却充满噪声。以下是在实际调试中常遇到的问题及应对策略:
✅ 误触发(震动/电磁干扰)
机械振动可能导致传感器短暂断开再闭合,造成“假落子”。解决方法是 软件去抖 :连续多次采样同一状态才认定有效。
例如:
if (current != stable_state[r][c]) {
debounce_count[r][c]++;
if (debounce_count[r][c] > 3) {
stable_state[r][c] = current;
trigger_event(r, c, current);
}
} else {
debounce_count[r][c] = 0;
}
✅ 多子同时移动
用户可能一次性摆多个棋子,或移动整片区域。此时若只上报单个坐标,信息就会丢失。改进方案是
完整帧比对
:扫描结束后遍历整个
chessboard
数组,批量发送所有差异点。
✅ 通信丢包
UART是单向异步通信,没有重传机制。一旦上位机重启或缓冲区溢出,就可能漏掉关键动作。建议增加轻量级确认机制,或改用带CRC校验的二进制协议(如
{0xAA, x, y, s, crc}
),提高鲁棒性。
✅ 功耗控制
对于电池供电的便携式棋盘,长时间运行会快速耗尽电量。可行方案包括:
- 使用低功耗模式(Stop Mode)+ EXTI外部中断唤醒;
- 仅在检测到操作时启动高频扫描,平时降频至1Hz以下;
- 关闭未使用的外设时钟,降低整体电流至μA级别。
系统架构与未来演进方向
典型的AI智能棋盘系统由四层构成:
[传感器阵列]
↓ (GPIO/ADC)
[WCH32F103 MCU] ←→ [调试串口 UART]
↓ (UART/I2C)
[通信模块:ESP-01S / HC-05 / nRF24L01]
↓
[上位机AI引擎 或 移动App]
前端负责感知,MCU进行预处理,无线模块负责联网,后端执行AI推理。目前多数方案依赖云端计算,但随着TinyML技术发展,未来有望在WCH32F103这类MCU上部署轻量化神经网络模型(如TensorFlow Lite Micro),实现本地化走法推荐,进一步降低延迟与网络依赖。
此外,多模态融合也是趋势之一。例如结合摄像头视觉识别与触觉传感,既能验证物理落子,又能捕捉手势意图,提升交互体验。
结语
WCH32F103以其出色的性价比和生态兼容性,正在成为国产智能硬件开发的重要基石。在AI棋盘这类强调实时性、稳定性与成本控制的应用中,它通过高效的矩阵扫描与串口通信机制,实现了对大量传感器数据的精准采集与可靠传输。
更重要的是,这种设计思路具有高度可复用性——无论是电子琴键、智能家居面板,还是工业按钮阵列,都可以借鉴相同的架构。当你下次面对“如何用最少资源监控最多节点”的问题时,不妨回想一下这个小小的棋盘背后所蕴含的工程智慧。
毕竟,真正的智能,不只是算法有多深,更是底层感知有多稳。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

被折叠的 条评论
为什么被折叠?



