FreeRTOS. Пример

Обещал я как то пример программы похожей на реальность, но с использованием FreeRTOS. Обещанного, как водится, три года ждут. Правда прошло уже пять… ой… В общем, ужасно не люблю писать бесполезные примеры. А полезные, которые можно выложить, как то не попадались под руку. И вот оно, нашло таки.

В общем, суть задачи, управление несколькими шаговиками не приходя в сознание. Причем все должно вращаться одновременно и сигнализировать о приходе на точку, а также прерываться по требованию. Да это все можно сделать десятком разных способов. И программно и полуаппаратно. Как угодно, короче. А у меня будет на FreeRTOS. Надо же на чем-то показать :)

Итак, все будет просто и в лоб. Не буду перегружать пример излишними усложнениями, проверками, контролем ошибок и прочим. А то даже в официальном мануале такого любят навертеть, что человек который в это дело только вникает поглядит и закроет.

▌Железо
Ну тут все элементарно. Pinboard II с STM32F103C8T6. Да шаговые драйверы TB6600. У драйверов этих есть сигнал STEP сигнал DIR и сигнал EN. Подаем разрешение на EN, выбираем направление на DIR и гоним тактовые импульсы на STEP. И таких четыре драйвера.

▌Среда
Делаю я все в EmBitz кто то ее в комментариях упомянул. Я попробовал и протащился. Работает НАМНОГО шустрей чем любая монструозная хрень на эклипсе. Так что атолик студио, кокос — все нахер. А внутре у ней тот же GCC.

▌Установка
Как ставить FreeRTOS описывалось в прошлой статье. С тех пор ничего не поменялось. Кинуть файлы в проект и подцепить их в среде. Элементарно. Также бегло пробегитесь по первой статье.

▌Драйвер шаговика
Начинаем строить драйвер управления шаговиком, первым делом заведем структуру в которой опишем все наши движки:

1
2
3
4
5
6
typedef struct
{
	tIOLine DIR;
	tIOLine STEP;
	tIOLine EN;
} tStepper;

tIOLine это тип мой библиотечки GPIO. На регистрах, без SPL, HAL и прочих аббревиатур. А нет, CMSIS там есть. Если интересно, то она в файлах (IO.h:IO.c) будут вопросы по ней распишу ее отдельной статьей. Хотя там все тривиально. Этот тип определяет ногу которой мы будем дрыгать. Вы можете писать свою какую угодно реализацию.

Дальше создаем массив шаговиков:

1
2
3
4
5
6
#define STEPPER_COUNT (4)
 
tStepper Motor[STEPPER_COUNT] ={{io_DIR1, io_STEP1, io_EN1},
                                {io_DIR2, io_STEP2, io_EN2},
                                {io_DIR3, io_STEP3, io_EN2},
                                {io_DIR4, io_STEP4, io_EN2}};

Из этой самой структуры. Тут же сразу прописываем кому какие ноги. Все эти io_**** прописывают в библиотечке IO.h похожей структурой вида:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define		cIO_COUNT		(13)
const tGPIO_Line IOs[cIO_COUNT] = 	 {{ GPIOB, 5,  OUT_10MHz + OUT_PP, HIGH},  	 // Blink
				    	  { GPIOB, 13,  OUT_10MHz + OUT_PP, HIGH},  	 // Dir1
					  { GPIOB, 15, OUT_10MHz + OUT_PP, HIGH},  	 // En1
					  { GPIOB, 12, OUT_10MHz + OUT_PP, HIGH},  	 // Step1
					  { GPIOB, 0, OUT_10MHz + OUT_PP, HIGH},  	 // Dir2
					  { GPIOB, 1, OUT_10MHz + OUT_PP, HIGH},  	 // En2
					  { GPIOB, 10, OUT_10MHz + OUT_PP, HIGH},  	 // Step2
					  { GPIOB, 12, OUT_10MHz + OUT_PP, HIGH},  	 // Dir3
					  { GPIOB, 12, OUT_10MHz + OUT_PP, HIGH},  	 // En3
					  { GPIOB, 12, OUT_10MHz + OUT_PP, HIGH},  	 // Step3
					  { GPIOB, 12, OUT_10MHz + OUT_PP, HIGH},  	 // Dir4
                                          { GPIOB, 12, OUT_10MHz + OUT_PP, HIGH},  	 // En4
					  { GPIOB, 12, OUT_10MHz + OUT_PP, HIGH}};  	 // Step4

