SPI介绍
SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,如今越来越多的芯片集成了这种通信协议。
案例1:
STM32F103C8+LD3320语音方案
STM32的SPI介绍:
对应的引脚:
SPI1_NSS ===========> PA4
SPI1_SCK ===========> PA5
SPI1_MISO ===========> PA6
SPI1_MOSI ===========> PA7
SPI2_NSS ===========> PB12
SPI2_SCK ===========> PB13
SPI2_MISO ===========> PB14
SPI2_MOSI ===========> PB15
利用SPI2和LD3320芯片4线链接。
代码实现
STM32对SPI2的初始化代码如下:
//SPI2初始化
static void LD3320_SPI_cfg(void)
{
SPI_InitTypeDef SPI_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
//spi端口配置
RCC_APB1PeriphClockCmd(LD3320SPI_CLK,ENABLE);
RCC_APB2PeriphClockCmd(LD3320WR_GPIO_CLK | LD3320SPIMISO_GPIO_CLK | LD3320SPIMOSI_GPIO_CLK | LD3320SPISCK_GPIO_CLK,ENABLE);
GPIO_InitStructure.GPIO_Pin = LD3320SPIMISO_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(LD3320SPIMISO_GPIO_PORT,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = LD3320SPIMOSI_PIN;
GPIO_Init(LD3320SPIMOSI_GPIO_PORT,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = LD3320SPISCK_PIN;
GPIO_Init(LD3320SPISCK_GPIO_PORT,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = LD3320WR_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(LD3320WR_GPIO_PORT, &GPIO_InitStructure);
LD_CS_H();
SPI_Cmd(LD3320SPI, DISABLE);
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //主模式
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //8位
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //时钟极性 空闲状态时,SCK保持低电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //时钟相位 数据采集从第一个时钟边沿开始
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //软件产生NSS
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64; //波特率控制 SYSCLK/128
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //数据保卫在前
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC多项式寄存器初始值为7
SPI_Init(LD3320SPI, &SPI_InitStructure);
SPI_Cmd(LD3320SPI, ENABLE);
SPI2_SetSpeed(SPI_BaudRatePrescaler_2);//设置为18M时钟,高速模式
}
//寄存器读写操作
static uint8 spi_send_byte(uint8 byte)
{
while (SPI_I2S_GetFlagStatus(LD3320SPI, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData(LD3320SPI,byte);
while (SPI_I2S_GetFlagStatus(LD3320SPI,SPI_I2S_FLAG_RXNE) == RESET);
return SPI_I2S_ReceiveData(LD3320SPI);
}
其中SPI2的管教定义如下:
#define LD3320SPI SPI2
#define LD3320SPI_CLK RCC_APB1Periph_SPI2
#define LD3320CS_PIN GPIO_Pin_12
#define LD3320CS_GPIO_PORT GPIOB
#define LD3320CS_GPIO_CLK RCC_APB2Periph_GPIOB
#define LD_CS_H() GPIO_SetBits(GPIOB, GPIO_Pin_12)
#define LD_CS_L() GPIO_ResetBits(GPIOB, GPIO_Pin_12)
#define LD3320SPISCK_PIN GPIO_Pin_13
#define LD3320SPISCK_GPIO_PORT GPIOB
#define LD3320SPISCK_GPIO_CLK RCC_APB2Periph_GPIOB
#define LD3320SPIMISO_PIN GPIO_Pin_14
#define LD3320SPIMISO_GPIO_PORT GPIOB
#define LD3320SPIMISO_GPIO_CLK RCC_APB2Periph_GPIOB
#define LD3320SPIMOSI_PIN GPIO_Pin_15
#define LD3320SPIMOSI_GPIO_PORT GPIOB
#define LD3320SPIMOSI_GPIO_CLK RCC_APB2Periph_GPIOB
查看LD3320数据手册
串行SPI方式写时序
写的时候要先给 SDI 发送一个 “写”指令(04H),然后给 SDI 发送 8 位寄存器地址,再给 SDI 发送 8 位数据。在这期间, SCS*必须保持在有效(低电平)。
static void LD_WriteReg(uint8 addr,uint8 data1)
{
LD_CS_L();
LD_SPIS_L();
spi_send_byte(0x04);
spi_send_byte(addr);
spi_send_byte(data1);
LD_CS_H();
LD_SPIS_H();
}
SPI方式读时序
写的时候要先给 SDI 发送一个 “读”指令(05H),然后给 SDI 发送 8 位寄存器地址,再从 SDO 接受 8 位数据。在这期间, SCS*必须保持在有效(低电平)。
static uint8 LD_ReadReg(uint8 reg_add)
{
uint8 i;
LD_CS_L();
LD_SPIS_L();
spi_send_byte(0x05);
spi_send_byte(reg_add);
i=spi_send_byte(0x00);
LD_CS_H();
LD_SPIS_H();
return(i);
}
SPI寄存器的详细说明在LD3320开发手册第6页
语音识别
语音识别的操作顺序是:
语音识别用初始化(包括通用初始化)→写入识别列表→开始识别,
并准备好中断响应函数,打开中断允许位。
这里需要说明一下,如果不用中断方式,也可以通过查询方式工作。在“开
始识别”后,读取寄存器 B2H 的值,如果为 21H 就表示有识别结果产生。
在此之后读取候选项等操作与中断方式相同。
- 1、通用初始化
static void LD_Init_Common(void)
{
LD_ReadReg(0x06); //FIFO状态
LD_WriteReg(0x17, 0x35); //进行软复位
LD3320_delay(5);
LD_ReadReg(0x06); //FIFO状态
LD_WriteReg(0x89, 0x03); //模拟电路初始化控制写03H,MP3播放时候写FFH
LD3320_delay(5);
LD_WriteReg(0xCF, 0x43); //内部省电摸模式初始化写43H
LD3320_delay(5);
LD_WriteReg(0xCB, 0x02); //ASR:读取ASR结果(候补4)
/*PLL setting*/
LD_WriteReg(0x11, LD_PLL_11); //时钟频率设置
if (nLD_Mode == LD_MODE_MP3) //MP3模式
{
LD_WriteReg(0x1E, 0x00); //ADC专用控制,应初始化为00H
LD_WriteReg(0x19, LD_PLL_MP3_19); //时钟设置频率2
LD_WriteReg(0x1B, LD_PLL_MP3_1B); //时钟设置频率3
LD_WriteReg(0x1D, LD_PLL_MP3_1D); //时钟设置频率4
}
else
{
LD_WriteReg(0x1E,0x00); //ADC专用控制,应初始化为00H
LD_WriteReg(0x19, LD_PLL_ASR_19); //时钟设置频率2
LD_WriteReg(0x1B, LD_PLL_ASR_1B); //时钟设置频率3
LD_WriteReg(0x1D, LD_PLL_ASR_1D); //时钟设置频率4
}
LD3320_delay(5);
LD_WriteReg(0xCD, 0x04); //允许DSP休眠
LD_WriteReg(0x17, 0x4c); //写4CH可以是DSP休眠,比较省电
LD3320_delay(1);
LD_WriteReg(0xB9, 0x00); //ASR:当前添加识别的字符串长度(拼音字符串)初始化时写入00H,每添加一条识别语句后要设定一次
LD_WriteReg(0xCF, 0x4F); //ASR初始化时写入4FH
LD_WriteReg(0x6F, 0xFF); //对芯片进行初始化设置为0xFF
}
- 2、语音识别用初始化
//语音识别初始化
static void LD_Init_ASR(void)
{
nLD_Mode=LD_MODE_ASR_RUN;
LD_Init_Common();
LD_WriteReg(0xBD, 0x00); //初始化控制寄存器,写入00H,启动ASR,写入02H,为MP3
LD_WriteReg(0x17, 0x48); //48H激活DSP
LD3320_delay(5);
LD_WriteReg(0x3C, 0x80); //FIFO_EXT下限低8位
LD_WriteReg(0x3E, 0x07); //FIFO_EXT下限高8位
LD_WriteReg(0x38, 0xff); //FIFO_EXT上限低8位
LD_WriteReg(0x3A, 0x07); //FIFO_EXT上限高8位
LD_WriteReg(0x40, 0); //FIFO_EXT MCU水线低8位
LD_WriteReg(0x42, 8); //FIFO_EXT MCU水线高8位
LD_WriteReg(0x44, 0); //FIFO_EXT DSP水线低8位
LD_WriteReg(0x46, 8); //FIFO_EXT DSP水线高8位
LD3320_delay( 1 );
}
- 3、写入识别列表
列表的规则是,每个识别条目对应一个特定的编号(1 个字节),不同的识别条目的编号可以相同,而且不用连续。本芯片最多支持 50个识别条目,每个识别条目是标准普通话的汉语拼音(小写),每 2个字(汉语拼音)之间用一个空格间隔。下面是一个简单的例子:
等待芯片空闲
//校验LD3320时候在空闲状态
uint8 LD_Check_ASRBusyFlag_b2(void)
{
uint8 j;
uint8 flag = 0;
for (j=0; j<10; j++)
{
if (LD_ReadReg(0xb2) == 0x21) //读取寄存器0xB2的值,如果是0x21则是空闲状态
{
flag = 1;
break;
}
LD3320_delay(10);
}
return flag;
}
设定编号
LD_WriteReg(0xc1, pCode[k] ); //ASR:识别字Index(00H-FFH)
LD_WriteReg(0xc3, 0); //ASR:识别字添加时输入00
LD_WriteReg(0x08, 0x04); //清除FIFO内容 写入04H---->清除FIFO_EXT
LD3320_delay(1);
LD_WriteReg(0x08, 0x00); //清除FIFO_DATA
LD3320_delay(1);
将字符串的数据按顺序,一次写入寄存器0x05
for (nAsrAddLength=0; nAsrAddLength<DATE_B; nAsrAddLength++)
{
if (sRecog[k][nAsrAddLength] == 0)
break;
LD_WriteReg(0x5, sRecog[k][nAsrAddLength]);
}
讲【字符长度】写入寄存器B9
向寄存器B2写入0xFF
向寄存器37写入0x04
LD_WriteReg(0xb9, nAsrAddLength);
LD_WriteReg(0xb2, 0xff);
LD_WriteReg(0x37, 0x04);
- 4、开始识别
设置几个相关的寄存器,就可以控制LD3320芯片开始语音识别。
static uint8 LD_AsrRun(void)
{
LD_WriteReg(0x35, MIC_VOL); //设置MIC的音量
LD_WriteReg(0x1C, 0x09); //09H Reserve保留命令字,
LD_WriteReg(0xBD, 0x20); // 20H,Reserve保留命令字
LD_WriteReg(0x08, 0x01); //清除FIFO_DATA
LD3320_delay( 5 );
LD_WriteReg(0x08, 0x00); //
LD3320_delay( 5);
if(LD_Check_ASRBusyFlag_b2() == 0) //判断是否空闲状态
{
return 0;
}
LD_WriteReg(0xB2, 0xff); //0x21表示闲。
LD_WriteReg(0x37, 0x06); //通知DSP开始识别语音
LD_WriteReg(0x37, 0x06); //通知DSP开始识别语音
LD3320_delay(5);
LD_WriteReg(0x1C, 0x0b); //写麦克风输入ADC通道可用
LD_WriteReg(0x29, 0x10); //FIFO中断允许,1表示允许,0表示不允许
LD_WriteReg(0xBD, 0x00); //启动ASR模块
return 1;
}
- 5、响应中断
如果麦克风采集到声音,不管是否识别出正常结果,都会产生一个中断信号。而中断程序要根据寄存器的值分析结果。
读取 BA 寄存器的值,可以知道有几个候选答案,而 C5 寄存器里的答案是得分最高、最可能正确的答案。
STM32中断处理
//中断初始化
static void LD3320_EXTI_Cfg(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
RCC_APB2PeriphClockCmd(LD3320IRQ_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin =LD3320IRQ_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(LD3320IRQ_GPIO_PORT, &GPIO_InitStructure);
//外部中断线配置
GPIO_EXTILineConfig(LD3320IRQEXIT_PORTSOURCE, LD3320IRQPINSOURCE);
EXTI_InitStructure.EXTI_Line = LD3320IRQEXITLINE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger =EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
//中断嵌套配置
NVIC_InitStructure.NVIC_IRQChannel = LD3320IRQN;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
中断处理
void EXTI9_5_IRQHandler(void)
{
if(EXTI_GetITStatus(LD3320IRQEXITLINE)!= RESET )
{
ProcessInt();
printf("½øÈëÖжÏ5\r\n");
EXTI_ClearFlag(LD3320IRQEXITLINE);
EXTI_ClearITPendingBit(LD3320IRQEXITLINE);//Çå³ýLINEÉϵÄÖжϱê־λ
}
}
//中断处理函数
static void ProcessInt(void)
{
uint8 nAsrResCount=0;
ucRegVal = LD_ReadReg(0x2B);
//语音识别产生的中断
//(有声音输入,不论识别成功或失败都有中断)
LD_WriteReg(0x29,0) ; //关掉FIFO中断
LD_WriteReg(0x02,0) ; //关掉FIFO中断
if((ucRegVal & 0x10) && LD_ReadReg(0xb2)==0x21 && LD_ReadReg(0xbf)==0x35) //正确识别到一次ASR
{
nAsrResCount = LD_ReadReg(0xba); //N个识别候选
if(nAsrResCount>0 && nAsrResCount<=4)
{
nAsrStatus=LD_ASR_FOUNDOK;
}
else
{
nAsrStatus=LD_ASR_FOUNDZERO;
}
}
else
{
nAsrStatus=LD_ASR_FOUNDZERO;//执行没有识别
}
LD_WriteReg(0x2b,0); //
LD_WriteReg(0x1C,0); //写0:ADC不可用
LD_WriteReg(0x29,0); //关FIFO中断
LD_WriteReg(0x02,0); //关FIFO中断
LD_WriteReg(0x2B,0); //中断请求编号
LD_WriteReg(0xBA,0); //中断辅助信息
LD_WriteReg(0xBC,0); //识别过程强制结束
LD_WriteReg(0x08,1); //清除FIFO_DATA
LD_WriteReg(0x08,0); //清除FIFO_DATA 再次写0
}
一次识别ASR识别流程结束,读取ASR识别结果
LD_ReadReg(0xc5);