Алгоритм Брезенхэма. Моя реализация.

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


Для начала небольшой дисклеймер. Род моей деятельности — сраный сисадмин-эникейщик и быдлокодер на Делфи, причем теоретическими познаниями не богат, поскольку институтов не заканчивал :). Эникейщиной зарабатываю на жизнь, Делфями изредка на ништяки. Т.е. типичный провинциальный компьютерщик. МК изучать начал с момента регистрации в сообществе. На С никогда до этого не писал, изучаю по мере необходимости. После Делфи этот процесс конкретно выносит мне мозг, но выбора нет, поскольку разработка на ассемблере объективно сложнее, хоть он мне гораздо ближе и я с ним довольно сносно знаком, а микропаскаль убог. Остается С. И осваивать его приходится с чистого заполненного Делфями листа. На момент написания статьи кнопка «New project» в AVR Studio была нажата четвертый раз в жизни. Поэтому прошу сообщество отнестись к моим потугам с пониманием и органикой в меня не кидать :).

Итак, к делу. Алгоритм Брезенхэма предназначен для построения прямых, заданных координатами (X1,Y1)-(X2,Y2) на растровых устройствах. Если не врут источники, изначально разрабатывался для графопостроителей, но во всех источниках, которые я изучил, описано его применение для вывода графики на экран и только для двух осей. Теория расписана более чем подробно в той же Википедии, цитировать ее тут особого смысла не вижу.

Но мне экран был не нужен, я станочек строить пытаюсь. Поэтому пришлось его адаптировать под свои цели. Сначала он крутился на хост-приложении и был реализован, естественно, на Делфи. Но передавать шаги на контроллер через V-USB — не айс, поскольку медленно и неравномерно, движки работали рывками. Поэтому было решено перенести эти расчеты в контроллер и передавать туда уже координаты. Это было второе нажатие кнопки «New project». Получилось, как графопостроитель мой станок заработал нормально. Концы у окружностей сходились :). Но исходник получился такой, что показать его людям стыдно, да и не только сабж там был, а и V-USB, и управление движками, и все в перемешку. Короче, ужас нечитаемый. Поэтому решил оформить сабж в виде библиотеки и привести код в удобочитаемый вид.

Библиотека поддерживает до 8 осей, на выходе имеем два битовых поля — Step и Direction. Бит Direction, установленный в 0, указывает на уменьшение текущей координаты, установленный в 1 — увеличение. Бит Step указывает на необходимость выполнения шага по данной оси. Короче, стандартный STEP/DIR.

Вот исходник хидера:

#ifndef BRESENHAM_H
#define BRESENHAM_H

#include <stdlib.h>
#include <stdint.h>

#define BresAxisCount		2	//Количество осей

#define BresUseDirection		//Использовать определение направления движения

#define BresPosHolderSize	8	//Размер int переменных для хранения значений 
					//позиции. 8, 16 или 32 бита

#define BresStateIdle			0	//Ожидание нового набора позиций
#define BresStateStepEnable		1	//Разрешено выполнение шага
#define BresStateStepRequest	        2	//Запрос расчета нового шага
#define BresStateLock			3	//Блокировка работы

#if BresAxisCount < 2
	#error "You must use more than 1 axis"
#elif BresAxisCount > 8
	#error "You must use less than 8 axis"
#endif

#ifdef BresUseDirection 				//Назначаем тип BresPosHolderType
	#if BresPosHolderSize == 32				
		typedef int32_t BresPosHolderType;
		typedef int64_t BresErrHolderType;
	#elif BresPosHolderSize == 16
		typedef int16_t BresPosHolderType;
		typedef int32_t BresErrHolderType;
	#elif BresPosHolderSize == 8
		typedef int8_t BresPosHolderType;
		typedef int16_t BresErrHolderType;
	#else
		#error "Incorrect value of BresPosHolderSize"
	#endif
#else
	#if BresPosHolderSize == 32		    // Если не используем направление -
                                                    // используем unsigned
		typedef uint32_t BresPosHolderType;
		typedef int64_t BresErrHolderType;
	#elif BresPosHolderSize == 16
		typedef uint16_t BresPosHolderType;
		typedef int32_t BresErrHolderType;
	#elif BresPosHolderSize == 8
		typedef uint8_t BresPosHolderType;
		typedef int16_t BresErrHolderType;
	#else
		#error "Incorrect value of BresPosHolderSize"
	#endif
#endif

