你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32 定时器实现红外遥控数据接收

[复制链接]
STMCU小助手 发布时间:2023-1-6 17:57
一、原理1、红外发射协议
  • 红外通信的协议有很多种。这个实验使用的是NEC协议。这个协议采用PWM的方法进行调制,利用脉冲宽度来表示 0 和 1 。

  • NEC 遥控指令的数据格式为:同步码头、地址码、地址反码、控制码、控制反码。同步码由一个 9ms 的低电平和一个 4.5ms 的高电平组成,地址码、地址反码、控制码、控制反码均是 8 位数据格式。按照低位在前,高位在后的顺序发送。采用反码是为了增加传输的可靠性。因此,每帧的数据为 32 位,包括地址码,地址反码,控制码,控制反码。反码可用于解码时进行校验比对。

  • NEC码的位定义:一个脉冲对应 560us 的连续载波,一个逻辑 1 传输需要 2.25ms(560us 脉冲+1680us 低电平),一个逻辑 0 的传输需要 1.125ms(560us 脉冲+560us 低电平)。而遥控接收头在收到脉冲的时候为低电平,在没有脉冲的时候为高电平,这样,在接收头端收到的信号为:逻辑 1 应该是 560us 低+1680us 高,逻辑 0 应该是 560us 低+560us 高。

  • 红外数据的波形如下图:包括一个同步头和 32 帧数据。


    " R* B7 A4 f6 e) c

2 J+ R! W0 a* E/ ?9 O

3 o' j0 z# T) R3 W 20170808230617825.jpg
) A- A+ C/ Y, C; C0 b# }
) B# D8 s: N9 g9 p; A" A- X: Q
  • 下图可看出,同步头为 9ms 低电平加上 4.5ms 高电平,控制码为 8 个 0,控制反码为 8 个 1。

    ; R. R5 }7 G* Z2 I6 p
20170808231029615.jpg
) w5 k; ~; m8 S6 B  \5 g! H" M1 e; J4 r7 ]+ Z- J
2、定时器计数
  • 定时器就是按照一个特定的频率对计数值进行加一或减一操作,当数值溢出时则产生一个标志或中断。这里是用定时器计数产生一个周期性的中断。
    / O" ?7 M: A4 R& _2 b. f
  N$ U: I- w! p( s+ b
) n, }) H2 ?1 g0 o) k# A0 p& C
3、实现方法
  • 利用定时器记录两个下降沿之间的时间,通过该时间判断是否是同步头信息、数据 1 或者数据 0。当检测到同步头,开始记录 32 个数据的时间值。
    3 D$ k* H. K3 T9 F% v- {0 _/ z+ ^
    4 A8 ~8 l/ g$ ?) X) M0 g5 k 20170808225212279.jpg
      ?" {( V4 p8 u+ L) f% S% |: i) b

" e; B5 @: u) }& o

