Linux系统点亮LED
目录
- 应用层操控硬件的两种方式
- sysfs 文件系统
- sysfs 与/sys
- 总结
- 标准接口与非标准接口
- LED 硬件控制方式
- 编写LED 应用程序
- 在开发板上测试
对于一款学习型开发板来说,永远都绕不开LED 这个小小的设备,基本上每块板子都至少会有一颗
LED 小灯,对于我们的ALPHA/Mini I.MX6U 开发板来说同样也是如此。
ALPHA/Mini I.MX6U 开发板(包括核心板和底板)上一共有3 颗LED 小灯,当仅有一颗LED 能够被用户所控制,其它两颗均作为电源指示灯而存在,用户对其不可控制;LED 通常是由GPIO 所控制的,本章我们来学习如何编写应用程序控制LED 灯的亮灭。
应用层操控硬件的两种方式
在Linux 系统下,一切皆文件!应用层如何操控底层硬件,同样也是通过文件I/O 的方式来实现,前面我们给大家介绍了设备文件,包括字符设备文件和块设备文件,为啥叫设备文件?大家有没有想过这个问题呢?其实设备文件便是各种硬件设备向应用层提供的一个接口,应用层通过对设备文件的I/O 操作来操控硬件设备,譬如LCD 显示屏、串口、按键、摄像头等等,所以设备文件其实是与硬件设备相互对应的。设备文件通常在/dev/目录下,我们也把/dev 目录下的文件称为设备节点。
设备节点并不是操控硬件设备的唯一途径,除此之外,我们还可以通过sysfs 文件系统对硬件设备进行操控,接下来将进行介绍!
sysfs 文件系统
简单的说,sysfs 是一个基于内存的文件系统,同devfs、proc 文件系统一样,称为虚拟文件系统;它的作用是将内核信息以文件的方式提供给应用层使用。7.7 小节中我们学习过proc 文件系统,应用层可以通过
proc文件系统得到系统信息和进程相关信息,与proc文件系统类似,sysfs 文件系统的主要功能便是对系统设备进行管理,它可以产生一个包含所有系统硬件层次的视图。
sysfs 文件系统把连接在系统上的设备和总线组织成为一个分级的文件、展示设备驱动模型中各组件的层次关系。sysfs 提供了一种机制,可以显式的描述内核对象、对象属性及对象间关系,用来导出内核对象
(kernel object,譬如一个硬件设备)的数据、属性到用户空间,以文件目录结构的形式为用户空间提供对这些数据、属性的访问支持。表15.1.1 描述了内核对象、对象属性及对象间关系在用户空间sysfs 中的的表现:
sysfs 与/sys
sysfs 文件系统挂载在/sys 目录下,启动ALPHA/Mini I.MX6U 开发板,进入Linux 系统(开发板出厂系统)之后,我们进入到/sys 目录下查看,如下所示:
上图显示的便是sysfs 文件系统中的目录,包括block、bus、class、dev、devices、firmware、fs、kernel、
modules、power 等,每个目录下又有许多文件或子目录,对这些目录的说明如所示:
系统中所有的设备(对象)都会在/sys/devices 体现出来,是sysfs 文件系统中最重要的目录结构;而
/sys/bus、/sys/class、/sys/dev 分别将设备按照挂载的总线类型、功能分类以及设备号的形式将设备组织存放在这些目录中,这些目录下的文件都是链接到了/sys/devices 中。
设备的一些属性、数据通常会通过设备目录下的文件体现出来,也就是说设备的数据、属性会导出到用户空间,以文件形式为用户空间提供对这些数据、属性的访问支持,可以把这些文件称为属性文件;读这些属性文件就表示读取设备的属性信息,相反写属性文件就表示对设备的属性进行设置、以控制设备的状态。
总结
这里给大家进行一个总结,应用层想要对底层硬件进行操控,通常可以通过两种方式:
⚫ /dev/目录下的设备文件(设备节点);
⚫ /sys/目录下设备的属性文件。
具体使用哪种方式需要根据不同功能类型设备进行选择,有些设备只能通过设备节点进行操控,而有些设备只能通过sysfs 方式进行操控;当然跟设备驱动具体的实现方式有关,通常情况下,一般简单地设备会使用sysfs 方式操控,其设备驱动在实现时会将设备的一些属性导出到用户空间sysfs 文件系统,以属性文件的形式为用户空间提供对这些数据、属性的访问支持,譬如LED、GPIO 等。
但对于一些较复杂的设备通常会使用设备节点的方式,譬如LCD 等、触摸屏、摄像头等。
标准接口与非标准接口
Linux 内核中为了尽量降低驱动开发者难度以及接口标准化,就出现了设备驱动框架的概念;Linux 针对各种常见的设备进行分类,譬如LED 类设备、输入类设备、FrameBuffer 类设备、video 类设备、PWM 设备等等,并为每一种类型的设备设计了一套成熟的、标准的、典型的驱动实现的框架,这个就叫做设备驱动框架。设备驱动框架为驱动开发和应用层提供了一套统一的接口规范,譬如对LED 类设备来说,内核提供了LED 设备驱动框架,驱动工程师编写LED 驱动时,使用LED 驱动框架来开发自己的LED 驱动程序,这样做的好处就在于,能够对上层应用层提供统一、标准化的接口、同时又降低了驱动开发工程师的难度。
编写LED 驱动程序并不仅仅只能使用内核设计的LED 设备驱动框架,不用内核的LED 驱动框架也是可以开发出LED 驱动程序的,但如果你这样写,使用这个驱动程序注册的LED 那就不是标准设备了,因为该驱动程序向应用层提供的接口并不是统一、标准化接口。
除此之外,还有很多硬件外设,尤其是嵌入式系统中所使用到的这些硬件外设,它们可能并不属于Linux
系统所规划的设备分类当中的任何一种设备类型,譬如在Linux 系统中,有一种设备类型叫杂散/杂项类设备(misc device),大家可以想一想为啥叫杂散类设备,说明这种设备既不属于这种设备类型、又不属于另一种设备类型,无奈只能把它归为杂项类。
因为一个计算机系统所能够连接、使用的外设实在太多了,不可能每一种外设都能够精准地分类到某一个设备类型中,通常把这些无法进行分类的外设就称为杂项设备,杂项设备驱动程序向应用层提供的接口通常都不是标准化接口、它是一种非标准接口,具体如何去操控这个设备通常只有驱动工程师知道。所以在嵌入式系统中,很多硬件外设的驱动程序都是定制的。
LED 硬件控制方式
ALPHA/Mini I.MX6U 开发板底板上有一颗可被用户控制的LED 灯,如下所示:
上图中箭头所指的LED 便是开发板上唯一一个可以被用户所控制的LED,另外一颗LED 则(名称为
PWR)是底板上的电源指示灯。
对于ALPHA/Mini I.MX6U 开发板出厂系统来说,此LED 设备使用的是Linux 内核标准LED 驱动框架注册而成,在/dev 目录下并没有其对应的设备节点,其实现使用sysfs 方式控制。进入到/sys/class/leds 目录下,如下所示:
上小节介绍了/sys/class 目录,系统中的所有设备根据其功能分类组织到了/sys/class 目录下,所以
/sys/class/leds 目录下便存放了所有的LED 类设备。从上图可以看到该目录下有一个sys-led 文件夹,这个便是底板上的用户LED 设备文件夹,进入到该目录下,如下所示:
这里我们主要关注便是brightness、max_brightness 以及trigger 三个文件,这三个文件都是LED 设备的属性文件:
⚫ brightness:翻译过来就是亮度的意思,该属性文件可读可写;所以这个属性文件是用于设置LED
的亮度等级或者获取当前LED 的亮度等级,譬如brightness 等于0 表示LED 灭,brightness 为正整数表示LED 亮,其值越大、LED 越亮;对于PWM 控制的LED 来说,这通常是适用的,因为它存在亮度等级的问题,不同的亮度等级对应不同的占空比,自然LED 的亮度也是不同的;但对于GPIO
控制(控制GPIO 输出高低电平)的LED 来说,通常不存在亮度等级这样的说法,只有LED 亮(brightness 等于0)和LED 灭(brightness 为非0 值的正整数)两种状态,ALPHA/Mini I.MX6U
开发板上的这颗LED 就是如此,所以自然就不存在亮度等级一说,只有亮和灭两种亮度等级。
⚫ max_brightness:该属性文件只能被读取,不能写,用于获取LED 设备的最大亮度等级。
⚫ trigger:触发模式,该属性文件可读可写,读表示获取LED 当前的触发模式,写表示设置LED 的触发模式。不同的触发模式其触发条件不同,LED 设备会根据不同的触发条件自动控制其亮、灭状态,通过cat 命令查看该属性文件,可获取LED 支持的所有触发模式以及LED 当前被设置的触发模式:
方括号([heartbeat])括起来的表示当前LED 对应的触发模式,none 表示无触发,常用的触发模式包括
none(无触发)、mmc0(当对mmc0 设备发起读写操作的时候LED 会闪烁)、timer(LED 会有规律的一亮一灭,被定时器控制住)、heartbeat(心跳呼吸模式,LED 模仿人的心跳呼吸那样亮灭变化)。
通常系统启动之后,会将板子上的一颗LED 设置为heartbeat 触发模式,将其作为系统正常运行的指示灯,譬如ALPHA/Mini I.MX6U 开发板系统启动之后,底板上的用户LED 就会处于心跳呼吸模式,这个大家自己观察便可知道。
通过上面的介绍,已经知道如何去控制ALPHA/Mini I.MX6U 开发板底板上的用户LED 了,譬如通过
echo 命令进行控制:
echo timer > trigger //将LED 触发模式设置为timer
echo none > trigger //将LED 触发模式设置为none
echo 1 > brightness //点亮LED echo 0 > brightness//熄灭LED
大家可以自己动手使用echo 或cat 命令进行测试、控制LED 状态;除了使用echo 或cat 命令之后,同样我们编写应用程序,使用write()、read()函数对这些属性文件进行I/O 操作以达到控制LED 的效果。
Tips:命令cat 读取以及echo 写入到属性文件中的均是字符串,所以如果在应用程序中通过write()向属性文件写入数据,同样也要是字符串形式;同理,使用read()读取的数据也是字符串ASCII 编码的。
编写LED 应用程序
通过上一小节的介绍,我们已经知道了如何控制LED,接下来编写一个简单地示例代码演示如何控制
LED,测试代码如下所示:
本例程源码对应的路径为:开发板光盘->11、Linux C 应用编程例程源码->15_led->led.c。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define LED_TRIGGER "/sys/class/leds/sys-led/trigger"
#define LED_BRIGHTNESS "/sys/class/leds/sys-led/brightness"
#define USAGE() fprintf(stderr, "usage:\n" \" %s <on|off>\n" \" %s <trigger> <type>\n", argv[0], argv[0])
int main(int argc, char *argv[])
{int fd1, fd2;/* 校验传参*/if (2 > argc) {USAGE();exit(-1);}/* 打开文件*/fd1 = open(LED_TRIGGER, O_RDWR);if (0 > fd1) {perror("open error");exit(-1);}fd2 = open(LED_BRIGHTNESS, O_RDWR);if (0 > fd2) {perror("open error");exit(-1);}/* 根据传参控制LED */if (!strcmp(argv[1], "on")) {write(fd1, "none", 4); //先将触发模式设置为nonewrite(fd2, "1", 1); //点亮LED}else if (!strcmp(argv[1], "off")) {write(fd1, "none", 4); //先将触发模式设置为nonewrite(fd2, "0", 1); //LED 灭}else if (!strcmp(argv[1], "trigger")) {if (3 != argc) {USAGE();exit(-1);}if (0 > write(fd1, argv[2], strlen(argv[2])))perror("write error");}elseUSAGE();exit(0);
}
程序中定义了两个宏,LED_TRIGGER 和LED_BRIGHTNESS,分别对应/sys/class/leds/sys-led/trigger 和
/sys/class/leds/sys-led/brightness 属性文件,宏USAGE()用于打印程序的使用方法;程序首先会调用open()函数打开这两个属性文件,之后判断传入参数指向相应的动作,传入"on"表示点亮LED,先调用write()将"none"
写入到trigger 属性文件中,也就是设置为无触发,接着再向brightness 属性文件中写入"1"点亮LED;传入
"off"表示熄灭LED,同样也是先调用write()将"none"写入到trigger 属性文件设置LED 为无触发,接着再向
brightness 属性文件中写入"0"熄灭LED;传入"trigger"表示设置LED 的触发模式,则需要传入第二个参数,第二个参数表示需要设置的模式。
整个代码非常简单,接下来对测试代码进行编译,需要注意的时,由于我们是在ALPHA/Mini I.MX6U
开发板上运行程序,所以需要I.MX6U 平台对应的交叉编译工具来编译测试代码,这样编译得到的可执行文件才能在开发板上运行。
首先大家需要安装I.MX6U 硬件平台对应的交叉编译工具,如何安装呢?直接参考“开发板光盘资料
A-基础资料/【正点原子】I.MX6U 用户快速体验V1.7.3.pdf”文档中的第四章内容,根据文档的指示安装好交叉编译工具,当然如果你已经在Ubuntu 系统下安装过了,就不用再次安装了。
安装完成之后,在使用之前先对交叉编译工具的环境进行设置,使用source 执行安装目录下的
environment-setup-cortexa7hf-neon-poky-linux-gnueabi 脚本文件即可,如下所示:
source /opt/fsl-imx-x11/4.1.15-2.1.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
/opt/fsl-imx-x11/4.1.15-2.1.0 便是笔者在Ubuntu 系统下安装交叉编译工具时对应的安装目录,大家根据自己的情况设置正确的路径。处理完成之后,接下来我们便可以对示例代码15.3.1 进行编译了:
CC 变量其实就是交叉编译工具,如下所示:
所以CC 环境变量其实就是ARM 架构下的gcc 编译器—交叉编译工具arm-poky-linux-gnueabi-gcc,后面指定了一些选项,这些选项就不用管了;编译成功之后,会生成可在开发板上运行的可执行文件testApp,使用file 命令可以查看testApp 可执行文件的类型:
可以看出该文件是一个32 位ARM 架构下的可执行文件。
在开发板上测试
启动开发板进入Linux 系统,将上小节编译得到的可执行文件testApp 拷贝到开发板根文件系统中,譬如拷贝到开发板Linux 系统的家目录下,如下图所示:
拷贝方法很多,推荐大家使用scp 命令,这里就不再介绍了。
接下来执行testApp 程序测试:
./testApp on # 点亮LED
./testApp off # 熄灭LED
./testApp trigger heartbeat # 将LED 触发模式设置为heartbeat
查看LED 状态是否与程序执行的效果一致!
相关文章:

Linux系统点亮LED
目录应用层操控硬件的两种方式sysfs 文件系统sysfs 与/sys总结标准接口与非标准接口LED 硬件控制方式编写LED 应用程序在开发板上测试对于一款学习型开发板来说,永远都绕不开LED 这个小小的设备,基本上每块板子都至少会有一颗 LED 小灯,对于我…...

在superset中快速制作报表或仪表盘
在中小型企业,当下需要快速迭代、快速了解运营效果的业务,急需一款开源、好用、能快速迭代生产的报表系统。 老板很关心,BI工程师很关心,同时系统开发人员也同样关心,一个好的技术选型往往能够帮助公司减少很多成本&a…...

【可视化实战】Python 绘制出来的数据大屏真的太惊艳了
今天我们在进行一个Python数据可视化的实战练习,用到的模块叫做Panel,我们通过调用此模块来绘制动态可交互的图表以及数据大屏的制作。 而本地需要用到的数据集,可在kaggle上面获取 https://www.kaggle.com/datasets/rtatman/188-million-us…...

Obsidium一键编码作业,Obsidia惊人属性
Obsidium一键编码作业,Obsidia惊人属性 每个区域都包含几个可定制的功能,允许用户确定如何完全执行应用程序的安全性。Obsidia的功能区允许用户存储任何调整或一键编码作业。 Obsidia惊人属性: 代码虚拟化:代码虚拟化允许您转换程序代码的特定…...

约束优化:约束优化的三种序列无约束优化方法
文章目录约束优化:约束优化的三种序列无约束优化方法外点罚函数法L2-罚函数法:非精确算法对于等式约束对于不等式约束L1-罚函数法:精确算法内点罚函数法:障碍函数法等式约束优化问题的拉格朗日函数法:Uzawas Method fo…...

RocketMQ快速入门:消息发送、延迟消息、消费重试
一起学编程,让生活更随和! 如果你觉得是个同道中人,欢迎关注博主gzh:【随和的皮蛋桑】。 专注于Java基础、进阶、面试以及计算机基础知识分享🐳。偶尔认知思考、日常水文🐌。 目录1、RocketMQ消息结构1.1…...

FANUC机器人通过KAREL程序实现与PLC位置坐标通信的具体方法示例
FANUC机器人通过KAREL程序实现与PLC位置坐标通信的具体方法示例 在通信IO点位数量足够的情况下,可以使用机器人的IO点传输位置数据,这里以传输机器人的实时位置为例进行说明。 基本流程如下图所示: 基本步骤可参考如下: 首先确认机器人控制柜已经安装了总线通信软件(例如…...

[蓝桥杯 2015 省 B] 移动距离
蓝桥杯 2015 年省赛 B 组 H 题题目描述X 星球居民小区的楼房全是一样的,并且按矩阵样式排列。其楼房的编号为 1,2,3,⋯ 。当排满一行时,从下一行相邻的楼往反方向排号。比如:当小区排号宽度为 6 时,开始情形如下:我们的…...

Pandas库入门仅需10分钟
数据处理的时候经常性需要整理出表格,在这里介绍pandas常见使用,目录如下: 数据结构导入导出文件对数据进行操作 – 增加数据(创建数据) – 删除数据 – 改动数据 – 查找数据 – 常用操作(转置࿰…...
python的socket通信中,如何设置可以让两台主机通过外网访问?
要让两台主机通过外网进行Socket通信,需要在网络设置和代码实现两个方面进行相应的配置。下面是具体的步骤: 确认网络环境:首先要确保两台主机都能够通过外网访问。可以通过ping命令或者telnet命令来测试两台主机之间是否可以互相访问。 确定…...
检测数据的方法(回顾)
检测数据类型的4种方法typeofinstanceofconstructor{}.toString.call() 检测数据类型的4种方法 typeof 定义 用来检测数据类型的运算符 返回一个字符串,表示操作值的数据类型(7种) number,string,boolean,object,u…...

比特数据结构与算法(第三章_上)栈的概念和实现(力扣:20. 有效的括号)
一、栈(stack)栈的概念:① 栈是一种特殊的线性表,它只允许在固定的一端进行插入和删除元素的操作。② 进行数据插入的删除和操作的一端,称为栈顶 。另一端则称为 栈底 。③ 栈中的元素遵守后进先出的原则,即…...

JVM13 类的生命周期
1. 概述 在 Java 中数据类型分为基本数据类型和引用数据类型。基本数据类型由虚拟机预先定义,引用数据类型则需要进行类的加载。 按照 Java 虚拟机规范,从 class 文件到加载到内存中的类,到类卸载出内存为止,它的整个生命周期包…...

Docker网络模式解析
目录 前言 一、常用基本命令 (一)查看网络 (二)创建网络 (三)查看网络源数据 (四)删除网络 二、网络模式 (一)总体介绍 (二)…...
游山城重庆
山城楼梯多,路都是上坡。 为了赶早上8点从成都到重庆的动车,凌晨5点半就爬起床来,由于昨天喝了咖啡,所以我将尽3点才睡觉,这意味着我只睡了2个多小时。起来简单休息之后,和朋友协商好时间就一起出门了。 …...

Vuex的创建和简单使用
Vuex 1.简介 1.1简介 1.框框里面才是Vuex state:状态数据action:处理异步mutations:处理同步,视图可以同步进行渲染1.2项目创建 1.vue create 名称 2.运行后 3.下载vuex。采用的是基于vue2的版本。 npm install vuex3 --save 4.vu…...

Arduino IDE搭建Heltec开发板开发环境
Arduino IDE搭建Heltec开发板开发环境Heltec开发板开发环境下载与搭建Arduino IDE下载与安装搭建Heltec开发板的开发环境添加package URL方法通过Git的方法安装离线安装Heltec开发板开发环境下载与搭建 Arduino IDE下载与安装 Heltec的ESP系列和大部分的LoRa系列开发板都是用A…...
Using the GNU Compiler Collection 目录翻译
文章目录Introduction1 Programming Languages Supported by GCC2 Language Standards Supported by GCC2.1 C Language3 GCC Command Options3.1 Option Summary4 C Implementation-Defined Behavior6 Extensions to the C Language Family9 Binary Compatibility其他工具10 g…...

使用 OpenCV for Android 进行图像特征检测
android 开发人员,可能熟悉使用activities, fragments, intents以及最重要的一系列开源依赖库。但是,注入需要本机功能的依赖关系(如计算机视觉框架)并不像在 gradle 文件中直接添加实现语句那样简单!今天,将专注于使用 OpenCV 库…...

chatGPT笔记
文章目录 一、GPT之技术演进时间线二、chatGPT中的语言模型instructGPT跟传统语言LM模型最大不同点是什么?三、instructGPT跟GPT-3的网络结构是否一样四、GPT和BERT有啥区别五、chatGPT的训练过程是怎样的?六、GPT3在算数方面的能力七、GPT相比于bert的优点是什么八、元学习(…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...
golang循环变量捕获问题
在 Go 语言中,当在循环中启动协程(goroutine)时,如果在协程闭包中直接引用循环变量,可能会遇到一个常见的陷阱 - 循环变量捕获问题。让我详细解释一下: 问题背景 看这个代码片段: fo…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...

前端导出带有合并单元格的列表
// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

Android15默认授权浮窗权限
我们经常有那种需求,客户需要定制的apk集成在ROM中,并且默认授予其【显示在其他应用的上层】权限,也就是我们常说的浮窗权限,那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...

如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...
#Uniapp篇:chrome调试unapp适配
chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器:Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...
代码随想录刷题day30
1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...

R语言速释制剂QBD解决方案之三
本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...