// Полетные данные
struct BresDataStruct
{
	BresPosHolderType	StartPos[BresAxisCount];
	BresPosHolderType	TargetPos[BresAxisCount];
	BresPosHolderType	CurrentPos[BresAxisCount];
	BresPosHolderType	Delta[BresAxisCount];
	BresErrHolderType	E[BresAxisCount];    //Накопитель ошибки

	BresPosHolderType	MaxDelta;
	BresPosHolderType	StepCounter;

	uint8_t			Step;

#ifdef BresUseDirection 		
	uint8_t			Direction;
#endif

	uint8_t			Status;
};

typedef struct BresDataStruct BresData;

#ifdef BresUseDirection		// Callback
	typedef void (*UsersExecuteStep_T)(uint8_t, uint8_t); 
#else
	typedef void (*UsersExecuteStep_T)(uint8_t); 
#endif


// Инициализация полетных данных
extern void BresInit(BresData *aData);

// Установка начальных позиций
extern void BresSetStartPosition(BresData *aData, BresPosHolderType NewStartPos[BresAxisCount]);

// Установка задания
extern int BresSetTargetPosition(BresData *aData, BresPosHolderType NewTargetPos[BresAxisCount]);

// Расчет следующего шага
extern void BresProcessStep(BresData *aData);

// Выполнение рассчитанного шага
extern void BresExecuteStep(BresData *aData, UsersExecuteStep_T UsersExecuteStep);

#endif


Назначение дефайнов:
BresAxisCount — количество обрабатываемых осей, от 2 до 8;
BresUseDirection — если этот дефайн закомментировать, от STEP/DIR останется только STEP. Зачем мне это нужно — объясню ниже;
BresPosHolderSize — разрядность переменных, хранящих координаты. 8, 16 или 32 бита, если работаем без DIR — переменные беззнаковые.

Полетную информацию библиотека хранит в структуре BresDataStruct. Ниже назначение полей.
Массивы для каждой из осей:
StartPos[BresAxisCount] — начальная координата задания;
TargetPos[BresAxisCount] — конечная координата задания;
CurrentPos[BresAxisCount] — текущая координата;
Delta[BresAxisCount] — пробег :)
E[BresAxisCount] — накопитель ошибки. Обязательно signed и с большей разрядностью, чем у полей координат.

Общие для всех осей:
MaxDelta — самый большой пробег;
StepCounter — cчетчик шагов задания;
Step — собственно, результат;
Direction — тоже результат;
Status — состояние (спасибо, кэп!).

Нулевой бит полей Step и Direction соответствует оси с индексом 0.

Биты поля Status:
0 BresStateIdle — закончили выполнение задания (или еще не начинали) и ждем следующего;
1 BresStateStepEnable — следующий шаг обсчитан, разрешено выполнение;
2 BresStateStepRequest — шаг выполнен, запрос на вычисление следующего шага;
3 BresStateLock — блокировка работы. Процедуры ничего делать не будут, пока его не сбросишь.

Ну и сами процедуры:
BresInit(BresData *aData) — обнуляет все и вся;

BresSetStartPosition(BresData *aData, BresPosHolderType NewStartPos[BresAxisCount])
— установка начальной позиции. Текущие координаты устанавливаются в те же значения, что и начальные. Координаты передаются в виде массива. Прерывает выполнение задания. Назначение — ноль таскать по рабочему полю. Ну и еще есть применения. :)

int BresSetTargetPosition(BresData *aData, BresPosHolderType NewTargetPos[BresAxisCount]) — функция устанавливает конечные координаты задания, производит начальные вычисления и поднимает бит BresStateStepRequest в поле статус. При нормальной отработке (не было заблокировано и т.п.) возвращает 1, иначе 0;

BresProcessStep(BresData *aData); — расчет следующего шага. Срабатывает при поднятом бите BresStateStepRequest в поле статус. Считает следующий шаг для всех осей и поднимает бит BresStateStepEnable в поле статус. Если достроили прямую до конца — поднимает бит BresStateIdle;

BresExecuteStep(BresData *aData, UsersExecuteStep_T UsersExecuteStep) — выполнение рассчитанного шага. Срабатывает при поднятом бите BresStateStepEnable в поле статус. В параметрах получает указатель на пользовательскую процедуру, которую вызывает (да, callback. И не надо меня в Java-головости обвинять — в Java никогда не программировал :)))) и передает ей поля Step и Direction. Можно вызывать по таймеру или прерыванию. По завершении поднимает бит BresStateStepRequest в поле статус.

Вот, собственно, и все описание.
Сишник

#include <stdlib.h>
#include <stdint.h>

#include "Bresenham.h"

