红外通信原理

红外通信的整个工作过程

发送过程

数据 -> 编码 -> 调制 -> 发送电路

  1. 数据:假设发送的数据为 0XA5,二进制为:10100101

  2. 编码:用脉冲宽度编码(Pulse Width Encoding)来表示二进制数据。每个数据位由一系列的脉冲组成,逻辑0和逻辑1分别由不同的脉冲宽度表示。现假设如下:

逻辑0为:0.5ms高 + 0.5ms低; 逻辑1为:1ms高 + 0.5ms低;

  1. 调制:就是把编码数据放到一定频率的载波上面,即使用数据调制载波,形成一串脉冲信号
  2. 发送电路:即:红外灯珠对脉冲信号的发送 + 脉冲信号的放大

接收过程

接收电路 -> 解调 -> 解码 -> 数据

  1. 接收电路:接收发过来的红外信号
  2. 解调:当红外接收管接收到调制信号时,输出高电平,否则输出为低电平
  3. 解码:解调后将得到的一系列脉冲变为二进制
  4. 数据:解码后即可得到发来的数据,即 0XA5 (二进制为:10100101)

优缺点

红外通信的优点:抗电磁干扰、成本低

红外通信的缺点:传输效率低,易受到环境光干扰导致传输误码

ADC数据常用的处理

为什么要对采样的数据进行处理呢?直接拿来用不行吗?
因为任何系统都会存在干扰,进行数据处理就是为了尽量避免因外界干扰引起的误差。

常用的两种滤波方法

  1. 多次采样加权取平均,在写程序的时候可以运用一些技巧:首先我们ADC采集8次数据并且对这8次数据累加(可以每1ms调用该函数),累加完成后将累加结果右移3位得到最后的采样结果。
    当然这个方法还可以进一步改善,比如取样10次,然后去掉最大值和最小值,再对剩下的8次进行取平均。

但因为需要多次采样,更新数据的时间会很长,比如取样8次,每1ms取样一次,那么更新数据就需要8ms时间,所以对采样的数据实时性要求不是很高的系统才可以用这种方法

  1. 中值滤波法(可用在实时性比较高的系统):实现思想就是取5个数据,每次采样的新数据替换掉最老的数据;然后把中间的那个数据当做是采样数据,这样不仅可以有效抑制干扰,而且响应速度快。

十大滤波方法及代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
#include "windows.h"
#include <stdio.h>

unsigned short ftable[255] = {
2048, 2098, 2148, 2198, 2248, 2298, 2348, 2398, 2447, 2496,
2545, 2594, 2642, 2690, 2737, 2785, 2831, 2877, 2923, 2968,
3013, 3057, 3100, 3143, 3185, 3227, 3267, 3307, 3347, 3385,
3423, 3460, 3496, 3531, 3565, 3598, 3631, 3662, 3692, 3722,
3750, 3778, 3804, 3829, 3854, 3877, 3899, 3920, 3940, 3958,
3976, 3992, 4007, 4021, 4034, 4046, 4056, 4065, 4073, 4080,
4086, 4090, 4093, 4095, 4095, 4095, 4093, 4090, 4086, 4080,
4073, 4065, 4056, 4046, 4034, 4021, 4007, 3992, 3976, 3958,
3940, 3920, 3899, 3877, 3854, 3829, 3804, 3778, 3750, 3722,
3692, 3662, 3631, 3598, 3565, 3531, 3496, 3460, 3423, 3385,
3347, 3307, 3267, 3227, 3185, 3143, 3100, 3057, 3013, 2968,
2923, 2877, 2831, 2785, 2737, 2690, 2642, 2594, 2545, 2496,
2447, 2398, 2348, 2298, 2248, 2198, 2148, 2098, 2047, 1997,
1947, 1897, 1847, 1797, 1747, 1697, 1648, 1599, 1550, 1501,
1453, 1405, 1358, 1310, 1264, 1218, 1172, 1127, 1082, 1038,
995, 952, 910, 868, 828, 788, 748, 710, 672, 635,
599, 564, 530, 497, 464, 433, 403, 373, 345, 317,
291, 266, 241, 218, 196, 175, 155, 137, 119, 103,
88, 74, 61, 49, 39, 30, 22, 15, 9, 5,
2, 0, 0, 0, 2, 5, 9, 15, 22, 30,
39, 49, 61, 74, 88, 103, 119, 137, 155, 175,
196, 218, 241, 266, 291, 317, 345, 373, 403, 433,
464, 497, 530, 564, 599, 635, 672, 710, 748, 788,
828, 868, 910, 952, 995, 1038, 1082, 1127, 1172, 1218,
1264, 1310, 1358, 1405, 1453, 1501, 1550, 1599, 1648, 1697,
1747, 1797, 1847, 1897, 1947};

int a = 0;
int b = 1;

