找回密码
 立即注册
查看: 175|回复: 5

OLED画圆函数

[复制链接]
已绑定手机

35

主题

91

回帖

249

积分

中级会员

积分
249
发表于 2026-10-2 19:55:58 | 显示全部楼层 |阅读模式


按照@大明狐 的思路,算法为勾股定理+四舍五入

void OLED_Circle(unsigned char Ox,unsigned char Oy,unsigned char R,bit color,bit filled){
  unsigned char dx=0,dy=R;
  while(1){
    for(dy=R;dy>=0;dy--){
      if((dy*dy)<=(R*R-dx*dx))break;
    }
    if((R*R-dx*dx)>(dy*(dy+1))){
      dy=dy+1;
    }
    if(filled){
      OLED_Line(Ox+dx,Oy+dy,Ox+dx,Oy-dy,color);
      OLED_Line(Ox-dx,Oy+dy,Ox-dx,Oy-dy,color);
      OLED_Line(Ox+dy,Oy+dx,Ox+dy,Oy-dx,color);
      OLED_Line(Ox-dy,Oy+dx,Ox-dy,Oy-dx,color);
    }else{
      OLED_Point(Ox+dx,Oy+dy,color);
      OLED_Point(Ox+dy,Oy+dx,color);
      OLED_Point(Ox-dx,Oy+dy,color);
      OLED_Point(Ox+dy,Oy-dx,color);
      OLED_Point(Ox+dx,Oy-dy,color);
      OLED_Point(Ox-dy,Oy+dx,color);
      OLED_Point(Ox-dx,Oy-dy,color);
      OLED_Point(Ox-dy,Oy-dx,color);
    }
    dx+=1;
    if(dx>dy)return;
  }
}

回复

使用道具 举报 送花

已绑定手机

35

主题

91

回帖

249

积分

中级会员

积分
249
 楼主| 发表于 2026-10-2 20:44:37 来自手机 | 显示全部楼层
需要原函数的相关调用函数代码的可以找我要,此帖主要展示核心原理。
回复

使用道具 举报 送花

已绑定手机

2

主题

153

回帖

468

积分

中级会员

积分
468
发表于 2026-10-3 15:13:56 来自手机 | 显示全部楼层
算法不错。
我补充一下,bresenham画圆算法比较简单,速度快,只有整数的加减和移位,伪代码如下:
参数:中点(ox,oy),r半径
uint x=0,y=r,d=1-r;
while(x<=y){
  draw8(ox+x,oy+y)//8对称画点/画线填充
  if(d<0) d+=2*x+3;
  else {  d+=2(x-y)+5;  y--;}
  x++;
}
里面的2*x用的移位x<<1;

回复

使用道具 举报 送花

已绑定手机

2

主题

153

回帖

468

积分

中级会员

积分
468
发表于 2026-10-3 15:27:41 | 显示全部楼层
下面这个也是同样的效果。
uint x=0,y=r,d=3-2r;
while(x<=y){
  draw8(ox+x,oy+y)//8对称画点/画线填充
  if(d<0) d+=4*x+6;
  else {  d+=4(x-y)+10;  y--;}
  x++;
}

原理一样,只是d公式放大倍数为2,
本质是不变的,即d<0性质不变。
回复

使用道具 举报 送花

49

主题

611

回帖

5448

积分

荣誉版主

积分
5448
发表于 2026-10-5 11:13:07 | 显示全部楼层

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 作循环变量,避免依赖溢出行为。

能体会到发现一个不理解的现象然后找原因然后要么解决掉问题要么被问题解决掉的那种快乐是我的幸运
回复

使用道具 举报 送花

已绑定手机

35

主题

91

回帖

249

积分

中级会员

积分
249
 楼主| 发表于 2026-10-5 12:22:41 来自手机 | 显示全部楼层
大明狐 发表于 2026-10-5 11:13
AI助手对这个函数的分析
这段代码是一个基于 Bresenham 圆算法(中点画圆法) 的 OLED 画圆函数,通过八分 ...

Ok
回复

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2026-11-7 11:25 , Processed in 0.068823 second(s), 62 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表