登录
首页 无所不谈
回帖 发帖
正文

主题:STC Ai助手写一个增量式编码器程序 写着写着就撂挑子了

点击:129 回复:6

最近STC官方BBS右下角出现一个弹窗
Ai助手上线
让大家来测试
于是先让写一个RA8835控制的320240程序
再写一个简单的自动运行机器程序
嗖嗖嗖一分钟不到写出来了
然后让写一个增量式编码器正反转计数程序
刚开始简单的还行
然后问我
1. 您使用的是哪一款具体的STC单片机型号?
   例如:STC8H8K64U、STC8G1K08A、STC8A8K64D4、STC32G12K128、STC15W4K32S4 等
   不同型号的编码器接口(正交解码/外部中断/定时器捕获)和I2C硬件模块差异较大
2. 编码器的信号输出方式?
   AB相正交脉冲(最常见,带方向)→ 可用高级PWM的正交编码器模式或外部中断+定时器捕获
   单脉冲+方向电平(PULSE+DIR)→ 可用外部中断计数+IO判断方向
   仅单脉冲(无方向)→ 仅计数
3. 您希望OLED上显示什么内容?
   例如:当前位置值、转速(RPM)、方向(CW/CCW)、脉冲计数值等
4. 编码器每转多少脉冲(PPR)?
   例如 100、400、1000 等,用于计算转速
请先告诉我以上信息,我马上为您编写一份可直接编译运行的完整代码!
我答:1任意一款单片机2AB相正交脉冲3当前位置值、转速(RPM)、方向(CW/CCW)、脉冲计数值等4每转1000脉冲
提交以后开始刷刷刷刷出代码
出了几秒钟以后突然全部清空
说 抱歉,服务暂时不可用。
///////////////////////////////////////////////////////
///////////////////////////////////////////////////////
///////////////////////////////////////////////////////
选择一块STC单片机 写一个增量式编码器正反转计数程序
AI
好的,根据已有信息,我来整理一下。从管脚图可以看到,PWM1P(P1.0)是PWM通道1的捕获输入/输出正极,PWM2P(P5.4/P2.2等)是通道2的捕获输入/输出正极。在编码器模式下,TI1对应通道1输入(PWM1P),TI2对应通道2输入(PWM2P)。
现在我来编写完整的程序。我选择 STC8H8K64U 这款芯片,因为它资源丰富、支持高级PWM正交编码器模式,且带有USB接口方便调试。
让我先确认一下PWM1P和PWM2P的默认引脚位置。
从管脚图可以看到:
   PWM1P(通道1捕获输入)默认在 P1.0
   PWM2P(通道2捕获输入)默认在 P5.4(LQFP64的18脚)
不过为了更方便使用,我也可以使用P2.2作为PWM2P_2。让我用默认引脚P1.0和P5.4。
现在编写完整的程序:
方案选择
我选择 STC8H8K64U 系列单片机,理由如下:
   ? 内置高级PWM定时器,硬件支持正交编码器接口模式
   ? 硬件自动完成正反转判断和计数,无需软件干预
   ? 资源丰富(64KB Flash、8KB SRAM),带USB接口方便调试
   ? 价格适中,供货稳定
