[DemoCode下载] 呼吸灯要考虑伽马校正

[复制链接]
 楼主| 598330983 发表于 2020-7-18 10:00 | 显示全部楼层 |阅读模式
在此博客**中,我将向您展示如何使用PWM正确控制LED的亮度。这并不像您想的那么简单!

PWM通过改变方波的占空比来工作,如下所示:
762415f12567381d0f.png
输出的平均电压等于VCC * k,因此改变占空比将改变连接到输出的LED的亮度。几乎每个微控制器都可以通过专用外设来支持PWM,或者您也可以通过软件自己实现!

然而,对LED进行PWM的通用方法存在问题。以以下Arduino代码为例:
  1. void loop() {
  2.     byte i = 0;
  3.     while (1) {
  4.         analogWrite(2, i);
  5.         delay(10);
  6.         i++;
  7.     }
  8. }
这将使arduino引脚2上的LED从暗变亮。但是您可能会注意到LED的褪色效果不是很好。起初它们看起来会很快消失,然后在全亮度下花费很长时间:
527675f1256b95d4a9.png
其背后的原因是因为人眼对光的响应不是线性的,而是对数的。那肯定会使事情复杂化!

要解决此问题,我们必须校正PWM值,以使其对人眼呈线性。

常见的误解是应使用伽玛校正,因为它对眼睛的反应非常相似。但是,伽玛校正与人类对光的感知方式无关,这似乎是巧合。在CIE 1931亮度公式是实际描述了我们如何感知光线:
  1. L* = 903.3 ∙ Y,            if Y ≤ 0.008856
  2. L* = 116   ∙ Y^1/3 – 16,   if Y > 0.008856
其中Y是0.0到1.0之间的亮度(输出),L *是0到100之间的亮度(输入)
77015f12570541320.png
编辑:我混合了Y和L *,因此关系是错误的。**已更新以解决此问题
该公式需要根据L *重新排列:
  1. Y = (L* / 902.3)           if L* ≤ 8
  2. Y = ((L* + 16) / 116)^3    if L* > 8
当然,由于功率和除法的原因,该公式在微控制器上实现太慢了,因此应改用查找表。我创建了一个简单的python脚本来生成一个C头文件,该文件可以简单地包含在项目中:
  1. INPUT_SIZE = 255       # Input integer size
  2. OUTPUT_SIZE = 255      # Output integer size
  3. INT_TYPE = 'const unsigned char'
  4. TABLE_NAME = 'cie';

  5. def cie1931(L):
  6.     L = L*100.0
  7.     if L <= 8:
  8.         return (L/902.3)
  9.     else:
  10.         return ((L+16.0)/116.0)**3

  11. x = range(0,int(INPUT_SIZE+1))
  12. y = [round(cie1931(float(L)/INPUT_SIZE)*OUTPUT_SIZE) for L in x]

  13. f = open('cie1931.h', 'w')
  14. f.write('// CIE1931 correction table\n')
  15. f.write('// Automatically generated\n\n')

  16. f.write('%s %s[%d] = {\n' % (INT_TYPE, TABLE_NAME, INPUT_SIZE+1))
  17. f.write('\t')
  18. for i,L in enumerate(y):
  19.     f.write('%d, ' % int(L))
  20.     if i % 10 == 9:
  21.         f.write('\n\t')
  22. f.write('\n};\n\n')
生成的表如下所示:
  1. const unsigned char cie[256] = { 0, 0, 0, 0, 0, 1, 1, ..., 247, 250, 252, 255 };
根据所使用的微控制器,您可能需要更改类型,以便将值存储在ROM中而不是RAM中。
可以更改顶部的常量以适合微控制器。例如,如果您想使用10位PWM,则可以设置INT_SIZE=1024。这仍然会生成一个包含256个条目的表,但是输出将是10位。
由于这种转换会降低PWM的分辨率,因此使用10位PWM确实是明智的。这正是我在LED咖啡桌项目中所做的。
最后,使用查找表使LED褪色:
  1. #include "cie1931.h"

  2. void loop() {
  3.     byte i = 0;
  4.     while (1) {
  5.         analogWrite(2, cie[i]);
  6.         delay(10);
  7.         i++;
  8.     }
  9. }
