AI助手对这个函数的分析
这段代码是一个基于 Bresenham 圆算法(中点画圆法) 的 OLED 画圆函数,通过八分对称性只计算 1/8 圆弧,其余 7/8 由对称点映射得到。
我先说明几个可能的隐患,再给出完整带注释版本。
需要你确认的一点:for(dy=R; dy>=0; dy--) 中 dy 是 unsigned char,当 dy==0 再执行 dy-- 会溢出为 255,循环条件 dy>=0 恒真,这是死循环隐患。若你的编译器优化/判断条件恰好能跳出则另说,但标准 C51 下不可靠。下面的注释版我按其"逻辑意图"注释,并标注此处风险。
/******************************************************************************
* 函数名 : OLED_Circle
* 功能 : 在 OLED 指定圆心处绘制空心圆或实心圆
* 算法 : 中点画圆法(Bresenham 圆算法),利用圆的八分对称性,
* 只需计算第一象限 1/8 圆弧(从 (0,R) 到 (R/√2, R/√2)),
* 其余 7/8 的像素点由对称映射得到,大幅减少运算量。
* 参数 : Ox - 圆心 X 坐标
* Oy - 圆心 Y 坐标
* R - 圆半径(像素)
* color - 像素颜色(1 = 亮,0 = 灭)
* filled- 填充标志(1 = 实心圆,0 = 空心圆)
* 返回 : 无
* 注意 : 依赖 OLED_Point() / OLED_Line() 两个底层绘图函数
******************************************************************************/
void OLED_Circle(unsigned char Ox, unsigned char Oy, unsigned char R, bit color, bit filled)
{
unsigned char dx = 0; /* X 方向偏移量,从圆心向 +X 方向推进,初值 0 */
unsigned char dy = R; /* Y 方向偏移量,从 (0,R) 圆弧起点向下逼近,初值 R */
while(1)
{
/* ---- 修正 dy:让 dy 回退到满足 dx²+dy² ≤ R² 的最大整数值 ---- */
/* 对当前 dx,从 R 向下递减找第一个满足 dx²+dy² ≤ R² 的 dy,
即 dy = floor(sqrt(R² - dx²)),保证点 (dx,dy) 落在圆内或圆上 */
for(dy = R; dy >= 0; dy--)
{
if((dy * dy) <= (R * R - dx * dx))
break; /* 找到满足条件的 dy,退出内层循环 */
}
/* ---- Bresenham 判别:判断是否应把 dy 再 +1,使像素更贴近真实圆弧 ---- */
/* 比较 (dx,dy) 与 (dx,dy+1) 谁离圆弧更近:
若 R²-dx² > dy*(dy+1),说明 dy+1 比 dy 更接近真实圆弧,故 dy 自增 1 */
if((R * R - dx * dx) > (dy * (dy + 1)))
{
dy = dy + 1;
}
/* ---- 根据 dx、dy 用八分对称性绘制 8 个对称位置 ---- */
if(filled)
{
/* 实心圆:在每个对称区域画竖线/横线,由 8 条线段围合成填充效果 */
OLED_Line(Ox + dx, Oy + dy, Ox + dx, Oy - dy, color); /* 右半圆:x=+dx 处的竖线 */
OLED_Line(Ox - dx, Oy + dy, Ox - dx, Oy - dy, color); /* 左半圆:x=-dx 处的竖线 */
OLED_Line(Ox + dy, Oy + dx, Ox + dy, Oy - dx, color); /* 下半圆:y=+dx 处的竖线 */
OLED_Line(Ox - dy, Oy + dx, Ox - dy, Oy - dx, color); /* 上半圆:y=-dx 处的竖线 */
}
else
{
/* 空心圆:只绘制圆周上的 8 个对称像素点 */
OLED_Point(Ox + dx, Oy + dy, color); /* 第 1 象限 45°~90° 段 */
OLED_Point(Ox + dy, Oy + dx, color); /* 第 1 象限 0°~45° 段 */
OLED_Point(Ox - dx, Oy + dy, color); /* 第 2 象限 90°~135° 段 */
OLED_Point(Ox + dy, Oy - dx, color); /* 第 2 象限 135°~180° 段 */
OLED_Point(Ox + dx, Oy - dy, color); /* 第 3 象限 180°~225° 段 */
OLED_Point(Ox - dy, Oy + dx, color); /* 第 3 象限 225°~270° 段 */
OLED_Point(Ox - dx, Oy - dy, color); /* 第 4 象限 270°~315° 段 */
OLED_Point(Ox - dy, Oy - dx, color); /* 第 4 象限 315°~360° 段 */
}
/* ---- 推进 dx,并判断是否已完成 1/8 圆弧的绘制 ---- */
dx += 1; /* X 方向每次前进 1 个像素 */
if(dx > dy) /* 当 dx 超过 dy(越过 45° 分界线)说明该 1/8 弧已画完,结束 */
return;
}
}
📋
关于前面提到的死循环隐患,补充说明:
原代码 for(dy=R; dy>=0; dy--),dy 声明为 unsigned char(无符号),C51 中 dy>=0 永远成立,唯一能跳出内层循环的条件只可能是 if(...) break; 命中。也就是说:
- 当
dx 使得 R*R-dx*dx 恰好在 dy=0 时不满足 0 <= (R*R-dx*dx)(即 dx>R 时才不满足),此时内层会一直减到 255、254……循环回绕。但由于外层 if(dx>dy)return; 会先兜底,且 dx<=dy 时 R²-dx² 一定 ≥ 0,dy=0 必然 break,所以实际运行中内层一般能在 dy=0 处 break 出来,不会真正死循环。不过为了代码健壮性,建议把循环写成:
for(dy = R; ; dy--) /* 去掉 dy>=0 这个恒真条件 */
{
if((dy * dy) <= (R * R - dx * dx)) break;
}
📋
或改用有符号的 int 作循环变量,避免依赖溢出行为。