基于HAL库STM32F1与AD7705、AD7706通信(SPI)

STM32F1基于HAL库与AD7705、AD7706通信(SPI)


前言

使用STM32F1和一个AD7705和一个AD7706进行通信,网上参考内容很多,一定要参考英文手册


引言

SPI 是英语 Serial Peripheral interface 缩写,顾名思义就是串行外围设备接口
在这里插入图片描述
SPI 的引脚信息:
MISO(Master In / Slave Out)主设备数据输入,从设备数据输出。
MOSI(Master Out / Slave In)主设备数据输出,从设备数据输入。
SCLK(Serial Clock)时钟信号,由主设备产生。
CS(Chip Select)从设备片选信号,由主设备产生。

SPI 的工作原理:
在主机和从机都有一个串行移位寄存器,主机通过向它的 SPI 串行寄存器写入一个字节来发起一次传输。串行移位寄存器通过 MOSI 信号线将字节传送给从机,从机也将自己的串行移位寄存器中的内容通过 MISO 信号线返回给主机。这样,两个移位寄存器中的内容就被交换。外设的写操作和读操作是同步完成的。如果只是进行写操作,主机只需忽略接收到的字节。反之,若主机要读取从机的一个字节,就必须发送一个空字节引发从机传输。
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/234d10fc5e56481d821d7c01c4dee5c6.png

SPI 的传输方式:
SPI 总线具有三种传输方式:全双工、单工以及半双工传输方式。
全双工通信: 在任何时刻,主机与从机之间都可以同时进行数据的发送和接收。
单工通信: 在同一时刻,只有一个传输的方向,发送或者是接收。
半双工通信: 在同一时刻,只能为一个方向传输数据。

SPI 工作模式:

SPI工作模式CPOLCPHASCL空闲状态采样边沿采样时刻
000低电平上升沿奇数边沿
101低电平上降沿偶数边沿
210高电平上升沿奇数边沿
311高电平上降沿偶数边沿

CPOL,详称 Clock Polarity,就是时钟极性,当主从机没有数据传输的时候 SCL 线的电平状态(即空闲状态)。 假如空闲状态是高电平, CPOL = 1;若空闲状态时低电平,那么 CPOL = 0。
CPHA,详称 Clock Phase,就是时钟相位。在这里先科普一下数据传输的常识: 同步通信时,数据的变化和采样都是在时钟边沿上进行的,每一个时钟周期都会有上升沿和下降沿两个边沿,那么数据的变化和采样就分别安排在两个不同的边沿,由于数据在产生和到它稳定是需要一定的时间,那么假如我们在第 1 个边沿信号把数据输出了,从机只能从第 2 个边沿信号去采样这个数据。
在2.2中SPI初始化中配置相关参数

一、AD7705、AD7706

1.1 引脚排列

在这里插入图片描述
当引用一个AD7705和一个AD7706时,逻辑输出引脚(DRDY)不能共用一个,当新的数据准备好了,DRDY置低电平,告诉主机你来读取数据吧(踩坑1)

如果有两片以上级联CS、DRDY引脚不能共用;SCK、MISO、MOSI引脚可以共用

1.2 功能方框图

在这里插入图片描述
从图中可以看出,只有一个PGA且增益的范围为1~128。AD7705、AD7706都是双通道,在不同读取不同通道时,如果要设置不同增益,需要在每次读取之前设置寄存器。(踩坑:一开始把每个通道初始化,虽然能够读取数据,但是增益不对)

1.3 硬件电路设计

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


二、程序

2.1 配置DRDY引脚

使用中断的方式:

 /**
  * @brief  配置 7705--PB0,7706--PB1 为线中断口,并设置中断优先级
  * @param  无
  * @retval 无
  */
void EXTI_AD7705_7706_Config(void)
{	
  GPIO_InitTypeDef GPIO_InitStruct = {0};
	
	/*开启AD7705中断GPIO口的时钟*/
  __HAL_RCC_GPIOB_CLK_ENABLE();

  /* 选择 EXTI 中断源 */	
  GPIO_InitStruct.Pin = AD7705_DRDY_GPIO_PIN;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(AD7705_DRDY_GPIO_PORT, &GPIO_InitStruct);
	
  GPIO_InitStruct.Pin = AD7706_DRDY_GPIO_PIN;
  HAL_GPIO_Init(AD7706_DRDY_GPIO_PORT, &GPIO_InitStruct);
			
	/* EXTI0 interrupt init*/
  HAL_NVIC_SetPriority(EXTI0_IRQn, 2, 0);
  HAL_NVIC_EnableIRQ(EXTI0_IRQn);
	
	/* EXTI1 interrupt init*/
  HAL_NVIC_SetPriority(EXTI1_IRQn, 2, 0);
  HAL_NVIC_EnableIRQ(EXTI1_IRQn);
}