Первое поле описывает порт, второе значение битов CNF и MODE, а третье бит ODR в виде подтяжки или уровня на ноге. Заодно можно в цикле все ноги инициализировать и не париться. Ну и менять если что можно мгновенно в одном месте.

и символическая нумерация к ней:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef enum
{
	io_Blink = 0,
	io_DIR1 = 1,
	io_EN1,
	io_STEP1,
	io_DIR2,
	io_EN2,
	io_STEP2,
	io_DIR3,
	io_EN3,
	io_STEP3,
	io_DIR4,
	io_EN4,
	io_STEP4,
} tIOLine;

▌Строб
Продложаем про шаговики. Дрыг стробом сделан простой функцией:

1
2
3
4
5
6
7
8
9
10
void st_Step(tMotor Number, tMotorDir Direction)
{
IO_SetLine(Motor[Number].DIR,Direction);
 
IO_SetLine(Motor[Number].STEP,LOW);
 
taskYIELD(); // vTaskDelay(1);
 
IO_SetLine(Motor[Number].STEP,HIGH);
}

Вот такой вот нативный и наивный код. IO_SetLine ставит ногу нужного шаговика (берется из массива в нужное направление в ноль или 1)

tMotor это просто перечисление именованное. Чтобы не путаться. Можно оставить просто номера. У меня же они в перечислении прописаны:

1
2
3
4
5
6
7
typedef enum
{
	st_MotorR = 0,
	st_MotorL = 1,
	st_MotorA,
	st_MotorB
} tMotor;

Т.е. теперь компилятор сам подставит вместо имени номер от 0 до 4 и не позволит засунуть туда что-либо иное, например левое значение. Ругаться будет. Та же история и с tMotorDir это виды направления:

1
2
3
4
5
6
7
typedef enum
{
	CW = 0,
	CCW = 1,
	PUSH = 0,
	PULL =1
} tMotorDir;

У меня тут есть в системе не только вращающие, но и толкающие шаговики. Вот для них и прописаны направления. CW — clockwise, т.е. по часовой. А CCW — counter clockwise — т.е. против.

И, возвращаясь к коду, мы вначале выбираем ногу нужного шаговика с направлением:

1
IO_SetLine(Motor[Number].DIR,Direction);

А потом дергаем стробом STEP, дергая линию вниз-вверх:

1
2
3
4
5
IO_SetLine(Motor[Number].STEP,LOW);
 
taskYIELD(); // vTaskDelay(1);
 
IO_SetLine(Motor[Number].STEP,HIGH);

Обратите внимание на один момент. Я прижимаю линию, потом надо бы сделать выдержку, чтобы все переходные процессы закончились, чтобы принимающая микросхема восприняла это все. И только потом поднимать обратно вверх. Но использовать таймер аппаратный мне лень. А таймер RTOS, который по фукнции vTaskDelay(1) отдаст диспетчеру на 1мс управление, слишком медленный. Мельче 1мс он квантоваться не умеет. А 1мс многовато, с такой большой задержкой работать все будет, но быстро вращать шаговиком будет сложновато, особенно на большом числе микрошагов. Как получить небольшую задержку, такую чтобы логика ШД драйвера отработала ее, а ничего изобретать не пришлось? Да просто — отдать управление диспетчеру через функцию taskYIELD. Она крутанет диспетер выполнит там что то кооперативное, может быть, а потом вернет управление сюда же. Пройдет несколько микросекунд, но этого будет достаточно для четкого строба. А то что время неопределено это, так то небольшая беда, диспетчер все равно быстро проворачивает, на 72Мгц то тем более. На строб не повлияет сильно, главное он будет явным.

▌Вращение
С шагами все ясно. Сделаем вращение. Запихнем все в функцию у которой на входе номер шаговика, направление, число шагов которые надо сделать и скорость. Все элементарно:

1
2
3
4
5
6
7
8
9
10
11
12
13
void st_Rotate(tMotor Number, tMotorDir Direction, uint32_t N, uint16_t Speed)
{
uint32_t i;
    IO_SetLine(Motor[Number].EN,ON);  // Поднять ногу ENABLE для включения драйвера
 
    for(i=0;i<N;i++)
        {
        st_Step(Number,Direction);
        vTaskDelay(Speed);
        }
 
    IO_SetLine(Motor[Number].EN,OFF); // Выключаем драйвер
}