硬件连接
编码器信号 STC8H8K64U引脚 说明
A相(CH A) P1.0(PWM1P) 高级PWM通道1输入(TI1)
B相(CH B) P5.4(PWM2P) 高级PWM通道2输入(TI2)
Z相(零位,可选) 任意I/O口(如P3.2/INT0) 外部中断,用于归零
完整程序代码
/*
* ============================================================
* 文件名: encoder_counter.c
* 芯片型号: STC8H8K64U(LQFP64/QFN64)
* 功能描述: 增量式编码器正反转计数程序
*           使用高级PWM的硬件正交编码器接口模式,
*           硬件自动完成4倍频计数和方向判断。
* 硬件连接:
*   - 编码器A相 -> P1.0 (PWM1P/TI1)
*   - 编码器B相 -> P5.4 (PWM2P/TI2)
*   - 串口1(TxD/P3.1) -> 输出计数值到串口助手(115200,8n1)
* 时钟: 内部IRC 24MHz(使用STC-ISP下载时设置)
* 编译环境: Keil C51
* ============================================================
*/
#include "STC8H.h"
/* 使用内部IRC 24MHz,无需外部晶振 */
/* 全局变量 */
volatile signed long encoder_count;      // 编码器当前位置计数值(有符号)
volatile unsigned char encoder_dir;      // 当前方向:0=正转(向上计数),1=反转(向下计数)
volatile unsigned char update_flag;      // 更新标志,主循环轮询
/* 定时器2用于1ms定时,作为周期性读取编码器值的时基 */
volatile unsigned int timer2_count;
/* 函数声明 */
void System_Init(void);
void PWM_Encoder_Init(void);
void Timer2_Init(void);
void UART1_Init(void);
void UART1_SendString(unsigned char *str);
void UART1_SendLong(signed long val);
/* 主函数 */
void main(void)
{
   unsigned char buf[32];
   System_Init();          // 系统初始化
   PWM_Encoder_Init();     // 高级PWM编码器模式初始化
   Timer2_Init();          // 定时器2初始化(1ms时基)
   UART1_Init();           // 串口1初始化(115200bps)
   EA = 1;                 // 开启总中断
   UART1_SendString("STC8H8K64U 增量式编码器计数程序启动\r\n");
   UART1_SendString("编码器A相 -> P1.0, B相 -> P5.4\r\n");
   while(1)
   {
       /* 每100ms读取一次编码器值并输出 */
       if(update_flag)
       {
           update_flag = 0;
           /* 读取当前编码器计数值(16位有符号数) */
           /* PWMA_CNTR是16位计数器,编码器模式下自动在0~ARR之间计数 */
           /* 方向由硬件自动判断,DIR位指示方向 */
           /* 读取当前计数器值 */
           encoder_count = (signed int)PWMA_CNTR;
           /* 读取方向位:PWMA_CR1的bit4是DIR */
           if(PWMA_CR1 & 0x10)
               encoder_dir = 1;    // 反转(向下计数)
           else
               encoder_dir = 0;    // 正转(向上计数)
           /* 通过串口输出 */
           UART1_SendString("Pos: ");
           UART1_SendLong(encoder_count);
           if(encoder_dir)
               UART1_SendString(" DIR:REV\r\n");
           else
               UART1_SendString(" DIR:FWD\r\n");
       }
   }
}
/*============================================================
* 系统初始化
* 设置系统时钟为24MHz,端口模式等
*============================================================*/
void System_Init(void)
{
   /* 设置内部IRC频率为24MHz(需在STC-ISP下载时选择24MHz) */
   /* 如果下载时已设置好,此处无需额外操作 */
   /* 设置P1.0和P5.4为高阻输入模式(编码器信号输入) */
   P1M0 &= ~0x01;      // P1.0: M0=0
   P1M1 |= 0x01;       // P1.0: M1=1 -> 高阻输入
   P5M0 &= ~0x10;      // P5.4: M0=0
   P5M1 |= 0x10;       // P5.4: M1=1 -> 高阻输入
   /* P3.1(TxD)设置为推挽输出 */
   P3M0 |= 0x02;       // P3.1: M0=1
   P3M1 &= ~0x02;      // P3.1: M1=0 -> 推挽输出
}
/*============================================================
* 高级PWM编码器模式初始化
* 配置PWMA为编码器接口模式,TI1=P1.0(PWM1P), TI2=P5.4(PWM2P)
* 编码器模式3:TI1和TI2双边沿计数(4倍频)
*============================================================*/
void PWM_Encoder_Init(void)
{
   /* 使能访问XFR(扩展特殊功能寄存器) */
   P_SW2 |= 0x80;
   /*----------------------------------------------------------
    * 步骤1:配置捕获/比较通道1和2的输入映射
    * PWMA_CCMR1: CC1S[1:0]=01 -> IC1FP1映射到TI1
    * PWMA_CCMR2: CC2S[1:0]=01 -> IC2FP2映射到TI2
    *----------------------------------------------------------*/
   PWMA_CCMR1 = (PWMA_CCMR1 & ~0x03) | 0x01;   // CC1S=01
   PWMA_CCMR2 = (PWMA_CCMR2 & ~0x03) | 0x01;   // CC2S=01
   /*----------------------------------------------------------
    * 步骤2:配置输入极性
    * PWMA_CCER1: CC1P=0 (不反相), CC2P=0 (不反相)
    *----------------------------------------------------------*/
   PWMA_CCER1 = 0x00;   // CC1P=0, CC2P=0
   /*----------------------------------------------------------
    * 步骤3:配置从模式控制寄存器为编码器模式3
    * PWMA_SMCR: SMS[2:0]=011 -> 编码器模式3
    * 在TI1和TI2的上升沿和下降沿都计数(4倍频)
    *----------------------------------------------------------*/
   PWMA_SMCR = (PWMA_SMCR & ~0x07) | 0x03;     // SMS=011
   /*----------------------------------------------------------
    * 步骤4:设置自动重装载值(ARR)
    * 编码器模式下,计数器在0~ARR之间循环计数
    * 设置为最大值0xFFFF,获得最大计数范围
    * 注意:编码器模式下ARR决定计数范围,不决定频率
    *----------------------------------------------------------*/
   PWMA_ARR = 0xFFFF;   // 16位最大计数范围
   /*----------------------------------------------------------
    * 步骤5:使能计数器
    * PWMA_CR1: CEN=1 启动计数器
    *----------------------------------------------------------*/
   PWMA_CR1 |= 0x01;    // CEN=1
   /* 初始化计数值 */
   encoder_count = 0;
   encoder_dir = 0;
}
/*============================================================
* 定时器2初始化
* 定时器2作为1ms时基,用于周期性读取编码器
* 时钟源=24MHz, 12T模式, 重载值=24000000/12/1000=2000
*============================================================*/
void Timer2_Init(void)
{
   /* 定时器2是16位自动重装载定时器 */
   /* T2H/T2L = 65536 - 24000000/12/1000 = 65536 - 2000 = 63536 = 0xF830 */
   T2L = 0x30;          // 低字节
   T2H = 0xF8;          // 高字节
   /* 使能定时器2中断 */
   IE2 |= 0x04;         // ET2=1
   /* 启动定时器2 */
   AUXR |= 0x10;        // T2R=1, 且T2x12=0(12T模式)
}
/*============================================================
* 串口1初始化
* 波特率115200, 8位数据, 1位停止位, 无校验
* 使用定时器1作为波特率发生器
* 24MHz, 8位自动重载, 12T模式
* 重载值 = 256 - 24000000/12/32/115200 = 256 - 54 = 202 = 0xCA
*============================================================*/
void UART1_Init(void)
{
   /* 设置定时器1为8位自动重装载模式 */
   TMOD = (TMOD & ~0xF0) | 0x20;   // T1: 模式2(8位自动重载)
   /* 设置重载值 */
   TH1 = 0xCA;          // 波特率115200 @24MHz
   TL1 = 0xCA;
   /* 启动定时器1 */
   TR1 = 1;
   /* 设置串口1模式1(8位UART), 使能接收 */
   SCON = 0x50;         // SM0=0, SM1=1, REN=1
   /* 使用定时器1作为波特率发生器 */
   AUXR &= ~0x01;       // S1ST2=0, 使用T1
   AUXR |= 0x40;        // T1x12=1 (1T模式, 实际上面计算已按12T)
   /* 更正:上面按12T计算,这里应设T1x12=0 */
   AUXR &= ~0x40;       // T1x12=0 (12T模式)
   /* 使能串口1中断 */
   ES = 1;
}
/*============================================================
* 串口发送字符串
*============================================================*/
void UART1_SendString(unsigned char *str)
{
   while(*str)
   {
       SBUF = *str++;
       while(TI == 0);  // 等待发送完成
       TI = 0;
   }
}
/*============================================================
* 串口发送有符号长整数(十进制)
*============================================================*/
void UART1_SendLong(signed long val)
{
   unsigned char buf[12];
   unsigned char i = 0;
   unsigned char j;
   unsigned char negative = 0;
   if(val < 0)
   {
       negative = 1;
       val = -val;
   }
   /* 转换为十进制字符串(倒序) */
   do {
       buf[i++] = (unsigned char)(val % 10) + '0';
       val /= 10;
   } while(val > 0);
   if(negative)
       buf[i++] = '-';
   /* 倒序输出 */
   for(j = i; j > 0; j--)
   {
       SBUF = buf[j-1];
       while(TI == 0);
       TI = 0;
   }
   /* 发送结束 */
   SBUF = ' ';
   while(TI == 0);
   TI = 0;
}
/*============================================================
* 定时器2中断服务程序
* 每1ms中断一次,累计到100ms设置更新标志
*============================================================*/
void Timer2_ISR(void) interrupt 12
{
   /* 清除定时器2中断标志 */
   IE2 &= ~0x04;        // 软件清除TF2
   timer2_count++;
   if(timer2_count >= 100)     // 100ms
   {
       timer2_count = 0;
       update_flag = 1;
   }
}
/*============================================================
* 串口1中断服务程序
* 本示例中未使用接收功能,仅保留框架
*============================================================*/
void UART1_ISR(void) interrupt 4
{
   if(RI)
   {
       RI = 0;          // 清除接收中断标志
       /* 可在此处添加接收处理 */
   }
   if(TI)
   {
       TI = 0;          // 清除发送中断标志(已在查询方式中处理)
   }
}
程序说明
1. 编码器工作原理
增量式编码器输出两路相位差90°的方波信号(A相和B相):
   正转:A相领先B相90°
   反转:B相领先A相90°
