Часы на STM8L-Discovery и OSA

Данная статья — результат моих попыток поработать с кооперативной RTOS OSA на STM8L.
Так как просто мигать светодиодом под ОСью уж совсем примитив, неоднократно описанный в интернетах, для себя я решил начинать знакомство с OSA на проекте, более-менее приближенном к реальности. Первое, что пришло в голову, так сказать, в лучших традициях DIY — запилить часы. Тем более все необходимое на STM8L-Discovery есть — RTC с часовым кварцем, LCD и даже кнопка! Статья будет полезна тем, кто решит опробовать свои силы в освоении OSA или просто хочет переделать Discovery на часы :)
Подробности под катом...


Disclaimer

Я человек ленивый с небольшим количеством свободного времени, поэтому в проекте использую SPL, куски кода, библиотеки и наработки других разработчиков. Ссылки на использованные ресурсы — в последней главе.
Повторюсь, что это мой первый проект на ОСРВ под МК (и первая статья на we), так что скорее всего возможна высокая концентрация не самых оптимальных решений и быдлокода.

Подготовка проекта

Давно хотел прикрутить ОС к контроллеру, вот дошли руки. Почитал литературу, посмотрел примеры, и выбор пал на платформу STM8L и OSA. Выбор операционной системы обоснован наличием хорошей русскоязычной документации, что немаловажно при первом знакомстве с системами данного плана. Ссылка на сайт OSA [1]. Информация крайне желательна к прочтению для понимания работы кооперативной ОСРВ.

Итак, начнем с создания проекта в IAR для STM8. Сразу оговорюсь, подробного описания, на какие кнопочки нажимать с красными кружечками на скриншотах здесь не будет, на ресурсе эти вопросы хорошо освещены в других статьях. Опишу только ключевые моменты.

В IARе создаем новый проект по методу, описанному в [2]. Теперь нужно допилить к нему OSA. Скачиваем текущую версию здесь, распаковываем и указываем в настройках проекта (Project->Options->C/C++ Compiler->вкладка Preprocessor->поле Additional include directories) путь к папке osa дистрибутива. Заодно, в категории Debugger, в поле Driver нужно выбирать отладчик ST-LINK.

Затем нужно добавить к проекту несколько файлов. Не знаю как вы, я копирую их в папки проекта — мне так удобнее. В папку src/ проекта копируем файлы osa.c и sa_stm_iar.s (последний лежит в папке osa/port/stm8/). В папку inc/ копируем файл osa.h.
В часах нам понадобится ЖКИ. Воспользуемся готовой библиотекой из статьи [4]. Аналогично копируем h-файл в inc/, cpp-файл в src/.
Теперь нужно добавить все вышеперечисленные файлы в наш проект. Для этого в окне IDE выбираем пункт меню Project->Add Files и по очереди добавляем все файлы.

На этом этап подготовки проекта завершен. Если все сделали правильно, при сборке проекта (F7) не должно возникнуть никаких ошибок.

Настройка OSA

Настройка ОСРВ OSA проводится с помощью файла OSAcfg.h. Его созданием мы и займемся. Для более сложных проектов удобно использовать специальную утилиту OSAcfg_Tool, но это не тот случай. Так что создаем пустой файл (File->New->File) и сохраняем его под именем OSAcfg.h в папке inc/ (File->Save As->inc/OSAcfg.h->Ok). Далее копируем в него код:

#ifndef _OSACFG_H
#define _OSACFG_H

// SYSTEM
#define OS_TASKS 4  // Максимальное количество активных задач
#define OS_PRIORITY_LEVEL OS_PRIORITY_DISABLE  // Выключаем приоритеты задач

// ENABLE CONSTANTS
#define OS_ENABLE_TTIMERS  // Включаем службу таймеров (для работы временных задержек)
#define OS_USE_INLINE_TIMER  // Встраиваем функцию обработки таймеров в обработчик прерываний

// BSEMS
#define OS_BSEMS 5 // Количество бинарных семафоров

#endif

Думаю, по комментариям понятно, что у нас будет кооперативная ОСРВ с четырьмя задачами без приоритетов (проект простой, они здесь не нужны; подробнее о задачах и приоритеты ниже), службой временной задержки и пятью бинарными семафорами. Такого скромного набора достаточно для того, чтобы превратить STM8L-Discovery в простенькие часы. Более подробная информация о конфигурации OSAcfg.h на сайте проекта.