2.2 SPI引脚、初始化

这里代码模块更好:

/**
  * @brief  SPI_AD7705初始化
  * @param  无
  * @retval 无
  */
void AD7705_7706_SPI_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  
  /* 使能 AD7705_SPI 及GPIO 时钟 */
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

  /*!< AD7705_SPI 时钟使能 */
	__HAL_RCC_SPI1_CLK_ENABLE();
  
  /*!< 配置 AD7705_SPI 引脚: SCK */
  GPIO_InitStruct.Pin = SPI_SCK_GPIO_PIN;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(SPI_SCK_GPIO_PORT, &GPIO_InitStruct);	
  
	/*!< 配置 AD7705_SPI 引脚: MOSI */
  GPIO_InitStruct.Pin = SPI_MOSI_GPIO_PIN;
  HAL_GPIO_Init(SPI_MOSI_GPIO_PORT, &GPIO_InitStruct);
  
	/*!< 配置 AD7705_SPI 引脚: MISO */
  GPIO_InitStruct.Pin = SPI_MISO_GPIO_PIN;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_INPUT;
  HAL_GPIO_Init(SPI_MISO_GPIO_PORT, &GPIO_InitStruct);

	/*!< 配置 AD7705_片选 引脚: CS */
  GPIO_InitStruct.Pin = AD7705_CS_GPIO_PIN;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  HAL_GPIO_Init(AD7705_CS_GPIO_PORT, &GPIO_InitStruct);
	
  GPIO_InitStruct.Pin = AD7706_CS_GPIO_PIN;
  HAL_GPIO_Init(AD7706_CS_GPIO_PORT, &GPIO_InitStruct);
	

  /* 停止信号 AD7705: CS1 CS2 @引脚高电平*/
  AD7705_CS_High();
  AD7706_CS_High();

  /* AD7705_SPI 模式配置 */
  // AD7705芯片 支持SPI模式0及模式3,据此设置CPOL CPHA 	
  hspi1.Instance = SPI1;
  hspi1.Init.Mode = SPI_MODE_MASTER;//设置SPI工作模式:设置为主SPI
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;//设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
  hspi1.Init.DataSize = SPI_DATASIZE_8BIT;//设置SPI的数据大小:SPI发送接收8位帧结构
  hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH;//选择了串行时钟的稳态:时钟悬空高
  hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;//数据捕获于第二个时钟沿
  hspi1.Init.NSS = SPI_NSS_SOFT;//NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_64;//定义波特率预分频的值:波特率预分频值为64
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;//指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi1.Init.CRCPolynomial = 10;//CRC值计算的多项式
  if (HAL_SPI_Init(&hspi1) != HAL_OK)
  {
    Error_Handler();
  }
	
  /* 使能 FLASH_SPI  */
  __HAL_SPI_ENABLE(&hspi1);
}

2.3 读写一个字节

 /**
  * @brief  读写一个字节
	* @param  CS,TxData
  * @retval 读取的8位数据
  */
void SPIx_ReadWriteByte(uint8_t* TxData, uint8_t* RxData,uint8_t num)
{
	while (__HAL_SPI_GET_FLAG(&hspi1, SPI_FLAG_TXE) == RESET); //检查指定的SPI标志位设置与否:发送缓存空标志位
	
	HAL_SPI_TransmitReceive(&hspi1,TxData,RxData,num,1000);      
}

2.4 读写16位的数据

 /**
  * @brief  AD7705读16位数据
	* @param  无
  * @retval 读取的16位数据
  */
uint16_t ReadAD7705_7706_16BitValue(void)
{ 
	uint16_t Ret = 0;

	spi_tx_data[0] = 0xFF;
	spi_tx_data[1] = 0xFF;
	
	SPIx_ReadWriteByte(spi_tx_data,spi_rx_data,2);
	
	Ret = spi_rx_data[0] << 8; 
	Ret |= spi_rx_data[1]; 

	return(Ret);
}

