模块学习(一)——编码电机

想实现对电机的测速,因此开始接触编码电机。此次采用的是RS365编码器电机。

一、编码电机的初步了解

通过编码电机可以测出速度。常见一般编码电机分成两种,一是光电编码器,另一个是霍尔编码器。有六个接口,两个是电机的正负极,两个是编码器的正负极,还有两个是A,B相。

倍频的概念:如果只采集A/B相的上升/下降沿,即一倍频。若采集A/B的上升和下降沿,即二倍频,最多可以达到四倍频。这样测出来的速度是非常精确的。
在这里插入图片描述

具体直观了解可由上图可见。

在这里插入图片描述

以上是此次采用的编码电机的各个参数。所以可以得知,减速电机转动一周,可以产生30390个脉冲。若采用四倍频,则可以产生430*390个脉冲,可以大大提高精确度。

软件测量方面,可以采用外部中断捕获上升沿或者下降沿来实现测速。

二、如何测量一定时间内的脉冲

这就要用到TIM的输入捕获功能了,正好之前学习TIM的时候,仅仅涉及到普通PWM波的输出,今天顺便一起学习一下TIMA的输入捕获功能。

1.TIMA

根据手册可知TIMA具有7个输入捕获通道,并且具有广泛的中断功能。

在这里插入图片描述
由上图可初步了解定时器A的工作框图。

具体配置方法如下,这个配置方法之中,参阅了许多资料,最后还是只能靠自己一点点啃数据手册,如有不完善,还希望各位一起交流。

第一步:配置IO口
GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P1,GPIO_PIN2);//配置为外围输入

配置P2.0为外设输出

第二步:连续模式初始化
 //Start timer in continuous mode sourced by SMCLK
   Timer_A_initContinuousModeParam initContParam = {0};//定义连续模式初始化结构体
   initContParam.clockSource = TIMER_A_CLOCKSOURCE_SMCLK;//确定时钟源SMCLK
   initContParam.clockSourceDivider = TIMER_A_CLOCKSOURCE_DIVIDER_1;//不分频
   initContParam.timerInterruptEnable_TAIE = TIMER_A_TAIE_INTERRUPT_DISABLE;//禁用Tim A中断
   initContParam.timerClear = TIMER_A_DO_CLEAR;//重置计数方向等.....
   initContParam.startTimer = false;//先不开启计数器
   Timer_A_initContinuousMode(TIMER_A0_BASE, &initContParam);
第三步:清除比较模式中断
//清楚比较模式中断(初始化比较模式)
   Timer_A_clearCaptureCompareInterrupt(TIMER_A0_BASE,
       TIMER_A_CAPTURECOMPARE_REGISTER_1
       );
第四步:捕获模式初始化
Timer_A_initCaptureModeParam initCapParam = {0};//定义比较模式初始化结构体
   initCapParam.captureRegister = TIMER_A_CAPTURECOMPARE_REGISTER_1;//选择TIM_A0的捕获比较通道1
   initCapParam.captureMode = TIMER_A_CAPTUREMODE_RISING_EDGE;//捕获上升沿
   initCapParam.captureInputSelect = TIMER_A_CAPTURE_INPUTSELECT_CCIxA;//确定输入选择为CCIxA(这个不知道怎么看怎么确定)
   initCapParam.synchronizeCaptureSource = TIMER_A_CAPTURE_SYNCHRONOUS;//与时钟同步
   initCapParam.captureInterruptEnable = TIMER_A_CAPTURECOMPARE_INTERRUPT_ENABLE;//使能捕获中断
   Timer_A_initCaptureMode(TIMER_A0_BASE, &initCapParam);
第五步:开始计时,打开全局中断
Timer_A_startCounter( TIMER_A0_BASE,//开启计时
           TIMER_A_CONTINUOUS_MODE
               );

   //Enter LPM0, enable interrupts开启全局中断
   __bis_SR_register(LPM0_bits + GIE);

   //For debugger
   __no_operation();
第六步:中断服务函数

在main.c中,写上输入捕获的中断服务函数

#pragma vector = TIMER0_A1_VECTOR//定时器中断
__interrupt void Timer_A(void)//生命一个函数为中断函数
{
  static int CNT = -1;
  CNT++;
  switch(__even_in_range(TA0IV,14))//中断标志位——对应捕获通道
  {
    case 2 ://如果是CCR1产生的中断
    GPIO_setOutputHighOnPin(GPIO_PORT_P4,GPIO_PIN7);
    if(CNT % 2 == 0)//记录第一次的上升沿Counter计数值
    {
        edge_count1 = Timer_A_getCaptureCompareCount(TIMER_A0_BASE,TIMER_A_CAPTURECOMPARE_REGISTER_1);//将此时的上升沿计数值存入变量

    }
    else if(CNT % 2 == 1)//检测到下降沿触发
    {
        edge_count2 = Timer_A_getCaptureCompareCount(TIMER_A0_BASE,TIMER_A_CAPTURECOMPARE_REGISTER_1);//将此时的下降沿计数值存入变量
    }
    PWM_Frequency  = (unsigned int)(1000000/(abs(edge_count1 - edge_count2) * 0.948));
    break;
    case 4 : break;
    case 10: break;
    case 14:break;//此时发生计数器溢出
    default: break;
  }
}

