Files

436 lines
15 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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<data_len; i++)
{
log_buffer[log_idx++] = buffer[i]; // 将接收到的数据复制到日志缓冲区
if(log_idx >= 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<log_idx; i++)
{
LOG("%c", log_buffer[i]); // 打印日志缓冲区中的每个字符
}
// LOG("\n"); // 打印换行符,结束当前日志行
log_idx = 0; // 清空日志缓冲区索引,准备接收新的日志数据
}
Delay10us();
}
}