Следующим этапом будет настройка системного таймера. Для работы функций временных задержек в ОС, ей необходимо выделить один аппаратный таймер из арсенала МК, — обычно берут самый простой. В конкретном случае — Timer4. Я выбрал значение системного тика — 1 мс. Этого вполне достаточно для работы часов и удобно при задании задержек. Настройка таймера проводится с помощью макросов SPL, поэтому все достаточно просто:

//  МК тактируется от внутреннего генератора 16МГц без предделителя
CLK_SYSCLKDivConfig(CLK_SYSCLKDiv_1);
//  Включаем тактирование таймера 4
CLK_PeripheralClockConfig(CLK_Peripheral_TIM4, ENABLE);
//  Расчет входа в прерывание от таймера 4 с частотой 1кГц
//  Предделитель тактирования таймера = 128
//  ARR=16000000 / 128 = 125000
//  Значение счетчика 125
//  125000 / 125 = 1000 Hz
TIM4_TimeBaseInit(TIM4_Prescaler_128, 124);
//  Включаем прерывание по переполнению счетчика
TIM4_ITConfig(TIM4_IT_Update, ENABLE);
//  Запускаем TIM4
TIM4_Cmd(ENABLE);

В обработчике прерывания прописываем системную функцию обновления таймеров OS_Init() — она будет следить за выполнением временных задержек:

INTERRUPT_HANDLER(TIM4_handler,TIM4_UPD_OVF_TRG_IRQn)
{
  if (TIM4_GetFlagStatus(TIM4_FLAG_Update))
  {
    TIM4_ClearFlag(TIM4_FLAG_Update);
    OS_Timer();
  }
}

Кодим часы

Теперь можем приступить к написанию кода часов. Так как целью проекта является обучение, а не создание супер-крутых часов, ограничимся базовой функциональностью: вывод времени на ЖКИ, вывод даты при нажатии на кнопку, возможность изменения этих самых времени и даты и… все. Никаких будильников, органайзеров и прочих плюшек :) И надо было бы сделать возможность автоматической корректировки времени по заданному параметру, так как кварц у нас не прецизионный. Например, раз в сутки.

Сначала о трудностях. У нас есть только одна кнопка для связи с пользователем. Это, конечно, не глобальная проблема, но добавит много swith-case-ов в код. Понятно, что простого нажатия на кнопку не достаточно для реализации описанного функционала. Значит, будем писать обработчик нажатий на кнопку, который будет понимать два типа нажатий — короткое и длительное (более 0,8с). Вот этого уже будет достаточно.

В базовом режиме часы выводят время на ЖКИ, при коротком нажатии на кнопку — переходят в режим отображения даты. При повторном коротком нажатии — возвращаются обратно в базовый режим. Для изменения времени нужно нажать и удерживать кнопку в базовом режиме. Для изменения даты — в режиме отображения даты. Переход между полями час/минута/секунда для времени и день/месяц/год для даты, производится длительным нажатием в режиме настройки. Изменение текущего значения — коротким. Отрицательная сторона в том, что изменять значение можно будет только в сторону увеличения, а выйти из режима настройки можно только последовательно перебрав все три поля. За все приходиться платить… Вот такое вот выходит нехитрое управление.

Для хранения текущего состояния системы используется глобальная переменная state и набор бинарных семафоров. Для удобства, возможные состояния переменной state и имена семафоров оформлены в виде перечислений:

enum STATE_ENUM
{
  ST_SHOW_TIME,
  ST_SHOW_DATE,
  ST_SET_HOURS,
  ST_SET_MINUTES,
  ST_SET_SECONDS,
  ST_SET_DAYS,
  ST_SET_MONTHS,
  ST_SET_YEARS,
  ST_SET_SIGN,
  ST_SET_VALUE
} state;

enum OSA_BINSEMS_ENUM
{
  BS_POINT,
  BS_BUTTON_PUSH,
  BS_BUTTON_LONGPUSH,
  BS_SET_TIME,
  BS_SET_DATE,
};

По именам должно быть понятно назначение большинства состояний. Не очевидные значения ST_SET_SIGN, ST_SET_VALUE относятся к режиму ввода суточной корректировки. ST_SET_SIGN знак компенсатора ошибки, ST_SET_VALUE — его значения, в секундах. Семафор BS_POINT изменяет свое значение два раза в секунду. К нему будет привязано мигание курсора и разделительных ":" в часах.

Рассмотрение кода начнем из задачи-обработчика нажатий на кнопку:

