最近开始进行新项目的研发,考虑用DAC做一个前级输出,选择了DAC8552这个器件的一个模块,用了野火的指南者做主控,芯片是STM32F103VET6,主频是72MHz。
一、器件手册重要信息提取
1.DAC8552具有十六位的分辨率、双通道输出、通电后复位为零刻度、供电电压是2.7V-5.5V,轨到轨输出,在供电电压为5V的时候输出频率可以达到30MHz。
2.器件引脚说明
<1>2引脚,作为参考的基准电压,也就是和电压输出范围有关系的.计算公式如下:
VREF就是基准电压的大小
<2>5引脚SYNC相当于SPI的片选线,下降时也就是低电平时,启动输入移位寄存器,数据在SCLK的下降边沿进行传输。8位控制字节和16位数据字的操作由24个下降沿的SCLK时钟边缘触发(除非在该边缘之前SYNC被置为高电平,在这种情况下,SYNC的上升沿作为中断信号,写序列被DAC8552忽略)。
3.短路电流大小
4.数据输出的时序
高位在前三个字节二十四位数据,第一个字节高八位是控制字节、第二个字节和第三个字节十六位是数据。
5.高八位的控制说明
DB23和DB22 00
DB21和DB20 00两个通道都不加载 11同时加载DACA和DACB 01加载DACA并输出在完成后达到指定值 10加载DACB并输出在完成后达到指定值
DB19 0或1都行,一般情况下我会取0
D18 0=A 1=B 0:写入数据缓冲区A 1:写入数据缓冲区B
DB17和DB16 两个都取0就是正常模式
DB0-DB15 两个字节十六位的数据
6.手册中比较重要内容的截图
可能有重复地方加深一下印象
7.手册通信举例
示例重点看一下LDB\LDA\Buffer Select三个位,基本就搞明白哪个通道输出不输出了。
在项目开发中,我直接使用的事示例2的第一个,通过DACA去做输出。
<1>示例1:通过缓冲区B写入数据缓冲区A,同时通过DAC B加载DAC A
写入数据缓冲区A
写入数据缓冲区B,同时加载DAC A和DAC B
<2>示例2:将新数据依次加载到DAC A和DAC B
写入数据缓冲区A并加载DAC A:DAC A输出在完成后稳定到指定值:
写入数据缓冲区B并加载DAC B:DAC B输出在完成后达到指定值:
示例3:同时断电DAC A至1kΩ,断电DAC B至100kΩ
对数据缓冲区A写入减电命令
将断电命令写入数据缓冲区B,同时加载DAC A和DAC B
示例4:DAC A和DAC B依次断电高阻抗
将断电命令写入数据缓冲区A并加载DAC A: DAC A输出= Hi-Z:
将断电命令写入数据缓冲区B和加载DAC B: DAC B输出= Hi-Z:
二、项目说明
1.项目需求
使用该器件主要做一级的三角波输出,后级作用与处理在这里不进行详细说明。目标波形参数如下:五种三角波
2.关于输出计算问题
我的硬件设计中,基准电压是5V,因此要输出相应的电压大小,算出对应的D值即可,在设计中我直接设定D值为15000了,这是为了满足我的项目。
3.软硬件说明
因为是做立项测试,先用了野火的指南者开发板和该模块进行通信,使用HAl库的编程思想。
三、开发记录
1HAL库的设定
<1>RCC的设定
<2>SYS的设定
<3>USART的设定
<4>定时器2的设定
10us进入一次定时器2中断
<5>SPI设定
这个需要注意一下,DAC的最高通信速率是30M,我在这里设定了18M。
通信中是下降沿读取数据,因此CPOL和CPHA分别设定为LOW 2Edge
<6>NVIC
<7>芯片引脚图<7>芯片引脚图
四、编程
按键切换五种三角波状态,直接把main.c粘贴进来了,野火的原理图可以直接去网上搜就可以了
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2024 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
SPI_HandleTypeDef hspi1;
TIM_HandleTypeDef htim2;
UART_HandleTypeDef huart1;
/* USER CODE BEGIN PV */
//满偏的时候是准确的一半,但是下面就是不完全精准了
uint16_t DAC_Tx=0; //理论上对应的是2V,减半为1V。0.996 28000
uint32_t TIM_FLAG=0;
//1-5对应五种模式 0对应空状态
uint8_t Triange_Mode=0;
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_SPI1_Init(void);
static void MX_USART1_UART_Init(void);
static void MX_TIM2_Init(void);
/* USER CODE BEGIN PFP */
void ADC8552_Data(uint16_t *ADC8552_Data);
void Scan_Mode(void);
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
//第一个字节:0 0 0 1 0 0 0 0 0x10
//第二个和第三个字节 发送一个1V的电压对应的二进制数据
//13000 对应差不多0.98V 0011 0010 1100 1000 0x32 0xB8
// uint8_t SPI1_Receive[]={0x10,0xFF,0xFF};
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_SPI1_Init();
MX_USART1_UART_Init();
MX_TIM2_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
//开启定时器2
HAL_TIM_Base_Start_IT(&htim2);
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
//三角波说明 一共五个波形
// 状态 t宽 t升 t降 t止
//参数1:神经失用 1ms 1ms 0 20ms
//参数2:轻度失神经 10-50ms 10-50ms 1ms 50-150ms
//参数3:中度失神经 50-150ms 50-150ms 30-100ms 500-1000ms
//参数4:重度失神经 150-300ms 150-300ms 60-200ms 1000-3000ms
//参数5:极重度失神经 400-600ms 400-600ms 200-300ms 1000-5000ms
Scan_Mode();
// while(1);
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief SPI1 Initialization Function
* @param None
* @retval None
*/
static void MX_SPI1_Init(void)
{
/* USER CODE BEGIN SPI1_Init 0 */
/* USER CODE END SPI1_Init 0 */
/* USER CODE BEGIN SPI1_Init 1 */
/* USER CODE END SPI1_Init 1 */
/* SPI1 parameter configuration*/
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 10;
if (HAL_SPI_Init(&hspi1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN SPI1_Init 2 */
/* USER CODE END SPI1_Init 2 */
}
/**
* @brief TIM2 Initialization Function
* @param None
* @retval None
*/
static void MX_TIM2_Init(void)
{
/* USER CODE BEGIN TIM2_Init 0 */
/* USER CODE END TIM2_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
/* USER CODE BEGIN TIM2_Init 1 */
/* USER CODE END TIM2_Init 1 */
htim2.Instance = TIM2;
htim2.Init.Prescaler = 72-1;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 10-1;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM2_Init 2 */
/* USER CODE END TIM2_Init 2 */
}
/**
* @brief USART1 Initialization Function
* @param None
* @retval None
*/
static void MX_USART1_UART_Init(void)
{
/* USER CODE BEGIN USART1_Init 0 */
/* USER CODE END USART1_Init 0 */
/* USER CODE BEGIN USART1_Init 1 */
/* USER CODE END USART1_Init 1 */
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART1_Init 2 */
/* USER CODE END USART1_Init 2 */
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOE_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(DAC855X_SYNC_GPIO_Port, DAC855X_SYNC_Pin, GPIO_PIN_SET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin : DAC855X_SYNC_Pin */
GPIO_InitStruct.Pin = DAC855X_SYNC_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(DAC855X_SYNC_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : KEY1_MODE_Pin */
GPIO_InitStruct.Pin = KEY1_MODE_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(KEY1_MODE_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : LED_Pin */
GPIO_InitStruct.Pin = LED_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(LED_GPIO_Port, &GPIO_InitStruct);
/* EXTI interrupt init*/
HAL_NVIC_SetPriority(EXTI0_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
}
/* USER CODE BEGIN 4 */
//传递数据 第一个字节是数据的高八位,第二个字节是数据的低八位
void ADC8552_Data(uint16_t *ADC8552_Data)
{
uint8_t SPI_Tx[3]={0x10,0,0};
//数据的低八位
SPI_Tx[2]=(uint8_t)(*ADC8552_Data)|0x00;
// HAL_UART_Transmit(&huart1,&SPI_Tx[2],1,1000);
//数据的高八位
SPI_Tx[1]=(uint8_t)(*ADC8552_Data>>8)|0x00;
// HAL_UART_Transmit(&huart1,&SPI_Tx[1],1,1000);
// SPI_Tx[2]=0x66;
// SPI_Tx[1]=0x66;
//控制字节
// SPI_Tx[0]=0x10;
// HAL_UART_Transmit(&huart1,&SPI_Tx[0],1,1000);
//将传递进来的数据进行分字节处理
//开启DAC的片选
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_6,GPIO_PIN_RESET);
//发送第一个字节数据
HAL_SPI_Transmit(&hspi1,&SPI_Tx[0],1,1000);
//发送第二个字节数据
HAL_SPI_Transmit(&hspi1,&SPI_Tx[1],1,1000);
//发送第三个字节数据
HAL_SPI_Transmit(&hspi1,&SPI_Tx[2],1,1000);
//关闭DAC的片选
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_6,GPIO_PIN_SET);
}
//按键实现模式的切换
void Scan_Mode(void)
{
//三角波说明 一共五个波形
// 状态 t宽 t升 t降 t止 示波器状态
//参数1:神经失用 1ms 1ms 0 20ms 1V---1ms
//参数2:轻度失神经 10-50ms 10-50ms 1ms 50-150ms
//参数3:中度失神经 50-150ms 50-150ms 30-100ms 500-1000ms
//参数4:重度失神经 150-300ms 150-300ms 60-200ms 1000-3000ms
//参数5:极重度失神经 400-600ms 400-600ms 200-300ms 1000-5000ms
//神经失用模式
if(Triange_Mode==1)
{
if(TIM_FLAG<=100)
{
ADC8552_Data(&DAC_Tx);
}
else if((TIM_FLAG>100)&&(TIM_FLAG<2100))
{
DAC_Tx=0;
ADC8552_Data(&DAC_Tx);
}
else if((TIM_FLAG>=2100))
{
TIM_FLAG=0;
}
}
else if(Triange_Mode==2)//轻度失神经
{
if(TIM_FLAG<=1100) //包括上升和下降
{
ADC8552_Data(&DAC_Tx);
}
else if((TIM_FLAG>1100)&&(TIM_FLAG<6100))
{
DAC_Tx=0;
ADC8552_Data(&DAC_Tx);
}
else if((TIM_FLAG>=6100))
{
TIM_FLAG=0;
}
}
else if(Triange_Mode==3)//中度失神经
{
if(TIM_FLAG<=8000) //包括上升和下降
{
ADC8552_Data(&DAC_Tx);
}
else if((TIM_FLAG>8000)&&(TIM_FLAG<58000))
{
DAC_Tx=0;
ADC8552_Data(&DAC_Tx);
}
else if((TIM_FLAG>=58000))
{
TIM_FLAG=0;
}
}
else if(Triange_Mode==4)//重度失神经
{
if(TIM_FLAG<=21000) //包括上升和下降
{
ADC8552_Data(&DAC_Tx);
}
else if((TIM_FLAG>21000)&&(TIM_FLAG<121000))
{
DAC_Tx=0;
ADC8552_Data(&DAC_Tx);
}
else if((TIM_FLAG>=121000))
{
TIM_FLAG=0;
}
}
else if(Triange_Mode==5)//极重度失神经
{
if(TIM_FLAG<=60000) //包括上升和下降
{
ADC8552_Data(&DAC_Tx);
}
else if((TIM_FLAG>60000)&&(TIM_FLAG<160000))
{
DAC_Tx=0;
ADC8552_Data(&DAC_Tx);
}
else if((TIM_FLAG>=160000))
{
TIM_FLAG=0;
}
}
}
//进行一些说明
//目前这种编写方法相当于把幅值和波形以及固定了,如果做成产品应该怎么修改编程思想呢?
//1.关于幅值的说明
//关于幅值,就是对应治疗模式下的强度改变,欧姆定律决定了在R不变的情况下V和I的关系。
//可以增加一个二级处理,把中断出来的DAC_Tx进行二次加工,对应着不同的强度,处理好后在
//通过DAC去做传递,如果涉及到小数,可以全部改成double,最后做个强制类型转换,这样会让
//波形更加丝滑。
//2.关于波形,其他设备的波形都是固定的,如果设备想要增加不同参数模式,那就按着之前的
//方法去增加就可以了。
//定时器2,每10us进入一次
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
//满偏值暂时设定为150*100=15000/65536*5≈1.10-1.15V之间
//K1做成开始停止 K2做成功能切换
TIM_FLAG++;
//神经失用模式 1ms宽 1ms上升 0ms下降 20ms暂停
if((Triange_Mode==1)&&(TIM_FLAG<=100))
{
DAC_Tx+=150;//上升
}
else if((Triange_Mode==2)&&(TIM_FLAG<=1100))//轻度失神经 10ms宽 10ms上升1ms下降50ms暂停
{
if(TIM_FLAG<=1000)DAC_Tx+=15;//1000*15 上升
else if((TIM_FLAG>1000)&&(TIM_FLAG<=1100))DAC_Tx-=150;//下降
}
else if((Triange_Mode==3)&&(TIM_FLAG<=8000))//中度失神经
{
//50ms宽50ms上升30ms下降500ms暂停
if(TIM_FLAG<=5000)DAC_Tx+=3;//5000*3 上升
else if((TIM_FLAG>5000)&&(TIM_FLAG<=8000))DAC_Tx-=5;//下降
}
else if((Triange_Mode==4)&&(TIM_FLAG<=21000))//重度失神经
{
//50ms宽50ms上升30ms下降500ms暂停
if(TIM_FLAG<=15000)DAC_Tx+=1;//1000*15 上升
else if((TIM_FLAG>15000)&&(TIM_FLAG<=21000)&&(TIM_FLAG%2==0))DAC_Tx-=5;//下降
}
else if((Triange_Mode==5)&&(TIM_FLAG<=60000))//重度失神经
{
//50ms宽50ms上升30ms下降500ms暂停
if((TIM_FLAG<=40000)&&(TIM_FLAG%8==0))DAC_Tx+=3;//1000*15 上升
else if((TIM_FLAG>40000)&&(TIM_FLAG<=60000)&&(TIM_FLAG%4==0))DAC_Tx-=3;//下降
}
}
//按键外部中断,实现模式的切换
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
//模式切换
Triange_Mode+=1;
if(Triange_Mode==6)Triange_Mode=0;
//每次按键切换定时器累加值以及电压幅值都进行清零处理
TIM_FLAG=0;
DAC_Tx=0;
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5);
}
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
五、目标实现
观察信号幅值以及宽度大小,因为是拿杜邦线连的会发现干扰噪声很多,正常连接线噪声会少很多,如果感觉干扰过大可以加些滤波。