闪烁可以使用取反的方法
LedInfo = ~LedInfo; // 闪烁3次
依次点亮,或依次熄灭可以使用 左移或右移
LedInfo = LedInfo << 1; // 依次点亮
当顺序实现不同情况时,使用状态机FSM,有利理解及代码实现。
case 0: // 默认状态为3次闪烁
LedInfo = ~LedInfo;
Led(LedInfo);
if (LedCounter++ == 5) // 计到5时,正好是3次亮灭重复出现
{
LedStatus = 1; // 切换到下一个状态机
LedCounter = 0; // 计数归0
LedInfo = 0xff; // 设置下个状态的LED状态
}
break;
case 1: // 依次点亮情况
LedInfo = LedInfo << 1; // 左移
Led(LedInfo); // 点亮
if (++LedCounter == 8) // 一共8位LED,所以计数8
{
LedStatus = 2; // 切换状态机
LedCounter = 0; // 计数归0
BoolRelay = 1; // 依次点亮LED后,就是操作继电器,所以设定继电器状态启用
}
break;
case 2: // 等待状态,等待继电器 吸合 再断开后,进行依次熄灭LED的中间过渡
LedInfo = 0x00; // 因为依次熄灭使用的是取反操作,所以初始状态设为0x00。
if (!BoolRelay) // 继电器关闭,进入LED下一状态
LedStatus = 3;
break;
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/mouze2000/article/details/141139315
数码管的显示,建议使用状态机FSM。另外,数码管间隔显示时间1ms为宜。
void DynamicSeg()
{
switch (segStatus)
{
case 0: // 2,第1个显示
PortEnabled = 0x01;
OneSegDisplay(ShowInfos[segStatus], PortEnabled);
segStatus = 1;
break;
case 1: // 0,第2个显示
PortEnabled = 0x02; // 换成 PortEnabled = PortEnabled << 1会出现重影,未解决。
OneSegDisplay(ShowInfos[segStatus], PortEnabled);
segStatus = 2;
break;
case 2: // 1,第3个显示,
PortEnabled = 0x04;
OneSegDisplay(ShowInfos[segStatus], PortEnabled);
segStatus = 3;
break;
case 3: // 8,第4个显示
PortEnabled = 0x8;
OneSegDisplay(ShowInfos[segStatus], PortEnabled);
segStatus = 4;
break;
case 4: // -,第5个显示
PortEnabled = 0x10;
OneSegDisplay(ShowInfos[segStatus], PortEnabled);
segStatus = 5;
break;
case 5: // -,第6个显示
PortEnabled = 0x20;
OneSegDisplay(ShowInfos[segStatus], PortEnabled);
segStatus = 6;
break;
case 6: // 月份十位
PortEnabled = 0x40;
OneSegDisplay(ShowMonth / 10, PortEnabled); // 十位固定显示0
segStatus = 7;
break;
case 7: // 月份个位
PortEnabled = 0x80;
OneSegDisplay(ShowMonth % 10, PortEnabled); // 个位显示
segStatus = 8;
break;
case 8:
if (BoolHalfOneSecond) // 如果达到设定的增长间隔时间,这里用第2个定时器Timer1也可以实现。
{
ShowMonth++;
BoolHalfOneSecond = 0;
}
if (ShowMonth == 13) // 如果1-12已经循环显示完,重新循环
{
ShowMonth = 1;
}
segStatus = 0; // 回到状态0
break;
default:
segStatus = 0;
PortEnabled = 0x01;
ShowMonth = 1;
break;
}
}
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/mouze2000/article/details/141168052
加Delay1ms()的:
switch (arrayStatus)
{
case 0:
min10Seg1(stopWatchTimer.min10);
Delay1ms();
arrayStatus = 1;
break;
case 1:
min1Seg2(stopWatchTimer.min1);
Delay1ms();
arrayStatus = 2;
break;
case 2:
dashSeg3();
Delay1ms();
arrayStatus = 3;
break;
case 3:
sec10Seg4(stopWatchTimer.sec10);
Delay1ms();
arrayStatus = 4;
break;
case 4:
sec1Seg5(stopWatchTimer.sec1);
Delay1ms();
arrayStatus = 5;
break;
case 5:
dashSeg6();
Delay1ms();
arrayStatus = 6;
break;
case 6:
ms10Seg7(stopWatchTimer.ms10);
Delay1ms();
arrayStatus = 7;
break;
case 7:
ms1Seg8(stopWatchTimer.ms1);
Delay1ms();
arrayStatus = 0;
break;
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/mouze2000/article/details/141267009
需要锁定某个状态时,比如长按不松手,建议使用状态机FSM,加一个下降沿变量锁定。
case 1:
if ((k7 == 0) || (k6 == 0)) // 如果S6、S7按下,产生 下降沿,
{
keyStatus = 2;
Falling = 1;
}
if (((k5 == 0) || (k4 == 0)) && (selectEnableS6 || selectEnableS7)) // 如果S5、S4按下,同时是在S7或S6已经按下的情况,产生 下降沿,
{
keyStatus = 3;
Falling = 1;
}
selectEnableS6 = 1; // 初始化S6启用状态
selectEnableS7 = 1; // 初始化S7启用状态
break;
case 2:
if ((k7 == 0) && selectEnableS7) // S7按下低电平,同时满足启用状态(用于对应题目中"S6、S7不可响应"操作的要求)
{
selectedValue = 7; // 根据按键值对应返回值
if (Falling) // 第1次按下时,即下降沿时
{
ledEnableS7 = ~ledEnableS7; // 用于控制点亮LED的标记,长按不翻转
}
}
else if ((k6 == 0) && selectEnableS6) // 同S7
{
selectedValue = 6;
if (Falling)
{
ledEnableS6 = ~ledEnableS6;
}
}
if ((k7 == 0) || (k6 == 0)) // 按键不松开,停在当前状态,长按状态,在测试时,如果不区别长按短按,LED灯会闪烁,因为程序中有翻转情况
{
keyStatus = 2; // 停留在当前状态
Falling = 0; // 没有下降沿,
}
else
{
keyStatus = 3;
Falling = 1;
}
break;
case 3:
if (k5 == 0) // 同S7、S6
{
controlValue = 5;
if (Falling)
{
ledEnableS5 = ~ledEnableS5;
}
}
else if (k4 == 0) // 同S7、S6
{
controlValue = 4;
if (Falling)
{
ledEnableS4 = ~ledEnableS4;
}
}
if ((k5 == 0) || (k4 == 0)) // 按键不松开,停在当前状态
{
keyStatus = 3;
Falling = 0;
}
else
keyStatus = 4;
break;
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/mouze2000/article/details/141193720
if ((s4 == 0) | (s5 == 0)) // 如果长按锁定在当前状态
{
falling = 0;
keyStatus = 1;
}
else
keyStatus = 2;
break;
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/mouze2000/article/details/141267009
矩阵键盘,可以使用行列组成一个8位变量来进行检测状态,
uint8_t GetPortState()
{
uint8_t portState;
portState = P3 & 0x3F; // 0x3F: 0011 1111
(col2) ? (portState |= 0x40) : (portState &= 0xbf); // 0x40: 0100 0000,0xbf:1011 1111
(col1) ? (portState |= 0x80) : (portState &= 0x7f); // 0x80: 1000 0000, 0x7f: 0111 1111
return portState;
}
v
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/mouze2000/article/details/141217673
也可以使用单独列、行来实现检测。
// 矩阵键盘 行
sbit row1 = P3 ^ 0;
sbit row2 = P3 ^ 1;
sbit row3 = P3 ^ 2;
sbit row4 = P3 ^ 3;
// 矩阵键盘 列
sbit col1 = P4 ^ 4;
sbit col2 = P4 ^ 2;
sbit col3 = P3 ^ 5;
sbit col4 = P3 ^ 4;
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/mouze2000/article/details/141218118
为了对应矩阵的数值,可以自定义一下二维数组,注意,数组大小要定义。
// 从左到右,从上到下,依次显示0~F,定义一个二维数组,注意数组大小一定要写,不然会报销
code uint8_t ArrayNum[4][4] = {
{0, 1, 2, 3},
{4, 5, 6, 7},
{8, 9, 10, 11},
{12, 13, 14, 15}};
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/mouze2000/article/details/141217673
按键消抖,就是定时器,或者Delay1ms()函数。
if (counterEnable) // 达到计数间隔,即消抖
{
KeyScan();
if (keyValue == 1) // S7对应L1,
ledInfo = ~1;
else if (keyValue == 2) // S6对应L2
ledInfo = ~2;
else if (keyValue == 3) // S5对应L3
ledInfo = ~4;
else if (keyValue == 4) // S4对应L4
ledInfo = ~8;
else if (keyValue == 255) // 抬起按键
ledInfo = ~0;
LED(ledInfo);
counterEnable = 0;
}
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/mouze2000/article/details/141176358
外部中断,主要是看原理图中找到的复用按键,S5/S4、P32、P33
void INT0_ISR() interrupt 0
{
if (KeyS5 == 0)
{
interruptEnalbe = 1;
LightLed8(); // 这个可以写到外面,比如增加Led8Proc()函数,将LightLe8与OffLed8作为子函数调用。
}
else
interruptEnalbe = 0;
}
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/mouze2000/article/details/141230458
某个LED灯,或某端口某位不需要改变,同一端口另一位需要变动时,可以直接对端口使用与或运算。注意使用条件,端口数据不是实时变动。
case 0:
LED(P0 & L1); // 点亮L1
if (EnableHalfSec) // 0.5s延时
{
StatusHalfSec = 1;
EnableHalfSec = 0;
}
break;
case 1:
LED(P0 | 0x01); // 熄灭L1
if (EnableHalfSec) // 0.5秒延时
{
StatusHalfSec = 0;
EnableHalfSec = 0;
}
break;
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/mouze2000/article/details/141231326
端口数据实时变动的例子:
因为程序前面实时操作数码管显示时间,所以P0口数据不停变动。
所以在LED灯这里,只能定义一个变量LedInfo,保存上个传到P0口操作LED灯的数值,而不会受到数码管P0口数据的影响。
if (s5LedOnOff)
{
ledInfo = ledInfo & L7;
// 注意,不能使用P0口直接运算,比如P0&L7是不行的,因为数码管在不停变动,所以P0的数字也是在不停变动。
}
else
ledInfo = ledInfo | 0x40;
lightLED(ledInfo);
}
else if (keyValue == 4) // L8
{
if (s4LedOnOff)
{
ledInfo &= L8;
}
else
ledInfo |= 0x80;
lightLED(ledInfo);
}
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/mouze2000/article/details/141304759
在串口数据这儿也是,增加一个临时变量tmpInfo2,保存前一状态。
uint8_t tmpInfo1, tmpInfo2;
tmpInfo2 = ledInfo; // 保存当前ledinfo状态,用于后续输入不同A0、A1指令,可以控制亮灭
if ((recv_data & 0xF0) == 0xA0) // 0xA?
{
tmpInfo1 = ~(recv_data & 0x0f); // 计算当前指令,比如A1,对应操作为1111 1110
ledInfo = ledInfo & tmpInfo1; // 与原来的led状态进行与运算,比如S5控制的L7已经亮灯,
// 再与tmpInfo1进行与运算,L1、L7同时亮灯
lightLED(ledInfo);
ledInfo = tmpInfo2; // 恢复操作前的led状态信息
// 这里其实是个讨巧的方法,相当于:输入A1时,每次进入本函数后,重新运算一下
// 主要目的就是保证原来的S5-L7、S4-L8的亮灯状态不受影响。
}
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/mouze2000/article/details/141304759
每个模块的单独练习正常了,不代表综合运用会没问题。
工厂灯光控制系统这个项目中,数码管,LED灯,串口,每个单独运用没问题,但放在一起,因为P0口是复用的,所以状态信息在不停变,需要建立一个变量对应每个模块来保存当前模块的上一个状态。