Введение


Не так давно мой отдел столкнулся с трудностями поиска новых инженеров программистов для разработки встроенного ПО. Опытным и умным не нравился уровень зарплаты, а молодых просто нет в нашем городе. Поэтому под патронажем нашей доблестной глобальной компании со штаб квартирой где-то в Сент Луисе, мы начали сначала набирать студентов в интернатуру, а потом, решили пойти другим путем и сделать целых два курса по разработке ПО, а уже там самим выбирать самых “толковых” если понадобятся вдруг новые сотрудники. Это намного дешевле и позволяет охватить максимальное количество претендентов.
Немного отступлю от темы, сам я программировал последний раз очень давно, и вообще больше на С#, а последний глобальный проект на микроконтроллере (PIC16 на зыке Си) был сделан в далеком 2007 году.
Поэтому мне предстояло разобраться с современными микроконроллерами, языком С++ и операционной системой реального времени.
Конечно все наши проекты уже сейчас используют ОСРВ и пишутся на С++, но как разработчик я в них не учувствую, а занимаюсь тунеядством управлением проектами разработки такого ПО.

Выбор


Времени у меня на все про все было дано 1 месяц. С начала июня 2015 до начала июля 2015, потому что потом я собирался в отпуск, а после отпуска обычно полно работы. Надо было делать все быстро и четко.
Немного проконсультировавшись с коллегами, выяснил, что модное направление ARM Cortex различные ядра и из доступных отладочных плат можно заказать Olimex STM32P152 которые стоили 25 долларов. Они пришли очень быстро — 6 плат по цене примерно 2000 рублей. Стоит заметить, что эти платы были закуплены нами для университета, где собственно и будет проходить этот курс.
image

Исходные данные: Цель


Первым делом, нужно было определиться с тем, какую информацию и что давать студентам. Для себя я решил, что главной целью будет — показать весь процесс разработки ПО для устройств с использованием микропроцессора STM32, языка программирования С++ и операционной системой реального времени FreeRTOS, начиная от требований и разработки архитектуры и заканчивая кодированием.

Исходные данные: Ограничения


Итак задачей нашего проекта является создание демо приложения для отладочной платы Olimex STM32P152, по стандартам кодирования (который я тут приводить не буду), написанным на языке С++ с использование FreeRTOS.
Приложение должно быть понятным, простым и ненавязчивым, без заумных конструкций присущих языку С++. Архитектура должна быть описана на языке UML.

Исходные данные: Функциональные требования


SR0: Устройство должно измерять три параметра (иметь три переменных): Температуру микропроцессора, Напряжение VDDA, Напряжение с переменного резистора
SR1: Устройство должно выводить значение этих переменных на индикатор.
SR2: Единицы измерения для Температуры микропроцессора — градусы Цельсия, для остальных параметров — вольты.
SR3: При нажатии на кнопку 1, на индикаторе должен показываться экран со следующей измеряемой переменной,
SR4: При нажатии на кнопку 1 Светодиод 1 должен изменять свое состояние
SR5: При нажатии на кнопку 2, на индикаторе должен поменяться режим отображения переменных с постоянного показывания переменной на последовательное (менять экраны раз в 1.5 секунды) при следующем нажатии с последовательного на постоянное,
SR6: При нажатии на кнопку 2 светодиод 2 должен менять свое состояние.
SR7: Светодиод 3 должен моргать раз в 1 секунду.

Вкратце — это все требования для данной задачи.
Понятно, что задача не такая сложная, но позволяет практически полностью показать весь процесс разработки любого устройства по заданным требованиям.

Разработка: начало работы


И так требования готовы, можно приступать. Начнем с инфраструктуры. Для начала я создал проект в IAR для C++, ничего нового тут нет. Вот эта статья все описывает Создание проекта на C++ в IAR для STM32. Здесь останавливаться не будем.

Разработка: обертка для FreeRTOS


