HT7036计量芯片(二)

当初做毕设,把电能计量开发板从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初始化和发送接收函数(寄存器操作级别)。

objectivec 复制代码
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 |

特殊命令展示如下。

下面是我当初写的计量芯片读写函数。

objectivec 复制代码
//=====================================================================
//函数名称: 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;
}
相关推荐
网易独家音乐人Mike Zhou28 分钟前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
zy张起灵34 分钟前
48v72v-100v转12v 10A大功率转换电源方案CSM3100SK
经验分享·嵌入式硬件·硬件工程
lantiandianzi8 小时前
基于单片机的多功能跑步机控制系统
单片机·嵌入式硬件
哔哥哔特商务网8 小时前
高集成的MCU方案已成电机应用趋势?
单片机·嵌入式硬件
跟着杰哥学嵌入式8 小时前
单片机进阶硬件部分_day2_项目实践
单片机·嵌入式硬件
电子科技圈8 小时前
IAR与鸿轩科技共同推进汽车未来
科技·嵌入式硬件·mcu·汽车
东芝、铠侠总代136100683939 小时前
浅谈TLP184小型平面光耦
单片机·嵌入式硬件·物联网·平面
lantiandianzi9 小时前
基于单片机中医药柜管理系统的设计
单片机·嵌入式硬件
嵌入式知识大讲堂10 小时前
HDMI数据传输三种使用场景
单片机
黑客呀10 小时前
[系统安全]Rootkit基础
stm32·单片机·系统安全