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;
}
相关推荐
cjy_Somnr2 小时前
keil5报错显示stm32的SWDIO未连接不能烧录
stm32·单片机·嵌入式硬件
Lay_鑫辰3 小时前
西门子诊断-状态和错误位(“轴”工艺对象 V1...3)
服务器·网络·单片机·嵌入式硬件·自动化
无垠的广袤5 小时前
【工业树莓派 CM0 NANO 单板计算机】本地部署 EMQX
linux·python·嵌入式硬件·物联网·树莓派·emqx·工业物联网
雲烟7 小时前
嵌入式设备EMC安规检测参考
网络·单片机·嵌入式硬件
泽虞7 小时前
《STM32单片机开发》p7
笔记·stm32·单片机·嵌入式硬件
田甲8 小时前
【STM32】 数码管驱动
stm32·单片机·嵌入式硬件
up向上up8 小时前
基于51单片机垃圾箱自动分类加料机快递物流分拣器系统设计
单片机·嵌入式硬件·51单片机
纳祥科技17 小时前
Switch快充方案,内置GaN,集成了多个独立芯片
单片机
单片机日志18 小时前
【单片机毕业设计】【mcugc-mcu826】基于单片机的智能风扇系统设计
stm32·单片机·嵌入式硬件·毕业设计·智能家居·课程设计·电子信息
松涛和鸣19 小时前
从零开始理解 C 语言函数指针与回调机制
linux·c语言·开发语言·嵌入式硬件·排序算法