Включаем разрешение драйвера, подняв линию EN для выбранного привода и в цикле вызываем функцию Step которая делает дрыг. Потом ждем немного, отдав управление диспетчеру через vTaskDelay(Speed), где в Speed хранится скорость от обратного, т.е. 1 самая быстрая, а чем больше тем медленней идет скорость шагов.

Все здорово, но возникает вопрос. А как вращать двумя одновременно да не просто так, а под управлением. Поочередно их не запустишь, тогда сначала один провернется, потом второй. Можно раскидать их на разные задачи и запустить их одновременно. Хорошо, создаем задачу для каждого двигателя:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
// Задача для правого мотора
void st_vMotorR (void *pvParameters)
{
st_Rotate(st_MotorR,CCW,1000,1);
vTaskDelete(NULL);
}
 
// Задача для левого мотора
void st_vMotorL (void *pvParameters)
{
st_Rotate(st_MotorL,CCW,1000,1);
vTaskDelete(NULL);
}

vTaskDelete с параметром NULL завершает текущую задачу из которого она была вызвана. Т.е. если мы выполним задачу st_vMotorL, то она крутанет левым мотором против часовой стрелки на 1000 шагов с самоый быстрой скоростью (1), а после завершится. Если задачу не затерминировать так, то все повиснет в функции обработки исключения вида «некорректный выход из задачи». Т.е. выход из задачи может быть только на кладбище. Либо бесконечный цикл. Но никто не запрещает убить и потом запустить задачу вновь.

Создается и запускается она так:

1
2
xTaskCreate(st_vMotorL,"MotorL",  configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL); // Пускаем один мотор
xTaskCreate(st_vMotorR,"MotorR", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL);  // Пускаем второй мотор

▌Очередь
Отлично, после создания задачек, как только они попадут в диспетчер наши моторы одновременно стартанут и отработают. Но этого мало. Проблема в том, что задачи то у нас получились изолированные, каждая сама в себе. Каждая крутит свой моторчик и плевать ей на все. А по логике надо рулить всеми движками сразу по какой-то причудливой логике.

Т.е. должна быть третья задача, в которой будет вращаться та самая логика управления движками и которая должна командовать задачами вращения движков. Способов реализации можно придумать много. Первое что напрашивается это забацать глобальную переменную которая будет видна всем задчам и меняя ее в одной задаче влиять на другие. Работать будет, но способ этот кривой и имеет много косяков, прежде всего операционных. Которые могут вылезти в многозадачном режиме с вытеснением. Когда мы где-то еще не закончили запись, а в другом месте, по прерыванию от диспетчера, уже идет чтение. Опять же придется постоянно будить задачу, чтобы она постоянно проверяла нет ли изменений. Для решения этих проблем есть механизм очередей.

Суть очереди сообщений, напомню, в том, что регистрируем некий почтовый ящик в который любая задача может пихать что угодно. Число, строку, структуру. Любые данные. Надо только вначале договориться о том, что там будет. Точнее какого размера.

А принимающая задача даже не обязана этот ящик проверять. Ей главное задекларировать «Я Жду письмо из этого ящика» и уснуть, отдав управление диспетчеру. И как только в ящик что-то свалится, так диспетчер пнет своим кованым нацистким сапогом нашу спящую задачу и со словами «Подъем, швайне, тибе посилька. Куры, млеко, яйки». И принимающая задача очнется и достанет пакет с данными.

Давайте в нашу функцию st_vMotorR, которая только и умеет что крутить моторчиками, добавим ящик из которого она будет вынимать ценные указания на то сколько крутить, куда и как быстро.

Заведем в открытой области видимости тип tRotate в виде структурки, в которой опишем, что мы, собственно, ждем:

1
2
3
4
5
6
7
typedef struct
{
  tMotor aMotor;
  tMotorDir aDirection;
  uint16_t aSteps;
  uint8_t aSpeed;
} tRotate;

Тут, очевидно, номер мотора, направление, число шагов и скорость.

Зарегистрируем наш почтовый ящик ака очередь сообщений. Для этого надо обьявить видимую другим переменную которая будет указателем на ящик, определенного типа. Это:

