一:Python语音机器人控制STM32开发流程

1. 上位机的开发用到的工具:

1.1. Python3.9
1.2. Pycahrm社区版

2. 上位机开发用到的模块:


 


import sys # 使用sys来退出程序
import webbrowser # 用来模拟提交模拟浏览器提交
import requests # 获取API请求
import json # 使用JSON把获取的数据专成Python可读的Json代码
import playsound # 播放声音
import os # 使用os.remove移除录音文件
import win32api # 执行绝对路径的程序
import pyaudio # 录音
import wave # 转换声道
import threading # 线程
from aip import AipSpeech # 百度语音模块
from pynput.keyboard import Listener # 键盘监听
import serial # 与STM32串口通信模块

 

3. 下位机开发用到的工具:

3.1. Keil MDK
3.2. MCUISP

4. 使用Keil MDK开发STM32的核心代码:

main.c文件


// 头部引入"led.h"
// 头部引入"sys.h"
// 头部引入"usart.h"
int main(void)
{
u8 t;
u8 len;
u16 times=0;
uart_init(9600); //串口初始化为9600
LED_Init(); //³初始化与LED连接的硬件接口
while(1)
{
if(USART_RX_STA&0x8000)
{
len=USART_RX_STA&0x3fff;//获取上位机发送的字符串的长度
if(len==3)

LED0=!LED0;
LED1=!LED1;
for(t=0;t<len;t++)
{
USART_SendData(USART1, USART_RX_BUF[t]);//向串口一发送数据
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
}
USART_RX_STA=0;

}else
{
times++;
if(times%30==0)LED0=!LED0;
}
}

}



led.h文件


//#ifndef __LED_H
//#define __LED_H
//#include "sys.h"
//#define LED0 PAout(8) // PA8
//#define LED1 PDout(2) // PD2
void LED_Init(void);//初始化
//#endif



led.c文件


//#include "led.h"
void LED_Init() //LED的初始化函数
{
GPIO_InitTypeDef GPIO_InitStructure; //定义结构体,配置IO口的状态

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOD, ENABLE); // PA,PD端口时钟
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_8; //led0-->PA.8端口配置
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;//配置推挽输出模式,
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;//设置输出速度50MHz
GPIO_SetBits(GPIOA,GPIO_Pin_8);//输出高
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //LED1-->PD.2 端口配置 推挽输出
GPIO_Init(GPIOD, &GPIO_InitStructure); //设置输出速度50MHz
GPIO_SetBits(GPIOD,GPIO_Pin_2); //PD.2 输出高

}



usart.h文件


#define USART_REC_LEN 200 //定义最大接收字节数 200
#define EN_USART1_RX 1 //使能(1)/禁止(0)串口1接收

extern u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节, 末字节为换行符
extern u16 USART_RX_STA; //接收状态标记
//串口中断接收
void uart_init(u32 bound);
#endif



usart.c文件


//加入以下代码,支持printf函数,而不需要选择use MicroLIB
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;

};

FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
_sys_exit(int x)
{
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR = (u8) ch;
return ch;
}
#endif



#if EN_USART1_RX //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误
u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15, 接收完成标志
//bit14, 接收到0x0d
//bit13~0, 接收到的有效字节数目
u16 USART_RX_STA=0; //接收状态标记

void uart_init(u32 bound){
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟

//USART1_TX GPIOA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9

//USART1_RX GPIOA.10初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10

//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器

//USART 初始化设置

USART_InitStructure.USART_BaudRate = bound;//串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
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); //初始化串口1
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
USART_Cmd(USART1, ENABLE); //使能串口1

}

void USART1_IRQHandler(void) //串口1中断服务程序
{
u8 Res;
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntEnter();
#endif
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Res =USART_ReceiveData(USART1); //读取接收到的数据

if((USART_RX_STA&0x8000)==0)//接收未完成
{
if(USART_RX_STA&0x4000)//接收到了0x0d
{
if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
else USART_RX_STA|=0x8000; //接收完成了
}
else //还没收到0X0D
{
if(Res==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收
}
}
}
}
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntExit();
#endif
}
#endif



sys.h文件


//IO口操作宏定义
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
//IO口地址映射
#define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
#define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C
#define GPIOC_ODR_Addr (GPIOC_BASE+12) //0x4001100C
#define GPIOD_ODR_Addr (GPIOD_BASE+12) //0x4001140C
#define GPIOE_ODR_Addr (GPIOE_BASE+12) //0x4001180C
#define GPIOF_ODR_Addr (GPIOF_BASE+12) //0x40011A0C
#define GPIOG_ODR_Addr (GPIOG_BASE+12) //0x40011E0C

#define GPIOA_IDR_Addr (GPIOA_BASE+8) //0x40010808
#define GPIOB_IDR_Addr (GPIOB_BASE+8) //0x40010C08
#define GPIOC_IDR_Addr (GPIOC_BASE+8) //0x40011008
#define GPIOD_IDR_Addr (GPIOD_BASE+8) //0x40011408
#define GPIOE_IDR_Addr (GPIOE_BASE+8) //0x40011808
#define GPIOF_IDR_Addr (GPIOF_BASE+8) //0x40011A08
#define GPIOG_IDR_Addr (GPIOG_BASE+8) //0x40011E08

//IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //输入

#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) //输出
#define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n) //输入

#define PCout(n) BIT_ADDR(GPIOC_ODR_Addr,n) //输出
#define PCin(n) BIT_ADDR(GPIOC_IDR_Addr,n) //输入

#define PDout(n) BIT_ADDR(GPIOD_ODR_Addr,n) //输出
#define PDin(n) BIT_ADDR(GPIOD_IDR_Addr,n) //输入

#define PEout(n) BIT_ADDR(GPIOE_ODR_Addr,n) //输出
#define PEin(n) BIT_ADDR(GPIOE_IDR_Addr,n) //输入

#define PFout(n) BIT_ADDR(GPIOF_ODR_Addr,n) //输出
#define PFin(n) BIT_ADDR(GPIOF_IDR_Addr,n) //输入

#define PGout(n) BIT_ADDR(GPIOG_ODR_Addr,n) //输出
#define PGin(n) BIT_ADDR(GPIOG_IDR_Addr,n) //输入

//以下为汇编函数
void WFI_SET(void); //执行WFI指令
void INTX_DISABLE(void);//关闭所有中断
void INTX_ENABLE(void); //开启所有中断
void MSR_MSP(u32 addr); //设置堆栈地址

#endif
sys.c文件


//采用如下方法实现执行汇编指令WFI
void WFI_SET(void)
{
__ASM volatile("wfi");
}
//关闭所有中断
void INTX_DISABLE(void)
{
__ASM volatile("cpsid i");
}
//开启所有中断
void INTX_ENABLE(void)
{
__ASM volatile("cpsie i");
}
//设置栈顶地址
//addr:栈顶地址
__asm void MSR_MSP(u32 addr)
{
MSR MSP, r0 //set Main Stack value
BX r14
}

Python里的代码我贴一部分串口通信的:



语音识别核心技术
代码部分:
count = 1   # 计数
run = False
# pyaudio参数
CHUNK = 1024    # 数据包或者数据片段
FORMAT = pyaudio.paInt16    # pyaudio.paInt16表示我们使用量化位数 16位来进行录音
CHANNELS = 1    # 声道,1为单声道,2为双声道
RATE = 16000    # 采样率,每秒钟16000次
# RECORD_SECONDS = 5  # 录音时间
WAVE_OUTPUT_FILENAME = "output.wav"     # 保存录音文件名
client = AipSpeech(APP_ID, API_KEY, SECRET_KEY)     # 百度语音接口
// 在这里加载之前配置的Pyaudio参数来设置
def recoder():
    _frames = [] // 定义空数组
    global run // 把之前的局部变量提升为全局变量
    p = pyaudio.PyAudio()
    stream = p.open(channels=CHANNELS, // 声道
                    format=FORMAT, // 量化位数16来进行录音
                    rate=RATE, // 采样率
                    input=True, // 输入为真
                    frames_per_buffer=CHUNK) // 数据包
    while run: // 标识符
        data = stream.read(CHUNK) // 读取数据包
        _frames.append(data) 添加到之前设置的数组里
// 在这里把对象都关闭,以免浪费资源
    stream.stop_stream()
    stream.close()
    p.terminate()
//  创建wave音频,并写入数据,就不一一写注释了
    wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
    wf.setnchannels(CHANNELS)
    wf.setsampwidth(p.get_sample_size(FORMAT))
    wf.setframerate(RATE)
    wf.writeframes(b''.join(_frames))
    wf.close()
# 读取录音文件字节码
def get_file_content(filePath):
    with open(filePath, 'rb') as fp:
        return fp.read()
# 识别录音,语音转文字
def lic():
    res = client.asr(get_file_content(WAVE_OUTPUT_FILENAME), 'wav', 16000, {
        'dev_pid': 1537, // 这里有必要注释下:
                        //1537  普通话(纯中文识别)  
                        //1737  英语
                        //1637  粤语  
                        //1837  四川话 
                        //1936  普通话远场
    })
    text = res['result'][0]
    # print(text)
    return text
# 语音合成
def speak_(text):
// 这里参数说明下:test:合成的文本,zh:固定语音模式,客户端模式:默认1,
    result = client.synthesis(text, 'zh', 1, {
        'vol': 5, // 音量0-15
        'per': 0, // 基础语音库可选:0,1,3,4
        'spd': 5, // 语速0-15
        'pit': 5 //  音调0-15
    })
    # 识别正确返回语音二进制 错误则返回dict 参照下面错误码
    if not isinstance(result, dict):
        with open('audio.mp3', 'wb') as f:
            f.write(result)
        playsound.playsound('audio.mp3')
        os.remove('audio.mp3')
调用speek_()函数就可以实现语音说话了

最后。使用MCUISP进行烧录c写的编译后的.hex程序到单片机里即可,下来使用我们的Python程序里的serial模块的write对我们的程序进行发送数据,当下位机接收到三个字符的字符串时会先点亮led灯,再发送一次底层读取led状态然后反转。即实现开关led灯的功能