Поскольку я собрался использовать С++, а операционная система написана на Си, то мне нужна С++ обертка для FreeRTOS. В идеале можно было бы написать обертку так, чтобы она подходила под большинство ОСРВ, и тогда в проектах, можно было не зависеть от типа ОСРВ, но заморачиваться с этим я не стал и в данной ситуации выбрал только те функции операционной системы, которые мне нужны, и только их и обернул, тупо скопировав сигнатуру методов.
Также был добавлен статический метод static void run(void *parameters); Это как раз та функция, указатель на которую будет использоваться при создании задачи.
Для того, чтобы было возможно вызывать метод экземпляра класса в задаче, был сделан интерфейс iActiveObject с виртуальной функцией virtual void run(void) = 0; и глобальным атрибутом для хранения указателя на задачу.
iActiveObject.h
#include "types.h"          //Стандартные типы проекта
class iActiveObject
{
  public:              
    virtual void run(void) = 0;
    void *taskHandle;
};


Любой объект, который хочет быть задачей (активным) должен наследовать этот интерфейс и реализовывать метод run(). Указатель на этот объект передается в функцию run() обертки в качестве параметра.
На бумаге это выглядело это так:
image
Задачу реализации этой картины я поручил одаренному молодому специалисту, который успешно справился с ней и через пару дней выдал вот такой пустой рабочий проект в IAR 6.50
Пустой проект с оберткой FreeRTOS в IAR 6.50

Разработка: Общая архитектура


Пока молодой специалист делал обертку, я прикидывал архитектуру ПО. Для себя я выделил 3 пакета:
AHardware — пакет содержащий классы для работы и управления аппаратурой (Светодиоды, Индикатор, АЦП и так далее)
Application — пакет содержащий классы прикладного уровня и по задумке ничего не знающий про железо, поэтому может быть портрирован на любой микроконтроллер без изменения, ну при условии, что обертка разработана не человеком снежинкой. А в данном случае это не так:)
image
FreeRTOS — пакет с портированной операционкой и оберткой для неё.
Немного порисовав получилась следующая картина:
image

Разработка: Моргание светодиодом


Как обычно разработка начинается с самого интересного и сложного :) — реализации требования SR7: Светодиод 3 должен моргать раз в 1 секунду, показалась мне именно той задачей, освоив которую я смогу двигаться вперед.
Первым делом нужно было разобраться с портами микроконтроллера и настройкой частоты. И надо сказать, что за прошедшие 8 лет с моего последнего микроконтроллерного проекта, многе изменилось и это показалось совсем не изи. Пришлось очень внимательно прочитать датащит по этим разделам и в конце концов понять, что все довольно тривиально, просто настроек в разы больше (чем в PIC 16).
В итоге вся настройка портов была выкинута в __low_level_init(). И вообще всю настройку железа, которая не будет по ходу работы программы меняться я поместил в эту функцию. Она вызывается перед main(), до инициализации всех переменных и исполнения конструкторов глобальных экземпляров классов.
Настройка портов для светодидов
   //Настройка портов ввода-вывода
   //PE.10, PE.11 - светодиоды stat3 и stat4
   //PA.4, PA.5 - светодиоды stat1 и stat 2
   //Настраиваем порты PE.10, PE.11, PA.4, PA.5 на выход, cтр.174 CD00240194.pdf
   GPIOE->MODER |=  GPIO_MODER_MODER10_0;
   GPIOE->MODER |=  GPIO_MODER_MODER11_0;
   GPIOA->MODER |=  GPIO_MODER_MODER4_0;
   GPIOA->MODER |=  GPIO_MODER_MODER5_0;


Ну что же железо настроено и я опять сел за рисование, на этот раз — класса управления светодидами. Через пол часа получилось вот это:
image
И тут же реализация сего чуда:
LedsDriver.h
#include "types.h"            //Стандартные типы проекта tU8
#define LEDS_NUMBER    4
class cLedsDriver
{
  public:
    explicit cLedsDriver(void);
    void ledOn(const tU8 led);
    void ledOff(const tU8 led);
    void ledToggle(const tU8 led);
  private:
    static tPort ledsPort[LEDS_NUMBER];
    static const tU16 ledsPin[LEDS_NUMBER];
};


