如果一个控制板买了很多年而没有去理会,大概是因为太忙而无暇顾及。如果一旦闲下来,还是要翻出去来研究一下,算是对板子和自己一个交代(对,就是那个胶带)。
这个控制板主控是一个8脚的MCU,已打磨不知道型号,无法再利用了。
板上的数码管、按键、3个LED都是AIP650驱动。
还有红外接收和蜂鸣器。
这个板子驱动难度不大,但输入输出比较丰富,通用性很强,研究一下还是很有意义。
花了一些时间将主要的引脚引了出来,如图:

硬件已经OK,下面用STC8051U来驱动这个板子。
一、简单的调度系统
从STC论坛上学到的:
1、任务TASK定义
typedef struct
{
u8 Run; //任务状态:Run/Stop
u16 TIMCount; //定时计数器
u16 TRITime; //重载计数器
void (*TaskHook) (void); //任务函数
} TASK_COMPONENTS;
每个任务主要定义4个要素:
Run任务状态:0为不满足条件不被调用,1为即将被调用
TIMCount定时计数器:在定时器每次回调中计数器递减,直到为0时设置Run为1,等待执行。
TRITime重载计数器:在Task任务函数被执行后,将TIMCount重置为TRITime,开启下一次调度循环。
void (*TaskHook) (void);任务函数:Task被执行时运行的函数
static TASK_COMPONENTS Task_Comps[]=
{
//状态 计数 周期 函数
{0,10,10,key_Task},
{0,10,10,irrx_Task},
};
u8 Tasks_Max = sizeof(Task_Comps)/sizeof(Task_Comps[0]);
这里定义了2个Task,其中key_Task为按键相关处理,irrx_Task为红外接收相关处理
2、任务调度回调函数
主要实现在每个调度最小时间单位时对各Task的计数做减一操作。当计数为0时重载计数
void Task_Marks_Handler_Callback(void)
{
u8 i;
for(i=0; i<Tasks_Max; i++)
{
if(Task_Comps[i].TIMCount) /* If the time is not 0 */
{
Task_Comps[i].TIMCount--; /* Time counter decrement */
if(Task_Comps[i].TIMCount == 0) /* If time arrives */
{
/*Resume the timer value and try again */
Task_Comps[i].TIMCount = Task_Comps[i].TRITime;
Task_Comps[i].Run = 1; /* The task can be run */
}
}
}
}
此函数需要在定时器中断处理函数中调用
void timer1_int (void) interrupt 3
{
Task_Marks_Handler_Callback();
}
3、TASK函数执行
当Run为1时,执行TASK中定义的对应回调函数
void Task_Pro_Handler_Callback(void)
{
u8 i;
for(i=0; i<Tasks_Max; i++)
{
if(Task_Comps[i].Run) /* If task can be run */
{
Task_Comps[i].Run = 0; /* Flag clear 0 */
Task_Comps[i].TaskHook(); /* Run task */
}
}
}
此函数需要在main文件while(1)中调用
while(1){
Task_Pro_Handler_Callback();
}
4、Timer1初始化
应为调用处理中用到了Timer1,补充一下Timer1初始化:
#define Timer1_Reload (MAIN_Fosc / 2000) //Timer 1 中断频率, 2000次/秒
void Timer1_init(void)
{
TR1 = 0; //停止计数
#if (Timer1_Reload < 64) // 如果用户设置值不合适, 则不启动定时器
#error "Timer1设置的中断过快!"
#elif ((Timer1_Reload/12) < 65536UL) // 如果用户设置值不合适, 则不启动定时器
ET1 = 1; //允许中断
// PT1 = 1; //高优先级中断
TMOD &= ~0x30;
TMOD |= (0 << 4); //工作模式, 0: 16位自动重装, 1: 16位定时/计数, 2: 8位自动重装
// T1_CT = 1; //计数
T1_CT = 0; //定时
// T1CLKO = 1; //输出时钟
T1CLKO = 0; //不输出时钟
#if (Timer1_Reload < 65536UL)
T1x12 = 1; //1T mode
TH1 = (u8)((65536UL - Timer1_Reload) / 256);
TL1 = (u8)((65536UL - Timer1_Reload) % 256);
#else
T1x12 = 0; //12T mode
TH1 = (u8)((65536UL - Timer1_Reload/12) / 256);
TL1 = (u8)((65536UL - Timer1_Reload/12) % 256);
#endif
TR1 = 1; //开始运行
#else
#error "Timer1设置的中断过慢!"
#endif
}
5、key_Task
void key_Task(void)
{
static uint16_t num=0;
//static unsigned char i=0;
unsigned char key_value;
key_value=aip650_scan_key();
if(0xFF!=key_value)
{
delay_ms(100);
key_value=aip650_scan_key();
if(key_value==0x6c){
delay_ms(100);
key_value=aip650_scan_key();
if(key_value==0x6c) aip650_display_number(++num);
}else if(key_value==0x4c){
delay_ms(100);
key_value=aip650_scan_key();
if(key_value==0x4c) aip650_display_number(--num);
}else if(key_value==0x64){
delay_ms(100);
key_value=aip650_scan_key();
if(key_value==0x64){
//Buzzer_Control(1000, 500); // 1kHz,响0.5秒
//delay_ms(1000);
}
}
}
}
二、AIP650驱动
AIP650通常用于共阴极数码管和矩阵键盘的驱动。接口类似与I2C,但是没有I2C从地址。关于AIP650的原理和介绍,AIP659数据手册有详细的描述。这里只侧重于驱动这个板子上的数码管、按键、LED。
1、基本定义
// 端口定义 - 使用P3.2和P3.3
sbit AIP650_SCL = P3^2; // 时钟线
sbit AIP650_SDA = P3^3; // 数据线
根据这个板子的硬件连接情况,总结的真值表:
unsigned char code seg_table[] = {
0xFC, // 0
0x24, // 1
0xBA, // 2
0xAE, // 3
0x66, // 4
0xCE, // 5
0xDE, // 6
0xA4, // 7
0xFE, // 8
0xEE, // 9
0xF6, // A
0x5E, // b
0xD8, // C
0x3E, // d
0xDA, // E
0xD2, // F
0x80, // 小数点单独
0x00 // 全灭
};
2、软件I2C
I2C起始信号
// I2C起始信号
static void i2c_start(void)
{
AIP650_SDA = 1;
AIP650_SCL = 1;
delay_us(5);
AIP650_SDA = 0;
delay_us(5);
AIP650_SCL = 0;
}
I2C停止信号
// I2C停止信号
static void i2c_stop(void)
{
AIP650_SDA = 0;
AIP650_SCL = 1;
delay_us(5);
AIP650_SDA = 1;
delay_us(5);
}
I2C发送一个字节
// I2C发送一个字节,返回ACK(0:应答,1:非应答)
static unsigned char i2c_write_byte(unsigned char dat)
{
unsigned char i, ack;
for(i = 0; i < 8; i++)
{
if(dat & 0x80)
AIP650_SDA = 1;
else
AIP650_SDA = 0;
dat <<= 1;
delay_us(2);
AIP650_SCL = 1;
delay_us(5);
AIP650_SCL = 0;
delay_us(2);
}
// 释放SDA,读取ACK
AIP650_SDA = 1;
delay_us(2);
AIP650_SCL = 1;
delay_us(5);
ack = AIP650_SDA; // 读取应答位
AIP650_SCL = 0;
return ack;
}
I2C读取一个字节
// I2C读取一个字节,ack=0发送应答,ack=1发送非应答
static unsigned char i2c_read_byte(unsigned char ack)
{
unsigned char i, dat = 0;
AIP650_SDA = 1; // 释放总线
for(i = 0; i < 8; i++)
{
dat <<= 1;
AIP650_SCL = 1;
delay_us(5);
if(AIP650_SDA)
dat |= 1;
AIP650_SCL = 0;
delay_us(2);
}
// 发送应答/非应答
if(ack)
AIP650_SDA = 1; // 非应答
else
AIP650_SDA = 0; // 应答
delay_us(2);
AIP650_SCL = 1;
delay_us(5);
AIP650_SCL = 0;
AIP650_SDA = 1; // 释放
return dat;
}
3、AIP650控制
AIP650初始化
// AIP650初始化
void aip650_init(void)
{
AIP650_SCL = 1;
AIP650_SDA = 1;
delay_us(10);
// 开启显示
aip650_display_on();
// 设置亮度为6
aip650_set_brightness(6);
}
开启关闭显示、设置亮度
// 开启显示
void aip650_display_on(void)
{
i2c_start();
//i2c_write_byte(0xC0); // 设备地址+写
i2c_write_byte(0x48); // 亮度寄存器(示例地址)
i2c_write_byte(0x01); // 开启显示
i2c_stop();
}
// 关闭显示
void aip650_display_off(void)
{
i2c_start();
//i2c_write_byte(0xC0);
i2c_write_byte(0x48);
i2c_write_byte(0x00); // 关闭显示
i2c_stop();
}
// 设置亮度 (0-7)
void aip650_set_brightness(unsigned char level)
{
if(level > 7) level = 7;
i2c_start();
// i2c_write_byte(0xC0);
i2c_write_byte(0x48); // 亮度控制寄存器
i2c_write_byte(0x01|level<<4);
//i2c_write_byte(0x88 | level); // 开启显示+亮度
i2c_stop();
}
4、显示0-f字符
void aip650_display_char(unsigned char pos, unsigned char ascii_char)
{
unsigned char seg_data = 0x00;
unsigned char i;
// 判断是否有小数点
if(ascii_char == '.')
{
seg_data = 0x80; // 只显示小数点
}
else if(ascii_char >= '0' && ascii_char <= '9')
{
seg_data = seg_table[ascii_char - '0'];
}
else if(ascii_char >= 'A' && ascii_char <= 'F')
{
seg_data = seg_table[10 + (ascii_char - 'A')];
}
else if(ascii_char >= 'a' && ascii_char <= 'f')
{
seg_data = seg_table[10 + (ascii_char - 'a')];
}
else
{
seg_data = seg_table[17]; // 全灭
}
// 发送到AIP650
i2c_start();
switch(pos)
{
case 0:
i2c_write_byte(0x68);
break;
case 1:
i2c_write_byte(0x6C);
AIP_VAL[1]=seg_data|(AIP_VAL[1]&0x01);
break;
case 2:
i2c_write_byte(0x6A);
AIP_VAL[2]=seg_data|(AIP_VAL[2]&0x01);
break;
case 3:
i2c_write_byte(0x6E);
AIP_VAL[3]=seg_data|(AIP_VAL[3]&0x01);
break;
}
i2c_write_byte(seg_data);
i2c_stop();
}
5、显示数字
void aip650_display_number(unsigned int num)
{
unsigned char digits[4];
digits[3] = (num / 100) % 10;
digits[2] = (num / 10) % 10;
digits[1] = num % 10;
aip650_display_char(1, digits[1] + '0');
aip650_display_char(2, digits[2] + '0');
aip650_display_char(3, digits[3] + '0');
//将显示真值保存下来
AIP_VAL[3]=seg_table[digits[3]];
AIP_VAL[2]=seg_table[digits[2]];
AIP_VAL[1]=seg_table[digits[1]];
}
6、扫描按键
unsigned char aip650_scan_key(void)
{
unsigned char key_value = 0xFF;
// 起始信号
i2c_start();
// 发送读键命令 0x4F
if(i2c_write_byte(0x4F))
{
i2c_stop();
return 0xFF;
}
// 读取按键值(发送0x00提供时钟,最后发NACK)
key_value = i2c_read_byte(1);
// 停止
i2c_stop();
return key_value;
}
7、驱动3个LED
void aip650_led(unsigned char pos,unsigned char on)
{
i2c_start();
switch(pos)
{
case 1:
i2c_write_byte(0x6C); //DIG2
break;
case 2:
i2c_write_byte(0x6A); //DIG1
break;
case 3:
i2c_write_byte(0x6E); //DIG3
break;
}
//真值最后一位对应led,与数码管的真值取或,使2者同时显示
if(on==1){
i2c_write_byte(AIP_VAL[pos]|0x01);
}else
{
i2c_write_byte(AIP_VAL[pos]&~0x01);
}
i2c_stop();
}
三、红外接收驱动
这部分应该时通用的:
#include <STC8051U.h>
#include "main.h"
#include "aip650.h"
// 引脚定义
#define IR_INPUT P32 // 红外接收头连接到P3.2 (INT0)
// 全局变量
unsigned int ir_time; // 存储时间测量值
unsigned char ir_state; // 解码状态机状态
unsigned char ir_data[4]; // 存储4字节数据: [用户码, 用户反码, 按键码, 按键反码]
unsigned char ir_data_index; // 数据位索引(0-31)
bit ir_data_ready; // 数据接收完成标志
bit ir_repeat_flag; // 重复码标志
unsigned char ir_address; // 解码后的地址码
unsigned char ir_command; // 解码后的命令码
// 定时器0初始化 - 用于测量时间
void Timer0_Init(void) {
TMOD &= 0xF0; // 清除定时器0设置
TMOD |= 0x01; // 模式1: 16位定时器
TH0 = 0;
TL0 = 0;
TF0 = 0;
TR0 = 0; // 初始不启动
}
// 定时器0启动/停止控制
void Timer0_Run(bit flag) {
TR0 = flag;
}
// 设置定时器计数值
void Timer0_SetCounter(unsigned int value) {
TH0 = value >> 8;
TL0 = value & 0xFF;
}
// 获取定时器当前计数值
unsigned int Timer0_GetCounter(void) {
return (TH0 << 8) | TL0;
}
void PortInt_Init(void)
{
P4INTE = 0x08; //使能P4口中断
P4IM0 = 0x00; //设置P4口中断模式 (00:下降沿, 01:上升沿)
P4IM1 = 0x00; //设置P4口中断模式 (10:低电平, 11:高电平)
P4WKUE = 0x00; //设置P4口中断唤醒省电模式
}
// 红外接收初始化
void IR_Init(void) {
Timer0_Init();
PortInt_Init();
ir_state = 0;
ir_data_ready = 0;
ir_repeat_flag = 0;
}
// 获取接收到的地址码
unsigned char IR_GetAddress(void) {
return ir_address;
}
// 获取接收到的命令码
unsigned char IR_GetCommand(void) {
return ir_command;
}
// 检查是否有新数据
bit IR_IsDataReady(void) {
if(ir_data_ready) {
ir_data_ready = 0;
return 1;
}
return 0;
}
// 检查是否收到重复码
bit IR_IsRepeat(void) {
if(ir_repeat_flag) {
ir_repeat_flag = 0;
return 1;
}
return 0;
}
void Port4_Isr(void) interrupt 41{
// 24MHz主频下,定时器每计数1代表0.5μs
// 因为12T模式下,一个机器周期 = 12/24MHz = 0.5μs
P50=~P50;
P4INTE = 0x00; // 禁用所有P4口中断
if(P4INTF & 0x08) { // 判断是否是P4.3中断
P4INTF &= ~0x08; // 清除P4.3的中断标志位
switch(ir_state) {
case 0: // 空闲状态,等待第一个下降沿
Timer0_SetCounter(0);
Timer0_Run(1);
ir_state = 1;
break;
case 1: // 等待引导码结束或检测重复码
ir_time = Timer0_GetCounter();
Timer0_SetCounter(0);
// 检测引导码(9ms低+4.5ms高 = 13.5ms)
// 13.5ms / 0.5μs = 27000
if(ir_time > 26500 && ir_time < 27500) {
ir_state = 2; // 开始接收数据
ir_data_index = 0;
}
// 检测重复码(9ms低+2.25ms高 = 11.25ms)
// 11.25ms / 0.5μs = 22500
else if(ir_time > 22000 && ir_time < 23000) {
ir_repeat_flag = 1; // 设置重复码标志
Timer0_Run(0);
ir_state = 0;
}
else {
ir_state = 1; // 无效信号,继续等待
}
break;
case 2: // 接收数据位
ir_time = Timer0_GetCounter();
Timer0_SetCounter(0);
// 判断是"0"还是"1"
// 逻辑0: 560μs低 + 560μs高 = 1120μs
// 1120μs / 0.5μs = 2240
if(ir_time > 2000 && ir_time < 2500) {
// 收到"0",对应位清0
ir_data[ir_data_index / 8] &= ~(0x01 << (ir_data_index % 8));
ir_data_index++;
}
// 逻辑1: 560μs低 + 1680μs高 = 2240μs
// 2240μs / 0.5μs = 4480
else if(ir_time > 4200 && ir_time < 4800) {
// 收到"1",对应位置1
ir_data[ir_data_index / 8] |= (0x01 << (ir_data_index % 8));
ir_data_index++;
}
else {
// 时间异常,重新开始
ir_data_index = 0;
ir_state = 1;
}
// 接收完32位数据
if(ir_data_index >= 32) {
// 校验数据:第2字节应该是第1字节的反码
// 第4字节应该是第3字节的反码
if((ir_data[1] == (unsigned char)~ir_data[0]) &&
(ir_data[3] == (unsigned char)~ir_data[2])) {
// 数据有效
ir_address = ir_data[0]; // 地址码
ir_command = ir_data[2]; // 命令码
ir_data_ready = 1;
}
// 无论是否有效,都重置状态机
Timer0_Run(0);
ir_data_index = 0;
ir_state = 0;
}
break;
}
P4INTE = 0x08; // 重新使能P4.3中断
}
}
void irrx_Task(void)
{
unsigned char last_cmd = 0;
if(IR_IsRepeat()) {
// 按键被长按,可以执行连续动作
// 例如:音量连续增加
// P1_0 = ~P1_0; // LED闪烁提示
}
// 检查是否有新的按键数据
if(IR_IsDataReady()) {
unsigned char addr = IR_GetAddress();
unsigned char cmd = IR_GetCommand();
P27=~P27;
// 根据按键码执行不同操作
if(cmd != last_cmd) {
last_cmd = cmd;
printf("cmd:%d\r\n",cmd);
//显示键值
if(((cmd&0xf0)>>4)<10)
{
aip650_display_char(2,((cmd&0xF0)>>4)+'0');
}else
{
aip650_display_char(2,((cmd&0xF0)>>4)-10+'a');
}
if(((cmd&0x0f)<10))
{
aip650_display_char(1,(cmd&0x0F)+'0');
}else
{
aip650_display_char(1,(cmd&0x0F)-10+'a');
}
Buzzer_Control(1000, 500);
delay_ms(500);
switch(cmd)
{
case 0x07:
//aip650_led(3,0);
aip650_led(1,1);
break;
case 0x03:
//aip650_led(3,0);
aip650_led(2,1);
break;
case 0x13:
aip650_led(3,1);
break;
case 0x0b:
P04=~P04;
break;
case 0x50:
P05=~P05;
break;
case 0x0f:
P06=~P06;
break;
}
// 这里添加你的控制逻辑
// 例如:控制LED、电机、显示等
// P1 = ~cmd; // 示例:在P1口显示按键码的反码
}
}
}
四、蜂鸣器驱动
这部分应该时通用的:
#include <STC8051U.h>
#include "main.h"
// 蜂鸣器引脚定义(根据实际连接修改)
#define BUZZER_PIN P40 // 假设蜂鸣器连接到P1.0
// 全局变量
unsigned int buzzer_frequency = 0; // 当前频率(Hz)
unsigned int buzzer_duration = 0; // 持续时间(ms)
bit buzzer_enable = 0; // 蜂鸣器使能标志
unsigned int buzzer_count = 0; // 定时器计数(用于持续时间控制)
unsigned int buzzer_timer_reload = 0; // 定时器重载值
// Timer3初始化(用于蜂鸣器PWM输出)
void Timer3_Init_Buzzer(void) {
T3R = 0; //停止计数
ET3 = 1; //允许中断
T3_CT = 0; //定时
T3CLKO = 0; //不输出时钟
T3R = 1; //开始运行
}
// 启动Timer3
void Timer3_Start(void) {
T3R = 1; //开始运行
}
// 停止Timer3
void Timer3_Stop(void) {
T3R = 0; //开始运行
}
// 设置Timer3重载值
void Timer3_SetReload(unsigned int value) {
T3x12 = 1;
T3H = value >> 8; // 高8位
T3L = value & 0xFF; // 低8位
}
// 计算定时器重载值(用于蜂鸣器频率)
unsigned int CalculateBuzzerTimerValue(unsigned int freq) {
unsigned long timer_count;
if (freq == 0) return 0;
// 计算公式:定时器计数 = 主频 / (12 * 频率 * 2)
// 24MHz主频,12T模式,产生方波需要翻转两次
timer_count = 24000000UL / (12UL * freq * 2);
// 转换为定时器重载值(65536 - 计数值)
return 65536 - timer_count;
}
// 蜂鸣器控制函数
// freq: 频率(Hz),0表示关闭
// duration: 持续时间(ms),0表示持续响
void Buzzer_Control(unsigned int freq, unsigned int duration) {
unsigned int timer_value;
if (freq == 0) {
// 关闭蜂鸣器
buzzer_enable = 0;
Timer3_Stop(); // 停止Timer3
BUZZER_PIN = 0; // 拉低引脚(假设低电平不响)
return;
}
// 计算并设置定时器重载值
timer_value = CalculateBuzzerTimerValue(freq);
if (timer_value == 0 || timer_value >= 65536) return;
// 保存重载值和参数
buzzer_timer_reload = timer_value;
buzzer_frequency = freq;
buzzer_duration = duration;
buzzer_count = 0;
// 设置定时器初值并启动
Timer3_SetReload(timer_value);
Timer3_Start();
buzzer_enable = 1;
}
// Timer3中断服务程序
void Timer3_ISR(void) interrupt 19 {
static unsigned int ms_counter = 0;
static unsigned int half_period_count = 0;
if (!buzzer_enable) return;
// 重新加载定时器值(自动重载模式下,硬件会自动重载)
// 但为了确保,也可以手动重载
// Timer3_SetReload(buzzer_timer_reload);
// 翻转蜂鸣器引脚,产生方波
BUZZER_PIN = ~BUZZER_PIN;
// 持续时间控制
if (buzzer_duration > 0) {
half_period_count++;
// 每两次翻转(一个完整周期)计数一次
if (half_period_count >= 2) {
half_period_count = 0;
// 计算经过了多少毫秒
// 每毫秒需要的周期数 = 频率 / 1000
ms_counter++;
if (ms_counter >= 1) { // 每1ms检查一次
ms_counter = 0;
buzzer_count++;
// 达到指定持续时间
if (buzzer_count >= buzzer_duration) {
buzzer_enable = 0;
Timer3_Stop();
BUZZER_PIN = 0; // 关闭蜂鸣器
buzzer_count = 0;
}
}
}
}
}
// 音阶频率定义
#define DO 262
#define RE 294
#define MI 330
#define FA 349
#define SOL 392
#define LA 440
#define SI 494
// 音阶表
unsigned int tone_freq[] = {
DO, RE, MI, FA, SOL, LA, SI
};
// 播放提示音函数
// tone: 音调编号 0-6 (Do, Re, Mi, Fa, Sol, La, Si)
// duration: 持续时间(ms)
void Buzzer_PlayTone(unsigned char tone, unsigned int duration) {
if (tone < 7) {
Buzzer_Control(tone_freq[tone], duration);
}
}
// 蜂鸣器初始化
void Buzzer_Init(void) {
BUZZER_PIN = 0; // 初始关闭
Timer3_Init_Buzzer(); // 初始化Timer3
}
// 播放一段旋律(示例)
void Buzzer_PlayMelody(void) {
// 播放"小星星"片段
Buzzer_PlayTone(0, 400); // Do
delay_ms(100);
Buzzer_PlayTone(0, 400); // Do
delay_ms(100);
Buzzer_PlayTone(4, 400); // Sol
delay_ms(100);
Buzzer_PlayTone(4, 400); // Sol
delay_ms(100);
Buzzer_PlayTone(5, 800); // La
delay_ms(200);
Buzzer_PlayTone(5, 800); // La
delay_ms(200);
Buzzer_PlayTone(4, 1600); // Sol
delay_ms(400);
}
五、实际运行效果



