语音芯片WTN6的驱动

news2024/11/25 4:17:31

前言

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

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

学习目标

本章有三个任务:

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

学习内容

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

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


在这里插入图片描述

在这里插入图片描述

从上面的原理图很难看的出,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为低电平,功放工作

在这里插入图片描述

在这里插入图片描述

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

在这里插入图片描述

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句子

在这里插入图片描述

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)有的时候程序写完了却没有实现目的,可能的代码问题是,功能所用到的端口的时钟,没有全部打开。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/980654.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java八股文面试[数据库]——MySQL中事务的特性

在关系型数据库管理系统中,一个逻辑工作单元要成为事务,必须满足这 4 个特性,即所谓的 ACID:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持…

[移动通讯]【Carrier Aggregation-3】【5G】

前言: 参考: 5G Mobile Communications:《Carrier Aggregation in 5G》 目录: 1: carrier Allocation Schemes 2: 网络结构 3: LTE CA 4: 5G CA 一 Carrier Allocation Schemes CA 主要作用…

问脉基础调研

基本功能: 资产清点 清点镜像、镜像软件资产数据与详细信息清点容器、应用软件资产数据与详细信息清点集群、Pod、Service、Ingress、Secrets 等数十种资产数据与详细信息提供资产与资产、资产与事件关联查看提供仪表盘总览当前检测对象数据情况 镜像安全 镜像漏…

Text文件在MATLAB中读写示例基础

背景 为了便于和外部程序进行交换,以及查看文件中的数据,也常常采用文本数据格式与外界交换数据。在文本格式中,数据采用ASCII码格式,可以使用字母和数字字符。可以在文本编辑器中查看和编辑ASCII文本数据。MATLAB提供了导入函数…

raise EOFError(“No data left in file“) EOFError: No data left in file

在linux服务器里跑分割模型的时候出现了以下错误,但是在自己电脑上运行相同程序时没有错误,可以运行。 ise EOFError(“No data left in file”) EOFError: No data left in file 到底是因为什么? GPT给的回答: “EOFError: No …

3dMax全球学习资源、资源文件和教程 !

此样例教育教程和学习资源旨在提供使用Autodesk 3ds Max时的计划知识和培训、正确的工作流、流程管理和最佳实践。 您在Autodesk三维设计领域的职业生涯 有关使用3ds Max和Maya在计算机图形领域开始职业生涯的提示(包括新的3ds Max和Maya介绍教程,以复…

js reduce求和

let unReadCount resultList.reduce((pre, cur) > {return pre cur.unReadCount}, 0)

自动气象站:无线数据传输、多场景应用

自动气象站能够和环境监控云平台组成气象环境监控系统,能够全天候无人值守地监测气象要素,实现实时监测和数据传输,具有多要素集成、无线数据传输、多场景应用的优势。 一、自动气象站可以全天候不间断地监测空气温度、湿度、大气压力、雨量…

Gin框架---环境搭建

目录 一:MAC安装Go环境二:配置Go相关的环境变量三:设置GO国内代理四:GoLand初始化项目五:安装GIN框架六:Gin框架演示 一:MAC安装Go环境 Go官网地址:https://golang.google.cn/dl/直…

输入部件 QComboBox --组合框/下拉列表

QComboBox 类是 QWidget 类的直接子类,该类实现了一个组合框 一、QComboBox 类中的 属性 QComboBOx 类(组合框)属性速查表属性名说明属性名说明count获取项目数量minimumContentsLength组合框中最少字符数maxCount允许的最大项数maxVisibleItems向用户显示的最大项…

计算机网络第三章——数据链路层(中)

数声风笛离亭晚,君向潇湘我向秦 文章目录 ALOHA协议CSMA协议CSMA/CD协议CSMA/CA协议 总线型和星型都是广播式通信,看一下目的地址是否是我,若是我就接受否则就丢弃,总线型就是若是有一个断了则会影响其他的,型型的就是…

报错:为什么数组明明有内容但打印的length是0

文章目录 一、问题二、分析三、解决1.将异步改为同步2.设置延迟 一、问题 在日常开发中,for 循环遍历调用接口,并将接口返回的值进行拼接,即push到一个新的数组中,但是在for循环内部是可以拿到这个新的数组,而for循环…

接口测试工具开发文档

1 开发规划 1.1 开发人员 角 色 主要职责 负责模块 人员 备注 n xxx模块 xxx 1.2 开发计划 <附开发计划表> 1.3 开发环境和工具 开发工具 工具 作用 Notepad 编辑器 Perl 解释器 2 总体设计 设计思路&#xff1a;因为测试app和server。首先必须…

vue3中使用viewerjs实现图片预览效果

vue3中使用viewerjs实现图片预览效果 1、前言2、实现效果3、在vue3项目中使用viewer.js3.1 安装3.2 在main.js中引入3.3 组件中使用 1、前言 viewer.js是一款开源的图片预览插件&#xff0c;功能十分强大: 支持移动设备触摸事件支持响应式支持放大/缩小支持旋转&#xff08;类…

CPU及并发

2.9G Hz,即每秒进行2.9G次运算(即29亿次) 几个命令 us: 用户使用的cpu sy: 系统(内核)使用的cpu id: idle,即空闲cpu wa: 等待I/O的cpu st: 开虚拟机后会有的一个指标,即虚拟机的cpu使用率 一个进程拥有一整套虚拟地址空间,该进程的所有线程都共享该地址空间. 线程是CPU运算的最…

Docker如何安装seafile

SQLite 方式 要在 Docker 中安装 Seafile&#xff0c;您可以按照以下步骤进行操作&#xff1a; 安装 Docker&#xff1a;确保您的系统上已经安装了 Docker。您可以根据您的操作系统类型&#xff0c;在官方网站上找到适合您系统的 Docker 版本并进行安装。 下载 Seafile 镜像&…

管理类联考——数学——汇总篇——知识点突破——数据分析——计数原理——排列组合——涂色

⛲️ 一、考点讲解 1.题目特征 如果给几种颜色来填涂所给的图形&#xff0c;就是涂色问题。 2.解题方法 可以按照图形逐一依次填涂&#xff0c;也可以按照所用颜色的种数进行分类讨论。 二、考试解读 &#xff08;1&#xff09;涂色问题一般要求相邻的颜色不能相同&#xff0c…

2023年9月9日(星期六)骑行笔架山

2023年9月9日 (星期六)&#xff1a;骑行笔架山&#xff0c;早8:30到9:00&#xff0c; 大观楼门囗集合&#xff0c;9:30准时出发 【因迟到者&#xff0c;骑行速度快者&#xff0c;可自行追赶偶遇。】 偶遇地点: 大观楼门囗集合&#xff0c;家住东&#xff0c;南&#xff0c;北…

将Vivado设为xpr文件的默认打开方式

在linux上安装vivado后不会将vivado设为默认的打开方式&#xff0c;需要修改部分gnome配置文件解决。本文基于RHEL 8.8&#xff08;gnome 3.32.2&#xff09;编写&#xff0c;所涉及的文件路径可能因gnome版本而异。 操作步骤 1. 新增xpr文件类型&#xff08;可选&#xff0c;…

【zookeeper】ZooKeeper的特点及应用场景

ZooKeeper 的特点/设计目标 ZooKeeper&#xff08;动物园管理员&#xff09; &#xff0c;顾名思义&#xff0c;是用来管理Hadoop&#xff08;大象&#xff09;、Hive&#xff08;蜜蜂&#xff09;、Pig&#xff08;小猪&#xff09;的管理员&#xff0c;同时Apache HBase、Ap…