18位 SAR ADC ADS8694的使用

18位 SAR ADC ADS8694的使用

來自專欄 EE_bigFeline_>w<9 人贊了文章

前言

最近參加了TI杯電賽,又可以寫瞎點東西了XD

這次使用了一款18位的四通道SAR ADC, ADS8694 ,這裡把調試使用過程記錄一下

基於STM32 HAL庫的驅動程序可以在這裡找到:

https://github.com/Goose-Bomb/STM32F407ZGT6-DevBoard-UESTC-EE/blob/master/Src/ads8694.c?

github.com

電路設計

這款ADC集成度很高,模擬前端、PGA、電壓基準、時鐘全部都是片上的,通道0~3可以直接輸入雙極性信號,過壓保護±20V。基本上把信號直接懟進去就能用了XD,AUX通道是不經過片內模擬前端的直接進入ADC核的,如果要用前面要用運放做驅動電路(不過這裡我直接沒用它XD)。數字部分通過雙工SPI介面與控制器進行通信,再多兩個ALRAM和RESET信號,沒什麼特殊的。數字部分用+3.3V供電以兼容STM32的邏輯電平,模擬部分用+5V供電,把數字地和模擬地分開並在PCB背面單點接地。

電路原理圖,還有很多去耦電容沒在圖上~

datasheet上的PCB布局示例

寄存器配置

SPI時序圖

觀察時序圖,ADC在主機向ADC寫入數據的時間段(前16個SCK沿)進行轉換,並在之後把數據送回主機,共34個SCK沿。因為轉換時間t_CONV是固定的,因此SPI的時鐘頻率不能過快,datasheet上的最大值是18MHz。

根據時序圖設置STM32的SPI初始化代碼:

void SPI2_Init(void){ hspi2.Instance = SPI2; hspi2.Init.Mode = SPI_MODE_MASTER; hspi2.Init.Direction = SPI_DIRECTION_2LINES; hspi2.Init.DataSize = SPI_DATASIZE_8BIT; hspi2.Init.CLKPolarity = SPI_POLARITY_LOW; hspi2.Init.CLKPhase = SPI_PHASE_2EDGE; hspi2.Init.NSS = SPI_NSS_SOFT; hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi2.Init.TIMode = SPI_TIMODE_DISABLE; hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; if (HAL_SPI_Init(&hspi2) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); }}/* 讀寫一個位元組 */uint8_t SPI2_RW_Byte(uint8_t data){ while (!(SPI2->SR & SPI_FLAG_TXE)); SPI2->DR = data; while (!(SPI2->SR & SPI_FLAG_RXNE)); return SPI2->DR;}

SCK閑置時為低電平,在第二個沿(下降沿)讀取已穩定的數據,

32的APB1時鐘之前配置為PCLK1 = 84 MHz,四分頻 PCLK1 / 4 = 10.5 MHz < 18MHz

片上有兩個用戶可訪問寄存器,Command Register和Program register,前者用於設定工作模式(如單通道選、掃描模式、連續採樣、睡眠等),後者用於設定採樣時的一些具體的參數(如輸入範圍、掃描模式的序列、Alarm閾值等)。除了通過寫入command register,RESET、POWER_DOWN狀態也可以直接由那個RST/PD IO口拉低不同的時間來進入(具體見datasheet)

ADC的狀態轉移圖

器件上電或進入過RESET、POWER_DOWN模式後program register被清空為默認值,首先要配置program register(對應左邊的狀態框)再向command register指定工作模式開始採樣流程(對應右邊的狀態框)。

Program Register 寫時序

program register的寫操作函數:

寫兩個位元組讀回一個位元組,共24個SCK

static void ADS8694_WriteReg(uint8_t addr, uint8_t data){ union { struct { unsigned Data : 8; _Bool WR : 1; unsigned Addr : 7; } Reg; uint8_t Bytes[2]; } write_data; write_data.Reg.WR = 1; write_data.Reg.Addr = addr; write_data.Reg.Data = data; /* Start write frame */ ADS8694_CS0; /* Write program register */ SPI2_RW_Byte(write_data.Bytes[1]); SPI2_RW_Byte(write_data.Bytes[0]); /* Read the data just been written for check */ uint8_t echo_data = SPI2_RW_Byte(0xFF); /* Pull CS to HIGH for at least 30ns */ ADS8694_CS1; __NOP();}

<用位域注意大小端順序??>

commander register的寫操作函數:

寫兩個位元組讀回三個位元組,共40個SCK

本來寫入完成之後就可以把CS拉高結束了,不過datasheet上還是推薦產生的34個SCK,

不過STM32的硬體SPI最小一次都是讀1個位元組即8個bit,所以寫兩個位元組讀三個位元組湊到40個SCK。

static void ADS8694_SendCmd(uint8_t cmd){ /* Start command frame */ ADS8694_CS0; /* Send command */ SPI2_RW_Byte(cmd); SPI2_RW_Byte(0x00); /* Dummy read to form a complete frame */ SPI2_RW_Byte(0xFF); SPI2_RW_Byte(0xFF); SPI2_RW_Byte(0xFF); /* Pull CS to HIGH for at least 30ns */ ADS8694_CS1; __NOP();}

完成了底層IO函數就可以配置具體的工作參數了,具體的寄存器表可以看datasheet就不贅述了~這裡把採樣初始化過程又封裝成了一個介面函數。

