Proteus8.x + MSP430x2xx: программная реализация I2C интерфейса, подключение устройств: RTC DS1307/DS3231, EEPROM AT24C512/AT24C32

разделы: MSP430 , Proteus , I2C , RTC , дата: 5 апреля 2017г.

Полтора года назад я уже бегло рассматривал протокол I2C, теперь же настало время изучить его более подробно.

Попробуем написать программную реализацию протокола, рассмотрим "подводные камни" такой реализации, а также способы отладки шины I2C. Для обкатки алгоритма попробуем подключить следующие устройства на шине I2C: RTC DS1307/DS3231 и EEPROM AT24C512/AT24C32.

Для проектирования будет использована CAD Proteus_8.5. Для проверки на реальном железе будет использован чип MSP430G2453.

Часы реального времени на микросхеме DS1307

Итак, тренироваться будем на 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.

    Основные моменты:
  1. Есть две линии: SCL служит для тактирования, SDA служит для передачи данных.
  2. Скважность импульсов не играет роли. Один такт может длиться 5мкс, другой 5мс.
  3. Высокий уровень формируется pull-up режимом, низкий - земля.
  4. Данные передаются сессиями.
  5. Каждая отдельная сессия работает только на прием или только на передачу.
  6. В начале и в конце сессии соответственно формируются сигналы START и STOP. Между этими сигналами линия считается занятой.
  7. Только при START и STOP, SDA линия меняет уровень при высоком уровне SCL.
  8. START формируется последовательностью: a) падением SDA при высоком уровне SCL, б) падением SCL.
  9. STOP формируется последовательностью: a) поднятием SCL при низком SDA, б) поднятием SDA при высоком SCL.
  10. При передаче данных уровень SDA меняется только при низком SCL.
  11. При чтении данных, SDA уровень считывается только при высоком SCL.
  12. После формирования START, передается адрес устройства, с которым хотят связаться. Младший бит адреса указывает режим сессии.
  13. Младший бит равный нулю в адресе устройства означает, что сессия будет направлена на запись, т.е. передачу в адресуемое устройство.
  14. Младший бит равный единице в адресе устройства означает, что сессия будет направлена на чтение, т.е. будет происходить чтение с адресуемого устройства.
  15. Байты передаются побитно, начиная от старшего разряда к младшему.
  16. После передачи каждого байта, принимающая сторона должна формировать ответ, означающий, что передача прошла успешно: ACK.
  17. При чтении с устройства, не сформировав ответ, мы дадим понять устройству, что прием закончен, и мы получили все, что нужно. Это называется NACK.

Итак, работа с I2C идет сессиями. В руководстве представлены варианты допустимых сессий для записи и чтения:

Есть три варианта сессий: для записи, для чтения, и для чтения с определенного адреса.

Карта регистров DS1307:

Т.е. имеем 7 часовых регистра, 1 регистр конфигурации SQW и 56 байт ОЗУ где можно хранить свои данные.

Опрос I2C устройства

Самая простая успешная сессия для 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:

Отладка I2C шины

Когда я писал программу, для меня более всего непонятно было, как следует обратывать ответ ACK. В смысле, на какой стадии чип прижимает линию, стоит ли прижимать ее самому для формирования stop-бита, и нужно ли пропускать такт чтобы дать чипу ответить, т.е. послать ACK.

В Proteus мы можем запустить отладчик, и в пошаговом выполнении наблюдать как все происходит:

Здесь на 10-м выводе назначена SDA. Когда передается единица, линия принимает высокий уровень(красный квадратик). Когда же передается ноль, то линия прижимается к земле(синий квадратик).

На видео получение ACK происходит на 2:38, когда линия "отпускается", но на выводе продолжает оставаться низкий уровень, т.е. чип DS1307 удерживает шину.

Программа для работы с RTC 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 формате.

Собственно, программа сначала устанавливает значения первых семи "часовых" регистров, а затем в цикле через равные интервалы считывает их актуальные значения. Результат работы программы:

Программа для работы с RTC DS3231

Теперь вариант с DS3231 для "живого" MSP430G2453.

DS3231 совместим с DS1307, но при этом имеет несколько ключевых отличий:

  1. Чип имеет встроенный резонатор;
  2. Как питание, так и батарея могут иметь напряжение от 2.3V до 5.5V;
  3. Может работать на частоте 400kHz (fast I2C).

Если посмотреть на схему включения RTC, то увидим несколько новых фич:

  1. исчез кварц;
  2. появилась возможность сбросить чип через RESET
  3. наряду со знакомым уже SQW, появился высокочастотный вывод 32kHz

В 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.

Результат работы:

Работа в Proteus c EEPROM AT24C512

Самая скромная флешка с 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 AT24C32

Данная 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;
 }

Результат работы программы:

Вроде все работает.

поделиться: