В данной статье мы рассмотрим процесс сохранения данных в энергонезависимую память EEPROM (Electrically (Electrical) Erasable Programmable Read-Only Memory – электрически стираемое программируемое постоянное запоминающее устройство), присутствующую в микроконтроллере PIC16F877A. Сохранение данных в этом виде памяти необходимо в случаях, когда мы хотим обеспечить их сохранность когда питание микроконтроллера будет отключено. На первый взгляд сохранение данных в память EEPROM может показаться достаточно трудоемким процессом, но с помощью компилятора XC8 данный процесс можно значительно упростить. Подробнее об использовании компилятора XC8 для программирования микроконтроллеров PIC можно прочитать в этой статье.
В памяти EEPROM микроконтроллера PIC16F877A можно хранить лишь небольшие объемы данных. Когда вам необходимо сохранить большой объем данных (мегабайты и более) в энергонезависимом типе памяти, используйте для этой цели SD карту.
Для лучшего понимания материала данной статьи рекомендуем ознакомиться с проектами по подключению ЖК дисплея к микроконтроллеру PIC и использование в нем модуля АЦП (аналого-цифрового преобразования) поскольку работа с данными элементами будет использоваться в настоящей статье.
Энергонезависимая память EEPROM в микроконтроллере PIC16F877A
Как следует из ее названия память EEPROM представляет собой электрически стираемое программируемое постоянное запоминающее устройство. Данные в данной памяти не уничтожаются после отключения питания микроконтроллера – их можно стереть только соответствующими инструкциями в программе. Объем имеющейся памяти EEPROM зависит от модели микроконтроллера. Как следует из даташита на микроконтроллер PIC16F877A объем памяти EEPROM составляет в нем 256 байт.
Схема проекта
Схема для демонстрации возможностей использования памяти EEPROM в микроконтроллере PIC16F877A представлена на следующем рисунке.
ЖК дисплей используется для отображения необходимых данных. Потенциометр подключен к контакту AN4 микроконтроллера PIC16F877A – считываемое с него значение мы и будем сохранять в памяти EEPROM. Также в проекте мы подключили кнопку к контакту RB0 микроконтроллера – при ее нажатии данные с выхода АЦП контакта AN4 будут записываться в память EEPROM. Схему можно собрать на перфорированной или макетной плате.
Соединения микроконтроллера PIC16F877A в данной схеме приведены в следующей таблице.
№ п/п | № контакта микроконтроллера | Наименование контакта микроконтроллера | Куда подключен |
1 | 21 | RD2 | RS of LCD |
2 | 22 | RD3 | E of LCD |
3 | 27 | RD4 | D4 of LCD |
4 | 28 | RD5 | D5 of LCD |
5 | 29 | RD6 | D6 of LCD |
6 | 30 | RD7 | D7 of LCD |
7 | 33 | RBO/INT | кнопка |
8 | 7 | AN4 | потенциометр |
Как видите, соединений сравнительно немного.
Моделирование работы проекта
Прежде чем "вживую" собирать схему проекта, можно сначала протестировать работу проекта в симуляторе Proteus. Hex файл программы для этого можно скачать по следующей ссылке. Внешний вид смоделированной в симуляторе Proteus схемы проекта приведен на следующем рисунке.
Во время моделирования работы схемы в симуляторе Proteus вы можете выводить на экран ЖК дисплея измеренное в текущий момент значение с выхода АЦП и хранящееся в памяти EEPROM значение АЦП. Чтобы сохранить текущее значение АЦП в памяти EEPROM нажмите кнопку, подключенную к контакту RB0 микроконтроллера.
Объяснение программы для микроконтроллера PIC
Полный код программы приведен в конце статьи, здесь же мы кратко рассмотрим его основные фрагменты. В коде нашей программы мы будем считывать значения с модуля АЦП микроконтроллера PIC и при нажатии кнопки сохранять их в память EEPROM микроконтроллера.
В даташите на микроконтроллер PIC16F877A написана следующая фраза: “These devices have 4 or 8K words of program Flash, with an address range from 0000h to 1FFFh for the PIC16F877A”. Это означает что память EEPROM в нашем микроконтроллере занимает адреса с 0000h до 1FFFh. Мы можем непосредственно обратиться к любому из этих адресов. Для сохранения данных по определенному адресу памяти EEPROM используйте следующую команду:
1 |
eeprom_write(0,adc); |
В этой команде “adc” обозначает переменную целого типа, значение которой нужно сохранить в памяти. А “0” – это адрес в памяти EEPROM, по которому необходимо сохранить значение этой переменной. Синтаксис этой команды “eeprom_write” поддерживается нашим компилятором XC8, поэтому при ее выполнении регистры микроконтроллера автоматически будут сконфигурированы необходимым образом.
Для извлечения данных из памяти EEPROM и их записи в необходимую переменную можно использовать следующую команду:
1 |
Sadc = (int)eeprom_read(0); |
В результате выполнения данной команды считанное из памяти EEPROM значение будет сохранено в переменной “Sadc”. А “0” в этой команде обозначает адрес памяти EEPROM, с которого мы будем считывать данные. Синтаксис команды “eeprom_read” также обеспечивается нашим компилятором XC8, поэтому при ее выполнении регистры микроконтроллера автоматически будут сконфигурированы необходимым образом. Данные в памяти EEPROM хранятся в шестнадцатеричном формате, поэтому после считывания мы конвертируем их в целый тип данных добавляя префикс (int) перед командой.
Тестирование работы проекта
После сборки схемы проекта и загрузки программы в микроконтроллер вы можете приступить к тестированию работы проекта. Если все работает должным образом, на экране ЖК дисплея вы должны увидеть считываемые значения АЦП. Для сохранения текущего значения с АЦП в память EEPROM нажмите кнопку. Проверить, действительно ли ваше значение сохранилось в память EEPROM, можно, отключив питание схемы и затем снова ее включив. При повторном подаче питания на схему вы должны увидеть сохраненное ранее в памяти значение на экране ЖК дисплея.
Более подробно работу проекта вы можете посмотреть на видео, приведенном в конце статьи.
Исходный код программы
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 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
#define _XTAL_FREQ 20000000 #define RS RD2 #define EN RD3 #define D4 RD4 #define D5 RD5 #define D6 RD6 #define D7 RD7 #include <xc.h> #pragma config FOSC = HS // Oscillator Selection bits (HS oscillator) #pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled) #pragma config PWRTE = ON // Power-up Timer Enable bit (PWRT enabled) #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 is digital I/O, HV on MCLR must be used for programming) #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) //LCD Functions Developed by Circuit Digest. void Lcd_SetBit(char data_bit) //Based on the Hex value Set the Bits of the Data Lines { if(data_bit& 1) D4 = 1; else D4 = 0; if(data_bit& 2) D5 = 1; else D5 = 0; if(data_bit& 4) D6 = 1; else D6 = 0; if(data_bit& 8) D7 = 1; else D7 = 0; } void Lcd_Cmd(char a) { RS = 0; Lcd_SetBit(a); //Incoming Hex value EN = 1; __delay_ms(4); EN = 0; } void Lcd_Clear() { Lcd_Cmd(0); //очищаем экран ЖК дисплея Lcd_Cmd(1); //перемещаем курсор в 1-ю позицию } void Lcd_Set_Cursor(char a, char b) { char temp,z,y; if(a== 1) { temp = 0x80 + b - 1; //80H is used to move the curser z = temp>>4; //младшие 8 бит y = temp & 0x0F; //старшие 8 бит Lcd_Cmd(z); //устанавливаем строку Lcd_Cmd(y); //устанавливаем столбец } else if(a== 2) { temp = 0xC0 + b - 1; z = temp>>4; // младшие 8 бит y = temp & 0x0F; // старшие 8 бит Lcd_Cmd(z); // устанавливаем строку Lcd_Cmd(y); // устанавливаем столбец } } void Lcd_Start() { Lcd_SetBit(0x00); for(int i=1065244; i<=0; i--) NOP(); Lcd_Cmd(0x03); __delay_ms(5); Lcd_Cmd(0x03); __delay_ms(11); Lcd_Cmd(0x03); Lcd_Cmd(0x02); //02H is used for Return home -> Clears the RAM and initializes the LCD Lcd_Cmd(0x02); //02H is used for Return home -> Clears the RAM and initializes the LCD Lcd_Cmd(0x08); //Select Row 1 Lcd_Cmd(0x00); //Clear Row 1 Display Lcd_Cmd(0x0C); //Select Row 2 Lcd_Cmd(0x00); //Clear Row 2 Display Lcd_Cmd(0x06); } void Lcd_Print_Char(char data) //Send 8-bits through 4-bit mode { char Lower_Nibble,Upper_Nibble; Lower_Nibble = data&0x0F; Upper_Nibble = data&0xF0; RS = 1; // => RS = 1 Lcd_SetBit(Upper_Nibble>>4); //Send upper half by shifting by 4 EN = 1; for(int i=2130483; i<=0; i--) NOP(); EN = 0; Lcd_SetBit(Lower_Nibble); //Send Lower half EN = 1; for(int i=2130483; i<=0; i--) NOP(); EN = 0; } void Lcd_Print_String(char *a) { int i; for(i=0;a[i]!='\0';i++) Lcd_Print_Char(a[i]); //разделяем строку на отдельные символы с помощью указателей и отдельно передаем каждый символ } /*****End of LCD Functions*****/ //**функции для работы с АЦП***// void ADC_Initialize() { ADCON0 = 0b01000001; //ADC ON and Fosc/16 is selected (включаем АЦП и устанавливаем частоту Fosc/16) ADCON1 = 0b11000000; // выбираем внутреннее опорное напряжение } unsigned int ADC_Read(unsigned char channel) { ADCON0 &= 0x11000101; //Clearing the Channel Selection Bits ADCON0 |= channel<<3; //Setting the required Bits __delay_ms(2); //Acquisition time to charge hold capacitor GO_nDONE = 1; //Initializes A/D Conversion (старт АЦП) while(GO_nDONE); //ждем пока процесс АЦП не завершится return ((ADRESH<<8)+ADRESL); //возвращаем результат } //***End of ADC Functions***// int main() { int adc=0; //переменная для считывания значения АЦП int a1,a2,a3,a4; //переменные чтобы разделить значение АЦП на отдельные символы int Sadc=0; // переменная для считывания значения АЦП int Sa1,Sa2,Sa3,Sa4; // переменные чтобы разделить значение АЦП на отдельные символы TRISD = 0x00; // контакты порта PORTD будут работать на вывод данных – к ним подключен ЖК дисплей TRISA4 =1; //контакт AN4 будет работать на ввод данных TRISB0 = 1; OPTION_REG=0b00000000; ADC_Initialize(); Lcd_Start(); Lcd_Clear(); while(1) { adc=ADC_Read(4); //считываем значение с АЦП //**Display ADC**// a1 = (adc/1000)%10; a2 = (adc/100)%10; a3 = (adc/10)%10; a4 = (adc/1)%10; Lcd_Set_Cursor(1,1); Lcd_Print_String("ADC:"); Lcd_Print_Char(a1+'0'); Lcd_Print_Char(a2+'0'); Lcd_Print_Char(a3+'0'); Lcd_Print_Char(a4+'0'); //**Display SADC**// Sa1 = (Sadc/1000)%10; Sa2 = (Sadc/100)%10; Sa3 = (Sadc/10)%10; Sa4 = (Sadc/1)%10; Lcd_Set_Cursor(2,1); Lcd_Print_String("Saved ADC:"); Lcd_Print_Char(Sa1+'0'); Lcd_Print_Char(Sa2+'0'); Lcd_Print_Char(Sa3+'0'); Lcd_Print_Char(Sa4+'0'); /*These devices have 4 or 8K words of program Flash, with an address range from 0000h to 1FFFh for the PIC16F877A*/ if (RB0==0) {eeprom_write(0,adc);} Sadc = (int)eeprom_read(0); Lcd_Set_Cursor(1,1); Lcd_Print_String("ADC:"); } return 0; } |