Được tạo bởi Blogger.

Labels

Thứ Hai, 24 tháng 10, 2016

TivaC - ADC


Trong phần này chúng ta cùng xem xét ADC trong TMC123GH6PM MCU. Một số cấu hình và tất cả các bảng được lấy từ datasheet của MCU. Sử dụng datasheet để biết thêm thông tin về ADC của Tiva. Tài liệu này chỉ đưa ra một số giới thiệu về ADC, để có nhiều thông tin chuyên sâu hơn về ADC bạn nên đọc trong datasheet.

Đầu tiên, một ADC là gì?

ADC là viết tắt của Analog to Digital Converter, nó cho phép một vi điều khiển làm việc với tín hiệu số và các mạch số khác để đọc tín hiệu tương tự. Có một số phiên bản của ADC, với các phương thức khác nhau để chuyển một tín hiệu tương tự sang một tín hiệu số. Và với các giao tiếp khác nhau, từ song song đến I2C.

Một vi điều khiển (MCU) chỉ làm việc với các dữ liệu 0 và 1. Một tín hiệu tương tự thay đổi điện áp với một dải đa giá trị, vì vậy cần đến ADC.

ADC trong Tiva có một điện áp tham chiếu nội là 3V và đó là giá trị điện áp lớn nhất có thể đo được. Giá trị nhỏ nhất bằng GND.

Các ADC cũng có một độ phân giải max, chẳng hạn như ADC 12bit, sẽ có thể phân biệt 4096 giá trị khác nhau giữa GND và điện áp tham chiếu. Với một điện áp tham chiếu là 3V thì nó có nghĩa là mỗi giá trị tương ứng với khoảng 0.73mV.

Độ phân giải lớn hơn của ADC, một tín hiệu tương tự analog tốt hơn được đọc và lưu trữ. Nếu bạn cần khôi phục tín hiệu tương tự lúc trước thì bạn cần ghi nó lại một cách chính xác nhất có thể. Bạn có thể xem hình dưới đây để thấy cách convert một tín hiệu tương tự có thể mất rất nhiều giá trị với một ADC độ phân giải thấp. Có một số giá trị đã bị mất giữa các lần đọc.

(một ảnh ở đây)

Tôi vi vọng bạn đã có cái nhìn tổng quan về ADC. Để có thêm thông tin về ADC bạn nên tìm kiếm trên Web. Và cũng nên tìm hiểu về DAC, Digital to Analog converter là một bộ chuyển đổi từ số sang tương tự, nó ngược với ADC. 

Các tính năng của modul ADC

Vi điều khiển TM4C123GH6PM cung cấp 2 modul ADC với các tính năng dưới đây:
+ 12 kênh đầu vào tín hiệu tương tự
+ Độ phân giải 12bit
+ Các cấu hình Single - ended và differential - input
+ Một cảm biến nhiệt độ nội
+Tần số lấy mẫu tối đa là 1Mhz
+ Tùy chọn chuyển đổi giai đoạn trong thời gian lấy mẫu khả trình từ 22.5 đến 337.5 độ C.
+ Điều khiển bộ kích linh hoạt
    - Bộ điều khiển: phần mềm
    - Các timer
    - Các bộ so sánh tương tự
    - PWM
    - GPIO

Đó là các tính năng. Một số khá phức tạp trong sử dụng.
Có 2 modul ADC và mỗi modul có 4 bộ lấy mẫu cho phép lấy mẫu đa nguồn mà không cần vi điều khiển can thiệp.
Phần lớn các ngoại vi có các chân xác định cho mỗi input/output. Adc cũng có chân xác định kết nối tới nó giống như bạn xem trong bảng dưới đây. Nhưng bạn có thể sử dụng bất kỳ cái nào cho bất kỳ bộ sequncer hoặc modul ADC. Chẳng hạn, bạn có thể sử dụng AIN9 cho ADC0 hoặc ADC1 và bất kỳ sequencer nào.



Lấy mẫu ADC có thể được kích hoạt bởi nhiều nguồn khác nhau. Đơn giản thì bằng phần mềm hoặc bất kỳ ngoại vi nào sau: timer, PWM, GPIO hoặc bộ so sánh tương tự. Bộ kích hoạt bắt đầu đọc một cấu hình có sẵn để bắt đầu.

