H O M E - APOLLO181 INTRODUCTION PWM LED Dimmer Step Motor Controller Sound Generator: Part 1 Sound Generator: Part 2 Random Number Generator

How to generate a PWM signal for accurate LED brightness control

 APOLLO181 LED brightness at two different PWM duty cycles

This is a simple program to implement a LED dimmer: a PWM signal is used to light the leds at varying brightness according to a 4-bit linear scale made of sixteen steps.

PWM, or Pulse Width Modulation, is a great method of controlling analog circuits by using the digital output of a microprocessor.

The PWM signal consists of a modulated square wave. The modulation can change the clock cycle of the signal (frequency) or the percentage of the power delivered during each clock cycle (duty cycle). The amplitude of the signal remains stable during the time.

If we drive a LED with a PWM digital signal, we can effectively control the light output (brightness) by varying the duty cycle: for example if we use a 33% of duty cycle over a short duration of time, we will turn the LED ON for the 33% of the cycle and OFF for the remaining 67% of that time period, so the LED will appear less bright because it does receive a third of the power.

Since humans perceive brightness not in a linear way but logarithmically like hearing response, we need to implement a PWM signal in which the duty cycle varies according to a logarithmic curve.

Because implementing transcendental functions via algorithm is cumbersome and the values we need to cover are only sixteen, the most efficient way is to create a sort of look-up table for all the sixteen possible duty cycle values. A duty cycle with 8-bit resolution, with a range from 0 to 255, looks enough for our purpose.

 Magnitude Input Magnitude PWM 8-bit 8-bit 8-bit Scale Switches Scale % Dec Binary Hex (Linear) Bin Code (Logarithmic) DC Value DC Value DC Value DC Value 1 0000 1,00 100% 255 1111 1111 FF 2 0001 0,75 75% 191 1011 1111 BF 3 0010 0,60 60% 153 1001 1001 99 4 0011 0,50 50% 127 0111 1111 7F 5 0100 0,42 42% 106 0110 1010 6A 6 0101 0,35 35% 90 0101 1010 5A 7 0110 0,30 30% 76 0100 1100 4C 8 0111 0,25 25% 63 0011 1111 3F 9 1000 0,21 21% 52 0011 0100 34 10 1001 0,17 17% 43 0010 1011 2B 11 1010 0,14 14% 34 0010 0010 22 12 1011 0,10 10% 26 0001 1010 1A 13 1100 0,07 7% 19 0001 0011 13 14 1101 0,05 5% 12 0000 1100 0C 15 1110 0,02 2% 5 0000 0101 05 16 1111 0,00 0% 0 0000 0000 00

To much simplify the algorithm I preferred to program the eight switches of APOLLO181 using the Johnson Counter sequence (i.e. 0000; 0001; 0011; 0111; 1111; 1110; 1100; 1000 ...), in which only one switch changes status for each brightness magnitude step. This is a convenient way to fast switch between 16 distinct states for controlling the duty cycle of the PWM signal.

 Magnitude Johnson Magnitude PWM 8-bit 8-bit 8-bit Scale Counter Scale % Dec Binary Hex (Linear) Bin Code (Logarithmic) DC Value DC Value DC Value DC Value 1 00000000 1,00 100% 255 1111 1111 FF 2 00000001 0,75 75% 191 1011 1111 BF 3 00000011 0,60 60% 153 1001 1001 99 4 00000111 0,50 50% 127 0111 1111 7F 5 00001111 0,42 42% 106 0110 1010 6A 6 00011111 0,35 35% 90 0101 1010 5A 7 00111111 0,30 30% 76 0100 1100 4C 8 01111111 0,25 25% 63 0011 1111 3F 9 11111111 0,21 21% 52 0011 0100 34 10 11111110 0,17 17% 43 0010 1011 2B 11 11111100 0,14 14% 34 0010 0010 22 12 11111000 0,10 10% 26 0001 1010 1A 13 11110000 0,07 7% 19 0001 0011 13 14 11100000 0,05 5% 12 0000 1100 0C 15 11000000 0,02 2% 5 0000 0101 05 16 10000000 0,00 0% 0 0000 0000 00

The important factor here is the refresh rate: if we use a frequency that is too much low, we will turn the light on and off too slowly and the viewer will see the LED ‘flickering’ and not a stable light.

So we need a clock cycle beyond the limit of the flicker perception of human eye, and anything faster than 100 Hz would be probably OK. Thus we will adopt in this program an approximate cycle of 180 Hz, that has a period time of 5555 microseconds.

