Полтора года назад я уже бегло рассматривал протокол I2C, теперь же настало время изучить его более подробно.
Попробуем написать программную реализацию протокола, рассмотрим "подводные камни" такой реализации, а также способы отладки шины I2C. Для обкатки алгоритма попробуем подключить следующие устройства на шине I2C: RTC DS1307/DS3231 и EEPROM AT24C512/AT24C32.
Для проектирования будет использована CAD Proteus_8.5. Для проверки на реальном железе будет использован чип MSP430G2453.
Итак, тренироваться будем на RTC DS1307, но здесь должен заметить, что данный чип относиться к 5-вольтовой логике поэтому с 3.3-вольтовым MSP430 он работать не будет. Заставить работать данную связку можно только в Proteus. Для тестирования на реальном железе я буду использовать DS3231 модуль. Однако для того чтобы понять I2C не нужен готовый модуль, он будет только мешать.
Когда не очень опытный радиолюбитель берет устройство на I2C, ему надо его как-то проверить, убедиться что оно хотя бы в принципе работает. Как это сделать? Обычно делается это на Arduino (я по крайней мере так делаю). Находится какой-нибудь скетч для проверки, и на нем проверяется.
Однако, при наличии проблем, как узнать в чем кроется загвоздка: в "косячной" библиотеке или в железке? Например, если мы загрузим этот скетч для работы с DS1307, и запустим его БЕЗ всякого модуля, то получим такую картинку:
Как видно, мы получили какие-то непонятные значения и если бы сбойная микросхема RTC была бы подключена, нам оставалось бы только гадать, где скрывается проблема: в коде или в железке.
И здесь проблема здесь не в низком качестве кода скетча. Если посмотрим API References:
то увидим, что при установлении связи нам ничего не возвращается, в то время как по спецификации протокола, если устройство приняло свой адрес и готово к работе, оно посылает ответ - ACK.
Поэтому давайте отбросим Arduino в сторону и напишем свой код c шахматами и поэтессами с обработкой исключительных ситуаций.
Но прежде, чем что-то написать, следует заглянуть в руководство по DS1307:
В первую очередь нас будет интересовать принципиальная схема. На ней ничего сложного нет, только три подтягивающих резистора(красные стрелки), батарея для автономной работы, да кварц. Без батареи чип вполне будет работать, даже без кварца будет работать, часы конечно тогда не будут идти, но на чип вполне можно будет записывать и считывать с него.
Первые два подтягивающих резистора Rpu формируют шину I2C. Если посмотреть на схему китайского модуля(взято отсюда):
То здесь используются резисторы номиналом на 3.3K, но я встречал схемы где резисторы были и на 10K.
У меня на паечной макетке это получилось как-то так:
Если у вас получится лучше, запишите это себе в плюсы. Здесь слева - это чип EEPROM(о нем позже). Моя наверное главная ошибка - резонатор следовало повернуть на 90 градусов, сэкономилось бы место на плате.
Теперь в Proteus соберем следующую схему:
Здесь кроме чипа DS1307, часового кварца и гальванической батареи, также понадобится источник постоянного напряжения на 3.3 Вольт, и I2C отладчик.
Для освежения памяти, я скопировал из своего предыдущего поста диаграмму протокола I2C и его основные положения:
показать
Waveform showing the clock and data timing for an I2C message.
Итак, работа с I2C идет сессиями. В руководстве представлены варианты допустимых сессий для записи и чтения:
Есть три варианта сессий: для записи, для чтения, и для чтения с определенного адреса.
Карта регистров DS1307:
Т.е. имеем 7 часовых регистра, 1 регистр конфигурации SQW и 56 байт ОЗУ где можно хранить свои данные.
Самая простая успешная сессия для I2C шины, это передача адреса в режиме записи, с последующим завершением сессии. Т.е. это такой опрос, есть ли такое устройство на линии/на связи.
Взяв за основу программы программного UART-передатчика из предыдущего поста и программной реализации I2C для AVR из поста за 2015 год, (в основу которой в свою очередь лег код из Procyon avrLib,) я сочинил такой код:
#include <msp430g2553.h> #include <sys/types.h> #include <stdio.h> #define LED BIT0 #define BTN BIT1 // UART #define RXD BIT1 #define TXD BIT2 #define uOUT P1OUT #define uDIR P1DIR // I2C #define SCL BIT0 #define SDA BIT1 #define sclOUT P2OUT #define sdaOUT P2OUT #define sclDIR P2DIR #define sdaDIR P2DIR #define sclREN P2REN #define sdaREN P2REN #define sdaIN P2IN #define sclIN P2IN #define QDEL __delay_cycles(20); #define HDEL __delay_cycles(40); #define SDA_I2C_HI sdaDIR&=~SDA #define SCL_I2C_HI sclDIR&=~SCL #define SDA_I2C_LO sdaDIR|=SDA #define SCL_I2C_LO sclDIR|=SCL #define SCL_TOGGLE_I2C HDEL; SCL_I2C_HI;HDEL;SCL_I2C_LO; #define DS1307_ADR 0xD0 volatile uint16_t count; #pragma vector=TIMER0_A0_VECTOR __interrupt void Timer0(void){ count=0; } /////////////// I2C ////////////////////////////////// static int start_i2c() { int ret=64; uint8_t valueSDA, valueSCL; // init I2C sdaOUT &=~SDA; sclOUT &=~SCL; do { SDA_I2C_HI; SCL_I2C_HI; QDEL; valueSDA=sdaIN & SDA; valueSCL=sclIN & SCL; } while((!valueSDA || !valueSCL) && --ret>0); if (!ret) return 0; SDA_I2C_LO;QDEL; SCL_I2C_LO;QDEL; return ret; } static int stop_i2c(){ SDA_I2C_LO; SCL_I2C_LO;HDEL; SCL_I2C_HI;QDEL; SDA_I2C_HI;QDEL; if (!(sdaIN & SDA) || !(sclIN & SCL)) return 1; else return 0; } static uint8_t send_i2c(uint8_t value) { int bits=8; while(bits>0) { bits--; SCL_I2C_LO; QDEL; while((sclIN & SCL)); if (value & (1<<bits)) SDA_I2C_HI; else SDA_I2C_LO; SCL_TOGGLE_I2C; } //get ACK QDEL; SDA_I2C_HI; QDEL; SCL_I2C_HI; while(!(sclIN & SCL)); QDEL; uint8_t ack; ack=(sdaIN & SDA); QDEL; SCL_I2C_LO; HDEL; return ack; } ///////////// UART ///////////////////////////////// uint8_t send_uart(uint8_t ch){ count=1; int i; ch=~ch; TA0CCR0=208; // 4800/1MHz TA0CCTL0=CCIE; // enable interrupt // begin while(count);count=1; uOUT&=~TXD; // START bit for(i=0;i<8;i++) // DATA bis { while(count); count=1; uOUT=(ch&(1<<i)) ? uOUT & ~TXD: uOUT | TXD; } while(count);count=1; uOUT &= ~TXD; while(count); uOUT |= TXD; // STOP bit TA0CCTL0=0; // disable interrupt return ch; } void send_string(char str[]) { int i; for(i=0; str[i]!='\0';i++) { if (str[i] == '\n') send_uart('\r'); else send_uart(str[i]); } } void send_number(int value) { char str[10]; sprintf(str,"%d",value); send_string(str); } int main (void) { WDTCTL = WDTPW | WDTHOLD; P1DIR=LED; count=0; // F_CPU 1MHz BCSCTL1 = CALBC1_1MHZ; DCOCTL = CALDCO_1MHZ; BCSCTL2 &=~(DIVS_0); //SMCLK=DCO BCSCTL3 |= LFXT1S_2; //ACLK= VLO =~ 12KHz ///////// TIMER0_A used by Sowtfare UART/////////////////// uDIR+=TXD; TA0CTL=TASSEL_2 + MC_1 + ID_0 + TACTL; /* TASSEL_2 =use SMCLK; MC_1 =continous to TACCR0; ID_0 =prescaler = 1; TACLR =clean counter TAR; TAIE; =enable timer interrupt; */ __enable_interrupt(); for(;;) { int i; // delay ~~1 sec for(i=0;i<10;i++) __delay_cycles(0xAFFF); // Check i2c device 0xD0 (DS1307) // START bit int attempts; attempts=start_i2c(); if (attempts){ //send_string("OK, attempts: "); send_number(65-attempts); send_uart('\r'); // DEBUG message // send ADRESS uint8_t ack=send_i2c(DS1307_ADR); // if ACK == 0 it't SUCCESS, otherwise FAILURE if (ack) { send_string("ERROR: DS1307 device not found!\r"); continue; //exit with FAILURE } else send_string("DS1307 device found.\r"); // DEBUG message // STOP bit if(stop_i2c()) { send_string("ERROR set STOP bit!"); return 2;// exit with FAILURE } }else send_string("ERROR of connect!\r"); } return 0; }
Результат работы программы:
В I2C отладчике символ S - означает Start, начало сесси, P - stoP, завершение сессии, и D0 - это передаваемый программой адрес. A - означает, что от устройства был получен ACK, ответ т.е.
Если константу DS1307_ADR заменить на другое четное значение, то через UART терминал будет выдавать сообщение, что устройство не найдено. Если передать нечтный адрес, т.е. адрес с режимом чтения, то программа войдет а ERROR-loop, т.к. чип DS1307 будет ожидать NACK для завершения режима чтения.
Для реального чипа программа немного отличается:
#include <msp430g2553.h> #include <sys/types.h> #include <stdio.h> //////// DEFINES /////////////////////////////////////// #define LED BIT0 // UART #define RXD BIT1 #define TXD BIT2 #define uOUT P1OUT #define uDIR P1DIR // I2C #define SCL BIT0 #define SDA BIT1 #define sclOUT P2OUT #define sdaOUT P2OUT #define sclDIR P2DIR #define sdaDIR P2DIR #define sclREN P2REN #define sdaREN P2REN #define sdaIN P2IN #define sclIN P2IN #define QDEL __delay_cycles(20); #define HDEL __delay_cycles(40); #define SDA_I2C_HI sdaDIR&=~SDA #define SCL_I2C_HI sclDIR&=~SCL #define SDA_I2C_LO sdaDIR|=SDA #define SCL_I2C_LO sclDIR|=SCL #define SCL_TOGGLE_I2C HDEL; SCL_I2C_HI;HDEL;SCL_I2C_LO; #define DS1307_ADR 0xD0 /////////// END DEFINES ///////////////////////////////////// ////////////////////////////////////////////////// /////// INTERUPTS //////////////////////////// volatile uint16_t count; #pragma vector=TIMER0_A0_VECTOR __interrupt void Timer0(void) { count=0; } // for delay_ms() #pragma vector=TIMER0_A1_VECTOR __interrupt void Timer0_OVF(void) { switch( TA0IV ) { case TA0IV_TACCR1: break; // CCR1 not used case TA0IV_TACCR2: break; // CCR2 not used case TA0IV_TAIFG: if (count) count--; // overflow break; } } /////////////// I2C ////////////////////////////////// static int start_i2c() { int ret=64; uint8_t valueSDA, valueSCL; // init I2C sdaOUT &=~SDA; sclOUT &=~SCL; do { SDA_I2C_HI; SCL_I2C_HI; QDEL; valueSDA=sdaIN & SDA; valueSCL=sclIN & SCL; } while((!valueSDA || !valueSCL) && --ret>0); if (!ret) return 0; SDA_I2C_LO;QDEL; SCL_I2C_LO;QDEL; return ret; } static int stop_i2c(){ SDA_I2C_LO; SCL_I2C_LO;HDEL; SCL_I2C_HI;QDEL; SDA_I2C_HI;QDEL; if (!(sdaIN & SDA) || !(sclIN & SCL)) return 1; else return 0; } static uint8_t send_i2c(uint8_t value) { int bits=8; while(bits>0) { bits--; SCL_I2C_LO; QDEL; while((sclIN & SCL)); if (value & (1<<bits)) SDA_I2C_HI; else SDA_I2C_LO; SCL_TOGGLE_I2C; } //get ACK QDEL; SDA_I2C_HI; QDEL; SCL_I2C_HI; while(!(sclIN & SCL)); QDEL; uint8_t ack; ack=(sdaIN & SDA); QDEL; SCL_I2C_LO; HDEL; return ack; } ///////////// UART ///////////////////////////////// uint8_t send_uart(uint8_t ch){ count=1; int i; ch=~ch; TA0CCR0=108; //9600 TA0CCTL0=CCIE; // enable interrupt while(count);count=1; // START bit uOUT &= ~TXD; for(i=0;i<8;i++) // DATA transmission { while(count); count=1; uOUT=(ch&(1<<i)) ? uOUT & ~TXD: uOUT | TXD; } // STOP bit while(count);count=1; P1OUT &= ~TXD; while(count); P1OUT |=TXD; TA0CCTL0=0; // disable interrupt return ch; } void send_string(char str[]) { int i; for(i=0; str[i]!='\0';i++) { if (str[i] == '\r') send_uart('\n'); else send_uart(str[i]); } } void send_number(int value) { char str[10]; sprintf(str,"%d",value); send_string(str); } ////////FUNCTIONS ///////////// void wait_ms(uint16_t ms) { count=ms; TA0CCR0=1000; // delay TA0CTL |= TAIE; // enable interrupt while(count); TA0CTL &=~TAIE; // disable interrupt } int main (void) { // WDTCTL = WDTPW | WDTHOLD; P1DIR = LED; count = 0; ///////// TIMER0_A /////////////////// TA0CTL=TASSEL_2 + MC_1 + ID_0 + TACTL; /* TASSEL_2 =use SMCLK; MC_1 =continous to TACCR0; ID_0 =prescaler = 1; TACLR =clean counter TAR; TAIE; =enable timer interrupt; */ /////////// SOFT UART ////////// uDIR += TXD; __enable_interrupt(); for(;;) { wait_ms(1000); P1OUT ^= LED; // Check i2c device 0xD0 (DS1307) // START bit int attempts; attempts=start_i2c(); if (attempts) { // send ADRESS uint8_t ack=send_i2c(DS1307_ADR); // if ACK == 0 it't SUCCESS, otherwise FAILURE if (ack) { send_string("ERROR: DS1307 device not found!\r"); continue; //exit with FAILURE } else send_string("DS1307 device found.\r"); // DEBUG message // STOP bit if(stop_i2c()) { send_string("ERROR set STOP bit!"); return 2;// exit with FAILURE } }else send_string("ERROR of connect!\r"); } return 0; }
Здесь также используется программные UART и I2C протоколы, но UART работает на скорости 9600bit/s, а вместо DS1307 используется DS3231(он совместим с DS1307, а также может работать c 3.3-вольтовой логикой).
Выглядит вся конструкция на макетках как-то так:
В данном случае подключать подтягиваюшие резисторы для I2C шины не нужно, они уже есть в модуле. Ну и конечно, там еще дурацкая схема зарядки аккумулятора, которая батарею просто убъет.
Результат работы:
Здесь я на какое-то время отключал питание от I2C модуля. Как видно, обработка исключительных ситуаций работает.
Между прочим, как оказалось, писать прошивки для реального железа очень удобно в Sublime_text_3:
Когда я писал программу, для меня более всего непонятно было, как следует обратывать ответ ACK. В смысле, на какой стадии чип прижимает линию, стоит ли прижимать ее самому для формирования stop-бита, и нужно ли пропускать такт чтобы дать чипу ответить, т.е. послать ACK.
В Proteus мы можем запустить отладчик, и в пошаговом выполнении наблюдать как все происходит:
Здесь на 10-м выводе назначена SDA. Когда передается единица, линия принимает высокий уровень(красный квадратик). Когда же передается ноль, то линия прижимается к земле(синий квадратик).
На видео получение ACK происходит на 2:38, когда линия "отпускается", но на выводе продолжает оставаться низкий уровень, т.е. чип DS1307 удерживает шину.
Теперь, когда разобрались с самым сложным моментом протокола I2C, написать программу для обмена данными с DS1307 - дело техники.
Вариант для Proteus c DS1307 у меня получился таким:
#include <msp430g2553.h> #include <sys/types.h> #include <stdio.h> #define LED BIT0 #define BTN BIT1 // UART #define RXD BIT1 #define TXD BIT2 #define uOUT P1OUT #define uDIR P1DIR // I2C #define SCL BIT0 #define SDA BIT1 #define sclOUT P2OUT #define sdaOUT P2OUT #define sclDIR P2DIR #define sdaDIR P2DIR #define sclREN P2REN #define sdaREN P2REN #define sdaIN P2IN #define sclIN P2IN #define QDEL __delay_cycles(20); #define HDEL __delay_cycles(40); #define SDA_I2C_HI sdaDIR&=~SDA #define SCL_I2C_HI sclDIR&=~SCL #define SDA_I2C_LO sdaDIR|=SDA #define SCL_I2C_LO sclDIR|=SCL #define SCL_TOGGLE_I2C HDEL; SCL_I2C_HI;HDEL;SCL_I2C_LO; #define DS1307_ADR 0xD0 #define initLEN 2 #define rtcLEN 7 #define LAST 1 #define NOLAST 0 uint8_t init[rtcLEN+initLEN]={0xD0, 0x00, 0x00, 0x10, 0x08, 0x01, 0x19, 0x03, 0x17}; // 08:10:00 19-March-17 uint8_t rtc[rtcLEN]; volatile uint16_t count; #pragma vector=TIMER0_A0_VECTOR __interrupt void Timer0(void){ count=0; } /////////////// I2C ////////////////////////////////// static int start_i2c() { int ret=64; uint8_t valueSDA, valueSCL; // init I2C sdaOUT &=~SDA; sclOUT &=~SCL; do { SDA_I2C_HI; SCL_I2C_HI; QDEL; valueSDA=sdaIN & SDA; valueSCL=sclIN & SCL; } while((!valueSDA || !valueSCL) && --ret>0); if (!ret) return 0; SDA_I2C_LO;QDEL; SCL_I2C_LO;QDEL; return ret; } static int stop_i2c(){ SDA_I2C_LO; SCL_I2C_LO;HDEL; SCL_I2C_HI;QDEL; SDA_I2C_HI;QDEL; if (!(sdaIN & SDA) || !(sclIN & SCL)) return 1; else return 0; } static uint8_t send_i2c(uint8_t value) { int bits=8; while(bits>0) { bits--; SCL_I2C_LO; QDEL; while((sclIN & SCL)); if (value & (1<<bits)) SDA_I2C_HI; else SDA_I2C_LO; SCL_TOGGLE_I2C; } //get ACK QDEL; SDA_I2C_HI; QDEL; SCL_I2C_HI; while(!(sclIN & SCL)); QDEL; uint8_t ack; ack=(sdaIN & SDA); QDEL; SCL_I2C_LO; HDEL; return ack; } uint8_t read_i2c(int last) { uint8_t c, b; int i; b=0; SDA_I2C_HI; for(i=0;i<8;i++) { HDEL; SCL_I2C_HI; c=(sdaIN & SDA); b <<=1; if(c) b|=1; HDEL; SCL_I2C_LO; } if(last) SDA_I2C_HI; else SDA_I2C_LO; SCL_TOGGLE_I2C; SDA_I2C_HI; return b; } ///////////// UART ///////////////////////////////// uint8_t send_uart(uint8_t ch){ count=1; int i; ch=~ch; TA0CCR0=208; // 4800/1MHz TA0CCTL0=CCIE; // enable interrupt // begin while(count);count=1; uOUT&=~TXD; // START bit for(i=0;i<8;i++) // DATA bis { while(count); count=1; uOUT=(ch&(1<<i)) ? uOUT & ~TXD: uOUT | TXD; } while(count);count=1; uOUT &= ~TXD; while(count); uOUT |= TXD; // STOP bit TA0CCTL0=0; // disable interrupt return ch; } void send_string(char str[]) { int i; for(i=0; str[i]!='\0';i++) { if (str[i] == '\n') send_uart('\r'); else send_uart(str[i]); } } void send_number(int value) { char str[10]; sprintf(str,"%d",value); send_string(str); } //////////////// DS1307 /////////////////////////////// uint8_t bcd_dec(uint8_t bcd) { return (bcd-6*(bcd>>4)); } void send_bcd(uint8_t bcd) { static const uint8_t symbol[] ="0123456789ABCDEF"; send_uart(symbol[bcd>>4]); send_uart(symbol[bcd&0x0F]); } static int ds1307_set_data() { // START bit int attempts,i; attempts=start_i2c(); if (attempts){ //send_string("OK, attempts: "); send_number(65-attempts); send_uart('\r'); // DEBUG message // send DATA i=0; uint8_t ack; do { ack=send_i2c(init[i]); } while (!ack && ++i<9); // if ACK == 0 it't SUCCESS, otherwise FAILURE if (ack) { send_string("transmission was failure. broken on: "); send_number(i); send_uart('\r'); return 1; //exit with FAILURE }// else send_string("transmission was success\r"); // DEBUG message // STOP bit if(stop_i2c()) { send_string("ERROR set STOP bit!"); return 2;// exit with FAILURE } }else { send_string("ERROR of write!\r"); return 3; // exit with FAILURE } return 0; } static int ds1307_get_data(){ //////////// WRITE ADDRESS ///////////////////// // START bit int attempts,i; attempts=start_i2c(); if (attempts){ //send_string("OK, attempts: "); send_number(65-attempts); send_uart('\r'); // DEBUG message // send DATA i=0; uint8_t ack; do { ack=send_i2c(init[i]); } while (!ack && ++i<initLEN); // if ACK == 0 it't SUCCESS, otherwise FAILURE if (ack) { send_string("transmission was failure. broken on: "); send_number(i); send_uart('\r'); return 1; //exit with FAILURE }// else send_string("transmission was success\r"); // DEBUG message // STOP bit if(stop_i2c()) { send_string("ERROR set STOP bit!"); return 2;// exit with FAILURE } }else { send_string("ERROR of write!\r"); return 3; // exit with FAILURE } ////////// READ DATA /////////////////////////////// // START bit attempts=start_i2c(); if (attempts){ // send_string("OK, attempts: "); send_number(65-attempts); send_uart('\r'); // DEBUG mesage uint8_t ack=send_i2c(DS1307_ADR | 0x01); if (!ack) { // read DATA for(i=0;i<(rtcLEN-1);i++) rtc[i]=read_i2c(NOLAST); rtc[rtcLEN-1]=read_i2c(LAST); } else { send_string("ERROR reading data!\r"); return 4; // exit with FAILURE; } // STOP bit if(stop_i2c()) { send_string("ERROR set STOP bit!"); return 5; // exit with FAILURE; } } else { send_string("ERROR of read!\r"); return 6; // exit with FAILURE } return 0; // SUCCESS } int main (void) { WDTCTL = WDTPW | WDTHOLD; P1DIR=LED; count=0; // F_CPU 1MHz BCSCTL1 = CALBC1_1MHZ; DCOCTL = CALDCO_1MHZ; BCSCTL2 &=~(DIVS_0); //SMCLK=DCO BCSCTL3 |= LFXT1S_2; //ACLK= VLO =~ 12KHz ///////// TIMER0_A used by Sowtfare UART/////////////////// uDIR+=TXD; TA0CTL=TASSEL_2 + MC_1 + ID_0 + TACTL; /* TASSEL_2 =use SMCLK; MC_1 =continous to TACCR0; ID_0 =prescaler = 1; TACLR =clean counter TAR; TAIE; =enable timer interrupt; */ __enable_interrupt(); ds1307_set_data(); for(;;) { int i; // delay ~~1 sec for(i=0;i<10;i++) __delay_cycles(0xAFFF); if (!ds1307_get_data()) // if OK { send_string("hour: "); send_bcd(rtc[2]); send_string(" minutes: "); send_bcd(rtc[1]); send_string(" seconds: "); send_bcd(rtc[0]&0x7F); send_uart('\r'); } else { send_string("ERROR: \r" ); } } return 0; }
Здесь, для избежании преобразований форматов из BCD в DEC и обратно, установка даты происходит через инициализацию init[] массива УЖЕ в BCD-формате. А через функцию void send_bcd(uint8_t bcd) печатается число в BCD формате.
Собственно, программа сначала устанавливает значения первых семи "часовых" регистров, а затем в цикле через равные интервалы считывает их актуальные значения. Результат работы программы:
Теперь вариант с DS3231 для "живого" MSP430G2453.
DS3231 совместим с DS1307, но при этом имеет несколько ключевых отличий:
Если посмотреть на схему включения RTC, то увидим несколько новых фич:
В DS3231 нет ячеек памяти, только регистры.
Как видно, кроме самих часов появилось два будильника. По ним можно выводить микроконтроллер из спящего режима.
Если в RTC, нужны ячейки ОЗУ, то можно взять DS3232, там имеется 235 свободных байт. Но это сейчас нас не будет интересовать, как и фичи в виде будильников. Только часы.
Программа для установки и чтения значений часов DS3231 для реального чипа MSP430x2xx:
#include <msp430g2553.h> #include <sys/types.h> #include <stdio.h> //////// DEFINES /////////////////////////////////////// #define LED BIT0 // UART #define RXD BIT1 #define TXD BIT2 #define uOUT P1OUT #define uDIR P1DIR // I2C #define SCL BIT0 #define SDA BIT1 #define sclOUT P2OUT #define sdaOUT P2OUT #define sclDIR P2DIR #define sdaDIR P2DIR #define sclREN P2REN #define sdaREN P2REN #define sdaIN P2IN #define sclIN P2IN #define QDEL __delay_cycles(3); #define HDEL __delay_cycles(6); #define SDA_I2C_HI sdaDIR&=~SDA #define SCL_I2C_HI sclDIR&=~SCL #define SDA_I2C_LO sdaDIR|=SDA #define SCL_I2C_LO sclDIR|=SCL #define SCL_TOGGLE_I2C HDEL; SCL_I2C_HI;HDEL;SCL_I2C_LO; #define DS1307_ADR 0xD0 #define initLEN 2 #define rtcLEN 7 #define LAST 1 #define NOLAST 0 uint8_t init[rtcLEN+initLEN]={0xD0, 0x00, 0x00, 0x10, 0x08, 0x01, 0x19, 0x03, 0x17}; // 08:10:00 19-March-17 uint8_t rtc[rtcLEN]; /////////// END DEFINES ///////////////////////////////////// ////////////////////////////////////////////////// /////// INTERUPTS //////////////////////////// volatile uint16_t count; #pragma vector=TIMER0_A0_VECTOR __interrupt void Timer0(void) { count=0; } // for delay_ms() #pragma vector=TIMER0_A1_VECTOR __interrupt void Timer0_OVF(void) { switch( TA0IV ) { case TA0IV_TACCR1: break; // CCR1 not used case TA0IV_TACCR2: break; // CCR2 not used case TA0IV_TAIFG: if (count) count--; // overflow break; } } /////////////// I2C ////////////////////////////////// static int start_i2c() { int ret=64; uint8_t valueSDA, valueSCL; // init I2C sdaOUT &=~SDA; sclOUT &=~SCL; do { SDA_I2C_HI; SCL_I2C_HI; QDEL; valueSDA=sdaIN & SDA; valueSCL=sclIN & SCL; } while((!valueSDA || !valueSCL) && --ret>0); if (!ret) return 0; SDA_I2C_LO;QDEL; SCL_I2C_LO;QDEL; return ret; } static int stop_i2c(){ SDA_I2C_LO; SCL_I2C_LO;HDEL; SCL_I2C_HI;QDEL; SDA_I2C_HI;QDEL; if (!(sdaIN & SDA) || !(sclIN & SCL)) return 1; else return 0; } static uint8_t send_i2c(uint8_t value) { int bits=8; while(bits>0) { bits--; SCL_I2C_LO; QDEL; while((sclIN & SCL)); if (value & (1<<bits)) SDA_I2C_HI; else SDA_I2C_LO; SCL_TOGGLE_I2C; } //get ACK QDEL; SDA_I2C_HI; QDEL; SCL_I2C_HI; while(!(sclIN & SCL)); QDEL; uint8_t ack; ack=(sdaIN & SDA); QDEL; SCL_I2C_LO; HDEL; return ack; } uint8_t read_i2c(int last) { uint8_t c, b; int i; b=0; SDA_I2C_HI; for(i=0;i<8;i++) { HDEL; SCL_I2C_HI; c=(sdaIN & SDA); b <<=1; if(c) b|=1; HDEL; SCL_I2C_LO; } if(last) SDA_I2C_HI; else SDA_I2C_LO; SCL_TOGGLE_I2C; SDA_I2C_HI; return b; } ///////////// UART ///////////////////////////////// uint8_t send_uart(uint8_t ch){ count=1; int i; ch=~ch; TA0CCR0=108; //9600 TA0CCTL0=CCIE; // enable interrupt while(count);count=1; // START bit uOUT &= ~TXD; for(i=0;i<8;i++) // DATA transmission { while(count); count=1; uOUT=(ch&(1<<i)) ? uOUT & ~TXD: uOUT | TXD; } // STOP bit while(count);count=1; P1OUT &= ~TXD; while(count); P1OUT |=TXD; TA0CCTL0=0; // disable interrupt return ch; } void send_string(char str[]) { int i; for(i=0; str[i]!='\0';i++) { if (str[i] == '\r') send_uart('\n'); else send_uart(str[i]); } } void send_number(int value) { char str[10]; sprintf(str,"%d",value); send_string(str); } //////////////// DS1307 /////////////////////////////// uint8_t bcd_dec(uint8_t bcd) { return (bcd-6*(bcd>>4)); } void send_bcd(uint8_t bcd) { static const uint8_t symbol[] ="0123456789ABCDEF"; send_uart(symbol[bcd>>4]); send_uart(symbol[bcd&0x0F]); } static int ds1307_set_data() { // START bit int attempts,i; attempts=start_i2c(); if (attempts){ //send_string("OK, attempts: "); send_number(65-attempts); send_uart('\r'); // DEBUG message // send DATA i=0; uint8_t ack; do { ack=send_i2c(init[i]); } while (!ack && ++i<9); // if ACK == 0 it't SUCCESS, otherwise FAILURE if (ack) { send_string("transmission was failure. broken on: "); send_number(i); send_uart('\r'); return 1; //exit with FAILURE }// else send_string("transmission was success\r"); // DEBUG message // STOP bit if(stop_i2c()) { send_string("ERROR set STOP bit!"); return 2;// exit with FAILURE } }else { send_string("ERROR of write!\r"); return 3; // exit with FAILURE } return 0; } static int ds1307_get_data(){ //////////// WRITE ADDRESS ///////////////////// // START bit int attempts,i; attempts=start_i2c(); if (attempts){ //send_string("OK, attempts: "); send_number(65-attempts); send_uart('\r'); // DEBUG message // send DATA i=0; uint8_t ack; do { ack=send_i2c(init[i]); } while (!ack && ++i<initLEN); // if ACK == 0 it't SUCCESS, otherwise FAILURE if (ack) { send_string("transmission was failure. broken on: "); send_number(i); send_uart('\r'); return 1; //exit with FAILURE }// else send_string("transmission was success\r"); // DEBUG message // STOP bit if(stop_i2c()) { send_string("ERROR set STOP bit!"); return 2;// exit with FAILURE } }else { send_string("ERROR of write!\r"); return 3; // exit with FAILURE } ////////// READ DATA /////////////////////////////// // START bit attempts=start_i2c(); if (attempts){ // send_string("OK, attempts: "); send_number(65-attempts); send_uart('\r'); // DEBUG mesage uint8_t ack=send_i2c(DS1307_ADR | 0x01); if (!ack) { // read DATA for(i=0;i<(rtcLEN-1);i++) rtc[i]=read_i2c(NOLAST); rtc[rtcLEN-1]=read_i2c(LAST); } else { send_string("ERROR reading data!\r"); return 4; // exit with FAILURE; } // STOP bit if(stop_i2c()) { send_string("ERROR set STOP bit!"); return 5; // exit with FAILURE; } } else { send_string("ERROR of read!\r"); return 6; // exit with FAILURE } return 0; // SUCCESS } ////////FUNCTIONS ///////////// void wait_ms(uint16_t ms) { count=ms; TA0CCR0=1000; // delay TA0CTL |= TAIE; // enable interrupt while(count); TA0CTL &=~TAIE; // disable interrupt } int main (void) { // WDTCTL = WDTPW | WDTHOLD; P1DIR = LED; count = 0; ///////// TIMER0_A /////////////////// TA0CTL=TASSEL_2 + MC_1 + ID_0 + TACTL; /* TASSEL_2 =use SMCLK; MC_1 =continous to TACCR0; ID_0 =prescaler = 1; TACLR =clean counter TAR; TAIE; =enable timer interrupt; */ /////////// SOFT UART ////////// uDIR += TXD; __enable_interrupt(); ds1307_set_data(); for(;;) { wait_ms(1000); P1OUT ^= LED; if (!ds1307_get_data()) // if OK { send_string("hour: "); send_bcd(rtc[2]); send_string(" minutes: "); send_bcd(rtc[1]); send_string(" seconds: "); send_bcd(rtc[0]&0x7F); send_uart('\n'); } else { send_string("ERROR: \r" ); } } return 0; }
Хочу обратить внимание на существенно уменьшенные значения задержек QDEL и HDEL. This is Fast I2C.
Результат работы:
Самая скромная флешка с I2C интерфейсом которая была в библиотеке Proteus, это AT24C512 т.е. на 64Кбайт. Данная серия чипов фирмы ATMEL изготавливается в вариантах с напряжением питания: 2.7V-5.5V и 1.8V-3.6V. Тип питания отображается в суффиксе микросхемы.
На I2C шину может быть установлено до четырех чипов включительно, объединяемых в единый банк.
Если посмотреть на руководство к микросхеме, то кроме знакомых выводов SDA и SCL, можно увидеть WP - защита от записи, A0/A1 - номер в банке.
Номер в банке выбирается через адрес I2C устройства:
Адресация в EEPROM двухбайтная. Сначала передается старший байт адреса, затем младший.
Имеется два режима записи в EEPOROM: запись байта с произвольным адресом, и запись "страницами" т.е. последовательностями до 128 байт включительно. Если, например, нужно записать подряд 200 байт по адресу А, то их можно записать двумя сессиями I2C: сначала первые 128 байт, затем оставшиеся 72.
Взято здесь:
page write pseudocode for 1 pages: 0) address is a 16-bit unsigned integer variable 1)send start condition 2)send byte : eeprom i2c address with write bit 3)send byte : high address 4)send byte : low address 5)send 128 bytes of data 6)send stop condition 7)wait until eeprom has finished writing, delay or ACK polling 8)address = address + 128; 9)send start condition 10)send byte : eeprom i2c address with write bit 11)send byte : high address 12)send byte : low address 13)send 128 bytes of data 14)send stop condition 15)wait until eeprom has finished writing, delay or ACK polling
При чтении ограничние на длину поледовательности нет. Здесь все также как в DS1307 за исключением того, что адрес двухбайтный:
Существенным ограничением EEPROM является длительное время записи, до 10ms на байт.
В Proteus добавим на схему чип AT24C512:
На мой взгляд самой востребованной функцией работы с eeprom является чтение запись и чтение массивов. Мы не можем записывать за раз блоками более 128 байт, т.е. для более длиных массивов запись надо бить по примеру того как это делается c буферами в С/С++:
const int BUF_SIZE=65536; char * bf = new char[BUF_SIZE]; for (int j=0; j< I.length; j+=BUF_SIZE) { int chank=std::min(I.length-j,BUF_SIZE); in.read(bf,chank); out.write(bf,chank); } delete[] bf;
Я реализовал функции записи буфера размером до 128 байт и его чтения. Для проверки алгоритма, я решил организовать цикл, который последовательно будет записывать массив со случайными данными на флешку, затем считывать его и проверять контрольную сумму. Контрольная сумма записывается в последний байт массива и рассчитывается через XOR функцию.
Из-за нежелания подключать математическую библиотеку, я вставил собственную функцию генерации случайных чисел в надежде, что она будет не таким громоздким как функция rand(). Алгоритм основан на методе середины квадратов, его реализация была подсмотрена здесь, посмотреть на его работу можно здесь.
Текст программы:
#include <msp430g2553.h> #include <sys/types.h> #include <stdio.h> #define LED BIT0 #define BTN BIT1 // UART #define RXD BIT1 #define TXD BIT2 #define uOUT P1OUT #define uDIR P1DIR // I2C #define SCL BIT0 #define SDA BIT1 #define sclOUT P2OUT #define sdaOUT P2OUT #define sclDIR P2DIR #define sdaDIR P2DIR #define sclREN P2REN #define sdaREN P2REN #define sdaIN P2IN #define sclIN P2IN #define QDEL __delay_cycles(20); #define HDEL __delay_cycles(40); #define SDA_I2C_HI sdaDIR&=~SDA #define SCL_I2C_HI sclDIR&=~SCL #define SDA_I2C_LO sdaDIR|=SDA #define SCL_I2C_LO sclDIR|=SCL #define SCL_TOGGLE_I2C HDEL; SCL_I2C_HI;HDEL;SCL_I2C_LO; #define eeprom_ADR 0xA0 #define LAST 1 #define NOLAST 0 #define pageSIZE 8 #define AT_ARD_LEN 3 uint8_t at24_ard[3]={0xA0,0x00,0x00}; uint8_t at24_data[pageSIZE]; volatile uint16_t count; #pragma vector=TIMER0_A0_VECTOR __interrupt void Timer0(void){ count=0; } long a[] = { 1, 10, 100, 1000, 10000, 100000, 1000000}; long middleSquareNumber(long numb, int dig) { long sqn = numb * numb, next_num = 0; long trim = (dig >>1); sqn = sqn / a[trim]; int i; for (i = 0; i < dig; i++) { next_num += (sqn % (a[trim])) * (a[i]); sqn = sqn / 10; } return next_num; } /////////////// I2C ////////////////////////////////// static int start_i2c() { int ret=64; uint8_t valueSDA, valueSCL; // init I2C sdaOUT &=~SDA; sclOUT &=~SCL; do { SDA_I2C_HI; SCL_I2C_HI; QDEL; valueSDA=sdaIN & SDA; valueSCL=sclIN & SCL; } while((!valueSDA || !valueSCL) && --ret>0); if (!ret) return 0; SDA_I2C_LO;QDEL; SCL_I2C_LO;QDEL; return ret; } static int stop_i2c(){ SDA_I2C_LO; SCL_I2C_LO;HDEL; SCL_I2C_HI;QDEL; SDA_I2C_HI;QDEL; if (!(sdaIN & SDA) || !(sclIN & SCL)) return 1; else return 0; } static uint8_t send_i2c(uint8_t value) { int bits=8; while(bits>0) { bits--; SCL_I2C_LO; QDEL; while((sclIN & SCL)); if (value & (1<<bits)) SDA_I2C_HI; else SDA_I2C_LO; SCL_TOGGLE_I2C; } //get ACK QDEL; SDA_I2C_HI; QDEL; SCL_I2C_HI; while(!(sclIN & SCL)); QDEL; uint8_t ack; ack=(sdaIN & SDA); QDEL; SCL_I2C_LO; HDEL; return ack; } uint8_t read_i2c(int last) { uint8_t c, b; int i; b=0; SDA_I2C_HI; for(i=0;i<8;i++) { HDEL; SCL_I2C_HI; c=(sdaIN & SDA); b <<=1; if(c) b|=1; HDEL; SCL_I2C_LO; } if(last) SDA_I2C_HI; else SDA_I2C_LO; SCL_TOGGLE_I2C; SDA_I2C_HI; return b; } ///////////// UART ///////////////////////////////// uint8_t send_uart(uint8_t ch){ count=1; int i; ch=~ch; TA0CCR0=208; // 4800/1MHz TA0CCTL0=CCIE; // enable interrupt // begin while(count);count=1; uOUT&=~TXD; // START bit for(i=0;i<8;i++) // DATA bis { while(count); count=1; uOUT=(ch&(1<<i)) ? uOUT & ~TXD: uOUT | TXD; } while(count);count=1; uOUT &= ~TXD; while(count); uOUT |= TXD; // STOP bit TA0CCTL0=0; // disable interrupt return ch; } void send_string(char str[]) { int i; for(i=0; str[i]!='\0';i++) { if (str[i] == '\n') send_uart('\r'); else send_uart(str[i]); } } void send_number(int value) { char str[10]; sprintf(str,"%d",value); send_string(str); } //////////// AT24C512 ////////////////////////////// int at24c512_set_ard(int stp){ //////////// WRITE ADDRESS ///////////////////// // START bit int attempts,i; attempts=start_i2c(); if (attempts){ send_string("OK, attempts: "); send_number(65-attempts); send_uart('\r'); // DEBUG message // send DATA i=0; uint8_t ack; do { ack=send_i2c(at24_ard[i]); } while (!ack && ++i<AT_ARD_LEN); // if ACK == 0 it't SUCCESS, otherwise FAILURE if (ack) { send_string("transmission was failure. broken on: "); send_number(i); send_uart('\r'); return 1; //exit with FAILURE } else send_string("transmission was success\r"); // DEBUG message // STOP bit if(stp && stop_i2c()) { send_string("ERROR set STOP bit!"); return 2;// exit with FAILURE } }else { send_string("ERROR of write!\r"); return 3; // exit with FAILURE } return 0; } int eeprom_write_page(int address) { if (pageSIZE > 128) return 0; at24_ard[1]=(uint8_t)(address>>8); at24_ard[2]=(uint8_t)(address&0x00FF); if (!at24c512_set_ard(0)) // if OK { send_string("AT24C512 address setting was success!\r"); } else { send_string("ERROR address setting was failure!\r" ); return 0; } uint8_t i=0; uint8_t ack; do { ack=send_i2c(at24_data[i]); __delay_cycles(10000); // delay 10ms for writing } while (!ack && ++i<pageSIZE); // if ACK == 0 it't SUCCESS, otherwise FAILURE if (ack) { send_string("transmission was failure. broken on: "); send_number(i); send_uart('\r'); return 0; //exit with FAILURE }; //else // send_string("wrinting was success\r"); // DEBUG message // STOP bit if(stop_i2c()) { send_string("ERROR set STOP bit!"); return 0; // exit with FAILURE; } return 1; } static int eeprom_read_page(int address){ //////////// WRITE ADDRESS ///////////////////// // START bit int attempts,i; at24_ard[1]=(uint8_t)(address>>8); at24_ard[2]=(uint8_t)(address&0x00FF); if (!at24c512_set_ard(1)) // if OK { send_string("AT24C512 address setting was success!\r"); } else { send_string("ERROR address setting was failure!\r" ); return 0; } for(i=0;i<pageSIZE; i++) at24_data[i]=0; ////////// READ DATA /////////////////////////////// // START bit attempts=start_i2c(); if (attempts){ // send_string("OK, attempts: "); send_number(65-attempts); send_uart('\r'); // DEBUG mesage uint8_t ack=send_i2c(eeprom_ADR | 0x01); if (!ack) { // read DATA for(i=0;i<(pageSIZE-1);i++) at24_data[i]=read_i2c(NOLAST); at24_data[pageSIZE-1]=read_i2c(LAST); } else { send_string("ERROR reading data!\r"); return 4; // exit with FAILURE; } // STOP bit if(stop_i2c()) { send_string("ERROR set STOP bit!"); return 5; // exit with FAILURE; } } else { send_string("ERROR of read!\r"); return 6; // exit with FAILURE } return 0; // SUCCESS } int main (void) { WDTCTL = WDTPW | WDTHOLD; P1DIR=LED; count=0; // F_CPU 1MHz BCSCTL1 = CALBC1_1MHZ; DCOCTL = CALDCO_1MHZ; BCSCTL2 &=~(DIVS_0); //SMCLK=DCO BCSCTL3 |= LFXT1S_2; //ACLK= VLO =~ 12KHz ///////// TIMER0_A used by Sowtfare UART/////////////////// uDIR+=TXD; TA0CTL=TASSEL_2 + MC_1 + ID_0 + TACTL; /* TASSEL_2 =use SMCLK; MC_1 =continous to TACCR0; ID_0 =prescaler = 1; TACLR =clean counter TAR; TAIE; =enable timer interrupt; */ __enable_interrupt(); //ds1307_set_data(); int j; for(j=0;j<pageSIZE;j++) at24_data[j]=j; long rnd=78; for(;;) { int i; int a=0; int page=0; do { uint8_t k,r; for(j=0;j<(pageSIZE-1);j++) { rnd=middleSquareNumber(rnd,4); r=(rnd%255); k=(!j) ?r : k^r; at24_data[j]=r; } at24_data[pageSIZE-1]=k; for(i=0;i<10;i++) __delay_cycles(0xAFFF); while(!eeprom_write_page(a)) __delay_cycles(0xAFFF); send_string("page: "); send_number(page);send_string(" address: "); send_number(a); send_string("page: "); send_number(page);send_string(" ard: "); send_number(a); send_string (" xor: "); send_number(k); send_uart('\r'); for(i=0;i<10;i++) __delay_cycles(0xAFFF); eeprom_read_page(a); for(j=0;j<(pageSIZE-1);j++) { r=at24_data[j]; k=(!j) ?r : k^r; } if (k == at24_data[pageSIZE-1]) send_string("checksum is: OK\r"); else send_string("checksum is: BAD\r"); a+=pageSIZE; } while (++page <(65536/pageSIZE)); } return 0; }
Результат работы программы:
Здесь красным подчеркнуты сессии на запись и на чтение одного и того же массива.
В терминал UART'а валится довольно много отладочной информации, которую конечно же можно безболезненно удалить, сократив код.
Данная EEPROM имеет размер страницы для записи - 32 байта. В остальном она идентична AT24C512, кроме размера памяти, конечно. Причем I2C адрес флешки которую сам впаивал осталась та же - 0xA0, а адрес флешки с DS3231 модуля оказался 0xA4. Причем перемычки ответственные за указание банка, были не тронуты.
Вариант программы для реального железа:
#include <msp430g2553.h> #include <sys/types.h> #include <stdio.h> //////// DEFINES /////////////////////////////////////// #define LED BIT0 // UART #define RXD BIT1 #define TXD BIT2 #define uOUT P1OUT #define uDIR P1DIR // I2C #define SCL BIT0 #define SDA BIT1 #define sclOUT P2OUT #define sdaOUT P2OUT #define sclDIR P2DIR #define sdaDIR P2DIR #define sclREN P2REN #define sdaREN P2REN #define sdaIN P2IN #define sclIN P2IN #define QDEL __delay_cycles(3); #define HDEL __delay_cycles(6); #define SDA_I2C_HI sdaDIR&=~SDA #define SCL_I2C_HI sclDIR&=~SCL #define SDA_I2C_LO sdaDIR|=SDA #define SCL_I2C_LO sclDIR|=SCL #define SCL_TOGGLE_I2C HDEL; SCL_I2C_HI;HDEL;SCL_I2C_LO; #define eeprom_ADR 0xA0 #define LAST 1 #define NOLAST 0 #define pageSIZE 8 #define AT_ARD_LEN 3 uint8_t at24_ard[3]={0xA0,0x00,0x00}; uint8_t at24_data[pageSIZE]; /////////// END DEFINES ///////////////////////////////////// ////////////////////////////////////////////////// /////// INTERUPTS //////////////////////////// volatile uint16_t count; #pragma vector=TIMER0_A0_VECTOR __interrupt void Timer0(void) { count=0; } // for delay_ms() #pragma vector=TIMER0_A1_VECTOR __interrupt void Timer0_OVF(void) { switch( TA0IV ) { case TA0IV_TACCR1: break; // CCR1 not used case TA0IV_TACCR2: break; // CCR2 not used case TA0IV_TAIFG: if (count) count--; // overflow break; } } ////////FUNCTIONS ///////////// void wait_ms(uint16_t ms) { count=ms; TA0CCR0=1000; // delay TA0CTL |= TAIE; // enable interrupt while(count); TA0CTL &=~TAIE; // disable interrupt } long a[] = { 1, 10, 100, 1000, 10000, 100000, 1000000}; long middleSquareNumber(long numb, int dig) { long sqn = numb * numb, next_num = 0; long trim = (dig >>1); sqn = sqn / a[trim]; int i; for (i = 0; i < dig; i++) { next_num += (sqn % (a[trim])) * (a[i]); sqn = sqn / 10; } return next_num; } /////////////// I2C ////////////////////////////////// static int start_i2c() { int ret=64; uint8_t valueSDA, valueSCL; // init I2C sdaOUT &=~SDA; sclOUT &=~SCL; do { SDA_I2C_HI; SCL_I2C_HI; QDEL; valueSDA=sdaIN & SDA; valueSCL=sclIN & SCL; } while((!valueSDA || !valueSCL) && --ret>0); if (!ret) return 0; SDA_I2C_LO;QDEL; SCL_I2C_LO;QDEL; return ret; } static int stop_i2c(){ SDA_I2C_LO; SCL_I2C_LO;HDEL; SCL_I2C_HI;QDEL; SDA_I2C_HI;QDEL; if (!(sdaIN & SDA) || !(sclIN & SCL)) return 1; else return 0; } static uint8_t send_i2c(uint8_t value) { int bits=8; while(bits>0) { bits--; SCL_I2C_LO; QDEL; while((sclIN & SCL)); if (value & (1<<bits)) SDA_I2C_HI; else SDA_I2C_LO; SCL_TOGGLE_I2C; } //get ACK QDEL; SDA_I2C_HI; QDEL; SCL_I2C_HI; while(!(sclIN & SCL)); QDEL; uint8_t ack; ack=(sdaIN & SDA); QDEL; SCL_I2C_LO; HDEL; return ack; } uint8_t read_i2c(int last) { uint8_t c, b; int i; b=0; SDA_I2C_HI; for(i=0;i<8;i++) { HDEL; SCL_I2C_HI; c=(sdaIN & SDA); b <<=1; if(c) b|=1; HDEL; SCL_I2C_LO; } if(last) SDA_I2C_HI; else SDA_I2C_LO; SCL_TOGGLE_I2C; SDA_I2C_HI; return b; } ///////////// UART ///////////////////////////////// uint8_t send_uart(uint8_t ch){ count=1; int i; ch=~ch; TA0CCR0=108; //9600 TA0CCTL0=CCIE; // enable interrupt while(count);count=1; // START bit uOUT &= ~TXD; for(i=0;i<8;i++) // DATA transmission { while(count); count=1; uOUT=(ch&(1<<i)) ? uOUT & ~TXD: uOUT | TXD; } // STOP bit while(count);count=1; P1OUT &= ~TXD; while(count); P1OUT |=TXD; TA0CCTL0=0; // disable interrupt return ch; } void send_string(char str[]) { int i; for(i=0; str[i]!='\0';i++) { if (str[i] == '\n') send_uart('\n'); else send_uart(str[i]); } } void send_number(int value) { char str[10]; sprintf(str,"%d",value); send_string(str); } //////////// AT24C32 ////////////////////////////// int at24c512_set_ard(int stp){ //////////// WRITE ADDRESS ///////////////////// // START bit int attempts,i; attempts=start_i2c(); if (attempts){ send_string("OK, attempts: "); send_number(65-attempts); send_uart('\n'); // DEBUG message // send DATA i=0; uint8_t ack; do { ack=send_i2c(at24_ard[i]); } while (!ack && ++i<AT_ARD_LEN); // if ACK == 0 it't SUCCESS, otherwise FAILURE if (ack) { send_string("transmission was failure. broken on: "); send_number(i); send_uart('\n'); return 1; //exit with FAILURE } else send_string("transmission was success\n"); // DEBUG message // STOP bit if(stp && stop_i2c()) { send_string("ERROR set STOP bit!"); return 2;// exit with FAILURE } }else { send_string("ERROR of write!\n"); return 3; // exit with FAILURE } return 0; } int eeprom_write_page(int address) { if (pageSIZE > 32) return 0; at24_ard[1]=(uint8_t)(address>>8); at24_ard[2]=(uint8_t)(address&0x00FF); if (!at24c512_set_ard(0)) // if OK { send_string("AT24C32 address setting was success!\n"); } else { send_string("ERROR address setting was failure!\n" ); return 0; } uint8_t i=0; uint8_t ack; do { ack=send_i2c(at24_data[i]); wait_ms(10); // delay 10ms for writing } while (!ack && ++i<pageSIZE); // if ACK == 0 it't SUCCESS, otherwise FAILURE if (ack) { send_string("transmission was failure. broken on: "); send_number(i); send_uart('\n'); return 0; //exit with FAILURE }; //else // send_string("wrinting was success\n"); // DEBUG message // STOP bit if(stop_i2c()) { send_string("ERROR set STOP bit!"); return 0; // exit with FAILURE; } return 1; } static int eeprom_read_page(int address){ //////////// WRITE ADDRESS ///////////////////// // START bit int attempts,i; at24_ard[1]=(uint8_t)(address>>8); at24_ard[2]=(uint8_t)(address&0x00FF); if (!at24c512_set_ard(1)) // if OK { send_string("AT24C32 address setting was success!\n"); } else { send_string("ERROR address setting was failure!\n" ); return 0; } for(i=0;i<pageSIZE; i++) at24_data[i]=0; ////////// READ DATA /////////////////////////////// // START bit attempts=start_i2c(); if (attempts){ // send_string("OK, attempts: "); send_number(65-attempts); send_uart('\n'); // DEBUG mesage uint8_t ack=send_i2c(eeprom_ADR | 0x01); if (!ack) { // read DATA for(i=0;i<(pageSIZE-1);i++) at24_data[i]=read_i2c(NOLAST); at24_data[pageSIZE-1]=read_i2c(LAST); } else { send_string("ERROR reading data!\n"); return 4; // exit with FAILURE; } // STOP bit if(stop_i2c()) { send_string("ERROR set STOP bit!"); return 5; // exit with FAILURE; } } else { send_string("ERROR of read!\n"); return 6; // exit with FAILURE } return 0; // SUCCESS } int main (void) { // WDTCTL = WDTPW | WDTHOLD; P1DIR = LED; count = 0; ///////// TIMER0_A /////////////////// TA0CTL=TASSEL_2 + MC_1 + ID_0 + TACTL; /* TASSEL_2 =use SMCLK; MC_1 =continous to TACCR0; ID_0 =prescaler = 1; TACLR =clean counter TAR; TAIE; =enable timer interrupt; */ /////////// SOFT UART ////////// uDIR += TXD; __enable_interrupt(); int j; for(j=0;j<pageSIZE;j++) at24_data[j]=j; long rnd=78; for(;;) { int i; int a=0; int page=0; do { uint8_t k,r; for(j=0;j<(pageSIZE-1);j++) { rnd=middleSquareNumber(rnd,4); r=(rnd%255); k=(!j) ?r : k^r; at24_data[j]=r; } at24_data[pageSIZE-1]=k; // for(i=0;i<10;i++); // __delay_cycles(0xAFFF); while(!eeprom_write_page(a)); // __delay_cycles(0xAFFF); send_string("page: "); send_number(page);send_string(" address: "); send_number(a); send_string("page: "); send_number(page);send_string(" ard: "); send_number(a); send_string (" xor: "); send_number(k); send_uart('\n'); //for(i=0;i<10;i++) // __delay_cycles(0xAFFF); eeprom_read_page(a); for(j=0;j<(pageSIZE-1);j++) { r=at24_data[j]; k=(!j) ?r : k^r; } if (k == at24_data[pageSIZE-1]) send_string("checksum is: OK\n"); else send_string("checksum is: BAD\n"); a+=pageSIZE; wait_ms(1000); P1OUT ^= LED; } while (++page <(65536/pageSIZE)); } return 0; }
Результат работы программы:
Вроде все работает.