Bạn có thể thấy rõ ràng trong ảnh sau rằng tất cả các kênh đầu vào ADC được kết nối tới cả 2 modul ADC và bộ kích có thể kích cho bất kỳ ADC nào.



Cảm biến nhiệt độ nội

Trong phần này tôi sẽ chỉ bạn cách lập trình cơ bản với ADC bằng cách đọc cảm biến nhiệt độ nội có trong kit TM4C123GH6PM.

Cảm biến này đưa ra nhiệt độ bên trong chip. Nhiệt độ thường lớn hơn nhiệt độ môi trường do chip có thể bị nóng lên. 

Ví dụ này sẽ giải thích với các hàm có trong TivaWare, tuy nhiên nó cần một số tinh chỉnh, vì vậy tôi sẽ sửa nó một chút.

Bắt đầu code thôi!

Để xem giá trị ADC nhận được, chúng ta cần sử dụng UART để truyền giá trị lên máy tính. Để làm điều đó, tôi sẽ sử dụng các cấu hình trong bài hướng dẫn về UART.

SysCtlClockSet(SYSCTL_SYSDIV_2_5|SYSCTL_USE_PLL|SYSCTL_OSC_MAIN|SYSCTL_XTAL_16MHZ);
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOC);
SysCtlDelay(3);
GPIOPinConfigure(GPIO_PC4_U4RX);
GPIOPinConfigure(GPIO_PC5_U4TX);
GPIOPinTypeUART(GPIO_PORTC_BASE, GPIO_PIN_4|GPIO_PIN_5);
UARTConfigSetExpClk(UART4_BASE, SysCtlClockGet(), 115200 (UART_CONFIG_WLEN_8 |UART_CONFIG_STOP_ONE|UART_CONFIG_PAR_NONE));

Khai báo một số biến

uint32_t ADCValues[1];

Mảng này chứa dữ liệu đọc được từ ADC FIFO. Nó cần là một mảng là do một số sequencer có các FIFO với độ rộng lớn hơn 1 giá trị. Mảng này cần có độ rộng của FIFO. Chẳng hạn sequencer 3 sẽ được sử dụng có một FIFO depth là 1. Thậm trí vậy thì mảng vẫn nên được sử dụng ở đây hoặc ít nhất là một con trỏ (đọc một hàm chỉ chấp nhận một con trỏ). Nhớ rằng, cần khai báo một mảng đủ lớn cho một sequencer FIFO nếu bạn quyết định sử dụng một sequencer khác sequencer 3.

uint32_t TempValueC;
uint32_t TempValueF;

Chúng sẽ chưa nhiệt độ với đơn vị độ C và độ F.

Bây giờ là cấu hình cho ADC
SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0); 
SysCtlDelay(3);

Sử dụng ADC0, kích hoạt clock cho nó giống như những ngoại vi khác. Nhớ rằng ADC0 và ADC1 chia sẻ cùng một chân.

ADCSequenceConfigure(ADC0_BASE, 3, ADC_TRIGGER_PROCESSOR, 0);

Bạn sử dụng ADC base như biến đầu tiên trong lệnh trên. Biến thứ 2 chính là sequencer. Sequencer 3 với FIFO có depth là 1 sẽ được sử dụng như đã nói ở trên. Biến thứ 3 là một loại bộ kích mà bạn muốn khởi động một bộ lấy mẫu. Trong trường hợp này kích nó bằng phần mềm. Bạn có thể có các bộ kích khác (xem trong TivaWare). Biến thứ 4 là độ ưu tiên so với các sequencer khác. Có 4 mức từ 0 đến 3, 3 là độ ưu tiên thấp nhất. Cài đặt này sẽ làm bộ lấy mẫu sequencer đến đầu tiên khi một kích ở một thời điểm. Nhớ rằng để cài đặt các sequencer thì cần phân biệt các giá trị độ ưu tiên nếu có lớn hơn 1 bộ sequencer được sử dụng. Điều này sẽ tránh các lỗi và tránh các giá trị không tương ứng.

ADCSequenceStepConfigure(ADC0_BASE, 3, 0, ADC_CTL_TS | ADC_CTL_IE | ADC_CTL_EN D);