STC8H系列的高级PWM定时器内置硬件正交编码器接口,可以自动:
   在A相和B相的上升沿和下降沿都计数(4倍频,分辨率×4)
   根据相位关系自动判断方向(DIR位)
   计数器自动向上/向下计数
2. 关键配置说明
配置项 寄存器 值 说明
CC1S PWMA_CCMR1[1:0] 01 IC1FP1映射到TI1(PWM1P)
CC2S PWMA_CCMR2[1:0] 01 IC2FP2映射到TI2(PWM2P)
CC1P/CC2P PWMA_CCER1 0 输入不反相
SMS PWMA_SMCR[2:0] 011 编码器模式3(双边沿计数)
CEN PWMA_CR1[0] 1 使能计数器
ARR PWMA_ARR 0xFFFF 最大计数范围65535
3. 引脚分配
引脚 功能 说明
P1.0 PWM1P (TI1) 编码器A相输入
P5.4 PWM2P (TI2) 编码器B相输入
P3.1 TxD 串口输出(115200bps)
4. 使用步骤
   在 STC-ISP下载软件 中:
       选择芯片型号:STC8H8K64U
       设置内部IRC频率:24MHz
       打开程序文件,下载到芯片
   硬件连接:
   编码器A相 ----> P1.0
   编码器B相 ----> P5.4
   编码器VCC ----> 3.3V/5V(根据编码器规格)
   编码器GND ----> GND
   打开串口助手(115200, 8N1),旋转编码器即可看到:
   Pos: 1234 DIR:FWD
   Pos: 1240 DIR:FWD
   Pos: 1238 DIR:REV
5. 扩展功能建议
   Z相归零:可将编码器Z相(零位信号)接到外部中断引脚(如P3.2/INT0),在中断中复位计数器
   速度测量:使用另一个定时器测量两次编码器读数的时间间隔,计算转速
   增加滤波:可通过PWMA_CCMR1/2的ICF[3:0]位配置输入滤波器,增强抗干扰能力
如果您需要其他封装(如TSSOP20、LQFP32等)或不同型号的适配版本,请告诉我具体的芯片型号和封装,我可以为您调整引脚配置。
?? 搜索 "选择一块STC单片机 写一个增量式编码器正反转计数程序"
?? 相关论坛讨论
   STC8H8K64U有没有用开关控制电机正反转,根据编码器计数判电机受阻后反转,PWM频率宽
   PWM编码器计数溢出中断怎么用?
   电机小白咨询
?? 查找用时 38.7s | 总回答用时 72.3s
?
将数据显示到0.96英寸IIC OLED上
AI
好的,我已经查到了STC各主流系列(STC8H、STC8G、STC8A8K64D4等)的硬件I2C模块寄存器配置方法。不过,在给出完整代码之前,我需要您先确认一下:
请问您使用的是哪一款具体的STC单片机型号?(例如:STC8H8K64U、STC8G1K08A、STC8A8K64D4、STC15W4K32S4、STC32G12K128 等)
不同型号的差异主要在于:
   I2C硬件模块:STC8系列/STC32系列有硬件I2C控制器,而STC15系列没有独立I2C硬件,需要用GPIO模拟I2C时序
   主频:影响I2C速率配置和延时参数
   I/O口:I2C引脚可切换的端口不同
请告诉我您的具体型号,我马上为您编写一份可直接编译运行的OLED驱动代码!
?? 搜索 "将数据显示到0.96英寸IIC OLED上"
?? 相关论坛讨论
   STC32测试SPI接口0.96英寸 OLED显示
   STC32G + IIC 0.96寸 Oled 显示 米老鼠、懒羊羊 图片
   I2C驱动0.96寸 OLED屏幕, STC32位8051
?? 查找用时 7.6s | 总回答用时 12.1s
?
将增量式脉冲编码器测量数据显示到0.96英寸IIC OLED上
AI
好的,这是一个非常实用的组合需求——用STC单片机读取增量式编码器,然后将数据(如位置、速度、方向)显示在0.96寸IIC OLED(SSD1306)上。
不过,在给出完整代码之前,我需要先确认几个关键信息,以确保代码能直接为您所用:
1. 您使用的是哪一款具体的STC单片机型号?
   例如:STC8H8K64U、STC8G1K08A、STC8A8K64D4、STC32G12K128、STC15W4K32S4 等
   不同型号的编码器接口(正交解码/外部中断/定时器捕获)和I2C硬件模块差异较大
2. 编码器的信号输出方式?
   AB相正交脉冲(最常见,带方向)→ 可用高级PWM的正交编码器模式或外部中断+定时器捕获
   单脉冲+方向电平(PULSE+DIR)→ 可用外部中断计数+IO判断方向
   仅单脉冲(无方向)→ 仅计数
3. 您希望OLED上显示什么内容?
   例如:当前位置值、转速(RPM)、方向(CW/CCW)、脉冲计数值等
4. 编码器每转多少脉冲(PPR)?
   例如 100、400、1000 等,用于计算转速
请先告诉我以上信息,我马上为您编写一份可直接编译运行的完整代码!
?? 搜索 "增量式编码器 I2C OLED 数据显示"
?? 相关论坛讨论
   STC32测试SPI接口0.96英寸 OLED显示
   2023电赛E题国二作品开源(文末附核心代码)
   国二,使用 STC32完成E题获得国二!