1
2
3
typedef struct
QueueHandle_t qMotorR; // Очередь правого мотора 
QueueHandle_t qMotorL; // Очередь для левого мотора

Две очереди, по одной на каждый мотор. А затем присвоим этим указателям адреса очереди, инициализировав их.

1
2
3
typedef struct
qMotorR = xQueueCreate (2, sizeof(tRotate));
qMotorL = xQueueCreate(2, sizeof(tRotate));

В каждую очередь теперь влезает по 2 команды, содержащие упаковку данных вида tRotate. Т.е. мы можем сказать крути сначала влево так, а потом вправо этак и движок обработает две команды. А больше в очередь не влезет, но ничто не мешает добавить и побольше, главное чтобы оперативки хватило. Второй параметр это размер запихиваемой туда посылки в байтах. Чтобы не считать его вручную воспользуемся командой sizeof, дав ей понюхать тип.

Запись в очередь, чтобы передать команду, будет выглядеть как то так:

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct
tRotate Sendit;  // создаем переменную Sendit с типом tRotate
 
// Наполняем ее параметрами
 
Sendit.aMotor = st_MotorL;
Sendit.aDirection = CCW;
Sendit.aSteps = 10000;
Sendit.aSpeed = 5;
 
// И пихаем эту хероту в очередь qMotorL. Скормив в качестве данных адрес указателя на структуру (&Sendit)
xQueueSend(qMotorL,&Sendit,4);

Что за циферка 4 в xQueueSend в последнем параметре? А это число тиков которые наша функция посылания данных будет ждать прежде чем выдаст Fail. Т.е. если очередь переполнена, например, потому, что в нее валятся данные от многих источников, а принимающая сторона не успевает разгружать эти вагоны или вообще висит, то сообщение не уйдет в пустоту. Наткнувшись на переполненную очередь умная функция отправки отдаст управление диспетчеру и вернется через тик, попробовав снова. И так 4 попытки. И только потом вернет 0, мол нешмогла я, не шмогла. Можно вместо 4 поставить portMAX_DELAY и тогда программа дальше не пойдет пока не пропихнет данные в эту несчастную очередь. Или, если поставить 0, то попытка будет всего одна.

Окей, приняли. Разгружаем!

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct
void st_vMotorR (void *pvParameters)
{
 
tRotate Rcv;  // Переменная в виде структуры типа tRotate в которую мы выгрузим содержимое посылки
 
while(1)
    {
    xQueueReceive(qMotorR,&Rcv,portMAX_DELAY);
    st_Rotate(Rcv.aMotor,Rcv.aDirection,Rcv.aSteps,Rcv.aSpeed,StopMotorR);
    }
}

Тут у нас уже бесконечный цикл как бы. Но на самом деле, нифига, у нас задача встанет на фукнции xQueueReceive и уснет пока там что-либо не появится. На это намекает параметр длительности portMAX_DELAY, т.е. ждать вечно. А как только появится, так содержимео сразу же будет выгружено в Rcv и засунуто в st_Rotate уже разложенное по полочкам.

Как все красиво и просто получается. Все задачи дробятся на тупые и прямолинейные, как гвоздь, участки.

Так что ежели нам где захотелось моторчиками покрутить, то всего то надо сформировать пакован с данными и распихать его по очередям:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
typedef struct
void Sender (void *pvParameters)
{
tRotate Sendit;
 
// Заполняем данными структуру
Sendit.aMotor = st_MotorL;
Sendit.aDirection = CCW;
Sendit.aSteps = 10000;
Sendit.aSpeed = 5;
 
// Отправляем ее в очередь левому движку
xQueueSend(qMotorL,&Sendit,4);
 
// Аналогично для правого
Sendit.aMotor = st_MotorR;
Sendit.aDirection = CCW;
Sendit.aSteps = 10000;
Sendit.aSpeed = 5;
 
xQueueSend(qMotorR,&Sendit,4);
 
vTaskDelete(NULL);
}

И моторчики стартанут одновременно. И рулить ими можно будет как угодно и независимо, пихая данные каждому в очередь. У очереди там еще разные другие функции есть. Типа очистка от всего содержимого, узнать сколько там внутри занято, прочитать из очереди, но не удалять… Почитайте доки.

▌Флаги и семафоры
Все здоровски, но чего то не хватает. Чего? Да например возможности остановить движок. Пнули мы его в крутячку и он в нашем цикле