ledsdriver.cpp
#include "ledsdriver.h"       // Определение класса и тип tLeds
#include <stm32l1xx.h>        //Регистры STM32
#include "susuassert.h"       // for ASSERT
#include "types.h"            // для типов tPort, tU16, tU8
#include "bitutil.h"          // для макросов работы с битами 
#define LED1_PIN        GPIO_OTYPER_ODR_4
#define LED1_PORT	GPIOA
#define LED2_PIN        GPIO_OTYPER_ODR_5
#define LED2_PORT       GPIOA
#define LED3_PIN        GPIO_OTYPER_ODR_10
#define LED3_PORT       GPIOE
#define LED4_PIN        GPIO_OTYPER_ODR_11
#define LED4_PORT       GPIOE
tPort cLedsDriver::ledsPort[LEDS_NUMBER] = {LED1_PORT, LED2_PORT, LED3_PORT, LED4_PORT};
const tU16 cLedsDriver::ledsPin[LEDS_NUMBER] = {LED1_PIN, LED2_PIN, LED3_PIN, LED4_PIN};
/*******************************************************************************
* Function:  constructor
* Description: 
******************************************************************************/
cLedsDriver::cLedsDriver(void) 
{
}

/*******************************************************************************
* Function:  ledOn
* Description: Зажигает выбранный светодид
******************************************************************************/
void cLedsDriver::ledOn(const tU8 led)
{
  ASSERT(led < LEDS_NUMBER); 
  SETBIT(this->ledsPort[led]->ODR, this->ledsPin[led]);   
}

/*******************************************************************************
* Function:  ledOff
* Description: Гасит выбранный светодид
******************************************************************************/
void cLedsDriver::ledOff(const tU8 led)
{
  ASSERT(led < LEDS_NUMBER); 
  CLRBIT(this->ledsPort[led]->ODR, this->ledsPin[led]);
}
/*******************************************************************************
* Function:  ledToggle
* Description: Меняет состояние выбранного светодиода
******************************************************************************/
void cLedsDriver::ledToggle(const tU8 led)
{
  ASSERT(led < LEDS_NUMBER); 
  TOGGLEBIT(this->ledsPort[led]->ODR, this->ledsPin[led]);
}


Теперь снова нужно немного порисовать, чтобы сделать класс управления логикой работы светодиодов — класса cLedsDirector. Это будет активный класс, т.е. его функция run(), будет запускаться в задаче. Как я уже писал выше, все мои активные классы должны наследовать интерфейс iActiveObject. Поэтому рисунок выглядит тоже не сложно.
image
Опять же реализация тоже простая и по силам человеке снежинке, вроде меня:
ledsdirector.h
#include "ledsdriver.h"    //для cLedsDriver
#include "iactiveobject.h"  //для iActiveObject
typedef enum 
{
  LD_led1 = 0,
  LD_led2 = 1,
  LD_led3 = 2,
  LD_led4 = 3,
  LD_none = 4
} tLeds;
class cLedsDirector: public iActiveObject
{
  public:
    explicit cLedsDirector(void);
    void run(void);      
  private:
    cLedsDriver* pLedsDriver;
};


ledsdirector.cpp
#include "ledsdirector.h"      // Определение класса
#include "frtoswrapper.h"   // для oRTOS
#include "types.h"             // lля типов

#define LED_DELAY (tU32)500/portTICK_PERIOD_MS
/*******************************************************************************
* Function:  constructor
* Description: Создает объект класса cLedsDriver
******************************************************************************/
cLedsDirector::cLedsDirector(void)  
{
  this->pLedsDriver =  new cLedsDriver();   
}
/*******************************************************************************
* Function:  runTask
* Description: Задача управления ледами. led3 просто моргает
******************************************************************************/
void cLedsDirector::run(void)
{
  for(;;)
  {
    oRTOS.taskDelay(LED_DELAY);
    this->pLedsDriver->ledToggle(LD_led3);    
  }
}


Ну что же все написано, осталось самая малость — создать экземпляр класса cLedsDirector и задачу в main() и проверить как все это работает.
функция main()
#define LEDSDIRECTOR_STACK_SIZE configMINIMAL_STACK_SIZE
#define LEDSDIRECTOR_PRIORITY (tU32)2
// Не охота было заморачиваться с синглтоном, сделал oRTOS глобальным объектом
// можно было конечно сделать сRTOS статическим, но че-то тоже заморочек много
// зато просто, все равно всем нужен 
cRTOS oRTOS;
....
void main(void)
{  
  cLedsDirector *pLedsDirector = new cLedsDirector();
  oRTOS.taskCreate(pLedsDirector, LEDSDIRECTOR_STACK_SIZE, LEDSDIRECTOR_PRIORITY, "Leds"); 
  oRTOS.startScheduler();
}



