четверг, 5 июня 2014 г.

Часы на STM32VL Discovery


Плата STM32 Discovery несколько лет назад произвела настоящий «взрыв» интереса среди любителей микроконтроллерной техники. За прошедшее время появились как новые процессоры, так и новые варианты отладочных плат Discovery. Тем не менее, первую плату еще можно использовать в качестве основы разнообразных устройств. Одним из таких могут стать часы.

Реализация часовых алгоритмов на основе микроконтроллеров STM32 не создает особых проблем. Встроенный модуль RTC со всей необходимой обвязкой уже присутствует в составе процессора. Достаточно только его настроить и можно работать. В применяемом контроллере STM32F100 модуль RTC несколько отличается от последующих версий. В частности его счетный регистр не разделен на отдельные части, а выполнен в виде двух 16-разрядных последовательно соединенных счетчиков. Это означает дополнительную потребность в преобразовании времени и даты, а также невозможность жесткой привязки к календарю. Первоначально такое положение дел кажется сложным, но как и многих других элементов STM32 все оказывается не таким уж и страшным.

Схема часов
Принципиальная схема часов не отличается от таковой в других проектах. Индикатор, подключенный для реализации динамического режима, две кнопки настройки и несколько служебных светодиодов составляют практически всю схему. Все остальные элементы уже разведены и установлены на STM32VL Discovery. Все что требуется – запрограммировать алгоритм работы. Условно этот алгоритм можно разбить на несколько частей: динамическая индикация, настройка модуля RTC, опрос часов, настройка времени. Для программирования выбран компилятор MikroC.
Динамическая индикация реализуется стандартно, через прерывание. Особых проблем при ее реализации не возникло. Гораздо больше времени ушло на запуск RTС. Множество регистров, зачастую расположенных в разных блоках, запутывают программиста. Тем не менее, результат получился простой и работоспособный. Определение времени также не вызвало больших затруднений, так как реализуется банальным делением. Например:
  tmp = (RTC_CNTH << 16) + RTC_CNTL; //Получение значения счетчиков
  hour = ((tmp % (365 * 24 * 3600)) % (24 * 3600)) / 3600; //Часы
  minutes = (((tmp % (365 * 24 * 3600)) % (24 * 3600)) % 3600)/ 60; //Минуты
  seconds = (((tmp % (365 * 24 * 3600)) % (24 * 3600)) % 3600)% 60; //Секунды
Если для 8-ми разрядных микроконтроллеров приведенный код тяжел, то в 32-разрядных он не составляет особых проблем, так как деление реализовано аппаратно.
Наибольшее время пришлось потратить на реализацию алгоритма установки времени. Первоначальное решение сделать его через прерывания на кнопках показалось громоздким и плохо работающим, поэтому было решено вернуться к чему-то подобному, что легко прописывалось в 8-ми разрядных системах. Установка времени выполнена простым сложением счетчика RTC и числа 60 для увеличения минут, или 3600 для часов. Такая операция очень проста. Единственный ее недостаток заключается в изменении значения часов, если минуты становятся больше 59.
Схема первоначально была собрана на макетной плате и показала хорошую работоспособность. Сейчас есть желание сделать шилд под STM32VL Discovery, на который установить термометр, датчик давления, реле и звонок.

 Программа часов


#define Dig1_On GPIOB_ODR.B1
#define Dig2_On GPIOB_ODR.B0
#define Dig3_On GPIOB_ODR.B4
#define Dig4_On GPIOB_ODR.B5
#define mig_max 300
unsigned long  tmp;
int Dig_show; //Текущая цифра
int Dig1,Dig2,Dig3,Dig4; //Цифры для индикации
int hour, minutes, seconds;  //Время
int mig,rezim,sec;
int btn0,cnt_btn0,btn1,btn1s,cnt_btn1; //Кнопки

void Read_RTC(){
  tmp = (RTC_CNTH << 16) + RTC_CNTL;
  hour = ((tmp % (365 * 24 * 3600)) % (24 * 3600)) / 3600;
  minutes = (((tmp % (365 * 24 * 3600)) % (24 * 3600)) % 3600)/ 60;
  seconds = (((tmp % (365 * 24 * 3600)) % (24 * 3600)) % 3600)% 60;
}

