Microchip
直播中

文妮

7年用户 246经验值
私信 关注
[问答]

如何用PIC24EP系列单片机在触发模式下使用OCX实现一次触发功能?

PIC MCU的增长领域之一是遥控器(RC)无人机(如四头飞行器)的控制模块。在这些应用中,MCU通过产生用于电子速度控制器(ESC)的控制信号来控制模型的电机。ESC由脉冲控制,脉冲的持续时间决定了ESC提供给电机的信号(节流)水平。例如,在传统的RC应用中,1毫秒脉冲对应于0%节气门和2毫秒脉冲至100%节气门。在1至2毫秒范围内改变脉冲的持续时间允许在0和100%之间的任何级别设置电机控制。由于控制速度对于RC应用是至关重要的,所以较新的ESC接受较短的脉冲——对于0%节流阀125usec,对于100%节流阀250usec,这减少了传输控制信号的延迟。ESC需要控制脉冲之间的一些延迟,所以对于较老的ESC(由1-2毫秒脉冲控制)来说,最大刷新频率大约为400-450Hz,对于较新的ESC(1-2KHz),控制RC应用中ESC的典型方法是在PWM模式下使用输出比较模块将刷新速率设置为m。当来自控制模块的电机控制的新更新时,ESC接受的轴和改变PWM的占空比以匹配所需的脉冲持续时间。由于OCxR寄存器在PWM模式下是双缓冲的,因此我们可以随时更新占空比,在下一个同步事件中占空比会发生变化。被ESC接受的级别,迫使ESC处理新的脉冲,即使没有电机控制级别的更新;由于占空比仅在同步事件上更新,随机时间生成的OCxR的更新必须平均等待PWM周期的一半,直到它可用于ESC通过改变持续时间的新脉冲,幸运的是OC模块为PWM模式提供了一个功能!根据FRM:13.3.3.14.2“单击功能”的第13.3.3.14.2节……通过设置TRIGMODE位(OCxCON1)启用“单击模式”。此事件设置TrutStAT位,计时器开始计数。当计时器滚动到0x000时,如果TrimeMod=1,则TrutStButt将被硬件清除。这将定时器保持在Reset,直到下一个触发事件,创建一个单次定时器。他持续时间的OCXR,然后把它下来产生单脉冲所需的持续时间。当OCx定时器在OCxRS寄存器(PWM周期)中达到值时,OCx模块将清除TRIGSTAT位,并将定时器保持在RESET中,直到我们重复这个循环。不幸的是,它不是这样发生的!在将定时器放在复位之前,OCX模块检查OCXR寄存器的值,如果不是0,则会增加与模块相关联的引脚上的信号。因此,下次我们通过更新OCXR和设置TrimStAT=1来重复周期,我们将得到脉冲持续时间,它将是OCXR的新值加上从先前触发的PWM脉冲结束的间隔的持续时间,直到触发下一个脉冲的时刻为止!这是根据FRM的部分:133.3.1边缘对齐PWM模式…边缘校准的PWM模式操作:-当同步发生时,在下个增量周期发生以下四个事件:-定时器复位为零,并恢复计数-OCx管脚设置为高(例外:如果OCxRS=0000,OCx管脚将不设置)-OCxR和OCxRS缓冲寄存器是更新从OCXR和OCXRS -中断标志,OCxIF,设置-当定时器和OCXR匹配,引脚将设置为低。显然,在模块分析TRIGMODE位并且意识到它处于触发模式并且需要在RESET中保持定时器之前,上面的顺序,包括将OCx引脚设置为高。我认为这种行为在硬件上是BUG,应该反映在Errata文档中。保持OCX引脚低,直到我们重复循环。另外,在设置TRIGSTAT=1之前更新OCxR之后,请注意6条Nop()语句——由于PWM模式下OCxR的双缓冲特性,模块需要几个周期来传播更改。小于6个(),我们可能或不可能得到一个脉冲,小于3个(),我们将永远不会得到脉冲!附上完整的MPlab-X项目,说明产生单次脉冲的完整工作程序;它已经在PIC24EP512GU810和PIC24EP512GP806上进行了测试。但是,由于PIC24EP和dsPIC33EP的整个版本都由同一个FRM所覆盖,我猜这个代码可以适用于所有这些。如果您在我的逻辑或代码中发现错误,或者有更简单的实现,我将非常感谢您的评论!谢谢您。