// Инициализация полетных данных
void BresInit(BresData *aData)
{
	uint8_t i;
	for(i=0; i<BresAxisCount; i++)
	{
		(*aData).StartPos[i] = 0;
		(*aData).TargetPos[i] = 0;
		(*aData).CurrentPos[i] = 0;
		(*aData).Delta[i] = 0;
		(*aData).E[i] = 0;
	}

	(*aData).MaxDelta = 0;
	(*aData).Step = 0;

#ifdef BresUseDirection 		
	(*aData).Direction = 0;
#endif

	(*aData).Status = 1 << BresStateIdle;		  
}

// Установка начальных позиций
void BresSetStartPosition(BresData *aData, BresPosHolderType NewStartPos[BresAxisCount])
{
/*	Вызов функции прерывает выполнение текущего задания.
	Если этого не нужно - перед вызовом проверяем Idle бит. 
	Lock тоже игнорируется.  */
   
	uint8_t i;

	(*aData).Status = 1 << BresStateLock; //Блокируем исполнение

	for(i=0; i<BresAxisCount; i++)
	{
		(*aData).StartPos[i] = NewStartPos[i];
		(*aData).CurrentPos[i] = NewStartPos[i];
	}

	(*aData).Status = 1 << BresStateIdle;	//Снимаем блокировку, устанавливаем Idle, 
						//ждем задания
}

// Установка задания
int BresSetTargetPosition(BresData *aData, BresPosHolderType NewTargetPos[BresAxisCount])
{
	uint8_t i;

	if ((~((*aData).Status & (1 << BresStateLock))) && ((*aData).Status & (1 << BresStateIdle)))
	{
		(*aData).Status = 1 << BresStateLock; //Блокируем исполнение и сбрасываем Idle

		(*aData).MaxDelta = 0;
#ifdef BresUseDirection
		(*aData).Direction = 0;
#endif
		
		for(i=0; i<BresAxisCount; i++)
		{
			(*aData).TargetPos[i] = NewTargetPos[i];
			(*aData).StartPos[i] = (*aData).CurrentPos[i];
			(*aData).Delta[i] = abs((*aData).TargetPos[i] - (*aData).StartPos[i]);

			if ((*aData).Delta[i] > (*aData).MaxDelta)	//Считаем дельты
			{
				(*aData).MaxDelta = (*aData).Delta[i];	//Находим самую большую дельту
			}

#ifdef BresUseDirection
			if ((*aData).TargetPos[i] < (*aData).StartPos[i]) //Устанавливаем биты Direction
			{
				(*aData).Direction = ((*aData).Direction >> 1) & 0x7F;
			} else
			{
				(*aData).Direction = ((*aData).Direction >> 1) | 0x80;
			}
#endif
		}

		for(i=0; i<BresAxisCount; i++)
		{
			(*aData).E[i] = 2*(*aData).Delta[i] - (*aData).MaxDelta;    //Определяем 
                                                                                    //начальные значения Е
		}


#ifdef BresUseDirection
		(*aData).Direction = (*aData).Direction >> (8 - BresAxisCount); //Доталкиваем биты
                                                                                //Direction до нужной позиции
#endif

		(*aData).StepCounter = 0;	//Сбрасываем счетчик шагов

		(*aData).Status = 1 << BresStateStepRequest;	//Снимаем блокировку и запрашиваем первый шаг

		return(1);
	} else
	{
		return(0);
	}
}

// Расчет следующего шага
void BresProcessStep(BresData *aData)
{
	uint8_t i;

	if ((~((*aData).Status & (1 << BresStateLock))) && ((*aData).Status & (1 << BresStateStepRequest)))
	{
		(*aData).Status |= 1 << BresStateLock;	//Блокируем
		if ((*aData).StepCounter == (*aData).MaxDelta)
		{
			(*aData).Status |= 1 << BresStateIdle;	//Задание выпонено - ставим Idle
		} else
		{
			(*aData).Step = 0;

			for(i=0; i<BresAxisCount; i++)
			{
		    	if ((*aData).E[i] >= 0)
		    	{
					(*aData).Step = ((*aData).Step >> 1) | 0x80;	//Делаем шаг
					(*aData).E[i] += 2*((*aData).Delta[i]-(*aData).MaxDelta);
	    		} else
	    		{
					(*aData).Step = ((*aData).Step >> 1) & 0x7F;	//Не делаем шаг
					(*aData).E[i] += 2*(*aData).Delta[i];
		    	}
			}
			(*aData).Step = (*aData).Step >> (8 - BresAxisCount);	//Доталкиваем биты Step
                                                                                //до нужной позиции


			(*aData).Status |= 1 << BresStateStepEnable;		//Шаг подготовлен - разрешаем выполнение
		}

		(*aData).Status &= ~(1 << BresStateLock | 1<< BresStateStepRequest);	//Сбрасываем блокировку и запрос шага
	} 
}

