目录

前面已经介绍了如何在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. centos7部署mysql-5.7

    目录 一.环境声明 二.程序部署 三.更改初始密码 一.环境声明 [mysql-Server] 主机名 = host-1 系统 = centos-7.3 地址 = 1.1.1.1 软件 = mysql ...

  2. pipeline post指令

    目录 一.介绍 二.参数说明 三.使用实例 一.介绍 post步骤包含的是在整个pipeline或阶段完成后一些附加的步骤.post步骤是可选的,所以并不包含在声明式pipeline最简结构中,但这并 ...

  3. QT QApplication干了啥?

    ------------恢复内容开始------------ QCoreApplicationPrivate 会取得current thread; 在windows平台创建TLS变量,记录线程信息,并 ...

  4. 数据脱敏 t-closeness介绍与实现

    数据脱敏 t-closeness介绍与实现 本文主要基于t-closeness的首次提出团队Ninghui Li, Tiancheng Li, Suresh Venkatasubramanian发表的 ...

  5. Table.FirstN保留前面N….First…(Power Query 之 M 语言)

    数据源: "姓名""基数""个人比例""个人缴纳""公司比例""公司缴纳"&qu ...

  6. 一文详解TDSQL PG版Oracle兼容性实践

    TDSQL PG版分布式关系型数据库,是一款同时面向在线事务交易和MPP实时数据分析的高性能HTAP数据库系统.面对应用业务产生的不定性数据爆炸需求,不管是高并发交易还是海量实时数据分析,TDSQL ...

  7. AT5341 [ABC156D] Bouquet 题解

    Content 有一个人有 \(n\) 种不同的话可供选择,TA 可以选择至少一种花做花束,但是 TA 不喜欢花的种数为 \(a\) 或者 \(b\) 的花束.求选花的方案数对 \(10^9+7\) ...

  8. DNS解析超时排查/etc/resolv.conf single-request-reopen参数说明

    添加 options rotate timeout:1 attempts:3 single-request-reopen 添加到/etc/resolv.conf 中 #释义: 循环查询 超时时间 重试 ...

  9. JAVA发送xml格式的接口请求

    /** * * @param urlStr 接口地址 * @param xmlInfo xml格式参数数据 * @return */ public static String sendMsgXml(S ...

  10. JAVA验证手机号码是否正确

    PhoneUtils.java package com.common.util; import java.util.regex.Matcher; import java.util.regex.Patt ...