void button_hdl (void)
{
  static unsigned char _i=0;
  static bool _isLong = 0;
  
  for (;;)
        {
                OS_Wait(GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_1) == RESET);
                
                _isLong = 1;
                for (_i=0; _i<8; ++_i)
                {
                  if (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_1))
                  {
                    _isLong = 0;
                    break;
                  }
                  else
                    OS_Delay(100);
                }
                
                if (_isLong)
                  OS_Bsem_Set(BS_BUTTON_LONGPUSH);
                else
                  OS_Bsem_Set(BS_BUTTON_PUSH);

                OS_Wait(GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_1) != RESET);
        } 
}

По обыкновению для функций-задач кооперативных ОС, в основе лежит бесконечный цикл, внутри которого вставлены системные функции, передающие управление планировщику ОС.
Задача ожидает нажатия на пользовательскую кнопку (системная функция OS_Wait()) и следит за её длительностью, проверяя состояние порта с паузой в 100мС. Если нажатие длится больше 0,8с — выставляется флаг (бинарный семафор) BS_BUTTON_LONGPUSH, если меньше — BS_BUTTON_PUSH. Для реализации временной задержки используется системная функция OS_Delay(), возвращающая управление планировщику на время, указанное в виде передаваемого параметра. После выставления соответственного семафора задача ожидает, пока кнопка будет отпущена. Данная проверка исключает ложные срабатывания.

Теперь можно взглянуть в код main():

#include "stm8l15x.h"
#include "osa.h"
#include "LCD.h"
#include "string.h"

void main(void)
{
  //  Инициализация микроконтроллера
  Init();
  
  //  Инициализация ОС
  OS_Init();

  //  Создаем системные задачи
  OS_Task_Create(0, refresh);
  OS_Task_Create(0, button_hdl);
  OS_Task_Create(0, push_hdl);
  OS_Task_Create(0, longpush_hdl);
   
  //  Включаем прерывания
  OS_EI();

  //  Передаем управление планировщику операционной системы
  OS_Run();  
}

Код инициализации микроконтроллера здесь приводить не буду, что бы ещё больше не раздувать статью. Там, как и выше, используются макросы SPL. Настраиваются входы-выходы, таймер 4, часы реального времени, ЖКИ. Кому интересно — загляните в исходники проекта. Комментарии в наличии. Следует только напомнить, что при использовании SPL нужно внести изменения в файл inc\stm8l15x_conf.h, убрав комментирование со строчек:

#include "stm8l15x_clk.h"
#include "stm8l15x_flash.h"
#include "stm8l15x_gpio.h"
#include "stm8l15x_itc.h"
#include "stm8l15x_rtc.h"
#include "stm8l15x_tim4.h"


После инициализации микроконтроллера, нужно инициализировать ОСРВ, вызвав системную функцию OS_Init(). В теле данной функции очищаются дескрипторы, обнуляются таймеры, семафоры и т.п. После инициализации можно создавать задачи. Для этого служит системная функция OS_Task_Create(), в которую нужно передать два параметра: приоритет задачи (если они отключены — 0) и указатель на функцию задачи. Сейчас самое время разобраться в задачах и приоритетах. Весь смысл кооперативной ОСРВ — это некая программная оболочка, надстроенная над пользовательскими функциями, которая поочередно проверяет готовность задач к выполнению и, в случае их готовности, передает им управление (ресурсы и процессорное время), учитывая при этом их приоритеты. Если приоритеты отсутствуют, как в нашем проекте, ОС просто проверяет готовность задачи к выполнению в порядке их создания и, опять-таки, в случае готовности передает ей управление.

Если же система более сложная, задач больше и каждая имеет свой приоритет, ОС проводит мониторинг готовности и передает управление задаче с самым высоким приоритетом среди готовых. После выполнения, задача должна вернуть управление планировщику, который снова проверит готовность и отдаст управление самой приоритетной функции. И так непрерывно… В этом и состоит вся прелесть ОСРВ. Но если присмотреться, то не такие они уже и реального времени. Точнее сказать — не жесткого реального времени. Если в задаче стоит временная задержка или таймер, то это не гарантирует, что функция получит управление строго по истечению этого времени. В случае, если в это же время будет/будут готовы задачи с более высокими приоритетами, управление сначала будет передано им. И лишь по их выполнению планировщик даст отработать другим функциям. Это нужно учитывать при разработке систем, где важна синхронизация и тайминги. Если вам нужно гарантированное предсказуемое время реакции системы — смотрите в сторону так называемых ОС жесткого реально времени, микроядерных операционных систем