// Выполнение рассчитанного шага
void BresExecuteStep(BresData *aData, UsersExecuteStep_T UsersExecuteStep)
{
	uint8_t i;

	if ((~((*aData).Status & (1 << BresStateLock))) && ((*aData).Status & (1 << BresStateStepEnable)))
	{
		(*aData).Status |= 1 << BresStateLock;	//Блокируем

// Callback пользовательской функции обработки Step/Direction
#ifdef BresUseDirection
		UsersExecuteStep((*aData).Step, (*aData).Direction);
#else
		UsersExecuteStep((*aData).Step);
#endif

		for(i=0; i<BresAxisCount; i++)
		{
			if ((*aData).Step & (1 << i))
			{
#ifdef BresUseDirection
				if ((*aData).Direction & (1 << i))
				{
					(*aData).CurrentPos[i]++;
				} else
				{
					(*aData).CurrentPos[i]--;
				}
#else
				(*aData).CurrentPos[i]++;
#endif
			}
		}

		(*aData).StepCounter++;

		(*aData).Status |= 1 << BresStateStepRequest;		//Шаг выполнен - запрашиваем следующий
		(*aData).Status &= ~(1 << BresStateLock | 1<< BresStateStepEnable);	//Сбрасываем блокировку и запрос шага
	}
}

и программа, в которой я его отлаживал
#include "Bresenham.h"

volatile static BresData aBrData;

void MyExecStep(uint8_t aStep, uint8_t aDir)
{
   //Do something...
	aBrData.Status |= 0x80;
	aBrData.Status &= 0x7F;
}

int main(void)
{
	BresInit(&aBrData);

	BresPosHolderType aArr[2] = {0,0};
	BresPosHolderType bArr[2] = {10,3};

	while(1)
	{
	    if (aBrData.Status == 1)
		{
			BresSetStartPosition(&aBrData, aArr);
			BresSetTargetPosition(&aBrData, bArr);
		}

		BresProcessStep(&aBrData);

		BresExecuteStep(&aBrData, MyExecStep);
	}
}


Теперь расскажу зачем мне понадобилось отключение Direction. Дело в том, что сабж применим в задачах, где нужно более-менее равномерно распределить некое количество импульсов относительно другого количества импульсов. Например, симмисторный регулятор мощности с пропуском полупериодов. BresExecuteStep вешаем на прерывание, вызываемое детектором перехода синусоиды через ноль. Выбираем две оси, отключаем Direction, размерность выбираем 8 бит. Пишем код
int main(void)
{
	BresInit(&aBrData);

	BresPosHolderType aArr[2] = {0,0}; // Direction отключен, поэтому постоянно нужно сбрасывать
                                           // начальную позицию в ноль.
	BresPosHolderType bArr[2] = {100,30}; // 30 - значение мощности в процентах

	while(1)
	{
	    if (aBrData.Status == 1) //Idle
		{
			BresSetStartPosition(&aBrData, aArr);
			BresSetTargetPosition(&aBrData, bArr);
		}

		BresProcessStep(&aBrData);
	}
}

в пользовательской процедуре обработки отправляем соответствующий бит Step на пин, подключенный ко входу симмисторного ключа и вуаля — на выход пройдет 30 равномерно распределенных полупериодов из 100.

Гонял код под дебагером от AVR Studio. Вроде работает.

Исходники во вложении.

Вот и все. Поскольку в С я полный нуб, то замечания не только приветствуются, а настоятельно реквестируются.
  • +6
  • 01 декабря 2012, 20:06
  • B-Screw
  • 1
Файлы в топике: Bresenham.zip

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

RSS свернуть / развернуть
Поскольку в С я полный нуб, то замечания не только приветствуются
Я бы разименование структур делал бы следующим образом

aData->StartPos[i] = 0;

Может я ошибаюсь, поправьте если что, но какой смысл определять тип таким образом?
typedef struct BresDataStruct BresData;
можно просто
BresDataStruct BresData;
0
В Си имя структуры != имя типа
0
какой смысл определять тип таким образом?
typedef struct BresDataStruct BresData;
Это не переменная, это новое имя типа.

Далее я делаю следующее:
volatile static BresData aBrData;

на
volatile static BresDataStruct aBrData;
компилятор ругается, требует
volatile static struct BresDataStruct aBrData;