以上来自于百度翻译


      以下为原文

    One of the growing area of PIC MCUs is the control modules for remote control (RC) drones like quadrocopters. In these applications MCU is being used to control model’s motors by generating control signals for Electronic Speed Controllers (ESCs). ESCs are being controlled by pulses were duration of the pulse defines the level of signal (throttle) provided by ESC to the motor. For example, in traditional RC applications 1 msec pulse corresponds to 0% throttle and 2 msec pulse – to 100% throttle. Changing duration of the pulses in the range between 1 and 2 msec allows to set motor control at any level between 0 and 100%. As the speed of control is critical for RC applications, newer ESCs accept shorter pulses – 125 usec for 0% throttle and 250 usec for 100% throttle, which reduces delay in transmitting control signal. ESCs require some delay between control pulses, so for older ESCs (controlled by 1-2 msec pulses) the maximum refresh frequency is around 400-450 Hz, and for newer – 1 to 2 KHz.
Typical way to control ESCs in RC applications is to use Output Compare module in PWM mode setting refresh rate to the maximum accepted by ESC and changing the duty cycle of PWM to match required duration of the pulse when a new update for motor control comes from the control module. As the OCxR register is double-buffered in PWM mode, we could update duty cycle at any moment of time and change in the duty cycle will happen on the next Sync event.
There are two disadvantages in using straight PWM for controlling ESCs:
  • To reduce delay, we need to set PWM frequency at the highest level accepted by the ESC, forcing ESC to process new pulses even when there is no update to the level of motor control;
  • As duty cycle is being updated only on Sync event, an update to OCxR generated at random time would have to wait on average half of the PWM period until it is made available to the ESC through a new pulse of changed duration.
Luckily OC module offers One-Shot functionality for PWM mode! According to section 13.3.3.14.2 of the FRM:
13.3.3.14.2 One-Shot Functionality
… The One-Shot mode is enabled by setting the TRIGMODE bit (OCxCON1).
In One-Shot mode, the timer remains in Reset until a trigger event occurs. This event sets the
TRIGSTAT bit and the timer begins to count. When the timer rolls over to 0x0000, the TRIGSTAT
bit would be cleared by the hardware if TRIGMODE = 1. This holds the timer in Reset until the
next trigger event, creating a one-shot timer.

Looks simple – while TRIGSTAT is 0, load OCxR with the value corresponding to the new pulse duration and set TRIGSTAT=1; OCx module will wake up raise the value on the pin associated with OCx and keep it up for the duration of OCxR, and then bring it down generating single pulse of required duration. When OCx timer reaches value in OCxRS register (PWM period), OCx module will clear the TRIGSTAT bit and hold the timer in RESET until we repeat the cycle.
Unfortunately, it does not happen this way! Prior to putting timer in RESET, OCx module checks the value of OCxR register and if it is not 0, will raise the signal on the pin associated with the module. So the next time we repeat the cycle by updating OCxR and setting TRIGSTAT=1, we will get pulse duration of which will be the new value of OCxR PLUS duration of the interval from the end of the previous triggered PWM pulse until the moment we trigger next pulse! This is in accordance with section of the FRM:


13.3.3.1 EDGE-ALIGNED PWM MODE
... Edge-Aligned PWM Mode Operation:
- When synchronization occurs, the following four events occur on the next increment cycle:
- The timer is reset to zero and resumes counting
- The OCx pin is set high (exception: if OCxRS = 0000, the OCx pin would not be set)
- The OCxR and OCxRS Buffered registers are updated from OCxR and OCxRS
- Interrupt flag, OCxIF, is set
- When the timer and OCxR match, the pin would be set low. This match does not generate
interrupts.



Apparently the sequence above, including setting OCx pin high, happens BEFORE the module analyzes TRIGMODE bit and realizes that it is in a triggered mode and need to hold timer in RESET! I think this behavior is a BUG in the hardware and should have reflected in Errata document.
Anyway, the work-around and the way to implement proper one-shot pulse is the following:
           if (0 == OC1CON2bits.TRIGSTAT)
                {
                OC1R = OC_Width;
                Nop();
                Nop();
                Nop();
                Nop();
                Nop();
                Nop();
                //--------------------------
                OC1CON2bits.TRIGSTAT = 1;
                //--------------------------
                Nop();
                OC1R = 0;
                }

The important piece here that one Nop() after setting TRIGSTAT=1, the OCxR is set to 0 so at next Sync event module will keep OCx pin low until we repeat the cycle. Also, please note 6 Nop() statements after updating OCxR before setting TRIGSTAT=1 – because of the double-buffered nature of OCxR in PWM mode, module requires several cycles to propagate the change. With less than 6 Nop() we may or may not get a pulse; with less than 3 Nop() we will never get pulse!
Attached is complete MPlab-X project illustrating fully working program generating One-shot pulses; it has been tested on PIC24EP512GU810 and PIC24EP512GP806. However, as the whole slew of PIC24EP and dsPIC33EP are covered by the same FRM, I guess this code will be applicable to all of them.
If you find a fault in my logic or in the code, or have a simpler implementation, I would appreciate your comments!
Thank you.
   Attachment(s)

