436 lines
15 KiB
C
436 lines
15 KiB
C
/*
|
||
* 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();
|
||
}
|
||
}
|