В данном проекте создаются четыре задачи:
OS_Task_Create(0, refresh); — основная задача, где происходит обновление времени, даты и экрана.
OS_Task_Create(0, button_hdl); — задача-обработчик нажатий на кнопку, код приведен выше.
OS_Task_Create(0, push_hdl); — задача-обработчик короткого нажатия на кнопку.
OS_Task_Create(0, longpush_hdl); — задача-обработчик длительного нажатия на кнопку.

Дальше будет много кода… Рассмотрим реализацию функции longpush_hdl():

void longpush_hdl (void)
{
  for (;;)
        {
                OS_Bsem_Wait(BS_BUTTON_LONGPUSH);
                
                switch (state)
                {
                case ST_SHOW_TIME:
                  state = ST_SET_HOURS;
                  OS_Bsem_Set(BS_SET_TIME);
                  break;
                case ST_SHOW_DATE:
                  state = ST_SET_DAYS;
                  OS_Bsem_Set(BS_SET_DATE);
                  break;
                case ST_SET_HOURS:
                  state = ST_SET_MINUTES;
                  break;
                case ST_SET_MINUTES:
                  state = ST_SET_SECONDS;
                  break;
                case ST_SET_SECONDS:
                  RTC_SetTime(RTC_Format_BIN, &RTC_TimeStr);
                  RTC_SetDate(RTC_Format_BIN, &RTC_DateStr);
                  OS_Bsem_Reset(BS_SET_TIME);
                  state = ST_SHOW_TIME;
                  break;
                case ST_SET_DAYS:
                  state = ST_SET_MONTHS;
                  break;
                case ST_SET_MONTHS:
                  state = ST_SET_YEARS;
                  break;
                case ST_SET_YEARS:
                  RTC_SetDate(RTC_Format_BIN, &RTC_DateStr);
                  OS_Bsem_Reset(BS_SET_DATE);
                  state = ST_SHOW_TIME;
                  break;
                case ST_SET_SIGN:
                  state = ST_SET_VALUE;
                  break;
                case ST_SET_VALUE:
                  state = ST_SHOW_TIME;
                  FLASH_Unlock(FLASH_MemType_Data);
                  correction_value=corr_val;
                  correction_sign=corr_sign;
                  FLASH_Lock(FLASH_MemType_Data);
                  break;
                }
                
                OS_Bsem_Reset(BS_BUTTON_LONGPUSH);
        }  
}

Снова вечный цикл, внутри — ожидание выставления семафора BS_BUTTON_LONGPUSH системной функцией OS_Bsem_Wait(). А далее длииинный swith(), который реализует вход в режим настройки времени и даты, и переходы между текущими полями. При обработке длительного нажатия полей «секунда» и «год» выполняется запись измененных параметров в регистры RTC и переход в базовый режим ST_SHOW_TIME. В принципе, данную функцию можно было бы упростить, использовав ++state и проверки, но такая реализация показалась мне не столь прозрачной, как приведенная выше. После обработки, семафор BS_BUTTON_LONGPUSH сбрасывается.

Далее реализация функции push_hdl() — обработчика коротких нажатий:

void push_hdl (void)
{
  for (;;)
        {
                OS_Bsem_Wait(BS_BUTTON_PUSH);
                
                switch(state)
                {
                case ST_SHOW_TIME:
                  state = ST_SHOW_DATE;
                  break;
                case ST_SHOW_DATE:
                  state = ST_SHOW_TIME;
                  break;
                case ST_SET_HOURS:
                  if (++RTC_TimeStr.RTC_Hours > 23) RTC_TimeStr.RTC_Hours=0;
                  break;
                case ST_SET_MINUTES:
                  if (++RTC_TimeStr.RTC_Minutes > 59) RTC_TimeStr.RTC_Minutes=0;
                  break;
                case ST_SET_SECONDS:
                  if (++RTC_TimeStr.RTC_Seconds > 59) RTC_TimeStr.RTC_Seconds=0;
                  break;
                case ST_SET_DAYS:
                  if (++RTC_DateStr.RTC_Date>31) RTC_DateStr.RTC_Date=1;
                  break;
                case ST_SET_MONTHS:
                  if (++RTC_DateStr.RTC_Month>12) RTC_DateStr.RTC_Month=RTC_Month_January;
                  break;
                case ST_SET_YEARS:
                  if (++RTC_DateStr.RTC_Year>29) RTC_DateStr.RTC_Year=13;
                  break;
                case ST_SET_SIGN:
                  corr_sign=!corr_sign;
                  break;
                case ST_SET_VALUE:
                  if (++corr_val>29) corr_val=0;
                  break;
                }
                               
                OS_Bsem_Reset(BS_BUTTON_PUSH);                                
        }  
}