2.5 写通道数据

 /**
  * @brief  写AD7705 Channel数据
  * @param  Channel
  * @retval 
  */
uint16_t ReadAD7705(uint8_t Channel)
{
	uint16_t Ret = 0;
	
	AD7705_CS_Low();
	
	AD7705_ch_Init(Channel);
	
	spi_tx_data[0] = 0x38 + Channel;
	SPIx_ReadWriteByte(spi_tx_data,spi_rx_data,1);       //设置读当前通道数据

	Ret = ReadAD7705_7706_16BitValue();
	
	AD7705_CS_High();
	
	return Ret;
}

 /**
  * @brief  写AD7706 Channel数据
  * @param  Channel
  * @retval 
  */
uint16_t ReadAD7706(uint8_t Channel)
{
	uint16_t Ret = 0;
	
	AD7706_CS_Low();
	
	AD7706_ch_Init(Channel);
	
	spi_tx_data[0] = 0x38 + Channel;
	SPIx_ReadWriteByte(spi_tx_data,spi_rx_data,1);       //设置读当前通道数据

	Ret = ReadAD7705_7706_16BitValue();
	
	AD7706_CS_High();
	
	return Ret;
}

2.6 写片内寄存器数据

**
  * @brief  AD7705设置有效通道的增益
  * @param  Channel通道
  * @retval 无
  */
void AD7705_ch_Init(uint8_t Channel)
{
	uint8_t i;	
		
	for(i = 0; i < 5; i++)
	{
		spi_tx_data[0] = 0xFF;
		SPIx_ReadWriteByte(spi_tx_data,spi_rx_data,1);      //持续DIN高电平写操作,恢复AD7705接口
	}	
	switch(Channel)
	{
		case 0 :
			spi_tx_data[0] = 0x20;
			SPIx_ReadWriteByte(spi_tx_data,spi_rx_data,1);        //通道1 ,下一个写时钟寄存器
			spi_tx_data[0] = 0x04;
			SPIx_ReadWriteByte(spi_tx_data,spi_rx_data,1);        //写时钟寄存器设置更新速率为50Hz
			spi_tx_data[0] = 0x10;
			SPIx_ReadWriteByte(spi_tx_data,spi_rx_data,1);        //通道1 ,下一个写设置寄存器
			spi_tx_data[0] = 0x76;
			SPIx_ReadWriteByte(spi_tx_data,spi_rx_data,1);        //写设置寄存器 ,设置成单极性、缓冲、增益为8、滤波器工作、自校准 /**/
      break;
		
		case 1 :			
			spi_tx_data[0] = 0x21;
			SPIx_ReadWriteByte(spi_tx_data,spi_rx_data,1);        //通道2 ,下一个写时钟寄存器
			spi_tx_data[0] = 0x04;
			SPIx_ReadWriteByte(spi_tx_data,spi_rx_data,1);        //写时钟寄存器设置更新速率为50Hz
			spi_tx_data[0] = 0x11;
			SPIx_ReadWriteByte(spi_tx_data,spi_rx_data,1);        //通道2 ,下一个写设置寄存器
			spi_tx_data[0] = 0x46;
			SPIx_ReadWriteByte(spi_tx_data,spi_rx_data,1);        //写设置寄存器 ,设置成单极性、缓冲、增益为8、滤波器工作、自校准 
		break;
	}
}

 /**
  * @brief  AD7706有效通道的增益设置
	* @param  CS
  * @retval 无
  */
