В данной статье я хочу рассказать о программном создании ШИМ-регулятора на STM8L-Discovery. Проект полностью переносим на другие микроконтроллеры типа STM8. Процесс переноса описан неоднократно, в т.ч. и мной в статье Термометр на STM8L-Discovery
Итак для начала разберёмся, что такое ШИМ. Широтно-импульсная модуляция — это процесс управления средней мощностью в нагрузке за счет изменения коэффициента заполнения сигнала прямоугольной формы. По определению средняя мощность Pсредняя= U2среднее/Rнагрузки. Не будем вдаваться в математические подробности, просто отмечу, что Uсреднее = Umax*t1/t2. Отношение t1/t2 ещё называют коэффициентом заполнения.
Пояснения на рисунке 1.
Таким образом, наша задача генерировать прямоугольные импульсы с различным коэффициентом заполнения. Она, в свою очередь, сводится к тому, чтобы управлять ножкой микроконтроллера, переводя её то в положение «1», то в положение «0». Кажется, что мы получили простое решение задачи: бесконечный цикл, где мы поднимаем выход, ждем, опускаем выход, ждем. Есть одно но: бесконечный цикл подвесит ядро, поэтому никаких других функций после запуска ШИМ мы выполнить не сможем (можно, конечно, извернуться и что-нибудь придумать с прерываниями, но это уже извращения). Потому я предлагаю другой способ — с прерываниями от таймера. Смысл такой: таймер дает прерывания, скажем, раз в 10 нс — это и будет нашим минимальным значением длительности импульса, или МЗДИ. Далее, возьмем период в 256 МЗДИ. Теперь нам нужна переменная, в которой будет храниться значение длительности импульса — от 0 до 255; соответственно коэффициент заполнения будет от 0 до 1, а средняя мощность нагрузки от 0% до 100%. Теперь посмотри на мою реализацию данного алгоритма
int _q, count;
//описание прерывания
#pragma vector = 27
__interrupt void tim4_ovf( void )
{
if ( count < _q ) // если от начала периода прошло меньше времени импульса
PC_ODR_bit.ODR5 = 1; //то на выходе «1»
else //иначе
PC_ODR_bit.ODR5 = 0; //»0″
count++; //увеличим счетчик времени
if ( count >= 256 ) //если период закончился
count = 0; //сбросим счетчик
TIM4_SR1_bit.UIF = 0; //выйдем из прерывания
}
void line_PWM( int q )
{
asm( «RIM» ); //разрешаю прерывания
CLK_PCKENR1_bit.PCKEN12 = 1; // подаю тактирование
TIM4_PSCR = 0x00; //ставлю предделители
TIM4_ARR = 0x0D;
TIM4_IER_bit.UIE = 1; //ставлю биты конфигурации
TIM4_CR1_bit.URS = 1;
TIM4_EGR_bit.UG = 1;
TIM4_CR1_bit.CEN = 1; //запускаю таймер
count = 0; //сброс счетчика прерываний
PC_DDR_bit.DDR5 = 1; //нога на выход
PC_CR1_bit.C15 = 1;
_q = q; //сохранили аргумент команды — длительность импульса
}
Эта штука очень хорошо работает, но имеет один недостаток — он проявляется при работе со светодиодами. Дело в том, что наш глаз нелинеен, а потому при линейном нарастании мощности на низкой яркости видимая яркость света нарастает быстрее, чем на высокой яркости при одинаковой скорости нарастания мощности. Причина в том, что реакция наших органов чувств ближе к экспоненциальной, поэтому для видимого равномерного нарастания яркости необходимо в действительности изменять ее логарифмически. Для этого я использовал другую функцию, которая автоматически подбирает из массива нужное значение длительности импульса. В ходе расчёта этого массива я для простоты использовал не логарифмическую зависимость, а близкую к ней параболическую.
const int table[256] = {0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
1,
1,
1,
1,
1,
1,
1,
2,
2,
2,
2,
2,
3,
3,
3,
3,
4,
4,
4,
4,
5,
5,
5,
5,
6,
6,
6,
7,
7,
7,
8,
8,
9,
9,
9,
10,
10,
11,
11,
11,
12,
12,
13,
13,
14,
14,
15,
15,
16,
16,
17,
17,
18,
18,
19,
19,
20,
20,
21,
22,
22,
23,
23,
24,
25,
25,
26,
27,
27,
28,
29,
29,
30,
31,
31,
32,
33,
33,
34,
35,
36,
36,
37,
38,
39,
40,
40,
41,
42,
43,
44,
44,
45,
46,
47,
48,
49,
50,
50,
51,
52,
53,
54,
55,
56,
57,
58,
59,
60,
61,
62,
63,
64,
65,
66,
67,
68,
69,
70,
71,
72,
73,
74,
75,
76,
77,
79,
80,
81,
82,
83,
84,
85,
87,
88,
89,
90,
91,
93,
94,
95,
96,
97,
99,
100,
101,
102,
104,
105,
106,
108,
109,
110,
112,
113,
114,
116,
117,
118,
120,
121,
122,
124,
125,
127,
128,
129,
131,
132,
134,
135,
137,
138,
140,
141,
143,
144,
146,
147,
149,
150,
152,
153,
155,
156,
158,
160,
161,
163,
164,
166,
168,
169,
171,
172,
174,
176,
177,
179,
181,
182,
184,
186,
188,
189,
191,
193,
195,
196,
198,
200,
202,
203,
205,
207,
209,
211,
212,
214,
216,
218,
220,
222,
224,
225,
227,
229,
231,
233,
235,
237,
239,
241,
243,
245,
247,
249,
251,
253,
255
};
void LED_PWM( int q )
{
line_PWM( table[q] );
}
В главном файле напишем простую обвязочку для тестирования функций ШИМ
void delay( void )
{
for ( int i =0; i < 20; i++ );
}
int main()
{
CLK_CKDIVR = 0;
while ( true )
{
for ( long int i = 0; i <= 255; i++ )
{
line_PWM(i);
delay();
}
for ( long int i = 255; i >= 0; i— )
{
LED_PWM(i);
delay();
}
}
return 0;
}
Для подключения мощных нагрузок к плате обязательно надо использовать транзисторный ключ, иначе можно сжечь контроллер. При индуктивной нагрузке необходимо добавить диод.
Прикрепленные файлы:
- PWM.c (2 Кб)