/******************************************************************************************************
方法一:限幅滤波法
方法:根据经验判断,确定两次采样允许的最大偏差值(设为A),每次检测到新值时判断:
如果本次值与上次值之差<=A,则本次值有效,
如果本次值与上次值之差>A,则本次值无效,放弃本次值,用上次值代替本次值。
优点:能克服偶然因素引起的脉冲干扰
缺点:无法抑制周期性的干扰,平滑度差
******************************************************************************************************/

// #define A 51
// unsigned short Value1 = 0;

// short filter1()
// {
// static unsigned short NewValue = 0;
// Value1 = ftable[b - 1];
// NewValue = ftable[b];

// a++;
// if (a == 254)
// {
// a = 0;
// }

// b++;
// if (b == 255)
// {
// b = 1;
// }

// if (((NewValue - Value1) > A) || ((Value1 - NewValue) > A))
// {
// printf("------\n");
// }
// else
// {
// printf("%d, %d, %d\n", NewValue - Value1, NewValue, Value1);
// }
// }

/******************************************************************************************************
方法二:中位值滤波法
方法: 连续采样N次(N取奇数),把N次采样值按大小排列,取中间值为本次有效值。
优点:克服偶然因素(对温度、液位的变化缓慢的被测参数有良好的滤波效果)
缺点:对流量、速度等快速变化的参数不宜
******************************************************************************************************/

// #define N 3
// unsigned short value_buf[N];

// short filter2()
// {
// unsigned short count, i, j, temp;

// // 将采集的ADC值分别存入数组中,且后边存的值覆盖前边存的值
// for (count = 0; count < N; count++)
// {
// value_buf[count] = ftable[a];
// a++;
// if (a == 254)
// {
// a = 0;
// }
// }

// // 冒泡排序 - 从小到大排
// for (j = 0; j < N - 1; j++)
// {
// for (i = 0; i < N - 1 - j; i++)
// {
// if (value_buf[i] > value_buf[i + 1])
// {
// temp = value_buf[i];
// value_buf[i] = value_buf[i + 1];
// value_buf[i + 1] = temp;
// }
// }
// }

// // 选出中位数
// printf("%d\n", value_buf[(N - 1) / 2]);
// }

/******************************************************************************************************
方法三:算术平均滤波法
方法:连续取N个采样值进行算术平均运算:( N值的选取:一般流量,N=12;压力:N=4。)
N值较大时:信号平滑度较高,但灵敏度较低;
N值较小时:信号平滑度较低,但灵敏度较高;
优点:适用于对一般具有随机干扰的信号进行滤波;这种信号的特点是有一个平均值,信号在某一数值范围附近上下波动
缺点:对于测量速度较慢或要求数据计算速度较快的实时控制不适用,比较浪费RAM。
******************************************************************************************************/

// #define N 5

// short filter3()
// {
// unsigned short sum = 0, count;
// for (count = 0; count < N; count++)
// {
// sum += ftable[a];
// a++;
// if (a == 254)
// {
// a = 0;
// }
// }

// printf("%d\n", sum / N);
// }

/******************************************************************************************************
方法四:递推平均滤波法(又称滑动平均滤波法)
方法: 把连续取得的N个采样值看成一个队列,队列的长度固定为N,
每次采样到一个新数据放入队尾,并扔掉原来队首的一次数据(先进先出原则),
把队列中的N个数据进行算术平均运算,获得新的滤波结果。
N值的选取:流量,N=12;压力,N=4;液面,N=4-12;温度,N=1-4。
优点:对周期性干扰有良好的抑制作用,平滑度高;
适用于高频振荡的系统。
缺点:灵敏度低,对偶然出现的脉冲性干扰的抑制作用较差;
不易消除由于脉冲干扰所引起的采样值偏差;
不适用于脉冲干扰比较严重的场合;
比较浪费RAM。
******************************************************************************************************/

// #define FILTER4_N 3
// unsigned short filter_buf[FILTER4_N + 1]; // 滑动窗口大小 4

// short filter4()
// {
// static unsigned int j = 0;
// int i = 0, filter_sum = 0;
// filter_buf[FILTER4_N] = ftable[a]; // 将采集得到的数据依次存放到窗口数组最后

// a++;
// if (a == 254)
// {
// a = 0;
// }

// for (i = 0; i < FILTER4_N; i++)
// {
// // i = 0 1 2
// j++;
// filter_buf[i] = filter_buf[i + 1]; // 将窗口数组元素左移,即舍弃窗口数组的0元素
// filter_sum += filter_buf[i]; // 累加窗口数组前三个元素
// }

// // 两轮过后才出现正确的均值,一轮3次,两轮6次;故 > 6
// if (j > 6)
// {
// printf("%d\n", filter_sum / FILTER4_N); // 窗口的前三个元素求平均
// }
// }

