STM32智能垃圾分类系统(basic1)
STM32智能垃圾分类系统(basic1)
先看演示
来由
学完了江科大STM32的教程,想做个项目顺便总结一下。所以就在网上找了一个简单点的项目复刻。这个垃圾分类项目原作者用的是LD3320语音模块,然后还用纽扣电池做了个时钟来实现多种投放模式。但好像不能调时间,要调就只能重新下载程序。我觉得这个不太合理使用暂时就没有一起搬过来。所以就先做了这个只有基础功能的版本,后续有时间会移植freertos并加入esp8266联网实现原项目的多种投放模式以及类似NTP校时的功能。
改动
1.我把原项目的LD3320换成了asrpro(2M版本),因为上网搜了一下LD3320效果一般般,关键是价格还要52,加个喇叭就60了,比C8T6板子还贵[doge]。而asrpro加上咪头喇叭也才16,而且效果也不错。
asrpro可以使用类似于scratch的模块化编程,也开放了一些源码,官方提供了天问Block和网页版的IDE。唯一的缺点就是生成模型需要联网并且3分钟内只能生成一次(只修改代码不改语音不用),不过也够用了。
参数:32 位高性能 CPU,运行频率最高支持 240MHz,内置 640KB SRAM,内置2MB/4MB Flash两种规格,支持 DNN\TDNN\RNN 等神经网络及卷积计算,支持语音识别、声纹识别、语音增强、语音检测等功能。
并且内置3路 UART 接口,1 路 IIC 接口,6 路 PWM 接口,内置 4 组 32-bit timer还有10个GPIO口。
所以如果只需要实现一些简单的小功能的话一块asrpro的板子就够了。不过因为核心板并没有引出那么多引脚,再加上有些引脚已经被喇叭驱动和麦克风电路给占用了。因此在本项目中就把asrpro和咪头音响当做外设了,通过串口和C8T6通信。
2.原项目用的是spi的显示屏,我用的I2C,原理都差不多,就是给显示驱动芯片发命令和数据。
3.还有就是原项目中使用delay模块来实现10秒后关盖,我觉得这样不太合理。在主函数使用delay会导致线程阻塞,需要等关盖后才才能进行其它操作。所以我改成了使用STM32的通用定时器并定义四个计数变量来实现相互独立开关盖。
4.最后就是这个项目因为不需要演示给导师看,所以就自己在面包板上简单的搭建了一下,没打印电路板。剩下的三个舵机和垃圾桶模型也没买[doge]。
正文
首先是简单的系统框图
材料包括:STM32F103C8T6最小系统板、asrpro(2M)、4015咪头、8欧3W喇叭、3*6*4.3mm两脚按键、0.96寸OLED显示屏还有面包板飞线。
此外还需要ch340用来现在程序,STLink用来调试STM32(选配)。
主程序运行在C8T6开发板上,使用keil5开发,程序里包含了key驱动、舵机驱动、显示屏驱动(软件I2C)、asrpro驱动(基于串口)、定时器程序和主程序。
OLED_Font_CN.h是中文字库,直接用中景园资料里配的取模工具转成数组,输出格式选C51方便一点。
关于四个垃圾桶独立开关盖的功能我是用通用定时器来实现的,在舵机驱动里包含了计时器的头文件,(计时器里就是开启TIM2设置1秒溢出一次并开启NVIC)并定义8个变量用来存储标致位和计数。
flag置1每次进中断就计数,到了10秒把flag和count清零
这样就可以实现独立的开关了,相当于主函数只需要发出开启和开始计时命令,到了10秒进中断垃圾桶自动关。后续如果移植了Freertos应该实现起来会简单点。
asrpro的驱动的串口数据用的是自己定义的格式,先一个字节表示数据类型,比如第一个字节01发给STM32表示接下来要发两个字节,一个表示垃圾类别,一个表示语音识别id。STM32第一个字节01发给asrpro表示接下来将发送一个字节表示播放id。这样做的目的是方便之后拓展其它功能。
STM32的接收程序里也使用了状态机的编程思想,同样是方便以后拓展更多功能。
asrpro收到后需要自己焊一下排针,我没啥技巧,多弄点松香或者助焊剂就非常好焊。咪头商家配的4015,官方也有建议的参数为保障好的语音识别效果,建议采用灵敏度为-32±3dB,信噪比≥65dB的麦克风
喇叭配的8欧3W 3070腔体的喇叭,喇叭和麦克风也可以根据需要换用更合适的。
asrpro程序就非常简单了,就识别相应的垃圾名称并通过串口发送,以及在串口接收到数据后播报相应的语音。在程序接收数据这边遇到了一个小问题,一开始的代码是检测到串口有数据后连着接收两个字节,但这会出现一个问题。就是有一定的概率会受到数据1 FF 数据2 FF
这样的数据。
查看源码发现函数read发现串口缓冲队列没有数据会返回-1对应十六进制FF。所以应该是程序运行过快导致第二个数据还没放进缓冲区read就被调用了。解决办法是在两次读取之间加入1ms的延时,但这样不太严谨。第二种办法就是调用读取字符串的函数,但因为没有/0
作为结尾,所以需要设置更短的串口超时时间以减少等待。最后一种办法,也就是我用的办法。在接收第二个字节前再调用available判断一下。这边是available的源码
是判断一个循环队列里有没有新的数据进来,这个一个让中断向量进行二次跳转,执行指定的函数。
这边是中断函数
拷贝串口收到的数据到缓冲队列
所以需要使用while(serial.available){}
来等待下一个字节。不过实际测试这样写代码会出现问题,第一次可以正常收到,之后就不一定了,可能会直接程序卡死,啥也收不到。参考官方给出的程序示例,应该是需要在循环里加入一些比好耗时的操作或者直接delay个一两毫秒。
当然这只是一个实现了基础功能的版本,后续还会加入更多功能,比如加入纽扣电池实现断电后时钟继续运行,加入esp8266实现手机控制和类似NTP校时的功能,以及移植freertos等。