MaixCAM + STM32 串口联调
页面定位
本页记录一次从“摄像头预览”到“视觉结果通过 UART 交给 STM32”的完整实验。阅读时按 先单板、再协议、后联调 的顺序进行;代码块保留现场版本,设备地址、引脚和文件路径属于本次实验事实,换硬件后必须重新核对。
实验目标与验收标准
| 阶段 | 要证明的事情 | 验收信号 |
|---|---|---|
| MaixCAM 上手 | 摄像头、屏幕和 MaixVision 正常 | 能看到实时预览,MaixVision 显示已连接 |
| 视觉识别 | 能输出目标中心坐标 | 识别时生成 T,cx,cy,pixels,未识别时生成 N |
| 串口物理链路 | TX/RX、电平和共地正确 | STM32 能收到任意完整帧 |
| 协议联调 | 接收端能稳定解析 | PA2 按帧翻转,日志中无乱码/丢帧 |
阅读导航
1. 项目背景与设备上手

这块 MaixCAM 是 2026-03-28 从嵌入式实验室拿到的板子。
本次联调的目标是:
- 在 MaixCAM 上完成颜色识别。
- 通过串口把识别结果发给 STM32。
- 在 STM32 侧接收并解析数据。
- 用
PA2调试灯验证是否收到了完整串口帧。
MaixCAM 初始上手流程
按官方推荐路线,先走了一遍设备基础流程:
- 插好 TF 卡,开机进入系统。
- 连接 Wi-Fi。
- 安装运行库。
- 运行内置应用,确认系统、屏幕、摄像头正常。
- 用 MaixVision 连接设备并在线运行 MaixPy。
联网上报的信息如下:
- 设备 IP:
192.168.0.48 - MaixVision 已成功连接
这里踩过一个点:
- 不是只要设备联网就行,还需要在 MaixVision 左下角主动点击连接。
2. 视觉程序从预览到识别
2.1 最小摄像头预览
先跑最小预览,确认:
- 摄像头正常
- 显示链路正常
- MaixVision 在线运行正常
最小代码:
from maix import camera, display
cam = camera.Camera(640, 480)
disp = display.Display()
while True:
img = cam.read()
disp.show(img)
2.2 颜色识别入门
后续做了颜色块识别,核心思路是:
find_blobs()找色块- 取最大色块
- 输出
cx、cy、pixels
调试过程中做过这些优化:
cam.skip_frames(30)- 使用
roi - 限制
pixels_threshold - 限制
area_threshold - 只保留最大色块
3. 串口协议、引脚与接线
最终 STM32 侧兼容两种有效数据:
T,cx,cy,pixels\ncx,cy,pixels\n
未检测到目标时发送:
N\n
3.1 本次硬件特殊情况
原来的默认串口脚状态不理想,后面改为使用 MaixCAM 的:
A19 -> UART1_TXA18 -> UART1_RX
对应串口设备:
/dev/ttyS1
波特率:
115200
3.2 最终接线

最终验证通过的接线是:
- MaixCAM
A19(TX)-> STM32PA10(USART1_RX) - MaixCAM
A18(RX)-> STM32PA9(USART1_TX) - MaixCAM
GND-> STM32GND
其中最重要的是:
TX和RX交叉连接- 必须共地
在只验证单向接收时,最少可以只接:
- MaixCAM
A19(TX)-> STM32PA10(RX) GND->GND
4. STM32 配置与最小验证
STM32 侧最终使用:
USART11152008N1- 中断接收
PA2作为调试灯
说明:
- 这次没有用
PC13。 - 当前调试灯实际使用的是
GPIOA + GPIO_PIN_2。
4.1 为什么后来能跑通

