如何做酒店网站,上海景泰建设有限公司网站,基层建设论文查询官方网站,如何给网站添加网站地图不同数据类型间的相互转换 在C语言中#xff0c;不同数据类型之间是可以混合运算的。当表达式中的数据类型不一致时#xff0c;首先转换为同一类型#xff0c;然后再进行计算。C语言有两种方式实现类型转换。一是自动类型转换#xff0c;另外一种是强制类型转换。 转换的主…不同数据类型间的相互转换 在C语言中不同数据类型之间是可以混合运算的。当表达式中的数据类型不一致时首先转换为同一类型然后再进行计算。C语言有两种方式实现类型转换。一是自动类型转换另外一种是强制类型转换。 转换的主要原则短字节的数据向长字节数据转换。 比如 unsigned char a; unsigned int b; unsigned int c; c a * b;
如果 a 10; b 200; 那么C的结果就是2000。 那么当 a 100 , b 700 ,C会是70000吗 unsigned int 的范围是0 ~65535但是70000超过了65535其结果就会溢出 最终C的结果是70000 - 65536 4464 这个结果具体和你的编译器有关不同的编译器结果可能不一样。1111 1111 1111 1111 这个二进制表达的数组是65535也就是16位数据能表达的最大数值70000的二进制是0001 0001 0001 0111 0000因为它被限制只能储存16位的数据因此高4位被砍掉了C显示的就是低16的数据 0001 0001 0111 0000 4464 要想C正常获得70000这个结果需要把C定义成一个unsigned long型
比如 unsigned char a; unsigned int b; unsigned long c; c a * b;
发现结果仍是4464 C语言不同类型运算的时候数组会转换为同一类型运算但是每一步运算都会进行识别判断但不会进行一个总的分析判断。上述 a * b 的时候是按照unsigned int运算的那么运算的结果也是unsigned int运算的结果就会是unsigned int类型的4464最终就是把unsigned int的4464赋值给了一个unsigned long型的变量而已。若想避免产生此类问题就需要采用强制类型转换变量。
所谓强制类型转换就是在一个变量前面加上一个数据类型名且这个类型名用小括号括起来如
C unsigned long)a * b; 由于强制类型转换运算符优先级高于*所以先把a转换成unsigned long型的变量而后与b相乘。根据C语言的规则b会自动转换成一个unsigned long型的变量而后运算结果也是unsigned long型了最后赋值给C。
但是 c (unsigned long)(a*b) 的结果依然是4464注意区分圆括号的运算优先级是最高的。
在51单片机里有一种特殊情况就是bit类型的变量这个bit类型的强制类型转换是不符合上边讲的这个原则的比如
bit a 0; unsigned char b ; a (bit)b;
使用bit做强制类型转换不是取b的最低位而是它会判断b这个变量是0还是非0的值如果b是0那么a的结果就是0如果b是任意非0的其它值那么a的结果都是1.
秒表功能分析
首先笔者的单片机开发版数码管共6个秒表的精度要求达到小数点后2位。
首先计算秒表的精度99ii 1s i10ms精度是10ms
显然如果精度达到小数点后3位 999i i 1s i1ms 精度是1ms
因此6个数码管前4个显示秒表的整数部分后两个显示秒表的小数部分。先上代码该代码来自教材
# includereg52.hsbit ADDR3 P1^3;
sbit ENLED P1^4;sbit KEY1 P2^4;
sbit KEY2 P2^5;
sbit KEY3 P2^6;
sbit KEY4 P2^7;code unsigned char LedChar[] {0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, //数码管0-F的值0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char KeySta[4] { //按键当前态1,1,1,1
};
unsigned char LedBuff[6] { //数码管显示缓冲区0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
};bit StopwatchRunning 0; //秒表运行标记
bit StopwatchRefresh 1; //秒表计数刷新标志
unsigned char DecimalPart 0; //秒表的小说部分
unsigned int IntegerPart 0; //秒表的整数部分
unsigned char T0RH 0; //T0 重载值的高字节
unsigned char T0RL 0; //T0 重载值的低字节void ConfigTimer0(unsigned int ms);
void StopwatchDisplay();
void KeyDriver();void main()
{EA 1; //开总中断ENLED 0; //使能数码管 ADDR3 1; P2 0xFE; //1111 1110 P2.0置0选择第4行按键作为独立按键ConfigTimer0(2); // 配置T0定时2mswhile(1){if(StopwatchRefresh) //需要刷新秒表示数时调用显示函数{StopwatchRefresh 0;StopwatchDisplay();}KeyDriver();}}/*配置并启动ms-T0定时时间 */
void ConfigTimer0(unsigned int ms)
{unsigned long tmp; // 定义临时变量tmp 11059200 / 12; //定时器计数频率即每秒的机器周期数tmp (tmp * ms) / 1000 ; //计算所需的计数值tmp 65536 - tmp ; //计算定时器的初值重载值tmp tmp 18; //补偿响应延时造成的误差T0RH (unsigned char)(tmp 8) ; //定时器重载值拆分成为高低字节T0RL (unsigned char)tmp;TMOD 0xF0; //清零T0的控制位TMOD | 0x01; //配置T0位模式1TH0 T0RH;TL0 T0RL;ET0 1; //使能T0中断TR0 1; //启动T0定时器}/*秒表计数显示函数 */
void StopwatchDisplay()
{signed char i;unsigned char buf[4]; //数据转换的缓冲区//小数部分转换到低2位//LedBuff[0] LedChar[DecimalPart % 10];//LedBuff[1] LedChar[DecimalPart / 10];LedBuff[0] LedChar[DecimalPart%10];LedBuff[1] LedChar[DecimalPart/10];//整数部分转换到高4位buf[0] IntegerPart % 10;buf[1] (IntegerPart / 10) %10;buf[2] (IntegerPart / 100) %10;buf[3] (IntegerPart / 1000) %10;for(i 3; i 1; i--) // 整数部分高位的0转换为空字符{if( buf[i] 0)LedBuff[i2] 0xFF;elsebreak;}for(; i 0; i--) //有效数字位转换为显示字符{LedBuff[i2] LedChar[buf[i]];}LedBuff[2] 0x7F; //点亮小数点}/*秒表启停函数 */
void StopwatchAction()
{if(StopwatchRunning)StopwatchRunning 0; //已启动则停止elseStopwatchRunning 1; //未启动则启动
}/*秒表复位函数 */void StopwatchReset()
{StopwatchRunning 0; //停止秒表DecimalPart 0; //清零计数值IntegerPart 0;StopwatchRefresh 1; //重置刷新标志
}/*按键驱动函数检测按键动作调度相应动作函数需要在主循环中调用*/void KeyDriver()
{unsigned char i;static unsigned char backup[4] {1,1,1,1}; //循环检测4个按键for(i 0; i 4; i){if(backup[i] ! KeySta[i]) //按键动作检测{if( backup[i] ! 0){if( i 1) //ESC键复位秒表StopwatchReset();else if ( i 2) //回车键启停秒表StopwatchAction();}backup[i] KeySta[i]; //刷新前一次的备份值}}
}/* 按键扫描函数需在定时中断中调用 */void KeyScan()
{unsigned char i;static unsigned char keybuf[4] { //按键扫描缓冲区0xFF,0xFF,0xFF,0xFF };//按键值移入缓冲区keybuf[0] (keybuf[0] 1) | KEY1;keybuf[1] (keybuf[1] 1) | KEY2;keybuf[2] (keybuf[2] 1) | KEY3;keybuf[3] (keybuf[3] 1) | KEY4;//消抖后更新按键状态for (i 0; i 4; i){if (keybuf[i] 0x00){ //连续8次扫描为0即16ms内都是按下状态可以认为按键已稳定的按下KeySta[i] 0;} else if (keybuf[i] 0xFF) //连续8次扫描为1即16ms内都是弹起状态可以认为按键已稳定的弹起KeySta[i] 1;}
}/* 按键扫描函数需在定时中断中调用 */void LedScan()
{static unsigned char i 0;P0 0xFF; //显示消隐P1 (P1 0xF8) | i; //F81111 1000 位选索引值赋值P1口低3位P0 LedBuff[i]; //缓冲区中索引位置的数据送到P0口if(i 5) //索引递增循环遍历整个缓冲区i;elsei 0; }/* 秒表计数函数每隔10ms调用一次进行秒表计数累加 */
void StopwatchCount()
{if(StopwatchRunning) //当处于运行状态时递增计数值{DecimalPart; //小数部分1if (DecimalPart 100) //小数部分计到100时进位到整数部分{DecimalPart 0;IntegerPart; //整数部分1if(IntegerPart 10000) //整数部分计到10000时归零{IntegerPart 0;}}StopwatchRefresh 1; //设置秒表计数刷新标志}}/* T0中断服务函数完成数码管、按键扫描与秒表计数 */void InterruptTimer0() interrupt 1
{static unsigned char tmr10ms 0;TH0 T0RH; //重新加载重载值TL0 T0RL;LedScan(); //数码管扫描显示KeyScan(); //按键扫描tmr10ms;if(tmr10ms 5){tmr10ms 0;StopwatchCount(); //调用秒表计数函数}}
精度10ms的秒表计时_哔哩哔哩_bilibili
看一下结果视频可以看到秒表确实正常工作了有清零暂停两种基本功能。该秒表的最大量程是9999.99秒。
然后分析下该程序的一些可能有疑问的部分。
1
void LedScan()
{static unsigned char i 0;P0 0xFF; //显示消隐P1 (P1 0xF8) | i; //F81111 1000 位选索引值赋值P1口低3位P0 LedBuff[i]; //缓冲区中索引位置的数据送到P0口if(i 5) //索引递增循环遍历整个缓冲区i;elsei 0; }
下图是上图的等效语句上图显然精炼点。 P0 0xFF; //刷新数码管前P0口8位全部置1使LED都不工作。switch(i){case 0: ADDR2 0; ADDR1 0; ADDR0 0; i; P0 LedBuff[0]; break; //数码管1刷新case 1: ADDR2 0; ADDR1 0; ADDR0 1; i; P0 LedBuff[1]; break; //数码管2刷新case 2: ADDR2 0; ADDR1 1; ADDR0 0; i; P0 LedBuff[2]; break; //数码管3刷新case 3: ADDR2 0; ADDR1 1; ADDR0 1; i; P0 LedBuff[3]; break; //数码管4刷新case 4: ADDR2 1; ADDR1 0; ADDR0 0; i; P0 LedBuff[4]; break; //数码管5刷新case 5: ADDR2 1; ADDR1 0; ADDR0 1; i 0; P0 LedBuff[5]; break; //数码管6刷新default: break;}
2 这个小数点的值算法可以参考我之前的博文初学51单片机定时器数码管及C语言实践_51单片机定时器控制数码管程序-CSDN博客
这个点是数码管的dp位它是最高位因为是低电平使能因此要加上这个点只要
0111 1111 0x7F 与原先的数相与就可以把原先数值的最高位置0。而本案的数码管真值表示的都是不带点的数因此最高位都是1。该与运算就把点加上去了。
3 该程序里的18是怎么来的tmp是中断初值设置因此这个18是18个机器周期的意思加18会使tmp值变大相应中断时间变短了说明该程序在某处浪费了时间。
首先这个误差可以通过软件debug出来也可以通过累计计时比如计时30分钟和正确的秒表计时比较后确定总的误差是多少然后通过计算转换到每个中断需要补偿的时间。
那边必然产生一个问题误差到底是由哪些语句产生的
首先我们得知道数码管显示语句在哪里通读程序可知他是由中断函数里的LedScan()函数提供我们之前说秒表的精度是10ms但是实际上它每隔2ms就会刷新一下数码管。假设我们有子弹时间的话一开始同一数码管的视觉上时间显示间隔可以这么理解。当然这个速度很快
事实上当开始执行的时候
数码管0-5间隔2ms依次显示下去的一开始的显示表示。 数码管0显示0只持续了2ms即1个中断间隔但是肉眼看上去没有灭过是因为人眼的余晖效应每个数码管的刷新频率是1/0.01283HZ,肉眼是不会感到明显的闪烁感的。通过两图结合程序对比可知事实上当过去10ms的时候即第5次进入中断小数部分低位数码管显示数值已经刷新了即0变成1了但是第5次中断数码管显示的是第5个数码管的数值
P0 LedBuff[4];此时第5个的数码管的数值是0但高位0不显示。而需要再次显示
P0 LedBuff[0] 还需要两个中断也就是说秒表在计时的时候除却其他人为操作造成的误差由于程序结构照成的误差就有1个精度单位本案为10ms。注这是显示误差不是计时误差。这个误差是必然发生的要想降低这个误差就需要更改中断间隔提高秒表的精度。
所以除却该误差如果要想数码管显示准确无误它必须满足这几个条件
1中断的时间间隔是非常精确的2ms而这个精度由单片机和晶振提供的就是该单片机的机器周期本案是12/110592001.085us也就是微秒级的。这个对于我们的普通秒表来说应该是够了
2进入中断后马上执行显示语句并且显示语句本身以及显示语句之前都没有时间损耗显然不可能
3电路响应足够快快到对于秒表精度来说忽略不计。
第二个条件
查看中断函数与数码管显示函数
void InterruptTimer0() interrupt 1
{static unsigned char tmr10ms 0;TH0 T0RH; //重新加载重载值TL0 T0RL;LedScan(); //数码管扫描显示KeyScan(); //按键扫描tmr10ms;if(tmr10ms 5){tmr10ms 0;StopwatchCount(); //调用秒表计数函数}}
void LedScan()
{static unsigned char i 0;P0 0xFF; //显示消隐P1 (P1 0xF8) | i; //F81111 1000 位选索引值赋值P1口低3位P0 LedBuff[i]; //缓冲区中索引位置的数据送到P0口if(i 5) //索引递增循环遍历整个缓冲区i;elsei 0; }
显然数码管显示语句P0 LedBuff[i];前有好几个语句这些都有可能照成误差开始分析一下。
一开始进入中断TF0 0(中断函数自动清0)那代表着第二个中断时间开始计时了这时定时器的初值是0x00 TH0 T0RH; TL0 T0RL; 执行完这句定时器0初值重载那么前面的三句都是时间上的误差。执行了该句以后马上执行LedScan(); 该函数 static unsigned char i 0; P0 0xFF; //显示消隐 P1 (P1 0xF8) | i; //F81111 1000 位选索引值赋值P1口低3位 P0 LedBuff[i]; 执行完该句数码管才显示结束因此前面的加上它本身共4句都是误差来源。 事实上对于该程序的时间误差统筹的看会比较好一点。对于时间流逝流程如下图这个过程不太好描述笔者无法准确表述各位自己感悟一下秒表的显示逻辑是这样的我们肉眼看到也是如此 debug一下把程序停在该句 P0 LedBuff[i]
第一次运行是0.00421875
第二次运行是0.00623806
第三次运行是0.00825738
......
第11次运行是0.02441406
计算第11次的值减去第1次的值除以10.求得该时间间隔是0.002019531
我们希望的时间是2ms即0.002s所以多余的时间就是误差这个误差我们需要消除
即0.000019531*11059200/1217.9997个机器周期取18这就是程序里补偿时间18的来由。
debug过程debug误差过程_哔哩哔哩_bilibili
事实上对于中断函数里的程序笔者看了一会觉得逻辑是这样的才对
一开始写大概率这种逻辑的但是从debug的过程中可以看到第5次中断的时候会进入if函数那么时间间隔就会发生变化了当然这依然可以时间补偿就是求时间的时候需要注意一下而且这会导致每5次中断其中一个数码管的刷新时间变得长一点虽然不影响显示结果因此该函数放在LedSCan后面而且因为几乎必然发生的显示延迟对于进位结果下个中断使能好像也不是很难接受的结果。
按照刷新频率来说第一个0ms-100ms为例小数部分最低位的数码管我们希望的值是0-9共显示10次数字但是数码管的刷新完一次要12ms 则100/12 8 余4也就是说在头一个100ms里它只完成扫描了8次第9次只扫描了前4个数码管。因此必然有个数没显示经过笔者计算第一个100ms里无法显示的数是6它是从5直接跳到7当然下一个100ms未显示的数未必是6了。 最低位数码管从5跳到7.这个7显示正确吗精度有问题吗事实上7的精度是最高的5的误差是最大的这边可以理解为显示延迟已经不支持它显示6了数码管是第6次刷新但是时间已经累计到7了并且该处ledbuff[0]值已进被程序赋值为7了因此跳过了6直接显示7。可以预见的是如果一直计时在未来的某个100ms里可能会出现跳过两次数字显示毕竟发现一次跳过显示只需要72ms但不会出现三次跳过的情况。
3只要电路的响应频率远大于2ms应该都没问题。
笔者自己梳理的程序逻辑导图 总结今天又进步了一小步。