1
2
3
4
5
6
7
8
9
10
11
12
13
void st_Rotate(tMotor Number, tMotorDir Direction, uint32_t N, uint16_t Speed)
{
uint32_t i;
    IO_SetLine(Motor[Number].EN,ON);
 
    for(i=0;i<N;i++)
        {
        st_Step(Number,Direction);
        vTaskDelay(Speed);
        }
 
    IO_SetLine(Motor[Number].EN,OFF);
}

Будет крутиться пока не остановится. А если мы поняли, что уже все, хватит крутить. На конечник заехали, дальше уже погнет все к ебеням? Тут бы добавить флаг какой… Но не возвращаться же к переменным и флажкам? Нет, конечно же нет. Применим тут семафор.

По сути Семафор это частный вариант очереди. Только еще более примитивный. Из одного значения которое может быть либо 0 и 1 для двоичного семафора, или счетным т.е. три раза семафор нажали — внутри образовалось 3, три раза считали — они последовательно уменьшились до 0 с каждым чтением.

Читается он функцией

1
xSemaphoreTake(Name,N)

Где Name это имя семафора который мы ждем, а N время ожидания.

И у семафора также есть время ожидания его. Можно поставить portMAX_DELAY и тогда пока нам семафор не зажгет 1 программа отсюда не уедет никуда. Заснет и будет ждать отмашки. Либо сделать 0 и тогда семафор будет работать подобно флажку, возвращая 0 если там ничего нет. И 1 если что то есть.

Применим семафор в виде флага для обрыва цикла шагов, вписав его в код:

Сначала объявим указатели на парочку семафоров:

1
SemaphoreHandle_t StopMotorR,StopMotorL;

А затем инициализируем их адресами:

1
2
StopMotorR = xSemaphoreCreateBinary();
StopMotorL = xSemaphoreCreateBinary();

А затем добавим в функцию которая щелкает тактами ожидание светофора:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void st_Rotate(tMotor Number, tMotorDir Direction, uint32_t N, uint16_t Speed, SemaphoreHandle_t Stopper)
{
uint32_t i;
 
    IO_SetLine(Motor[Number].EN,ON);
 
    for(i=0;i<N;i++)
        {
        if(xSemaphoreTake(Stopper,0)) break;
 
        st_Step(Number,Direction);
        vTaskDelay(Speed);
        }
 
    IO_SetLine(Motor[Number].EN,OFF);
}

В функцию приходит указатель на семафор, дальше он проверяется и если семафор вдруг вернул 1, т.е. он считался и не пустой, его кто то выставил, то все. Стоп машина. Выход из цикла через break; Гасим Enable на драйвера, выходим.

Ну и вызов функции в задаче:

1
2
3
4
5
6
7
8
9
10
void st_vMotorR (void *pvParameters)
{
tRotate Rcv;
 
while(1)
    {
    xQueueReceive(qMotorR,&Rcv,portMAX_DELAY);
    st_Rotate(Rcv.aMotor,Rcv.aDirection,Rcv.aSteps,Rcv.aSpeed,StopMotorR);
    }
}

Теперь мы можем запустить вращение, подав команду. Она пройдя через очередь разбудит задачу которая подает такты, и начнет крутить движком. А если надо подать стоп, то в семафор кто-нибудь, должен записать команду стоп. И все встанет как вкопаное. Команда подается так:

1
2
typedef struct
xSemaphoreGive(StopMotorR);

У на семафоры именные, у каждого свой. Все получается очень красиво и наглядно. Там в коде есть еще и обратный семафор, дающий фидбэк, что мол машина остановилась. А уж кто его считает я не решил.

Вот какие функции получились в итоге:

▌stepper.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include "main.h"
#include "stepper.h"
 
 
typedef struct
{
	tIOLine DIR;
	tIOLine STEP;
	tIOLine EN;
} tStepper;
 
 
 
 
#define STEPPER_COUNT (4)
 
tStepper Motor[STEPPER_COUNT] ={{io_DIR1, io_STEP1, io_EN1},
                                {io_DIR2, io_STEP2, io_EN2},
                                {io_DIR3, io_STEP3, io_EN2},
                                {io_DIR4, io_STEP4, io_EN2}};
 
 
 