GU810-20-OneShotSample.zip (15.38 KB) - downloaded 132 times

回帖(4)

李路明

2019-7-30 07:55:55
我很感兴趣地阅读你的文章,因为这可能是控制NIPOXIX的好方法。碰巧,我不得不用PIC24F而不是EP,但是措辞IDSS772B似乎是相同的。我注意到有一个“双比较单脉冲模式”的部分,我想知道这与一次性有什么不同(最终效果,如果不是在初始阶段)?在您的代码中,6xNOP是一个经验度量吗?在此之后,假设OCxR缓冲区已经从OCx加载是安全的,或者这个度量有文档记录吗?

以上来自于百度翻译


      以下为原文

    I read your post with interest because this might be a good way to control NeoPixels. It happens I have to hand a PIC24F rather than EP, but the wording in DS39723B seems identical. I notice there is a section "DUAL COMPARE SINGLE PULSE MODE".
 
I wonder how this differs (in final effect, if not in initiation) from one-shot?
 
In your code, was the 6xNOP an empirical measure, after which it seems safe to assume the OCxR buffer has been loaded from OCx - or is this measure documented?
 
举报

古克满

2019-7-30 08:14:32
嗨,在文档中没有关于6Nop()所需延迟的任何内容-我通过实验计算出它在64MHz下运行我的MCU-可能在不同的时钟频率下会有所不同,但我怀疑这一点。我想这不是实际的物理时间,而是需要的循环数,所以在不同的时钟频率使用6Nop()应该是安全的。当OCx关闭时,母鸡再次触发一次改变脉冲持续时间(如果必要的话)——可能是可能的,但是我不能方便的组合设置,因为我必须在4、6或8个OCx模块(对于四路、六路或八路直升飞机)上发出脉冲。触发模式似乎更容易实现。如果你只做一个镜头,请分享你的代码。问候,亚历克斯。

以上来自于百度翻译


      以下为原文

    Hi,
There was nothing in the documentation regarding the required delay of 6 Nop() - I figured it out experimentally running my MCU at 64 MHz - maybe at different clock frequency it would be different, but I doubt that. I guess this is not the actual physical time, but number of cycles required, so it should be safe to use 6 Nop() at different clock frequencies.
 
I also originally thought about using the One-Shot mode (which I do use for PPM generation in other project), but it requires stopping the OCx module and then trigger it again for One-Shot changing (if necessary) pulse duration while OCx is off - probably, possible, but I was not able to come with the convenient combination of setting considering that I have to issue pulses on 4, 6, or 8 OCx modules (for quad-, hexa, or octa-copter). The triggered mode appeared to be easier to implement.
 
If you do it with One-Shot, please share your code.
 
Regards,
--Alex
举报

李路明

2019-7-30 08:21:06
这是我当前的代码。我不知道如何监视“模拟器OCX PIN”-相应的LAT保持固定。我可以改变脉宽,推断总时间(OCXR+OCXRS)是正确的。我不能很容易地建立和监测硬件设置在Simult.RESULTS32 MHz时钟,16兆赫的时间周期,(62.5NS)秒表清除。秒表循环计数=0(0纳秒)目标停止。秒表循环计数=819(51.1875μs)& lt;-10US==OCXR+40US==OCXRS目标停止。秒表循环计数=35(2.1875秒)秒表清除。秒表循环计数=0(0纳秒)目标停止。秒表循环计数=819(51.1875秒)

以上来自于百度翻译


      以下为原文

    This is my current code. I do not know a way to monitor the 'simulator OCx pin' - the corresponding LAT stays fixed. I can vary the pulse width, and infer that the total time (OCxR+OCxRS) is about right. I cannot easily build and monitor a hardware setup at present.

Results
32MHz clock, 16 MHz Tcy period, (62.5nS)
   Stopwatch cleared. Stopwatch cycle count = 0 (0 ns)
   Target halted. Stopwatch cycle count = 819 (51.1875 µs)   <---10uS==OCxR + 40uS==OCxRS
   Target halted. Stopwatch cycle count = 35 (2.1875 µs)
   Stopwatch cleared. Stopwatch cycle count = 0 (0 ns)
   Target halted. Stopwatch cycle count = 819 (51.1875 µs)



 
#include
 

#define PTR_2_SFR volatile uint16_t *
 
#define FCK _32MHZ

