本实验使用到硬件资源如下: (1)D1 指示灯 (2)串口 1 (3)红外遥控器和红外接收头 D1 指示灯、串口 1 电路在前面章节都介绍过,这里就不多说,红外遥控器属于外部器件,只有红外接收头集成在开发板上,其电路如下图所示:
$ U6 f I5 I& m 从电路图中可以看到,红外接收头的数据输出管脚连接在 STM32F1 芯片的PG15管脚上,这里我们使用PG15 引脚内部上拉,所以可以省去一个上拉电阻,不会有影响,当然也可以接一个 10K 的上拉电阻。通过配置 PG15 引脚为外部中断功能,按照 NEC 协议进行解码。 D1 指示灯用来提示系统运行状态,红外遥控器用来发射红外键值的编码信 号,通过红外接收头进行解码,并将解码后的数据通过串口 1 打印输出。 本实例所要实现的功能是:使用外部中断功能将遥控器键值编码数据解码后通过串口打印输出,同时控制 D1指示灯闪烁,提示系统运行。程序框架如下: (1)使能 PG15 端口及 AFIO 时钟,映射 PG15 至外部中断线上,初始化 EXTI等 (2)编写红外解码函数(在 EXTI 中断处理) (3)编写主函数 外部中断的配置在“外部中断实验”章节中都有介绍,不清楚的可以回过头 看下。下面我们打开“ 红外遥控实验”工程,在APP 工程组中添加 hwjs.c 文件(里面包含了红外解码驱动程序),在 StdPeriph_Driver 工程组中添加stm32f10x_exti.c 库文件。EXTI 操作的库函数都放在stm32f10x_exti.c和stm32f10x_exti.h文件中, 所以使用到EXTI就必须加入 stm32f10x_exti.c文件,同时还要包含对应的头文件路径。 这里我们分析几个重要函数。 * Y# d% e* f9 y( \( P
外部中断初始化函数 我们知道红外接收头数据输出管脚是接在 PG15 上, 所以配置 PG15 为外部中断功能,初始化代码如下: - /****************************************************************
! Y. f( u) I1 o/ O# x - * 函 数 名 : Hwjs_Init, p; N! z: w7 `; L0 ?6 c
- * 函数功能 : 红外端口初始化函数 时钟端口及外部中断初始化
% M+ f* c) W+ I8 L - * 输 入 : 无
. a0 P" i0 ^$ {) M. [ - * 输 出 : 无- S, l$ N, w! i2 ~! ^
- *****************************************************************/
( K: E# D2 h( r/ x" q) L, R - void Hwjs_Init()) T; p4 L; ~0 J, s c
- {
5 w5 h" R- p$ N/ c - GPIO_InitTypeDef GPIO_InitStructure;& w- E' j: i8 }) e8 T7 p6 m
- EXTI_InitTypeDef EXTI_InitStructure;
( E2 P6 s* Y* x3 ^1 U - NVIC_InitTypeDef NVIC_InitStructure;! e/ _7 R/ K8 [- k1 [- |# [- H
- /* 开启 GPIO 时钟及管脚复用时钟 */
( y' ]0 A7 R8 g. W. @ ?! f - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG|RCC_APB2Periph_AFIO,ENABLE);
$ A4 C4 J `* M9 Y+ y7 a8 ?1 F$ O - GPIO_InitStructure.GPIO_Pin=GPIO_Pin_15;//红外接收
P" T. [7 V) | - GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;. R6 M' j' |2 I4 V7 s$ W3 G
- GPIO_Init(GPIOG,&GPIO_InitStructure);' A2 R# d) U; g5 W+ H) ~
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOG, GPIO_PinSource15); //选择 GPIO 管脚用作外部中断线路
F4 M& @; o" y; c& ~ - EXTI_ClearITPendingBit(EXTI_Line15);+ Y& [- V( h& p! V# E4 b& g5 G1 L7 l
- /* 设置外部中断的模式 */
! n2 L5 t, D: A - EXTI_InitStructure.EXTI_Line=EXTI_Line15;
, i! I7 E5 {0 ]9 H - EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
* o# ~6 w& Z) ], @- c9 V+ s( O) H& Z - EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;
# z( P9 m; v' e9 b - EXTI_InitStructure.EXTI_LineCmd=ENABLE;6 p* p! @ q! R2 V K% \
- EXTI_Init(&EXTI_InitStructure);7 Z: X: D4 ?! I) w" G
- /* 设置 NVIC 参数 */3 o+ ^8 \# z y! H# s6 P
- NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; //打开全局中断
N6 |8 _& D* ]! i4 S/ O; h - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占优先级为 0* B% D- p% Q5 `
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //响应优先级为1( w' L/ j% B0 R& O% K
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能/ L8 f3 l* O9 ~3 ^3 f# ?) I S
- NVIC_Init(&NVIC_InitStructure);
8 J& J, G8 y0 x - }
复制代码, K0 c1 C1 ^2 N u3 c" A
在Hwjs_Init()函数中,首先使能 GPIOG 端口和 AFIO 时钟,然后将PG15 映射到外部中断线 15 上, 并配置 PG15 为上拉输入模式, 最后初始化 EXTI 及NVIC,这里需要注意,PG15 的外部中断通道是 EXTI15_10_IRQn。
3 m4 _3 U1 `: h" k3 }0 o$ }" i% K! s
红外解码函数
* C# @* a a* m, @+ K 初始化外部中断后,中断就已经开启了,当 PG15 引脚上来了一个下降沿,就会触发一次中断,在中断内我们可以计算高电平时间,通过高电平时间判断是否进入引导码及数据 0 和1。具体代码如下: - void EXTI15_10_IRQHandler(void) //红外遥控外部中断
) x( h" I* p D5 d! f - {
7 r4 Z4 Y) o' M - u8 Tim=0,Ok=0,Data,Num=0;
) q# B! D- n/ t( S) n5 ~7 B2 z - while(1), b0 C5 b. a w' J, Z! O7 w
- {$ I3 H w4 l& {
- if(GPIO_ReadInputDataBit(GPIOG,GPIO_Pin_15)==1)
% m2 Y: X1 C5 ? - {
2 C! w3 M$ V( D# u# `' q - Tim=HW_jssj();//获得此次高电平时间
5 F+ w" x1 C8 |' J7 A, ]' ] Q - if(Tim>=250) break;//不是有用的信号' O- b+ R8 H' F
- if(Tim>=200 && Tim<250)% C, m5 K0 p/ P4 a
- {* j7 B% B: c% O/ e: O5 ^* Y+ P
- Ok=1;//收到起始信号; f- n3 N! X6 i7 Q0 h9 P
- }8 O* T* U: {) o" z4 G/ j: c
- else if(Tim>=60 && Tim<90)# Q# ?! Z' m0 o0 T( l7 e; B
- {
. G# o. z- q3 q# B3 @* k - Data=1;//收到数据 10 g1 E1 L! |, i# Z$ B G8 \
- }& T% C6 p" s! Q. `$ g
- else if(Tim>=10 && Tim<50)1 i7 N" `7 \& E0 n
- {, i! D4 v) d- T2 m
- Data=0;//收到数据 0: u! M: {9 F V
- }' K1 Z/ ^* n3 P4 x7 Z
- if(Ok==1)! l9 x) v2 G6 l2 q+ J) ?9 ]
- {4 y# l9 R, _ Q
- hw_jsm<<=1;
7 j, i' X7 o8 O9 }& z' Y# ~ - hw_jsm+=Data;
7 u% e3 `( i- j# O4 H - if(Num>=32)
$ S6 O9 m5 Q: A; E) T5 D& h - {
6 D6 O9 G7 K: A7 ` z - hw_jsbz=1;
; D( w1 t9 o, S' v, g - break; D- x3 n, r+ i( n- d+ H& _
- }4 T9 P; X2 e g% {/ `+ d; Y( M
- }
+ m9 e( n1 ] s& j. `) l6 H5 Q9 y - Num++;
$ C$ p6 g2 c( b4 x, X1 F - }- G1 U; R4 d& _" g5 W
- }
( P A8 O7 i' P( U' [, i - EXTI_ClearITPendingBit(EXTI_Line15);+ C3 f# V: }; E+ o6 @$ l* W
- }
复制代码
% s9 h$ a6 q0 _6 ~, z2 T
中断函数内调用了 HW_jssj函数获取高电平时间,此函数代码如下: - /****************************************************************
s- T T, t5 M - * 函 数 名 : HW_jssj
& i) x' Z. |' t0 _, m4 i - * 函数功能 : 高电平持续时间,将记录的时间保存在t中返回,其中一次大约20us
" h: R1 _7 g* G) a. @- | - * 输 入 : 无" B) V# r4 Q c- S9 y7 Y
- * 输 出 : t
- J% j" p( I7 E' ? - *****************************************************************/7 i0 ^+ y- c: N. {% T
- u8 HW_jssj()
# W/ K0 ~, ~7 Z+ c! m% w& S - {3 [. P; P( z F1 i# S) E6 h I9 F
- u8 t=0;. X6 u$ }- F' R: R. v9 }
- while(GPIO_ReadInputDataBit(GPIOG,GPIO_Pin_15)==1)//高电平. {1 x& F4 V5 ]1 l4 g# Q! G
- {
; d7 v3 n( d4 @) V& w# v - t++;
9 O! ~) ^ j6 M; l- x7 d - delay_us(20);1 U5 @. U% \, w: G2 }
- if(t>=250) return t;//超时溢出
6 _# P' O, s" C/ o/ P2 g - }
7 q& i+ e/ a4 A. e - return t;
1 h* F3 j9 ?; `; E$ ? - }
复制代码
- g% G6 ~6 O5 N
通过此函数返回值就可以计算高电平时间,t 累加一次积累的时间为20us。中断函数内,将此函数返回值保存在变量 Tim 中,通过 Tim 值就可以判断是否进入引导码,如果是引导码,就可以接着判断接收的数据是1 还是 0,然后将数据进行按低位在前高位在后顺序保存在 hw_jsm 内,当接收完 32 位数据后将hw_jsbz 标志位置 1,并退出解码函数。这里要注意,判断是否为引导码、数据1 和 0时,我们不要硬性的固定在高电平区分时间上,而应该给它一个在固定值旁的范围判断,以防止因为干扰而导致误操作。引导码及数据 0 和 1 的高电平区分时间在前面介绍 NEC 码已经讲解了,不清楚的可以回过头看下。
; M# V( v/ t) h
主函数( B/ ?, W; M# Q* \/ Y8 O
编写好外部中断初始化和红外解码函数后,接下来就可以编写主函数了,代码如下: - /****************************************************************
, ^1 j1 s$ T! L% e7 ]! E2 ?$ m - * 函 数 名 : main- @- f1 [5 {, @
- * 函数功能 : 主函数1 r! P: g% z( o+ F9 \
- * 输 入 : 无: t1 A+ p s X& Z8 a
- * 输 出 : 无: J$ V% T" V; p& u6 r
- *****************************************************************/
+ X; b- T- f! J# Y9 y5 r4 w4 F: C - int main()
) h4 r7 ^6 b/ }6 d' M& m4 P - {2 P5 R3 e9 v* J: W5 _: M
- u8 i=0;
$ S% m1 Y N5 u/ Y4 O) L8 b7 Z - SysTick_Init(72);5 ]/ J7 P0 [! \& i% M9 @/ K: M& ?
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组 分2 组5 n1 B* L6 x- m1 V# l
- LED_Init();
) V; y% i: l, j- Z* c) n6 p* ? - USART1_Init(9600);7 O/ G9 j6 C9 a; z v7 M
- Hwjs_Init();! {) {' ~. ^% n1 q9 j
- while(1)7 x0 r) w: V$ G
- {
: z& c& E9 v6 I/ P0 ?, G - if(hw_jsbz==1) //如果红外接收到
9 u' Z8 K& W- y5 [7 s - { f+ b4 v' o- T6 b( z
- hw_jsbz=0; //清零& v2 i9 y4 I8 {( b0 |, V S
- printf("红外接收码 %0.8X\r\n",hw_jsm); //打印
! _( B2 j4 b6 W* o2 W - hw_jsm=0; //接收码清零9 R1 g9 ]4 T' D/ g9 Z
- }1 z% a# ^! p+ b9 z6 V
- i++;
! T0 D" Y# y! R# `2 T4 X: K - if(i%20==0)
! q* n, o- E4 F- W) Y - {
: _, r: p8 g4 T/ b3 W. n - led1=!led1;- S. W. g- W9 @, Y+ g
- }! V Y& n; t+ l
- delay_ms(10);$ p% X# _# _) T" }
- }7 d* e2 I' c* O e
- }
复制代码
9 }* L( P9 @1 _2 ~8 ?6 K" M
主函数实现的功能很简单,首先调用之前编写好的硬件初始化函数,包括 SysTick系统时钟, 中断分组, LED初始化等。然后调用我们前面编写的Hwjs_Init函数初始化 PG15 外部中断功能,最后进入 while 循环,通过 hw_jsbz 标志判断是否解码成功,如果成功将解码后的数据 hw_jsm 打印输出,同时控制 D1 指示灯闪烁,提示系统正常运行。 . A1 i( _# A3 x6 d8 g( w* T
实验现象
8 ~- A( ^8 d: z) G# }+ I 将工程程序编译后下载到开发板内,可以看到 D1 指示灯不断闪烁,表示程序正常运行。当按下红外遥控器键时,串口将打印输出解码后的数据(地址码+地址反码+控制码+控制反码)。如果想在串口调试助手上看到输出信息,可以打开 “串口调试助手” , 首先勾选下标号1 DTR框,然后再取消勾选。这是因为此串口助手启动时会把系统复位住,通过 DTR状态切换下即可。然后设置好波特率等参数后,串口助手上即会收到串口发送过来的信息。(串口助手上先勾选下标号 1 DTR 框,然后再取消勾选)如下图所示: ' B9 ^5 m7 P5 H1 n3 Y5 a
我们配的遥控器所有键的地址码与反码都是相同的,不同的是控制码和控制反码,其实知道了控制码就知道了控制反码,所以如果要使用红外遥控控制其他设备,可以通过区分控制码来实现。
* q1 W0 x. q/ A& O w |