void Set_DIG12(){
  Dig1=hour/10;
  Dig2=hour%10;
}

void Set_DIG34(){
  Dig3=minutes/10;
  Dig4=minutes%10;
}

//Преобразование цифры в код индикатора
 int Mask(int Dig){
  dig=dig & 0x000f;
  switch(Dig){
     case 0:  {return 0xED00; break;}
     case 1:  {return 0x2100; break;}
     case 2:  {return 0x7C00; break;}
     case 3:  {return 0x7900; break;}
     case 4:  {return 0xB100; break;}
     case 5:  {return 0xD900; break;}
     case 6:  {return 0xDD00; break;}
     case 7:  {return 0x6100; break;}
     case 8:  {return 0xFD00; break;}
     case 9:  {return 0xF900; break;}
     case 10: {return 0x0000; break;}
  }
}

//Прерывание по таймеру2. Переключение цифр на индикаторе
void Timer2_interrupt() iv IVT_INT_TIM2 {
  TIM2_SR.UIF = 0;
  if (RTC_CRL.secf) {sec=~sec; RTC_CRL.secf=0;} //Секундной мигание
  switch(Dig_show){
     case 1: {Dig4_On=0; GPIOB_ODR = mask(Dig1); Dig1_On=1; break;}
     case 2: {Dig1_On=0;  if (sec) GPIOB_ODR = mask(Dig2)+0x0200;
                              else GPIOB_ODR = mask(Dig2);
                                                 Dig2_On=1; break;}
     case 3: {Dig2_On=0; GPIOB_ODR = mask(Dig3); Dig3_On=1; break;}
     case 4: {Dig3_On=0; GPIOB_ODR = mask(Dig4); Dig4_On=1; break;}
  }
  Dig_show++;
  if(Dig_show>4) Dig_show=1;
  mig++;
  if (mig>300)mig=0;
}

//Прерывание по таймеру 3. Выход из режима установки
void Timer3_interrupt() iv IVT_INT_TIM3 {
  TIM3_SR.UIF = 0;
  Rezim=0;
  TIM3_CR1.CEN = 0;
 }

void Set_param(){
  TIM3_CNT=0;
  btn0=1;
  btn1=1;
  btn1s=0;
  PWR_CR.DBP = 1;

  while (rezim>0){
    TIM3_CR1.CEN = 1;
    //Переключение режимов
    cnt_btn0=0;
    if (btn0==0){
      do{cnt_btn0++;}while(GPIOC_IDR.B0&&(cnt_btn0<10000));
      if(cnt_btn0==10000){rezim++; btn0=1; TIM3_CNT=0;}
    }
    cnt_btn0=0;
    if (btn0==1){
      do{cnt_btn0++;}while(!GPIOC_IDR.B0&&(cnt_btn0<10000));
      if(cnt_btn0==10000){btn0=0;}
    }
    if (rezim>2&&!btn0)rezim=0;
   
    //Опрос кнопки установки
    cnt_btn1=0;
    if (btn1==0){
      do{cnt_btn1++;}while(GPIOC_IDR.B1&&(cnt_btn1<10000));
      if(cnt_btn1==10000){btn1=1; btn1s=1; TIM3_CNT=0;}
    }
    cnt_btn1=0;
    if (btn1==1){
      do{cnt_btn1++;}while(!GPIOC_IDR.B1&&(cnt_btn1<10000));
      if(cnt_btn1==10000){btn1=0;}
    }
   
    if (Rezim==1){
      Read_RTC();
      Set_DIG12();
      if (mig>150){
      Set_DIG34();}else {
        Dig3=10;
        Dig4=10;
        if (btn1s){
          RTC_CRL.CNF=1;
          tmp=RTC_CNTL+60;
          RTC_CNTL=tmp;
          btn1s=0;
          RTC_CRL.CNF=0;
        }
      }
    }
   
    if (Rezim==2){
      Read_RTC();
      Set_DIG34();
      if (mig>150){
      Set_DIG12();}else {
        Dig1=10;
        Dig2=10;
        if (btn1s){
          GPIOC_ODR.B11=~GPIOC_ODR.B11;
          RTC_CRL.CNF=1;
          tmp=(RTC_CNTH << 16)+RTC_CNTL+3600;
          RTC_CNTL=tmp&0x0000ffff;
          RTC_CNTH=tmp >> 16;
          RTC_CRL.CNF=0;
          btn1s=0;
          }
      }
    }
  }
}