/*
Sequence (PIC24FV32KA304 FAMILY)
   OCxCON1
   OCxCON2
   OCxRS
   OCxR
   OCxTMR
NOTE: For devices with PPS, the OCx output must be assigned to an RPn pin.
*/
void OneShotCfg(PTR_2_SFR pOcxCon1)
{
   PTR_2_SFR pOcxCon2 = pOcxCon1 + 1; //NOT "+sizeof(uint16_t)" as it's a pointer of the correct type
   
   *pOcxCon1 = (0b111 << 10); // = Peripheral clock (FCY)
   *pOcxCon2 = 0b11111; //SYNCSEL<4:0> = 0b11111 --> timer reset when it matches OCxRS
}//OneShotCfg

/*
User reponsible for ensuring (pulseDelay+pulseWidth) <= 16 bits
DS39723B
35.3.2.1 DUAL COMPARE SINGLE PULSE MODE
When OCxCON1:OCM = 0b100, the OCx pin is initialized low and a single output pulse occurs.
1. The OCx pin is driven low.
2. Upon a timer match with OCxR, (OCx) is driven high.
3. When the timer count matches OCxRS, the second, trailing edge (1-->0) occurs and the OCxIF interrupt flag bit gets set.
*/
void OneShot(PTR_2_SFR pOcxCon1, uint16_t pulseDelay, uint16_t pulseWidth)
{
   PTR_2_SFR pOcxRS = pOcxCon1 + 2; //Uses POINTER ARITHMETIC
   PTR_2_SFR pOcxR = pOcxCon1 + 3;     
//Each time we [re]write 'OCM = 0b100' we see a new pulse, even if we keep writing b100.
//Here we want to change pulse width, so we have to write to OCM twice (firstly to disable it)               
   *pOcxCon1 &= ~0x07; //Disable while we adjust timings
   *pOcxR = pulseDelay; //Can OC1R be 0?
   *pOcxRS = pulseDelay+pulseWidth;
   *pOcxCon1 |= 0xF4; //Trip a new pulse <---
}//OneShot
 

void main(void)
{
   OneShotCfg(&OC2CON1);
   for(;;)
   {
      Nop();
      Nop();
      OneShot(&OC2CON1, 0x00A0, 4*0x00A0); //32 MHz Tck-->16MHzTCy, 62.5nS. Want 10uS delay, 40uS pulse.
//How can the simulator monitor pin 22, OC2/RB11 while under CCP control ?
    /*while(! _LATB11) //NEVER CHANGES TO 1
      {
         Nop();
         Nop();
      } */
      Nop(); <---
      while(! _OC2IF)
      {
         Nop();
         Nop();
      }
      Nop(); <---
      _OC2IF = 0;
   }//for
}//main
举报

古克满

2019-7-30 08:28:41
是的,这个代码可以工作;然而,为了控制ESC,脉冲应该是反向的-高脉冲(对于单次ESC-在125至250用户之间),然后是低输出的“间隙”。不确定你的MCU,但PIC24EP允许反转OCX输出。“间隙”的长度可以是可变的,一般不超过20毫秒,因此,如果没有来自“控制回路”的更新,则应该自动触发新的单击操作。我在为OCx模块驱动定时器的同一个定时器上使用中断。我的另一个挑战是确保当我需要与Output-Compare模块通信时,我不会在控制循环中引入任何“等待”——如果模块对脉冲“忙碌”,则新值被保存,并且关于监控OCx的输出,您可以尝试将作为OCx输出使用的相同管脚分配给ICx中的一个作为输入——我相信这是PPS允许的——在一个示例中,它是一种为UART modu实现“闭环通信”的推荐方法。莱斯,问候,亚历克斯

以上来自于百度翻译


      以下为原文

    Yep, this code would work; however, for controlling ESC the pulse should be reversed - high pulse (for one-shot ESCs - between 125 and 250 usec) and then the "gap" of low output. Not sure about your MCU, but PIC24EP allows to invert OCx output. The"gap" could be of variable length - typically, not exceeding 20 msec, so if there was no updates from the Control Loop, the new One-shot should be triggered automatically. I use interrupt on the same timer that drives timer for OCx modules.
 
My other challenge was to make sure that that I do not introduce any "waits" into my control loop when I need to communicate with the Output-Compare modules - if the modules are 'busy" with the pulse, the new values just saved and will be picked up on the next cycle.
 
As to monitoring the output of OCx, you may try to assign the same pin that you use as OCx output to one of the ICx as input - I believe this is allowed by PPS - in one of the samples it was a recommended way to implement "closed loop communication" for UART modules.
 
Regards,
--Alex
举报

更多回帖

发帖
×
20
完善资料,
赚取积分