Объявление, начинающееся с ключевого слова typedef, вводит новое имя для типа. Т.е. я ввожу синоним для «struct BresDataStruct» и когда я указываю «BresData», компилятор подразумевает «struct BresDataStruct», что в дальнейшем позволяет мне не указывать ключевое слово «struct»
при назначении переменных данного типа.
0
Можно не объявлять typedef, если описать структуру так:
struct BresDataStruct
{
    type1 data1;
    type2 data2;
} BresData;

Тогда объявление «BresDataStruct aBrData;» будет правильным.
0
Упс, конечно же «BresData aBrData;»
0
Тогда BresData — это не тип, а переменная. Упс…
0
Да уж, надо суметь так протупить. Нужно обьявить typedef вместе с объявлением самой структуры:
typedef struct BresDataStruct
{ 
    type1 data1;
    type2 data2;
} BresData; 

Вот теперь все ;)
0
Если уж совсем занудствовать, то можно и вовсе без BresDataStruct:

typedef struct
{ 
    type1 data1;
    type2 data2;
} BresData; 

Имя структуры нужно только если внутри нее есть указатели или ссылки на нее же.
0
Подробно не вникал, но чисто внешне весьма акуратно написано (даже без скидок на нубство). Так держать.
0
  • avatar
  • evsi
  • 01 декабря 2012, 23:31
Спасибо за оценку.
Правила написания читаемого кода для всех языков приблизительно одинаковы. Пришлось ознакомиться, чтобы в собственной писанине не путаться :).
0
Код действительно написан хорошо, зря Вы прибедняетесь по поводу знания С.

Ели хотите критики, то есть несколько замечаний по стилю, исключительно рекомендательного характера.

— как правильно заметил Satellite , для разадресации указателей на структуру удобнее использовать "->"
— extern перед предекларацией функций обычно не пишут
— объявить тип со структурой можно сразу typedef struct {... } BresData;
— для обнуления полей структуры в BresInit() можно воспользоваться memset();
0
Спасибо за рекомендации.
— как правильно заметил Satellite, для разадресации указателей на структуру удобнее использовать "->"
Уже заметил. После его замечания нашел несколько примеров, где это реализовано. Бум использовать, а то от скобок в глазах пестрит.
— extern перед предекларацией функций обычно не пишут
Буду знать. Просто первый хидер, который мне на глаза попал — библиотека для ds18x20, так там extern используют.
— объявить тип со структурой можно сразу typedef struct {… } BresData;
— для обнуления полей структуры в BresInit() можно воспользоваться memset();
Тоже учту. Правда с memset() есть маленькое «но» — не люблю я с памятью напрямую работать, предпочитаю более высокоуровневый подход. Хотя это и менее эффективно. «Неумение программировать компенсируется мегабайтами и мегагерцами» :)

зря Вы прибедняетесь по поводу знания С.
Для того, чтобы просто написать программу на каком либо языке, знать его не обязательно. Достаточно иметь базовые знания о программировании вообще, справочник по языку и кучку готовых исходников для примеров, там подсмотрел одно, там другое — конструктор «Лего».
Под знанием языка я понимаю знание его нюансов и Ваши замечания — тому подтверждение. «Дьявол кроется в деталях». Без этих знаний эффективно программировать невозможно. Так что я его еще не знаю. Пару-тройку проектов запущу, тогда знать более-менее буду.
+1
Просто первый хидер, который мне на глаза попал — библиотека для ds18x20, так там extern используют.
Спецификатор extern делает функцию «видимой» во всех исходниках проекта. Я так из asm исходников вытаскиваю реализации в исходники на Си. Где необходима реализация именно на asm, ессно. =)
А вообще, хоть я и далеко не эксперт, но присоединюсь к словам
Код действительно написан хорошо, зря Вы прибедняетесь по поводу знания С.
0
Спецификатор extern делает функцию «видимой» во всех исходниках проекта.
Нет, он только говорит, что сама функция (или переменная) определены где-то в другом месте.
0
Так я вроде об этом и говорил. Возможно использование из любого файла проекта.
Да только я неправ принципиально с самого начала, а e_mc2 — прав:
extern перед предекларацией функций обычно не пишут
ибо функции по умолчанию — extern. Так что тут речь может идти только о переменных, это я не в ту степь поскакал.
Где швабра, хоть полы тут протру, раз уж зашел? =))
0
Так я вроде об этом и говорил. Возможно использование из любого файла проекта.
Это не совсем так, потому я и уточнил. Ее можно использовать везде, где доступна декларация (то есть известна сигнатура функции). А это совершенно не обязательно все файлы проекта. То, что extern для функций игнорируется это, как бы ортогонально. Его вполне имеет смысл писать, например, из соображений читабельности. Скажем, объявления без extern использовать для предобъявления если оно нужно для использования внутри файла, но в самом файле присутствует и тело функции. А объявления с extern — если функции в файле нет. С другой стороны, если объявления собираются в заголовках, то такой подход теряет смысл.

