潘多拉板子个人笔记(一)——点亮一盏LED灯

发表在 内核学习营2019-5-8 01:18 [复制链接] 4 790

本帖最后由 家定不举棋 于 2019-5-8 01:19 编辑
4 W, c' b+ l: l+ k% O$ y4 c# @5 b2 v, a. n7 M6 o; @; r: _; o4 D0 `! H
买了好久的潘多拉板子,一直积灰,有点浪费,收拾了一下学习学习,然后将心得记在这里,也想各位指点。
( Y5 Q; k* _' U: m6 G

点亮一颗LED

/ h3 c; L1 X2 f" L& l' b

主函数流程

- Z& a$ B7 n. u

首先将 PE7 设置为输出模式,然后进入循环模式:向引脚写入低电平信号并打印 led on 和 count 的值,等待500ms,向引脚写入高电平信号并打印 led off,等待500ms,将 count 加一。

! x; Q. e- x, ^5 f' K9 J

所使用的RT-Thread资源

) q: H. I$ g9 R7 P2 {% l

整个程序使用到了 RT-Thread 中的 PIN设备、内核线程,在程序中共调用了三个头文件,其作用如下:

& h7 F  H# q8 S0 c$ ~- q  I+ v$ z. z5 M/ h  s  Z, \: Z% _  v( d( h/ V% J2 @3 j, _, Z8 y9 m' z6 e  A' h. M+ K6 A' {) H4 T2 p: e& a  h& @6 o$ X4 C) w4 F" S0 @6 y! W$ m2 d3 U. Q! J8 u8 x0 O1 @& v  Q6 r0 S. R7 U' {2 _% g0 r; t  R6 {  H2 a3 \* C+ S/ L. R7 I7 A9 \$ G/ I7 }7 k& e/ k% a, W7 u) ^* P8 [; e- g! W! }( b/ j# s! t/ Q1 M( r5 A+ I) w! f% J; m% g* O4 ^) l! m* ?; Q* A; b/ }6 e% O5 f) Z% A( Z( T, J1 x" m7 a* m8 O4 c8 J: Z& I- h2 r) z: _! B. k5 H5 c* z8 K8 K8 a4 Y8 q% j+ ~% k6 b; u, Q- v2 r' f! _
头文件 作用
rtthread.h rtthread内核头文件,内核 API 的申明
rtdevice.h rtthread设备驱动框架的整合
board.h 针对stm32l4xx的头文件
2 `5 S* L/ O- ^3 x

整个程序使用到了如下 API:

1 G7 J$ ~  P  h# k% X- P; L% E + y' n% ]- s- ?" U8 T9 C$ X4 d- J1 j' m1 f2 Y6 m8 e8 C7 U1 c8 Q" [% {  Y9 {$ f% q+ W4 ]% u9 z/ j$ b- |9 S* Q% ^0 |! j+ ?# t$ o' b) |  B2 b) X4 _+ J, t, w. Q- h# F/ _9 k; Z1 r8 [, O  c0 V7 r  ^- P; S; [# a. M( y. ?( I  g1 v4 `# I  ?  C! U  }, R! l9 R' b& @% I- `) V8 I; |3 N. Q( q+ K5 ~, _3 Y* l% \' C- K) e1 P  Q3 W! M6 O! {$ n: O5 o; m9 N; r# c: ~. ]' }! E; ^8 l. s& W5 ^2 v: d; H+ ?3 {" c% w; V$ ~* ^' M: M- G7 D4 O* k' L& e& I6 F" i0 I0 e3 [9 p9 L' f' w2 s! V5 C$ h# r2 C* e3 P* T" {; i6 B5 B( S" C* d0 c/ N! F& v# r: G: i' Q# \( Z( ^& O7 s, m. K8 H& L: L: {1 ?" ?% K- j- `7 \  f, K0 y4 ^" B1 v. D2 ~4 V" C2 }0 S; @7 q( J. T( R8 q' `! W2 t& _. E2 Y. i+ x1 A/ r1 L& M4 V
名称 定义文件 作用
rt_pin_mode pin.c 定义引脚的工作模式
rt_pin_write pin.c 向引脚写信号
rt_kprintf kseervicce.c 向串口打印数据
rt_thread_mdelay thread.c 延时函数,并将线程挂起
& _: ?: @  ~" H4 k- i0 Y

rt-thread提供了完善的设备框架,其真正的运行原理还需要对其进行分析。

) N& u# M9 a9 j9 C