Запускаем на плате, и о чудо — работает с первого раза. Значит все сделал правильно. Есть реализация первого требования. Будем идти дальше. А пока сохраним проект
Проект морганием светодиодом в IAR 6.50

Разработка: Кнопки


Светодиоды работают, теперь можно реализовать требования SR6: При нажатии на кнопку 2 светодиод 2 должен менять свое состояние и SR4: При нажатии на кнопку 1 светодиод 1 должен менять свое состояние.
Кнопки я решил делать без прерываний, да и вообще прерывания в этом проекте использовать не буду, хотя ничто не запрещает использовать прерывания, но просто я так решил.
Согласно процедуре рисуем класс cButtonsDriver.
image
Тут надо сказать одну вещь, что с первого раза у меня кнопки не заработали, точнее заработала только одна. Вторая не работала. Разобравшись, я понял, что одна кнопка подтянута к нулю, а вторая к единице. Поэтому нажатие определяется у них по разному. Для определения нажатия я и ввел дополнительный атрибут -buttonsTrigger (которого изначально в архитектуре не было). Он показывает по какому значению считается нажата кнопка по 0 или по 1. И вот после этого, все стало работать как часы.
Реализация очень простая.
buttonsdriver.h
#include "types.h"            //Стандартные типы проекта для tU16 и tU8
#define BUTTONS_NUMBER    2
typedef enum
{
  BS_buttonNotPressed = 0,
  BS_buttonPressed = 1
} tButtonState;
class cButtonsDriver
{
  public:
    explicit  cButtonsDriver();
    tButtonState getButtonState(const tU8 button);
  private:
    static tPort buttonsPort[BUTTONS_NUMBER];
    static const tU16 buttonsPin[BUTTONS_NUMBER];
    static const tBoolean buttonsTrigger[BUTTONS_NUMBER]; 
};


buttonsdriver.cpp
#include "buttonsdriver.h"    // Определение класса и тип tLeds
#include <stm32l1xx.h>        //Регистры STM32
#include "susuassert.h"       //for ASSERT
#include "types.h"            //для типов tPort, tU16, tU8
#define BUTTON1_PIN         GPIO_OTYPER_IDR_13
#define BUTTON1_PORT	    GPIOC
#define BUTTON2_PIN         GPIO_OTYPER_IDR_0
#define BUTTON2_PORT        GPIOA
tPort cButtonsDriver::buttonsPort[BUTTONS_NUMBER] = {BUTTON1_PORT, BUTTON2_PORT};
const tU16 cButtonsDriver::buttonsPin[BUTTONS_NUMBER] = {BUTTON1_PIN, BUTTON2_PIN};
// первая кнопка нажата, когда во входном регистре 0, вторая когда 1
const tBoolean cButtonsDriver::buttonsTrigger[BUTTONS_NUMBER]  =  {FALSE, TRUE}; 
/*******************************************************************************
* Function:  constructor
* Description: 
******************************************************************************/
cButtonsDriver::cButtonsDriver()
{
}
/*******************************************************************************
* Function:  getButtonState
* Description: Возвращает состояние кнопки, нажата или нет
******************************************************************************/
tButtonState cButtonsDriver::getButtonState(const tU8 button)
{
  tButtonState eState = BS_buttonNotPressed;  
  ASSERT(button < BUTTONS_NUMBER); 
  //У нас кнопки работают по разному, одна подтянута к 1, вторая к 0,
  //Поэтому тут танцы с бубном, вначале смотрим состояние копки, а потом
  //чтобы не городить много ифов и кейзов просто через массив состояний кнопки
  //с помощью исключаещего или определяем её нажати.  
  tBoolean isLogicalZero = !(this->buttonsPort[button]->IDR & 
                             this->buttonsPin[button]);
  if(isLogicalZero ^ this->buttonsTrigger[button])
  {
    eState = BS_buttonPressed;
  }  
  return eState;   
}


Ну что же с драйвером покончено, теперь надо подумать про задачу, которая будет переодически опрашивать кнопки.
Картинка выглядит очень похожей на активный класс для светодиодов, оно и верно, чего изобретать велосипед:
image

Нажатие на кнопку должно каким-то образом оповещать те задачи, которым нужно об этом знать. Можно сделать это несколькими способами: Через события, либо использовать очередь, либо воспользоваться последним достоянием freeRTOS нотификацией (Notify).
Проанализируем требования. По нажатию кнопки у нас должно осуществляться две вещи: Во-первых, менять свои состояния светодиоды 1 и 2, а во-вторых, на индикаторе должен меняться режим вывода и экраны. Проблема заключается в том, что очереди и события могут быть приняты только одной задачей, после чего вторая об этом не узнает, и тогда архитектура будет усложнена. Придется переадресовывать события, скажем что задача Светодиодов должна будет переадресовывать событие или очередь от кнопок задаче Индикатора. Что-то в этом меня не устроило, и я решил сделать нотификацию конктретной задаче. Т.е наша задача опроса кнопок после определения нажатия будет нотифицировать только те задачи, которым эти события нужны.
Для этого и был заведен массив указателей pTaskToNotify на задачи, которые необходимо оповещать.
Теперь реализация:
buttonscontroller.h
#include "types.h"            //Стандартные типы проекта tU32
#include "iactiveobject.h"    //для iActiveObject
#include "buttonsdriver.h"    //для cButtonsDriver
#include "frtosWrapper.h"     // для tTaskHandle
typedef enum 
{
  BT_button1 = 0,
  BT_button2 = 1,
  BT_none = 2
} tButtons;
class cButtonsController: public iActiveObject
{
  public:
    explicit  cButtonsController(const tTaskHandle *pTaskToNotify, 
                                 const tU32 countOfNotifiedTask);
    tButtons  getPressedButton(void) const { return pressedButton; };
    void run(void);
  private:
    cButtonsDriver* pButtonsDriver;
    tButtons getButton(void);
    tButtons pressedButton;
    const tTaskHandle *pTaskToNotify;
    tU32 countOfNotifiedTask;    
};


buttonscontroller.cpp
#include "buttonscontroller.h" // Определение класса
#include "susuassert.h"       // для ASSERT
#include "types.h"            // для типов tPort, tU16, tU8
#include "bitutil.h"          // для макросов работы с битами  
#define BUTTON_TASK_DELAY   (tU32) 50/portTICK_PERIOD_MS
#define NEXT_PRESS_DELAY    (tU32) 500/portTICK_PERIOD_MS 
/*******************************************************************************
* Function:  constructor
* Description: Инициализируем список задач для нотификации и количество задач
*              для нотификации. А также создаем драйвер кнопок.
******************************************************************************/
cButtonsController::cButtonsController(const tTaskHandle *pTaskToNotify, 
                                       const tU32 countOfNotifiedTask)
{
  ASSERT(pTaskToNotify != NULL);  
  this->pButtonsDriver =  new cButtonsDriver();
  this->pTaskToNotify = pTaskToNotify;
  this->countOfNotifiedTask = countOfNotifiedTask;  
}
/*******************************************************************************
* Function:  run
* Description: Определеяет нажатие кнопок и посылает нотификацию нужным 
*              нужным задачам
******************************************************************************/
void cButtonsController::run(void)
{
   tRtosStatus eStatus = RS_fail;
   tButtons eButtonPreviousState = BT_none;
   tButtons  eButtonCurrentState = BT_none;
   const tTaskHandle *pTaskHandle;
   tU32 i = 0;
   for(;;)
   {
     eButtonPreviousState = this->getButton();
     if (eButtonPreviousState != BT_none)
     {
       //еще раз проверяем для антибребезга
       oRTOS.taskDelay(BUTTON_TASK_DELAY);
       eButtonCurrentState = this->getButton();
       if (eButtonPreviousState == eButtonCurrentState)
       {
         pTaskHandle = this->pTaskToNotify;
         i = 0;
         //пробегаем по списку задач, которые нужно оповестить и оповещаем их
         while ((pTaskHandle != NULL) && (i != countOfNotifiedTask))
         {
           eStatus = oRTOS.taskNotify(*(pTaskHandle), (tU32)eButtonCurrentState, 
                                      eSetValueWithOverwrite);
           if(eStatus == RS_fail) 
           {
             ;//Обработка ошибки
           }           
           pTaskHandle++;
           i++;
         }
         //следующее нажатие будет определяться только после задержки  в 0.5 сек
         oRTOS.taskDelay(NEXT_PRESS_DELAY);
       }
     }
     oRTOS.taskDelay(BUTTON_TASK_DELAY);
   }
}
/*******************************************************************************
* Function:  getPressedButton
* Description: Определеяет какая из конопок нажата
******************************************************************************/
tButtons cButtonsController::getButton(void)
{
  tButtons eButton =  BT_none;
  if (BS_buttonPressed == this->pButtonsDriver->getButtonState(BT_button1))
  {
    eButton = BT_button1;    
  }
  else if (BS_buttonPressed == this->pButtonsDriver->getButtonState(BT_button2))
  {
    eButton = BT_button2;    
  }
  this->pressedButton = eButton;
  return eButton;
}