void st_Step(tMotor Number, tMotorDir Direction)
{
IO_SetLine(Motor[Number].DIR,Direction);
 
IO_SetLine(Motor[Number].STEP,LOW);
 
taskYIELD(); // vTaskDelay(1);
 
IO_SetLine(Motor[Number].STEP,HIGH);
}
 
 
void st_Rotate(tMotor Number, tMotorDir Direction, uint32_t N, uint16_t Speed, SemaphoreHandle_t Stopper)
{
uint32_t i;
 
    IO_SetLine(Motor[Number].EN,ON);
 
    for(i=0;i<N;i++)
        {
        if(xSemaphoreTake(Stopper,0)) break;
 
        st_Step(Number,Direction);
        vTaskDelay(Speed);
        }
 
    IO_SetLine(Motor[Number].EN,OFF);
}
 
 
void st_vMotorR (void *pvParameters)
{
tRotate Rcv;
 
while(1)
    {
    xQueueReceive(qMotorR,&Rcv,portMAX_DELAY);
    st_Rotate(Rcv.aMotor,Rcv.aDirection,Rcv.aSteps,Rcv.aSpeed,StopMotorR);
    xSemaphoreGive(OK_MotorR);
    }
}
 
void st_vMotorL (void *pvParameters)
{
tRotate Rcv;
 
while(1)
    {
    xQueueReceive(qMotorL,&Rcv,portMAX_DELAY);
    st_Rotate(Rcv.aMotor,Rcv.aDirection,Rcv.aSteps,Rcv.aSpeed,StopMotorL);
    xSemaphoreGive(OK_MotorL);
    }
}
 
void Stepper_Init(void)
{
if(pdTRUE != xTaskCreate(st_vMotorR,"MotorR", 	configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL)) ERROR_ACTION(TASK_NOT_CREATE,0);
qMotorR = xQueueCreate (2, sizeof(tRotate));
StopMotorR = xSemaphoreCreateBinary();
OK_MotorR = xSemaphoreCreateBinary();
 
 
if(pdTRUE != xTaskCreate(st_vMotorL,"MotorL", 	configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL)) ERROR_ACTION(TASK_NOT_CREATE,0);
qMotorL = xQueueCreate( 2, sizeof(tRotate));
StopMotorL = xSemaphoreCreateBinary();
OK_MotorL = xSemaphoreCreateBinary();
}

▌stepper.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#ifndef STEPPER_H_INCLUDED
#define STEPPER_H_INCLUDED
 
typedef enum
{
	st_MotorR = 0,
	st_MotorL = 1,
	st_MotorA,
	st_MotorB
} tMotor;
 
 
typedef enum
{
	CW = 0,
	CCW = 1,
	PUSH = 0,
	PULL =1
} tMotorDir;
 
 
typedef struct
{
  tMotor aMotor;
  tMotorDir aDirection;
  uint16_t aSteps;
  uint8_t aSpeed;
} tRotate;
 
 
 
void Stepper_Init(void);
 
QueueHandle_t qMotorR,qMotorL;
 
SemaphoreHandle_t StopMotorR,StopMotorL;
SemaphoreHandle_t OK_MotorR,OK_MotorL;
 
 
#endif /* STEPPER_H_INCLUDED */

▌main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include "main.h"
 
 
 
void vBlinker (void *pvParameters)
{
    while(1)
    {
	IO_SetLine(io_Blink,ON);
	vTaskDelay(80);			            
	IO_SetLine(io_Blink,OFF);
	vTaskDelay(180);				    
    }
}
 
void Sender (void *pvParameters)
{
tRotate Sendit;
 
Sendit.aMotor = st_MotorL;
Sendit.aDirection = CCW;
Sendit.aSteps = 10000;
Sendit.aSpeed = 5;
 
xQueueSend(qMotorL,&Sendit,4);
 
Sendit.aMotor = st_MotorR;
Sendit.aDirection = CCW;
Sendit.aSteps = 10000;
Sendit.aSpeed = 5;
 
xQueueSend(qMotorR,&Sendit,4);
 
 
vTaskDelay(1000);
 
xSemaphoreGive(StopMotorL);
 
vTaskDelay(5000);
 
xSemaphoreGive(StopMotorR);
 
 
vTaskDelete(NULL);
}
 
 
 