! Z$ k) {+ H+ o3 b* ]/ M" k二、实现1、配置 GPIO 口下降沿触发中断
  • 示例代码中使用 PA7 管脚,配置为上拉输入模式。

  • 选择下降沿触发,是因为红外接收管默认情况下保持高电平,接收到数据时从高电平转变为低电平。

  • 中断源选择为 EXTI_Line7 ,在库函数中对该中断源定义的服务函数为 EXTI9_5_IRQHandler(),也就是说外部中断 5 到 9 是 共用一个中断服务函数的。

  • 配置代码如下:


    * p% }/ {: @+ W0 ]. K  }
  1. void IR_Pin_init()* {# _6 l3 B- G$ O  d
  2. {
    + n3 P; J$ ^- A2 k! `# a3 e
  3.   GPIO_InitTypeDef GPIO_InitStructure;! I0 T8 g0 x5 d' c! G. M: I; _2 U
  4.   EXTI_InitTypeDef EXTI_InitStructure;! o$ [- q" A; n0 o* }' \2 S. F
  5.   NVIC_InitTypeDef NVIC_InitStructure;, q  d7 `% @$ P% b
  6.   RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,ENABLE);
    7 ]& j' w. n* ^  o% k( V
  7. - z9 o& F! C3 j" q* C6 P
  8.   GPIO_InitStructure.GPIO_Pin=GPIO_Pin_7;, J' e4 l, T! [; r  b
  9.   GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
    6 H3 ~6 v. N; A4 M1 B% B- X
  10.   GPIO_Init(GPIOA,&GPIO_InitStructure);: l7 r9 v# H* N- S4 E
  11. " _# ]5 w+ l/ [+ ^' ]# q
  12.   EXTI_ClearITPendingBit(EXTI_Line7);
    9 P4 f  b9 x5 Y: B7 i. L/ ^
  13.   GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource7);
    ' ]# d2 s, G: m* ]4 c7 C3 i2 C, ?9 ]9 t$ _
  14.   EXTI_InitStructure.EXTI_Line=EXTI_Line7;
    ; x- `  P  W2 W1 V: R: F1 ?
  15.   EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;8 g: t: l6 }4 V# C8 E; ?
  16.   EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;
    + e& c8 [0 i3 f* Z! |
  17.   EXTI_InitStructure.EXTI_LineCmd=ENABLE;
    ' Y6 j1 Y9 [( s  g/ I& T
  18.   EXTI_Init(&EXTI_InitStructure);
    / p! b' e' H. s+ z/ d7 L7 u

  19. , [8 B+ g" N# u( q/ R
  20.   NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); 7 f6 o; u* s& M5 A" h
  21.   NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
    & N. g! L* Z) ^
  22.   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    7 F' l4 T: w8 H  H. ~2 ^8 u
  23.   NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;     
    % T/ v; A* _5 \8 v
  24.   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  6 m- |2 C7 [/ e, m7 q
  25.   NVIC_Init(&NVIC_InitStructure);
    5 [+ A- c0 l$ H* N
  26. }
复制代码
6 {& V( B7 w  w& X0 U+ d
5 {0 T# A& b, O4 A

# q( z& Z6 K6 y3 w( f2、配置定时器计数值
  • 定时器使用的是 TIM2 通用定时器,模式为向上计数。在该模式中,计数器从 0 计数到自动加载值 (TIMx_ARR计数器的内容) ,然后重新从 0 开始计数并且产生一个计数器溢出事件。

  • 示例函数接收两个参数,分别为预分频器的值和自动加载值。通过调整这两个参数,可以灵活地改变定时器的计数周期。例如在 TIM2 的默认时钟源 PCLK1 为96MHz时,使用语句 Tim2_UPCount_Init(SystemCoreClock/1000000-1,100-1); //0.1ms 进行初始化,可以每 0.1ms 产生一次中断。

  • 示例代码如下:


    ' \( [' D; v+ a5 ~/ C

- B1 M" V$ y- H- f2 @  R, K
8 z+ L9 A+ b/ d/ I5 _' b. P
  1. void Tim2_UPCount_Init(u16 Prescaler,u16 Period)
    9 n9 q9 f, E" W* m: A" K6 `
  2. {
    : P! r1 O& x. T5 r/ U# b
  3.   TIM_TimeBaseInitTypeDef TIM_StructInit;  d- i$ B. ?1 c/ {# k
  4.   NVIC_InitTypeDef NVIC_StructInit;# j% Z2 s  g/ g+ P
  5. % f  m$ X2 D. x7 n, @
  6.   RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    # \8 Q/ z5 n# V, P" U
  7. 8 C, o$ O: D! Y. [1 ]/ ]2 B# \
  8.   TIM_StructInit.TIM_Period=Period;
    0 Z5 t- i$ c  X) K  u8 o$ y- J
  9.   TIM_StructInit.TIM_Prescaler=Prescaler;2 j8 k5 x* c8 @6 K# v5 L) k
  10.   TIM_StructInit.TIM_ClockDivision=TIM_CKD_DIV1;5 Y  d' F% H  @4 s7 o2 W
  11.   TIM_StructInit.TIM_CounterMode=TIM_CounterMode_Up;+ ]& I% D) c# |$ A3 ~
  12.   TIM_StructInit.TIM_RepetitionCounter=0;
    0 X, R# f# [" ?
  13.   TIM_TimeBaseInit(TIM2, &TIM_StructInit);) q/ v' c' N( v) R! F# t; t
  14. + K, t2 A- g, ^0 o5 A# D" w
  15.   TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);- i7 i3 \2 O: U# \$ Y# w
  16.   TIM_Cmd(TIM2, ENABLE);    : U( e' Q. C5 B# [6 d) o! H
  17.   TIM_ClearFlag(TIM2, TIM_FLAG_Update);
    7 ~6 I: y) f, d. Z- L

  18. 9 u9 x% n; H, D
  19.   NVIC_StructInit.NVIC_IRQChannel=TIM2_IRQn;7 K% G) P" r; C
  20.   NVIC_StructInit.NVIC_IRQChannelCmd=ENABLE;
    * F! X* y, U) w3 q( O! R
  21.   NVIC_StructInit.NVIC_IRQChannelPreemptionPriority=0;
      Q( e% F7 j+ ~: m
  22.   NVIC_StructInit.NVIC_IRQChannelSubPriority=1;% D" \4 y9 X; K# ~4 C, K# e, C
  23.   NVIC_Init(&NVIC_StructInit);
    3 t: P0 S# m6 ]5 j
  24. }
复制代码

2 T  N# G% L2 @/ R) @  {' `7 @9 j1 F  z# j
+ T5 |$ E5 Y! l5 e$ Z0 _

+ }5 G7 J, h' q0 g- i/ a3、定时器中断函数统计时间
  • 如上所说,定时器每 0.1ms 计数完成,产生中断,在中断函数中对标志位 ucTim2Flag 加 1,意味着时间过去了 0.1ms。

  • 时间标志位原型为 uint16_t ucTim2Flag; 。

  • 示例代码如下:

    6 Y5 T, j. L. L' j# j( v5 r: i
  1. void TIM2_IRQHandler(void)
    0 f8 n; F# k& a2 B, D) w
  2. {* N( m5 B# W$ N. _, G, F5 G7 p
  3.   TIM_ClearITPendingBit(TIM2, TIM_IT_Update);' j! X- o7 \# W% H5 P, c
  4.   ucTim2Flag++;
    0 n' j0 K) p4 o9 O: m! E- |6 I( v
  5. }
复制代码

7 h9 d0 A; O  D. l' E/ E
& t5 k: z' f  m

* D/ a; L; d  s; J/ s8 {  S  ]" G: l4、GPIO 中断函数中接收 32 位数据
  • 在下降沿触发的 IO 口中断函数中,需要实现统计两个下降沿之间的时间,并将其记录在数组中。

  • 下降沿第一次触发时,清除当前定时器中的计数值,以便统计时间。之后每一次下降沿触发就记录下当前计数值,然后再对其清零。如果该时间在同步头的时间区间内,对索引进行清零,表示重新开始接收数据。

  • 完整接收同步头和 32 个数据之后,表示接收完成。

  • 示例代码如下:

    " R$ c4 b9 o) J# X! r- }
$ f' b, {5 `0 U4 \) I5 H' \/ p
8 R0 a6 Q. ^& O1 Q) U0 ^$ f
  1. uint8_t irdata[33];   //用于记录两个下降沿之间的时间
    ! j* }0 W; d) x' C
  2. bool receiveComplete; //接收完成标志位
    0 F! x9 B7 P3 H3 L& ?
  3. uint8_t idx;          //用于索引接收到的数值% f3 J6 a& n! }( V
  4. bool startflag;       //表示开始接收6 ^2 i+ q; j9 f( @7 j

  5. & H* [$ U$ e' D+ o
  6. void EXTI9_5_IRQHandler(void)6 y& g, h7 m" N, G$ l
  7. {
    1 O. D7 g9 x9 r& D2 I1 m) o
  8.   uint16_t ir_time;
    ! P$ j# o, b! o% A9 J8 r
  9.   if(startflag)
    5 R0 ]$ n2 X1 I
  10.   {7 h) Q( ^7 O3 f4 F
  11.       ir_time = ucTim2Flag;
    1 v6 \& Z% K. m, v: `. v. E
  12.       if(ucTim2Flag < 150 && ucTim2Flag >= 50 ) // 接收到同步头0 u, O- `  \) ?
  13.       {
    $ B0 G! z, ~+ o9 _6 h* J
  14.           idx=0;  // 数组下标清零
    8 W  ]3 s7 k( O% u$ x% m1 j" T) @* h- I
  15.       }' x8 H: ^' N! Y/ O1 w

  16. 6 `% [$ ?8 v6 Y2 n
  17.       irdata[idx] = ucTim2Flag;  // 获取计数时间/ \3 J. o' M3 `% p7 }7 d
  18.       ucTim2Flag = 0;            // 清零计数时间,以便下次统计
    2 ~5 g2 Z7 k3 I. q7 R; y
  19.       idx++;                     // 接收到一个数据,索引加1
    2 X% D/ b7 |, g2 k
  20. ! g& \& y0 C4 p% `
  21.       if(idx==33)       // 如果接收到33个数据,包括32位数和以一个同步头2 c- i9 D: q& {6 C8 ~. g3 V
  22.       {
    . M3 r+ _9 q9 F, Q- ~. \. y+ r( @
  23.           idx=0;
    9 m5 y$ _6 r0 H4 e6 ~
  24.           ucTim2Flag = 0;
    9 O: v! c% E$ ]( @* N. m/ H7 n
  25.           receiveComplete = TRUE;
    ! R2 U/ \% B3 X* h( [- w3 w
  26.       }
    6 j+ E! Q; Q: U4 _" k8 W( r- R
  27.   }2 ^" B7 \6 O% \1 `; v$ d2 }
  28.   else   // 下降沿第一次触发
    / v" e/ ]0 e& z. c" ^& p
  29.   {
    0 E, Z' e  E! V, T* |, b$ ^1 p% H5 ^
  30.       idx = 0;& s3 q) W: o( Z7 r3 D
  31.       ucTim2Flag = 0;& J- J) V) Q% ^! V& [1 p  S
  32.       startflag = TRUE;+ N4 s. ^0 c2 w" n; ]2 F) |5 ^
  33.   }
    3 n+ K. Y, [" D* y  `& X0 z- N: \

  34. / x5 }. @8 i. G7 m5 h
  35. EXTI_ClearITPendingBit(EXTI_Line7);  // 清除中断标志
    4 C5 X) Z5 a" o- S7 D/ E* C6 F3 d
  36. }