现在,我们有线性衰减的LED!
31995f1257af8b7a5.png

 楼主| 598330983 发表于 2020-7-18 11:05 | 显示全部楼层
  1. /*---------------------------------------------------------------------------------------------------------*/
  2. /*                                                                                                         */
  3. /* Copyright(c) 2019 Nuvoton Technology Corp. All rights reserved.                                         */
  4. /*                                                                                                         */
  5. /*---------------------------------------------------------------------------------------------------------*/

  6. //***********************************************************************************************************
  7. //  Website: http://www.nuvoton.com
  8. //  E-Mail : MicroC-8bit@nuvoton.com
  9. //  Date   : Jan/21/2019
  10. //***********************************************************************************************************

  11. //***********************************************************************************************************
  12. //  File Function: ML51 GPIO toggle demo code
  13. //***********************************************************************************************************
  14. #include "ML51.H"
  15. #include "math.h"


  16. //----------------------------------------------------------------------------------------------//
  17. void main (void)
  18. {
  19.         int i=0;
  20.         unsigned int j=0;
  21. const unsigned char cie[256] = {
  22.         0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
  23.         1, 1, 1, 1, 2, 2, 2, 2, 2, 2,
  24.         2, 2, 2, 3, 3, 3, 3, 3, 3, 3,
  25.         3, 4, 4, 4, 4, 4, 4, 5, 5, 5,
  26.         5, 5, 6, 6, 6, 6, 6, 7, 7, 7,
  27.         7, 8, 8, 8, 8, 9, 9, 9, 10, 10,
  28.         10, 10, 11, 11, 11, 12, 12, 12, 13, 13,
  29.         13, 14, 14, 15, 15, 15, 16, 16, 17, 17,
  30.         17, 18, 18, 19, 19, 20, 20, 21, 21, 22,
  31.         22, 23, 23, 24, 24, 25, 25, 26, 26, 27,
  32.         28, 28, 29, 29, 30, 31, 31, 32, 32, 33,
  33.         34, 34, 35, 36, 37, 37, 38, 39, 39, 40,
  34.         41, 42, 43, 43, 44, 45, 46, 47, 47, 48,
  35.         49, 50, 51, 52, 53, 54, 54, 55, 56, 57,
  36.         58, 59, 60, 61, 62, 63, 64, 65, 66, 67,
  37.         68, 70, 71, 72, 73, 74, 75, 76, 77, 79,
  38.         80, 81, 82, 83, 85, 86, 87, 88, 90, 91,
  39.         92, 94, 95, 96, 98, 99, 100, 102, 103, 105,
  40.         106, 108, 109, 110, 112, 113, 115, 116, 118, 120,
  41.         121, 123, 124, 126, 128, 129, 131, 132, 134, 136,
  42.         138, 139, 141, 143, 145, 146, 148, 150, 152, 154,
  43.         155, 157, 159, 161, 163, 165, 167, 169, 171, 173,
  44.         175, 177, 179, 181, 183, 185, 187, 189, 191, 193,
  45.         196, 198, 200, 202, 204, 207, 209, 211, 214, 216,
  46.         218, 220, 223, 225, 228, 230, 232, 235, 237, 240,
  47.         242, 245, 247, 250, 252, 255,
  48. };

  49.                
  50.                 //PWM时钟源为系统时钟FSYS
  51.         PWM0_ClockSource(PWM_FSYS,128);

  52.         MFP_P03_PWM0_CH2;
  53.         P03_PUSHPULL_MODE;
  54.         PWM0_ConfigOutputChannel(2,Independent,EdgeAligned,0x6FF,100);
  55.         PWM0_RUN();
  56.   while(1)
  57.   {
  58.                 for(i=0;i<=255;i++)
  59.                                 {
  60.                                 Timer3_Delay(24000000,4,1,10000);
  61.                                 set_PWM0CON0_LOAD;
  62. //                PWM0_ConfigOutputChannel(2,Independent,EdgeAligned,0x6FF,i);
  63.                                 SFRS = 0x01;
  64. //设置周期
  65.                                 PWM0PH = 0;
  66.                                 PWM0PL = 255;               
  67. //设置 高电平,高电平除以周期就是占空比                                       
  68.                                 PWM0C2H=0;
  69.                                 PWM0C2L=cie[i];
  70.                                        
  71.                                 }
  72.                 for(i=255;i>=0;i--)
  73.                                 {
  74.                                 Timer3_Delay(24000000,4,1,10000);
  75.                                 set_PWM0CON0_LOAD;
  76. //                PWM0_ConfigOutputChannel(2,Independent,EdgeAligned,0x6FF,i);
  77.                                 SFRS = 0x01;

  78.                                 PWM0PH = 0;
  79.                                 PWM0PL = 255;                                       
  80.                                 PWM0C2H=0;
  81.                                 PWM0C2L=cie[i];
  82.                                        
  83.                                 }
  84.                         }
  85. }
 楼主| 598330983 发表于 2020-7-18 11:06 | 显示全部楼层