/******************************************************************************************************
方法五:中位值平均滤波法(又称防脉冲干扰平均滤波法)
方法: 采一组队列去掉最大值和最小值后取平均值, (N值的选取:3-14)。
相当于“中位值滤波法”+“算术平均滤波法”。
连续采样N个数据,去掉一个最大值和一个最小值,
然后计算N-2个数据的算术平均值。
优点: 融合了“中位值滤波法”+“算术平均滤波法”两种滤波法的优点。
对于偶然出现的脉冲性干扰,可消除由其所引起的采样值偏差。
对周期干扰有良好的抑制作用。
平滑度高,适于高频振荡的系统。
缺点:对于测量速度较慢或要求数据计算速度较快的实时控制不适用,比较浪费RAM。
******************************************************************************************************/

// #define N 3

// int filter5()
// {
// int i, j;
// int filter_temp, filter_sum = 0;
// int filter_buf[N];

// for (i = 0; i < N; i++)
// {
// // i = 0 1 2
// filter_buf[i] = ftable[a];
// a++;
// if (a == 254)
// {
// a = 0;
// }
// }

// // 冒泡排序 - 从小到大排
// for (j = 0; j < N - 1; j++)
// {
// for (i = 0; i < N - 1 - j; i++)
// {
// if (filter_buf[i] > filter_buf[i + 1])
// {
// filter_temp = filter_buf[i];
// filter_buf[i] = filter_buf[i + 1];
// filter_buf[i + 1] = filter_temp;
// }
// }
// }

// // 去除最大最小极值后求平均 - filter_buf[0]为最小值,filter_buf[N]为最大值
// for (i = 1; i < N - 1; i++)
// {
// // i = 1
// filter_sum += filter_buf[i];
// }

// printf("%d\n", filter_sum / (N - 2));
// }

/******************************************************************************************************
方法六:限幅平均滤波法
方法: 相当于“限幅滤波法”+“递推平均滤波法”;
每次采样到的新数据先进行限幅处理,
再送入队列进行递推平均滤波处理。
优点: 融合了两种滤波法的优点;
对于偶然出现的脉冲性干扰,可消除由于脉冲干扰所引起的采样值偏差。
缺点:比较浪费RAM。
******************************************************************************************************/

// #define FILTER6_N 3
// #define FILTER6_A 51
// int filter_buf[FILTER6_N];

// int filter6()
// {
// int i;
// int filter_sum = 0;
// filter_buf[FILTER6_N - 1] = ftable[a]; // 将采样值依次存到数组最后一个元素

// a++;
// if (a == 254)
// {
// a = 0;
// }

// if (a < 3)
// {
// filter_buf[FILTER6_N - 3] = ftable[0];
// filter_buf[FILTER6_N - 2] = ftable[1];
// filter_buf[FILTER6_N - 1] = ftable[2];
// }
// else
// {
// // 若 filter_buf数组 中后两个元素之差大于 FILTER6_A
// if (((filter_buf[FILTER6_N - 1] - filter_buf[FILTER6_N - 2]) > FILTER6_A) || ((filter_buf[FILTER6_N - 2] - filter_buf[FILTER6_N - 1]) > FILTER6_A))
// {
// // 将倒数第二个元素覆盖最后一个元素 - 若超出规定幅值,则舍弃本次采集的值,并使用上次的ADC值代替
// filter_buf[FILTER6_N - 1] = filter_buf[FILTER6_N - 2];
// }

// for (i = 0; i < FILTER6_N - 1; i++)
// {
// // i = 0 1
// filter_buf[i] = filter_buf[i + 1]; // 数组元素左移
// filter_sum += filter_buf[i];
// }
// printf("%d\n", filter_sum / (FILTER6_N - 1));
// }
// }

/******************************************************************************************************
方法七:一阶滞后滤波法
方法: 取a=0-1,本次滤波结果=(1-a)*本次采样值+a*上次滤波结果。
优点: 对周期性干扰具有良好的抑制作用;
适用于波动频率较高的场合。
平滑度高,适于高频振荡的系统。
缺点: 相位滞后,灵敏度低;
滞后程度取决于a值大小;
不能消除滤波频率高于采样频率1/2的干扰信号。
******************************************************************************************************/

// #define FILTER7_A 0.01
// unsigned short Value;

// short filter7()
// {
// int NewValue;
// Value = ftable[b - 1];
// NewValue = ftable[b];
// b++;
// if (b == 255)
// {
// b = 1;
// }
// Value = (int)((float)NewValue * FILTER7_A + (1.0 - FILTER7_A) * (float)Value);
// printf("%d\n", Value);
// }

/******************************************************************************************************
方法八:加权递推平均滤波法
方法: 是对递推平均滤波法的改进,即不同时刻的数据加以不同的权;
通常是,越接近现时刻的数据,权取得越大。
给予新采样值的权系数越大,则灵敏度越高,但信号平滑度越低。
优点: 适用于有较大纯滞后时间常数的对象,和采样周期较短的系统。
缺点: 对于纯滞后时间常数较小、采样周期较长、变化缓慢的信号;
不能迅速反应系统当前所受干扰的严重程度,滤波效果差。
******************************************************************************************************/