复制代码

) r9 s8 h3 _  W' L! k4 N  f+ R( k0 R+ u. O7 U
5、判断控制码值
  • 由于中断函数中接收并记录下的数据是两个下降沿之间的时间,并不是红外所发送的数据。因此需要根据红外协议,对 32 个时间进行判断,从而获得红外真正发送的数据。

  • 下面这个函数需要在红外完整接收数据后执行,可通过判断接收完成标志位 receiveComplete 来实现。

  • 示例代码如下:


    ) ?9 V1 g/ Z* A9 \+ X

( _5 ^) q5 G; |4 V
7 ~6 Z6 K6 }$ H
  1. uint8_t Ir_Server()
    : `; l3 |" ?$ b, |0 @7 d
  2. {  t% i: A+ E# h6 T# N; Z
  3.   uint8_t i,j,idx=1; //idx 从1 开始表示对同步头的时间不处理
    8 Z- z0 |: P- @4 [
  4.   uint8_t temp;! V8 N% H4 U. Z% m$ ~
  5.   for(i=0; i<4; i++)3 w- p! x0 y6 H5 h2 X' t& ~6 \
  6.   {
      D4 ^* F7 k2 j" L1 y9 m9 b9 f( v0 W
  7.       for(j=0; j<8; j++)
    ! ^" o$ O0 L& ]. F2 i
  8.       {! e* @4 ]! K5 x) z4 H3 C. R4 C8 Q
  9.           if(irdata[idx] >=8 && irdata[idx] < 15)   //表示 0
    $ d: N6 E2 g, f4 B
  10.           {7 ^8 P5 q, o6 U, ?* {3 h  n0 t: {
  11.               temp = 0;( T& d' @: A$ V  v
  12.           }( i! n9 D! y: A* r, `
  13.           else if(irdata[idx] >=18 && irdata[idx]<25) //表示 1, ^  e! I1 q& C( n- _7 o2 C
  14.           {: m, P# P+ k/ N6 G
  15.               temp = 1;
    ' L" H- G* s$ E: H: ]
  16.           }/ o5 \" j# S9 _) H& e$ f, N
  17.           remote_code[i] <<= 1;  s. J. X3 `/ i7 i& T+ Z- D7 Q0 ]
  18.           remote_code[i] |= temp;/ G+ v: \+ s; @
  19.           idx++;
    : R& p/ w/ d6 }1 D; q
  20.       }$ k9 j3 X1 j9 E* ~
  21.   }
    $ a" q; b- n2 B* t; J. D

  22. - Q- M; @# x3 A$ ~( d
  23.   return remote_code[2];  // 该数组中记录的是控制码,每个按键不一样
    ' l  `# m& U' G
  24.   //for(idx=0; idx<4; idx++)9 H) E- \2 t' g8 Z4 b9 ?- q8 z/ G
  25.   //{6 [" U. O- H6 t: O6 u; G9 G
  26.   //    printf("remote_code[%d] = %#x\n",idx,remote_code[idx]);# Y' ?5 `: X; D  f1 Y5 `# @
  27.   //}
    & r1 a. _9 Y+ x/ |
  28. }
