Linux实现方波输出和PWM技术 (linux 方波输出pmw)
随着人们对数字化和自动化需求的增加,嵌入式系统在市场上的应用越来越广泛,而Linux系统作为软件开发非常成熟的一个操作系统,被广泛应用于嵌入式系统中。在嵌入式系统开发中,有许多应用需要使用到PWM技术或者方波输出,本文将会详细讲解如何在Linux系统中实现方波输出和PWM技术。
一、方波输出
方波信号是一种具有特定周期和占空比的信号,一般用于嵌入式系统中的定时器、DAC转换、蜂鸣器等场合,现在我们将借助Linux系统实现方波输出。
1.1 硬件搭建
方波输出的硬件搭建如图1所示,需要一个单片机作为信号发生器和一个示波器进行观察。
![图1](https://img-blog.csdn.net/20230331210056508?watermark/2/text/aHR0cDovL2J2cuY3Nkbi5uZXQvbG9naW5fZ3VpZGUx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/q/75)
1.2 打开XT2时钟
在CCS(Code Composer Studio)中,多数情况需要用到内部低频晶振(LFXT1),然而现在我们没用到它,而使用外部高频晶振(XT2)。需要进行如下配置:
(1)flash主程序分配区域
在 startup_ccs/hw_memmap.h 文件中添加如下宏定义:
#define HW_NMI (0xFFFEu)
将NMI vector定义设置在微控制器的外部RAM中。
(2)时钟配置
在CPU主频为16MHz(CLK)和数量级于4~20MHz的外部时钟的设定下,格式为:
#include
…
UCSCTL3 |= SELREF_2; // Set DCO FLL reference = XT2 = 16MHz
UCSCTL4 |= SELA_2; // Set ACLK = XT2 = 16MHz
UCSCTL0 |= UCSON; // Enable UCS subsystem
UCSCTL4 |= SELS_4 | SELM_4; // CLK=MCLK=XT2
此处,UCSCTL3是内部系统时钟,表示将此时钟配置为使用XT2作为DCO FLL参考时钟,UCSCTL4是时钟门控寄存器,SELREF_2表示使用XT2作为DCO FLL参考时钟,SELA_2表示设置ACLK时钟源为XT2,SELS_4和SELM_4表示将时钟源分别设置为CLK和MCLK。
1.3 实现方波读写操作
(1)打开输出口
P8SEL |= BIT0;
这里P8SEL和SEL和dir是三个位于io.h头文件中的宏定义。P8SEL代表P8口,SEL口和DIR口分别用于配置端口是输入还是输出,这里我们将P8口的P8.0位于SEL高阻态。
(2)关闭滤波器
/*
* Regarding Digital IOs’ filtering, if a I/O line,
* primary or secondary function, is expected to experience
* a sharp rising or falling edge, and that we want to
* capture that signal, one would have to disable the
* digital filter associated to the I/O line using the
* digital IO disable register DIO#_FSEL.
*
* DIO#_FSEL &= ~bitfield;
*
* DIO# is the name of the Digital IO and bitfield is the
* bitfield associated to that Digital IO.
* __even_in_range is a macro that dynamically compares the
* user defined number with the base of the register.
*
* We’ll disable all digital filters for this example.
*/
P8DIR |= BIT0;
P8DS |= BIT0;
P8OUT &= ~BIT0;
P8SEL &= ~BIT0;
P8REN &= ~BIT0;
P8SEL |= BIT0;
这里需要将滤波器概念介绍一下。数字IO端口在信号输入时,当解析器的输出跳变时,端口会使用一个低频率振荡电路(低通滤波器)将输入信号进行滤波。滤波器按照设备的预定阈值设定为一个可滤波的更大上升沿时延(通常在几微秒到几百微秒之间),过滤掉了较慢的信号干扰。当端口输出时,需要将端口滤波器关掉,否则会影响输出。通过配置P8口的P8.0相应的FSEL控制寄存器,可以实现关闭数字滤波器。
(3)实现方波输出
while (1) { // Loop forever
period = 20; // 20ms period
pulsewidth = period / 2; // Time the signal stays high
TA0CCR0 = TA1CCR0 = period * 1000 / 25; // Counter for up/down mode
TA0CCR1 = TA1CCR1 = pulsewidth * 1000 / 25; // Time when output is high
TA0CCTL1 = TA1CCTL1 = OUTMOD_7; // Set output mode to toggle
TA0CTL = TA1CTL = TASSEL__XT2 | MC__UPDOWN | TACLR; // Set upcounter & clear timer
while (1){}; // Let period edge interrupt handle next pulse
}
在这段代码中,我们通过配置TA0和TA1两个定时器,实现了单片机生成一段特定占空比的方波信号。这里的定时器是通用定时器(Timer_A),是单片机中常用的高级定时器。
TA0CCR0是计数器阈值,TA0CCR1是比较器阈值,TA0CCTL1是比较器控制器,OUTMOD_7表示设置输出模式为“比较输出模式7”(即:除计数器为0时置位外,其他情况下,比较器寄存器与计数器寄存器相等则翻转信号,否则不翻转)。TASSEL_2是选择TA的时钟源为XT2,MC_UPDOWN是计数模式设为向上向下计数模式,TACLR是允许清除TA计时器计数器。
1.4 完整代码
下面是生成方波信号的完整代码:
#include
…
int mn (void) {
WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer
P8SEL |= BIT0; // Set P8.0 for secondary peripheral module function (GPIO output)
P8DIR |= BIT0; // Configure P8.0 as output
P8DS |= BIT0; // Connect P8.0 to I/O pad
P8OUT &= ~BIT0; // Set initial output to 0
P8SEL &= ~BIT0; // Disconnect P8.0 from NMI/nRST
P8REN &= ~BIT0; // Disable internal pullup resistor
P8SEL |= BIT0; // Select peripheral module function (in this case, TACLK)
while (1) { // Loop forever
period = 20; // 20ms period
pulsewidth = period / 2; // Time the signal stays high
TA0CCR0 = TA1CCR0 = period * 1000 / 25; // Counter for up/down mode
TA0CCR1 = TA1CCR1 = pulsewidth * 1000 / 25; // Time when output is high
TA0CCTL1 = TA1CCTL1 = OUTMOD_7; // Set output mode to toggle
TA0CTL = TA1CTL = TASSEL__XT2 | MC__UPDOWN | TACLR; // Set upcounter & clear timer
while (1){}; // Let period edge interrupt handle next pulse
}
…
二、PWM技术
PWM技术(Pulse-width modulation)又称脉宽调制技术,是一种通过调节周期相等的脉冲宽度来控制输出电压或电流的技术,一般应用于马达控制、LED的亮度控制等场合。下面我们将介绍在Linux系统中如何实现PWM技术。
2.1 硬件搭建
PWM技术的硬件搭建如图2所示,需要一个单片机作为信号发生器和一个示波器进行观察。
![图2](https://img-blog.csdn.net/20230331211053518?watermark/2/text/aHR0cDovL2J2cuY3Nkbi5uZXQvbG9naW5fZ3VpZGUx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/q/75)
2.2 驱动器
PWM技术的es0风驱动器通过将电源转换为PWM信号来控制电机的转速。驱动器是由一个控制器、一个驱动芯片和一个三相桥组成的。控制器负责控制电机的电流,驱动芯片ON/OFF是在这个信号的控制下进行的。使用PWM技术,我们可以调整PWM信号的峰值来控制电机的输出功率。
2.3 PWM方式实现
在Linux系统中,实现PWM方式有两种:软件PWM和硬件PWM。软件PWM的优点是实现简单,但精度有限,很难实现更高分辨率的PWM波形。硬件PWM技术通过直接控制MCU的输出端口来生成高分辨率的PWM波形,但由于接口的限制,硬件PWM技术的灵活性比软件PWM要低。
下面我们将介绍如何在Linux系统中通过硬件PWM技术来实现控制一个3相电机的转速。
(1)配置PWM输出输出引脚
我们需要向设备树引擎添加定时器、PWM驱动程序和节点,以实现PWM控制。
给xilinx,dma节点添加定时器属性,将PWM信号输出到zynq (ps)端口。定时器只需要设定时钟源和计数器周期即可。
pwm-leds {
compatible = “pwm-leds”;
led0 {
pwms = ;
pwn-period = ;
line-names = “pwm0”;
default-brightness-level = ;
};
};
&pwm0 {
status = “okay”;
pwn-period = ; /* 100Hz with internal clock */
ti,shoot-yes;
};
配置完毕后,我们可以读取PWM周期和占空比并将其写入设备文件,以控制电机的速度。
(2)控制电机的速度
控制电机的转速是通过调整PWM信号的频率和占空比来实现的。具体来说,我们可以通过增加PWM信号的频率来加快电机的转速,同时通过增加PWM信号的占空比来提高其输出功率。
下面是代码,用于通过PWM技术驱动一个3相电机:
/* pwm_leds.c */
#include
#include
#include
#include
#include
#include
#include
#include
#define DEVICE_NAME “pwm-leds”
static struct pwm_device *pwm_dev;
static struct device *dev;
static void pwm_leds_set_brightness_level(unsigned int level)
{
printk(KERN_INFO “pwm_leds: Set brightness level to %u\n”, level);
/* Level should be in the range of [0, pwn-period] */
level = min_t(unsigned int, pwm_get_period(pwm_dev), level);
/* Set PWM duty cycle */
pwm_config(pwm_dev, level, pwm_get_period(pwm_dev));
/* Enable the PWM signal */
pwm_enable(pwm_dev);
/* Wt a short while for the new settings to take effect */
msleep(50);
}
/* Device attribute callbacks */
static ssize_t brightness_level_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return scnprintf(buf, PAGE_SIZE, “%u\n”,
pwm_get_duty_cycle(pwm_dev));
}
static ssize_t brightness_level_store(struct device *dev,
struct device_attribute *attr, const char *buf,
size_t count)
{
int value;
int ret;
ret = kstrtoint(buf, 10, &value);
if (ret
return ret;
if ((value pwm_get_period(pwm_dev)))
return -EINVAL;
pwm_leds_set_brightness_level(value);
return count;
}
/* Device attributes */
static DEVICE_ATTR(brightness_level, S_IWUSR|S_IRUGO,
brightness_level_show, brightness_level_store);
/* Platform device driver */
static int pwm_leds_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
int err;
if (!np)
return -ENODEV;
pwm_dev = devm_pwm_get(&pdev->dev, NULL);
if (IS_ERR(pwm_dev))
return PTR_ERR(pwm_dev);
/* Initialize device attributes */
err = device_create_file(&pdev->dev, &dev_attr_brightness_level);
if (err)
goto fl;
/* Create a new device node */
dev = device_create(pc_class, NULL, 0, NULL, DEVICE_NAME);
if (IS_ERR(dev)) {
err = PTR_ERR(dev);
goto fl;
}
dev_set_drvdata(dev, pwm_dev);
return 0;
fl:
if (&dev_attr_brightness_level.attr)
device_remove_file(&pdev->dev, &dev_attr_brightness_level);
return err;
}
static int pwm_leds_remove(struct platform_device *pdev)
{
device_remove_file(&pdev->dev, &dev_attr_brightness_level);
device_unregister(dev);
return 0;
}
static struct platform_driver pwm_leds_driver = {
.probe = pwm_leds_probe,
.remove = pwm_leds_remove,
.driver = {
.name = DEVICE_NAME,
.owner = THIS_MODULE,
},
};
static int __init pwm_leds_init(void)
{
return platform_driver_register(&pwm_leds_driver);
}
static void __exit pwm_leds_exit(void)
{
platform_driver_unregister(&pwm_leds_driver);
}
MODULE_LICENSE(“GPL”);
module_init(pwm_leds_init);
module_exit(pwm_leds_exit);