?? 查找用时 0.7s | 总回答用时 5.6s
?
1任意一款单片机2AB相正交脉冲3当前位置值、转速(RPM)、方向(CW/CCW)、脉冲计数值等4每转1000脉冲
AI
抱歉,服务暂时不可用。
26-07-29 11:30
应该说 好多基础程序写出来还是很快的
进步很大 达到这种效果很不简单了
可以帮助实现STC内置的USB PWM IO配置 定时器 IIC 计数器等等大部分功能
免得自己去翻PDF手册
效率提高数倍
以前可能要几天时间整理的资料 Ai助手几分钟就搞好了
我试了几个程序都还好
条理比较清晰
语法规范
所以提出更加复杂的要求
一时满足不了 也情有可原
如果再进一步完善
可以实现PLC一样的效果
那就不得了了
26-07-29 18:52
以后都不用人工编程了
26-07-29 18:57
继续测试了一下
您好!我是 STC AI 技术助手,已加载 STC全系列MCU专业技术手册。
我可以帮您:
- 回答STC技术手册中的技术问题
- 自动生成您需要的通用MCU程序
- 自动高效排查您程序中的错误
- 搜索论坛(www.STCAIMCU.com)历史讨论
- 点此可跳转 Web-ISP 在线下载工具
- 您喜爱的Agent工具也可接入 STC数据手册MCP服务器和可以自动控制STC-ISP的MCP服务
- MCP服务可以让AI帮您参考手册写程序,控制STC-ISP自动下载程序,享受全自动
请问您有什么STC技术问题需要帮助 ?
?
增量式编码器正反转程序 1任意一款单片机 2AB相正交脉冲 3当前位置值、转速(RPM)、方向(CW/CCW)、脉冲计数值等4每转1000脉冲
将数据显示到0.96英寸IIC OLED上
AI
现在我已经掌握了所有必要的信息。下面我来编写完整的程序。
完整程序:STC8H8K64U 增量式编码器正反转检测 + 0.96寸 IIC OLED 显示
硬件连接说明
功能 引脚 说明
编码器A相 P1.0 (PWM1P) PWMA通道1输入
编码器B相 P1.2 (PWM2P) PWMA通道2输入
OLED SCL P1.5 (I2C_SCL) I2C时钟线
OLED SDA P1.4 (I2C_SDA) I2C数据线
OLED VCC 3.3V 供电
OLED GND GND 地
   编码器参数:每转1000脉冲,使用编码器模式3(双边沿计数),每转计数值 = 1000 × 4 = 4000
