语音芯片WTN6的驱动

前言

(1)本系列是基于STM32的项目笔记,内容涵盖了STM32各种外设的使用,由浅入深。

(2)小编使用的单片机是STM32F105RCT6,项目笔记基于小编的实际项目,但是博客中的内容适用于各种单片机开发的同学学习和使用。

学习目标

本章有三个任务:

  1. 关于语言芯片外设电路的设计
  2. 语言芯片驱动的逻辑分析
  3. 驱动芯片程序的开发
  4. 语音芯片功能的测试

学习内容

语音芯片的主要作用是让主机有语音提示功能。 这个是本产品最主要的功能之一。我们选择的是广州唯创电子的一颗定制芯片。

1.硬件原理图纸的分析:

语音芯片WTN6的驱动_第1张图片
语音芯片WTN6的驱动_第2张图片

语音芯片WTN6的驱动_第3张图片

从上面的原理图很难看的出,WTN06的通讯的方式的。

但是可以看出,接单片机引脚的三个口,是单片机输出来驱动语音芯片,输出是PWM,PWM再进入功放电路,功放就是声音放大的作用,不加功放的话声音会小一点。

WTN06支持多种通讯方式,刚开始原理图是按照单线通讯的方式设计的,但和厂家沟通后,单线通讯不支持我们的功能,所有我们有修改成了2线串口方式。

PC1 – WTN6_DATA – CLK 时钟线

PC2 – WTN6_NC – DAT

PC3 – WTN6_BUSY – NC (PC3没用到,预留在这,我们只用到了PC1和PC2,CLK和DAT,他俩是什么,看下面的逻辑分析)

功放芯片:

PA0 — 8002_EN 功放控制脚 8002_EN写入高电平,将三极管导通,SH为低电平,功放工作

语音芯片WTN6的驱动_第4张图片

在这里插入图片描述

2.语言芯片驱动的逻辑分析

语音芯片WTN6的驱动_第5张图片

96H:1001 0110 先发送低位,再高位,0110 1001

96H发送完后,就播放96H地址对应的语音,在表里面可以找。

例如要发在家布防: 0000 0001,则DATA发送1000 0000,如下

50, 3,3, 3,3, 3,3, 3,3, 3,3, 3,3, 3,3, 3,3, 3,3, 3(多一个,是最后数据发送完了,要对CLK和DATA拉高,这里多一位相当于把这位提前初始化了,是0是1并不重要)

CLK-L 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1

DATA-L 1 0 0 0 0 0 0 0 0

①时钟脚CLK.

​ ● 空闲:高电平

​ ● 唤醒:5ms的低电平

​ ● 输出数据:40-3.2ms周期的时钟 推荐高低电平300uS

​ ● 结束:高电平

5ms 300us 300us…(16个高低电平,16个300us)最后持续高电平

②数据脚DATA

​ ○ 空闲:高电平

​ ○ 唤醒: 和CLK同步5ms的低电平

​ ○ 数据传输:低位在前 数据1:高电平 数据0:低电平

CLK和DATA空闲的时候都是高电平。

数据的就和我们的语音对应

程序语音的排序:sentence句子

语音芯片WTN6的驱动_第6张图片

3.驱动芯片程序的开发

上面的代码我们用定时器矩阵来完成。如果没有定时器矩阵功能的话,这个功能如果做起来还是有很大的困难的。裸机去跑的话还是有很多办法去实现的,但是如果是放在系统(Freertos)里面,那做起来还是有难度的。

首先我们用定时器矩阵依次定时5ms + 16个300us,控制CLK 管脚

其次我们根据CLK的变化,来控制DATA的高低电平

步骤

①程序版本修改为V1.14 我们在V1.14版本上完成WTN6驱动的开发

②新建hal_wtn6.c和hal_wtn6.h文件。并加载到V1.14工程里

③相关IO的初始化

端口定义:

#include "hal_wTn6.h"
#include "stm32F10x.h"
#include "hal_timer.h"