rt_pin_mode

- ~5 b/ l8 p' j2 t) g" G3 M

线程执行的第一步便是对引脚的工作模式定义,其中 rt_pin_mode 的源码如下:

- L2 N3 H: H* S( C0 o) }' M" N
    void rt_pin_mode(rt_base_t pin, rt_base_t mode)
7 ~, S$ N% _4 q+ t; a% {) Q: z    {
' ]# p1 z, u" U! [; n7 k1 a8 X        RT_ASSERT(_hw_pin.ops != RT_NULL);
9 y% `1 s& D% M, r2 g, q        _hw_pin.ops->pin_mode(&_hw_pin.parent, pin, mode);
' E" ]2 \, L8 t5 e- z" x    }
! ~9 j! ^% D1 v& O. V% F. C

代码简单,但是内部原理不简单。其主要原理便是对 _hw_pin.ops 进行断言判断是否为空,若不为空便对其对象进行设置引脚和模式 。那么有以下问题:

& ^; `: N/ R3 l- o; A
    2 ^& I. _" Q2 e
  1. _hw_pin 是什么;
  2. , @$ `, N9 g5 m6 ]- C  ]5 n' m5 t
  3. 为什么需要判断 _hw_pin.ops 里的值是否为空,他里面有什么;
  4. & M- t! U/ ?$ H( T% a. q
  5. _hw_pin.ops 中的 pin_mode 具体实质操作是什么;
  6.   n' J* s4 ?$ a/ M: K0 C! r. R
& Z( x0 l  G6 a! `% `" l5 m2 k# C1 U

为了解决第一个问题,查看了 _hw_pin 的定义,即为 static struct rt_device_pin _hw_pin;,那么可以知道 _hw_pin 为结构体 rt_device_pin,这里可以回答第一个问题,其中  rt_device_pin 结构体如下:

% L% l6 A0 r" P8 e
    struct rt_device_pin; D2 y' z$ s7 \
    {
( R+ |! n# u: h        struct rt_device parent;
, D. @$ h8 p. N  m* e        const struct rt_pin_ops *ops;7 I0 I8 w* E+ o* C; M
    };
0 T1 j* u0 R( T1 Y; i/ {  Z

其内部便是设备的内核对象和对引脚的操作接口,但是对于 _hw_pin 里具体内容还是一概不知,但是可以确定如果  _hw_pin.ops 内部为空则程序无法执行,这也可以回答第二个问题的前一半问题。

  \0 @: K7 G' S8 I7 n

通过查找可以知道在 pin.c 文件中提供了 rt_device_pin_register 来对_hw_pin 进行初始化。在这里可以看到它的内核类型为 RT_Device_Class_Miscellaneous (该值在 rt_device_class_type 中定义),设置了常用设备接口中的读、写和控制为 _pin_read_pin_write_pin_control。抱着好奇的心思去看看这三个真正的操作是怎么实现的:

0 M8 F8 b3 m7 O. |
    5 B( t2 V& Q" D& K0 T5 S" d( `
  • ( o8 a3 x5 M$ k- ~) D' d$ l

    _pin_read:先把 pin 进行断言判断不为 RT_NULL,确定 status 不为空和 sizestatus 的大小相同,执行 pin->ops->pin_read,返回 size大小。

    ' l: b0 ^1 r- @) Q6 C5 L9 r1 X
  • + r, L2 A0 [/ x) S: ^$ G& f  y
  • , Q  E4 D( J( Y+ X# b6 \

    _pin_write:先判断 pin 进行断言判断不为 RT_NULL,确定 status 不为空和 sizestatus 的大小相同,执行 pin->ops->pin_write,返回 size 大小。

    0 z1 q+ D7 g8 T9 S8 _. y) y
  • 0 Y" W* E" t5 ?7 k, I6 z  c8 C
  • _pin_control:先判断 pin 进行断言判断不为 RT_NULL,确定 mode 不为 RT_NULL,执行 pin->ops->pin_mode,返回0。
  • ) s" {, |- \% \' H/ Y6 z
$ c% G" `" p# w( A7 r

由上述可知还需知道 ops 中的操作,但是 ops 为输入形参,那么产生新的问题:

% E7 @, F2 p! |0 Q
    6 }2 ]' [$ R& k5 s4 B1 h8 r
  1. rt_device_pin_register 在程序中实质在哪里执行;
  2. / P, C) |, g1 H% j) d- b
  3. rt_device_pin_register 在执行的时候赋值进去的参数是什么;
  4. 6 K* J3 h+ D; d# ]
