1、题目
利用艾克姆科技 STC8A8K64D4 开发板套件和 STC 大学计划实验箱 STC8H8K64U 设计一个可由多终端控制的数据采集系统。 题目部分简称: A 板——艾克姆科技 STC8A8K64D4 开发板套件; B 板——STC 大学 计划实验箱 STC8H8K64U;三种模式——“2.2”节中的三种模式;三类按键——A 板矩阵 按键、B 板按键、遥控器按键;LCD——LCD12864。
器件:两种开发板各一套, A 板套件中的 PAJ7620 手势识别传感器、遥控器、跳线帽 杜邦线等耗材。
2、要求
2.1 通信部分
A 板和 B 板通过串口的方式通信,遥控器与 B 板通过红外的方式通信,A 板与遥控器 不直接通信
2.2 数据采集
在 A 板制作三种模式以采集数据,A 板的按键可以随时控制三种模式的切换。模式内 容如下:
模式 1:PAJ7620 检测到的“目标物体亮度”数据绘制对应的曲线实时绘制在 LCD 上。
模式 2:PAJ7620 检测到的“体积”数据绘制对应的曲线实时绘制在 LCD 上。
模式 3:旋转 A 板电位器,在 LCD 上实时绘制获取到的数据对应的曲线。
三种模式中被采集的数据(亮度、体积、ADC 值)应实时显示在数码管上。
2.3 采集间隔部分
三种模式的采集时间间隔可变(以毫秒为单位),三类按键均可以调整三种模式的数据 采集时间间隔。时间间隔的不同通过曲线的绘制速率体现,并将时间间隔显示在数码管上。
2.4 报警部分
三类按键均可以设定报警阈值,阈值对应的直线绘制在 LCD 上,超过阈值则通过 B 板 的 LED 发出警报。
2.5 拓展部分
拓展功能至少包含一个本题目未包含的单片机知识点或器件。
3、设计与分析
3.1 外设结构图
3.2外设
艾克姆科技 STC8A8K64D4 开发板套件
STC 大学计划实验箱 STC8H8K64U
PAJ7620 手势识别传感器
红外遥控器
其他外设皆集成在开发板上。
3.3 题目分析
- 根据题目,涉及到两套开发板的串口通信,A板作为主板,B板作为上位机主要下达两类指令——红外与按键;A板将超过阈值的信息传给B板,B板发出警报。
- 两类开发板均有涉及到数码管,所以会用到定时器中断显示数码管。
- 题目涉及到传感器数值与ADC电压值的读取,调用固定的函数。
- 读取的数值显示在数码管,在指定位的数码管显示变化的数值,动态显示(中断执行)。
- LCD12864绘制曲线,每读取一个数值绘制对应一个点,与前一个点连线。
- 采集间隔,使用延时函数。
- 阈值设定,主要是扫描指令,控制屏幕Y坐标。
3.4 I/O口设置
因为多数外设使用的是开发板上的集成好的外设,只需复制源代码即可,但其中A板上的LCD屏与板载的数码管引脚冲突,所以这里将数码管的引脚用杜邦线飞线到了其他没有占用的引脚
数码管:
sbit SEG_A0=P1^3; //74HC138µÚ1Òý½Å
sbit SEG_A1=P1^4; //74HC138µÚ2Òý½Å
sbit SEG_A2=P1^5; //74HC138µÚ3Òý½Å
sbit SEG_DATA=P7^3; //74HC595µÚ14Òý½Å
sbit SEG_SCK=P7^0; //74HC595µÚ11Òý½Å
sbit SEG_LCLK=P1^2; //74HC595µÚ12Òý½Å
串口通信采用的是UART2。Rx--------P10
Tx--------P11
LED灯:
D1----------P52
D2----------P53
4、具体设计过程
4.1 模式选择
A板代码基于艾克姆科技 STC8A8K64D4 开发板官方提供的PAJ7620 手势识别传感器代码更改添加。原版代码将目标物体亮度与体积值放在了一个函数中,为方便分别调用亮度值与体积值,首先将亮度与体积分成两个函数。
uint8 get_ps_result(uint16 *p_brightness)
{
uint8 temp;
temp = gs_read_byte(PAJ_OBJECT_BRIGHTNESS);//亮度
*p_brightness = (uint16)temp;
return GS_SUCCESS;
}
uint8 get_ps1_result(uint16 *p_size)
{
uint8 read_buf[2];
read_buf[0] = gs_read_byte(PAJ_OBJECT_SIZE_1);//大小
read_buf[1] = gs_read_byte(PAJ_OBJECT_SIZE_2);
*p_size = ((uint16)read_buf[1] & 0x0f)<<8 | read_buf[0];
return GS_SUCCESS;
}
在主函数循环调用两个函数读取传感器数值
if(get_ps_result(&obj_brightness) == GS_SUCCESS)
{
printf("obj_brightness: %d\r\n",obj_brightness);
temp_x++;
}
再调用ADC电压读取函数
float HandleADC1(void)
{
uint16 Temp_signal1;
float g_voltage;
Temp_signal1=HandleADC();
if((Temp_signal1<TEMPMAX)&&(Temp_signal1>TEMPMIN))
{
g_voltage=(2.5*Temp_signal1)/4096;
}
return g_voltage;
}
按照题目要求用按键控制三种模式,在主循环中对按键进行条件判断
button_num = Keys_Scan(0);
if(button_num == KEY1_ON)
{
ps_init();
leds_off();
printf("PS test started\r\n");
while(1)
{
if(get_ps_result(&obj_brightness) == GS_SUCCESS)
{
printf("obj_brightness: %d\r\n",obj_brightness);
}
ss[0]=obj_brightness/100;ss[1]=(obj_brightness%100)/10;ss[2]=obj_brightness%10;
delay_ms(80);
led_toggle(LED_1);
button_num = Keys_Scan(0);
if((button_num == KEY2_ON) || (button_num == KEY3_ON))break;
}
}
4.2 数码管显示获取的数值
题目要求将获取到数值显示在数码管上,其中亮度与体积最大值分别是255和900,ADC获取的电压值最大是2.5V,也就是说获取到数值最多有三位数,由于数码管一次中断只能显示一位数,所以要将获取到的数值按位拆分成三份,存入数组。
ss[0]=obj_brightness/100;ss[1]=(obj_brightness%100)/10;ss[2]=obj_brightness%10;
由于ADC电压值只有两位且带有小数点,带有小数点的数码管的“段选”值需要单独设置,所以,在选择模式三(ADC)时要设置一个全局变量作为标志位,在中断中判断该标志位,当中断已知是模式三时,第一位数的段选值要带有小数点。
if(button_num == KEY3_ON)
{
ps_init();
leds_off();
printf("ADC\r\n");
while(1)
{
adc_flag=1;//模式三的标志位
printf("\r\n ADC¶Ë¿Úµçѹֵ£º %.1f V\r\n",HandleADC1());
adc_num=HandleADC1();
integer_part=(int)adc_num;
decimal_part=(int)((adc_num-integer_part)*10);
ss[0]=integer_part;ss[1]=decimal_part;ss[2]=0;
delay_ms(80); //Èí¼þÑÓʱ100ms
led_toggle(LED_3);
button_num = Keys_Scan(0);
if((button_num == KEY1_ON) || (button_num == KEY2_ON))
{
adc_flag=0;//跳出模式三,标志位清空
break;
}
}
}
定时器中断服务函数
void Timer3_Isr(void) interrupt 19
{
cnt++; //2ms进入一次中断
if(cnt == 1) //2msË¢ÐÂÏÔʾÊýÂë¹Ü1λµÄÐÅÏ¢
{
cnt = 0;
if(adc_flag==0)
{
if(xyz==2)
seg_duan=duanxuan_point(ss[xyz]);
else
seg_duan=duanxuan(ss[xyz]);
}
else //显示ADC
{
if(xyz==0)
seg_duan=duanxuan_point(ss[xyz]);//带小数点
else
seg_duan=duanxuan(ss[xyz]);//不带小数点
}
SEG_Write_Data(seg_duan,seg_wei); //显示
SEG_Refresh(); //刷新
if(seg_wei>4)
seg_wei--;
else
seg_wei=7;
if(xyz<3)
xyz++;
else
xyz=0;
}
}
两种数码管段选函数
uint8 duanxuan(uint8 iii)//不带小数点
{
xuanduan=0;
switch(iii)
{
case 0: xuanduan = seg_num[0]; break;
case 9: xuanduan = seg_num[9]; break;
case 8: xuanduan = seg_num[8]; break;
case 7: xuanduan = seg_num[7]; break;
case 6: xuanduan = seg_num[6]; break;
case 5: xuanduan = seg_num[5]; break;
case 4: xuanduan = seg_num[4]; break;
case 3: xuanduan = seg_num[3]; break;
case 2: xuanduan = seg_num[2]; break;
case 1: xuanduan = seg_num[1]; break;
}
return xuanduan;
}
uint8 duanxuan_point(uint8 kkk)//带小数点
{
xuanduan=0;
switch(kkk)
{
case 0: xuanduan = seg_num[0]| 0x01; break;
case 9: xuanduan = seg_num[9]| 0x01; break;
case 8: xuanduan = seg_num[8]| 0x01; break;
case 7: xuanduan = seg_num[7]| 0x01; break;
case 6: xuanduan = seg_num[6]| 0x01; break;
case 5: xuanduan = seg_num[5]| 0x01; break;
case 4: xuanduan = seg_num[4]| 0x01; break;
case 3: xuanduan = seg_num[3]| 0x01; break;
case 2: xuanduan = seg_num[2]| 0x01; break;
case 1: xuanduan = seg_num[1]| 0x01; break;
}
return xuanduan;
}
4.3 采样速度数码管显示
题目要求将采样时间间隔显示在数码管上,显示传感器数值占用前三位数码管(7-5位),用第八位数码管(第0位)显示采样间隔。在矩阵键盘文件内设置全局变量(show_isr_speed),将对应按键下的时间间隔赋值给该变量,在中断服务函数中调用该变量;判断数码管是否显示了前三位(7-5位),显示前三位后“位选”值为0,显示第零位,在显示第0为数码管时,有关前三位数码管的例程通过条件判断不运行
void Timer3_Isr(void) interrupt 19
{
cnt++;
if(cnt == 1)
{
cnt = 0;
if(seg_wei==0)//第0位数码管显示采样速率
{
show_speed=show_isr_speed;
seg_duan=duanxuan(show_speed);
}
if(seg_wei != 0)//显示传感器数值
{
if(adc_flag==0)
{
if(xyz==2)
seg_duan=duanxuan_point(ss[xyz]);
else
seg_duan=duanxuan(ss[xyz]);
}
else
{
if(xyz==0)
seg_duan=duanxuan_point(ss[xyz]);
else
seg_duan=duanxuan(ss[xyz]);
}
}
SEG_Write_Data(seg_duan,seg_wei);
SEG_Refresh();
if(seg_wei>4)
seg_wei--;
else if(seg_wei==4)
seg_wei=0;
else
seg_wei=7;
if(seg_wei != 0)//显示采样率时不执行数组
{
if(xyz<3)
xyz++;
else
xyz=0;
}
}
4.4 LCD绘制波形
在LCD屏上画数据波形,为了使波形连续且平滑,主函数每读取一次传感器数值X轴加1,Y轴坐标与前一次Y坐标相连。当曲线到屏幕边沿时(127)刷屏。
绘制波形函数
void Port_DrawCurve(uint8 x,uint16 value)
{
static uint8 last_x,last_y;
uint16 temp_y=0;
temp_y=value/5;
if(x==0)
{
LCD_DrawDot(x,temp_y,1);
last_x=0;
last_y=temp_y;
}
else
{
LCD_DrawLine(last_x,last_y,x,temp_y);
last_x=x;
last_y=temp_y;
}
}
主函数
if(get_ps_result(&obj_brightness) == GS_SUCCESS)
{
printf("obj_brightness: %d\r\n",obj_brightness);
temp_x++;
}
if(temp_x>=128)
{
temp_x=0;
Fill_GDRAM(0x00); //ÇåÆÁ
DrawHline(1,127,1,1);
DrawVline(1,0,63,1);
DrawHline(0,127,yu,1);
}
Port_DrawCurve(temp_x,obj_brightness);
4.5 矩阵按键
四个按键分别对应三种模式和清屏暂停功能,设置采样间隔和阈值设定需要使用矩阵键盘。第一排(S3-S6)对应第一采样速度到第四采样速度;第二排(S7-S10)对应设置阈值、阈值加、阈值减、退出阈值设置。
采样间隔主要使用不同时间间隔的延时函数,以达到控制采样速率的效果。
获取调速键值
uint8 key_speed(void)
{
static uint8 last_speed;
uint8 temp1=0;
uint8 now_speed;
now_speed=KeyScan();
if(now_speed==1)
{
temp1=0;
last_speed=temp1;
}
else if(now_speed==2)
{
temp1=2;
last_speed=temp1;
}
else if(now_speed==3)
{
temp1=4;
last_speed=temp1;
}
else if(now_speed==4)
{
temp1=6;
last_speed=temp1;
}
else temp1=last_speed;
return temp1;
}
主函数调用
temp=key_speed();
if(temp != 0)
{
switch(temp)
{
case 2:
delay_ms(50);break;
case 4:
delay_ms(100);break;
case 6:
delay_ms(200);break;
}
}
设定阈值的模式就是将阈值加与阈值减放进一个while循环中,判断条件就是是否按下阈值设定的按键,按下后便判断是加阈值还是减阈值,当按下退出设置后跳出循环。退出时要清屏,将之前的阈值清掉。
uint8 key_top(void)
{
static uint8 last_top;
uint8 now_top;
now_top=KeyScan();
if(now_top==5)
{
top_flag=1;
}
if(now_top==8)
{
top_flag=0;
}
if(top_flag==1)
{
if(now_top==6)
{
top+=10;
last_top=top;
}
else if(now_top==7)
{
top-=10;
last_top=top;
}
else top=last_top;
}
return top;
}
主函数调用
key_yu=key_top();
while(key_yu==1)
{
key_yu=key_top();
if(key_yu==0)
{
Fill_GDRAM(0x00); //ÇåÆÁ
DrawHline(1,127,1,1);
DrawVline(1,0,63,1);
break;
}
if((KeyScan()==6)||(HW_module==14))
{
yu+=5;
}
if((KeyScan()==7)||(HW_module==15))
{
yu-=5;
}
DrawHline(0,127,yu,1);
}
4.6 B板按键
A板的功能几乎已经实现。
B板的代码是基于红外遥控接收程序(NEC码)进行的修改与增加。B板采用定时器2进行串口通信。红外遥控编码的接收通过定时器0中断实现。主函数循环中一直读取接收数值,设置按键“0”“100+”“200+”“EQ”分别实现三种模式与刷屏暂停的功能;按键“1”“2”“3”“4”分别对应四种采样间隔;“⏪”“⏩”“+”“-”分别对应“设置阈值”“退出阈值”“增大阈值”“减小阈值”功能。
当接收到遥控指令后,将指令赋值给定时器的SBUF寄存器,通过定时器2的中断服务函数发送出去。
if(B_IR_Press) //红外识别到按键
{
B_IR_Press = 0;
LED8[0] = (u8)((UserCode >> 12) & 0x0f); //Óû§Âë¸ß×ֽڵĸ߰ë×Ö½Ú
LED8[1] = (u8)((UserCode >> 8) & 0x0f); //Óû§Âë¸ß×ֽڵĵͰë×Ö½Ú
LED8[2] = (u8)((UserCode >> 4) & 0x0f); //Óû§ÂëµÍ×ֽڵĸ߰ë×Ö½Ú
LED8[3] = (u8)(UserCode & 0x0f); //Óû§ÂëµÍ×ֽڵĵͰë×Ö½Ú
LED8[6] = IR_code >> 4;
LED8[7] = IR_code & 0x0f;
S2BUF = IR_code;//将红外指令发送给A
B_TX2_Busy = 1;
while(B_TX2_Busy);
}
A板通过定时器2实现串口通信功能,接收到B板发送的遥控按键对应的十六进制数值,设置一个全局变量,通过给全局变量赋不同的值代表不同的按键指令。(在使用完红外遥控后再使用A板按键需要先按遥控的“CH-”使红外指令处于空指令状态,A板按键才能有效)
void UART2_Tx_Puts(void)
{
if(Flag2) //接收到数据
{
if(uart2temp==0x45)//空指令
{
HW_module=0;
}
if((uart2temp==0x16)||(uart2temp==0xC1))//MODULE
{
HW_module=1;
}
else if((uart2temp==0x19)||(uart2temp==0xC2))
{
HW_module=2;
}
else if((uart2temp==0x0d)||(uart2temp==0xC3))
{
HW_module=3;
}
else if((uart2temp==0x09)||(uart2temp==0xC4))
{
HW_module=4;
}
else if((uart2temp==0x0c)||(uart2temp==0xA1))//SPEED
{
HW_module=5;
}
else if((uart2temp==0x18)||(uart2temp==0xA2))
{
HW_module=6;
}
else if((uart2temp==0x5e)||(uart2temp==0xA3))
{
HW_module=7;
}
else if((uart2temp==0x08)||(uart2temp==0xA4))
{
HW_module=8;
}
else if((uart2temp==0xB1)||(uart2temp==0x44))//阈值
{
HW_module=13;
}
else if((uart2temp==0xB2)||(uart2temp==0x15))
{
HW_module=14;
}
else if((uart2temp==0xB3)||(uart2temp==0x07))
{
HW_module=15;
}
else if((uart2temp==0xB4)||(uart2temp==0x40))
{
HW_module=16;
}
Flag2=FALSE;
}
}
由于B板矩阵按键较少,所以B板按键采用ADC按键,每个按键设置键值,当主循环中条件判断到固定的键值时,就发送设定好的十六进制编码,A板接收。ADC按键设置了12个,功能与红外遥控相同。
if(KeyCode > 0) //检测到按键
{
if(KeyCode == 1) //模式
{
S2BUF = 0xC1;
B_TX2_Busy = 1;
while(B_TX2_Busy);
}
if(KeyCode == 2) //hour +1
{
S2BUF = 0xC2;
B_TX2_Busy = 1;
while(B_TX2_Busy);
}
if(KeyCode == 3) //hour +1
{
S2BUF = 0xC3;
B_TX2_Busy = 1;
while(B_TX2_Busy);
}
if(KeyCode == 4) //hour +1
{
S2BUF = 0xC4;
B_TX2_Busy = 1;
while(B_TX2_Busy);
}
if(KeyCode == 5) //采样间隔
{
S2BUF = 0xA1;
B_TX2_Busy = 1;
while(B_TX2_Busy);
}
if(KeyCode == 6) //hour -1
{
S2BUF = 0xA2;
B_TX2_Busy = 1;
while(B_TX2_Busy);
}
if(KeyCode == 7) //minute +1
{
S2BUF = 0xA3;
B_TX2_Busy = 1;
while(B_TX2_Busy);
}
if(KeyCode == 8) //minute -1
{
S2BUF = 0xA4;
B_TX2_Busy = 1;
while(B_TX2_Busy);
}
if(KeyCode == 9) //阈值
{
S2BUF = 0xB1;
B_TX2_Busy = 1;
while(B_TX2_Busy);
}
if(KeyCode == 10) //hour -1
{
S2BUF = 0xB2;
B_TX2_Busy = 1;
while(B_TX2_Busy);
}
if(KeyCode == 11) //minute +1
{
S2BUF = 0xB3;
B_TX2_Busy = 1;
while(B_TX2_Busy);
}
if(KeyCode == 12) //minute -1
{
S2BUF = 0xB4;
B_TX2_Busy = 1;
while(B_TX2_Busy);
}
if(KeyCode == 16) //空指令
{
S2BUF = 0xF0;
B_TX2_Busy = 1;
while(B_TX2_Busy);
}
KeyCode = 0;
}
当A板传感器数值超过阈值时通过串口发送个B板一个特定的十六进制指令同时A板上的LED4闪烁警报,B板收到指令后通过蜂鸣器发出警报。
A板
if((obj_brightness/5)>yu)
{
led_toggle(LED_4);
SendDataByUart2(0x01);
}
else
{
led_off(LED_4);
SendDataByUart2(0x02);
}
B板
if((TX2_Cnt != RX2_Cnt)&& (!B_TX2_Busy)) //接收到数据
{
if(RX2_Buffer[TX2_Cnt]==0x01)
{
BEEP=0;
delay_ms(250);
BEEP=1;
delay_ms(250);
}
else if(RX2_Buffer[TX2_Cnt]==0x02)
{
BEEP=1;
}
if(++TX2_Cnt >= UART2_BUF_LENGTH) TX2_Cnt = 0;
}
5、代码
本项目大致思路如上,经调试几乎可以达到题目要求效果,有些步骤可能过于繁琐或表述不清,还请批评指正。
下附两套代码,一套是A、B板完整的代码,一套是删减版,删减版包含功能:获取传感器数据、三种模式切换(A版基础按键和红外)、A板矩阵按键调速、B板报警(阈值不可调)
链接:https://pan.baidu.com/s/1zfW9rdHGPwiOEiP52g3CFw?pwd=fw9r
提取码:fw9r