AI大模型落地应用之电机驱动
来源:互联网
时间:2026-08-12 15:58:09
AI大模型落地应用之电机驱动

一、关于将AI大模型落地的一点思考
2023年,ChatGPT的横空出世让大模型进入高速发展期,这一年也被业界称为“人工智能元年”。到了2024年,大模型技术逐渐成熟,开始试图迈入下一个阶段——大模型落地阶段。但问题来了:到底该如何落地?怎么落地?
说到底,AI虽然能赋能各行各业,但从某种意义上讲,它更像一个虚拟产品。要让它真正落地,就得跟实体产业结合起来。而在众多实体产业中,机器人无疑是最契合的一个选择。
二、如何使用大模型技术结合机器人技术?
相比硬件部分,机器人控制部分的软件应该是最适合与AI大模型结合的。这里以简单的AI驱动电机作为例子来说明整个流程。
1. 准备工作
首先需要获取API密钥:OpenAI API官网链接:https://platform.openai.com/docs/api-reference
安装包含Python环境的Anaconda,相比直接安装Python,它能隔离多个环境,给每个项目创建纯净的环境,避免包之间的冲突。
|
Python 环境安装 安装anaconda https://www.anaconda.com/products/individual 打开命令行,创建虚拟环境 conda create -n openai_control python=3.11 下载需要的包 pip install openai
|
这里使用的是PyCharm,所以有些终端操作可以直接略过。
2. 代码编写
关于电机驱动控制的操作,因为只是介绍AI落地应用的方向,所以没有搞得太复杂。核心思路就是:大模型 + Tool + 串口通信 + STM32控制。
首先构造出ChatGPT对话的基本框架:
|
Python import openai
api_key = open('api_key.txt', 'r').read().strip() # 创建OpenAI的client,需要设置api_key client = openai.OpenAI(api_key=api_key)
# 调用chat接口 chat_completion = client.chat.completions.create( # 发送的消息内容,类似在ChatGPT中输入"hi,你好,请介绍下自己" messages=[{ "role": "user", "content": "hi,你好,请介绍下自己.", }], # gpt模型选择 model='gpt-3.5-turbo-1106', # 非流式输出 stream=False )
# GPT返回的答复 print(chat_completion.choices[0].message.content)
|
3. 构造电机驱动的Tools
Tools的本质就是插件,相当于给OpenAI增加了一个额外的扩展,开发者通过这个扩展提升它的能力。调用流程如下:
- 定义本地函数Motor_control,实现从API拉取(这里直接写一个简单的参数输出模拟)。
- 第一次调用接口:返回大模型分析出的函数名称和参数。
- 调用本地函数:获取返回值,进行本地Python方法调用。
- 第二次调用接口:将第一次接口的返回值message与本地函数调用的结果拼装起来,再次调用接口。
具体代码:
|
Python import openai
# 自定义一个插件方法,用于进行电机的启动控制 def Motor_control(): return "启动"
# 定义messages messages = [{ "role": "user", "content": "hi,你好,你能启动一下电机吗?", }]
api_key = open('api_key.txt', 'r').read().strip() client = openai.OpenAI(api_key=api_key)
first_chat_completion = client.chat.completions.create( messages=messages, model='gpt-3.5-turbo-1106', stream=False, tool_choice="auto", tools=[{ "type": "function", "function": { "name": "Motor_control", "description": "控制电机的启动和停止", } }] )
response_message = first_chat_completion.choices[0].message print(response_message) messages.append(response_message)
if response_message.tool_calls is not None: print("命中插件,进行调用") messages.append({ "role": "tool", "content": Motor_control(), "tool_call_id": response_message.tool_calls[0].id })
second_chat_completion = client.chat.completions.create( messages=messages, model='gpt-3.5-turbo-1106', stream=False, tool_choice="auto", tools=[{ "type": "function", "function": { "name": "Motor_control", "description": "控制电机的启动和停止", } }] ) print(second_chat_completion.choices[0].message.content)
|
逻辑上看起来没问题,也命中了插件,但这里报错了:
|
Python raise self._make_status_error_from_response(err.response) from None openai.BadRequestError: Error code: 400 - {'error': {'message': "An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'. The following tool_call_ids did not ha ve response messages: call_PEhO1CpC6DcW3QKQFacJqO2b", 'type': 'invalid_request_error', 'param': 'messages', 'code': None}}
|
经过排查,发现是tool_call_id的问题。如果确定只有单个tool_call_id需要响应,代码应该只关注处理这一个响应,确保在向messages列表追加响应时正确引用tool_call_id并提供实际响应内容。但这里的代码似乎不止一次引用。于是进行修正:
|
Python import openai
def Motor_control(): return "电机已启动"
messages = [{ "role": "user", "content": "hi,你好,你能启动一下电机吗?", }]
api_key = open('api_key.txt', 'r').read().strip() client = openai.OpenAI(api_key=api_key)
first_chat_completion = client.chat.completions.create( messages=messages, model='gpt-3.5-turbo-1106', stream=False, tool_choice="auto", tools=[{ "type": "function", "function": { "name": "Motor_control", "description": "控制电机的启动和停止", } }] )
response_message = first_chat_completion.choices[0].message print(response_message) messages.append(response_message)
if response_message.tool_calls is not None: print("命中插件,进行调用") for tool_call in response_message.tool_calls: tool_response_content = Motor_control() messages.append({ "role": "tool", "content": tool_response_content, "tool_call_id": tool_call.id })
second_chat_completion = client.chat.completions.create( messages=messages, model='gpt-3.5-turbo-1106', stream=False, tool_choice="auto", tools=[{ "type": "function", "function": { "name": "Motor_control", "description": "控制电机的启动和停止", } }] ) print(second_chat_completion.choices[0].message.content)
|
这次成功调用。
4. 串口通信 + STM32控制
在基础的插件命中之后,就可以进行串口通信+STM32的控制操作了。因为本次只是为了电机的启动和停止,只需要对原本的Motor_control函数进行修改即可。当然,还需要下载pyserial的包。修改后的Motor_control函数:
|
Python import serial
ser = serial.Serial('COM3', 9600)
def Motor_control(user_input): """ 根据用户输入决定发送启动(1)或停止(0)命令到串口。 :param user_input: 用户的原始输入文本 :return: 成功执行的响应信息 """ if "启动" in user_input or "开" in user_input: command = "1" ser.write(command.encode()) return "电机已启动" elif "停止" in user_input or "关" in user_input: command = "0" ser.write(command.encode()) return "电机已停止"
|
由于涉及到参数,还需要对GPT回复部分的插件参数进行重新注册:
|
Python def interact_with_gpt(): completion = client.chat.completions.create( messages=messages, model='gpt-3.5-turbo-1106', stream=False, tool_choice="auto", tools=[ { "type": "function", "function": { "name": "Motor_control", "description": "控制电机的启动和停止", "parameters": { "type": "object", "properties": { "command": { "type": "string", "description": "启动或停止电机的命令", } }, "required": ["command"] }, } }, ] ) return completion
|
再次报错,原因是tool_call.function.arguments是一个字符串而不是字典,需要用json.loads转换:
|
Python if response.tool_calls is not None: print("命中插件,进行调用") if response.tool_calls: for tool_call in response.tool_calls: arguments = json.loads(tool_call.function.arguments) if tool_call.function.name == "Motor_control": tool_response = Motor_control(arguments.get("command", "")) else: tool_response = {"content": "未知的工具调用。"} messages.append({ "role": "tool", "content": json.dumps(tool_response), "tool_call_id": tool_call.id })
|
至此,电机启动!但有个问题:我们的目的是通过串口控制电机,得知道到底发送了什么串口数据。GPT只告诉电机启动成功,没有告诉发送了什么数据。于是再次修改电机控制函数,明确告诉GPT发送了什么数据:
|
Python def Motor_control(user_input): if "启动" in user_input or "start" in user_input: command = "1" ser.write(command.encode()) return "已发送数据1,电机已启动" elif "停止" in user_input or "stop" in user_input: command = "0" ser.write(command.encode()) return "已发送数据0,电机已停止"
|
效果仍然不理想,原因在于询问时没有给出具体要求。调整问题后,终于得到期望的结果。
5. 编写STM32控制代码
核心功能包括串口通信和PWM输出。以下为主要代码:
|
C #include "stm32f10x.h"
void USART1_Init(void); void TIM1_PWM_Init(void); void StartMotor(void); void USART1_IRQHandler(void);
int main(void) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); USART1_Init(); TIM1_PWM_Init(); NVIC_EnableIRQ(USART1_IRQn); while (1) { } }
void USART1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); USART_InitStructure.USART_BaudRate = 9600; 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_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); USART_Cmd(USART1, ENABLE); NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); }
void TIM1_PWM_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); TIM_TimeBaseStructure.TIM_Period = 1000; TIM_TimeBaseStructure.TIM_Prescaler = 84 - 1; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = 0; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC1Init(TIM1, &TIM_OCInitStructure); TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_ARRPreloadConfig(TIM1, ENABLE); TIM_Cmd(TIM1, ENABLE); }
void StartMotor(void) { TIM1->CCR1 = TIM1->ARR / 2; }
void USART1_IRQHandler(void) { if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { char receivedData = USART_ReceiveData(USART1); if (receivedData == '1') { StartMotor(); } } }
|
当然,这里只是一个伪代码,具体配置参数还需要根据实际情况调整,但大致流程就是如此。