Все аналогично обработчику длительных нажатий: ожидаем выставления семафора BS_BUTTON_PUSH, только вместо переходов, производится инкремент значений активного поля. Значения времени и даты хранятся в двух структурах, объявленных глобальными переменными. Как видно, все значения проверяются на вхождение в допустимый диапазон. В SPL для этих целей есть специальные макросы, но они чересчур громоздкие.
Календарь до 2029 года. И да, контроль количества дней в месяце при вводе — на совести пользователя :)

Нажатия мы получили, обработали — теперь можно заняться основной задачей.

void refresh (void)
{
  static bool _c;
  
  for (;;)
        {
                OS_Delay(500);     
                
                switch(state)
                {
                case ST_SHOW_TIME:
                  OS_Wait(RTC_WaitForSynchro() == SUCCESS);
                  RTC_GetTime(RTC_Format_BIN, &RTC_TimeStr);
                  Show_Time(&RTC_TimeStr);
                  break;
                case ST_SHOW_DATE:
                  OS_Wait(RTC_WaitForSynchro() == SUCCESS);
                  RTC_GetDate(RTC_Format_BIN, &RTC_DateStr);
                  Show_Date(&RTC_DateStr);
                  break;
                case ST_SET_HOURS:
                case ST_SET_MINUTES:
                case ST_SET_SECONDS:
                  Show_Time(&RTC_TimeStr);
                  break;                
                case ST_SET_DAYS:
                case ST_SET_MONTHS:
                case ST_SET_YEARS:  
                  Show_Date(&RTC_DateStr);
                  break;                
                case ST_SET_SIGN:
                case ST_SET_VALUE:
                  Correction();
                  break;
                }
                
                OS_Bsem_Switch(BS_POINT);                
        }  
}

Основная задача запускается два раза в секунду, используя системную функцию временной задержки OS_Delay(). При переходе в активное состояние, в зависимости от текущего значения переменной state, вызывает соответствующую функцию вывода информации на ЖКИ. Если активный базовый режим, после синхронизации с RTC, выполняется обновление структуры RTC_TimeStr, в которой хранится значение текущего времени. Аналогично для даты и структуры RTC_DateStr. Также в основной задаче производится изменение значения семафора BS_POINT на противоположное.

Теперь перейдем к реализации функции, которая непосредственно занимается формированием изображения, выводимого на экран. Функция Show_Time() в студию! :)

void Show_Time(const RTC_TimeTypeDef* _Time)
{
  char buffer [4];
  char lcd_buffer[9]="";
     
  if ((state==ST_SET_HOURS) && OS_Bsem_Check(BS_POINT))
    strcpy(lcd_buffer, "  :");
  else
  {
    if (_Time->RTC_Hours<10) strcpy(lcd_buffer, "0");
    strcat(lcd_buffer, utoa(_Time->RTC_Hours, buffer));
    if (OS_Bsem_Check(BS_POINT)||OS_Bsem_Check(BS_SET_TIME)) strcat(lcd_buffer, ":");
  }
  
  if ((state==ST_SET_MINUTES) && OS_Bsem_Check(BS_POINT))
    strcat(lcd_buffer, "  :");
  else
  {
    if (_Time->RTC_Minutes<10)  strcat(lcd_buffer, "0");
    strcat(lcd_buffer, utoa(_Time->RTC_Minutes, buffer));
     if (OS_Bsem_Check(BS_POINT)||OS_Bsem_Check(BS_SET_TIME)) strcat(lcd_buffer, ":");
  }
  
  if ((state==ST_SET_SECONDS) && OS_Bsem_Check(BS_POINT))
    strcat(lcd_buffer, "  ");
  else
  {
    if (_Time->RTC_Seconds<10)  strcat(lcd_buffer, "0");
    strcat(lcd_buffer, utoa(_Time->RTC_Seconds, buffer));
  }
  
  LCD_Write_String((unsigned char*)lcd_buffer);
}

Функция получает указатель на структуру, хранящую в себе текущее значение времени и преобразует его в строку, использую вспомогательную функцию utoa(), затем передает ее драйверу ЖКИ на отображение. Основа для реализации utoa() взята в [5]. Мигание курсора реализовано с помощью замены чисел соответствующего поля на пробелы в моменты, когда выставлен семафор BS_POINT.
Стоит заметить, что этот вариант функции — четвертый. До этого были разные реализации с помощью sprintf(), memcpy(), strncpy(), но окончательный вариант мне показался самым простым, прозрачным и наименее ресурсоёмким.
Функция, отображающая дату, написана аналогично и приводить её здесь не вижу смысла. Желающих — милости прошу в исходники.