int main(void)
{
SystemInit();  // Запуск системной инициализации тактовой частоты
IO_Init();       // Инициализируем порты GPIO 
Stepper_Init();  // Инициализируем наши движки. Там создаются очереди, семафоры и запускаются задачи
 
// Запустили задачу "Мигалка", чтобы понимать, что контроллер еще жив и диспетчер работает. 
if(pdTRUE != xTaskCreate(vBlinker,"Blinker", 	configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL)) ERROR_ACTION(TASK_NOT_CREATE,0);
 
// Запустили задачу посылалку. Именно она шлет команды моторам
if(pdTRUE != xTaskCreate(Sender,"Sender", 	configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL)) ERROR_ACTION(TASK_NOT_CREATE,0);
 
// Запустили диспетчер. Дальше оно едет само. 
vTaskStartScheduler();
return 0;
}
 
// Это хуки на разные косяки в работе ОС, Если случается лажа, то прога вылетает сюда. Пихаем в эти функции отладочную ебанину и понимаем, что у нас происходит. 
 
void vApplicationIdleHook( void )
{
}
 
 
 
 
void vApplicationMallocFailedHook( void )
{
    for( ;; );
}
 
 
void vApplicationStackOverflowHook( xTaskHandle pxTask, signed char *pcTaskName )
{
    ( void ) pcTaskName;
    ( void ) pxTask;
    for( ;; );
}
 
 
void vApplicationTickHook( void )
{
}

Ну и архив со всем проектом

З.Ы.
Наверняка будут вопросы, а что это за херня откуда я выдрал этот кусок кода. Да что то вроде вендингового автомата по торговле всякой шнягой. Только более замороченная чем стандартная будка с крутящимися пружинками.

16 thoughts on “FreeRTOS. Пример”

    1. А хз, в бице тот же GCC, что и в остальных средах. По идее создать исходники может под любой эклипсовый проект, а потом пересунуть их в биц. Другое дело, что нахер нужен этот куб, создающий невообразимую кашу.

      1. Самое страшное, что в кубе появился свой таймер и delay. Раньше FreeRTOS + stl можно было не боясь запускать на любую разумную частоту квантования, 100 — 10 000 кГц обычно работало. А теперь этот гребаный таймер может вызвать xPortPendSVHandler до нашего запуска шедулера, они конечно впендюрили туда костыль по проверке запущена FreeRT или нет, но теперь проверка происходит каждый тик. Ни у квантование теперь только 1000 Гц

        1. Потому я всегда и говорю — хочешь писать код, пиши его сам. А иначе получишь неведомую ебанину которая не пойми как работает. Куб пригоден, в лучшем случае. сгенерить какую нибудь херь и в ней поглядеть на инициализацию, чтобы ее потом копипастом себе утащить.

  1. маленькая придирка. третий и четвёртый моторчик вряд ли пойдут.
    в tStepper Motor[]… у них общий io_EN2 со вторым мотором. В архиве проекта так же…

    1. Так это затычки. Они просто копипастой сделаны и пока заложены на будущее. На них даже обработчики не написаны.

    1. Это в ближайших планах. Но для этого надо допилить туда микротаймер, т.к. размера выдержек фриртос не хватает для нормального управления разгоном. Слишком дискретно управляется скорость. 1 еще более менее быстро, а 2 уже ощутимо медленней, а 3 так вообще еле плетется. Надо делать отдельный тикатель на аппаратном таймере.

  2. А как твои ощущения от TB6600? Тут скоро понадобится что-то в этом роде. Как раз начал лениво раздумывать ваять самому или взять что-то готовое.

    1. Эм… а какие у меня должны быть ощущения? Он крутит, не греется, но нагрузка невелика. Стоит копье. Ваять готовое смысла нет при таких ценах.

        1. Делая его самостоятельно ты гарантированно сделаешь дороже чем оно стоит. Это имеет смысл если по другому никак (например не вписываешься в габариты)

  3. Aneg, это весьма бюджетное решение для управления шаговиками. Самодел с аналогичными характеристиками даже по комплектующим будет как минимум не дешевле. А ещё платы, сборка-настройка, отладка с бегом по граблям. Поэтому в данном случае проще купить и пользоваться.

  4. «Наткнувшись на переполненную очередь умная функция отправки отдаст управление диспетчеру и вернется через тик, попробовав снова.»

    Вернется только если в очереди появится свободное место, иначе через 4 мсек вернет pdFALSE.

Добавить комментарий

Ваш e-mail не будет опубликован.

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.