中断服务函数中这种编写方式,对于低频50HZ,100HZ是比较准确的。

看效果图
在这里插入图片描述
在这里插入图片描述

问题:

一旦频率上升超过1Khz,就不准确了。对于此时才用的是MCLK时钟,频率为1.054Mhz,也就是说CNT计数器每次+1,对应的时间是0.948us,按理来说,可测量频率范围应该在0-1.054Mhz之间,而实际可测频率仅有几百HZ,这是为什么呢??

采用一下测量方法,效果依然不好,这是为啥啊啊啊啊啊
下面这部分进行了10倍分频…因此是105400

#pragma vector = TIMER0_A1_VECTOR//定时器中断
__interrupt void Timer_A(void)//生命一个函数为中断函数
{
  static int CNT = -1;
  CNT++;
  switch(__even_in_range(TA0IV,14))//中断标志位——对应捕获通道
  {
    case 2 ://如果是CCR1产生的中断
        PWM_Frequency = 105400/Timer_A_getCounterValue(TIMER_A0_BASE);
        Timer_A_clear(TIMER_A0_BASE);
        break;
    case 4 : break;
    case 10: break;
    case 14:break;//此时发生计数器溢出
    default: break;
  }
}

等我解决后再来继续补充!如果各位大佬有解决办法,也麻烦留言评论一下,谢谢!

问题在昨日晚八点解决!但是具体原因我还是不太清楚为啥。在那之后,我将主频升到16MHZ之后,发现对于频率的测量可以精准到1KHZ误差之内,再改进一下算法,现在误差只有十几赫兹了,代码如下。

基本配置

 GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P1,GPIO_PIN2);//配置为外围输入

    //Start timer in continuous mode sourced by SMCLK
   Timer_A_initContinuousModeParam initContParam = {0};//定义连续模式初始化结构体
   initContParam.clockSource = TIMER_A_CLOCKSOURCE_SMCLK;//确定时钟源SMCLK
   initContParam.clockSourceDivider = TIMER_A_CLOCKSOURCE_DIVIDER_10;//不分频
   initContParam.timerInterruptEnable_TAIE = TIMER_A_TAIE_INTERRUPT_DISABLE;//禁用Tim A中断
   initContParam.timerClear = TIMER_A_DO_CLEAR;//重置计数方向等.....
   initContParam.startTimer = false;//先不开启计数器
   Timer_A_initContinuousMode(TIMER_A0_BASE, &initContParam);

   //清除比较模式中断(初始化比较模式)
   Timer_A_clearCaptureCompareInterrupt(TIMER_A0_BASE,
       TIMER_A_CAPTURECOMPARE_REGISTER_1
       );

   Timer_A_initCaptureModeParam initCapParam = {0};//定义比较模式初始化结构体
   initCapParam.captureRegister = TIMER_A_CAPTURECOMPARE_REGISTER_1;//选择TIM_A0的捕获比较通道1
   initCapParam.captureMode = TIMER_A_CAPTUREMODE_FALLING_EDGE;//捕获上升沿
   initCapParam.captureInputSelect = TIMER_A_CAPTURE_INPUTSELECT_CCIxA;//确定输入选择为CCIxA(这个不知道怎么看怎么确定)
   initCapParam.synchronizeCaptureSource = TIMER_A_CAPTURE_SYNCHRONOUS;//与时钟同步
   initCapParam.captureInterruptEnable = TIMER_A_CAPTURECOMPARE_INTERRUPT_ENABLE;//使能捕获中断
   Timer_A_initCaptureMode(TIMER_A0_BASE, &initCapParam);

   Timer_A_startCounter( TIMER_A0_BASE,//开启计时
           TIMER_A_CONTINUOUS_MODE
               );

   //Enter LPM0, enable interrupts开启全局中断
   __bis_SR_register(LPM0_bits + GIE);

   //For debugger
   __no_operation();

中断服务函数