Уточню: я не имею в виду, что так надо поступать или это рекомендуемая практика. Я всего лишь уточняю, что использование extern для функций вполне может иметь практический смысл и выбор использовать его или не использовать — дело вкуса разработчика и/или особенностей конкретного проекта.
0
Так я вроде об этом и говорил. Возможно использование из любого файла проекта.
Не совсем. Все символы, кроме static, и так можно использовать из любого файла проекта. extern говорит компилятору о том, что этот символ можно использовать как описано, но его самого в исходнике нет.
0
Все символы, кроме static, и так можно использовать из любого файла проекта.
С предекларацией, разумеется?
0
Ну да. Это же С, он компилирует только один файл. Все используемое должно быть объявлено в нем.
0
С предекларацией, разумеется?

Можно и без, но так лучше никогда не делать.

Предекларация в С (напрямую) не влияет на то, будет ли экспортироваться функция из модуля. Все не static функции доступны извне.

Предекларация нужна, чтобы при использовании функции из другого модуля, чтобы компилятор знал сигнатуру функции (мог проверить кол-во параметров, их типы). Некоторые компиляторы позволяют вызывать функции из другого модуля без предекларации (при этом компилятор генерит worning). Некоторые компиляторы считают отсутствие предекларациии ошибкой.
0
Некоторые компиляторы считают отсутствие предекларациии ошибкой.
Ага, вот откуда ноги растут. Поэтому-то я и задал вопрос по поводу предекларации. Я всегда считал такой «финт ушами» недопустимым, и теперь ясно почему.
0
Возможно. Но все вышесказанное относиться именно к экспорту и импорту в рамках С. В других случаях (использование модулей на С из С++, экспорт функций из ASM ) – есть нюансы.
0
Ну implicit declaration вроде тока к функциям применимо, переменные без объявления использовать не дает. К ним, кстати, и extern обязателем, если они в другом модуле.
0
Ну implicit declaration вроде тока к функциям применимо

Да, Вы абсолютно правы. С экспортируемыми переменными – все иначе, я говорил именно о функциях.
0
С экспортируемыми переменными – все иначе

Вернее, с экспортом все тоже (глобальные, не static — все экспортируются). А вот с использованием таких переменных в других модулях – все действительно не так однозначно как с функциями.
0
А вообще, хоть я и далеко не эксперт, но присоединюсь к словам
Код действительно написан хорошо, зря Вы прибедняетесь по поводу знания С.
А вот я не соглашусь. Я соглашусь со словами автора:
Для того, чтобы просто написать программу на каком либо языке, знать его не обязательно. Достаточно иметь базовые знания о программировании вообще, справочник по языку и кучку готовых исходников для примеров, там подсмотрел одно, там другое — конструктор «Лего».
Под знанием языка я понимаю знание его нюансов и Ваши замечания — тому подтверждение. «Дьявол кроется в деталях». Без этих знаний эффективно программировать невозможно. Так что я его еще не знаю. Пару-тройку проектов запущу, тогда знать более-менее буду.
Правила написания читаемого кода для всех языков приблизительно одинаковы. Пришлось ознакомиться, чтобы в собственной писанине не путаться :).
0
А вот я не соглашусь

Не совсем Вас понял – с чем именно Вы не согласны. ИМХО, автор действительно знает С лучше, чем он это описывает «Поскольку в С я полный нуб …».

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

Я здесь тоже полностью согласен с автором.
0
ИМХО, автор действительно знает С лучше, чем он это описывает «Поскольку в С я полный нуб …».
Ну, если он даже -> не знает — то таки нуб. Я тоже в С нуб. И вообще, чтобы в С более-менее разбираться нужно лет 5 активной практики с параллельным изучением минимум. Камней и прочих мелочей не просто много — очень много.
0
Хм, деваете тогда так «нуб»-«не нуб» понятие относительное. Я тоже не знаю С/С++. И когда я слышу от кого-то фразы типа «я в совершенстве знаю С++» — это у меня вызывает улыбку.

Скромность (и объективное понимание «глубины» таких вещей как «программирование на С» ) – это хорошо. Но слишком заниженное представление о свих знаниях – это тоже плохо.