实际上计数器是16位的,可以更好的利用起来,不用查表法肯定就不适合了。哈哈。
qiangtech 发表于 2020-7-18 11:39 | 显示全部楼层
有空验证一下,看做出来的呼吸灯是不是更好看一些。
dongnanxibei 发表于 2020-7-18 15:39 | 显示全部楼层
没看懂啊,具体公式是什么。
qiangtech 发表于 2020-7-18 15:50 | 显示全部楼层
2020-07-18_154815.png
把这部分用PYTHON运行一下,慢慢的理解。没有解释真的好难看懂。
734774645 发表于 2020-7-18 16:20 | 显示全部楼层
难以理解。啊
734774645 发表于 2020-7-18 16:20 | 显示全部楼层
qiangtech 发表于 2020-7-18 15:50
把这部分用PYTHON运行一下,慢慢的理解。没有解释真的好难看懂。

这个好像是生成一个头文件的。不知道这个公式怎么理解,没懂,这个只能生成0到255的,那么如果是16位的,如何计算0到0xFFFF的呢。
qiangtech 发表于 2020-7-18 17:35 | 显示全部楼层
734774645 发表于 2020-7-18 16:20
这个好像是生成一个头文件的。不知道这个公式怎么理解,没懂,这个只能生成0到255的,那么如果是16位的, ...

把INPUT_SIZE ,OUTPUT_SIZE改为65535,但好多MCU都没有这大的ROM,所以分辨率这么高有没有现实意义?
huangcunxiake 发表于 2020-7-18 19:44 | 显示全部楼层
前来取经。
huangcunxiake 发表于 2020-7-18 19:44 | 显示全部楼层
qiangtech 发表于 2020-7-18 17:35
把INPUT_SIZE ,OUTPUT_SIZE改为65535,但好多MCU都没有这大的ROM,所以分辨率这么高有没有现实意义? ...

说的是,那么精细的曲线,人眼也看不出来有多少变化,得不偿失。
character 发表于 2020-8-31 13:03 | 显示全部楼层
学习了
单片小菜 发表于 2020-9-1 18:49 | 显示全部楼层
呼吸灯有必要做成这样的吗?人眼能够分辨出来吗?还是给机器用的呢?
sadicy 发表于 2021-7-16 11:23 | 显示全部楼层
呼吸灯的话,这样似乎过于复杂了,
但是,好像在其他应用上,貌似可以学习这种方法
aple0807 发表于 2021-7-19 16:05 | 显示全部楼层
本帖最后由 aple0807 于 2021-7-19 16:09 编辑

直接用递归乘加就是呼吸效果, 变亮 out = out + out  * k 。  变暗 out=out - out  * k
k为系数,out为输出。 加一个最大值最小值限定就可以了。

实际上,只要满足值越大,变化增量越大的函数都可以,我测试了很多,这个函数效果是我用过的最好的。

 楼主| 598330983 发表于 2021-9-14 20:51 | 显示全部楼层
aple0807 发表于 2021-7-19 16:05
直接用递归乘加就是呼吸效果, 变亮 out = out + out  * k 。  变暗 out=out - out  * k
k为系数,out为输 ...

感谢大佬提供思路。
xinpian101 发表于 2021-9-14 22:39 | 显示全部楼层
比较随意啊。
xinpian101 发表于 2021-9-14 22:40 | 显示全部楼层
其实无所谓,就看喘气匀不
daichaodai 发表于 2021-9-15 07:54 来自手机 | 显示全部楼层
一个呼吸灯也可以这么高级
match007 发表于 2021-9-16 18:37 | 显示全部楼层
什么是伽马校正?呼吸灯而已,比我系统都复杂了
您需要登录后才可以回帖 登录 | 注册

本版积分规则

266

主题

5573

帖子

22

粉丝
快速回复 在线客服 返回列表 返回顶部