复制代码
$ h( K& L( _# j6 Q+ b5 {- h9 L/ Z

  v  J- |' e1 m& b4 G& o" M' g5 U
5 r. ^& @& q4 E' H
6、主函数
  • 在 main 函数中,对 IO 口和 定时器进行初始化。

  • 主循环中,通过判断接收完成标志位,对接收完成的按键控制码进行打印。

  • 示例代码如下:


    8 d) t# I) Z7 F; u5 [- K
5 s1 E2 @  [" i7 [& n) Y' G
0 ?; }6 Q' O3 ]& q! ?/ L  q
  1. void main()
    - b8 ], n" S  h
  2. {
    # ^  G6 Y0 k' n, }( {; ?0 M; v8 l* Y
  3.   ...9 v% ^" [( @; _( A/ V

  4. 2 x7 V8 ?2 D" l9 {+ P6 d9 V- s
  5.   IR_Pin_init();
    7 Q! t1 U  ^& J; j4 G( M! V
  6.   Tim2_UPCount_Init(SystemCoreClock/1000000-1,100-1);
    1 V; G) T1 H) @( m
  7. - f2 Q. X6 o9 E# N
  8.   while(1)
    1 z0 n) x! D: g4 Q+ F8 C+ [/ v
  9.   {5 r8 L- t. I- r7 X
  10.       if(repeatEnable)8 T5 _. \7 \7 G9 [; ~2 v- Q
  11.       {
    ' H: X$ H* [# d$ T
  12.           repeatEnable = FALSE;8 F, s3 h/ [5 Q6 U
  13.           Ir_Server();0 |8 j5 W/ d; {$ ]% J
  14.           printf("key_code = %#x\n",remote_code[2]);
    6 A4 ]4 i6 j& w* ?+ `
  15.       }
    / z. S8 _/ [' R" j- I; l, e
  16.   }
    . ^- v6 i" N0 V+ z! ^

  17. 1 F9 s; v. I" B$ ?' s# d5 d* E9 W
  18. }
复制代码

) w' a7 @8 A, a1 J, R! D$ g. [  O. G  `4 e' M9 O
: z' u0 s9 ~( `+ }) S1 w9 ?( ]
三、演示

如下图为串口打印出接收的红外按键值信息:

6 X. c5 _8 F# E0 g. u, a

20170808225232539.png

* I; q; D' h$ j4 ?

说明1:这只是实现红外接收的其中一种方法,网上还有一种比较常见的方法是利用下降沿触发,在中断中进行延迟,判断高电平持续时间以此来判断信号类别。个人感觉这不是一种很好的方法,因为在中断中进行延时会导致主函数得不到及时的处理。

说明2:在调试时,不要在中断处理中加入过多无关语句,例如打印语句,这会导致结果出错。

7 P% Z1 m0 b' Q/ ^1 _! J* N( B

转载自:xqhrs232


* m, w& [0 T8 Z: y% t
/ V( `* _( `. g! T1 j. H- i
收藏 评论0 发布时间:2023-1-6 17:57

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版