Теперь в main нужно создать список задач для оповещения и новую задачу для опроса кнопок:
main.cpp
#include <stm32l1xx.h>          // Регистры STM2
#include "ledsdirector.h"       // Для класса cLedsDirector
#include "buttonscontroller.h"  // Для класса cButtonsController
#include "types.h"              // Для типов проекта
#include "frtoswrapper.h"       // для cRtos
#define LEDS_TASK_HANDLE_INDEX          0
#define BUTTON_TASKS_NOTYFIED_NUM       1
#define LEDSDIRECTOR_STACK_SIZE configMINIMAL_STACK_SIZE
#define LEDSDIRECTOR_PRIORITY (tU32)2
#define BUTTONSCONTROLLER_STACK_SIZE 256//configMINIMAL_STACK_SIZE
#define BUTTONSCONTROLLER_PRIORITY (tU32)3
// Не охота было заморачиваться с синглтоном, сделал oRTOS глобальным объектом
// можно было конечно сделать сRTOS статическим, но че-то тоже заморочек много
// зато просто, все равно всем нужен :)
cRTOS oRTOS;
..
void main( void )
{  
  //задача ButtonControllera должна оповещать другие задачи о нажатии
  //на кнопку, и передавать её значение. Для этого заводим массив указателей на 
  //задачи, которые надо оповещать
  static tTaskHandle tasksToNotifyFromButton[BUTTON_TASKS_NOTYFIED_NUM];
  cLedsDirector *pLedsDirector = new cLedsDirector();
  oRTOS.taskCreate(pLedsDirector, LEDSDIRECTOR_STACK_SIZE, LEDSDIRECTOR_PRIORITY, "Leds"); 
  tasksToNotifyFromButton[LEDS_TASK_HANDLE_INDEX] = pLedsDirector->taskHandle;
  cButtonsController *pButtonsController =  new cButtonsController(tasksToNotifyFromButton, BUTTON_TASKS_NOTYFIED_NUM);
  oRTOS.taskCreate(pButtonsController, BUTTONSCONTROLLER_STACK_SIZE, BUTTONSCONTROLLER_PRIORITY, "Buttons");   
  oRTOS.startScheduler();
}


Запускаем, проверяем и все работает — да просто удивительно :) Вот что значит вначале порисовать.
Как обычно сохраняем проект:
Проект Кнопки и светодиоды для IAR 6.50
А проект стал выглдятеть вот так:
image