文件1:main.c
/*
* ============================================================
*  程序名称:STC8H8K64U 增量式编码器正反转检测 + OLED显示
*  硬件平台:STC8H8K64U (工作频率 11.0592MHz)
*  编码器  :每转1000脉冲,A相→P1.0,B相→P1.2
*  OLED    :0.96寸 SSD1306,I2C地址 0x78 (7位地址0x3C)
*            SCL→P1.5, SDA→P1.4
*  显示内容:当前位置值、转速(RPM)、方向(CW/CCW)、脉冲计数值
*  编码器模式3:每个脉冲4个边沿计数,每转计数值 = 1000 × 4 = 4000
* ============================================================
*/
#include "stc8h.h"
#include "intrins.h"
#include "stdio.h"
#define MAIN_Fosc 11059200L
/* ==================== 全局变量 ==================== */
volatile int  g_encoder_pos;        // 当前位置值(有符号,可正可负)
volatile int  g_encoder_last_pos;   // 上一次采样位置
volatile unsigned int g_pulse_count; // 脉冲计数值(绝对值)
volatile bit  g_pos_changed;        // 位置变化标志
unsigned int g_speed_rpm;           // 转速 (RPM)
bit          g_direction;           // 方向: 0=CW(正转), 1=CCW(反转)
unsigned int g_timer0_cnt;          // 定时器0计数,用于1秒定时
/* ==================== OLED 相关 ==================== */
#define OLED_ADDR_W  0x78    // OLED I2C写地址 (7位地址0x3C左移1位)
/* OLED 显存缓冲区 (128x64, 每8行一页, 共8页) */
unsigned char xcode g_oled_buf[128][8];  // 注意:实际使用一维或二维需根据SSD1306结构
/* ==================== 函数声明 ==================== */
void PWMA_Encoder_Init(void);
void Timer0_Init(void);
void I2C_Init(void);
void I2C_Start(void);
void I2C_Wait(void);
void I2C_SendData(unsigned char dat);
void I2C_Stop(void);
void OLED_WriteCmd(unsigned char cmd);
void OLED_WriteData(unsigned char dat);
void OLED_Init(void);
void OLED_SetPos(unsigned char x, unsigned char y);
void OLED_Fill(unsigned char fill);
void OLED_ShowChar(unsigned char x, unsigned char y, unsigned char ch);
void OLED_ShowString(unsigned char x, unsigned char y, unsigned char *str);
void OLED_ShowNum(unsigned char x, unsigned char y, unsigned long num, unsigned char len);
void OLED_Refresh(void);
void CalcSpeed(void);
void UpdateDisplay(void);
void Delay_ms(unsigned int ms);
/* ==================== 主函数 ==================== */
void main(void)
{
   unsigned char i;
   P_SW2 |= 0x80;          // 使能XFR访问
   /* 设置所有I/O为准双向模式 */
   P0M0 = 0x00; P0M1 = 0x00;
   P1M0 = 0x00; P1M1 = 0x00;
   P2M0 = 0x00; P2M1 = 0x00;
   P3M0 = 0x00; P3M1 = 0x00;
   P4M0 = 0x00; P4M1 = 0x00;
   P5M0 = 0x00; P5M1 = 0x00;
   /* 初始化变量 */
   g_encoder_pos      = 0;
   g_encoder_last_pos = 0;
   g_pulse_count      = 0;
   g_pos_changed      = 0;
   g_speed_rpm        = 0;
   g_direction        = 0;
   g_timer0_cnt       = 0;
   /* 初始化外设 */
   I2C_Init();             // I2C初始化
   OLED_Init();            // OLED初始化
   OLED_Fill(0x00);        // 清屏
   OLED_Refresh();
   /* 显示初始界面 */
   OLED_ShowString(0, 0, "Encoder Demo");
   OLED_ShowString(0, 2, "Init...");
   OLED_Refresh();
   Delay_ms(500);
   PWMA_Encoder_Init();    // 编码器初始化
   Timer0_Init();          // 定时器0初始化(1秒定时)
   EA = 1;                 // 开总中断
   OLED_Fill(0x00);
   OLED_ShowString(0, 0, "Pos:");
   OLED_ShowString(0, 2, "RPM:");
   OLED_ShowString(0, 4, "Dir:");
   OLED_ShowString(0, 6, "Pulse:");
   OLED_Refresh();
   while(1)
   {
       if(g_pos_changed)
       {
           g_pos_changed = 0;
           UpdateDisplay();
       }
   }
}
/* ============================================================
*  函数: PWMA_Encoder_Init
*  描述: 初始化PWMA模块为编码器模式3(双边沿计数)
*         A相→P1.0 (PWM1P), B相→P1.2 (PWM2P)
*         编码器模式3:每个脉冲4个边沿计数
*         每转1000脉冲 → 每转计数值 = 4000
* ============================================================ */
void PWMA_Encoder_Init(void)
{
   /* PWMA_PS = 0x00: 通道1→P1.0/P1.1, 通道2→P1.2/P1.3 */
   PWMA_PS = 0x00;
   /* 预分频器 = 0,计数器时钟 = 系统时钟 */
   PWMA_PSCRH = 0x00;
   PWMA_PSCRL = 0x00;
   /* 自动重装载值设为最大值,使计数器范围最大 */
   PWMA_ARRH  = 0xFF;
   PWMA_ARRL  = 0xFF;
   /* 计数器清零 */
   PWMA_CNTRH = 0x00;
   PWMA_CNTRL = 0x00;
   /* 禁止PWM输出(编码器模式不需要输出) */
   PWMA_ENO = 0x00;
   /* 通道1配置为输入,映射到TI1,带滤波(滤波时钟数=10) */
   PWMA_CCMR1 = 0x01 | (10 << 4);
   /* 通道2配置为输入,映射到TI2,带滤波(滤波时钟数=10) */
   PWMA_CCMR2 = 0x01 | (10 << 4);
   /* 编码器模式3:TI1和TI2双边沿都计数,每个脉冲计4次 */
   PWMA_SMCR = 0x03;
   /* 配置通道使能和极性:
    * CC1P=1, CC1E=1, CC2P=1, CC2E=1
    * 即 0x55 = 0101 0101
    */
   PWMA_CCER1 = 0x55;
   PWMA_CCER2 = 0x55;
   /* 使能编码器中断(更新中断) */
   PWMA_IER = 0x02;
   /* 使能计数器,自动重装载缓冲,边沿对齐,向上计数 */
   PWMA_CR1 = 0x01;
}
/* ============================================================
*  函数: Timer0_Init
*  描述: 初始化定时器0,1秒定时中断
*         系统时钟11.0592MHz,定时器0工作于16位自动重载模式
*         定时时间 = (65536 - 重载值) × 12 / Fosc
*         重载值 = 65536 - 11059200 / 12 / 1000 = 65536 - 921 = 64615 = 0xFC67
*         定时1ms,软件计数1000次得到1秒
* ============================================================ */
void Timer0_Init(void)
{
   AUXR &= ~0x7F;          // 定时器0:12T模式
   TMOD &= ~0xF0;
   TMOD |= 0x01;           // 定时器0:16位自动重载模式
   TH0 = 0xFC;             // 1ms定时初值
   TL0 = 0x67;
   ET0 = 1;                // 使能定时器0中断
   TR0 = 1;                // 启动定时器0
}
/* ============================================================
*  I2C 底层驱动(查询方式)
* ============================================================ */
void I2C_Init(void)
{
   /* I2C主机模式,使能I2C,速度设置:
    * 速度 = Fosc / 2 / (MSSPEED*2 + 4)
    * MSSPEED = 20 → 速度 ≈ 11059200/2/(20*2+4) ≈ 125kHz
    */
   I2CCFG = 0xE0 | 20;
   I2CMSST = 0x00;
}
void I2C_Wait(void)
{
   while(!(I2CMSST & 0x40));   // 等待命令完成
   I2CMSST &= ~0x40;           // 清中断标志
}
void I2C_Start(void)
{
   I2CMSCR = 0x01;             // 发送START命令
   I2C_Wait();
}
void I2C_SendData(unsigned char dat)
{
   I2CTXD = dat;
   I2CMSCR = 0x02;             // 发送数据命令
   I2C_Wait();
}
void I2C_RecvACK(void)
{
   I2CMSCR = 0x03;             // 接收ACK命令
   I2C_Wait();
}
void I2C_Stop(void)
{
   I2CMSCR = 0x06;             // 发送STOP命令
   I2C_Wait();
}
/* ============================================================
*  OLED 驱动函数 (SSD1306, 128x64)
* ============================================================ */
/* 向OLED写命令 */
void OLED_WriteCmd(unsigned char cmd)
{
   I2C_Start();
   I2C_SendData(OLED_ADDR_W);
   I2C_RecvACK();
   I2C_SendData(0x00);         // Co=0, D/C#=0 (命令)
   I2C_RecvACK();
   I2C_SendData(cmd);
   I2C_RecvACK();
   I2C_Stop();
}
/* 向OLED写数据 */
void OLED_WriteData(unsigned char dat)
{
   I2C_Start();
   I2C_SendData(OLED_ADDR_W);
   I2C_RecvACK();
   I2C_SendData(0x40);         // Co=0, D/C#=1 (数据)
   I2C_RecvACK();
   I2C_SendData(dat);
   I2C_RecvACK();
   I2C_Stop();
}
/* OLED初始化 (SSD1306) */
void OLED_Init(void)
{
   Delay_ms(100);              // 等待OLED上电稳定
   OLED_WriteCmd(0xAE);        // 关闭显示
   OLED_WriteCmd(0xD5);        // 设置显示时钟分频/振荡频率
   OLED_WriteCmd(0x80);
   OLED_WriteCmd(0xA8);        // 设置多路复用比
   OLED_WriteCmd(0x3F);        // 64行
   OLED_WriteCmd(0xD3);        // 设置显示偏移
   OLED_WriteCmd(0x00);
   OLED_WriteCmd(0x40);        // 设置显示起始行
   OLED_WriteCmd(0x8D);        // 电荷泵设置
   OLED_WriteCmd(0x14);        // 使能电荷泵
   OLED_WriteCmd(0x20);        // 设置内存地址模式
   OLED_WriteCmd(0x00);        // 水平寻址模式
   OLED_WriteCmd(0xA1);        // 段重映射 (左右翻转)
   OLED_WriteCmd(0xC8);        // COM扫描方向 (上下翻转)
   OLED_WriteCmd(0xDA);        // COM引脚配置
   OLED_WriteCmd(0x12);
   OLED_WriteCmd(0x81);        // 对比度设置
   OLED_WriteCmd(0xCF);
   OLED_WriteCmd(0xD9);        // 预充电周期
   OLED_WriteCmd(0xF1);
   OLED_WriteCmd(0xDB);        // VCOMH取消选择级别
   OLED_WriteCmd(0x40);
   OLED_WriteCmd(0xA4);        // 全局显示开启(跟随RAM)
   OLED_WriteCmd(0xA6);        // 正常显示(非反相)
   OLED_WriteCmd(0xAF);        // 开启显示
}
/* 设置OLED写入位置 (x: 0~127, y: 0~7 页) */
void OLED_SetPos(unsigned char x, unsigned char y)
{
   OLED_WriteCmd(0xB0 + y);                // 设置页地址
   OLED_WriteCmd(((x & 0xF0) >> 4) | 0x10); // 设置列地址高4位
   OLED_WriteCmd(x & 0x0F);                // 设置列地址低4位
}
/* 全屏填充 */
void OLED_Fill(unsigned char fill)
{
   unsigned char i, j;
   for(j = 0; j < 8; j++)
   {
       OLED_SetPos(0, j);
       for(i = 0; i < 128; i++)
       {
           OLED_WriteData(fill);
       }
   }
}
/* 6x8 ASCII字库 (空格~~) */
unsigned char code F6x8[][6] = {
   {0x00,0x00,0x00,0x00,0x00,0x00},   // 空格
   {0x00,0x00,0x5F,0x00,0x00,0x00},   // !
   {0x00,0x07,0x00,0x07,0x00,0x00},   // "
   {0x14,0x7F,0x14,0x7F,0x14,0x00},   // #
   {0x24,0x2A,0x7F,0x2A,0x12,0x00},   // $
   {0x23,0x13,0x08,0x64,0x62,0x00},   // %
   {0x36,0x49,0x55,0x22,0x50,0x00},   // &
   {0x00,0x05,0x03,0x00,0x00,0x00},   // '
   {0x00,0x1C,0x22,0x41,0x00,0x00},   // (
   {0x00,0x41,0x22,0x1C,0x00,0x00},   // )
   {0x08,0x2A,0x1C,0x2A,0x08,0x00},   // *
   {0x08,0x08,0x3E,0x08,0x08,0x00},   // +
   {0x00,0x50,0x30,0x00,0x00,0x00},   // ,
   {0x08,0x08,0x08,0x08,0x08,0x00},   // -
   {0x00,0x60,0x60,0x00,0x00,0x00},   // .
   {0x20,0x10,0x08,0x04,0x02,0x00},   // /
   {0x3E,0x51,0x49,0x45,0x3E,0x00},   // 0
   {0x00,0x42,0x7F,0x40,0x00,0x00},   // 1
   {0x42,0x61,0x51,0x49,0x46,0x00},   // 2
   {0x21,0x41,0x45,0x4B,0x31,0x00},   // 3
   {0x18,0x14,0x12,0x7F,0x10,0x00},   // 4
   {0x27,0x45,0x45,0x45,0x39,0x00},   // 5
   {0x3C,0x4A,0x49,0x49,0x30,0x00},   // 6
   {0x01,0x71,0x09,0x05,0x03,0x00},   // 7
   {0x36,0x49,0x49,0x49,0x36,0x00},   // 8
   {0x06,0x49,0x49,0x29,0x1E,0x00},   // 9
   {0x00,0x36,0x36,0x00,0x00,0x00},   // :
   {0x00,0x56,0x36,0x00,0x00,0x00},   // ;
   {0x00,0x08,0x14,0x22,0x41,0x00},   // <
   {0x14,0x14,0x14,0x14,0x14,0x00},   // =
   {0x41,0x22,0x14,0x08,0x00,0x00},   // >
   {0x02,0x01,0x51,0x09,0x06,0x00},   // ?
   {0x32,0x49,0x79,0x41,0x3E,0x00},   // @
   {0x7E,0x11,0x11,0x11,0x7E,0x00},   // A
   {0x7F,0x49,0x49,0x49,0x36,0x00},   // B
   {0x3E,0x41,0x41,0x41,0x22,0x00},   // C
   {0x7F,0x41,0x41,0x22,0x1C,0x00},   // D
   {0x7F,0x49,0x49,0x49,0x41,0x00},   // E
   {0x7F,0x09,0x09,0x01,0x01,0x00},   // F
   {0x3E,0x41,0x41,0x51,0x32,0x00},   // G
   {0x7F,0x08,0x08,0x08,0x7F,0x00},   // H
   {0x00,0x41,0x7F,0x41,0x00,0x00},   // I
   {0x20,0x40,0x41,0x3F,0x01,0x00},   // J
   {0x7F,0x08,0x14,0x22,0x41,0x00},   // K
   {0x7F,0x40,0x40,0x40,0x40,0x00},   // L
   {0x7F,0x02,0x04,0x02,0x7F,0x00},   // M
   {0x7F,0x04,0x08,0x10,0x7F,0x00},   // N
   {0x3E,0x41,0x41,0x41,0x3E,0x00},   // O
   {0x7F,0x09,0x09,0x09,0x06,0x00},   // P
   {0x3E,0x41,0x51,0x21,0x5E,0x00},   // Q
   {0x7F,0x09,0x19,0x29,0x46,0x00},   // R
   {0x46,0x49,0x49,0x49,0x31,0x00},   // S
   {0x01,0x01,0x7F,0x01,0x01,0x00},   // T
   {0x3F,0x40,0x40,0x40,0x3F,0x00},   // U
   {0x1F,0x20,0x40,0x20,0x1F,0x00},   // V
   {0x7F,0x20,0x18,0x20,0x7F,0x00},   // W
   {0x63,0x14,0x08,0x14,0x63,0x00},   // X
   {0x03,0x04,0x78,0x04,0x03,0x00},   // Y
   {0x61,0x51,0x49,0x45,0x43,0x00},   // Z
};
/* 在OLED指定位置显示一个字符 (6x8) */
void OLED_ShowChar(unsigned char x, unsigned char y, unsigned char ch)
{
   unsigned char i;
   if(ch < ' ' || ch > 'Z') ch = ' ';  // 仅支持空格~Z
   ch -= ' ';
   OLED_SetPos(x, y);
   for(i = 0; i < 6; i++)
   {
       OLED_WriteData(F6x8[ch][i]);
   }
}
/* 在OLED指定位置显示字符串 */
void OLED_ShowString(unsigned char x, unsigned char y, unsigned char *str)
{
   while(*str != '\0')
   {
       OLED_ShowChar(x, y, *str);
       x += 6;
       if(x > 122) { x = 0; y++; }    // 自动换行
       str++;
   }
}
/* 在OLED指定位置显示数字 (len: 数字位数) */
void OLED_ShowNum(unsigned char x, unsigned char y, unsigned long num, unsigned char len)
{
   unsigned char i;
   unsigned char buf[12];
   unsigned char show_buf[12];
   unsigned char idx = 0;
   /* 将数字转为字符串 */
   if(num == 0)
   {
       buf[idx++] = '0';
   }
   else
   {
       while(num > 0)
       {
           buf[idx++] = (num % 10) + '0';
           num /= 10;
       }
   }
   /* 反转 */
   for(i = 0; i < idx; i++)
   {
       show_buf[i] = buf[idx - 1 - i];
   }
   /* 如果位数不足,前面补空格 */
   if(idx < len)
   {
       for(i = idx; i < len; i++)
       {
           show_buf[i] = ' ';
       }
       /* 整体右移 */
       for(i = 0; i < len; i++)
       {
           if(i < len - idx)
               show_buf[len - 1 - i] = show_buf[len - 1 - i - (len - idx)];
       }
       for(i = 0; i < len - idx; i++)
       {
           show_buf[i] = ' ';
       }
   }
   /* 显示 */
   for(i = 0; i < len; i++)
   {
       OLED_ShowChar(x + i * 6, y, show_buf[i]);
   }
}
/* 刷新OLED显示(本程序使用直接写方式,此函数保留兼容) */
void OLED_Refresh(void)
{
   /* 本程序采用直接写方式,无需额外刷新 */
}
/* ============================================================
*  函数: CalcSpeed
*  描述: 计算转速 (RPM)
*         每转计数值 = 1000脉冲 × 4 = 4000
*         转速 = (位置变化量 / 4000) × 60 (RPM)
*         每1秒计算一次
* ============================================================ */
void CalcSpeed(void)
{
   int delta;
   unsigned long abs_delta;
   unsigned long rpm;
   delta = g_encoder_pos - g_encoder_last_pos;
   g_encoder_last_pos = g_encoder_pos;
   if(delta >= 0)
   {
       abs_delta = (unsigned long)delta;
       g_direction = 0;    // CW 正转
   }
   else
   {
       abs_delta = (unsigned long)(-delta);
       g_direction = 1;    // CCW 反转
   }
   /* RPM = (delta_count / 4000) × 60 */
   rpm = abs_delta * 60 / 4000;
   if(rpm > 99999) rpm = 99999;
   g_speed_rpm = (unsigned int)rpm;
}
/* ============================================================
*  函数: UpdateDisplay
*  描述: 更新OLED显示内容
* ============================================================ */
void UpdateDisplay(void)
{
   unsigned char buf[20];
   /* 第0行: 当前位置值 (有符号数) */
   if(g_encoder_pos < 0)
   {
       OLED_ShowString(30, 0, "-");
       OLED_ShowNum(36, 0, (unsigned long)(-g_encoder_pos), 6);
   }
   else
   {
       OLED_ShowString(30, 0, " ");
       OLED_ShowNum(36, 0, (unsigned long)g_encoder_pos, 6);
   }
   /* 第2行: 转速 RPM */
   OLED_ShowNum(30, 2, (unsigned long)g_speed_rpm, 5);
   OLED_ShowString(66, 2, "RPM");
   /* 第4行: 方向 */
   if(g_direction == 0)
   {
       OLED_ShowString(30, 4, "CW  (CW)  ");
   }
   else
   {
       OLED_ShowString(30, 4, "CCW (CCW) ");
   }
   /* 第6行: 脉冲计数值 */
   OLED_ShowNum(36, 6, (unsigned long)g_pulse_count, 8);
}
/* ============================================================
*  函数: Delay_ms
*  描述: 软件延时 (近似)
* ============================================================ */
void Delay_ms(unsigned int ms)
{
   unsigned int i, j;
   for(i = 0; i < ms; i++)
   {
       for(j = 0; j < 1200; j++)
       {
           _nop_();
       }
   }
}
/* ============================================================
*  中断服务函数
* ============================================================ */
/* PWMA 中断 (中断号26) - 编码器计数更新 */
void PWMA_ISR(void) interrupt 26
{
   unsigned int current_cnt;
   if(PWMA_SR1 & 0x02)     // 编码器更新中断
   {
       /* 读取当前计数值(16位) */
       current_cnt = ((unsigned int)PWMA_CNTRH << 8) | PWMA_CNTRL;
       /* 将硬件计数值转换为有符号位置值 */
       /* PWMA_CR1的DIR位指示方向:0=向上(正转), 1=向下(反转) */
       if(PWMA_CR1 & 0x10)     // DIR=1, 反转
       {
           /* 反转时,计数值从ARR向下计数,需要转换 */
           g_encoder_pos = -(int)(0x10000 - current_cnt);
       }
       else                    // DIR=0, 正转
       {
           g_encoder_pos = (int)current_cnt;
       }
       /* 脉冲计数值 = 位置值的绝对值 / 4 (因为模式3每个脉冲计4次) */
       if(g_encoder_pos >= 0)
           g_pulse_count = (unsigned int)(g_encoder_pos / 4);
       else
           g_pulse_count = (unsigned int)((-g_encoder_pos) / 4);
       g_pos_changed = 1;
       PWMA_SR1 &= ~0x02;      // 清中断标志
   }
   else
   {
       PWMA_SR1 = 0;           // 清其他标志
   }
}
/* 定时器0中断 (中断号1) - 1秒定时 */
void Timer0_ISR(void) interrupt 1
{
   g_timer0_cnt++;
   if(g_timer0_cnt >= 1000)    // 1秒到
   {
       g_timer0_cnt = 0;
       CalcSpeed();            // 计算转速
   }
}
文件2:stc8h.h(头文件片段——如果使用Keil自带头文件可省略)
如果您使用的是 Keil C51 + STC 官方头文件包,直接 #include "stc8h.h" 即可。如果手头没有,以下是程序中用到的 XFR 寄存器定义的关键部分(可添加到项目头文件中):
/* ============================================================
*  STC8H8K64U 关键 XFR 寄存器定义(用于编码器 + I2C)
*  完整头文件请使用 STC-ISP 软件生成
* ============================================================ */
#ifndef __STC8H_EXTRA_H__
#define __STC8H_EXTRA_H__
/* ---------- PWMA 寄存器 (XFR 地址) ---------- */
#define PWMA_CR1     (*(unsigned char volatile xdata *)0xFEC0)
#define PWMA_CR2     (*(unsigned char volatile xdata *)0xFEC1)
#define PWMA_SMCR    (*(unsigned char volatile xdata *)0xFEC2)
#define PWMA_IER     (*(unsigned char volatile xdata *)0xFEC4)
#define PWMA_SR1     (*(unsigned char volatile xdata *)0xFEC5)
#define PWMA_SR2     (*(unsigned char volatile xdata *)0xFEC6)
#define PWMA_CCMR1   (*(unsigned char volatile xdata *)0xFEC8)
#define PWMA_CCMR2   (*(unsigned char volatile xdata *)0xFEC9)
#define PWMA_CCER1   (*(unsigned char volatile xdata *)0xFECC)
#define PWMA_CCER2   (*(unsigned char volatile xdata *)0xFECD)
#define PWMA_CNTRH   (*(unsigned char volatile xdata *)0xFECE)
#define PWMA_CNTRL   (*(unsigned char volatile xdata *)0xFECF)
#define PWMA_PSCRH   (*(unsigned char volatile xdata *)0xFED0)
#define PWMA_PSCRL   (*(unsigned char volatile xdata *)0xFED1)
#define PWMA_ARRH    (*(unsigned char volatile xdata *)0xFED2)
#define PWMA_ARRL    (*(unsigned char volatile xdata *)0xFED3)
#define PWMA_ENO     (*(unsigned char volatile xdata *)0xFEB1)
#define PWMA_PS      (*(unsigned char volatile xdata *)0xFEB2)
/* ---------- I2C 寄存器 (XFR 地址) ---------- */
#define I2CCFG       (*(unsigned char volatile xdata *)0xFE80)
#define I2CMSCR      (*(unsigned char volatile xdata *)0xFE81)
#define I2CMSST      (*(unsigned char volatile xdata *)0xFE82)
#define I2CSLCR      (*(unsigned char volatile xdata *)0xFE83)
#define I2CSLST      (*(unsigned char volatile xdata *)0xFE84)
#define I2CSLADR     (*(unsigned char volatile xdata *)0xFE85)
#define I2CTXD       (*(unsigned char volatile xdata *)0xFE86)
#define I2CRXD       (*(unsigned char volatile xdata *)0xFE87)
#define I2CMSAUX     (*(unsigned char volatile xdata *)0xFE88)
#endif
程序说明
项目 说明
单片机 STC8H8K64U(工作频率 11.0592MHz)
编码器接口 PWMA 高级PWM编码器模式3,A相→P1.0,B相→P1.2
每转脉冲 1000 PPR,模式3下每转计数值 = 1000 × 4 = 4000
OLED接口 I2C硬件主机模式,SCL→P1.5,SDA→P1.4,地址0x78
显示内容 第0行:当前位置值(有符号);第2行:转速RPM;第4行:方向CW/CCW;第6行:脉冲计数值
转速计算 每1秒计算一次,RPM = (Δ位置 / 4000) × 60
中断使用 PWMA中断(中断号26)读取编码器计数值;定时器0中断(中断号1)做1秒定时
使用步骤
   打开 STC-ISP 软件,选择单片机型号 STC8H8K64U
   设置系统时钟为 11.0592MHz
   生成头文件:点击"头文件"选项卡,生成 stc8h.h 放入项目
   在 Keil C51 中新建项目,将上述 main.c 加入项目
   编译下载到单片机
   连接硬件:
       编码器 A 相 → P1.0
       编码器 B 相 → P1.2
       OLED SCL → P1.5
       OLED SDA → P1.4
       OLED VCC → 3.3V,GND → GND
