介绍一个用C语言编写的硬件外设访问库

转自 | 老吴嵌入式
今天要介绍的开源软件叫 c-periphery,一个用 c 语言编写的硬件外设访问库。
我们可以用它来读写 serial、spi、i2c 等,非常适合在嵌入式产品上使用。
我们可以基于它优秀的代码框架,不断地扩展出更多的功能模块,最终形成自己产品适用的 linux 硬件抽象层。
源文件:
$ tree .├── src│   ├── gpio.c│   ├── gpio.h│   ├── i2c.c│   ├── i2c.h│   ├── led.c│   ├── led.h│   ├── mmio.c│   ├── mmio.h│   ├── pwm.c│   ├── pwm.h│   ├── serial.c│   ├── serial.h│   ├── spi.c│   ├── spi.h│   ├── version.c│   └── version.h  
约 4500 行代码,每个硬件模块的代码都是相对独立,上手难度小。
能收获什么?
1、降低硬件编程的门槛;
2、了解 linux 应用层如何访问 gpio / i2c / spi / pwm 等硬件;
3、了解如何对硬件外设进行封装,并提供良好的 api;
4、了解如何将代码封装成库;
5、了解如何为代码编写单元测试程序;
c-periphery 很好地示范了如何在 linux 平台上进行硬件编程,定义出来的接口即丰富又实用。
另外,它最终输出的是静态库 libperiphery.a,并且为每一个硬件模块功能都编写了单元测试代码,代码质量有保障。
c-periphery 的用法
简单例子
我们以最常见的串口读写为例:
int main(void){    serial_t *serial;    uint8_t s[] = hello world!;    uint8_t buf[128];    int ret;    serial = serial_new();    /* open /dev/ttyusb0 with baudrate 115200, and defaults of 8n1, no flow control */    if (serial_open(serial, /dev/ttyusb0, 115200) < 0) {        fprintf(stderr, serial_open(): %s, serial_errmsg(serial));        exit(1);    }    /* write to the serial port */    if (serial_write(serial, s, sizeof(s)) < 0) {        fprintf(stderr, serial_write(): %s, serial_errmsg(serial));        exit(1);    }    /* read up to buf size or 2000ms timeout */    if ((ret = serial_read(serial, buf, sizeof(buf), 2000)) < 0) {        fprintf(stderr, serial_read(): %s, serial_errmsg(serial));        exit(1);    }    printf(read %d bytes: _%s_, ret, buf);    serial_close(serial);    serial_free(serial);    return 0;}  
serial_t 是对串口设备的抽象;
serial_new() 用于创建一个串口设备, 这里只是申请了数据,使用完毕后, 要通过 serial_free() 将其释放掉。
serial_open() 用于初始化串口,设置设备节点、波特率等; 相应地,用 serial_close() 可以关闭串口。
serial_write() 用于给串口发数据,模仿了系统调用 write()。
serial_read() 用于从串口读数据,比系统调用 read() 多了一个 timeout_ms 的参数,有了超时机制后,至少可以避免程序一直阻塞。
这就是一个最简单的基于 c-periphery 的串口示例。即便是嵌入式初学者,基于这些接口,也能轻松地读写串口了。
另外,这里只用到了最常用的几个 api。对于串口模块,c-periphery 还有很多实用的 api:
比较有意思的几个 api:
serial_poll() 类似 select(),用于监控串口是否有数据,避免死等;
serial_get/set_xxx() 用于读写串口的属性;
serial_fd() 用于获取文件描述符,有了 fd 就意味这所有 linux 应用编程的机制都可以使用了。例如我们可以将这个 fd 传递给 libev,然后就能进行事件驱动编程了。
c-periphery 的实现
关键数据
c-periphery 里对每个硬件模块封装的方法都是类似,用一个结构体来保存模块所有相关的信息,看下面这几个例子。
serial:
i2c:
gpio:
它们的成员变量大多都有文件描述符 fd、用于记录错误状态的 errno / error string,然后再加上一些硬件模块特有的成员变量。
最终库的调用者只会看到 serial_t、i2c_t、gpio_t 这种类似描述符的数据类型,使用时不需要关心内部细节。
后续我们要添加自己的硬件模块时,可以依葫芦画瓢,模仿着定义出属于该硬件的 xxx_t 结构体,然后一步步地为 c-periphery 扩展出新的功能模块。
几个关键 api 的实现
我们以 serial 为例,看下其核心 api 的实现。
分配与释放:
就是在申请分配和释放 serial_t 的内存。
写数据 serial_write() 就是调用 write(),读数据 serial_read() 则是利用 select() 实现了超时的功能:
serial_poll() 则是使用 poll() 来完成 io 监控。
其他硬件模块的实现都是类似的。
到此,c-periphery 的核心实现代码就拆解完毕了。
为 c-periphery 添加新的硬件模块
学以致用,我们按照 c-periphery 的框架,添加背光 backlight 功能。
backlight 的控制方法可以参考这篇文章:一个控制背光的命令行小工具。
先定义 backlight_t:
然后再实现好下面这些 api:
api 的具体实现代码就不再这里展示了,因为控制背光无非就是读写 /sys/class/backlight/ 内的文件节点,难度不大。
总结
c-periphery 是一个 c 语言编写的硬件访问库,已支持 serial、i2c、spi、mmio、pwm、gpio 等硬件。约 4500 行代码,每个硬件模块的代码都是相对独立,上手难度小,非常使用在嵌入式 linux 平台上使用。
另外,我们可以基于它优秀的代码框架,不断地扩展出自己需要的功能模块,最终形成自己产品专用的 linux 硬件抽象层,绝对的嵌入式开发的利器。


工业机器人的应用将成为推动工业4.0的重要力量
制品仓智能化管理,引领数字化时代的软件供应链变革
电烙铁焊锡有毒吗?电烙铁焊PCB真的有毒?
欧菲光旗下合肥欧菲智能车联举行开业庆典
单火线应用解决方案
介绍一个用C语言编写的硬件外设访问库
基于51单片机的万年历时钟仿真设计
2020年云计算已开辟了新战场
乐泡淳迅快充移动电源评测 支持双向快充可谓是商务良品
中国智慧城市发展的机遇与挑战
微软CEO纳德拉获2023年度CEO殊荣,AI大模型引领新时代
用于测量测绘的高速连传数传电台模块RD400D
小米确定造车,或将由雷军亲自带队
湖南省政务服务和大数据中心党委书记、主任禹向群一行调研拓维信息
p-NiO插入终端结合MOS结构实现高性能GaN基SBD
国际影响力日益增强 引领性研究能力和产业竞争力尚未显现
家用电磁炉电路原理图
传感器建判断机制 智慧家庭增添安全/节能
六种复杂的机械传动原理动图分享
麒麟990将是第一个采用台积电加强版7纳米+制程的处理器