// #define FILTER8_N 12
// int coe[FILTER8_N] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; // 加权系数表
// int sum_coe = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12; // 加权系数和
// int filter_buf[FILTER8_N + 1];

// int filter8()
// {
// int i;
// int filter_sum = 0;
// filter_buf[FILTER8_N] = ftable[a];
// a++;
// if (a == 254)
// {
// a = 0;
// }
// for (i = 0; i < FILTER8_N; i++)
// {
// filter_buf[i] = filter_buf[i + 1]; // 数组元素左移,将第0个元素舍弃
// filter_sum += filter_buf[i] * coe[i];
// }
// filter_sum /= sum_coe;
// printf("%d\n", filter_sum);
// }

/******************************************************************************************************
方法九: 消抖滤波法
方法: 设置一个滤波计数器,将每次采样值与当前有效值比较:
如果采样值 = 当前有效值,则计数器清零;
如果采样值 < 或 > 当前有效值,则计数器+1,并判断计数器是否 >= 上限N(溢出);
如果计数器溢出,则将本次值替换当前有效值,并清计数器。
优点: 对于变化缓慢的被测参数有较好的滤波效果;
可避免在临界值附近控制器的反复开/关跳动或显示器上数值抖动。
缺点: 对于快速变化的参数不宜;
如果在计数器溢出的那一次采样到的值恰好是干扰值,则会将干扰值当作有效值导入系统。
******************************************************************************************************/

// #define FILTER9_N 51
// unsigned short i = 0;
// unsigned short Value;

// short filter9()
// {
// int new_value;
// Value = ftable[b - 1];
// new_value = ftable[b];
// b++;
// if (b == 255)
// {
// b = 1;
// }
// if (Value != new_value)
// {
// i++;
// if (i > FILTER9_N)
// {
// i = 0;
// Value = new_value;
// }
// }
// else
// {
// i = 0;
// }

// printf("%d\n", Value);
// }

/******************************************************************************************************
方法十:限幅消抖滤波法
方法: 相当于“限幅滤波法”+“消抖滤波法”;
先限幅,后消抖。
优点: 继承了“限幅”和“消抖”的优点;
改进了“消抖滤波法”中的某些缺陷,避免将干扰值导入系统。
缺点: 对于快速变化的参数不宜。
******************************************************************************************************/

// #define FILTER10_A 51
// #define FILTER10_N 5
// unsigned short i = 0;
// unsigned short Value;

// short filter10()
// {
// unsigned short NewValue;
// unsigned short new_value;
// Value = ftable[b - 1];
// NewValue = ftable[b];
// b++;
// if (b == 255)
// {
// b = 1;
// }
// if (((NewValue - Value) > FILTER10_A) || ((Value - NewValue) > FILTER10_A))
// {
// new_value = Value;
// }
// else
// {
// new_value = NewValue;
// }
// if (Value != new_value)
// {
// i++;
// if (i > FILTER10_N)
// {
// i = 0;
// Value = new_value;
// }
// }
// else
// {
// i = 0;
// }

// printf("%d\n", Value);
// }

/**
* @brief 主函数
*/
int main(void)
{
while (1)
{
// filter1();
// filter2();
// filter3();
// filter4();
// filter5();
// filter6();
// filter7();
// filter8();
// filter9();
// filter10();
Sleep(300);
}
}

程序顺序执行、定时器任务执行及各种RTOS的优缺点及原理

程序顺序执行优缺点

顺序执行:是指程序按照代码的书写顺序依次执行,每个任务必须等待前面的任务执行完成后才能执行。这种执行方式简单明了,易于理解和调试,但执行效率相对较低,因为程序需要等待前面的任务完成后才能开始执行下一个任务。

并发执行:是指程序中的多个任务可以同时执行,不必等待前面的任务完成。这种执行方式可以提高执行效率充分利用计算机的资源,但也会带来一些问题,如竞争条件、死锁等

程序顺序执行的特征主要包括顺序性、封闭性、可再现性、可控制性、自上而下、确定性:

顺序性:程序按照编写的顺序一步一步地执行,每一步完成后才能进入下一步,直到程序执行完毕。这一特征确保了程序执行的顺序性,即程序的操作严格按照程序所规定的顺序执行,每个操作必须在下一操作开始之前结束。

封闭性:程序在封闭的环境下执行,即程序运行时独占全机资源,资源的状态(除初始状态外)只有本程序才能改变它。程序一旦开始执行,其执行结果不受外界因素影响。

可再现性:程序的顺序执行是可重复的,即每次执行都按照相同的顺序和方式执行,结果也应该是相同的。只要程序执行时的环境和初始条件相同,当程序重复执行时,无论它是从头到尾不停顿地执行,还是“停停走走”地执行,都将获得相同的结果。

可控制性:程序的顺序执行是可控制的,即可以通过程序中的控制语句(如条件语句、循环语句)来改变程序的执行顺序和方式。

自上而下:程序的顺序执行是自上而下的,即从程序的第一行开始执行,直到最后一行结束。