Не поверите, реализация основного функционала закончена! Все это дело теперь можно скомпилить и получить вполне себе часы на основе STM8L-Discovery. Но это ещё не конец! Остался один мелкий штрих. Так как плату Discovery делали не в Швейцарии, отличной точностью RTC не блещут. Для примера, моя плата дает -6с/сутки погрешности. Да, это неплохой результат даже для дорогих механических часов, но у нас то кварцевые! Конечно, сервера синхронизировать по этим часам не будем, но все же, хотелось бы получить приемлемую точность. Поэтому будем допиливать суточную коррекцию. Надо сказать, что компания STMicroelectronics дает большое число параметров для аппаратной калибровки RTC, но вникать в их суть мне было лень. и я решил, что проще выдумать свой велосипед.
В функциях-обработчиках нажатий уже есть пункты для работы с изменением корректирующего значения. Осталось дописать вспомогательную функцию вывода на экран, условие входа в режим корректировки и автоматический инициализатор, который буде производить корректировку раз в сутки. Начнем с того, что объявим две переменные — знак корректора и его значение в энергонезависимой памяти контроллера:

#pragma location=0x1000
__no_init unsigned char correction_value;
#pragma location=0x1002
__no_init unsigned char correction_sign;

bool corr_sign;
unsigned char corr_val;

Для того, чтобы указать компилятору, что хранить эти переменные нужно в EEPROM, используем препроцессорную директиву #pragma location, после которой указываем конкретный адрес. В STM8L152C6 адресация EEPROM начинается из 0х1000, его и укажем. Спецификатор __no_init запрещает обнулять переменную при инициализации. Также объявим копии этих переменных в глобальной области. Они служат для того, чтобы не лазить каждый раз в EEPROM при текущем изменении значений корректора.

Переход в режим настройки корректора будет осуществляться в случае включения устройства с нажатой пользовательской кнопкой. После создания задач в main(), дописываем код:

if (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_1) == RESET) 
{
  state=ST_SET_SIGN;
  corr_sign=correction_sign;
  corr_val=correction_value;
}

Вывод на экран похож на функции Show_Time и Show_Date:

void Correction (void)
{
  char buffer [8];
  char lcd_buffer[8]="COR:";
  
  if ((state==ST_SET_SIGN) && OS_Bsem_Check(BS_POINT))
    strcat(lcd_buffer, " "); 
  else if (corr_sign) 
    strcat(lcd_buffer, "+"); 
  else
    strcat(lcd_buffer, "-");
  
  if ((state==ST_SET_VALUE) && OS_Bsem_Check(BS_POINT))
    strcat(lcd_buffer, "  "); 
  else
    strcat(lcd_buffer, utoa(corr_val, buffer)); 
  
  LCD_Write_String((unsigned char*)lcd_buffer);
};

Ну и сама коррекция. В основной задаче проверяем время, если оно равно 0:0:30 и коррекция в эти сутки ещё не проводилась, выполняем код:

if (correction_sign)
  RTC_TimeStr.RTC_Seconds+=correction_value;
else
  RTC_TimeStr.RTC_Seconds-=correction_value;

RTC_SetTime(RTC_Format_BIN, &RTC_TimeStr);

Стоит обратить внимание на запись переменных в энергонезависимую память. По умолчанию такое действие заблокировано системой, для исключения ошибок. Чтобы сохранить данные в EEPROM нужно разблокировать область памяти макросом FLASH_Unlock(FLASH_MemType_Data). После записи нужно все вернуть, как было.

FLASH_Unlock(FLASH_MemType_Data);
correction_value=corr_val;
correction_sign=corr_sign;
FLASH_Lock(FLASH_MemType_Data);

Заключение

Вот и простыне конец, кто читал — тот молодец! :)
Вот такой вот немаленький получился обучающий проект. Понятное дело, что в этих часах не используется и третьей части всех тех прелестей, которые дает кооперативная операционная система. Но надо ж с чего-то начинать!
Надеюсь, что для кого-то эта статья будет полезна. Ну, и по традиции: статья первая, сильно не пинать; конструктивная критика приветствуется!
Кого интересуют подробности — полный код в исходниках.

Добавлю ещё пару картинок. Захотелось мне сегодня посмотреть на время выполнения функций. Вот, что получилось (желтый график — задача refresh(), синий — функция Show_Time(), зеленый — функция LCD_Write_String(), фиолетовый — функция utoa()):