真正起作用的关键不是某一行“魔法代码”,而是把问题拆开了。
最关键的三步是:
1. STM32 先真正进入 USART1 中断接收
2. Maix 用 uart_heartbeat.py 固定发送串口心跳
3. STM32 收到任意完整帧就用 PA2 翻灯
一旦这三步跑通,就说明:
- 接线没问题
- 波特率没问题
- 协议没问题
- 中断链路没问题
后面如果 Maix 一直发送 N,那说明:
- 通信是通的
- 只是当前视觉没有识别到目标
4.2 当前涉及的完整代码文件
本次真正联调用到的完整代码文件是:
maixcam/Core/Src/main.cmaixcam/Core/Src/stm32f1xx_it.cmaixcam/Core/Src/stm32f1xx_hal_msp.cmaixvision_scripts/start_uart2.pymaixvision_scripts/uart_heartbeat.py
5. 完整代码与后续改进
下面保留本次联调涉及的原始代码。main.c 在原始记录中只有文件名,没有贴出正文,因此不伪造内容;其余代码按文件分块展示。
5.1 maixcam/Core/Src/stm32f1xx_it.c
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file stm32f1xx_it.c
* @brief Interrupt Service Routines.
******************************************************************************
* @attention
*
* Copyright (c) 2026 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f1xx_it.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN TD */
/* USER CODE END TD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/* External variables --------------------------------------------------------*/
extern UART_HandleTypeDef huart1;
/* USER CODE BEGIN EV */
/* USER CODE END EV */
/******************************************************************************/
/* Cortex-M3 Processor Interruption and Exception Handlers */
/******************************************************************************/
/**
* @brief This function handles Non maskable interrupt.
*/
void NMI_Handler(void)
{
/* USER CODE BEGIN NonMaskableInt_IRQn 0 */
/* USER CODE END NonMaskableInt_IRQn 0 */
/* USER CODE BEGIN NonMaskableInt_IRQn 1 */
while (1)
{
}
/* USER CODE END NonMaskableInt_IRQn 1 */
}
/**
* @brief This function handles Hard fault interrupt.
*/
void HardFault_Handler(void)
{
/* USER CODE BEGIN HardFault_IRQn 0 */
/* USER CODE END HardFault_IRQn 0 */
while (1)
{
/* USER CODE BEGIN W1_HardFault_IRQn 0 */
/* USER CODE END W1_HardFault_IRQn 0 */
}
}
/**
* @brief This function handles Memory management fault.
*/
void MemManage_Handler(void)
{
/* USER CODE BEGIN MemoryManagement_IRQn 0 */
/* USER CODE END MemoryManagement_IRQn 0 */
while (1)
{
/* USER CODE BEGIN W1_MemoryManagement_IRQn 0 */
/* USER CODE END W1_MemoryManagement_IRQn 0 */
}
}
/**
* @brief This function handles Prefetch fault, memory access fault.
*/
void BusFault_Handler(void)
{
/* USER CODE BEGIN BusFault_IRQn 0 */
/* USER CODE END BusFault_IRQn 0 */
while (1)
{
/* USER CODE BEGIN W1_BusFault_IRQn 0 */
/* USER CODE END W1_BusFault_IRQn 0 */
}
}
/**
* @brief This function handles Undefined instruction or illegal state.
*/
void UsageFault_Handler(void)
{
/* USER CODE BEGIN UsageFault_IRQn 0 */
/* USER CODE END UsageFault_IRQn 0 */
while (1)
{
/* USER CODE BEGIN W1_UsageFault_IRQn 0 */
/* USER CODE END W1_UsageFault_IRQn 0 */
}
}
/**
* @brief This function handles System service call via SWI instruction.
*/
void SVC_Handler(void)
{
/* USER CODE BEGIN SVCall_IRQn 0 */
/* USER CODE END SVCall_IRQn 0 */
/* USER CODE BEGIN SVCall_IRQn 1 */
/* USER CODE END SVCall_IRQn 1 */
}
/**
* @brief This function handles Debug monitor.
*/
void DebugMon_Handler(void)
{
/* USER CODE BEGIN DebugMonitor_IRQn 0 */
/* USER CODE END DebugMonitor_IRQn 0 */
/* USER CODE BEGIN DebugMonitor_IRQn 1 */
/* USER CODE END DebugMonitor_IRQn 1 */
}
/**
* @brief This function handles Pendable request for system service.
*/
void PendSV_Handler(void)
{
/* USER CODE BEGIN PendSV_IRQn 0 */
/* USER CODE END PendSV_IRQn 0 */
/* USER CODE BEGIN PendSV_IRQn 1 */
/* USER CODE END PendSV_IRQn 1 */
}
/**
* @brief This function handles System tick timer.
*/
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
/* USER CODE END SysTick_IRQn 0 */
HAL_IncTick();
/* USER CODE BEGIN SysTick_IRQn 1 */
/* USER CODE END SysTick_IRQn 1 */
}
/******************************************************************************/
/* STM32F1xx Peripheral Interrupt Handlers */
/* Add here the Interrupt Handlers for the used peripherals. */
/* For the available peripheral interrupt handler names, */
/* please refer to the startup file (startup_stm32f1xx.s). */
/******************************************************************************/
/**
* @brief This function handles USART1 global interrupt.
*/
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
5.2 maixcam/Core/Src/stm32f1xx_hal_msp.c
/* USER CODE BEGIN Header */
/**
******************************************************************************
*@filestm32f1xx_hal_msp.c
* @brief This file provides code for the MSP Initialization
* and de-Initialization codes.
******************************************************************************
* @attention
*
* Copyright (c) 2026 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN TD */
/* USER CODE END TD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN Define */
/* USER CODE END Define */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN Macro */
/* USER CODE END Macro */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* External functions --------------------------------------------------------*/
/* USER CODE BEGIN ExternalFunctions */
/* USER CODE END ExternalFunctions */
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* Initializes the Global MSP.
*/
void HAL_MspInit(void)
{
/* USER CODE BEGIN MspInit 0 */
/* USER CODE END MspInit 0 */
__HAL_RCC_AFIO_CLK_ENABLE();
__HAL_RCC_PWR_CLK_ENABLE();
/* System interrupt init*/
/** NOJTAG: JTAG-DP Disabled and SW-DP Enabled
*/
__HAL_AFIO_REMAP_SWJ_NOJTAG();
/* USER CODE BEGIN MspInit 1 */
/* USER CODE END MspInit 1 */
}
/**
* @brief UART MSP Initialization
* This function configures the hardware resources used in this example
* @param huart: UART handle pointer
* @retval None
*/
void HAL_UART_MspInit(UART_HandleTypeDef* huart)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(huart->Instance==USART1)
{
/* USER CODE BEGIN USART1_MspInit 0 */
/* USER CODE END USART1_MspInit 0 */
/* Peripheral clock enable */
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
/* USER CODE BEGIN USART1_MspInit 1 */
/* USER CODE END USART1_MspInit 1 */
}
}
/**
* @brief UART MSP De-Initialization
* This function freeze the hardware resources used in this example
* @param huart: UART handle pointer
* @retval None
*/
void HAL_UART_MspDeInit(UART_HandleTypeDef* huart)
{
if(huart->Instance==USART1)
{
/* USER CODE BEGIN USART1_MspDeInit 0 */
/* USER CODE END USART1_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_USART1_CLK_DISABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10);
HAL_NVIC_DisableIRQ(USART1_IRQn);
/* USER CODE BEGIN USART1_MspDeInit 1 */
/* USER CODE END USART1_MspDeInit 1 */
}
}
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
5.3 maixvision_scripts/start_uart2.py
from maix import image, camera, display, uart, pinmap, err
cam = camera.Camera(320, 240)
cam.skip_frames(30)
disp = display.Display()
err.check_raise(pinmap.set_pin_function("A19", "UART1_TX"), "set A19 failed")
err.check_raise(pinmap.set_pin_function("A18", "UART1_RX"), "set A18 failed")
serial = uart.UART("/dev/ttyS1", 115200)
print("UART ready: /dev/ttyS1, TX=A19, RX=A18, baud=115200")
thresholds = [0, 80, 40, 80, 10, 80]
send_every_n_frames = 3
frame_counter = 0
last_sent_text = ""
last_logged_text = ""
while True:
img = cam.read()
status_text = "N"
blobs = img.find_blobs(
thresholds,
pixels_threshold=50,
area_threshold=50,
merge=True,
margin=10
)
if blobs:
blob = max(blobs, key=lambda b: b[4])
x, y, w, h = blob[0], blob[1], blob[2], blob[3]
pixels = blob[4]
cx = x + w // 2
cy = y + h // 2
img.draw_rect(x, y, w, h, image.COLOR_GREEN)
img.draw_circle(cx, cy, 4, image.COLOR_RED)
img.draw_string(x, y, f"({cx},{cy})", image.COLOR_RED)
status_text = f"T,{cx},{cy},{pixels}"
frame_counter += 1
if status_text != last_sent_text or frame_counter >= send_every_n_frames:
serial.write_str(status_text + "\n")
frame_counter = 0
last_sent_text = status_text
if status_text != last_logged_text:
print("send:", status_text)
last_logged_text = status_text
img.draw_string(4, 4, last_sent_text if last_sent_text else status_text, image.COLOR_YELLOW)
disp.show(img)
5.4 maixvision_scripts/uart_heartbeat.py
from maix import uart, pinmap, err, time
err.check_raise(pinmap.set_pin_function("A19", "UART1_TX"), "set A19 failed")
err.check_raise(pinmap.set_pin_function("A18", "UART1_RX"), "set A18 failed")
serial = uart.UART("/dev/ttyS1", 115200)
print("UART heartbeat ready: /dev/ttyS1, TX=A19, RX=A18, baud=115200")
count = 0
while True:
msg = f"T,160,120,{count}\n"
serial.write_str(msg)
print("send:", msg.strip())
count += 1
time.sleep_ms(500)
5.5 当前结论
当前已经确认:
- MaixCAM 与 STM32 串口链路已经打通。
- 最终可用的 Maix 串口是
A19/A18 + /dev/ttyS1。 - STM32 最终可用的是
USART1 + PA10/PA9 + 115200 8N1。 PA2可以作为调试灯,用来判断是否收到了完整帧。uart_heartbeat.py用于最小通信验证。start_uart2.py用于视觉识别 + 串口发送。
5.6 后续建议
后面如果继续推进,可以按这个顺序:
- 继续调整
thresholds,让视觉识别更稳定。 - 让
start_uart2.py更稳定输出T,cx,cy,pixels。 - 在 STM32 里补上真正的“左转 / 右转 / 直走”控制逻辑。
- 如果
PA2闪得太快,可以把 STM32 改成: 只在target_found = 1时翻灯,或者每若干帧再翻一次。