确定性:程序执行的结果与它的执行过程无关。换言之,就是程序无论是从头到尾地执行,还是“停停走走”地执行,都不会影响所得结果。

定时器任务执行的优缺点

定时器的工作原理是基于时序控制的模块,它的作用是按照程序预定的时序点,定时触发动作,从而起到程序控制的作用,它可以按照一定的时序,定时触发某些事件

优点:

  1. 可以增加程序的灵活性
  2. 可以降低程序的复杂度(可以用它来控制单片机的某些动作,这样可以减少程序中的指令数)
  3. 可以提高程序的稳定性(它可以按照一定的时序触发某些事件,从而提高程序的可靠性)

缺点

  1. 容易出现程序错误(它可以按照一定的时序触发某些事件,但是这种触发事件可能会出现误差,从而导致程序错误)
  2. 会降低单片机的运行效率(按照一定的时序编程会占用一定的单片机处理时间,从而降低单片机的运行效率)

主要用途:

  1. 延时调节:可以用来对某种设备进行延时调节,控制某种设备的工作时间,从而达到控制设备的效果

  2. 定时控制:可以用来定时控制多种设备,按照一定的时序控制设备,从而获得更加精确的控制效果

  3. 事件触发:可以触发某种事件,按照一定的时序触发某种事件,从而达到控制程序的作用

  4. 系统调度:可以用来调度系统的操作,按照一定的时序来进行系统的调度控制,从而达到最佳的操作效果

RTOS的优缺点

RTOS,全称 Real-Time Operating System ,即 实时操作系统。它是一种专门用于实时系统的操作系统,主要目的是提供及时响应和高可靠性。RTOS的核心在于其任务调度功能,它能严格按照优先级分配CPU时间片,确保实时任务得到及时处理。

RTOS技术的概念及特点:

  1. 实时性:RTOS的最主要特征是对实时性的支持。它能够确保系统在规定的时间内对事件作出响应,包括硬实时系统(需要对任务响应时间进行硬性保证)和软实时系统(对任务的响应时间有一定的容忍度)。
  2. 任务调度:RTOS包含一个任务调度器,能够有效地管理多个任务的执行。它负责按照优先级或其他调度算法,决定哪个任务在何时执行。
  3. 任务管理:RTOS提供任务管理功能,允许开发者创建、删除、挂起和恢复任务。任务是系统中的基本执行单元,可以看作是一个独立的线程。
  4. 中断服务例程: RTOS通常支持中断服务例程,以处理来自外部设备或其他任务的中断请求。中断服务例程是一段能够在中断事件发生时迅速执行的代码。
  5. 实时时钟:RTOS提供实时时钟服务,用于测量和记录时间,帮助任务和事件的时间同步。
  6. 同步和通信机制:为了实现任务之间的合作和通信,RTOS提供了同步和通信机制,如信号量、消息队列、互斥锁等。
  7. 资源管理:RTOS能够有效地管理和分配系统资源,包括内存、外设等,以确保任务能够按照预期的方式协同工作。
  8. 可裁剪性:RTOS通常具有可裁剪的特性,允许用户根据具体应用的需求选择性地启用或禁用某些功能,以减小系统的开销。

优点:

  1. 系统结构更清晰:当代码量较大时,RTOS会提供一个稳定的、结构清晰的框架,方便后续的开发与维护。
  2. 模块化和高内聚:使用RTOS写任务可以做到更加模块化,高内聚,低耦合,有利于代码的学习和技术提升。
  3. 中间件丰富:RTOS中间件比较多,可以方便移植使用,减少开发者的工作量。
  4. 趋势和适应性:随着32位CPU市场的扩大,RTOS的使用也成为一个趋势,尤其在一些特定的行业如工控、医疗、航空军工等,RTOS的强实时性和高稳定性使其更具优势。

缺点:

  1. 资源开销:RTOS本身会占用一些系统资源,包括内存和处理器时间。
  2. 学习曲线:学习RTOS可能需要一些时间,特别是对于初学者。
  3. 不适用于所有应用:对于一些简单的嵌入式应用,引入RTOS可能会显得过于庞大和不必要。

RTOS与裸机如何选择:

  1. 实时性需求: 如果系统对实时性能要求较高,特别是在响应时间上有硬性要求,RTOS可能更为适用。
  2. 开发周期:对于较为简单的项目,裸机开发可能更快速。
  3. 复杂性:如果项目较为复杂,多任务、同步和通信需求较多,RTOS可能提供更好的抽象和管理。
  4. 资源约束:如果系统资源有限,裸机开发可能更为合适,因为RTOS本身会占用一些额外的资源。

IIC

模拟iic和硬件iic区别

I2C协议可以被模拟和硬件实现:
模拟I2C是用两条GPIO(General Purpose Input Output)管脚的软件模拟的,将一个GPIO设置为数据线SDA,另外一个设置为时钟线SCL。
硬件I2C则是通过一个I2C控制器实现的,该控制器被建立在微控制器芯片或单独的I2C芯片中,通过集成的硬件内部逻辑和电路来控制时序和数据格式,实现I2C总线通信。