//只用了PC1和PC2
#define WTN6_CLK_PORT    GPIOC
#define WTN6_CLK_PIN     GPIO_Pin_1

#define WTN6_DAT_PORT    GPIOC
#define WTN6_DAT_PIN     GPIO_Pin_2

#define SC8002_SH_PORT	 GPIOA
#define SC8002_SH_PIN    GPIO_Pin_0

//时钟低电平,时钟高电平
#define WTN6_CLK_LOW   GPIO_ResetBits(WTN6_CLK_PORT,WTN6_CLK_PIN)
#define WTN6_CLK_HIG   GPIO_SetBits(WTN6_CLK_PORT,WTN6_CLK_PIN)

//数据高低电平
#define WTN6_DAT_LOW   GPIO_ResetBits(WTN6_DAT_PORT,WTN6_DAT_PIN)
#define WTN6_DAT_HIG   GPIO_SetBits(WTN6_DAT_PORT,WTN6_DAT_PIN)

//控制功放引脚
#define SC8002_SH_LOW   GPIO_ResetBits(SC8002_SH_PORT,SC8002_SH_PIN)
#define SC8002_SH_HIG   GPIO_SetBits(SC8002_SH_PORT,SC8002_SH_PIN)


GPIO的初始化:

初始化相关的GPIO口。配置CLK DAT 为高电平(空闲为高电平)

static void hal_Wtn6Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC , ENABLE); 	//注意时钟端口一定都要打开
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE); 	

	GPIO_InitStructure.GPIO_Pin = WTN6_CLK_PIN | WTN6_DAT_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 
	GPIO_Init(GPIOC, &GPIO_InitStructure);

	GPIO_InitStructure.GPIO_Pin = SC8002_SH_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; 
	GPIO_Init(SC8002_SH_PORT, &GPIO_InitStructure);	
	
	GPIO_SetBits(GPIOC,GPIO_Pin_1);	
	GPIO_SetBits(GPIOC,GPIO_Pin_2);
	GPIO_SetBits(SC8002_SH_PORT,SC8002_SH_PIN);	
}

④播放语音的初始化

在hal_timer.h文件中增加T_WTN6定时器矩阵,用来驱动WTN6语音播报。

typedef enum
{
	T_LED,					//LED定时器
    T_WTN6,
	T_SUM,
}TIMER_ID_TYPEDEF;

利用定时器矩阵依次定时5ms 300us 300us…300us(17个),并初始化相关参数。

定义三个变量:

unsigned short wtn6[] = {50,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3}; //5ms 300us 300us…300us(17个),

unsigned char Wtn6Playflag;// CLK的序号

unsigned char Wtn6VolueNum;// 当前播放的语音

unsigned char Wtn6NextNum; // 下一个播放的语音

unsigned short wtn6[] = {50,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3};	//50*100us=5ms;3*100us=300us
unsigned char Wtn6Playflag;// CLK的序号的计数个数
unsigned char Wtn6VolueNum;// 当前播放的语音
unsigned char Wtn6NextNum; // 下一个播放的语音
volatile unsigned short Wtn6Timer;  //当前WTN计时次数
static void hal_Wtn6_PlayHandle(void);
void hal_wtn6(void)
{
	hal_Wtn6Config();
	WTN6_DAT_HIG;
	WTN6_CLK_HIG;
	Wtn6Playflag = 0;
	Wtn6VolueNum = 0;
	Wtn6NextNum = 0xff;
    hal_CreatTimer(T_WTN6,hal_Wtn6_PlayHandle,2,T_STA_STOP);//100us
}

开始语音播报程序:

