通义千问1.5-1.8B-Chat-GPTQ-Int4开发指南使用STM32CubeMX配置外设并生成模型交互代码框架想让你的STM32单片机也能和AI大模型“对话”吗听起来很酷但一想到要在单片机上跑动辄几十亿参数的模型是不是觉得头都大了别担心我们今天不聊在单片机上直接部署模型那确实不现实。我们要做的是更巧妙、更实用的“端侧云侧”协同。简单来说就是让STM32这个“小个子”负责它最擅长的活——采集传感器数据、控制执行器然后通过串口、Wi-Fi等通信方式把数据发给运行在PC或服务器上的“大个子”AI模型比如通义千问。模型分析完数据后再把决策指令发回给STM32去执行。这样STM32就获得了AI的“大脑”能做出更智能的响应。今天这篇教程我就手把手带你走通这个流程。我们会用STM32开发中最常用的图形化配置工具STM32CubeMX快速搭建硬件通信的桥梁并编写一个简洁的通信协议框架让你的STM32项目轻松接入AI能力。1. 教程目标与准备工作在开始动手之前我们先明确一下这个教程能帮你实现什么以及需要准备哪些东西。学习目标 通过本教程你将学会使用STM32CubeMX图形化配置USART串口和I2C等关键通信外设。生成一个基础的STM32工程代码框架。编写一个简单的、用于STM32与上位机运行AI模型的PC之间通信的数据协议。理解如何将传感器数据打包发送给AI模型并解析模型返回的指令。你需要准备的环境硬件一块STM32开发板如STM32F103C8T6“蓝桥杯”板、STM32F407 Discovery等USB数据线以及你可能用到的传感器如温湿度传感器DHT11/22光照传感器等本教程以DHT11为例。软件STM32CubeMX用于配置MCU和生成代码。IDEKeil MDK、IAR或STM32CubeIDE用于编写和编译代码。串口调试助手如SecureCRT、Putty或MobaXterm用于监视串口数据。Python环境PC端用于运行通义千问模型并处理串口通信。需要安装pyserial库。整体思路 我们的项目流程是这样的STM32定时读取传感器数据 - 通过串口发送给PC - PC上的Python程序接收数据并调用通义千问模型 - 模型生成自然语言或结构化指令 - Python程序解析指令并通过串口发回STM32 - STM32执行相应操作如控制LED、电机等。2. 使用STM32CubeMX配置工程与外设STM32CubeMX是ST官方推出的神器能极大简化初始化工作。我们用它来配置时钟、调试接口以及最重要的通信外设。2.1 创建新工程与芯片选型首先打开STM32CubeMX点击“New Project”。在芯片选择器里输入你的开发板主控型号例如STM32F103C8。在右侧的筛选结果中双击选中对应的芯片。2.2 配置系统核心与时钟引脚分配视图在图形化界面中间你会看到芯片的引脚图。配置调试接口必须为了能下载和调试程序需要启用调试接口。在左侧“System Core”分类下找到“SYS”。在右侧“Debug”下拉菜单中根据你的调试器选择例如使用ST-LINK就选择“Serial Wire”。这会自动占用PA13和PA14引脚。配置时钟源在“RCC”设置中将“High Speed Clock (HSE)”选择为“Crystal/Ceramic Resonator”如果你的板子有外部高速晶振的话。这为系统提供准确的时钟基准。配置时钟树点击上方“Clock Configuration”标签。这里可以图形化配置系统主频。对于STM32F103C8外部晶振通常为8MHz你可以通过配置PLL倍频将系统时钟SYSCLK设置为72MHz这是该芯片的常见最高频率。CubeMX会自动帮你计算和设置分频系数通常一键点击“HCLK”输入框并填入72即可。2.3 配置关键通信外设这是本教程的核心步骤我们将配置两个最常用的通信接口。配置USART1用于与PC通信在引脚图中找到PA9和PA10这是USART1的默认TX和RX引脚或者根据你的板子原理图找到串口引脚。点击PA9引脚在弹出的功能列表中选择“USART1_TX”。点击PA10引脚选择“USART1_RX”。在左侧“Connectivity”分类下找到并点击“USART1”。在右侧配置面板中“Mode”选择“Asynchronous”异步通信。“Baud Rate”设置为115200这是一个常用波特率确保与PC端软件一致。“Word Length”为8 Bits。“Parity”为None。“Stop Bits”为1。“Over Sampling”为16 Samples。别忘了开启中断可选但推荐在“NVIC Settings”标签页下勾选“USART1 global interrupt”。这样当收到数据时可以通过中断及时处理不占用主循环。配置I2C1用于连接传感器以DHT11为例的注意点重要说明DHT11是单总线协议并非标准的I2C设备。这里配置I2C是为了举例说明如何配置该外设。如果你使用真正的I2C设备如OLED屏幕、MPU6050陀螺仪请按此步骤。如果只用DHT11可以跳过I2C配置后续我们用GPIO模拟时序读取。假设我们使用I2C1其默认引脚是PB6(SCL) 和PB7(SDA)。在引脚图中点击它们并选择“I2C1_SCL”和“I2C1_SDA”。在左侧“Connectivity”下点击“I2C1”。在右侧配置面板中“Mode”选择“I2C”。其他参数如时钟速度可以保持默认或根据传感器手册调整。2.4 生成工程代码点击上方“Project Manager”标签。“Project”子标签下“Project Name”给你的工程起个名字例如AI_Edge_Device。“Project Location”选择存放路径。“Toolchain / IDE”选择你使用的IDE比如“MDK-ARM V5”Keil。“Code Generator”子标签下建议进行如下设置让生成的代码更清晰勾选“Generate peripheral initialization as a pair of ‘.c/.h’ files per peripheral”这会把每个外设的代码单独成文件结构清晰。勾选“Backup previously generated files when re-generating”避免覆盖你自己的代码。点击右上角的“GENERATE CODE”按钮。CubeMX会生成完整的工程文件并自动打开你选择的IDE如Keil。3. 编写传感器数据读取与串口发送代码现在我们进入Keil或其他IDE在CubeMX生成的代码框架上添加业务逻辑。3.1 在IDE中定位用户代码区CubeMX生成的代码非常规范它用特殊的注释块/* USER CODE BEGIN XXX */和/* USER CODE END XXX */来标记出用户可以安全添加代码的区域。重新通过CubeMX生成代码时这些区域内的内容会被保留。我们需要关注的主要是main.c文件。3.2 实现DHT11数据读取GPIO模拟时序由于DHT11不是I2C我们用一个GPIO引脚来模拟单总线协议。假设我们连接到了PA1引脚。首先在main.c文件开头的/* USER CODE BEGIN PV */区域定义变量和函数原型/* USER CODE BEGIN PV */ // 用于DHT11的变量 uint8_t DHT11_Data[5] {0}; // 存放温湿度等5个字节数据 float Temperature, Humidity; // 函数原型声明 void DHT11_Start(void); uint8_t DHT11_ReadByte(void); uint8_t DHT11_ReadData(void); /* USER CODE END PV */然后在/* USER CODE BEGIN 4 */区域实现DHT11的驱动函数。这里是一个简化的示例实际应用需要考虑超时和校验/* USER CODE BEGIN 4 */ // 发送开始信号 void DHT11_Start(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; // 配置PA1为输出模式 GPIO_InitStruct.Pin GPIO_PIN_1; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET); // 拉低至少18ms HAL_Delay(20); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET); // 拉高20-40us HAL_Delay_us(30); // 切换为输入模式等待DHT11响应 GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); } // 读取一个字节 uint8_t DHT11_ReadByte(void) { uint8_t data 0; for(int i0; i8; i) { // 等待低电平结束表示数据位开始 while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) GPIO_PIN_RESET); HAL_Delay_us(40); // 延时40us后检测电平 if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) GPIO_PIN_SET) { data | (1 (7-i)); // 高电平表示‘1’ } // 等待高电平结束准备下一个数据位 while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) GPIO_PIN_SET); } return data; } // 读取完整的温湿度数据 uint8_t DHT11_ReadData(void) { DHT11_Start(); // 等待DHT11拉低响应 if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) GPIO_PIN_SET) return 1; HAL_Delay_us(80); if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) GPIO_PIN_RESET) return 2; HAL_Delay_us(80); // 读取5个字节数据 for(int i0; i5; i) { DHT11_Data[i] DHT11_ReadByte(); } // 简单校验前4个字节之和等于第5个字节 if(DHT11_Data[4] (DHT11_Data[0]DHT11_Data[1]DHT11_Data[2]DHT11_Data[3])) { Humidity DHT11_Data[0] DHT11_Data[1] / 10.0; Temperature DHT11_Data[2] DHT11_Data[3] / 10.0; return 0; // 成功 } return 3; // 校验失败 } /* USER CODE END 4 */3.3 实现串口数据发送函数我们需要一个函数将传感器数据格式化成字符串通过串口发送出去。在/* USER CODE BEGIN 4 */区域继续添加// 通过串口发送传感器数据 void Send_Sensor_Data(void) { char tx_buffer[128]; // 发送缓冲区 int len; if(DHT11_ReadData() 0) { // 读取成功 // 格式化数据为JSON-like字符串方便PC端解析 len sprintf(tx_buffer, {\temp\:%.1f, \humi\:%.1f}\r\n, Temperature, Humidity); HAL_UART_Transmit(huart1, (uint8_t*)tx_buffer, len, 1000); // 通过USART1发送超时1秒 } else { len sprintf(tx_buffer, {\error\:\DHT11 read failed\}\r\n); HAL_UART_Transmit(huart1, (uint8_t*)tx_buffer, len, 1000); } }3.4 在主循环中调用最后在main.c的/* USER CODE BEGIN WHILE */区域即主循环while (1)中定期调用发送函数/* USER CODE BEGIN WHILE */ while (1) { Send_Sensor_Data(); // 发送传感器数据 HAL_Delay(5000); // 每5秒发送一次可根据需要调整 /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */4. 设计STM32与AI模型的通信协议通信协议是双方对话的“语言”。我们需要定义一个简单、明确、易于解析的格式。4.1 协议设计原则简单性易于在资源有限的STM32上实现。可扩展性未来可以方便地增加新的传感器或指令类型。容错性能处理部分数据错误。4.2 定义数据格式我们采用类似JSON的键值对文本格式用换行符\r\n作为一帧数据的结束标志。这种格式对人类和机器都友好。STM32 - PC (上行数据) 主要发送传感器状态。例如{type:sensor, temp:25.5, humi:60.0, light:320}\r\nPC - STM32 (下行指令) PC上的AI模型分析数据后发送控制指令。例如{type:cmd, device:led, action:on, param:red}\r\n{type:cmd, device:motor, action:set_speed, param:80}\r\n4.3 STM32端的指令接收与解析STM32需要接收并解析来自PC的指令。我们使用串口中断来接收数据。首先在CubeMX中确保已使能USART1全局中断我们在2.3步已经做了。在main.c的/* USER CODE BEGIN PV */区域定义接收缓冲区#define RX_BUFFER_SIZE 128 uint8_t uart_rx_buffer[RX_BUFFER_SIZE]; uint16_t uart_rx_index 0;在/* USER CODE BEGIN 4 */区域重写串口接收中断回调函数// 串口接收中断回调函数 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart-Instance USART1) { uint8_t received_byte uart_rx_buffer[uart_rx_index]; // 实际上应从huart-pRxBuffPtr获取这里简化 // 简单处理如果收到换行符则认为一帧结束 if(received_byte \n uart_rx_index 0 uart_rx_buffer[uart_rx_index-1] \r) { uart_rx_buffer[uart_rx_index1] \0; // 添加字符串结束符 Parse_Command((char*)uart_rx_buffer); // 调用解析函数 uart_rx_index 0; // 重置索引 } else if(uart_rx_index RX_BUFFER_SIZE - 1) { uart_rx_index; } else { uart_rx_index 0; // 缓冲区溢出清空 } // 重新启动接收中断等待下一个字节 HAL_UART_Receive_IT(huart1, uart_rx_buffer[uart_rx_index], 1); } }实现一个简单的指令解析函数示例实际应用可能需要更复杂的JSON解析void Parse_Command(char* cmd) { // 这是一个非常简单的字符串匹配解析实际项目建议使用轻量级JSON解析库如 cJSON if(strstr(cmd, \device\:\led\) ! NULL) { if(strstr(cmd, \action\:\on\) ! NULL) { HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET); // 打开LED } else if(strstr(cmd, \action\:\off\) ! NULL) { HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET); // 关闭LED } } // 可以继续解析其他设备指令如电机、蜂鸣器等 }在main函数初始化部分启动串口接收中断 在/* USER CODE BEGIN 2 */区域添加// 启动串口接收中断每次接收1个字节 HAL_UART_Receive_IT(huart1, uart_rx_buffer[0], 1);5. PC端Python程序示例最后我们来看看PC端如何“搭桥”。这个Python脚本负责三件事从串口读取STM32数据、调用通义千问模型、把模型回复的指令发回给STM32。import serial import json from transformers import AutoTokenizer, AutoModelForCausalLM import torch # 1. 初始化串口 (请根据你的实际COM端口修改) ser serial.Serial(COM3, 115200, timeout1) # Windows # ser serial.Serial(/dev/ttyUSB0, 115200, timeout1) # Linux # 2. 加载通义千问模型和分词器 (这里以1.8B的GPTQ量化版为例需提前下载) model_name Qwen/Qwen1.5-1.8B-Chat-GPTQ-Int4 print(f正在加载模型: {model_name}...) tokenizer AutoTokenizer.from_pretrained(model_name, trust_remote_codeTrue) model AutoModelForCausalLM.from_pretrained( model_name, torch_dtypetorch.float16, # 使用半精度节省显存 device_mapauto, # 自动分配设备 (GPU/CPU) trust_remote_codeTrue ) print(模型加载完毕) def get_ai_response(sensor_data_str): 将传感器数据构造为提示词获取模型回复 # 构造一个简单的系统提示让模型扮演智能家居控制器 system_prompt 你是一个智能家居控制助手。请根据传感器数据用简洁的JSON格式输出控制指令。 user_prompt f当前传感器数据{sensor_data_str}。如果温度高于28度请打开风扇指令打开风扇如果湿度低于30%请打开加湿器指令打开加湿器否则一切正常。请只输出JSON例如{{\cmd\: \打开风扇\}} messages [ {role: system, content: system_prompt}, {role: user, content: user_prompt} ] # 应用聊天模板 text tokenizer.apply_chat_template( messages, tokenizeFalse, add_generation_promptTrue ) # 模型推理 model_inputs tokenizer([text], return_tensorspt).to(model.device) generated_ids model.generate( **model_inputs, max_new_tokens512 ) generated_ids [ output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids) ] response tokenizer.batch_decode(generated_ids, skip_special_tokensTrue)[0] return response def parse_and_send_command(ai_response): 解析AI回复并转换为给STM32的指令格式 try: # 尝试解析AI返回的JSON cmd_dict json.loads(ai_response) cmd cmd_dict.get(cmd, ) # 将自然语言指令映射为协议指令 stm32_cmd if 打开风扇 in cmd: stm32_cmd {type:cmd, device:fan, action:on}\\r\\n elif 打开加湿器 in cmd: stm32_cmd {type:cmd, device:humidifier, action:on}\\r\\n elif 一切正常 in cmd: stm32_cmd {type:cmd, device:none, action:idle}\\r\\n else: return if stm32_cmd: ser.write(stm32_cmd.encode(utf-8)) print(f已发送指令至STM32: {stm32_cmd.strip()}) except json.JSONDecodeError: print(fAI回复不是有效的JSON: {ai_response}) except Exception as e: print(f处理指令时出错: {e}) # 主循环读取串口数据 - 调用AI - 发送指令 print(开始监听串口等待STM32数据...) try: while True: if ser.in_waiting 0: # 读取一行数据 (以\\r\\n结尾) line ser.readline().decode(utf-8, errorsignore).strip() if line: print(f收到STM32数据: {line}) try: # 1. 解析传感器数据 sensor_data json.loads(line) print(f解析数据: 温度{sensor_data.get(temp, N/A)}°C, 湿度{sensor_data.get(humi, N/A)}%) # 2. 获取AI决策 ai_response get_ai_response(line) print(fAI回复: {ai_response}) # 3. 解析并发送控制指令 parse_and_send_command(ai_response) except json.JSONDecodeError: print(f无法解析的STM32数据: {line}) except KeyboardInterrupt: print(\\n程序被用户中断。) finally: ser.close()这个脚本只是一个起点。你可以根据需求设计更复杂的提示词让模型处理更丰富的传感器数据如光照、人体感应并生成更复杂的控制逻辑。6. 总结与后续建议走完这一整套流程你应该已经成功搭建起了一个STM32与AI模型通信的雏形。从CubeMX的图形化配置到嵌入式端的驱动编写和协议解析再到PC端的Python桥接程序我们完成了一个完整的“感知-思考-执行”闭环。用下来的感觉是CubeMX确实大大简化了底层硬件初始化的繁琐工作让我们能更专注于业务逻辑。而文本格式的通信协议虽然效率不是最高但对于这种小数据量、需要可读性的场景来说开发和调试都非常方便。在实际项目中你可能会遇到一些需要进一步优化的地方。比如STM32端的JSON解析可以换成更高效的纯字符串处理或微型解析库串口通信可以增加校验和重传机制来提升可靠性PC端的Python程序可以做成更稳定的服务并加入日志和错误处理。此外如果想让设备脱离电脑独立运行可以考虑让STM32通过ESP8266/ESP32这类Wi-Fi模块连接网络直接与云端的模型API进行通信。希望这个指南能为你打开一扇门把强大的AI模型与真实的物理世界连接起来做出更多有趣、有用的智能设备。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。