注意事项
   编码器输出电平:如果编码器是 5V/24V 输出,需要加电平转换电路(如电阻分压或光耦隔离),确保输入到 P1.0/P1.2 的电平不超过 3.6V(STC8H 为 3.3V/5V tolerant,但建议 3.3V 逻辑电平)。
   滤波设置:程序中 PWMA_CCMR1/2 设置了滤波时钟数为 10,可根据编码器实际输出质量调整(值越大滤波越强,但会丢失高速脉冲)。
   编码器模式选择:当前使用模式3(SMS=011),每个脉冲计4次。如果编码器抖动较大,可改为模式1或模式2(每个脉冲计2次),修改 PWMA_SMCR = 0x01 或 0x02 即可。
   位置值溢出:16位计数器最大 65535,如果旋转圈数很多会溢出。如需更大范围,可在中断中增加软件扩展计数(如增加一个 long 变量累加溢出次数)。
?? 搜索 "增量式编码器正反转程序 1任意一款单片机 2AB相正交脉冲 3当前位置值、转速(RPM)、方向(CW/CCW)、脉冲计"
?? 查找用时 24.9s | 总回答用时 97.2s
26-07-29 20:22
简单的应用还行,稍微复杂一点就不行了,例如:驱动一个彩色液晶屏试试。
26-07-30 22:44
可以实现基本的应用就不错了
Ai的本质还是以搜索整理为主
对于STC单片机林林总总不同规格
不同差异化功能来说
这个工作也是比较繁琐的
是一个逐步积累的过程
不可能具备自主思考的能力
26-07-31 05:27
以下服务需要缴费,
你就发现还不如你自己写。
26-08-05 17:42

工控新闻

更多新闻资讯