I2C协议传输时分为两类线,一类为时钟线SCL,另一类为数据线SDA。时钟线由主节点产生,用于同步数据传输,数据线用于传输真正的数据。

模拟I2C的主要限制是不能在高速模式下运行,并且有可能出现线路噪音、误码等问题。
硬件I2C通信更加可靠,并且具有更高的性能。

硬件控制器内置在微控制器芯片或者单独的I2C芯片中,在控制器的支持下,可以实现高速数据传输,避免了SCL和SDA之间的相互影响。此外,在硬件I2C中,处理信号和协议的复杂算法已经内置在控制器中,不需要用户自己实现,简化了通信过程,提高了可靠性。

软件I2C作为一个模拟方法,还存在另一个重要的缺点就是不同于硬件I2C,模拟I2C的芯片在同时进行通信时需要耗费大量的CPU资源,对内存和处理器速度的要求更高。
反过来,硬件I2C通常可以在任何操作系统和平台上轻松使用,这种通信协议不需要使用大量的额外的内存或处理器周期。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/**
* 函 数:I2C写SCL引脚电平
* 参 数:BitValue 协议层传入的当前需要写入SCL的电平,范围0~1
* 返 回 值:无
* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SCL为低电平,当BitValue为1时,需要置SCL为高电平
*/
void MyI2C_W_SCL(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue); // 根据BitValue,设置SCL引脚的电平
Delay_us(10); // 延时10us,防止时序频率超过要求
}

/**
* 函 数:I2C写SDA引脚电平
* 参 数:BitValue 协议层传入的当前需要写入SDA的电平,范围0~0xFF
* 返 回 值:无
* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SDA为低电平,当BitValue非0时,需要置SDA为高电平
*/
void MyI2C_W_SDA(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue); // 根据BitValue,设置SDA引脚的电平,BitValue要实现非0即1的特性
Delay_us(10); // 延时10us,防止时序频率超过要求
}

/**
* 函 数:I2C读SDA引脚电平
* 参 数:无
* 返 回 值:协议层需要得到的当前SDA的电平,范围0~1
* 注意事项:此函数需要用户实现内容,当前SDA为低电平时,返回0,当前SDA为高电平时,返回1
*/
uint8_t MyI2C_R_SDA(void)
{
uint8_t BitValue;
BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11); // 读取SDA电平
Delay_us(10); // 延时10us,防止时序频率超过要求
return BitValue; // 返回SDA电平
}

/**
* 函 数:I2C初始化
* 参 数:无
* 返 回 值:无
* 注意事项:此函数需要用户实现内容,实现SCL和SDA引脚的初始化
*/
void MyI2C_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 开启GPIOB的时钟

/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); // 将PB10和PB11引脚初始化为开漏输出

/*设置默认电平*/
GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11); // 设置PB10和PB11引脚初始化后默认为高电平(释放总线状态)
}

/*协议层*/

/**
* 函 数:I2C起始
* 参 数:无
* 返 回 值:无
*/
void MyI2C_Start(void)
{
MyI2C_W_SDA(1); // 释放SDA,确保SDA为高电平
MyI2C_W_SCL(1); // 释放SCL,确保SCL为高电平
MyI2C_W_SDA(0); // 在SCL高电平期间,拉低SDA,产生起始信号
MyI2C_W_SCL(0); // 起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
}

/**
* 函 数:I2C终止
* 参 数:无
* 返 回 值:无
*/
void MyI2C_Stop(void)
{
MyI2C_W_SDA(0); // 拉低SDA,确保SDA为低电平
MyI2C_W_SCL(1); // 释放SCL,使SCL呈现高电平
MyI2C_W_SDA(1); // 在SCL高电平期间,释放SDA,产生终止信号
}

/**
* 函 数:I2C发送一个字节
* 参 数:Byte 要发送的一个字节数据,范围:0x00~0xFF
* 返 回 值:无
*/
void MyI2C_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i++) // 循环8次,主机依次发送数据的每一位
{
MyI2C_W_SDA(Byte & (0x80 >> i)); // 使用掩码的方式取出Byte的指定一位数据并写入到SDA线
MyI2C_W_SCL(1); // 释放SCL,从机在SCL高电平期间读取SDA
MyI2C_W_SCL(0); // 拉低SCL,主机开始发送下一位数据
}
}