Итак, за каких-то три дня я реализовал три требования — не плохая скорость, учитывая, что всего у меня их 7. И решив, что за оставшиеся 2.5 недели я то точно все сделаю, отправился на два дня на озеро.
Как потом оказалось, зря я так оптимистически смотрел на вещи, но об этом уже во второй части.

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


  1. ignat99
    05.07.2015 11:58
    +1

    Очень интересно. Продолжайте пожалуйста.


  1. Goganchic
    05.07.2015 13:18
    +2

    спасибо за статью, очень здорово!

    у меня вопрос: а почему именно эта IDE? Т.е. как я понимаю, она платная и только под win. Что именно из IDE нужно студентам? Средства отладки? Просто как я понимаю, проект-то учебный, маленький, его можно хоть в блокноте накидать.
    Почему C++ а не C? Я в микроконтроллерах совсем чайник, но всегда казалось что C++ для многих задач — явный оверкил.


    1. lamerok Автор
      05.07.2015 13:33
      -1

      Здравствуйте,
      IAR лицензионный мы используем на работе, и привыкли к нему, задача наша, найти толковыъ студнтов и потом по возможности взять на работу, а ведь хорошо, когда они уже знают среду в которой мы работаем.
      Триал версия IAR для ARM доступна со всеми фичами для приложений до 30 кБ кода. Собтсвенно, это меня устраивает, код получился значительно меньше.
      С++ мне нравится по одной простой причине — что при грамотном подходе код получается очень читабельным и понятным. Кроме того, поскольку кодирование это вообще у нас второстепенно :) на первом месте разработка архитектуры, а она у нас делается на UML. С диаграмм UML потом очень удобно все перекладывать в код и классы, чисто теоретически можно их потом автоматом генерить (но это моветон). Для коммерческих проектов, конечно все это делается вручную. Зато по архитектуре сразу понятна задумка автора и можно на раннем этапе указать на ошибки.
      А вообще микроконтроллеры шагнули далеко вперед за 8 лет :) И вот микроконтроллер за 2 бакса с 16 кБатам ОЗУ и 128 ПЗУ. поэтому использование С++ тут как бы уже нормально, но если очень надо, можно использовать С++ и на более мелких микроконтроллерах, только надо будет например переопределить new, чтобы динамически память не выделялась, по возможности не использовать виртуальные функции и и еще ряд ухищрений, но думаю, что сейчас это уже не нужно — памяти хватит для любого нашего проекта :)


      1. Goganchic
        05.07.2015 15:38
        +3

        С одной стороны мотивация ясна, и все же с IDE задумка несколько спорная: велик риск получить на выходе не толкового студента, а мартышку которая научилась по кнопкам в IDE нажимать. Я понимаю, если задача сложная, когда разбираешь какое-нибудь хитрое состояние гонок, там да, и дебаггер был бы полезен, или когда рефакторишь крупный проект — то крутые возможности IDE — очень кстати, — но все эти плюшки, на мой взгляд, нужно давать уже состоявшемуся специалисту, а никак не зеленому студенту, от которого требуется понимание как компилятор запустить, код зальется на микроконтроллер и т.п.

        По поводу C++: все правильно пишете — код читаемый получается при грамотном подходе, а чтобы подход с C++ был грамотным — ему нужно лет 5 обучаться, собирать все шишки и грабли. Если вы хорошо разбираетесь в C++ — то вам будет относительно просто писать код, а студенты, которые ничего в этом не понимают, при первой же ошибке впадут в ступор и ничего не дадут на выходе. Опять же, для больших проектов — да, может быть C++ в плане организации архитектуры кода хорош, но для маленьких учебных задач — оверкил.


    1. HomoLuden
      06.07.2015 11:42

      Я какое-то время назад (пол-года примерно) тоже спрашивал у опытных в Embedded коллег, почему IAR, а не Eclipse + GCC, например.
      Были случаи, когда GCC генерировал приложение, зависавшее через некоторое время работы (несколько часов — сутки, где-то). Серьезные проблемы были в продакшене. При этом компилятор IAR выдавал полностью рабочее приложение по тем же сорцам. Конечно, мог иметь место человеческий фактор при портировании кода в иную среду компиляции. Однако, по моим «умозрительным» оценкам IAR выдает значительно более компактный машинный код. Возможно, у IAR более компактные подмены стандартным функциям Си или еще что-то.


      1. Goganchic
        07.07.2015 10:13

        А причину зависаний не искали? Просто интересно же.


    1. sam_satan
      06.07.2015 13:19
      +2

      Вставлю свои пять копеек.
      В настоящее время для разработки под arm и в частности stm32 наиболее распространены 4 ide(не претендую на истину).
      Первые две, это IAR и KEIL. Коммерческие ide, специально созданные для создания по встраиваемых систем. Они платны, есть триал версии с ограничением кода прошивки.
      Вторые две это eclipse + gcc для arm или собранный на его основе Cocoox. Они бесплатны.
      Собственно главное различие в том, что iar и keil работают из коробки, под них есть масса примеров, драйвера для программаторов встроены в дистрибутив и.т.п.
      Во вторых двух совершенно другая история, как пример, в eclipse нельзя просто залить код в микроконтроллер, есть кнопка залить и запустить дебаггер, периодически глючит просмотр регистров контроллера.
      Теперь представьте себе человека, который только знакомится с микроконтроллерами серии стм32. Reference manual (20мб) на серию stm32f4 это тысяча с лишним страниц описывающих периферию контроллера. Обилие конфигурирующих дефайнов и файлов в проекте, совершенно не понятных студенту, но без которых ничего работать не, расчёт адресов памяти для линкера. И не разобравшись в КАЖДОМ из пунктов досконально, не получится даже поморгать светодиодом.
      У вас есть 2 варианта: поставить свободную ide типа eclipse и зарыться в непонятных для новичка вещах, разгребая которые вы потеряете весь энтузиазм.
      Или же поставить iar keil, запустить готовый проект коих тысяча и наслаждаться моргающим светодиодом, параллельно разбираясь зачем в проекте подключен тот или иной файл.
      ps Сам пишу в Eclipse +gcc на C++ под stm32, но начинал с keil.


  1. lamerok Автор
    05.07.2015 16:16
    -3

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

    По поводу IDE, тут стоит уточнить, что IDE мы используем только для отладки и очень редко. Для сборки используется обычный make файл, сборка прошивается сразу в плату где проходят смоке тесты. И только в случае, если что-то вдруг не работает, делает тестовый проект в IDE для отладки. Но на моей практике, такое было редко. Кстати мейк есть и в моем проекте, просто я не доделал и бросил — это не входило в мою задачу. Там и линтом, например обертка проверялась, чтобы более менее код чистый был.
    С++ мы используем у себя во всех проектах, поэтому если студент пришел к нам скажем на практику, первым делом он должен хотя бы на начальном уровне знать его — дальше дело практики.
    Основное требование — простота кода и его поддерживаемость, чтобы, скажем — через 3 года, любой инженер мог открыть архитектуру, посмотреть код и быстро все понять. Чистый Си в этом плане проигрывает С++, раньше мы на Си программировали как на С++ для этого, используя структуры в качестве классов. Но это тоже еще то извращение.


  1. Amomum
    06.07.2015 11:04
    +1

    Скажите, а что означает маленькая буква «с» перед именем каждой функции? Это ваш корпоративный стандарт или тлетворное влияние freeRTOS?


    1. lamerok Автор
      06.07.2015 11:23
      -1

      Это стандарт кодирования, обзначает class. Наследие использования Си и структур в качестве классов.


  1. yanvasilij
    06.07.2015 12:25
    +2

    Подход с C++ мне понравился — красиво получается. А как вы собираетесь «оборачивать» разного рода стеки (TCP/IP, lwip тот же, GUI, файловые системы)? По протоколам, тоже интересно, как Вы собираетесь сделать? У IAR кстати есть похожая штука, позволят получать неплохие результаты.

    А вот что касается мотивации, я не могу не воздержаться от критического комментария. У меня такое ощущение, что работодатели не понимают простого правила — хороший специалист стоит денег. И то, что он не хочет идти к Вам за малые деньги, так он полностью прав. Если у вас нет денег на специалиста, то вариантов у вас собственно два, либо вообще не занимайтесь тем, чем занимаетесь, либо нанимайте студентов. Вот только во втором случае будьте готовы, во-первых, к низкому качеству по-началу, и, во-вторых, к тому, что он уйдет от вас, в другую контору за большие деньги когда наберется профессионализма и будет прав. А писать гуевые обертки, что-бы программировать «мышкой», я думаю это полезно только тем кто пишет эти обертки. Тот, кто будет ей пользоваться рискует стать бестолковой мартышкой без понимания сути того, что происходит внутри. Профессионализм — это результат долгого и упорного труда, за месяц или даже год, хорошего спеца не получить. А как только он им становится — он растет в цене.