Being  3 MHz the clock of APOLLO181 and because every instruction takes four clock cycles, each instruction takes 1/3 * 4 = 1,333333 µs to be executed.

We will need then to create a precise routine of delay which shall last in total 5555 µs, that approximately equals to 5555/1,333333 = 4166, about 4096 instructions that consist of 256 cycles of 16 instructions each.

Here APOLLO181 program:

 00 E7 ACC = IN PORT [7] 01 B0 Compare ACC with REG0 02 1A ACC = A# 03 C0 IF ZERO GOTO 0A# 04 E7 ACC = IN PORT [7] 05 20 REG [0] = ACC 06 E8 ACC = IN PORT [8] 07 2A REG [A] = ACC 08 10 ACC = 0# 09 01 GOTO 10# 0A E8 ACC = IN PORT [8] 0B BA Compare ACC with REGA 0C 15 ACC = 5# 0D CB IF ZERO GOTO B5# 0E 16 ACC = 6# 0F 00 GOTO 06# 10 3A ACC = REG [A] 11 A8 Compare ACC with 8# 12 10 ACC = 0# 13 C5 IF ZERO GOTO 50# 14 3A ACC = REG [A] 15 AC Compare ACC with C# 16 16 ACC = 6# 17 C5 IF ZERO GOTO 56# 18 3A ACC = REG [A] 19 AE Compare ACC with E# 1A 1C ACC = C# 1B C5 IF ZERO GOTO 5C# 1C 3A ACC = REG [A] 1D AF Compare ACC with F# 1E 11 ACC = 1# 1F CE IF ZERO GOTO E1# 20 30 ACC = REG [0] 21 A8 Compare ACC with 8# 22 18 ACC = 8# 23 C6 IF ZERO GOTO 68# 24 30 ACC = REG [0] 25 AC Compare ACC with C# 26 1E ACC = E# 27 C6 IF ZERO GOTO 6E# 28 30 ACC = REG [0] 29 AE Compare ACC with E# 2A 14 ACC = 4# 2B C7 IF ZERO GOTO 74# 2C 30 ACC = REG [0] 2D AF Compare ACC with F# 2E 17 ACC = 7# 2F CE IF ZERO GOTO E7# 30 3A ACC = REG [A] 31 A7 Compare ACC with 7# 32 10 ACC = 0# 33 C8 IF ZERO GOTO 80# 34 3A ACC = REG [A] 35 A3 Compare ACC with 3# 36 16 ACC = 6# 37 C8 IF ZERO GOTO 86# 38 3A ACC = REG [A] 39 A1 Compare ACC with 1# 3A 1C ACC = C# 3B C8 IF ZERO GOTO 8C# 3C 3A ACC = REG [A] 3D A0 Compare ACC with 0# 3E 1D ACC = D# 3F CE IF ZERO GOTO ED# 40 30 ACC = REG [0] 41 A7 Compare ACC with 7# 42 18 ACC = 8# 43 C9 IF ZERO GOTO 98# 44 30 ACC = REG [0] 45 A3 Compare ACC with 3# 46 1E ACC = E# 47 C9 IF ZERO GOTO 9E# 48 30 ACC = REG [0] 49 A1 Compare ACC with 1# 4A 14 ACC = 4# 4B CA IF ZERO GOTO A4# 4C 30 ACC = REG [0] 4D A0 Compare ACC with 0# 4E 1A ACC = A# 4F CA IF ZERO GOTO AA# 50 1F ACC = F# 51 21 REG [1] = ACC 52 1F ACC = F# 53 22 REG [2] = ACC 54 1A ACC = A# 55 05 GOTO 5A# 56 1F ACC = F# 57 21 REG [1] = ACC 58 1B ACC = B# 59 22 REG [2] = ACC 5A 10 ACC = 0# 5B 06 GOTO 60# 5C 19 ACC = 9# 5D 21 REG [1] = ACC 5E 19 ACC = 9# 5F 22 REG [2] = ACC 60 16 ACC = 6# 61 6 GOTO 66# 62 1F ACC = F# 63 21 REG [1] = ACC 64 17 ACC = 7# 65 22 REG [2] = ACC 66 1C ACC = C# 67 06 GOTO 6C# 68 1A ACC = A# 69 21 REG [1] = ACC 6A 16 ACC = 6# 6B 22 REG [2] = ACC 6C 12 ACC = 2# 6D 07 GOTO 72# 6E 1A ACC = A# 6F 21 REG [1] = ACC 70 15 ACC = 5# 71 22 REG [2] = ACC 72 18 ACC = 8# 73 07 GOTO 78# 74 1C ACC = C# 75 21 REG [1] = ACC 76 14 ACC = 4# 77 22 REG [2] = ACC 78 1E ACC = E# 79 07 GOTO 7E# 7A 1F ACC = F# 7B 21 REG [1] = ACC 7C 13 ACC = 3# 7D 22 REG [2] = ACC 7E 14 ACC = 4# 7F 08 GOTO 84# 80 14 ACC = 4# 81 21 REG [1] = ACC 82 13 ACC = 3# 83 22 REG [2] = ACC 84 1A ACC = A# 85 08 GOTO 8A# 86 1B ACC = B# 87 21 REG [1] = ACC 88 12 ACC = 2# 89 22 REG [2] = ACC 8A 10 ACC = 0# 8B 09 GOTO 90# 8C 12 ACC = 2# 8D 21 REG [1] = ACC 8E 12 ACC = 2# 8F 22 REG [2] = ACC 90 16 ACC = 6# 91 09 GOTO 96# 92 1A ACC = A# 93 21 REG [1] = ACC 94 11 ACC = 1# 95 22 REG [2] = ACC 96 1C ACC = C# 97 09 GOTO 9C# 98 13 ACC = 3# 99 21 REG [1] = ACC 9A 11 ACC = 1# 9B 22 REG [2] = ACC 9C 12 ACC = 2# 9D 0A GOTO A2# 9E 1C ACC = C# 9F 21 REG [1] = ACC A0 10 ACC = 0# A1 22 REG [2] = ACC A2 18 ACC = 8# A3 0A GOTO A8# A4 15 ACC = 5# A5 21 REG [1] = ACC A6 10 ACC = 0# A7 22 REG [2] = ACC A8 1E ACC = E# A9 0A GOTO AE# AA 10 ACC = 0# AB 21 REG [1] = ACC AC 10 ACC = 0# AD 22 REG [2] = ACC AE 40 SET ALU to 0# AF 31 ACC = REG [1] B0 70 ACC = Logic f (ACC, 0#) B1 23 REG [3] = ACC B2 32 ACC = REG [2] B3 70 ACC = Logic f (ACC, 0#) B4 24 REG [4] = ACC B5 90 SET NO CARRY B6 31 ACC = REG [1] B7 2E REG [E] = ACC B8 32 ACC = REG [2] B9 2F REG [F] = ACC BA 10 ACC = 0# BB F7 OUT PORT [7] = ACC BC F8 OUT PORT [8] = ACC BD F9 OUT PORT [9] = ACC BE FA OUT PORT [A] = ACC BF 3E ACC = REG [E] C0 49 SET ALU to 9# C1 51 ACC = Arithm f (ACC, 1#, CARRY) C2 2E REG [E] = ACC C3 3F ACC = REG [F] C4 50 ACC = Arithm f (ACC, 0#, CARRY) C5 2F REG [F] = ACC C6 1A ACC = A# C7 DC IF CARRY GOTO CA# C8 1A ACC = A# C9 0B GOTO BA# CA 90 SET NO CARRY CB 33 ACC = REG [3] CC 2E REG [E] = ACC CD 34 ACC = REG [4] CE 2F REG [F] = ACC CF 1F ACC = F# D0 F7 OUT PORT [7] = ACC D1 F8 OUT PORT [8] = ACC D2 F9 OUT PORT [9] = ACC D3 FA OUT PORT [A] = ACC D4 3E ACC = REG [E] D5 49 SET ALU to 9# D6 51 ACC = Arithm f (ACC, 1#, CARRY) D7 2E REG [E] = ACC D8 3F ACC = REG [F] D9 50 ACC = Arithm f (ACC, 0#, CARRY) DA 2F REG [F] = ACC DB 1F ACC = F# DC DD IF CARRY GOTO DF# DD 1F ACC = F# DE 0C GOTO CF# DF 10 ACC = 0# E0 00 GOTO 00# E1 30 ACC = REG [0] E2 A0 Compare ACC with 0# E3 12 ACC = 2# E4 C6 IF ZERO GOTO 62# E5 10 ACC = 0# E6 02 GOTO 20# E7 3A ACC = REG [A] E8 AF Compare ACC with F# E9 1A ACC = A# EA C7 IF ZERO GOTO 7A# EB 10 ACC = 0# EC 03 GOTO 30# ED 30 ACC = REG [0] EE AF Compare ACC with F# EF 12 ACC = 2# F0 C9 IF ZERO GOTO 92# F1 10 ACC = 0# F2 04 GOTO 40#