Позволю себе привести один пример из жизни. Один мой хороший знакомый всегда хотел пойти в проф. программирование. У него (по моим оценкам) для этого были все шансы. Но он очень сильно недооценивал свои знания. Имея объективно хорошие знания/опыт, он всегда мыслил типа «я нуб, а там сидят умные специалисты и т.д.». Из-за этого он так и не решился сделать первый шаг – бросить насиженною но скучную работу в банке и, хотя бы, попробовать себя в проф. программировании. Я теперь, уже поздно…

Вот, исходя из вышеизложенного, я и хотел поддержать автора.
0
И когда я слышу от кого-то фразы типа «я в совершенстве знаю С++» — это у меня вызывает улыбку.
Первейший признак начинающего.=) Я тоже считал одно время, что «знаю Си», изучив операторы, директивы, типы данных и прочее первостепенное, относящееся к основам. Чем больше узнаешь, тем меньше уверенность в том, что хоть что-то знаешь.)
+2
С самооценкой у меня все в порядке :). Программирую уже не первый год, и завершенных проектов в эксплуатации пару-тройку имею. Просто на случай, если вылезет уж очень глупый косяк, предупредил, что проект всего четвертый по счету. А за поддержку — спасибо.
0
А на чем обычно программируешь?
0
В основном Delphi. Базы данных под Firebird строю.
0
Унесу-ка я это куда-нибудь… например в алгоритмы.
0
Хозяин — барин. Если этому топику там место, то буду рад.
0
0
Эти реализаций — как кур нерезаных. Еще одна из них. Реализован только базовый расчет для одной оси в пределах одного октанта. Для распределения неплохое решение. Для станка — допиливать нужно.
0
Я смотрю, никто не задал главный вопрос — а зачем Брезенхем??? В контроллере что, нет умножения? Да тут на условных переходах больше тактов потеряешь.
0
Можно подробнее? Лично я методов эффективнее Брезенхэма не знаю.
0
например, интерполяция методом оценочной функции… и проще в реализации, и эффективнее…
0
Кстати, в пределах расчета следующего шага на одну ось всего одна проверка условия.

if ((*aData).E[i] >= 0)
{
      (*aData).Step = ((*aData).Step >> 1) | 0x80;    //Делаем шаг
      (*aData).E[i] += 2*((*aData).Delta[i]-(*aData).MaxDelta);
} else
{
      (*aData).Step = ((*aData).Step >> 1) & 0x7F;    //Не делаем шаг
      (*aData).E[i] += 2*(*aData).Delta[i];
}

Вот и весь Брезенхэм. Все остальное — рюшечки библиотеки. А вот математика — цельночисленная.
0
Перечитав свой же код, я тут грешным делом подумал, а не погорячился ли я вынеся расчет следующего шага в отдельную процедуру. Не так тут много операций, а установка/сброс/проверка флагов съедает дополнительные такты.
0
Напиши inline и пусть у компилятора голова болит, делать ее инлайном или нет.
0
What??? :))) Если бы я знал, что есть inline… Хотя, даже не зная что это, могу утверждать, что к моим сомнениям оно не относится :).
Я имел ввиду метод раздельного вычисления и выполнения шага. Поскольку выполнение шага (выгрузка в порты) вероятнее всего будет вызываться из прерывания или по таймеру, не хотелось перегружать этот вызов расчетом. А сейчас смотрю, что же займет больше времени — расчет следующего шага или установка/сброс/проверка флагов? И на сколько больше? Если выполнение совместить с вычислением в одном вызове, то можно будет вообще убрать флаги BresStateStepEnable и BresStateStepRequest. Нужно будет протрассировать и посмотреть.
0
inline — встраивание кода функции на место ее вызова. Позволяет сэкономить на выполнении вызова (запихивание аргументов, сохранение-восстановление регистров, CALL-RET...) ценой некоторого уеличения скомпилированного кода. Но да, тебе не поможет.
0
Разве не должна быть _inline функция короче краткого? Там же все три вложенных условных оператора… Если я правильно понимаю и речь о BresProcessStep(BresData *aData).
0
Ага, а еще цикл. inline тут не прокатывает.
0
Разве не должна быть _inline функция короче краткого?
Насколько я знаю, это всего лишь рекомендация. При инлайнинге длинных функций просто код будет очень шустро расти в размере, а выигрыш приближаться к нулю.
0
Ну да, рекомендация. Просто компилятор кругом в код навтыкает функцию целиком; одно дело — две строки, и другое — двадцать.
0
Пока еще плохо подумал, но уже кажется что это может быть верным решением.
Т.к. бывают ситуации, когда нужен «предпросмотр» следующего шага.
Также бывает более сложный алгоритм управления двигателем вместо «STEP\DIR». (Например в одной из реализаций микрошагового режима для инертных двигателей).
0
Все эти операции выполняются в MyExecStep, для этого я коллбэк и делал.
Вопрос в перемещении операций расчета шага из BresProcessStep в BresExecuteStep и в отказе от установки/проверки двух флагов BresStateStepRequest и BresStateStepEnable.
0
Спасибо! Удобно сделан интерфейс, плюшки в виде обнаружения ошибок при прекомпиляции, было бы легко использовать для STM…
Как жаль что 2 месяца назад мне пришлось изобретать велосипед для 3х осей.
Не думали о том, как можно было бы реализовать алгоритм распределения, при наличии осей вращения (не знаю как такой алгоритм назвать)?
Я сейчас пытаюсь создать нечто такое для 5ти координат (3-евклидовы, 2-вращения)…
0
  • avatar
  • Azzoo
  • 07 декабря 2012, 06:39