/**
* 函 数:I2C接收一个字节
* 参 数:无
* 返 回 值:接收到的一个字节数据,范围:0x00~0xFF
*/
uint8_t MyI2C_ReceiveByte(void)
{
uint8_t i, Byte = 0x00; // 定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到
MyI2C_W_SDA(1); // 接收前,主机先确保释放SDA,避免干扰从机的数据发送
for (i = 0; i < 8; i++) // 循环8次,主机依次接收数据的每一位
{
MyI2C_W_SCL(1); // 释放SCL,主机在SCL高电平期间读取SDA
if (MyI2C_R_SDA() == 1)
{
Byte |= (0x80 >> i);
} // 读取SDA数据,并存储到Byte变量
// 当SDA为1时,置变量指定位为1,当SDA为0时,不做处理,指定位为默认的初值0
MyI2C_W_SCL(0); // 拉低SCL,从机在SCL低电平期间写入SDA
}
return Byte; // 返回接收到的一个字节数据
}

/**
* 函 数:I2C发送应答位
* 参 数:Byte 要发送的应答位,范围:0~1,0表示应答,1表示非应答
* 返 回 值:无
*/
void MyI2C_SendAck(uint8_t AckBit)
{
MyI2C_W_SDA(AckBit); // 主机把应答位数据放到SDA线
MyI2C_W_SCL(1); // 释放SCL,从机在SCL高电平期间,读取应答位
MyI2C_W_SCL(0); // 拉低SCL,开始下一个时序模块
}

/**
* 函 数:I2C接收应答位
* 参 数:无
* 返 回 值:接收到的应答位,范围:0~1,0表示应答,1表示非应答
*/
uint8_t MyI2C_ReceiveAck(void)
{
uint8_t AckBit; // 定义应答位变量
MyI2C_W_SDA(1); // 接收前,主机先确保释放SDA,避免干扰从机的数据发送
MyI2C_W_SCL(1); // 释放SCL,主机机在SCL高电平期间读取SDA
AckBit = MyI2C_R_SDA(); // 将应答位存储到变量里
MyI2C_W_SCL(0); // 拉低SCL,开始下一个时序模块
return AckBit; // 返回定义应答位变量
}

注意

  1. 根据传输的位来拉SCLSDA线电平:若该位为1,则拉高SCLSDA线电平,否则就拉低
  2. SCLSDA需默认拉高,以释放总线状态,准备好传输数据
  3. IIC起始信号:SCL高电平,SDA下降沿(注:起始后需把SCL也拉低,使主机占用总线准备发送数据);IIC结束信号:SCL高电平,SDA上升沿;
  4. 主机发送:每次发送1位,循环8次即可发送一个字节;在发送数据时,应先拉高SCL(从机在SCL高电平期间读取SDA),再拉低SCL(主机开始发送下一位数据)
  5. 从机接收:在接收前,应先拉高SDA,避免主机抢占SDA,导致数据出错;为存储接收到的数据,需拉高SCL,使主机在SCL高电平期间读取SDA,每读取一位就存入提前申请好的变量中;读完后即可得到一个字节的数据。然后拉低SCL,使从机可写入SDA,这样即可实现从机发送。
  6. 应答信号:主机把应答位数据放到SDA线;然后先拉高SCL,以读取应答位;再拉低SCL,开始下一个时序模块;

有刷电机的驱动方式,转速调节;频率对转速和电流的影响

有刷电机转动原理

组成:定子、转子、电刷、换向器

原理:外接电源给电机通电,电流经过电刷流向与换向器连接的线圈(转子),电流流过线圈产生磁场;产生的磁场与定子磁场互斥,则转子转动;

转子转动半圈后若无外力,由于转子磁场与定子磁场同性相吸,则趋于稳定,不再转动;但由于换向器的存在导致线圈中电流流向与之前相反(因为换向器位置与之前相反,而换向器与线圈相连,故线圈中电流流向与之前相反),故而又与此刻的定子磁场互斥,又使转子转动;

以上步骤不停循环就导致转子持续转动。

有刷电机转速受哪些因素影响

电压、负载、磁通量等;其中,电压与电流是影响电机转速为重要的因素

如何调节有刷电机转速

  1. 调节电压,有刷电机的转速与电压成正比,因此可以通过调节电压来改变电机的转速。但是需要注意,过高或过低的电压都会对电机造成损害,因此需要根据电机的额定电压来调节电压。
  2. 调节电流,电机的转速还与其所受的负载有关,因此可以通过调节电流来改变电机的转速。增加电流可以提高电机的转速,但是需要注意电机的额定电流,避免电机过载。
  3. 更换齿轮更换不同大小的齿轮可以改变电机的输出转速。一般来说,大齿轮可以降低电机的转速,小齿轮则可以提高电机的转速。
  4. 调节磁通量调节磁通量也可以影响电机的转速。增加磁通量可以提高电机的转速,但是需要注意电机的磁通量和饱和磁通量,以避免电机工作不稳定或过载。

频率对转速和电流的影响

增加频率会增加转速