void AD7706_ch_Init(uint8_t Channel)
{
	uint8_t i;
	
	for(i = 0; i < 5; i++)
	{
		spi_tx_data[0] = 0xFF;
		SPIx_ReadWriteByte(spi_tx_data,spi_rx_data,1);      //持续DIN高电平写操作,恢复AD7705接口
	}
	
	switch(Channel)
	{
		case 0:
				spi_tx_data[0] = 0x20;
				SPIx_ReadWriteByte(spi_tx_data,spi_rx_data,1);        //通道1 ,下一个写时钟寄存器
				spi_tx_data[0] = 0x04;
				SPIx_ReadWriteByte(spi_tx_data,spi_rx_data,1);        //写时钟寄存器设置更新速率为50Hz
				spi_tx_data[0] = 0x10;
				SPIx_ReadWriteByte(spi_tx_data,spi_rx_data,1);        //通道1 ,下一个写设置寄存器
				spi_tx_data[0] = 0x46;
				SPIx_ReadWriteByte(spi_tx_data,spi_rx_data,1);        //写设置寄存器 ,设置成单极性、缓冲、增益为1、滤波器工作、自校准
			break;
		
		case 1:
				spi_tx_data[0] = 0x21;
				SPIx_ReadWriteByte(spi_tx_data,spi_rx_data,1);        //通道2 ,下一个写时钟寄存器
				spi_tx_data[0] = 0x04;
				SPIx_ReadWriteByte(spi_tx_data,spi_rx_data,1);        //写时钟寄存器设置更新速率为50Hz
				spi_tx_data[0] = 0x11;
				SPIx_ReadWriteByte(spi_tx_data,spi_rx_data,1);        //通道2 ,下一个写设置寄存器
				spi_tx_data[0] = 0x46;
				SPIx_ReadWriteByte(spi_tx_data,spi_rx_data,1);        //写设置寄存器 ,设置成单极性、缓冲、增益为1、滤波器工作、自校准
			break;
		
		case 3:
				spi_tx_data[0] = 0x23;
				SPIx_ReadWriteByte(spi_tx_data,spi_rx_data,1);        //通道4 ,下一个写时钟寄存器
				spi_tx_data[0] = 0x04;
				SPIx_ReadWriteByte(spi_tx_data,spi_rx_data,1);        //写时钟寄存器设置更新速率为50Hz
				spi_tx_data[0] = 0x13;
				SPIx_ReadWriteByte(spi_tx_data,spi_rx_data,1);        //通道4 ,下一个写设置寄存器
				spi_tx_data[0] = 0x46;
				SPIx_ReadWriteByte(spi_tx_data,spi_rx_data,1);        //写设置寄存器 ,设置成单极性、缓冲、增益为1、滤波器工作、自校准
			break;	
	

2.7 中断方式读取

/**
  * @brief This function handles EXTI0 interrupts.
  */
void EXTI0_IRQHandler(void)
{	
  __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
		
	switch(AD7705_7706_Data.Channel7705_Addr)
	{
		case 0:
			AD7705_7706_Data.Pump_Temp = ReadAD7705(1); 	  //读取为上一个通道
			break;
		case 1:
			AD7705_7706_Data.Pump_Voltage = ReadAD7705(0);  
		  break;
	}
	
	if(AD7705_7706_Data.Channel7705_Addr++ >= 1)
	{
		AD7705_7706_Data.Channel7705_Addr = 0;
		System_Data.AD7705_Flag = DEF_TRUE;
	}
}

/**
  * @brief This function handles EXTI1 interrupts.
  */
void EXTI1_IRQHandler(void)
{	
    __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_1);
	
	switch(AD7705_7706_Data.Channel7706_Addr) //读取为上一个通道
	{
		case 0:
			AD7705_7706_Data.Pump_Current = ReadAD7706(1);
			break;
		case 1:
			AD7705_7706_Data.Sensor_Temp = ReadAD7706(3);
		  break;
		case 2:
			AD7705_7706_Data.Sensor_AD = ReadAD7706(0);
			break;
	}
	
	if(AD7705_7706_Data.Channel7706_Addr++ >= 2)
	{
		AD7705_7706_Data.Channel7706_Addr = 0;
		System_Data.AD7706_Flag = DEF_TRUE;
	}
}

AD7705、AD7706当前读取的数据为上一个通道的数据,所以当在赋值每个通道数据时依次顺位。举例:比如 AD7705_7706_Data.Pump_Temp 硬件连接的通道是通道0,AD7705_7706_Data.Pump_Voltage 硬件连接的通道是通道1,所以在读取数据时,

	AD7705_7706_Data.Pump_Temp = ReadAD7705(1); 	  
	AD7705_7706_Data.Pump_Voltage = ReadAD7705(0);

总结

这是我首次撰写的学习与踩坑笔记,记录的是我第一个项目的点滴历程,还有开发一个上位机(使用C#)。我广泛参考了众多前辈的经验分享和技术文档和手册,并亲自动手实践了一番。这份笔记旨在总结关键知识点和个人感悟,便于日后回顾时能够快速找回状态、避免重蹈覆辙。希望它能成为我个人成长道路上的一块垫脚石。

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值