Крупнее, вызовы utoa(), и сам препарируемый ->:


И, конечно же, видео работы! Извините, качество г, снимал тем, что было под рукой.



UPD: Вопросы снижения энергопотребления и использования режима Active halt совместно с ОСРВ рассмотрены в продолжении статьи.


Ссылки и благодарности

Авторам всех ниже перечисленных статей и ресурсов выражаю свою личную благодарность за проделанный труд.
[1] Документация по RTOS OSA
[2] Использование SPL
[3] Знакомство с OSA на STM8
[4] Работа с ЖКИ
[5] Преобразование целых чисел в строку
  • +5
  • 24 января 2013, 15:39
  • ARMag
  • 1
Файлы в топике: OSA clock.zip

Комментарии (29)

RSS свернуть / развернуть
Сколько часики в итоге электричества жрут?
0
  • avatar
  • Vga
  • 24 января 2013, 16:44
Сколько потребляют сами часы — не измерял. С горящим светодиодом по питанию, двумя поочередно мигающими и программатором, с USB потребляют около 30мА. Чтобы адекватно измерить потребление, нужно заюзать ту обвязку, что на плате. Будет время на выходных — займусь.
0
UM0970 figure16 на стр.26
снимаем джампер JP1 (вот этот, с тремя пинами прям под ЖКИ) и становимся щупами на пины 1-2.
0
По этому методу методу более адекватные цифры: со светодиодами — колеблется от 3.92мА (горит зеленый) до 6.45мА (горит синий). С отключенными светодиодами 1.02мА.
0
перемерял более адекватным прибором — 3.008мА — 3.075мА, в зависимости от количества активных сегментов на ЖКИ.
0
Довольно много. Не думал над прикручиванием к этому еще и энергосбережения?
0
ну, без слипа нормальные цифры.
а учитывая
Повторюсь, что это мой первый проект на ОСРВ под МК
0
Угу. Но почему бы не двигаться дальше, тем более что выбранное семейство МК прямо намекает дальнейший путь?
0
Кстати, секунды по кнопке лучше не инкрементировать, а сбрасывать на ноль.
+1
  • avatar
  • Vga
  • 24 января 2013, 17:14
