/* * main.c * 功能:实现I2C从机接收数据,控制LED和PWM输出,并通过UART1输出LOG信息 * 作者: * 日期: * 版本:1.0 * * 主要功能: * 1. I2C从机接收数据,地址为0x8A(对应7位地址45) * 2. 支持接收设备地址、寄存器地址和数据 * 3. 控制LED开关(0xCC关闭,0xDD打开) * 4. 控制PWM占空比(0x00-0x64对应0%-100%) * 5. 通过UART1输出接收到的整段数据 * 6. 每次发送完毕清空缓冲区 */ // 包含必要的头文件 #include "xtell.h" // 定义LOG宏和其他辅助功能 #include "uart.h" // UART1相关函数声明 #include "intrins.h" // 提供_nop_()等内置函数 // 定义PCA(可编程计数器阵列)相关寄存器 sfr CCON = 0xd8; // PCA控制寄存器,用于控制PCA模块的运行和中断 sbit CF = CCON^7; // PCA溢出标志,当PCA计数器溢出时置1 sbit CR = CCON^6; // PCA计数器运行控制位,置1时启动PCA计数器 sbit CCF2 = CCON^2; // PCA模块2中断标志,当模块2发生中断时置1 sbit CCF1 = CCON^1; // PCA模块1中断标志,当模块1发生中断时置1 sbit CCF0 = CCON^0; // PCA模块0中断标志,当模块0发生中断时置1 sfr CMOD = 0xd9; // PCA模式寄存器,用于配置PCA的时钟源和工作模式 sfr CL = 0xe9; // PCA计数器低8位,存储PCA计数器的低8位值 sfr CH = 0xf9; // PCA计数器高8位,存储PCA计数器的高8位值 sfr CCAPM1 = 0xdb; // PCA模块1比较/捕获控制寄存器,用于配置模块1的工作模式 sfr CCAP1L = 0xeb; // PCA模块1比较/捕获寄存器低8位,用于存储PWM占空比的低8位 sfr CCAP1H = 0xfb; // PCA模块1比较/捕获寄存器高8位,用于存储PWM占空比的高8位 sfr PCA_PWM1 = 0xf3; // PCA模块1 PWM控制寄存器,用于配置PWM的输出位数 // 定义I2C相关寄存器(使用xdata指针访问扩展SFR) #define I2CCFG (*(unsigned char volatile xdata *)0xfe80) // I2C配置寄存器,用于配置I2C的工作模式和参数 #define I2CMSCR (*(unsigned char volatile xdata *)0xfe81) // I2C主机控制寄存器,用于控制I2C主机的操作 #define I2CMSST (*(unsigned char volatile xdata *)0xfe82) // I2C主机状态寄存器,用于指示I2C主机的状态 #define I2CSLCR (*(unsigned char volatile xdata *)0xfe83) // I2C从机控制寄存器,用于控制I2C从机的操作 #define I2CSLST (*(unsigned char volatile xdata *)0xfe84) // I2C从机状态寄存器,用于指示I2C从机的状态 #define I2CSLADR (*(unsigned char volatile xdata *)0xfe85) // I2C从机地址寄存器,用于设置I2C从机的地址 #define I2CTXD (*(unsigned char volatile xdata *)0xfe86) // I2C数据发送寄存器,用于发送I2C数据 #define I2CRXD (*(unsigned char volatile xdata *)0xfe87) // I2C数据接收寄存器,用于接收I2C数据 // 定义I2C事件标志位 #define I2C_EVENT_START 0x40 // START事件标志,当检测到I2C START信号时置1 #define I2C_EVENT_RECV 0x20 // RECV事件标志,当接收到I2C数据时置1 #define I2C_EVENT_SEND 0x10 // SEND事件标志,当需要发送I2C数据时置1 #define I2C_EVENT_STOP 0x08 // STOP事件标志,当检测到I2C STOP信号时置1 // 定义I2C从机地址和约定存储地址 #define I2C_SLAVE_ADDR 0x5A // I2C从机地址,对应7位2D // #define I2C_SLAVE_ADDR 0x8A // I2C从机地址,对应7位45 #define REG_ADDR 0x86 // 约定的有效存储地址,只有当接收到的寄存器地址为0x86时才进行硬件操作 // 全局变量定义 unsigned char xdata buffer[64]; // I2C数据缓冲区,用于存储接收到的I2C数据,使用xdata存储以节省data段空间 unsigned char data_len; // 接收到的数据长度 unsigned char recv_reg_addr_global; // 存储接收到的寄存器地址 bit isda; // 设备地址标志,为1时表示等待接收设备地址 bit isma; // 存储地址标志,为1时表示等待接收存储地址 bit is_header_received; // 帧头检测标志,为1时表示已接收到帧头0xAA // 日志缓冲区(中断内存数据,主循环打印,避免中断内串口耗时) unsigned char xdata log_buffer[128]; // 日志缓冲区,用于存储需要通过UART1输出的日志数据 unsigned char log_idx = 0; // 日志缓冲区索引,用于指示当前日志数据存储的位置 unsigned char buffer_idx; // 缓冲区索引,用于追踪当前数据存储位置 /** * @brief 延时函数(500ms) * @details 实现约500ms的延时,基于11.0592MHz的系统时钟 * @param 无 * @return 无 */ void Delay500ms(void) // @11.0592MHz { unsigned char data i, j, k; // 定义局部变量,用于延时计数 _nop_(); // 执行一个空操作指令,用于微调延时时间 _nop_(); // 执行一个空操作指令,用于微调延时时间 i = 22; // 初始化延时计数器i j = 3; // 初始化延时计数器j k = 227; // 初始化延时计数器k do { // 外层do-while循环 do { // 中层do-while循环 while (--k); // 内层while循环,k从227递减到0 } while (--j); // 中层循环,j从3递减到0 } while (--i); // 外层循环,i从22递减到0 } /** * @brief 延时函数(30ms) * @details 实现约30ms的延时,基于11.0592MHz的系统时钟 * @param 无 * @return 无 */ void Delay30ms(void) // @11.0592MHz { unsigned char data i, j, k; // 定义局部变量,用于延时计数 _nop_(); // 执行一个空操作指令,用于微调延时时间 _nop_(); // 执行一个空操作指令,用于微调延时时间 i = 2; // 初始化延时计数器i j = 175; // 初始化延时计数器j k = 220; // 初始化延时计数器k do { // 外层do-while循环 do { // 中层do-while循环 while (--k); // 内层while循环,k从220递减到0 } while (--j); // 中层循环,j从175递减到0 } while (--i); // 外层循环,i从2递减到0 } void Delay10us(void) //@11.0592MHz { unsigned char data i; i = 35; while (--i); } /** * @brief 硬件初始化函数 * @details 配置GPIO模式,设置各引脚的工作模式 * @param 无 * @return 无 */ void HardwareInit(void) { // 配置GPIO模式 // P1.0:推挽输出(BL_PWM), P1.2:推挽输出(LED), P1.1:高阻输入(TP_INT) // P1.3:推挽输出(EN_6211), P1.6:推挽输出(EN_DISP) P1M0 = 0x4d; // P1口模式控制寄存器0,设置P1.0、P1.2、P1.3、P1.6为推挽输出 P1M1 = 0x02; // P1口模式控制寄存器1,设置P1.1为高阻输入 // P3.2:SCL, P3.3:SDA, P3.4:BL_EN(推挽), P3.7:TP_SHUTDOWN(推挽) P3M0 = 0xb0; // P3口模式控制寄存器0,设置P3.4、P3.7为推挽输出 P3M1 = 0x00; // P3口模式控制寄存器1,设置其他引脚为准双向口 // 其他口默认准双向 P5M0 = 0x00; // P5口模式控制寄存器0,设置为默认准双向口 P5M1 = 0x00; // P5口模式控制寄存器1,设置为默认准双向口 } /** * @brief PWM初始化函数 * @details 初始化PCA模块,配置PWM参数,用于控制LED亮度或其他设备 * @param 无 * @return 无 */ void PWMInit() { CCON = 0x00; // 清除PCA控制寄存器,重置所有标志位 CMOD = 0x08; // PCA时钟为系统时钟,不使用外部触发,不使能溢出中断 CL = 0x00; // 重置PCA计数器低8位 CH = 0x00; // 重置PCA计数器高8位 CCAPM1 = 0x42; // PCA模块1为PWM工作模式,使能比较器 PCA_PWM1 = 0x00; // PCA模块1输出8位PWM,分辨率为256级 CCAP1L = 0x80; // PWM占空比为50% [(0xFF - 0x80) / 0xFF] CCAP1H = 0x80; // 与CCAP1L保持一致,确保PWM占空比正确 CR = 1; // 启动PCA计时器,开始PWM输出 } /** * @brief I2C初始化函数 * @details 配置I2C从机模式,设置从机地址和中断 * @param 无 * @return 无 */ void I2CInit() { // 配置I2C引脚映射为P3.2(SCL)/P3.3(SDA) P_SW2 = 0x30; // 设置P_SW2为0x30,选择SCL_4/P3.2, SDA_4/P3.3 // 进入扩展RAM访问模式,以便访问I2C相关寄存器 P_SW2 |= 0x80; // 配置I2C从机模式 I2CCFG = 0x81; // 使能I2C从机模式,设置SCL时钟为系统时钟/16 I2CSLADR = I2C_SLAVE_ADDR; // 设置从机设备地址为8A I2CSLST = 0x00; // 清除从机状态寄存器,重置所有状态标志 I2CSLCR = 0x78; // 使能从机模式中断,允许START、STOP、RECV、SEND事件中断 // 退出扩展RAM访问模式 P_SW2 &= ~0x80; // 全局中断使能 EA = 1; // 初始化用户变量 isda = 1; // 设备地址标志置1,表示等待接收设备地址 isma = 1; // 存储地址标志置1,表示等待接收存储地址 is_header_received = 0; // 帧头检测标志置0(未接收到帧头) buffer_idx = 0; // 缓冲区索引初始化为0,指向缓冲区的起始位置 data_len = 0; // 数据长度初始化为0 I2CTXD = buffer[buffer_idx]; // 初始化发送寄存器,准备发送第一个数据 } /** * @brief 设置PWM占空比函数 * @details 根据输入的占空比(0-100)设置PWM输出,用于控制LED亮度或其他设备 * @param duty_cycle 占空比,范围0-100 * @return 无 */ void SetPWM(unsigned char duty_cycle) { unsigned char ccap_value; // 用于存储计算得到的CCAP值 unsigned char pwm_duty; // 用于存储计算得到的占空比百分比 // 将0x00-0xFF映射到0-100%的占空比 pwm_duty = (duty_cycle * 100) / 255; // 计算CCAP值:ccap_value = 255 - (占空比 * 255 / 100) // 因为PCA的PWM输出是从CCAP值开始为高电平,到255时变为低电平 ccap_value = 255 - ((unsigned int)pwm_duty * 255 / 100); // 设置PWM占空比,同时更新CCAP1L和CCAP1H,确保两者值一致 CCAP1L = ccap_value; CCAP1H = ccap_value; } /** * @brief I2C中断处理函数(修复标志位+存储地址校验) * @details 处理I2C各种事件:START、RECV、SEND、STOP,实现I2C从机的通信逻辑 * @param 无 * @return 无 */ void I2C_Isr() interrupt 24 { // 定义局部变量 unsigned char i; // 用于循环计数 unsigned char recv_dev_addr; // 用于存储接收到的设备地址 unsigned char recv_reg_addr; // 用于存储接收到的寄存器地址 unsigned char recv_data; // 用于存储接收到的数据 // 保存P_SW2寄存器值,以便在函数结束时恢复 _push_(P_SW2); // 进入扩展RAM访问模式,以便访问I2C相关寄存器 P_SW2 |= 0x80; // 处理START事件,当检测到I2C START信号时执行 if (I2CSLST & I2C_EVENT_START) { I2CSLST &= ~I2C_EVENT_START; // 清除START事件标志 is_header_received = 0; // 重置帧头标志,避免状态残留 // 清空缓冲区和索引,为接收新的数据做准备 buffer_idx = 0; data_len = 0; // 清空缓冲区内容,确保每次通信开始时缓冲区都是干净的 for(i=0; i<64; i++) { buffer[i] = 0; // 将缓冲区的每个字节设置为0 } } // 处理RECV事件,当接收到I2C数据时执行 else if (I2CSLST & I2C_EVENT_RECV) { I2CSLST &= ~I2C_EVENT_RECV; // 清除RECV事件标志 // 检查是否等待接收设备地址 if (isda) { recv_dev_addr = I2CRXD; // 读取设备地址 buffer[buffer_idx++] = recv_dev_addr; // 将设备地址存入缓冲区 if(buffer_idx >= 63) buffer_idx = 0; // 防止缓冲区溢出 data_len++; isda = 0; // 设备地址匹配成功,清除设备地址标志 } // 检查是否等待接收存储地址 else if (isma) { isma = 0; // 清除存储地址标志 recv_reg_addr = I2CRXD; // 读取存储地址(寄存器地址) recv_reg_addr_global = recv_reg_addr; // 保存到全局变量 // 将寄存器地址存入buffer buffer[buffer_idx++] = recv_reg_addr; // 把寄存器地址写入buffer,后续日志能捕获 if(buffer_idx >= 63) buffer_idx = 0; // 防止缓冲区溢出 data_len++; // 校验存储地址 if (recv_reg_addr == REG_ADDR) { // 地址为0x96,正常处理后续数据 } else if (recv_reg_addr == 0x94 || recv_reg_addr == 0x95) { // 地址为0x94或0x95,只做应答不做硬件操作 } else { // 其他地址,重置标志位并返回 isda = 1; // 重置设备地址标志 isma = 1; // 重置存储地址标志 I2CTXD = buffer[0]; // 准备发送数据 _pop_(P_SW2); // 恢复P_SW2寄存器值 return; // 直接返回,不处理后续数据 } I2CTXD = buffer[0]; // 准备发送数据 } // 否则,接收的是普通数据 else { recv_data = I2CRXD; // 读取接收到的数据 // 保存数据到缓冲区,以便后续处理和日志输出 buffer[buffer_idx++] = recv_data; if(buffer_idx >= 63) buffer_idx = 0; // 防止缓冲区溢出 data_len++; // 只有当存储地址为0x96时才进行硬件操作 if (recv_reg_addr_global == REG_ADDR) { // 根据接收到的数据设置PWM占空比(0x00-0xFF对应0-100%) SetPWM(recv_data); } } } // 处理SEND事件,当需要发送I2C数据时执行 else if (I2CSLST & I2C_EVENT_SEND) { I2CSLST &= ~I2C_EVENT_SEND; // 清除SEND事件标志 // 检查是否接收到NAK(从机未确认) if (I2CSLST & 0x02) { I2CTXD = 0xff; // 接收到NAK,停止发送,发送0xff } else { static unsigned char send_idx = 0; I2CTXD = buffer[send_idx++]; // 接收到ACK,继续发送下一个数据 if(send_idx >= data_len) send_idx = 0; } } // 处理STOP事件,当检测到I2C STOP信号时执行 else if (I2CSLST & I2C_EVENT_STOP) { I2CSLST &= ~I2C_EVENT_STOP; // 清除STOP事件标志 // 重置各种标志位,为下一次通信做准备 isda = 1; // 重置设备地址标志 isma = 1; // 重置存储地址标志 is_header_received = 0; // 重置帧头标志 // 保存接收到的数据到日志缓冲区,以便主循环批量打印 for(i=0; i= 127) log_idx = 0; // 防止日志缓冲区溢出 } // 清空缓冲区内容,确保每次发送完毕后缓冲区都是干净的 for(i=0; i<64; i++) { buffer[i] = 0; // 将缓冲区的每个字节设置为0 } buffer_idx = 0; // 重置缓冲区索引 data_len = 0; // 重置数据长度 } // 恢复P_SW2寄存器值,确保函数结束后系统状态正确 _pop_(P_SW2); } /** * @brief 主函数(修复for循环变量定义,解决编译错误) * @details 初始化硬件并进入主循环,实现系统的整体控制逻辑 * @param 无 * @return 无 */ void main() { // 提前定义循环变量(解决C51编译器不支持循环内定义变量的问题) unsigned char i; // 硬件初始化 HardwareInit(); // 配置GPIO模式 PWMInit(); // 初始化PWM模块 I2CInit(); // 初始化I2C从机模式 uart1_init(); // 初始化串口1,用于输出LOG信息 SetPWM(50); // 初始化PWM占空比为50% // P3.5电平切换逻辑(LCD_RESET) P3 |= 0x20; // 第一步:拉高P3.5 Delay30ms(); // 延迟30ms P3 &= ~0x20; // 第二步:拉低P3.5 Delay30ms(); // 延迟30ms P3 |= 0x20; // 第三步:再次拉高P3.5(最终保持高电平) // P3.7电平切换逻辑(TP_SHUTDOWN) P3 |= 0x80; // 第一步:拉高P3.7 Delay30ms(); // 延迟30ms P3 &= ~0x80; // 第二步:拉低P3.7 Delay30ms(); // 延迟30ms P3 |= 0x80; // 第三步:再次拉高P3.7(最终保持高电平) // LED常亮,BL_EN置高 P1 |= 0x04; // 设置P1.2为高电平,LED常亮 P3 |= 0x10; // 设置P3.4为高电平,BL_EN置高 // 输出初始化信息,通过UART1发送到上位机 LOG("System initialized\r\n"); // 系统初始化完成 // 主循环:批量打印日志,避免中断内串口耗时 while (1) { // 检查日志缓冲区是否有数据 if (log_idx > 0) { // 遍历日志缓冲区并打印(使用提前定义的i) for (i=0; i