目录

前面已经介绍了如何在Keil5和PlatformIO环境下使用FwLib_STC8, 展示了ADC数模转换的例子. 这篇整体介绍一下这个封装库, 以及使用这个封装库进行开发的注意事项.

为什么要写 FwLib_STC8

如果直接用寄存器开发, 在不同的MCU之间切换就会感觉到每次写都像是第一次写, 都得去查手册去计算, 还容易出错, 费时费力. 把一些先验知识代码化, 就能简化这个过程, 用一次的时间节省将来无数时间.

写这个封装库的初衷是希望知识和经验能复用, 避免每次在做STC8G和STC8H的开发时去查手册, 这个是最主要的动机; 其次是要在复用的情况下还能使程序接近直接操作寄存器的效率, 不能因为引入封装库造成明显的资源开销.

在 STC89/STC90 这一代, 几十个SFR还是可以记忆的. 到了STC11, STC12, 开始出现ADC, SPI这些外设, 也还可以接受. 到STC15之后, SFR数量一下子上来, 单单PWM就有十几个SFR, 单凭记忆就很难记住这些东西了. 并且在STC15之后, 同系列之间差异增加, 每个MCU的运行时钟都可能不一样, 从6MHz到40MHz可以自由设定, 就连基础的定时器和串口设置都带来了很大的难度.

STC-ISP工具中提供了一些代码模板, 但是这些代码并非完全可用, 灵活性也不够, 例如延时方法都是不带参数的.

早期的尝试

逻辑代码化

在MCS51这个场景是比较尴尬的: 片内资源太少了.

如果你把各种初始化和计算的工作都放到代码里, 那么就会占用运行资源, 导致固件体积增大, 运行时耗费的内存增加, 一些稍微复杂一点的逻辑就没法跑了. 就像在 HML_FwLib_STC12 这个项目里的尝试一样, 很好用, 但是也很占资源, 一不小心就超出内存限制. 以至于后来将串口1初始化单独写了个直接写寄存器的方法.

HML_FwLib_STC12 这个项目还存在一个问题, 就是SFR变量名与STC官方的命名不一致. 如果仅仅是在Linux下开发, 自成一体, 这个问题不是很重要, 但是如果要使用网络上其他人的代码, 这些代码大都是在Keil C51下开发的, 就不能直接用 HML_FwLib_STC12 运行了, 因为有很多命名要改, 否则编译都通不过.

使用python工具生成代码

所以对于STC8, 最初从另一个方向做了尝试, 就是 stcmx 这个项目.

stcmx 这个项目是用python写的, 在命令行中以交互的形式对各个外设进行选项设置, 然后直接生成C代码.

生成的代码非常简洁, 都是对寄存器的直接赋值, 一步到位直接完成初始化. 风格是这样的

void clock_init()
{
// [ BAH,0,0x00]: 外设端口切换控制寄存器2,串口2/3/4,I2C,比较器
P_SW2 = 0x80;
// [FE01H,1,0x00]: 时钟分频寄存器,ISP可能写入预设值
CLKDIV = 0x00;
// [ 9FH,0,0x00]: IRC频率调整寄存器, ISP可能写入预设值, 0x75:24MHz
IRTRIM = 0x75;
// [ 9EH,0,0x00]: IRC频率微调寄存器, ISP可能写入预设值
LIRTRIM = 0x00;
// [ BAH,0,0x00]: 外设端口切换控制寄存器2,串口2/3/4,I2C,比较器
P_SW2 = 0x00;
} void timer_init()
{
// [ D6H,0,0x00]: 定时器2高字节
T2H = 0xFF;
// [ D7H,0,0x00]: 定时器2低字节
T2L = 0xCB;
// [ 87H,0,0x30]: 电源控制寄存器
PCON = 0xB0;
// [ 8EH,0,0x01]: 辅助寄存器
AUXR = 0x15;
} void uart_init()
{
// [ 98H,0,0x00]: 串口1控制寄存器
SCON = 0x50;
// [ 87H,0,0x30]: 电源控制寄存器
PCON = 0xB0;
// [ 8EH,0,0x01]: 辅助寄存器
AUXR = 0x15;
}