二分法

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 数组
unsigned short ADC_Table[101] = {
3999,
3967, 3961, 3954, 3947, 3940, 3932, 3924, 3916, 3907, 3899,
3889, 3880, 3870, 3860, 3850, 3839, 3827, 3816, 3804, 3791,
3779, 3766, 3752, 3738, 3724, 3708, 3693, 3676, 3660, 3643,
3625, 3607, 3589, 3570, 3550, 3530, 3510, 3489, 3468, 3446,
3424, 3401, 3378, 3354, 3330, 3306, 3281, 3255, 3229, 3203,
3175, 3146, 3117, 3088, 3058, 3028, 2997, 2966, 2935, 2903,
2871, 2839, 2806, 2774, 2741, 2707, 2674, 2640, 2607, 2573,
2539, 2505, 2471, 2436, 2402, 2368, 2334, 2300, 2265, 2231,
2197, 2163, 2130, 2096, 2063, 2028, 1993, 1959, 1925, 1891,
1857, 1823, 1790, 1758, 1725, 1693, 1661, 1630, 1599, 1580
};

/**
* @brief 查询ADC对应温度
* @param[1] arr ADC数组
* @param[2] left right 数组的首末下标
* @param[3] x 实时获取的ADC值
*/
int adc_search(const unsigned short* arr, unsigned char left, unsigned char right, unsigned short x)
{
if (x >= arr[0])
{
return 0;
}
if (x <= arr[100])
{
return 100;
}

while (left <= right)
{
int mid = left + (right - left) / 2;
if (arr[mid] >= x && arr[mid + 1] <= x)
{
return mid + 1;
}
else if (arr[mid] <= x)
{
right = mid - 1;
}
else if (arr[mid] >= x)
{
left = mid + 1;
}
}
return -1;
}

// 调用
adc_search(ADC_Table, 0, 100, ntc_adc);

问题

  1. 二分法为什么会陷入死循环?

判断条件选取的不对,多一个 = 就有可能陷入死循环;另外数组最好为单数,双数极可能陷入死循环

  1. 为什么 while (left <= right)<= 不用 <

因为 right 的值为 数组元素个数 - 1 ,这时 leftright 是可以相等的

  1. 为什么取中间数要写成 int mid = left + (right - left) / 2; 这样?

因为若 left + right 很大的时候会发生整形溢出,这样写可以尽量避免

  1. 为什么判断条件写成 if (arr[mid] >= x && arr[mid + 1] <= x) 这样?

因为数组元素为 unsigned short 型,不会出现浮点数,且数组元素不是等差为1的等差数列,不是非此即彼,可能会出现 arr[mid] >= x && arr[mid + 1] <= x 这种情况

  1. 建议:遇到死循环将 left right mid 打印出来看看

队列

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <stdio.h>
#include <stdlib.h>

#define QUEUE_SIZE 50

int queue[QUEUE_SIZE] = {0};
int head = 0; // 队头出
int tail = 0; // 队尾进

void init_queue(void)
{
head = 0;
tail = 0;
}

// 入队操作
void enqueue(int value)
{
if ((tail + 1) % QUEUE_SIZE == head)
{
printf("QUEUE IS FULL!\n"); // 队满
init_queue(); // 队头、队尾清零;重新开始,即循环队列
return;
}
queue[tail] = value;
tail = (tail + 1) % QUEUE_SIZE;
}

// 出队操作
int dequeue(void)
{
if (head == tail)
{
printf("QUEUE IS EMPTY!\n"); // 队空
return -1;
}
int result = queue[head];
queue[head] = 0;
head = (head + 1) % QUEUE_SIZE;
return result;
}

// 打印队列
void printQueue(void)
{
int i = head;
while (i != tail)
{
printf("%d ", queue[i]);
i = (i + 1) % QUEUE_SIZE;
}
printf("\n");
}

int main(void)
{
for (int i = 0; i < 50; i++)
{
enqueue(i);
}
printQueue(); // QUEUE IS FULL!

for (int i = 49; i < 98; i++)
{
enqueue(i);
}
printQueue(); // 49 ~ 98
}

注意

  1. 队列(数组)的长度一般都要比要存入的数据长度大不少
  2. 使用该操作,队列(数组)的最后一个元素不会被使用,这就是第一点的原因

六轴陀螺仪工作原理,数据处理

参考链接

参考:https://blog.csdn.net/propor/article/details/136938863

锂电池充放电原理

锂电池充电有四个阶段

涓流充电、恒流充电、恒压充电、充电结束

充电初期采用恒流充电,电池电压增加,当到达一定值后,再采用恒压充电,电流逐渐降低,直到到达设定值,完成充电过程。

阶段1:涓流充电

涓流充电用来先对完全放电的电池单元进行预充 — 恢复性充电。

在电池电压低于满电电压时,先采用最大xxc(一般为0.1C)的恒定电流对电池进行充电。

0.1C即 恒流充电电流的十分之一

阶段2:恒流充电

当电池电压上升到涓流充电阈值以上时,提高充电电流进行恒流充电。

恒流充电的电流一般在0.2C至 1.0C之间。恒流充电时的电流并不要求十分精确,准恒定电流也可以。

阶段3:恒压充电

当电池电压上升到满电电压时,恒流充电结束,开始恒压充电阶段。

阶段4:充电终止

充电结束