Цифро-аналоговые преобразователи (ЦАП) находят широкое применение в современной электронике, в основном, для синтеза звуков. Но также они используются для управления двигателями, регулировки силы свечения светодиода, аудио усилителях, видео декодерах и т.д. В данной статье мы рассмотрим подключение ЦАП MCP4921 к микроконтроллеру PIC16F877A. Результат цифро-аналогового преобразования мы будем отображать на экране ЖК дисплея 16х2.
Ранее на нашем сайте мы рассматривали подключение модуля ЦАП к платам Raspberry Pi, Arduino и STM32 Blue Pill.
Необходимые компоненты
- Микроконтроллер PIC16F877A (купить на AliExpress).
- Модуль ЦАП MCP4921 (купить на AliExpress).
- Программатор PICkit 3 (купить на AliExpress).
- ЖК дисплей 16х2 (купить на AliExpress).
- Кварцевый генератор 20 МГц (купить на AliExpress).
- Конденсаторы 33 пФ (2 шт.) (купить на AliExpress).
- Резисторы 2 кОм, 4,7 кОм (купить на AliExpress).
- Мультиметр для измерения выходного напряжения.
- Источник питания 5V.
- Макетная плата.
- Соединительные провода.
Реклама: ООО "АЛИБАБА.КОМ (РУ)" ИНН: 7703380158
Принципы работы ЦАП MCP4921
Модуль MCP4921 представляет собой 12-битный ЦАП, то есть работает с 12-битным разрешением. Это значит что на своем выходе он может формировать 4096 различных значений. Используя это и значение опорного напряжения можно легко рассчитать шаг преобразования. Если у нас опорное напряжение равно 5V, то, используя формулу 5/4095, получим что шаг ЦАП равен 0.00122100122 милливольт. То есть изменение значения на входе ЦАП на единицу будет приводить к изменению напряжения на его выходе на величину равную 0.00122100122 милливольт.
Распиновка микросхемы MCP4921 представлена на следующем рисунке. Как видим, она имеет 8 контактов.
С микроконтроллерами ЦАП MCP4921 обменивается информацией используя протокол SPI. Более подробно об использовании протокола SPI в микроконтроллерах PIC16F877A вы можете прочитать в этой статье.
Для передачи данных и команд модулю ЦАП MCP4921 необходимо понимать принципы работы его регистра команд. Структура данного регистра представлена на следующем рисунке.
Регистр команд представляет собой 16-битный регистр. Биты с 15 по 12 данного регистра используются для настройки команд. В данном проекте мы будем использовать конфигурацию данных бит, представленную в следующей таблице.
Номер бита | Конфигурация | Значение конфигурации |
Bit 15 | DACA | 0 |
Bit 14 | Unbuffered | 0 |
Bit 13 | 1x(VOUT*D/4096) | 1 |
Bit 12 | бит управления выходной мощностью | 1 |
Для того чтобы настроить регистр команд на эту конфигурацию, ему необходимо в двоичном виде передать 0011, а следом за ними передать информацию для бит с D11 до D0. То есть ЦАП MCP4921 необходимо передать 16 бит данных в формате 0011 xxxx xxxx xxxx, где первые 4 бита являются наиболее значащими битами (MSB), а остальные – наименее значащими битами (LSB). Более подробно это показано на следующей временной диаграмме.
Как видно из представленной диаграммы, на контакт CS необходимо подавать уровень low на все время передачи команды модулю MCP4921.
Схема проекта
Схема подключения ЦАП MCP4921 к микроконтроллеру PIC16F877A представлена на следующем рисунке.
Внешний вид собранной конструкции проекта показан на следующем рисунке.
Объяснение программы для микроконтроллера PIC
Полный код программы приведен в конце статьи, здесь же мы кратко рассмотрим его основные фрагменты.
Первым делом в программе настроим биты конфигурации микроконтроллера PIC.
1 2 3 4 5 6 7 8 9 10 11 12 |
// PIC16F877A Configuration Bit Settings // 'C' source line config statements // CONFIG #pragma config FOSC = HS // Oscillator Selection bits (HS oscillator) #pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled) #pragma config PWRTE = OFF // Power-up Timer Enable bit (PWRT disabled) #pragma config BOREN = ON // Brown-out Reset Enable bit (BOR enabled) #pragma config LVP = OFF // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3/PGM pin has PGM function; low-voltage programming enabled) #pragma config CPD = OFF // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off) #pragma config WRT = OFF // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control) #pragma config CP = OFF // Flash Program Memory Code Protection bit (Code protection off) |
Далее подключим необходимые заголовочные файлы, укажем частоту кварцевого генератора и контакт, к которому подключен контакт CS модуля ЦАП.
1 2 3 4 5 6 7 8 9 10 11 |
#include <xc.h> #include <stdint.h> #include "supporing_cfile\lcd.h" #include "supporing_cfile\PIC16F877a_SPI.h" /* Hardware related definition */ #define _XTAL_FREQ 200000000 //Crystal Frequency, used in delay #define DAC_CS PORTCbits.RC0 //Declaring DAC CS pin |
Запрограммируем функцию SPI_Initialize_Master(), в которой будут задаваться настройки для работы с протоколом SPI. Регистр SSPSTAT конфигурируется таким образом, чтобы входные данные дискретизировались в конце времени выходных данных, а частота синхронизации SPI настраивается таким образом, чтоб передача осуществляется во время переключение между активным и незанятым/нерабочим (idle). Остальные настройки для работы с протоколом SPI будут стандартными. В комментариях указаны страницы даташита на микроконтроллер, в которых описаны эти настройки.
1 2 3 4 5 6 7 |
void SPI_Initialize_Master() { TRISC5 = 0; // Set as output SSPSTAT = 0b11000000; //pg 74/234 SSPCON = 0b00100000; //pg 75/234 TRISC3 = 0; //Set as output for slave mode } |
Также немного модифицируем функцию SPI_Write() таким образом, что передача данных будет происходить после очистки буфера.
1 2 3 4 5 |
void SPI_Write(char incoming) { SSPBUF = incoming; //Write the user given data into buffer while (!SSPSTATbits.BF); } |
Основной частью нашей программы будет драйвер для работы с модулем MCP4921. Его код немного сложноват поскольку команды и цифровые данные объединяются вместе в 16-битный формат для передачи по протоколу SPI.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
/* This Function is for converting the digital value to the analog. */ void convert_DAC(unsigned int value) { /*Step Size = 2^n, Therefore 12bit 2^12 = 4096 For 5V reference, the step will be 5/4095 = 0.0012210012210012V or 1mV (approx)*/ unsigned int container ; unsigned int MSB; unsigned int LSB; /*Step: 1, stored the 12 bit data into the container Suppose the data is 4095, in binary 1111 1111 1111*/ container = value; /*Step: 2 Creating Dummy 8 bit. So, by dividing 256, upper 4 bits are captured in LSB LSB = 0000 1111*/ LSB = container/256; /*Step: 3 Sending the configuration with punching the 4 bit data. LSB = 0011 0000 OR 0000 1111. Result is 0011 1111 */ LSB = (0x30) | LSB; /*Step:4 Container still has the 21bit value. Extracting the lower 8 bits. 1111 1111 AND 1111 1111 1111. Result is 1111 1111 which is MSB*/ MSB = 0xFF & container; /*Step:4 Sending the 16bits data by dividing into two bytes. */ DAC_CS = 0; // CS is low during data transmission. As per the data-sheet it is required SPI_Write(LSB); SPI_Write(MSB); DAC_CS = 1; } |
В основной функции программы main в цикле loop мы последовательно на вход ЦАП подаем такие значения, чтобы на его выходе сформировались уровни напряжений 1V, 2V, 3V, 4V и 5V. Для этого мы необходимое значение выходного напряжения делим на величину 0.0012210012210012 милливольт.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
void main() { system_init(); introduction_screen(); int number=0; int volt=0; while (1) { for (volt=1; volt<=MAX_VOLT; volt++){ number = volt / 0.0012210012210012; clear_screen(); lcd_com(FIRST_LINE); lcd_puts("DATA Sent:- "); lcd_print_number(number); lcd_com(SECOND_LINE); lcd_puts("Output:- "); lcd_print_number(volt); lcd_puts("V"); convert_DAC(number); __delay_ms(300); } } } |
Тестирование работы проекта
Тестирование работы ЦАП мы производили с помощью мультиметра. Результаты тестирования вы можете посмотреть на следующих рисунках.
Более подробно работу проекта вы можете посмотреть на видео, приведенном в конце статьи.
Исходный код программы
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
/* * File: main.c * Author: Sourav Gupta *<|:-] * Created for: circuitdigest.com * Project On: mcp4921 interfacing * Created on March 21, 2019, 7:05 PM */ // PIC16F877A Configuration Bit Settings // 'C' source line config statements // CONFIG #pragma config FOSC = HS // Oscillator Selection bits (HS oscillator) #pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled) #pragma config PWRTE = OFF // Power-up Timer Enable bit (PWRT disabled) #pragma config BOREN = ON // Brown-out Reset Enable bit (BOR enabled) #pragma config LVP = OFF // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3/PGM pin has PGM function; low-voltage programming enabled) #pragma config CPD = OFF // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off) #pragma config WRT = OFF // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control) #pragma config CP = OFF // Flash Program Memory Code Protection bit (Code protection off) #include <xc.h> #include <stdint.h> #include "supporing_cfile\lcd.h" #include "supporing_cfile\PIC16F877a_SPI.h" /* Hardware related definition */ #define _XTAL_FREQ 200000000 //Crystal Frequency, used in delay #define DAC_CS PORTCbits.RC0 //Declaring DAC CS pin /* Program Flow related definition */ #define MAX_VOLT 5 #define FIRST_LINE 0x80 #define SECOND_LINE 0xC0 /* Other Specific function definition */ void system_init(void); void sw_delayms(unsigned int d); void convert_DAC(unsigned int digital_value); void clear_screen(void); void introduction_screen(void); void main() { system_init(); introduction_screen(); int number=0; int volt=0; while (1) { for (volt=1; volt<=MAX_VOLT; volt++){ number = volt / 0.0012210012210012; clear_screen(); lcd_com(FIRST_LINE); lcd_puts("DATA Sent:- "); lcd_print_number(number); lcd_com(SECOND_LINE); lcd_puts("Output:- "); lcd_print_number(volt); lcd_puts("V"); convert_DAC(number); __delay_ms(300); } } } /* This Function is for software delay. */ void sw_delayms(unsigned int d){ int x, y; for(x=0;x<d;x++) for(y=0;y<=1275;y++); } /* This Function is for system initializations. */ void system_init(void){ TRISB = 0x00; // LCD pin as output TRISCbits.TRISC0=0; // CS pin declared as output lcd_init(); // This will initialize the lcd SPI_Initialize_Master(); } /* Функция для очистки экрана ЖК дсиплея */ void clear_screen(void){ lcd_com(FIRST_LINE); lcd_puts(" "); lcd_com(SECOND_LINE); lcd_puts(" "); } /* Функция для отображения приветственного сообщения на экране дисплея */ void introduction_screen(void){ lcd_com(FIRST_LINE); lcd_puts("Welcome to"); lcd_com(SECOND_LINE); lcd_puts("circuit Digest"); __delay_ms(500); clear_screen(); lcd_com(FIRST_LINE); lcd_puts("mcp4921 with"); lcd_com(SECOND_LINE); lcd_puts("PIC16F877A"); __delay_ms(350); } /* Функция для преобразования цифрового значения в аналоговое */ void convert_DAC(unsigned int value) { /*Step Size = 2^n, Therefore 12bit 2^12 = 4096 Для опорного напряжения 5V шаг будет 5/4095 = 0.0012210012210012V or 1mV (approx)*/ unsigned int container ; unsigned int MSB; unsigned int LSB; /*Step: 1, сохраняем 12-битные данные в контейнер предполагая что данные у нас 4095, в двоичном виде это 1111 1111 1111*/ container = value; /*Step: 2 Creating Dummy 8 bit. So, by dividing 256, upper 4 bits are captured in LSB LSB = 0000 1111*/ LSB = container/256; /*Step: 3 Sending the configuration with punching the 4 bit data. (передаем настройки вместе с 4 битами данных) LSB = 0011 0000 OR 0000 1111. Result is 0011 1111 */ LSB = (0x30) | LSB; /*Step:4 Контейнер все еще содержит 21-битное значение. Вычитаем младшие 8 бит. 1111 1111 AND 1111 1111 1111. Result is 1111 1111 which is MSB*/ MSB = 0xFF & container; /*Step:4 Передаем 16 бит данных при помощи разделения их на 2 байта */ DAC_CS = 0; // на контакт CS модуля ЦАП необходимо подавать уровень low во время всего процесса передачи как требует его даташит SPI_Write(LSB); SPI_Write(MSB); DAC_CS = 1; } |