这种方式极其节省资源, 也解决了知识复用的问题, 比如我要在36.864MHz下用timer2开启uart1, 波特率为115200, 只需要设置选项, 输入这些数字, 直接就能得到寄存器的初始化代码.

但是这种形式的缺点是工具本身的开发极其繁琐, 等于要在python里面把MCU的每个寄存器每个bit的逻辑都结构化了, 还得配上文字说明, 可以认为和STM32CubeMx做的事情是类似的.

还有一个更大的缺点是不灵活, 在已经生成代码之后, 如果需要对某些项做调整, 那么要么重新生成一遍, 要么继续查手册.

在写了一段时间后, 投入太大, 逐渐放弃了这个方向.

使用宏的方式将逻辑代码化

这就是 FwLib_STC8 这个项目的尝试, 兼顾了灵活性和节约资源. 我从来都不喜欢宏语句, 但是在这个场景, 确实宏语句有独特的好处.

在 FwLib_STC8 中, 90%的寄存器操作都是用宏语句实现的.

宏语句提供了一种类似于寄存器的文字注释的功能, 在开发时的体验类似于方法调用, 因为像VSCode这样的IDE, 会代码提示并且自动补全.

在编译阶段, 宏语句就会被翻译成直接的寄存器操作, 中间节约了方法调用的堆栈. 没有用到的宏语句不会出现在编译结果里, 不占任何资源. 而如果你写了函数, 函数不管调用没调用, 只要同一个C文件的函数被调用了, 这个C文件里的所有函数都会一并出现在编译结果里. 这样带来的编译结果尺寸差异是很明显的.

唯一比直接使用寄存器赋值更占用资源的地方, 是对SFR的直接赋值操作可能会根据配置项的不同被拆成好几步, 但是这点overheat是值得的, 因为这样才能实现不查手册直接用封装库写代码, 调用的每一步知道自己在做什么.

现在的代码就变成了这样的风格

SYS_SetClock();
// UART1, baud 115200, baud source Timer2, 1T mode, interrupt on
UART1_Config8bitUart(UART1_BaudSource_Timer2, HAL_State_ON, 115200);
UART1_SetRxState(HAL_State_ON);
// Enable UART1 interrupt
EXTI_Global_SetIntState(HAL_State_ON);
EXTI_UART1_SetIntState(HAL_State_ON);

使用 FwLib_STC8 开发的注意事项

通过前面的两篇介绍, 能大致了解这个封装库在两个主流开发环境里的使用.

使用Keil C51的用户应该会相对简单, 因为直接将封装库加入项目就可以, 另外频率可以直接用STC-ISP设置, 省掉了维护一套频率参数的烦恼. 而在Linux下的用户, 就需要维护一套编译参数, 用于在程序中指定MCU频率, 如果使用PlatformIO开发, 封装库已经通过library.json做了适配, 只要放入项目lib目录, 就会自动识别并添加到include路径.

在demo目录下有丰富的演示示例, 基本上覆盖了全部片内外设. 另外还有对常见元件, 例如喜闻乐见的MAX7219 8x8点阵, NRF24L01无线模块, SSD1306 OLED屏, ST7735 LCD这些设备的驱动.

翻阅一下演示代码, 能基本了解这个封装库的调用方法.

下面说需要注意的几点

1. 不能随便在传参里使用表达式

类似于i++, var--这样的都是表达式.

这是宏调用的固有缺陷, 因为宏毕竟不是函数, 它只是字符串模板, 在使用++, --这类操作符时, 会将这个操作放到模板里展开, 如果在模板里对这个变量引用了两次, 那么它就会执行两次, 这会造成意想不到的问题.

2. 如果要同时对Keil C51和SDCC兼容, 就必须使用封装库提供的宏