1 |5 T' u3 B: X

带着上述问题重新查看程序,可以看到 drv_gpio.c 文件中的如下代码:

/ a' |2 [+ N/ g5 U
    int rt_hw_pin_init(void)
' [+ _2 r4 z, L    {0 Q4 q& S8 }  `: i5 Q# S" y1 F; F
        return rt_device_pin_register("pin", &_stm32_pin_ops, RT_NULL);* r+ ^8 E- a  {9 q$ k2 \! [1 `
    }
3 D  I: W, H" d1 l" f: Z, p

然后可以知道在 board.c 文件中的 rt_hw_board_init()中调用了 rt_hw_pin_init() 函数,且必须定义 BSP_USING_GPIO,该定义在 rtconfig.h 文件中(rtthread的裁剪工作本质也是在该文件中对定义进行修改, menuconfig 工具中的配置也是对该文件的一种操作,其中这也是C语言中的一种小技巧,在附录中进行详细说明),而 rt_hw_board_init() 在 rtthread 启动函数 rtthread_startup 中便得到了执行。

  N- u& C( o2 D; j" n# d( j1 _

而在这之中可以看到定义在 drv_gpio.c 文件中的 _stm32_pin_ops 的如下:

% k) E4 G5 H4 v) d7 j
    const static struct rt_pin_ops _stm32_pin_ops =
" R* A: O$ Q0 A# H5 T    {
2 c( w: ^" W4 C+ N+ y) C        stm32_pin_mode,
8 Z, n, q' y" L  }$ ~$ z        stm32_pin_write,
5 q8 h/ O3 y: Y/ j# k$ c4 i        stm32_pin_read,
: A" T* ^2 S; N        stm32_pin_attach_irq,
* m/ [. O. D# W7 w$ V/ \6 a, f        stm32_pin_dettach_irq," D5 Z8 W+ L0 e( I1 k5 C; d
        stm32_pin_irq_enable,
, b7 }# K) v) e% g( D- y6 h6 q' ~    };
- `/ u9 C1 p8 }2 \3 p$ g

上述API也是 _hw_pin.ops 的本质。

+ V9 t% c: ^5 d- n2 i3 U" o. n

附录

. a; q9 B/ ~6 r+ S% X; N

对于代码的注释最常用的为 ”//“  和 ”/... /“,在 RTThread 中使用了如下注释方法:

- d* d' F4 J6 v8 c- @7 n0 F2 ?7 ^
    #ifdef define_array9 i: _6 t) @- B- z. P; E6 _) f
    func()
* z& b4 v$ P- H    #endif
0 k% @( _$ k7 y' a

如果 define_array 变量在之前定义了那么就执行 func() 程序,如果未定义则不执行,即相当于:

3 I" I2 Y1 S/ y& S: O+ O: }. U' o
    #ifdef 0
* f2 j6 j) H& B$ u+ F( w( {; C    func()
% w1 @- O6 _0 t* r2 i    #endif
* d5 g6 c9 `* t" P

而上述方法也是一种很好的注释方法,因为 ”//“ 的注释会显得凌乱,而 ”/... /“ 不支持嵌套。

: y9 @! Z* E! O  K0 d
使用道具 举报 显示全部楼层 回复
最新评论 | 正序浏览
显示全部楼层 |楼层直达:
发表于 2019-6-11 10:32:02 | 显示全部楼层
主函数流程建议用程序流程图去表达,这样会不会让人更加容易理解些?
使用道具 举报 回复
发表于 2019-6-15 10:19:02 | 显示全部楼层
这里"#ifdef 0"是怎么回事?
使用道具 举报 回复
发表于 2019-6-15 10:20:04 | 显示全部楼层
  1. error: macro names must be identifiers) S9 i1 G% d( @8 X( D, h7 }
  2. #ifdef 0
复制代码
使用道具 举报 回复
发表于 2019-6-15 10:22:35 | 显示全部楼层
附录上的不叫注释吧,应该叫条件编译
使用道具 举报 回复
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|RT-Thread开发者社区 ( 沪ICP备13014002号-1

有害信息举报电话:021-31165890 手机:18930558079

© 2006-2019 上海睿赛德电子科技有限公司

Powered by RT-Thread

快速回复 返回顶部 返回列表