当初做毕设,把电能计量开发板从MKL36Z64+AT7022E,换成了STM32L431+HT7036,MCU与计量芯片之间仍采用SPI进行通信。一开始,一直没有打通SPI通信。怀疑是不是MOSI和MISO线接反了;亦或者通信格式不对。过了一段时间,才开始使用STM32CubeIDE进行开发,发现HAL库的SPI函数,可以完成SPI通信,读取到芯片ID(0x7122A0),这才意识到是实验室的SPI构件有问题。后来,自己重写了SPI发送接收函数。
1.1 SPI功能函数
spi_send_receive实际就是HAL库函数HAL_SPI_TransmitReceive的简化版,只是去除了繁杂的验证操作。当时操作的时候,不确定应该使用哪种SPI模式(CPOL,CPHA,极性和相位,是软件片选还是硬件片选),最后都试了一遍,才发现要使用硬件片选,发送和接收同时进行,不能采用先发送后接收的形式。下面的代码是我当初用的SPI初始化和发送接收函数(寄存器操作级别)。
SPI_TypeDef *SPI_ARR[] = {(SPI_TypeDef*)SPI1_BASE, (SPI_TypeDef*)SPI2_BASE, (SPI_TypeDef*)SPI3_BASE};
IRQn_Type table_irq_spi[3] = {SPI1_IRQn, SPI2_IRQn, SPI3_IRQn};
//=====================================================================
//函数名称:spi_init。
//功能说明:SPI初始化
//函数参数:No:模块号,可用参数可参见gec.h文件
// MSTR:SPI主从机选择,0选择为从机,1选择为主机。
// BaudRate:波特率,可取20000、10000、5000、2500、1250、625,单位:bps
// CPOL:CPOL=0:高有效SPI时钟(低无效);CPOL=1:低有效SPI时钟(高无效)
// CPHA:CPHA=0相位为0; CPHA=1相位为1;
//函数返回:无
//=====================================================================
void spi_init(uint8_t No,uint8_t MSTR,uint16_t BaudRate,uint8_t CPOL,uint8_t CPHA)
{
uint32_t temp = 0x00; //
uint16_t Freq_div;
uint8_t BaudRate_Mode;
if(No<0||No>2) No=0; //如果SPI号参数错误则强制选择 SPI1
//(1)使能SPI和对应GPIO时钟
switch(No)
{
case 0:
RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
RCC->AHB2ENR |= RCC_AHB2ENR_GPIOAEN;
//使能PTA5,PTA6,PTA7,PTA15为SPI(SCK,MISO,MOSI,NSS)功能
GPIOA->MODER &= ~(GPIO_MODER_MODE5|GPIO_MODER_MODE6|GPIO_MODER_MODE7|GPIO_MODER_MODE15);
GPIOA->MODER |= (GPIO_MODER_MODE5_1|GPIO_MODER_MODE6_1|GPIO_MODER_MODE7_1|GPIO_MODER_MODE15_1);
GPIOA->AFR[0] &= ~(GPIO_AFRL_AFSEL5|GPIO_AFRL_AFSEL6|GPIO_AFRL_AFSEL7);
GPIOA->AFR[0] |= ((GPIO_AFRL_AFSEL5_0 | GPIO_AFRL_AFSEL5_2) | (GPIO_AFRL_AFSEL6_0 | GPIO_AFRL_AFSEL6_2) | (GPIO_AFRL_AFSEL7_0 | GPIO_AFRL_AFSEL7_2));
GPIOA->AFR[1] &= ~GPIO_AFRH_AFSEL15;
GPIOA->AFR[1] |= (GPIO_AFRH_AFSEL15_0 | GPIO_AFRH_AFSEL15_2);
//配置引脚速率
GPIOA->OSPEEDR |= 0xcc00fc00;
break;
case 1:
//使能SPI2和GPIOB时钟
RCC->APB1ENR1 |= RCC_APB1ENR1_SPI2EN;
RCC->AHB2ENR |= RCC_AHB2ENR_GPIOBEN;
//使能PTB13,PTB14,PTB15,PTB12为SPI(SCK,MISO,MOSI,NSS)功能
GPIOB->MODER &= ~(GPIO_MODER_MODE12|GPIO_MODER_MODE13|GPIO_MODER_MODE14|GPIO_MODER_MODE15);
GPIOB->MODER |= (GPIO_MODER_MODE12_1|GPIO_MODER_MODE13_1|GPIO_MODER_MODE14_1|GPIO_MODER_MODE15_1);
GPIOB->AFR[1] &= ~(GPIO_AFRH_AFSEL12|GPIO_AFRH_AFSEL13|GPIO_AFRH_AFSEL14|GPIO_AFRH_AFSEL15);
GPIOB->AFR[1] |= (GPIO_AFRH_AFSEL12_0 | GPIO_AFRH_AFSEL12_2)|(GPIO_AFRH_AFSEL13_0 | GPIO_AFRH_AFSEL13_2)|(GPIO_AFRH_AFSEL14_0 | GPIO_AFRH_AFSEL14_2)|(GPIO_AFRH_AFSEL15_0 | GPIO_AFRH_AFSEL15_2);
//配置引脚速率
GPIOB->OSPEEDR |= 0xff000000;
break;
case 2:
RCC->APB1ENR1 |= RCC_APB1ENR1_SPI3EN;
RCC->AHB2ENR |= RCC_AHB2ENR_GPIOAEN;
RCC->AHB2ENR |= RCC_AHB2ENR_GPIOCEN;
//使能 PTC10(), PTC11(), PTC12(),PTA4()为SPI(SCK,MISO,MOSI,NSS)功能
GPIOC->MODER &= ~(GPIO_MODER_MODE10|GPIO_MODER_MODE11|GPIO_MODER_MODE12);
GPIOC->MODER |= (GPIO_MODER_MODE10_1|GPIO_MODER_MODE11_1|GPIO_MODER_MODE12_1);
GPIOC->AFR[1] &= ~(GPIO_AFRH_AFSEL10|GPIO_AFRH_AFSEL11|GPIO_AFRH_AFSEL12);
GPIOC->AFR[1] |= ((GPIO_AFRH_AFSEL10_1 | GPIO_AFRH_AFSEL10_2) | (GPIO_AFRH_AFSEL11_1 | GPIO_AFRH_AFSEL11_2) | (GPIO_AFRH_AFSEL12_1 | GPIO_AFRH_AFSEL12_2));
GPIOA->MODER &= ~(GPIO_MODER_MODE4);
GPIOA->MODER |= GPIO_MODER_MODE4_1;
GPIOA->AFR[0] &= ~GPIO_AFRL_AFSEL4;
GPIOA->AFR[0] |= (GPIO_AFRL_AFSEL4_1 | GPIO_AFRL_AFSEL4_2);
//配置引脚速率
GPIOC->OSPEEDR |=0x3F00000; //极高速
GPIOC->PUPDR &=~ 0x3F00000; //无上下拉
//配置引脚速率
GPIOA->OSPEEDR |= 0x300; //极高速
GPIOA->PUPDR &=~ 0x300; //无上下拉
break;
default:
break;
}
//(2)配置CR1寄存器
//(2.1)暂时禁用SPI功能
SPI_ARR[No]->CR1 &= ~SPI_CR1_SPE;
//(2.2)配置SPI主从机模式
if(MSTR == 1) //主机模式
{
temp |= SPI_CR1_MSTR;
//配置NSS脚由软件控制,置位为1
temp |= SPI_CR1_SSI|SPI_CR1_SSM;
}
else //从机模式
{
temp &= ~SPI_CR1_MSTR;
//配置NSS脚由软件控制,置位为0
temp |= SPI_CR1_SSM;
temp &= ~SPI_CR1_SSI;
}
//(2.3)配置SPI相位和极性
if(CPOL == 1)
temp |= SPI_CR1_CPOL;
else
temp &= ~SPI_CR1_CPOL;
if(CPHA == 1)
temp |= SPI_CR1_CPHA;
else
temp &= ~SPI_CR1_CPHA;
//(2.4)配置SPI波特率
Freq_div = SystemCoreClock/1000/BaudRate;
BaudRate_Mode = 0;
while(Freq_div/2 >= 2)
{
BaudRate_Mode++;
Freq_div = Freq_div/2;
}
temp |= (BaudRate_Mode<<3);
// printf("BaudRate_Mode=%d\r\n",BaudRate_Mode);
//(2.5)统一配置CR1寄存器
SPI_ARR[No]->CR1 |= temp;
//【20201021】SPI模块的片选脚默认为硬件SPI控制
if(No<2)
{
SPI_ARR[No]->CR1 &=~ (1<<9);
SPI_ARR[No]->CR1 |= (1<<8);
}
//(3)配置CR2寄存器
temp = 0;
//(3.1)配置数据为8bit
// temp |= SPI_CR2_DS;
temp |= (SPI_CR2_DS_0|SPI_CR2_DS_1|SPI_CR2_DS_2);
temp |= SPI_CR2_FRXTH;
if(No<2)
{
temp |= SPI_CR2_SSOE;
}
SPI_ARR[No]->CR2 |= temp;
//(4)使能SPI功能
SPI_ARR[No]->CR1 |= SPI_CR1_SPE;
}
//=====================================================================
//函数名称:spi_send_receive.
//功能说明:SPI接收数据。当n=1时,就是发送接收一个字节的数据……
//函数参数:No:模块号,可用参数可参见gec.h文件
// n: 要发送的字节个数。范围为(1~255),
// pTxData[]:发送数据存放的首地址。
// pRxData[]:接收到的数据存放的首地址。
//函数返回:1:成功;0:失败。
//=====================================================================
uint8_t spi_send_receive(uint8_t No,uint8_t pTxData[],uint8_t pRxData[],uint32_t Size)
{
if(No<0||No>2) return 0; //如果SPI号参数错误则发送失败
uint16_t Tsize,Rsize,txallowed;
Tsize=Size; //要发送的字节长度
Rsize=Size; //要接收的字节长度
txallowed=1; //接收发送标志位,1:发送;0:接收
//若SPI未使能,则使能
if ((SPI_ARR[No]->CR1 & SPI_CR1_SPE) != SPI_CR1_SPE)
{
SPI_ARR[No]->CR1 |= SPI_CR1_SPE;
}
if(Size>1) //此外,16位操作也要使用此模式
SPI_ARR[No]->CR2&=~SPI_CR2_FRXTH;
else
SPI_ARR[No]->CR2|=SPI_CR2_FRXTH;
if(Size==1||(!BGET(2,SPI_ARR[No]->CR1)))
{
if(Size>1)
{
SPI_ARR[No]->DR = *((uint16_t *)pTxData);
pTxData+=2;
Tsize-=2;
}
else
{
*(__IO uint8_t *)&SPI_ARR[No]->DR = (*pTxData);
Tsize--;
}
}
while ((Tsize > 0U) || (Rsize> 0U))
{
/* Check TXE flag */
if (((SPI_ARR[No]->SR&SPI_SR_TXE)==SPI_SR_TXE) && (Tsize > 0U) && (txallowed == 1U))
{
if (Tsize > 1U)
{
SPI_ARR[No]->DR = *((uint16_t *)pTxData);
pTxData+=2;
Tsize-=2;
}
else
{
*(__IO uint8_t *)&SPI_ARR[No]->DR = *((uint8_t *)pTxData);
Tsize--;
}
/* Next Data is a reception (Rx). Tx not allowed */
txallowed = 0U;
}
/* Wait until RXNE flag is reset */
if (((SPI_ARR[No]->SR&SPI_SR_RXNE)==SPI_SR_RXNE) && (Rsize> 0U))
{
if (Rsize> 1U)
{
*((uint16_t *)pRxData) = (uint16_t)SPI_ARR[No]->DR;
pRxData+=2;
Rsize-=2;
if (Rsize<= 1U)
{
/* Set RX Fifo threshold before to switch on 8 bit data size */
SET_BIT(SPI_ARR[No]->CR2, SPI_CR2_FRXTH);
}
}
else
{
(*(uint8_t *)pRxData) = *(__IO uint8_t *)&SPI_ARR[No]->DR;
Rsize-= 1U;
}
/* Next Data is a Transmission (Tx). Tx is allowed */
txallowed = 1U;
}
}
if((Tsize>0)||(Rsize>0))
return 0;
else
return 1; //成功
}
1.2 计量芯片读写函数
命令字节bit7 | 命令字节bit6 | 功能 | byte1~byte4 |
1 | 1 | 写入特殊命令 | byte1 cmd,byte2~byte4数据 |
1 | 0 | 更新校表数据 | byte1 cmd,byte2数据0,byte3、byte4数据 |
0 | x | 读取HT7036寄存器数据 | byte1 address,byte2~byte4冗余数据 0xFF |
特殊命令展示如下。
下面是我当初写的计量芯片读写函数。
//=====================================================================
//函数名称: write_SpeicalCommand
//函数返回: 无
//参数说明: command:特殊命令(1字节);data:特殊命令对应的数据(3字节)
//功能概要:写特殊命令
//注:(1)写特殊命令对应数据的时候先写高字节;写3字节
// (2)命令和数据
// 命令 数据
// 切换到读校表寄存器: 0xC6 0x00005A
// 切换到读计量寄存器: 0xC6 0x000000
// 清校表数据至上电状态: 0xC3 0x000000
// 软件复位: 0xD3 0x000000
//=====================================================================
void write_SpeicalCommand(uint8_t command,uint32_t data)
{
uint8_t dummyData[4]={0xFF,0xFF,0xFF,0xFF};
uint8_t receiveData[4]={0xFF,0xFF,0xFF,0xFF};
//(1)得到特殊命令对应的数据
dummyData[0]=(command|0x80);//命令
//数据
dummyData[1]=data>>16;//取最高8位
dummyData[2]=data>>8; //取中8位
dummyData[3]=data; //取低8位
spi_send_receive(ATT_1,dummyData,receiveData,4);
//防止频繁的切换
// ATT_Dly_ms(20);
}
//=====================================================================
//函数名称: write_CorrectionParameter
//函数返回: 无
//参数说明: address:待写寄存器的地址(1字节);data:待写入的数据(2字节)
//功能概要:写校表参数,向地址为address的寄存器写data数据
//注:(1)先写高字节;写3字节(最高字节是0x00,在函数内部实现),传参2字节
//=====================================================================
void write_CorrectionParameter(uint8_t address,uint16_t data)
{
uint8_t dummyData[4]={0xFF,0xFF,0xFF,0xFF};
uint8_t receiveData[4]={0xFF,0xFF,0xFF,0xFF};
//(1)组合写命令(Bit7:1表示写命令;Bit6…0:表示寄存器地址)
dummyData[0]=(address|0x80);
dummyData[1]=0;
dummyData[2]=data>>8; //用来存放校表数据
dummyData[3]=data;
//(2)发送写命令
spi_send_receive(ATT_1,dummyData,receiveData,4);
// ATT_Dly_ms(20);
}
//=====================================================================
//函数名称: read_CorrectionParameter
//函数返回: 返回指定地址的校表参数
//参数说明: address:待读寄存器的地址(1字节);
//功能概要:读校表参数,从地址为address的寄存器中读数据,并返回
//注:(1)先读高字节;读3字节(最高字节是0x00),只返回低2字节
//=====================================================================
uint16_t read_CorrectionParameter(uint8_t address)
{
uint8_t dummyData[4]={0xFF,0xFF,0xFF,0xFF};
uint8_t receiveData[4]={0xFF,0xFF,0xFF,0xFF};
uint16_t data=0; //存放读取的校表参数
//(1)切换到“读取校表参数”模式
write_SpeicalCommand(0xC6,0x00005A);
//(2)组合读命令(Bit7:0 表示读命令;Bit6…0:表示寄存器地址)
dummyData[0]=address&(0x7F);
dummyData[1]=0xFF;
dummyData[2]=0xFF;
dummyData[3]=0xFF;
spi_send_receive(ATT_1,dummyData,receiveData,4);
data=(receiveData[2]<<8)|receiveData[3];
//防止频繁的读校表参数
// ATT_Dly_ms(20);
//(5)切换到“读取计量参数”模式(系统复位后默认的模式)
write_SpeicalCommand(0xC6,0x000000);
//(6)返回读取到的校表参数
return data;
}
//=====================================================================
//函数名称: read_MeasurementParameter
//函数返回: 返回指定地址的计量参数(大小占三字节)
//参数说明: address:待读计量参数寄存器的地址(1字节)
//功能概要:读计量参数,从地址为address的寄存器中读数据,并返回
//注:(1)返回读取数据的时候是先返回高字节;返回3字节
//=====================================================================
uint32_t read_MeasurementParameter(uint8_t address)
{
uint8_t dummyData[4]={0xFF,0xFF,0xFF,0xFF};
uint8_t receiveData[4]={0xFF,0xFF,0xFF,0xFF};
uint32_t data=0;//用来存放读取的数据
//(1)组合读命令(Bit7:0 表示读命令;Bit6…0:表示寄存器地址)
dummyData[0]=(address&0x7F);
dummyData[1]=0xFF;
dummyData[2]=0xFF;
dummyData[3]=0xFF;
spi_send_receive(ATT_1,dummyData,receiveData,4);
data=(receiveData[1]<<16)|(receiveData[2]<<8)|receiveData[3];
//加延时,防止读太频繁
// ATT_Dly_us(20);
//(4)返回读取的数据
return data;
}