封装库中引入了一些宏定义, 用于保证对 Keil C51 和 SDCC 的兼容性. 命名和形式来源于 sdcc compiler.h.

如果你希望代码在 Keil C51 和 SDCC 下都能编译, 在编码时就应当使用这些宏, 而不是编译器对应的关键词.

以下是相关的宏定义列表

Macro Keil C51 SDCC
__BIT bit __bit
__IDATA idata __idata
__PDATA pdata __pdata
__XDATA xdata __xdata
__CODE code __code
SBIT(name, addr, bit) sbit name = addr^bit __sbit __at(addr+bit) name
SFR(name, addr) sfr name = addr __sfr __at(addr) name
SFRX(addr) (*(unsigned char volatile xdata *)(addr)) (*(unsigned char volatile __xdata *)(addr))
SFR16X(addr) (*(unsigned int volatile xdata *)(addr)) (*(unsigned int volatile __xdata *)(addr))
INTERRUPT(name, vector) void name (void) interrupt vector void name (void) __interrupt (vector)
INTERRUPT_USING(name, vector, regnum) void name (void) interrupt vector using regnum void name (void) __interrupt (vector) __using (regnum)
NOP() _nop_() __asm NOP __endasm

这些宏定义可以在 include/fw_reg_base.h 中查看

3. 部分宏语句的参数是枚举, 调用时要留意

使用宏语句的一个缺点就是没有类型提示, 虽然在变量名上我已经尽量体现出这个参数的类型, 但是写代码时, IDE是没有提示的. 所以这里需要注意的是, 有一些输入参数是枚举, 在调用时最好切换到声明这个宏的.h文件中看一眼, 这些枚举一般都定义在.h文件的开始部分.

4. 不同MCU之间的资源差异

封装库本身只区分了STC8G和STC8H两个大类, 例如STC8G 有 PCA但是没有PWM, STC8H 中有PWM没有PCA. 在大类的内部, 例如 STC8H 的各个子系列, 在功能上也是有差异的, 例如 STC8H1K 系列的ADC是 10bit, STC8H3K, STC8H8K 的ADC是12bit, 还有通道的数量以及和IO口的映射关系都有区别.

这些区别基本上都列在了对应外设的.h文件中, 在开发时可以多看一眼, 避免不必要的时间浪费.

结束

以上就是对 FwLib_STC8 封装库的开发说明. 希望这个封装库能加快STC8G,STC8H的开发速度, 节省更多人的时间.