void hal_Wtn6_PlayVolue(unsigned char VolNum) //VolNum是我们程序头文件枚举中的值,例如,播放在家布防就是1
{
	if(Wtn6Playflag == 0)  //时序等于0,表示当前是空闲状态
	{ //空闲 ,这次直接发
		hal_CreatTimer(T_WTN6,hal_Wtn6_PlayHandle,2,T_STA_START); //启动定时器矩阵,时间周期为100us
		WTN6_CLK_LOW;
		WTN6_DAT_LOW;  //这两个引脚拉低,去唤醒芯片
		Wtn6VolueNum = VolNum; //把当前要播放的语音复制给Wtn6VolueNum
		Wtn6Playflag = 0; //清零
		Wtn6Timer = wtn6[0];	//用来计数,50 3 3 3 ……,每进一次定时器矩阵处理函数,Wtn6Timer会减减
		Wtn6NextNum = 0xff;  //清零,这里的清零是初始化为定值0xff
	}
	else   //时序不等于0 ,则表示正在发送时序
	{ //忙碌,则保存为Next值,下一次发
		Wtn6NextNum = VolNum;
	}
}

语音播报处理函数程序:

/********
*CLK:
*开始时,CLK是50ms高电平,后面数组的第1,3,5,7,9……15都是低电平,WTN6_CLK_LOW,2,4,6,*8……16都是高电平,WTN6_CLK_HIG
*DATA:VolueNum & 0x01,每次只判断VolueNum(0000 0001)(VolueNum = Wtn6VolueNum)的最低位(因为DATA每次都是从最低位开始发),每次判断完后,将VolueNum右移一位,VolueNum >>= 1;每次判断最低位是1还是0,1则发高电平,0则发低电平
***************/
static void hal_Wtn6_PlayHandle(void)
{
	static unsigned char VolueNum;
	Wtn6Timer --;
	if(Wtn6Timer == 0)
	{
			Wtn6Playflag ++;   //对数组的元素,一个一个位来;比如,数组第一个数是50,则50--,到0的时候,Wtn6Playflag ++,后面Wtn6Timer= wtn6[Wtn6Playflag],即要3--直到0,才到下一次
		    Wtn6Timer= wtn6[Wtn6Playflag];
			switch(Wtn6Playflag)
			{
				case 1:
					VolueNum = Wtn6VolueNum;
					WTN6_CLK_LOW;
					if(VolueNum & 0x01)
					{
							WTN6_DAT_HIG;
					}
					else
					{
							WTN6_DAT_LOW;
					}		
				break;
				case 2:
				case 4:
				case 6:
				case 8:
				case 10:
				case 12:
				case 14:
			  case 16:
				{
						WTN6_CLK_HIG;	
					  VolueNum >>= 1;
				}		
				break;
				case 3:
				case 5:
				case 7:
				case 9:
				case 11:
				case 13:
				case 15:	
				{
					WTN6_CLK_LOW;	
					if(VolueNum & 0x01)
					{
							WTN6_DAT_HIG;
					}
					else
					{
							WTN6_DAT_LOW;
					}				
				}
				break;
			}
			if(Wtn6Playflag == 17) //发完了,对要清零的数据清零
			{
				WTN6_DAT_HIG;
				WTN6_CLK_HIG;
				Wtn6Playflag = 0;
				if(Wtn6NextNum != 0xff)  //若下一次用播放,即Wtn6NextNum!=0,则继续播放
				{
					hal_Wtn6_PlayVolue(Wtn6NextNum);
					Wtn6NextNum = 0xff;
				}
				return;
			}
	}
	hal_ResetTimer(T_WTN6,T_STA_START);	
}

分析(及时理解):

(1)语音播报程序void hal_Wtn6_PlayVolue(unsigned char VolNum) 其实就包含了语音播报处理函数static void hal_Wtn6_PlayHandle(void),

(2)因为语音播报处理函数是语音播报程序的回调函数

(3)语音播报程序void hal_Wtn6_PlayVolue(unsigned char VolNum) 相当于播报前的一个预处理,语音播报处理函数static void hal_Wtn6_PlayHandle(void)是正式处理播报功能

(4)这就体现出来,程序和做事一样,做得很周到,有预处理和对预处理的结果做正式处理。

⑤完善hal_wtn6.h代码