я сделал так в начале, но потом переделал. Не понравилось то, что если прозевал — нужно целую минуту ждать до следующей попытки. Для желающих сбрасывать в нуль, дописал в case-ах #ifdef по параметру ZEROSEC. Проект сейчас перезалью.
+1
Т.е. твой вариант предполагает выставить секунды на секунд пять вперед и потом для синхронизации нажать «применить» в момент, когда реальное время сравняется с выставленным? В принципе тоже неплохой вариант, но в данном случае ему несколько мешает то, что для «применить» требуется длительное нажатие.
0
Да, все правильно. И по поводу применения длительным нажатием — есть такое неудобство. Но длительное нажатие всего 0,8 сек. Поэтому, при определенной сноровке, можно достичь высокой точности синхронизации :)
+1
а выйти из режима настройки можно только последовательно перебрав все три поля.
Можно выходить по таймауту — секунд через пять после последнего нажатия на кнопку. Ну и правильно Vga говорит, секунды по кнопке сбрасывать на ноль.
Можно ещё плюшек прикрутить, даже с одной кнопкой, типа ввода времени азбукой Морзе (при определенной практике это будет очень быстро), но ведь не будет же отладочная плата всегда просто время показывать?
0
Посмотрел видео — когда пользователь жмякает кнопку, лучше не моргать цифрами, а то так и нужную проскочить недолго. Палец, правда, будет накаченный!
0
Если не моргать цифрами — не понятно, изменение какого поля производится. А не мигать пока нажато — не актуально, так как нету залипающего режима, один раз нажал — на единицу увеличилось значение. А вообще, фич для допиливания много, но вы же сами сказали — не будет же отладочная плата просто время показывать, потому тратить время на них пока не собираюсь. А вот по поводу энергосбережения можно подумать.
+1
Ну, с другой стороны, это вопрос не столько «часики из демоборда», сколько обкатка UI. Тоже, тащемта, неплохая область для экспериментов — сделать простой, удобный и функциональный интерфейс далеко не так просто.
0
Какой размер полученного кода? В 8 Кб уложились?
При длительном нажатии на кнопку можно включать один из BAR-ов на LCD индикаторе, для того что бы информировать пользователя, что можно уже и отпустить кнопку. Так же с помощью этих индикаторов можно выводить в каком режиме сейчас находимся.
Энергопотребление можно снизить за счет снижения основной частоты процессора (в моем варианте хватало 2 МГц), правда нужно будет следить за тем, что бы все задачи успевали выполняться за 1 системный такт. У Вас есть осциллограф, так что вы сможете более точно подобрать минимальную частоту.
Так же использование в качестве системного тика 1 мсек, не рационально, вы используете только 2 задержки по 100 и 500 мсек. Так что возможно использовать системный тик в 10 мсек.
В качестве прерывания для системного тика можно использовать WakeUp от RTC.
PS. А я так и не дописал часики для данной платы =(
0
Нет, в 8 Кб не уложился. При том далеко :) 16 670 байт. Во-первых ничего для уменьшения прошивки я не делал, т.к. 32 Кб доступно. И да, у меня нету нравственных ограничений в 8 Кб ;) проект не коммерческий, моя совесть чиста.
Для уменьшения прошивки стоит обратить внимание на библиотеку ЖКИ. Она большая, по причине наличия латинских и кириллических символов, которые не используются. Если выбросить все ненужное с массивов, оставить только цифры, COR и служебные символы, прошивка должна похудеть.
0
По поводу снижения энергопотребления. Никаких специальных мер не принималось, так как основной целью было знакомство с ОСРВ. Сопряжением OSA с режимами пониженного энергопотребления МК, если будет время, займусь позже. Хотя пока смутно представляю как это сделать.
По поводу снижения рабочей частоты и увеличения значения тика — взял на заметку, спасибо.
0
Хотя пока смутно представляю как это сделать.
По идее, для этого нужно отправлять МК спать в том месте, где ОС уходит в idle-цикл, а пробуждать системным тиком. Как вариант, можно завести задачу с приоритетом ниже всех остальных, которая будет всегда готова и тупо отправлять МК в спячку при получении управления, если первый вариант не предусмотрен.
0
То ли я больной, то ли хз.
Народ! Пост про OSA. Пример из собственного опыта. Пост не про то «какие опупенные часики я сделал». Не путайте пожалуйста. Вам по десять раз это повторяют, а вы всё равно свое гнете «жрет много для часов», «пользовательский интерфейс не удобен».
Да научитесь вы читать посты внимательно, а не тупо набивать счетчик сообщений.
+1
И что? Может, меня интересует вопрос скрещивания OSA с low power. Как именно часы для использования я это рассматриваю.
-1
Как именно часы для использования я это рассматриваю.
*не рассматриваю.
0
Тогда ставьте вопрос прямо «Как использовать режимы пониженного энергопотребления STM8 совместно с OSA, не разбирались?», а не выше
Сколько часики в итоге электричества жрут?
Вы же вроде не дурак, видите код, видите статью. Как вы смогли не заметить отсутствия настройки МК для PowerDown/пр.? «Из Ленинграда в Москву через Зимбабве».

Кстати, секунды по кнопке лучше не инкрементировать, а сбрасывать на ноль.
А Вам в голову не приходило, что там ещё и управление одной кнопкой не самое дружественное в принципе?

Нет, вы стремитесь сделать «свои идеальные часики», вас не интересует OSA и STM8.

P.S.: У ТС прошу прощения за оффтоп.
0
«Из Ленинграда в Москву через Зимбабве».
Бывает.
А Вам в голову не приходило, что там ещё и управление одной кнопкой не самое дружественное в принципе?
Ограничения платформы. В том и задача — получить максимально дружественный интерфейс в ограниченных условиях. Иногда попадаются очень любопытные идеи — как, скажем, у ARV в его однокнопочном таймере.
Нет, вы стремитесь сделать «свои идеальные часики», вас не интересует OSA и STM8.
Я лучше знаю, к чему я стремлюсь. Твой вариант не соответсвует действительности.
И да, «мои идеальные часики» очень далеко и от сабжа, и от того, о чем я говорю в комментах здесь.
Да научитесь вы читать посты внимательно, а не тупо набивать счетчик сообщений.
Не в том дело. Счетчик сообщений меня самого пугает.
0
Крутой у тебя осциллограф. Что это четырехканальный Тектроникс я и сам вижу, а какая модель?
0
  • avatar
  • Vga
  • 25 января 2013, 12:00
Tektronix TDS2014
Характеристики можно глянуть здесь.
Перед этим били UNI-T UTD2042CE и Tektronix TDS1001B. Tektronix-ами, очень доволен.
0
*были
0
Хорош зверь. Памяти разве что маловато.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.