MaixCAM + STM32 串口联调

info
页面定位

本页记录一次从“摄像头预览”到“视觉结果通过 UART 交给 STM32”的完整实验。阅读时按 先单板、再协议、后联调 的顺序进行;代码块保留现场版本,设备地址、引脚和文件路径属于本次实验事实,换硬件后必须重新核对。

实验目标与验收标准

阶段 要证明的事情 验收信号
MaixCAM 上手 摄像头、屏幕和 MaixVision 正常 能看到实时预览,MaixVision 显示已连接
视觉识别 能输出目标中心坐标 识别时生成 T,cx,cy,pixels,未识别时生成 N
串口物理链路 TX/RX、电平和共地正确 STM32 能收到任意完整帧
协议联调 接收端能稳定解析 PA2 按帧翻转,日志中无乱码/丢帧

阅读导航

  1. 项目背景与设备上手
  2. 视觉程序从预览到识别
  3. 串口协议、引脚与接线
  4. STM32 配置与最小验证
  5. 完整代码与后续改进

1. 项目背景与设备上手

image 9.png

这块 MaixCAM 是 2026-03-28 从嵌入式实验室拿到的板子。

本次联调的目标是:

  1. 在 MaixCAM 上完成颜色识别。
  2. 通过串口把识别结果发给 STM32。
  3. 在 STM32 侧接收并解析数据。
  4. 用 PA2 调试灯验证是否收到了完整串口帧。

MaixCAM 初始上手流程

按官方推荐路线,先走了一遍设备基础流程:

  1. 插好 TF 卡,开机进入系统。
  2. 连接 Wi-Fi。
  3. 安装运行库。
  4. 运行内置应用,确认系统、屏幕、摄像头正常。
  5. 用 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\n
  • cx,cy,pixels\n

未检测到目标时发送:

  • N\n

3.1 本次硬件特殊情况

原来的默认串口脚状态不理想,后面改为使用 MaixCAM 的:

  • A19 -> UART1_TX
  • A18 -> UART1_RX

对应串口设备:

  • /dev/ttyS1

波特率:

  • 115200

3.2 最终接线

image 10.png

最终验证通过的接线是:

  • MaixCAM A19(TX) -> STM32 PA10(USART1_RX)
  • MaixCAM A18(RX) -> STM32 PA9(USART1_TX)
  • MaixCAM GND -> STM32 GND

其中最重要的是:

  • TX 和 RX 交叉连接
  • 必须共地

在只验证单向接收时,最少可以只接:

  • MaixCAM A19(TX) -> STM32 PA10(RX)
  • GND -> GND

4. STM32 配置与最小验证

STM32 侧最终使用:

  • USART1
  • 115200
  • 8N1
  • 中断接收
  • PA2 作为调试灯

说明:

  • 这次没有用 PC13。
  • 当前调试灯实际使用的是 GPIOA + GPIO_PIN_2。

4.1 为什么后来能跑通

image 11.png

真正起作用的关键不是某一行“魔法代码”,而是把问题拆开了。

最关键的三步是:

1. STM32 先真正进入 USART1 中断接收
2. Maix 用 uart_heartbeat.py 固定发送串口心跳
3. STM32 收到任意完整帧就用 PA2 翻灯

一旦这三步跑通,就说明:

  • 接线没问题
  • 波特率没问题
  • 协议没问题
  • 中断链路没问题

后面如果 Maix 一直发送 N,那说明:

  • 通信是通的
  • 只是当前视觉没有识别到目标

4.2 当前涉及的完整代码文件

本次真正联调用到的完整代码文件是:

  • maixcam/Core/Src/main.c
  • maixcam/Core/Src/stm32f1xx_it.c
  • maixcam/Core/Src/stm32f1xx_hal_msp.c
  • maixvision_scripts/start_uart2.py
  • maixvision_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 当前结论

当前已经确认:

  1. MaixCAM 与 STM32 串口链路已经打通。
  2. 最终可用的 Maix 串口是 A19/A18 + /dev/ttyS1。
  3. STM32 最终可用的是 USART1 + PA10/PA9 + 115200 8N1。
  4. PA2 可以作为调试灯,用来判断是否收到了完整帧。
  5. uart_heartbeat.py 用于最小通信验证。
  6. start_uart2.py 用于视觉识别 + 串口发送。

5.6 后续建议

后面如果继续推进,可以按这个顺序:

  1. 继续调整 thresholds,让视觉识别更稳定。
  2. 让 start_uart2.py 更稳定输出 T,cx,cy,pixels。
  3. 在 STM32 里补上真正的“左转 / 右转 / 直走”控制逻辑。
  4. 如果 PA2 闪得太快,可以把 STM32 改成: 只在 target_found = 1 时翻灯,或者每若干帧再翻一次。