Cấu hình này chỉ ra các bước ADC thực hiện một đọc. Trong trường hợp cấu hình này sequencer 3 (biến thứ 2) trong ADC0 (biến thứ nhất). 
Biến thứ 3 cài đặt step tới 0. Sequencer 3 chỉ có một khả năng cấu hình nên chỉ có duy nhất một lựa chọn. Sequencer 1 và 2 có 4 step trong khi sequencer 0 có 8 step. Những sequencer này sẽ hữu ích trong các ứng dụng khác, không cần thiết trong tutorial này.
Biến thứ 4 là một biến logic hoặc một loạt các biến logic.
+ ADC_CTL_TS cài đặt kênh của cảm biến nhiệt độ, Với các kênh khác bạn sẽ sử dụng ADC_CTL_CH0 đến ADC_CTL_CH23.
+ ADC_CTL_IE, một interrupt sẽ được tạo ra khi sequencer kết thúc. Tuy nhiên, một ngắt sẽ không được sử dụng, trạng thái ngắt sẽ được sử dụng sau.
+ ADC_CTL_END định nghĩa bước này như là bước cuối cùng của sequence. Luôn luôn sử dụng biến này khi bạn không muốn sử dụng đa bước.
+ Thêm vào đó bạn có thể thêm ADC_CTL_CMP0 đến ADC_CTL_CMP7 để ADC gửi giá trị lấy mẫu tới một bộ so sánh.

Tất cả các cấu hình sẽ được sử dụng bởi sequencer khi nó được kích hoạt.

ADCSequenceEnable(ADC0_BASE, 3); 
ADCIntClear(ADC0_BASE, 3);

Bây giờ, mọi thứ đã được cấu hình cho sequencer 3. 2 lệnh trên dùng để kích hoạt sequencer 3 và cũng xóa cờ sequencer.

Bây giờ, làm sao tạo một đọc?

ADCProcessorTrigger(ADC0_BASE, 3); 
           
while(!ADCIntStatus(ADC0_BASE, 3, false)) 
   
ADCIntClear(ADC0_BASE, 3); 
      
ADCSequenceDataGet(ADC0_BASE, 3, ADC0Values); 

Đầu tiên, một đọc được hoàn thành bởi lần kích đầu tiên. Từ một bộ kích phần mềm được cài đặt trên sequencer 3. Lệnh này cần ADC base và sequencer như bạn thấy.

Thứ 2, Sau đó tất nhiên việc lấy mẫu cần thời gian nên chúng ta cần đợi cho nó tới. Đó là vì sao trong bước cấu hình tôi cài đặt một ngắt được gọi khi lấy mẫu hoàn thành (tuy nhiên ngắt không bao giờ được cấu hình để xảy ra và đi tới bộ xử lý ngắt).
Chúng ta đợi cho cờ ngắt được update lên 1. Bằng cách sử dụng ADCIntStatus(ADC0_BASE, 3, false) sẽ trả về một giá trị khác 0 khi sequencer được hoàn thành.
Chú ý rằng biến thứ 3 là "false" thay vì "true" như trong các bộ xử lý ngắt để kiểm tra ngắt có xảy ra hay không.

Thứ 3, Xóa cờ ADC

Thứ 4, Nhận giá trị từ FIFO. Hàm này sẽ đưa các giá trị từ FIFO vào trong một mảng. Biến thứ 3 cần phải là một mảng có kích thước tương ứng với sequencer FIFO như đã nói ở trước.

Chúng ta đã có các giá trị. Bây giờ là lấy nhiệt độ.

Chúng ta đã có giá trị của ADC vậy làm cách nào để lấy được nhiệt độ từ đó?

Các giá trị từ ADC đi từ 0 tới 4095. Nhưng những giá trị này có nghĩa là gì? Nếu điện áp tham chiều là 3.3V thì điều này có nghĩa là mỗi giá trị tương ứng với 3.3/4096 = 0.00081V. Cảm biến nhiệt độ đưa ra một giá trị voltage khác phụ thuộc vào nhiệt độ đo được. Vậy làm sao để chuyển từ điện áp sang nhiệt độ? Trong datasheet đã chỉ ra các phép toán giúp lấy giá trị nhiệt độ từ giá trị ADC:

+ VTSENS = 2.7 ­ ((TEMP + 55) / 75): được chỉ ra ở hình 13-11 trang 814.
+ TEMP = 147.5 ­ ((75 * (VREFP ­- VREFN) × ADCCODE) / 4096): Chỗ VREFP ­- VREFN là 3.3V trên kit. ADCCODE là giá trị bạn nhận được từ lấy mẫu ADC.