STC8H开发(四): FwLib_STC8 封装库的介绍和注意事项的更多相关文章

  1. STC8H开发(二): 在Linux VSCode中配置和使用FwLib_STC8封装库(图文详解)

    目录 STC8H开发(一): 在Keil5中配置和使用FwLib_STC8封装库(图文详解) STC8H开发(二): 在Linux VSCode中配置和使用FwLib_STC8封装库(图文详解) 前面 ...

  2. STC8H开发(一): 在Keil5中配置和使用FwLib_STC8封装库(图文详解)

    介绍 FwLib_STC8 是一个针对STC8G, STC8H系列MCU的C语言封装库, 适用于基于这些MCU的快速原型验证. 项目地址: Gitee FwLib_STC8 镜像地址: GitHub ...

  3. 响应式开发(四)-----Bootstrap CSS----------Bootstrap CSS概览和相关注意事项

    本章先记录一些与Bootstrap CSS相关的一些特点和注意事项以及兼容性. HTML 5 文档类型(Doctype) Bootstrap 使用了一些 HTML5 元素和 CSS 属性.为了让这些正 ...

  4. STC8H开发(十四): I2C驱动RX8025T高精度实时时钟芯片

    目录 STC8H开发(一): 在Keil5中配置和使用FwLib_STC8封装库(图文详解) STC8H开发(二): 在Linux VSCode中配置和使用FwLib_STC8封装库(图文详解) ST ...

  5. STC8H开发(六): SPI驱动ADXL345三轴加速度检测模块

    目录 STC8H开发(一): 在Keil5中配置和使用FwLib_STC8封装库(图文详解) STC8H开发(二): 在Linux VSCode中配置和使用FwLib_STC8封装库(图文详解) ST ...

  6. STC8H开发(五): SPI驱动nRF24L01无线模块

    目录 STC8H开发(一): 在Keil5中配置和使用FwLib_STC8封装库(图文详解) STC8H开发(二): 在Linux VSCode中配置和使用FwLib_STC8封装库(图文详解) ST ...

  7. STC8H开发(七): I2C驱动MPU6050三轴加速度+三轴角速度检测模块

    目录 STC8H开发(一): 在Keil5中配置和使用FwLib_STC8封装库(图文详解) STC8H开发(二): 在Linux VSCode中配置和使用FwLib_STC8封装库(图文详解) ST ...

  8. STC8H开发(八): NRF24L01无线传输音频(对讲机原型)

    目录 STC8H开发(一): 在Keil5中配置和使用FwLib_STC8封装库(图文详解) STC8H开发(二): 在Linux VSCode中配置和使用FwLib_STC8封装库(图文详解) ST ...

  9. STC8H开发(九): STC8H8K64U模拟USB HID外设

    目录 STC8H开发(一): 在Keil5中配置和使用FwLib_STC8封装库(图文详解) STC8H开发(二): 在Linux VSCode中配置和使用FwLib_STC8封装库(图文详解) ST ...

随机推荐

  1. 带你尝鲜LiteOS 组件EasyFlash

    摘要:EasyFlash是一个开源的轻量级嵌入式闪存库. 本文分享自华为云社区<LiteOS组件尝鲜-玩转EasyFlash>,作者:Lionlace . 基本介绍 EasyFlash是一 ...

  2. CF135A Replacement 题解

    Content 有 \(n\) 个数 \(a_1,a_2,a_3,...,a_n\),试用 \(1\) ~ \(10^9\) 之间的数(除了本身)代替其中的一个数,使得这 \(n\) 个数的总和最小, ...

  3. AT4151 [ABC099B] Stone Monument 题解

    Content 一个村里有 \(999\) 个房子,第 \(i\) 个房子的高度为 \(1+2+...+i=\sum\limits_{j=1}^ij\).现在下了一场雪,给定相邻两个房子没被雪覆盖的高 ...

  4. 磁盘分区丢失testdisk恢复

    故障修复步骤: 1. 检查磁盘分区级文件系统确实不在: 2. 云主机内部下载testdisk工具修复 yum install testdisk -y 3. 执行命令testdisk /dev/vdc进 ...

  5. FastAPI(六十五)实战开发《在线课程学习系统》基础架构的搭建

    在之前三篇,我们分享的就是需求的分析,基本接口的整理,数据库链接的配置.这次我们分享项目的基本框架,目录结构如下: common目录 通用的目录,一些通用的处理放在这里 models目录 数据库相关的 ...

  6. 网络编程之新函数inet_pton和inet_ntop

    1.头文件 1 #include <arpe/inet.h> 2.inet_pton 函数 A.原型 1 int inet_pton(int family, const char *str ...

  7. java源码——文件读写和单词统计

    本文要解决的问题:"键盘输入一段英语语句,将这段话写入content.txt中,然后输出这段话,并且统计语句中英文单词的数目以及各个单词出现的次数." 分析问题知,核心是文件读写和 ...

  8. 【LeetCode】979. Distribute Coins in Binary Tree 解题报告(C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 递归 日期 题目地址:https://leetcod ...

  9. 【LeetCode】689. Maximum Sum of 3 Non-Overlapping Subarrays 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 题目地址: https://leetcode.com/problems/maximum- ...

  10. 【LeetCode】842. Split Array into Fibonacci Sequence 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 日期 题目地址:https://leetcode.c ...