Продолжение поста "Алгоритм Брезенхэма в приложениях реального времени".
Напомним, что речь идет написании программы вывода для лазерных сканаторов
В моем случае лазерный сканатор используется на установке так называемой лазерной стереолитографии, где рисование идет лучом ультрафиолетового лазера по поверхности жидкого фотополимера. Глазом ультрафиолет увидеть нельзя, но можно увидеть гармоники (излучение относительно малой мощности в видимом диапазоне, которое генерируется в лазере на неосновной для него длине волны) а так же флюоресценцию от фотополимера или от простой белой мелованной бумаги.
Тут на снимке — мелованная бумага, слегка пропитавшаяся фотополимером, на покровном стекле нанесен слой фотополимера толщиной примерно 1 мм, на котором рисуется, как можно заметить, любимое на Хабре слово. Слева — «точка отстоя», куда луч уходит после рисования. След между рисунком и точкой отстоя — это холостой переброс луча, который мы видим только за счет значительного послесвечения флюоресценции бумаги. В терминах обсуждавшегося ранее алгоритма этот переброс можно представить как последовательное выполнение
Никаких «зигзагов» или «петель» у хорошего сканатора мы не увидим. По крайней мере — глазом.
Хотя если рядом будет источник электро-магнитной помехи типа мобильника может получится вот такое
Без мелованной бумаги мы никаких холостых перебросов не увидим
Вернемся теперь к нашему алгоритму. В комментах к первой части никто не догадался, что же мы увидим в результате реализации предложенной реализации алгоритма Брезенхэма.
А увидим мы вот такую штуку
Слева — то, что мы увидим (это фотошоп, заливать в контроллер неправильную программу совесть не позволила), справа — то, что ожидали увидеть.
Короткие яркие чёрточки говорят о том, что скорость сканирования иногда уменьшается. Как же она может уменьшаться, если у нас в контроллере нет никакой многозадачности и вообще операционной системы? Очень просто — у нас есть прерывания! Даже если контроллер не нагружен никаким другими функциями, кроме выдачи на ЦАПы, у нас всё равно остаются прерывания, связанные с коммуникацией с управляющим компьютером. Например, через UART (COM-порт) или Ethernet. Вроде бы не так много времени занимают прерывания и вроде бы не так часто мы делаем опрос. Чёрточки могут быть и короче, превращаясь в точки, но полностью от них избавится при такой реализации задержки невозможно
Часто задержки в программировании микроконтроллеров делают на простом счетчике
Дёшево исердито неправильно. В случае прерывания такая реализация задержки никогда не узнает о том, что программа «уходила» в другое место и требуемое время прошло. Идеологически более правильная задержка использует аппаратный счетчик таймера
В этом случае функция задержки автоматически узнает о произошедшем прерывании, хотя и не гарантирует абсолютную точность «до такта» процессора. Если программа основное время проводит в задержках этим можно было бы и ограничится, однако для случая быстрых перемещений использование аппаратного таймера должно быть сквозным
Заметим, что в этом варианте автоматически учитывается как разные времена для вывода на ЦАП для одного канала или двух каналов, так и разброс времени вывода, если используется вывод до получения готовности.
В данной реализации возможные прерывания учитываются внутри функции Line(), вне этой функции эффект влияния прерываний на длительность выполнения может по-прежнему проявляться. Надеюсь, что читателю не составит труда догадаться, как следует поступить в общем случае.
Ну и напоследок — если вы нахомутали с программой, то можете увидеть самые неожиданные загогулины вместо ожидаемых тестовых круга и квадрата.
В этом случае «всё почти работало», только появлялись непонятные зубчики. Как оказалось, одна строка y += sy; была заменена на x += sx;. Между прочим, из-за случайного задетого тачпада на ноутбуке.
Напомним, что речь идет написании программы вывода для лазерных сканаторов
В моем случае лазерный сканатор используется на установке так называемой лазерной стереолитографии, где рисование идет лучом ультрафиолетового лазера по поверхности жидкого фотополимера. Глазом ультрафиолет увидеть нельзя, но можно увидеть гармоники (излучение относительно малой мощности в видимом диапазоне, которое генерируется в лазере на неосновной для него длине волны) а так же флюоресценцию от фотополимера или от простой белой мелованной бумаги.
Тут на снимке — мелованная бумага, слегка пропитавшаяся фотополимером, на покровном стекле нанесен слой фотополимера толщиной примерно 1 мм, на котором рисуется, как можно заметить, любимое на Хабре слово. Слева — «точка отстоя», куда луч уходит после рисования. След между рисунком и точкой отстоя — это холостой переброс луча, который мы видим только за счет значительного послесвечения флюоресценции бумаги. В терминах обсуждавшегося ранее алгоритма этот переброс можно представить как последовательное выполнение
putXY(x,y, wait); // например, x=32000, y=32000
putXY(x0,y0, wait); // yнапример, x0=100, y0=100 - точка отстоя
Никаких «зигзагов» или «петель» у хорошего сканатора мы не увидим. По крайней мере — глазом.
Хотя если рядом будет источник электро-магнитной помехи типа мобильника может получится вот такое
Без мелованной бумаги мы никаких холостых перебросов не увидим
Вернемся теперь к нашему алгоритму. В комментах к первой части никто не догадался, что же мы увидим в результате реализации предложенной реализации алгоритма Брезенхэма.
А увидим мы вот такую штуку
Слева — то, что мы увидим (это фотошоп, заливать в контроллер неправильную программу совесть не позволила), справа — то, что ожидали увидеть.
Короткие яркие чёрточки говорят о том, что скорость сканирования иногда уменьшается. Как же она может уменьшаться, если у нас в контроллере нет никакой многозадачности и вообще операционной системы? Очень просто — у нас есть прерывания! Даже если контроллер не нагружен никаким другими функциями, кроме выдачи на ЦАПы, у нас всё равно остаются прерывания, связанные с коммуникацией с управляющим компьютером. Например, через UART (COM-порт) или Ethernet. Вроде бы не так много времени занимают прерывания и вроде бы не так часто мы делаем опрос. Чёрточки могут быть и короче, превращаясь в точки, но полностью от них избавится при такой реализации задержки невозможно
void putpixel(int x, int y, int wait))
{ outPortX(x); // установить на ЦАП X значение x
outPortY(y); // установить на ЦАП Y значение y
delay(wait); // задержка wait единиц таймера
}
Часто задержки в программировании микроконтроллеров делают на простом счетчике
void delay(int wait) // задержка wait единиц "таймера"
{ int i;
for(i=0;i<wait; i++);
}
Дёшево и
void delay(int wait)
{ unsigned int t0;
t0 = /LPC_TIM1->TC
//LPC_TIM1->TC - указатель на адрес аппаратного счетчика таймера й в контроллерах LPC 17xx
while(LPC_TIM1->TC - t0 < wait);
}
В этом случае функция задержки автоматически узнает о произошедшем прерывании, хотя и не гарантирует абсолютную точность «до такта» процессора. Если программа основное время проводит в задержках этим можно было бы и ограничится, однако для случая быстрых перемещений использование аппаратного таймера должно быть сквозным
/* real time Bresenham alg. implementation */
void Line(int x1, int y1, int x2, int y2, int wait)
{ int x1,y1,dx,dy,sx,sy,d,d1,d2;
unsigned int t0 = LPC_TIM1->TC;
int i, x,y;
if( x2 >= x1)
{ dx = x2 - x1;
sx = 1;
} else {
dx = x1 - x2;
sx = -1;
}
if( y2 >= y1)
{ dy = y2 - y1;
sy = 1;
} else {
dy = y1 - y2;
sy = -1;
}
/****************************/
if(dy <= dx)
{ d = (dy << 1) - dx;
d1 = dy << 1;
d2 = (dy - dx) << 1;
for(x=x1+sx,y=y1,i=1; i <= dx ; i++,x+=sx)
{ if(d > 0)
{ d += d2;
y += sy;
putXY(x,y,wait,&t0);
} else {
d += d1;
putX(x, wait, &t0);
}
}
} else {
d = (dx << 1) - dy;
d1 = dx << 1;
d2 = (dx - dy) << 1;
for(x=x1,y=y1+sy,i=1;i <= dy ; i++,y += sy)
{ if(d > 0)
{ d += d2;
x += sx;
putXY(x,y,wait, &t0);
} else {
d += d1;
putY(y, wait, &t0);
}
}
} /* endif(dy <=dx) */
}
void putXY(int x, int y, int wait, unsigned int *pT0)
{ outPortXY(x,y); // установить на ЦАПах X и Y значения x и y
// в простейшем случае #define outPortXY(x,y) outPortX(x); outPortY(y);
/* задержка wait единиц таймера */
while(LPC_TIM1->TC - *pT0 < wait);
*pT0 += wait;
}
void putX(int x, int wait, unsigned int *pT0)
{ outPortX(x); // установить на ЦАП X значение x
/* задержка wait единиц таймера */
while(LPC_TIM1->TC - *pT0 < wait);
*pT0 += wait;
}
void putY(int y, int wait, unsigned int *pT0)
{ outPortY(y); // установить на ЦАП Y значение y
/* задержка wait единиц таймера */
while(LPC_TIM1->TC - *pT0 < wait);
*pT0 += wait;
}
Заметим, что в этом варианте автоматически учитывается как разные времена для вывода на ЦАП для одного канала или двух каналов, так и разброс времени вывода, если используется вывод до получения готовности.
В данной реализации возможные прерывания учитываются внутри функции Line(), вне этой функции эффект влияния прерываний на длительность выполнения может по-прежнему проявляться. Надеюсь, что читателю не составит труда догадаться, как следует поступить в общем случае.
Ну и напоследок — если вы нахомутали с программой, то можете увидеть самые неожиданные загогулины вместо ожидаемых тестовых круга и квадрата.
В этом случае «всё почти работало», только появлялись непонятные зубчики. Как оказалось, одна строка y += sy; была заменена на x += sx;. Между прочим, из-за случайного задетого тачпада на ноутбуке.