Bây giờ bạn cần tạo phép toán và in kết quả nên màn hình.
Đồng thời một delay khoảng 250mS được thêm vào.

TempValueC= (uint32_t)(147.5 ‐ ((75.0*3.3 *(float)ADCValues[0])) / 4096.0); 
TempValueF= ((TempValueC* 9) + 160) / 5;
UARTprintf("Temperature = %3d*C or %3d*F\r", TempValueC, TempValueF); 
SysCtlDelay(80000000 / 12);

I hope you enjoyed this!!!

Dưới đây là code cho phần hướng dẫn này:

/*
 * main.c
 */
#include "stdint.h"
#include "stdio.h"
#include "stdbool.h"
#include "inc/hw_gpio.h"
#include "inc/hw_types.h"
#include "inc/hw_memmap.h"
#include <driverlib/uart.h>
#include <driverlib/gpio.h>
#include <driverlib/sysctl.h>
#include <driverlib/sysctl.c>
#include <driverlib/pin_map.h>
#include <driverlib/adc.h>

void delay_us(unsigned int time_us)
{
SysCtlDelay(time_us*26667/1000);
}

void delay_ms(unsigned int time_ms)
{
SysCtlDelay(time_ms*26667);
}

void put_string(unsigned char *string)
{
int i, length = 0;

while(string[length]!='\0') length++;

for(i=0; i<length; i++)
{
UARTCharPut(UART4_BASE, string[i]);
}
UARTCharPut(UART4_BASE, '\r');
UARTCharPut(UART4_BASE, '\n');
}

void toString(uint32_t temperature, unsigned char *temp)
{
uint32_t x;
uint32_t y;

x = temperature/1000;
y = temperature-x*1000;
if(y<10) sprintf((char *)temp, "Temperature = %d.00%d*C", x, y);
else if(y<100) sprintf((char *)temp, "Temperature = %d.0%d*C", x, y);
else sprintf((char *)temp, "Temperature = %d.%d*C", x, y);
}

int main(void) {
uint32_t ADCValues[1];
uint32_t TempValueC;
unsigned char temp[30] = "";

SysCtlClockSet(SYSCTL_SYSDIV_2_5|SYSCTL_USE_PLL|SYSCTL_XTAL_16MHZ|SYSCTL_OSC_MAIN);
SysCtlDelay(3);

SysCtlPeripheralEnable(SYSCTL_PERIPH_UART4);
SysCtlDelay(3);

SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOC);
SysCtlDelay(3);
GPIOPinConfigure(GPIO_PC4_U4RX);
GPIOPinConfigure(GPIO_PC5_U4TX);
GPIOPinTypeUART(GPIO_PORTC_BASE, GPIO_PIN_4|GPIO_PIN_5);
UARTConfigSetExpClk(UART4_BASE, SysCtlClockGet(), 115200, (UART_CONFIG_WLEN_8|UART_CONFIG_STOP_ONE|UART_CONFIG_PAR_NONE));

put_string("ADC:");
put_string("Internal Temperature Sensor");

SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0);
SysCtlDelay(3);

ADCSequenceConfigure(ADC0_BASE, 3, ADC_TRIGGER_PROCESSOR, 0);
ADCSequenceStepConfigure(ADC0_BASE, 3, 0, ADC_CTL_TS|ADC_CTL_IE|ADC_CTL_END);
ADCSequenceEnable(ADC0_BASE, 3);

while(1)
{
ADCProcessorTrigger(ADC0_BASE, 3);
while(!ADCIntStatus(ADC0_BASE, 3, false));
ADCIntClear(ADC0_BASE, 3);
ADCSequenceDataGet(ADC0_BASE, 3, ADCValues);
TempValueC = (uint32_t)((147.5-((75.0*3.3*(float)ADCValues[0]))/4096.0)*1000);

toString(TempValueC, temp);
put_string(temp);
delay_ms(250);
}
}

3 nhận xét:

  1. sao không làm tiếp bạn ơi. mình rất thích 2 bài của bạn làm. Nó giúp mình giải đáp những thắc mắc không biết hỏi ai. Rất cảm ơn bạn

    Trả lờiXóa
  2. ad làm tiếp về con tiva này đi ạ

    Trả lờiXóa