Не думали о том, как можно было бы реализовать алгоритм распределения, при наличии осей вращения
А что тут думать? Чем отличается привод евклидовой оси от привода оси вращения до того момента, как это все будет установлено на станок? Правильно, ничем. Пять шаговиков. На этом уровне все пять осей одинаковы. И системы координат на входе этой библиотеки задаются в шагах ШД. А вращение или поступательное движение мы получим на выходе — зависит от механики станка. А поскольку механика у всех разная, то нужно рассчитать эти координаты используя коэффициенты — сколько шагов нужно выполнить на миллиметр линейного перемещения или на градус вращения.
Лично я для передачи задания с компьютера на контроллер использую примитивный протокол, состоящий из следующих команд:
-установка состояния шпинделя (включен/выключен);
-установка скорости перемещения;
-переход в позицию в шагах ШД (X, Y, Z, A, B) — это для пяти осей;
-еще пара-тройка сервисных функций, типа установки начала координат в текущей позиции и т.п.
Все, весь протокол :).

Исходные данные (на данный момент я использую HPGL и Excellon) в этот протокол преобразовывает хост-программа на компе. Она же и передает их на контроллер.
0
У меня такой же протокол, примерно + обратная связь (завершение комманды, сообщение об ошибке, запрос на следующую комманду при начале выполнения текущей (программный кеш).
Я еще SVG обрабатываю. (кстати на JavaScript прямо в браузере).
Знаете, интересно было бы пообщатся детальнее (с картинками). Если хотите, добавьте меня в скайп (Semchenkov_Alexander). Думаю беседа может быть результативной!
0
А что касаемо коэффициентов, то при наличии осей вращения, они могут быть функцией Sin или Cos… или вообще совершенно нелинейно меняться.
Все дело в том, в каком виде передаются векторы.
0
Если честно, то я просто не вижу конструкции оси вращения с нелинейной зависимостью изменения угла от шага двигателя. В голову лезут только конструкции с линейной зависимостью. В любом случае, допустить такой ляп, как нелинейность в преобразователе, и потом мужественно с ним бороться программно, ИМХО, не айс.
0
Если передавать не векторы, а координаты просто, то для плавного хода при обработке становится много затыков.
Также сильно теряется скорость работы станка.
0
Тут вообще ничего не понял. А что мы передаем в виде нового задания, как не вектор, направленный из текущей точки в точку, заданную координатами (X, Y, Z, A, B). Причем тут плавность и скорость, и где тут возможность их потери при передаче координат, мне лично не понятно. Или Вы предлагаете кривыми Безье задания описывать для плавности хода (использование SVG натолкнуло меня на эту мысль)?
0
Примерно так, то есть при передаче кривой (даже окружности) нужно передавать много мелких координат…
А т.к. скорость UART при реальных режимах работы выбирается не очень высокой (чтобы исключить помехи),
То станок может «засыпать» перед принятием следующей координаты.
Хотя, я это решил все же буферизацией.
Т.е. пока режем, принимаем след. комманду.
0
скорость UART при реальных режимах работы выбирается не очень высокой (чтобы исключить помехи)
RS485, CRC и повторная передача рулят. И скорость можно повыше сделать.
Хотя, я это решил все же буферизацией.
Опять таки, буфер рулит.
Хотя в своем недоделанном я обходился и без CRC и без буферизации и данные гнал через V-USB. Не заметил, чтобы он засыпал. Правда скорость подачи у меня невысокая была.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.