#pragma vector = TIMER0_A1_VECTOR//定时器中断
__interrupt void Timer_A(void)//生命一个函数为中断函数
{
  static unsigned int CNT = 0;

  switch(__even_in_range(TA0IV,14))//中断标志位——对应捕获通道
  {
    case 2 ://如果是CCR1产生的中断
        if(CNT % 2 == 0)//记录第一次的上升沿Counter计数值
        {
//            edge_count1 = Timer_A_getCounterValue(TIMER_A0_BASE);//将此时的上升沿计数值存入变量
            edge_count1 = Timer_A_getCaptureCompareCount(TIMER_A0_BASE,TIMER_A_CAPTURECOMPARE_REGISTER_1);
        }
        else if(CNT % 2 == 1)//记录第二次上升沿Counter计数值
        {
            edge_count2 = Timer_A_getCaptureCompareCount(TIMER_A0_BASE,TIMER_A_CAPTURECOMPARE_REGISTER_1);//将此时的下降沿计数值存入变量
            Timer_A_clear(TIMER_A0_BASE);
        }
        PWM_Frequency  = (unsigned int)(1600000/(float)(abs(edge_count1 - edge_count2)));
        //PWM_Frequency = 1600000/Timer_A_getCounterValue(TIMER_A0_BASE);
        //Timer_A_clear(TIMER_A0_BASE);
        break;
    case 4 : break;
    case 10: break;
    case 14:break;//此时发生计数器溢出
    default: break;
  }
  CNT++;

  // Enable global interrupt
  _BIC_SR_IRQ(LPM0_bits);
}

尤其注意中断服务函数中的最后一句话,作用大概是是能全局中断的同时退出低功耗模式,如果缺少这句话,会使得你的代码一直在跑中断函数,而无法跑主函数!别问我为啥知道,我试了一整个上午才解决!!!望各位避坑!

接下来就是进行测速了!

unsigned int V_detect(unsigned int FRQ)
{
    //我们测出的频率就是单位时间内的脉冲数
    //对应该编码电机轮子一圈产生4.5 * 12 = 54个脉冲
    //轮子半径 32.5mm
    unsigned int speed = 0;
    float circle = 3.141*2*0.0325;
    speed = (unsigned int)(((float)FRQ/54)*circle);

    return speed;
}

这样测速就完成啦!

  • 6
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 15
    评论
ARM 是一种广泛使用的 CPU 架构,而 Linux 内核是一个开放源代码的操作系统内核。在 ARM 平台上,我们可以通过内核模块编程的方式与内核进行交互,实现一些自定义的功能。 下面,我们将介绍如何在 ARM Linux 上编写内核模块,并输出一个简单的 "Hello World" 消息。 ## 1. 环境准备 在开始编写内核模块之前,需要先准备好开发环境。具体步骤如下: 1. 安装交叉编译工具链。ARM 平台上的应用程序和内核模块需要使用交叉编译工具链进行编译。可以从官网下载对应的交叉编译工具链,也可以使用已经编译好的交叉编译工具链。 2. 下载内核源代码。可以从官网下载对应版本的内核源代码,也可以使用已经编译好的内核源代码。 3. 配置内核源代码。需要在内核源代码根目录下运行配置命令 `make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- menuconfig` 进行配置,选择需要的模块和功能。 ## 2. 编写内核模块 在准备好开发环境之后,可以开始编写内核模块了。具体步骤如下: 1. 创建一个新的文件夹,用于存放内核模块代码。 2. 创建一个新的 C 文件,命名为 `hello.c`。 3. 在 `hello.c` 文件中编写以下代码: ```c #include <linux/init.h> #include <linux/module.h> static int __init hello_init(void) { printk(KERN_INFO "Hello, world!\n"); return 0; } static void __exit hello_exit(void) { printk(KERN_INFO "Goodbye, world!\n"); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("A simple hello world module"); ``` 这段代码定义了一个简单的内核模块,当模块加载时会输出 "Hello, world!" 消息,当模块卸载时会输出 "Goodbye, world!" 消息。 4. 使用交叉编译工具链进行编译。在终端中进入 `hello.c` 文件所在的文件夹,运行以下命令进行编译: ```bash arm-linux-gnueabi-gcc -Wall -Werror -O2 -o hello.ko -c hello.c ``` 这个命令将生成一个名为 `hello.ko` 的内核模块文件。 ## 3. 加载和卸载内核模块 在编写好内核模块后,我们需要将它加载到内核中进行测试。具体步骤如下: 1. 将 `hello.ko` 文件复制到 ARM Linux 系统上。 2. 在终端中进入 `hello.ko` 文件所在的文件夹,运行以下命令以加载内核模块: ```bash insmod hello.ko ``` 这个命令将调用内核中的 `init_module` 函数,执行 `hello_init` 函数,输出 "Hello, world!" 消息。 3. 查看系统日志,可以看到 "Hello, world!" 消息。 ```bash dmesg ``` 4. 在终端中运行以下命令以卸载内核模块: ```bash rmmod hello ``` 这个命令将调用内核中的 `cleanup_module` 函数,执行 `hello_exit` 函数,输出 "Goodbye, world!" 消息。 5. 再次查看系统日志,可以看到 "Goodbye, world!" 消息。 至此,我们已经成功地在 ARM Linux 上编写了一个简单的内核模块,并输出了 "Hello, world!" 消息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值