void ADS8694_ConfigSampling(int32_t *pBuffer, uint32_t count, uint8_t channel, uint8_t inputRange){ // Set buffer pointer sample_buffer = pBuffer; sample_count = count; // Unused channels power down ADS8694_WriteReg(0x02, (~channel) & 0x0F); // Set input range for (uint8_t i = 0; i < 4; i++) { if (channel & 1) { ADS8694_WriteReg(0x05 + i, inputRange); } channel >>= 1; } if (channel & (channel - 1) == 0) { // Set single channel manual selection switch (channel) { case ADS8694_CHANNEL_0: ADS8694_SendCmd(MAN_CH0); break; case ADS8694_CHANNEL_1: ADS8694_SendCmd(MAN_CH1); break; case ADS8694_CHANNEL_2: ADS8694_SendCmd(MAN_CH2); break; case ADS8694_CHANNEL_3: ADS8694_SendCmd(MAN_CH3); break; default: break; } } else { //Set auto scan sequence ADS8694_WriteReg(0x01, channel); ADS8694_SendCmd(AUTO_RST); } TIM2_Init();}

為什麼要在最後初始化一個定時器呢?看下一節!

採樣操作

這次主要想實現兩個功能:

  1. 採樣頻率穩定可控(時鐘都不穩做什麼FFT哇~)
  2. 連續採樣以填充緩衝區

ADS8694提供了兩種連續採樣模式,單通道連續模式和自動掃描模式

單通道模式

自動掃描模式

其實大意就是只設置一次採樣模式,之後的幀裡邊把MOSI一直拉低(即寫入0x0000),ADC便會一直採樣之前指定的單個通道或按照增序掃描採樣指定的多通道序列。

採樣頻率實際上是片選信號CS的頻率,那就用定時器出個正脈寬很窄的pwm波(由datasheet正脈寬只要大於30ns就好)作為CS吧~

我們想在CS的正脈寬結束後馬上進行SPI通信,把採樣的數據讀回來,這裡可以利用定時器中斷。

PWM1模式下,當TIM2_CNT>TIM2_CCR4(即正脈寬結束)便產生CC4中斷,在中斷響應函數里進行數據讀寫就行。

void TIM2_Init(void){ TIM_ClockConfigTypeDef sClockSourceConfig; TIM_MasterConfigTypeDef sMasterConfig; TIM_OC_InitTypeDef sConfigOC; htim2.Instance = TIM2; htim2.Init.Prescaler = 6; //83MHz / (6 + 1) == 12MHz htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 119; //12MHz / (Period + 1) 即為採樣頻率,可以在運行時改變 htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; if (HAL_TIM_Base_Init(&htim2) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } if (HAL_TIM_PWM_Init(&htim2) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } sConfigOC.OCMode = TIM_OCMODE_PWM1; //正脈寬結束即會產生CC事件 sConfigOC.Pulse = 3; //正脈寬 3 / 12MHz == 250ns sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_4) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } /**TIM2 GPIO Configuration PB11 ------> TIM2_CH4 //這個GPIO就拉給ADS8694的片選端 */ GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin = GPIO_PIN_11; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; GPIO_InitStruct.Alternate = GPIO_AF1_TIM2; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);}

如果需要改變採樣頻率,直接改變定時器的溢出周期就行

void ADS8694_SetSamplingRate(uint32_t samplingRate){ __HAL_TIM_SET_AUTORELOAD(&htim2, (12000000U / samplingRate) - 1);}

中斷響應函數(一次獲取一幀數據,達到預定序列長度後停止定時器)

18位的ADC Code從MSB開始分3個位元組讀入,並按順序裝入一個int32_t類型中。

void TIM2_IRQHandler(void){ int32_t adc_code = 0; if (__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_CC4) != RESET) { __HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_CC4); // Send 0x0000 for continuous mode SPI2_RW_Byte(0x00); SPI2_RW_Byte(0x00); // Read 18-bit ADC Code adc_code |= SPI2_RW_Byte(0xFF) << 10; adc_code |= SPI2_RW_Byte(0xFF) << 2; adc_code |= SPI2_RW_Byte(0xFF); sample_buffer[sample_index++] = adc_code; if (sample_index >= sample_count) { HAL_TIM_PWM_Stop_IT(&htim2, TIM_CHANNEL_4); } }}

開始採樣函數,(序列採樣完成前會在這裡掛起)

void ADS8694_StartSampling(void){ /* 點個LED壓壓驚 */ HAL_GPIO_WritePin(GPIOF, GPIO_PIN_10, SET); sample_index = 0; /* 產生CS信號開始採樣 */ HAL_TIM_PWM_Start_IT(&htim2, TIM_CHANNEL_4); /* 就...等著它采完啊<CPU:好無聊啊?( ̄△ ̄?)> */ while (sample_index < sample_count) { __NOP(); } /* 滅掉LED */ HAL_GPIO_WritePin(GPIOF, GPIO_PIN_10, RESET);}

現在我們的ADS8694差不多可以開始幹活了,來看看效果吧!

ADS8694_Init();ADS8694_ConfigSampling(&adc_sample_buffer, SAMPLE_COUNT, ADS8694_CHANNEL_0, INPUT_RANGE_BIPOLAR_0_625x);ADS8694_SetSamplingRate(100000);/* ... */ADS8694_StartSampling();/* 最低電壓對應CODE為0, 最高電壓對應CODE為 2^18 - 1,因為配置為雙極性輸入,所以減去一個2^17的偏移量 */arm_offset_q31(adc_sample_buffer, -(1 << 17), adc_sample_buffer, SAMPLE_COUNT);/* ...數據處理顯示... */

Neat~!

總結

本來一開始想在github上找個現成的驅動用的,不過沒找到~

吶,還是自己擼比較有趣呢!( ??? )

推薦閱讀:

TAG:電子工程EE | ADC | 電子信息工程 |