void Set_RTC(){
int s;
/* Enable the PWR clock */
 RCC_APB1ENR.PWREN = 1;
 RCC_APB1ENR.BKPEN = 1;
/* Allow access to RTC */
 PWR_CR.DBP = 1;
 RCC_BDCR.RTCSEL0 = 1;
 RCC_BDCR.RTCSEL1 = 0;
 RCC_BDCR.RTCEN = 1;
 RCC_BDCR.LSeoN = 1;   //Включение внутреннего часового генератора
 /* Wait till LSE is ready */
 s=1;
 do{ delay_us(10); s++;
   } while((s<50)||(!RCC_BDCR.LSERDY));
 RTC_CRL.CNF=1;
 RTC_PRLH=0;
 RTC_PRLL=0x7FFF;
 RTC_CRL.CNF=0;
 RTC_CRL.RSF=1;
 PWR_CR.DBP = 0;
}

void main() {
  GPIO_Alternate_Function_Enable(&_GPIO_MODULE_SWJ_JTAGDISABLE); //Включение RB4 как GPIO

  GPIO_Config(&GPIOB_BASE,
            _GPIO_PINMASK_4,
            _GPIO_CFG_MODE_OUTPUT |_GPIO_CFG_SPEED_MAX | _GPIO_CFG_OTYPE_PP);
            
  GPIO_Digital_Output(&GPIOB_BASE,
                _GPIO_PINMASK_0|_GPIO_PINMASK_1|_GPIO_PINMASK_4|_GPIO_PINMASK_5|_GPIO_PINMASK_8|_GPIO_PINMASK_9|_GPIO_PINMASK_10
                 |_GPIO_PINMASK_11|_GPIO_PINMASK_12|_GPIO_PINMASK_13|_GPIO_PINMASK_14|_GPIO_PINMASK_15);
                
  GPIO_Digital_Output(&GPIOC_BASE, _GPIO_PINMASK_8|_GPIO_PINMASK_9|_GPIO_PINMASK_10|_GPIO_PINMASK_11|_GPIO_PINMASK_12);
  GPIO_Digital_Input(&GPIOC_BASE, _GPIO_PINMASK_0|_GPIO_PINMASK_1);

  Dig_show=1;
  GPIOB_ODR=0;
  GPIOC_ODR.B8=1;
  EnableInterrupts();
 
  //Настройка таймера переключения цифр
  RCC_APB1ENR.TIM2EN = 1;
  delay_ms(100);
  TIM2_CR1.CEN = 0;             // Disable timer
  TIM2_CNT = 1;
  TIM2_PSC = 0;               // Set timer prescaler.
  TIM2_ARR = 10000;
  TIM2_DIER.UIE = 1;            // Update interrupt enable
  TIM2_CR1.CEN = 1;             // Enable timer
  NVIC_IntEnable(IVT_INT_TIM2); // Enable timer interrupt
 
  //Настройка таймера выхода из режима установки
  RCC_APB1ENR.TIM3EN = 1;
  delay_ms(100);
  TIM3_CR1.CEN = 0;             // Disable timer
  TIM3_CNT = 1;
  TIM3_PSC = 1000;               // Set timer prescaler.
  TIM3_ARR = 50000;
  TIM3_DIER.UIE = 1;            // Update interrupt enable
  NVIC_IntEnable(IVT_INT_TIM3); // Enable timer interrupt
 
  Dig1=3;
  Dig2=1;
  Dig3=7;
  Dig4=0;
  rezim=0;
 
  Set_RTC(); 

  while(1){
    //Опрос кнопки настройки
    cnt_btn0=0;
    do{cnt_btn0++;}while(GPIOC_IDR.B0&&(cnt_btn0<10000));
    if(cnt_btn0==10000){rezim=1; Set_Param();}    //Переход в режим настройки
    Delay_ms(200);
    //Опрос часов
    Read_RTC();
    Set_DIG12();
    Set_DIG34();
  }
}

Комментариев нет:

Отправить комментарий