#ifndef ____HAL_WTN6_H_
#define ____HAL_WTN6_H_
//通过枚举的形式,把要播放的语音都写在这里,要播放哪个直接取值就行
enum
{
	WTN6_HOMEARM = 1,   ///在家布防
	WTN6_AWAYARM,       ///离家布防
	WTN6_DISARM,        ///撤防
	WTN6_STUDY_START,   ///开始成功,请出发探测器
	WTN6_STUDY_SUC,     ///配对成功 
	WTN6_HAVED_DETEC,   ///探测器已存在 
	WTN6_STUDY_FAIL,    ///配对失败
	WTN6_GET_WIFI_PASSWORD,    ///开始配网,请在APP上输入WIFI密码,点击链接按钮
	WTN6_GET_WIFI_OK,    ///配网成功
	WTN6_GET_WIFI_FAIL,  ///配网失败
	WTN6_WIFI_TIMEOUT,  ///配网超时	

	WTN6_AC_DOWN,    ///主机掉电
	WTN6_AC_RECOVER,  ///外电恢复
	WTN6_TO_FACTORY,  ///恢复出厂设置OK

	WTN6_UPGRADE_NEWFIREWARE,//new fireware
	WTN6_UPGRADE_DOWN_START,//
	WTN6_UPGRADE_DOWN_FAIL,
	WTN6_UPGRADE_DOWN_SUC,  ///升级成功,

	WTN6_VOLUE_SUC,         ///音效 成功音效
	WTN6_VOLUE_DI,        ///音效 DI DI
	WTN6_VOLUE_DINGDONG,    ///音效 叮咚
	WTN6_VOLUE_VOLT_LOW,    ///音效 电池低压	

	WTN6_VOLUE_110,         ///音效 110报警声音
	WTN6_VOLUE_110_12,         ///音效 110报警声音
	WTN6_VOLUE_110_15,         ///音效 110报警声音
	WTN6_VOLUE_110_18,         ///音效 110报警声音
};
	
void hal_wtn6(void);  //初始化的函数
void hal_Wtn6_PlayVolue(unsigned char VolNum);  //播放语音的函数

#endif


⑥在hal_task.c中初始化wtn6.代码如下。

#include "hal_task.h"
#include "hal_timer.h"
#include "hal_led.h"
#include "hal_gpio.h"
#include "hal_wtn6.h"

void hal_task_init(void)
{
	hal_timerInit();	
	hal_GpioConfig_init();		
	hal_LedInit();
	hal_wtn6(); //初始化语音播报芯片
}

⑦在hal_task.c 函数void hal_task(void)中增加wtn6.测试代码:

void hal_task(void)
{
	static unsigned short wtn6testDelay = 0;
	static unsigned char wtn6PlayNum = 1;
	wtn6testDelay ++;
	if(wtn6testDelay > 500) //10ms*500 = 5s,每5s执行一次播报,把枚举的几个语句全部读完又从头读
	{
		wtn6testDelay = 0;
		hal_Wtn6_PlayVolue(wtn6PlayNum);
		wtn6PlayNum ++;
		if(wtn6PlayNum >= WTN6_VOLUE_110_18)
		{
			wtn6PlayNum = 1;
		}
	}
}

4.语音芯片功能的测试

tips

(1)理解代码,先去找到最终实现功能的函数,

(2)根据目的,去研究相关的各个函数,每个函数的研究可以从函数里面的变量入手,去理解一个一个有什么作用。

(3)理解每个变量的作用,而后理解整个函数的思路,最后可以理解整个系统的逻辑思路。

(4)上面的驱动代码还是比较灵活的运用了定时器矩阵,不过我感觉这代码里面更值得欣赏的还是发送CLK和DATA数据的思路,巧妙的配合,灵活的程序编写,很优秀,值得深入研究学习。

(5)有的时候程序写完了却没有实现目的,可能的代码问题是,功能所用到的端口的时钟,没有全部打开。

你可能感兴趣的:(STM32系列,stm32)