{"id":6113,"date":"2018-07-29T19:07:07","date_gmt":"2018-07-29T17:07:07","guid":{"rendered":"https:\/\/playembedded.org\/?p=6113"},"modified":"2023-04-07T12:34:00","modified_gmt":"2023-04-07T10:34:00","slug":"stm32-tim-chibios-pwm","status":"publish","type":"post","link":"https:\/\/playembedded.org\/blog\/stm32-tim-chibios-pwm\/","title":{"rendered":"PWM in hardware with STM32 Timer and ChibiOS"},"content":{"rendered":"<div class=\"wp-block-group important-notice\"><div class=\"wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained\">\n<p class=\"notice-title\">Important notice<\/p>\n\n\n\n<p>Please note that the information contained in this article may no longer be accurate or useful due to changes in the ChibiOS codebase and ChibiStudio.<\/p>\n\n\n\n<p>Unfortunately, at the moment there is no direct replacement for this article. However, we are in the process of rewriting the series with updated information, a better writing style, and improved ease of use. The new series will also be completed with a larger set of examples and exercises to help you better understand how to use ChibiOS and ChibiStudio.<\/p>\n\n\n\n<p>We welcome any feedback or suggestions you may have, so please don&rsquo;t hesitate to <a href=\"https:\/\/playembedded.org\/contact-us\/\" data-type=\"page\" data-id=\"3156\">get in touch with us<\/a>. <\/p>\n\n\n\n<p>Thank you for your interest in our content.<\/p>\n<\/div><\/div>\n\n\n<h3 id=\"1_The_Pulse_Width_Modulation\" class=\"level_1\">The Pulse Width Modulation<\/h3>\n<p>The <strong>Pulse Width Modulation<\/strong> (also known as <strong>PWM<\/strong>) is a digital modulation technique which uses duty-cycle of square waves to encode information. In communication field PWM surrendered to more advanced communication technique which uses more complex waveforms showing better noise rejection ratio and less transmission errors at highest data rate. Nevertheless, PWM is still used in infrared communication where data rate is very low but transmitter and receiver are cheap.<\/p>\n<p>Anyway low rate communication is not the most relevant use case of PWM which is widely used in many fields very far from communication: maybe the most know use case is the control of Switching Mode Power Supply especially when used to drive inductive load like motors. Another interesting use case for PWM is light dimming especially with LEDs.<\/p>\n<p>The PWM is a waveform which can basically switch between two states with a negligible raise\/fall time and a constant period. The two states are usually two different voltage levels V<sub>high<\/sub> and V<sub>low<\/sub>. In a general sense the two possible states can be named <strong>active&nbsp;<\/strong>and <strong>idle.&nbsp;<\/strong><\/p>\n<p>The <strong>Period&nbsp;<\/strong>can be thus defined as<\/p>\n<p style=\"text-align: center;\"><img decoding=\"async\" src=\"https:\/\/s0.wp.com\/latex.php?latex=T%3Dt_%7Bactive%7D%2Bt_%7Bidle%7D&amp;bg=ffffff&amp;fg=82807a&amp;s=2&amp;c=20201002\" alt=\"T=t_{active}+t_{idle}\" class=\"latex\"><\/p>\n<p>The&nbsp;<strong>Duty Cycle<\/strong>&nbsp;is a ratio often expressed as percentage which can be defined as<\/p>\n<p style=\"text-align: center;\"><img decoding=\"async\" src=\"https:\/\/s0.wp.com\/latex.php?latex=Duty%3D%5Cfrac%7Bt_%7Bactive%7D%7D%7Bt_%7Bactive%7D%2Bt_%7Bidle%7D%7D%3D%5Cfrac%7Bt_%7Bactive%7D%7D%7BT%7D&amp;bg=ffffff&amp;fg=82807a&amp;s=2&amp;c=20201002\" alt=\"Duty=\\frac{t_{active}}{t_{active}+t_{idle}}=\\frac{t_{active}}{T}\" class=\"latex\"><\/p>\n<p>It is not guaranteed that the active state would be V<sub>high<\/sub> and idle V<sub>low<\/sub>&nbsp;(as example if we are using the PWM to switch a P-channel mosfet). Thus we can have two different types of PWM: the Active High (high is active, low is idle) and the Active Low (low is active, high is idle).<\/p>\n<figure id=\"attachment_6189\" aria-describedby=\"caption-attachment-6189\" style=\"width: 1144px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/playembedded.org\/wp-content\/uploads\/2018\/07\/PWM-active-high_low.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-6189\" src=\"https:\/\/playembedded.org\/wp-content\/uploads\/2018\/07\/PWM-active-high_low.png\" alt=\"\" width=\"1144\" height=\"848\" srcset=\"https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2018\/07\/PWM-active-high_low.png 1144w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2018\/07\/PWM-active-high_low-150x111.png 150w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2018\/07\/PWM-active-high_low-300x222.png 300w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2018\/07\/PWM-active-high_low-1024x759.png 1024w\" sizes=\"auto, (max-width: 1144px) 100vw, 1144px\"><\/a><figcaption id=\"caption-attachment-6189\" class=\"wp-caption-text\">A PWM having a duty cycle of 37.5% active high (top) active low (bottom)<\/figcaption><\/figure>\n<p>The most exploited property of the PWM signal is definitely the proportionality between Duty Cycle and signal mean value.&nbsp;Let us consideran active high PWM like that shown in figure 2. In this case the signal mean value for a periodic signal is defined as<\/p>\n<p style=\"text-align: center;\"><img decoding=\"async\" src=\"https:\/\/s0.wp.com\/latex.php?latex=%5Cbar%7Bx%7D%28t%29%3D%5Cfrac%7B1%7D%7BT%7D%5Cint_0%5ET+%5C%21+x%28t%29+%5C%2C+%5Cmathrm%7Bd%7Dt&amp;bg=ffffff&amp;fg=82807a&amp;s=2&amp;c=20201002\" alt=\"\\bar{x}(t)=\\frac{1}{T}\\int_0^T \\! x(t) \\, \\mathrm{d}t\" class=\"latex\"><\/p>\n<p>This integral can be split and solved easily<\/p>\n<p style=\"text-align: center;\"><img decoding=\"async\" src=\"https:\/\/s0.wp.com\/latex.php?latex=%5Cbar%7Bx%7D%28t%29%3D%5Cfrac%7B1%7D%7BT%7D%5Cint_0%5E%7BDT%7D+%5C%21+V_%7Bhigh%7D+%5C%2C+%5Cmathrm%7Bd%7Dt%2B%5Cfrac%7B1%7D%7BT%7D%5Cint_%7BDT%7D%5E%7BT%7D+%5C%21+V_%7Blow%7D+%5C%2C+%5Cmathrm%7Bd%7Dt&amp;bg=ffffff&amp;fg=82807a&amp;s=2&amp;c=20201002\" alt=\"\\bar{x}(t)=\\frac{1}{T}\\int_0^{DT} \\! V_{high} \\, \\mathrm{d}t+\\frac{1}{T}\\int_{DT}^{T} \\! V_{low} \\, \\mathrm{d}t\" class=\"latex\"><\/p>\n<p style=\"text-align: center;\"><img decoding=\"async\" src=\"https:\/\/s0.wp.com\/latex.php?latex=%5Cbar%7Bx%7D%28t%29%3DD%5Ccdot%7BV_%7Bhigh%7D%7D%2B%281+-+D%29%5Ccdot%7BV_%7Blow%7D%7D&amp;bg=ffffff&amp;fg=82807a&amp;s=2&amp;c=20201002\" alt=\"\\bar{x}(t)=D\\cdot{V_{high}}+(1 - D)\\cdot{V_{low}}\" class=\"latex\"><\/p>\n<p style=\"text-align: center;\"><img decoding=\"async\" src=\"https:\/\/s0.wp.com\/latex.php?latex=%5Cbar%7Bx%7D%28t%29%3DV_%7Blow%7D%2BD%5Ccdot%7B%28V_%7Bhigh%7D-V_%7Blow%7D%29%7D&amp;bg=ffffff&amp;fg=82807a&amp;s=2&amp;c=20201002\" alt=\"\\bar{x}(t)=V_{low}+D\\cdot{(V_{high}-V_{low})}\" class=\"latex\"><\/p>\n<figure id=\"attachment_6191\" aria-describedby=\"caption-attachment-6191\" style=\"width: 1144px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/playembedded.org\/wp-content\/uploads\/2018\/07\/PWM-details.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-6191\" src=\"https:\/\/playembedded.org\/wp-content\/uploads\/2018\/07\/PWM-details.png\" alt=\"\" width=\"1144\" height=\"364\" srcset=\"https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2018\/07\/PWM-details.png 1144w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2018\/07\/PWM-details-150x48.png 150w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2018\/07\/PWM-details-300x95.png 300w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2018\/07\/PWM-details-1024x326.png 1024w\" sizes=\"auto, (max-width: 1144px) 100vw, 1144px\"><\/a><figcaption id=\"caption-attachment-6191\" class=\"wp-caption-text\">An active high PWM with details<\/figcaption><\/figure>\n<p>In the particular case were low state is the reference ground the mean value becomes<\/p>\n<p style=\"text-align: center;\"><img decoding=\"async\" src=\"https:\/\/s0.wp.com\/latex.php?latex=%5Cbar%7Bx%7D%28t%29%3DD%5Ccdot%7BV_%7Bhigh%7D%7D&amp;bg=ffffff&amp;fg=82807a&amp;s=3&amp;c=20201002\" alt=\"\\bar{x}(t)=D\\cdot{V_{high}}\" class=\"latex\"><\/p>\n<h3 id=\"2_The_STM32_Timer\" class=\"level_1\">The STM32 Timer<\/h3>\n<p><strong>STM32 Timer<\/strong> (also abbreviated as <strong>TIM<\/strong>) is a peripheral which allows to generate PWM signals in hardware and this means once the Timer have been configured and started it can generate a PWM waveform on a certain output PIN without the intervention of the software. Actually the general purpose timer of the STM32 is a peripheral which can be used for a large variety of purposes. Mainly it can be used as reliable timing source, as example to properly schedule tasks in a RTOS like ChibiOS, or to trigger with a precise cadency the ADC peripheral. As said it can be additionally used to generate PWM signals or conversely capture square waves measuring elapsed time between two waveform edges and thus period and duty cycle.<\/p>\n<p>Each STM32 is equipped with a large amount of timers which are identified by a progressive number (TIM1, TIM2, TIM3, and so on). All the timers are independent and they do not share any resources even if they can be synchronised together.<\/p>\n<p>Some timer are known as <strong>Advanced Timers<\/strong> as they have some advanced features like the complementary outputs, programmable dead-time and break input to put the timer output in reset or known state. Such features are thought to fulfill Full Bridge control application needs. Usually those timers are the&nbsp;<strong>TIM1<\/strong> and <strong>TIM8<\/strong> but it is not guarantee they are always available on every STM32.<\/p>\n<p>The hardware itself is very complex and offers so many working mode that explain all of them in detail would be difficult and dispersive. Anyway I would like to give you some information about this peripheral and then focus on PWM output mode.<\/p>\n<h4 id=\"3_The_time_base_unit\" class=\"level_2\">The time base unit<\/h4>\n<p><span style=\"font-size: 1em;\">A timer basically is fed with a clock signal and it counts the clock pulses. As the clock speed is know, this basically allows to measure time windows. <\/span><\/p>\n<p><span style=\"font-size: 1em;\">On STM32 e<\/span><span style=\"font-size: 1em;\">ach Timer has in independent counter which can be configured to count upward or downward: basically the counter increases\/decreases its value on each clock pulse and the current value can be read accessing the register <strong>TIMx Counter<\/strong> (<strong>TIMx_CNT<\/strong>). The timer counter register has a 16-bit depth except on a limited number of timers which are equipped with a 32-bit counter register. Those timer, if available, are usually the&nbsp;<strong>TIM2<\/strong> and <strong>TIM5<\/strong>.<\/span><\/p>\n<p>A larger counter bit-depth guarantees higher resolution at given full scale. To understand what this means lets take a look to some definitions. The<strong> timer resolution<\/strong>&nbsp;is the minimum amount of time the timer is able to count. In term of time magnitude, it is equal to clock period<\/p>\n<p style=\"text-align: center;\"><img decoding=\"async\" src=\"https:\/\/s0.wp.com\/latex.php?latex=%5Cdelta+t_%7BTIM%7D%3DT_%7Bclock%7D%3D%5Cfrac%7B1%7D%7Bf_%7Bclock%7D%7D&amp;bg=ffffff&amp;fg=82807a&amp;s=2&amp;c=20201002\" alt=\"\\delta t_{TIM}=T_{clock}=\\frac{1}{f_{clock}}\" class=\"latex\"><\/p>\n<p>The <strong>timer full scale<\/strong>&nbsp;is the maximum amount of time that can be counted. It depends on timer resolution and counter depth<\/p>\n<p style=\"text-align: center;\"><img decoding=\"async\" src=\"https:\/\/s0.wp.com\/latex.php?latex=FS_%7BTIM%7D%3D%5Cdelta+t_%7BTIM%7D+%5Ccdot+2%5E%7Bdepth%7D&amp;bg=ffffff&amp;fg=82807a&amp;s=2&amp;c=20201002\" alt=\"FS_{TIM}=\\delta t_{TIM} \\cdot 2^{depth}\" class=\"latex\"><\/p>\n<p>About clock speed we should take a step back and take a look to clock distribution. On STM32 but more in general on ARM Cortex-M architecture, peripherals are interfaced to memory, core and DMA through the <strong>Advanced Peripheral Bus<\/strong> (also shortened as <strong>APB<\/strong>). The architecture of this bus is based on the <strong>ARM Advanced Microcontroller Bus Architecture<\/strong> (<strong>AMBA<\/strong>) which is an open-standard for the connection and management of subsystem in a system-on-chip. Depending on the amount of peripherals and chip size, there could be more than a APB on each micro and usually they are identified by a progressive number (<strong>APB1<\/strong>, <strong>APB2<\/strong>, <strong>APB3<\/strong> and so on). The main purposes of an APB is to provide clock distribution from the main domain (that of the core, memory and DMA) to the peripheral, as well as provide a memory-mapped register interface. As in STM32 there are dozen of timer, often they are connected on different APB and the maximum clock speed of a timer depends on which APB they are. Even more each APB has is own prescaler to run at a clock speed which is lower or equal to that from the main domain.<\/p>\n<p>Usually on STM32 at least an APB is able to run at the same speed of the ARM Cortex-M core thus at the maximum speed of the whole system. Anyway, to run at maximum speed is not always suitable for all the scenarios. Looking back to previous formula let us consider the case of a STM32F401RE and its TIM9 which is equipped with a 16-bit counter and is connected to APB2 and can be configured to run at 84MHz. At this speed we have an high time resolution which is approximately 11.9 ns but a small full scale in the order of 780 ms.<\/p>\n<p>If we cannot make use of a 32-bit timer the best way to deal with larger time windows would be to reduce clock speed. Even if APB offer a clock prescaler, changing this divider would impact all the peripherals connected to it. To ensure flexibility,&nbsp;<span style=\"font-size: 1em;\">each timer is equipped with an independent 16-bit prescaler which can be configured accessing the register <strong>TIMx prescaler<\/strong> (<strong>TIMx_PSC<\/strong>).<\/span><\/p>\n<p><span style=\"font-size: 1em;\">When a timer is running and overflows its maximum value, it rolls over to zero and continues to run. On overflow event the timer launches an interrupt request. Acting on a register known as&nbsp;<strong>Timer Auto-Reload Register <\/strong>(<strong>TIMx_ARR<\/strong>) is possible to reduce the upper limit and anticipate the rollover. This is actually true only in upperward counting mode when the counter runs from 0 to the value of the TIMx_ARR register then rolls over to 0. In downward counting mode the condition is inverted: the counter runs from the TIMx_ARR value down to 0, generates an interrupt on the underflow event and the rolls over to the TIMx_ARR value continuing to run.<\/span><\/p>\n<p>Basically once started the counter continues to run and launch interrupts with a specific frequency. Such kind of interrupt is periodic and we will refer to it as <strong>Timer Periodic Interrupt<\/strong>. The frequency of periodic interrupts (at given clock speed) can be changed acting on the Prescaler Register and on the Auto Reload Register value according to the formula<\/p>\n<p style=\"text-align: center;\"><img decoding=\"async\" src=\"https:\/\/s0.wp.com\/latex.php?latex=f_%7BIRQ%7D%3D%5Cfrac%7Bf_%7Bclk+tree%7D%7D%7BPSC+%5Ccdot+ARR%7D&amp;bg=ffffff&amp;fg=82807a&amp;s=2&amp;c=20201002\" alt=\"f_{IRQ}=\\frac{f_{clk tree}}{PSC \\cdot ARR}\" class=\"latex\"><\/p>\n<p>The <span style=\"font-size: 1em;\"><strong>TIMx_CNT <\/strong>and&nbsp;<strong>TIMx_PSC<\/strong> joint with the <strong>TIMx_ARR<\/strong>&nbsp;compose the core of the timer which is called <strong>Time Base Unit<\/strong>.<\/span><\/p>\n<h4 id=\"4_The_channels\" class=\"level_2\">The channels<\/h4>\n<p>Each STM32 timer is equipped with some repeated sub structures known as <strong>Channels<\/strong>. A timer can have up to 4 independent channels which are identified with a progressive number (<strong>TIMx_CH1<\/strong>, <strong>TIMx_CH2<\/strong>, and so on) and each channel is equipped with additional registers and an I\/O line.<\/p>\n<p>The Channel is able to launch additional interrupt when the counter reaches an intermediate value before the overflow\/underflow event. This can be done comparing the current counter value with that preloaded in an additional channel register known as&nbsp;<strong>Capture\/Compare Register <\/strong>(<strong>TIMx<\/strong><strong>_CCR1<\/strong>,<strong> TIMx_CCR2<\/strong>, etc.). Obviously such value shall be smaller than that stored in the TIMx_ARR. Such kind of interrupts are channel dependent so we will refer to them as <strong>Timer Channel Interrupts<\/strong>.<\/p>\n<p>The operation mode of a channel can be changed acting on the <strong>Capture\/Compare Mode Register <\/strong>(<strong>TIMx<\/strong><strong>_CCMR1<\/strong>,<strong> TIMx_CCMR2<\/strong>, etc.). When a channel is configured in PWM mode it is able to change the logic level of its I\/O line on periodic and channel interrupts. Just remember, to use this feature, we need to reroute some GPIO connections assigning them to TIM (take a look to&nbsp;<a href=\"https:\/\/playembedded.org\/stm32-gpio-chibios-pal\/\">GPIO article<\/a>&nbsp;if you are not familiar with).<\/p>\n<h4 id=\"5_The_PWM_mode\" class=\"level_2\">The PWM mode<\/h4>\n<p>W<span style=\"font-size: 1em;\">hen a Channel is configured in PWM mode the timer can generate a squarewave toggling its I\\O line twice per period.<\/span><\/p>\n<p>To clarify how PWM mode works let us do a simple example. Let us assume we would like to generate a PWM with a period of 1 kHz and duty cycle 47.5% active high.&nbsp;<span style=\"font-size: 1em;\">To do that let configure the timer to run at a frequency of 1MHz. We can then configure the <\/span><span style=\"font-size: 1em;\"><strong>TIMx_ARR<\/strong>&nbsp;to 1000 having a periodic interrupt every millisecond (which is equal to our PWM frequency e.g. 1 kHz) and the <strong>TIMx_CCR1<\/strong> to 475 (where the ratio TIMx_CCR1\/TIMx_ARR is equal to our PWM duty cycle).<\/span><\/p>\n<figure id=\"attachment_6330\" aria-describedby=\"caption-attachment-6330\" style=\"width: 1142px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/playembedded.org\/wp-content\/uploads\/2018\/08\/PWM-example-single.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-6330\" src=\"https:\/\/playembedded.org\/wp-content\/uploads\/2018\/08\/PWM-example-single.png\" alt=\"\" width=\"1142\" height=\"478\" srcset=\"https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2018\/08\/PWM-example-single.png 1142w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2018\/08\/PWM-example-single-150x63.png 150w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2018\/08\/PWM-example-single-300x126.png 300w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2018\/08\/PWM-example-single-1024x429.png 1024w\" sizes=\"auto, (max-width: 1142px) 100vw, 1142px\"><\/a><figcaption id=\"caption-attachment-6330\" class=\"wp-caption-text\">An example of PWM generation on a single channel<\/figcaption><\/figure>\n<p>We can also use the same timer to generate another PWM signal having the same period but a different duty cycle. As example configuring the TIMx_CCR2 to 700 we would have<\/p>\n<figure id=\"attachment_6329\" aria-describedby=\"caption-attachment-6329\" style=\"width: 1144px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/playembedded.org\/wp-content\/uploads\/2018\/08\/PWM-example-multi.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-6329\" src=\"https:\/\/playembedded.org\/wp-content\/uploads\/2018\/08\/PWM-example-multi.png\" alt=\"\" width=\"1144\" height=\"818\" srcset=\"https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2018\/08\/PWM-example-multi.png 1144w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2018\/08\/PWM-example-multi-150x107.png 150w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2018\/08\/PWM-example-multi-300x215.png 300w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2018\/08\/PWM-example-multi-1024x732.png 1024w\" sizes=\"auto, (max-width: 1144px) 100vw, 1144px\"><\/a><figcaption id=\"caption-attachment-6329\" class=\"wp-caption-text\">An example of multi PWM generation with more channel<\/figcaption><\/figure>\n<p>Thus basically with a 4 channel timer we can generate up to 4 PWM signals which have the same period and arbitrary duty cycle.<\/p>\n<blockquote><p>As each channel has independent Capture\\Compare module and shared Auto Reload Register using a timer it possible to generate a PWM signal for each channel having same period but different duty cycle.<\/p><\/blockquote>\n<p><span style=\"font-size: 1em;\">Note that when PWM is configured as active high the timer will switch high the channel line on periodic interrupt and low on channel interrupt. On contrary when it is configured in PWM mode active low the timer will set low on periodic and high on channel.<\/span><\/p>\n<p>What if we reduce the timer speed from 1MHz to 100kHz? Well we could scale all the previous value by 10 but we would lose resolution on duty cycle. In such case indeed the minimum duty we can set is 1%. Back to the previous example, with these settings, we can set duty cycle to 47% or 48% but not to 47.5%.<\/p>\n<figure id=\"attachment_6332\" aria-describedby=\"caption-attachment-6332\" style=\"width: 1144px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/playembedded.org\/wp-content\/uploads\/2018\/07\/PWM-example-resolution.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-6332\" src=\"https:\/\/playembedded.org\/wp-content\/uploads\/2018\/07\/PWM-example-resolution.png\" alt=\"\" width=\"1144\" height=\"478\" srcset=\"https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2018\/07\/PWM-example-resolution.png 1144w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2018\/07\/PWM-example-resolution-150x63.png 150w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2018\/07\/PWM-example-resolution-300x125.png 300w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2018\/07\/PWM-example-resolution-1024x428.png 1024w\" sizes=\"auto, (max-width: 1144px) 100vw, 1144px\"><\/a><figcaption id=\"caption-attachment-6332\" class=\"wp-caption-text\">The same example previously introduced with reduced resolution<\/figcaption><\/figure>\n<p>More in general the duty resolution is given by the formula<\/p>\n<p style=\"text-align: center;\"><img decoding=\"async\" src=\"https:\/\/s0.wp.com\/latex.php?latex=Duty_%7Bmin%7D%3D%5Cfrac%7Bf_%7BPWM%7D+%5Ccdot+100%7D%7Bf_%7BTimer%7D%7D%5C%25&amp;bg=ffffff&amp;fg=82807a&amp;s=2&amp;c=20201002\" alt=\"Duty_{min}=\\frac{f_{PWM} \\cdot 100}{f_{Timer}}\\%\" class=\"latex\"><\/p>\n<p>Note that on STM32 usually TIM6 and TIM7 have no channel as they are deputy to act as timing source for internal ADC and DAC.<\/p>\n<h3 id=\"6_The_ChibiOS_PWM_Driver\" class=\"level_1\">The ChibiOS PWM Driver<\/h3>\n<p>The ChibiOS PWM driver exploits the PWM output mode capability of STM32 TIM to generate PWM signal in hardware offering also the chance to intercept periodic and channel interrupts through callbacks. This allows to generate PWM on arbitrary I\/O line not necessarily internally connected to a timer channel with software intervention.<\/p>\n<blockquote><p>Each API of the PWM Driver starts with the prefix &ldquo;pwm&rdquo;. Function names are camel-case, pre-processor constants uppercase and variables lowercase.<\/p><\/blockquote>\n<p>Differently from other peripherals Timer is the same across all the STM32 subfamilies. Thus we have only TIMv1.<\/p>\n<h4 id=\"7_Different_driver_same_approach\" class=\"level_2\">Different driver same approach<\/h4>\n<p>We have presented many driver in this article series and again in PWM driver we can notice same certitudes we got used to dealing with ChibiOS. The&nbsp;<a href=\"https:\/\/playembedded.org\/chibioshal-design-an-object-oriented-approach\/\">design patterns <\/a>are constant and the PWM driver is organized like every other simple driver of ChibiOS\\HAL:<\/p>\n<ul>\n<li>The whole driver subsystem can be enabled disabled through the proper switch&nbsp;in the <em>halconf.h<\/em>. The switch name is&nbsp;<em>HAL_USE_PWM.<\/em><\/li>\n<li>To use the&nbsp;driver we have then to assign&nbsp; a TIM peripheral&nbsp; to it acting on <em>mcuconf.h.&nbsp;<\/em><\/li>\n<li>Assigning a peripheral to the driver a new object will become available: PWMD1 on TIM1 assignation, PWMD2 on TIM2 and so on.<\/li>\n<li>Each PWMDx object represent a driver which implements a Finite State Machine.<\/li>\n<li>A driver to be used shall be initialized but this is done automatically on <em>halInit()<\/em>;<\/li>\n<li>The driver shall be properly configured before to be used. This requires a call to <em>pwmStart().<\/em><\/li>\n<li>The <em>pwmStart()<\/em> receives as usual two parameters: a pointer to the driver and a pointer to its configuration.<\/li>\n<li>If the driver is not used it can be stopped through the <em>pwmStop()<\/em>.<\/li>\n<\/ul>\n<p>The following figure represent the state machine of the driver.<\/p>\n<figure id=\"attachment_6193\" aria-describedby=\"caption-attachment-6193\" style=\"width: 478px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/playembedded.org\/wp-content\/uploads\/2018\/07\/dot_inline_dotgraph_13.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-6193\" src=\"https:\/\/playembedded.org\/wp-content\/uploads\/2018\/07\/dot_inline_dotgraph_13.png\" alt=\"\" width=\"478\" height=\"145\" srcset=\"https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2018\/07\/dot_inline_dotgraph_13.png 478w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2018\/07\/dot_inline_dotgraph_13-150x46.png 150w, https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2018\/07\/dot_inline_dotgraph_13-300x91.png 300w\" sizes=\"auto, (max-width: 478px) 100vw, 478px\"><\/a><figcaption id=\"caption-attachment-6193\" class=\"wp-caption-text\">The PWM Driver state machine<\/figcaption><\/figure>\n<h4 id=\"8_Managing_peripheral_assignation_related_conflicts\" class=\"level_2\">Managing peripheral assignation related conflicts<\/h4>\n<p>The TIM peripheral can be assigned to many driver like ICU, GPT and ST. Before to assign a timer to the PWM driver, user should take care to check whereas it is already assigned to another driver.<\/p>\n<p>A special care is required by ST which stands for <strong>System Tick<\/strong>: the ChibiOS scheduler relies on hardware timer to achieve reliable timing for scheduling purposes. Thus, when available, it uses a 32-bit timer to have a larger resolution independently from clock tree configuration.<\/p>\n<p>A timer is assinged to the ST in the<em> mcuconf.h<\/em> header through the define <em>STM32_ST_USE_TIMER<\/em>. The following code as grabbed from the default demo for STM32 Nucleo-64 F401RE and here the ST uses the TIM2. This means basically PWMD2 cannot be used.<\/p>\n<pre class=\"lang:c decode:true\">\/*\n * ST driver system settings.\n *\/\n#define STM32_ST_IRQ_PRIORITY               8\n#define STM32_ST_USE_TIMER                  2<\/pre>\n<p>To assign TIM2 to the PWM we have to deassign it from ST. By default ST expects a 32-bit resolution timer, and the only alternative is the TIM5. Anyway, if we accept to lose resolution we can also reduce the ST resolution acting on <em>chconf.h <\/em>and moving&nbsp;<em>CH_CFG_ST_RESOLUTION<\/em> from 32 to 16<\/p>\n<pre class=\"lang:c decode:true\">\/**\n * @brief   System time counter resolution.\n * @note    Allowed values are 16 or 32 bits.\n *\/\n#if !defined(CH_CFG_ST_RESOLUTION)\n#define CH_CFG_ST_RESOLUTION                32\n#endif<\/pre>\n<h4 id=\"9_Configuring_the_PWM\" class=\"level_2\">Configuring the PWM<\/h4>\n<p>The PWM Driver offers a rich configuration structure which allow to properly configure the STM32 timer to work in PWM mode. These fields are aligned to what we have seen in the previous chapter.<\/p>\n<pre class=\"lang:c decode:true\">\/**\n * @brief   Type of a PWM driver configuration structure.\n *\/\ntypedef struct {\n  \/**\n   * @brief   Timer clock in Hz.\n   * @note    The low level can use assertions in order to catch invalid\n   *          frequency specifications.\n   *\/\n  uint32_t                  frequency;\n  \/**\n   * @brief   PWM period in ticks.\n   * @note    The low level can use assertions in order to catch invalid\n   *          period specifications.\n   *\/\n  pwmcnt_t                  period;\n  \/**\n   * @brief Periodic callback pointer.\n   * @note  This callback is invoked on PWM counter reset. If set to\n   *        @p NULL then the callback is disabled.\n   *\/\n  pwmcallback_t             callback;\n  \/**\n   * @brief Channels configurations.\n   *\/\n  PWMChannelConfig          channels[PWM_CHANNELS];\n  \/* End of the mandatory fields.*\/\n  \/**\n   * @brief TIM CR2 register initialization data.\n   * @note  The value of this field should normally be equal to zero.\n   *\/\n  uint32_t                  cr2;\n#if STM32_PWM_USE_ADVANCED || defined(__DOXYGEN__)\n  \/**\n   * @brief TIM BDTR (break &amp; dead-time) register initialization data.\n   * @note  The value of this field should normally be equal to zero.\n   *\/                                                                     \\\n   uint32_t                 bdtr;\n#endif\n   \/**\n    * @brief TIM DIER register initialization data.\n    * @note  The value of this field should normally be equal to zero.\n    * @note  Only the DMA-related bits can be specified in this field.\n    *\/\n   uint32_t                 dier;\n} PWMConfig;<\/pre>\n<p>The first parameter is <strong>frequency <\/strong>and represent the timer clock speed. In simple words this parameter will be used to compute the prescaler value starting from the APB frequency. Note that while the APB represent an upper limit to the reachable frequency, the timer counter depth provides an lower limit to it.<\/p>\n<p style=\"text-align: center;\"><img decoding=\"async\" src=\"https:\/\/s0.wp.com\/latex.php?latex=%5Cfrac%7Bf_%7BAPBx%7D%7D%7B2%5E%7Bdepth%7D%7D+%3C%3D+f_%7Bclock%7D+%3C%3D+f_%7BAPBx%7D&amp;bg=ffffff&amp;fg=82807a&amp;s=2&amp;c=20201002\" alt=\"\\frac{f_{APBx}}{2^{depth}} &lt;= f_{clock} &lt;= f_{APBx}\" class=\"latex\"><\/p>\n<p>ChibiOS allows to catch invalid frequency specification through the <strong>assertions<\/strong>. This debugging mechanism can be enabled in <em>chconf.h <\/em>and, in case, triggers an error at compile time when the choosen frequency is invalid.<\/p>\n<pre class=\"lang:c decode:true\">\/**\n * @brief   Debug option, consistency checks.\n * @details If enabled then all the assertions in the kernel code are\n *          activated. This includes consistency checks inside the kernel,\n *          runtime anomalies and port-defined checks.\n *\n * @note    The default is @p FALSE.\n *\/\n#if !defined(CH_DBG_ENABLE_ASSERTS)\n#define CH_DBG_ENABLE_ASSERTS               FALSE\n#endif<\/pre>\n<p>Assertions, like other debugging mechanism, should always be enabled during the development phase and disabled when the application is ready to be deployed.<\/p>\n<p>The second parameter is <strong>period <\/strong>and represent the initial PWM period expressed as clock pulses. Once that timer and PWM frequency are know, this value can easily be computed using the formula<\/p>\n<p style=\"text-align: center;\"><img decoding=\"async\" src=\"https:\/\/s0.wp.com\/latex.php?latex=period%3D%5Cfrac%7Bf_%7BTIM%7D%7D%7Bf_%7BPWM%7D%7D&amp;bg=ffffff&amp;fg=82807a&amp;s=2&amp;c=20201002\" alt=\"period=\\frac{f_{TIM}}{f_{PWM}}\" class=\"latex\"><\/p>\n<p>period is limited by <strong>TIMx_ARR<\/strong> bit depth and invalid value can be catched using assertion as well.<\/p>\n<p>The third parameter <strong>callback<\/strong> is a pointer to a callable which the driver will call on <strong>periodic interrupt<\/strong>. It can be set to NULL if unused.<\/p>\n<p>The fourth parameter is an array of structures having the same size of the available channels. This size of course depends on specific timer and each element represents the configuration of that channel<\/p>\n<pre class=\"lang:c decode:true\">\/**\n * @brief   Type of a PWM driver channel configuration structure.\n *\/\ntypedef struct {\n  \/**\n   * @brief Channel active logic level.\n   *\/\n  pwmmode_t                 mode;\n  \/**\n   * @brief Channel callback pointer.\n   * @note  This callback is invoked on the channel compare event. If set to\n   *        @p NULL then the callback is disabled.\n   *\/\n  pwmcallback_t             callback;\n  \/* End of the mandatory fields.*\/\n} PWMChannelConfig;<\/pre>\n<p>The first element of the structure is the channel mode, while the second element is again a callback&nbsp;which will be called on channel interrupt. The following code represent the available channel modes.<\/p>\n<pre class=\"lang:c decode:true \">\/**\n * @brief   Output not driven, callback only.\n *\/\n#define PWM_OUTPUT_DISABLED                     0x00U\n\n\/**\n * @brief   Positive PWM logic, active is logic level one.\n *\/\n#define PWM_OUTPUT_ACTIVE_HIGH                  0x01U\n\n\/**\n * @brief   Inverse PWM logic, active is logic level zero.\n *\/\n#define PWM_OUTPUT_ACTIVE_LOW                   0x02U<\/pre>\n<p>In addition to these parameters the driver accept also 3 additional fields which represent three registers of the timer: the <strong>Control Register 2<\/strong> (<strong>TIMx_CR2<\/strong>), the <strong>Break and Dead Time Register<\/strong> (<strong>TIMx_BDTR<\/strong>) and the <strong>DMA\/interrupt enable register<\/strong> (<strong>TIMx_DIER<\/strong>). In standard application their value can be left to 0 but note that the <strong>TIMx_BDTR<\/strong>&nbsp;is available only in Advanced Timers: because of that its field can be activated optionally setting the&nbsp;<strong>STM32_PWM_USE_ADVANCED<\/strong> preprocessor switch to TRUE&nbsp;<em>mcuconf.h<\/em>.<\/p>\n<p>The following snippet of code is an example of PWM configuration and related callback<\/p>\n<pre class=\"lang:c decode:true\">static PWMConfig pwmcfg = {\n  10000,                                    \/* 10kHz PWM clock frequency.     *\/\n  10000,                                    \/* Initial PWM period 1S.         *\/\n  NULL,                                     \/* Period callback.               *\/\n  {\n   {PWM_OUTPUT_ACTIVE_HIGH, NULL},          \/* CH1 mode and callback.         *\/\n   {PWM_OUTPUT_DISABLED, NULL},             \/* CH2 mode and callback.         *\/\n   {PWM_OUTPUT_DISABLED, NULL},             \/* CH3 mode and callback.         *\/\n   {PWM_OUTPUT_DISABLED, NULL}              \/* CH4 mode and callback.         *\/\n  },\n  0,                                        \/* Control Register 2.            *\/\n  0                                         \/* DMA\/Interrupt Enable Register. *\/\n};<\/pre>\n<p>This configuration sets the timer clock to 10 kHz, the PWM initial period to 1S, enables only the Channel 1. The PWM configuration is used on <em>pwmStart <\/em>specifying&nbsp;which PWM Driver we are actually going to use.<\/p>\n<pre class=\"lang:c decode:true\">\/**\n * @brief   Configures and activates the PWM peripheral.\n * @note    Starting a driver that is already in the @p PWM_READY state\n *          disables all the active channels.\n *\n * @param[in] pwmp      pointer to a @p PWMDriver object\n * @param[in] config    pointer to a @p PWMConfig object\n *\n * @api\n *\/\nvoid pwmStart(PWMDriver *pwmp, const PWMConfig *config) {\n  ...\n}<\/pre>\n<p>As instance the following code is using the configuration to setup the PWM Driver 1 which relies on TIM1.<\/p>\n<pre class=\"lang:c decode:true\">pwmStart(&amp;PWMD1, &amp;pwmcfg);<\/pre>\n<p>PWM driver configuration can be <a href=\"https:\/\/playembedded.org\/chibioshal-design-an-object-oriented-approach\/#Changing_configuration_on_the_fly\">changed on the fly<\/a> restarting the driver with a new configuration.<\/p>\n<h4 id=\"10_Enabling_Disabling_PWM_channels\" class=\"level_2\">Enabling\/Disabling PWM channels<\/h4>\n<p>The configuration structure does not specifies any information about the duty cycle. Indeed the <em>pwmStart<\/em> configures the timer but does not output anything. To see something on the I\/O line we have to enable the channel. Beside of that, remember that the related I\/O line shall be properly configured as <a href=\"https:\/\/playembedded.org\/stm32-gpio-chibios-pal\/#Alternate_mode\">Alternate Function<\/a>.<\/p>\n<p>A PWM channel can be enabled using the a specific API<\/p>\n<pre class=\"lang:c decode:true\">\/**\n * @brief   Enables a PWM channel.\n * @pre     The PWM unit must have been activated using @p pwmStart().\n * @post    The channel is active using the specified configuration.\n * @note    Depending on the hardware implementation this function has\n *          effect starting on the next cycle (recommended implementation)\n *          or immediately (fallback implementation).\n *\n * @param[in] pwmp      pointer to a @p PWMDriver object\n * @param[in] channel   PWM channel identifier (0...channels-1)\n * @param[in] width     PWM pulse width as clock pulses number\n *\n * @api\n *\/\nvoid pwmEnableChannel(PWMDriver *pwmp,\n                      pwmchannel_t channel,\n                      pwmcnt_t width) {\n\n  ...\n}<\/pre>\n<p>This API receives a pointer to the PWM driver, a channel identifier and the pulse width expressed as clock pulses. The channel identifier is a progressive number starting from 0 to N-1 whereas N is the number of available channels on that timer.<\/p>\n<p>The driver offers some helper to compute the width starting from a fraction<\/p>\n<pre class=\"lang:c decode:true \">\/**\n * @brief   Converts from fraction to pulse width.\n * @note    Be careful with rounding errors, this is integer math not magic.\n *          You can specify tenths of thousandth but make sure you have the\n *          proper hardware resolution by carefully choosing the clock source\n *          and prescaler settings, see @p PWM_COMPUTE_PSC.\n *\n * @param[in] pwmp      pointer to a @p PWMDriver object\n * @param[in] denominator denominator of the fraction\n * @param[in] numerator numerator of the fraction\n * @return              The pulse width to be passed to @p pwmEnableChannel().\n *\n * @api\n *\/\n#define PWM_FRACTION_TO_WIDTH(pwmp, denominator, numerator)                 \\\n  ((pwmcnt_t)((((pwmcnt_t)(pwmp)-&gt;period) *                                 \\\n               (pwmcnt_t)(numerator)) \/ (pwmcnt_t)(denominator)))<\/pre>\n<p>an angle<\/p>\n<pre class=\"lang:c decode:true \">\/**\n * @brief   Converts from degrees to pulse width.\n * @note    Be careful with rounding errors, this is integer math not magic.\n *          You can specify hundredths of degrees but make sure you have the\n *          proper hardware resolution by carefully choosing the clock source\n *          and prescaler settings, see @p PWM_COMPUTE_PSC.\n *\n * @param[in] pwmp      pointer to a @p PWMDriver object\n * @param[in] degrees   degrees as an integer between 0 and 36000\n * @return              The pulse width to be passed to @p pwmEnableChannel().\n *\n * @api\n *\/\n#define PWM_DEGREES_TO_WIDTH(pwmp, degrees)                                 \\\n  PWM_FRACTION_TO_WIDTH(pwmp, 36000, degrees)<\/pre>\n<p>or a percentage<\/p>\n<pre class=\"lang:c decode:true\">\/**\n * @brief   Converts from percentage to pulse width.\n * @note    Be careful with rounding errors, this is integer math not magic.\n *          You can specify tenths of thousandth but make sure you have the\n *          proper hardware resolution by carefully choosing the clock source\n *          and prescaler settings, see @p PWM_COMPUTE_PSC.\n *\n * @param[in] pwmp      pointer to a @p PWMDriver object\n * @param[in] percentage percentage as an integer between 0 and 10000\n * @return              The pulse width to be passed to @p pwmEnableChannel().\n *\n * @api\n *\/\n#define PWM_PERCENTAGE_TO_WIDTH(pwmp, percentage)                           \\\n  PWM_FRACTION_TO_WIDTH(pwmp, 10000, percentage)<\/pre>\n<p>This last is very interesting as we usually express the duty in form of percentage. As example the next snippet enables the TIM1_CH1 with a duty of 47.5%<\/p>\n<pre class=\"lang:c decode:true\">pwmEnableChannel(&amp;PWMD1, 0, PWM_PERCENTAGE_TO_WIDTH(&amp;PWMD1, 4750))\n<\/pre>\n<blockquote><p>It is possible to change the channel duty cycle on the fly calling the pwmEnableChannel repeatedly but with a different width.<\/p><\/blockquote>\n<p>To disable a channel there is another function named&nbsp;<em>pwmDisableChannel<\/em>. Such function receives only the PWM Driver and the channel identifier.<\/p>\n<pre class=\"lang:c decode:true \">\/**\n * @brief   Disables a PWM channel and its notification.\n * @pre     The PWM unit must have been activated using @p pwmStart().\n * @post    The channel is disabled and its output line returned to the\n *          idle state.\n * @note    Depending on the hardware implementation this function has\n *          effect starting on the next cycle (recommended implementation)\n *          or immediately (fallback implementation).\n *\n * @param[in] pwmp      pointer to a @p PWMDriver object\n * @param[in] channel   PWM channel identifier (0...channels-1)\n *\n * @api\n *\/\nvoid pwmDisableChannel(PWMDriver *pwmp, pwmchannel_t channel) {\n  ...\n}<\/pre>\n<h4 id=\"11_Changing_period_on_the_fly\" class=\"level_2\">Changing period on the fly<\/h4>\n<p>The PWM period can be changed on the fly with another API which overwrites the configuration applied on <em>pwmStart<\/em>.<\/p>\n<pre class=\"lang:c decode:true\">\/**\n * @brief   Changes the period the PWM peripheral.\n * @details This function changes the period of a PWM unit that has already\n *          been activated using @p pwmStart().\n * @pre     The PWM unit must have been activated using @p pwmStart().\n * @post    The PWM unit period is changed to the new value.\n * @note    If a period is specified that is shorter than the pulse width\n *          programmed in one of the channels then the behavior is not\n *          guaranteed.\n *\n * @param[in] pwmp      pointer to a @p PWMDriver object\n * @param[in] period    new cycle time in ticks\n *\n * @api\n *\/\nvoid pwmChangePeriod(PWMDriver *pwmp, pwmcnt_t period) {\n\n  ...\n}<\/pre>\n<h4 id=\"12_Callbacks_and_notifications\" class=\"level_2\">Callbacks and notifications<\/h4>\n<p>The PWM driver can trigger a callback on periodic interrupt and a callback on channel interrupt. Such kind of callbacks can be setup acting on PWM configuration. As instance the following code is identical to the previous example with the difference that here we have setup a periodic and a channel callback.<\/p>\n<pre class=\"lang:c decode:true\">static void pwmpcb(PWMDriver *pwmp) {\n\n  (void)pwmp;\n  palClearPad(GPIOA, GPIOA_LED_GREEN);\n}\n\nstatic void pwmc1cb(PWMDriver *pwmp) {\n\n  (void)pwmp;\n  palSetPad(GPIOA, GPIOA_LED_GREEN);\n}\n\nstatic PWMConfig pwmcfg = {\n  10000,                                    \/* 10kHz PWM clock frequency.     *\/\n  10000,                                    \/* Initial PWM period 1S.         *\/\n  pwmpcb,                                   \/* Period callback.               *\/\n  {\n   {PWM_OUTPUT_ACTIVE_HIGH, pwmc1cb},       \/* CH1 mode and callback.         *\/\n   {PWM_OUTPUT_DISABLED, NULL},             \/* CH2 mode and callback.         *\/\n   {PWM_OUTPUT_DISABLED, NULL},             \/* CH3 mode and callback.         *\/\n   {PWM_OUTPUT_DISABLED, NULL}              \/* CH4 mode and callback.         *\/\n  },\n  0,                                        \/* Control Register 2.            *\/\n  0                                         \/* DMA\/Interrupt Enable Register. *\/\n};<\/pre>\n<p>More in detail this configuration exploits callbacks to generate a PWM signal on the an arbitrary GPIO not necessarily interconnected to the timer. In this case the pin have been previously configured as output push-pull.<\/p>\n<p>Note that even if a callback has been set up it is not necessarily called. To enable callbacks we should enable notification using the following APIs<\/p>\n<pre class=\"lang:c decode:true\">\/**\n * @brief   Enables the periodic activation edge notification.\n * @pre     The PWM unit must have been activated using @p pwmStart().\n * @note    If the notification is already enabled then the call has no effect.\n *\n * @param[in] pwmp      pointer to a @p PWMDriver object\n *\n * @api\n *\/\nvoid pwmEnablePeriodicNotification(PWMDriver *pwmp) {\n  ...\n}\n\n\/**\n * @brief   Disables the periodic activation edge notification.\n * @pre     The PWM unit must have been activated using @p pwmStart().\n * @note    If the notification is already disabled then the call has no effect.\n *\n * @param[in] pwmp      pointer to a @p PWMDriver object\n *\n * @api\n *\/\nvoid pwmDisablePeriodicNotification(PWMDriver *pwmp) {\n  ...\n}\n\n\/**\n * @brief   Enables a channel de-activation edge notification.\n * @pre     The PWM unit must have been activated using @p pwmStart().\n * @pre     The channel must have been activated using @p pwmEnableChannel().\n * @note    If the notification is already enabled then the call has no effect.\n *\n * @param[in] pwmp      pointer to a @p PWMDriver object\n * @param[in] channel   PWM channel identifier (0...channels-1)\n *\n * @api\n *\/\nvoid pwmEnableChannelNotification(PWMDriver *pwmp, pwmchannel_t channel) {\n  ...\n}\n\n\/**\n * @brief   Disables a channel de-activation edge notification.\n * @pre     The PWM unit must have been activated using @p pwmStart().\n * @pre     The channel must have been activated using @p pwmEnableChannel().\n * @note    If the notification is already disabled then the call has no effect.\n *\n * @param[in] pwmp      pointer to a @p PWMDriver object\n * @param[in] channel   PWM channel identifier (0...channels-1)\n *\n * @api\n *\/\nvoid pwmDisableChannelNotification(PWMDriver *pwmp, pwmchannel_t channel) {\n  ...\n}<\/pre>\n<h3 class=\"entry-title post-title level_1\" id=\"13_Further_readings_and_Hands_on\">Further readings and Hands-on<\/h3>\n<p>We have already planned a collection of example and exercises for the PWM driver. If you are interested in follow us on <a href=\"http:\/\/facebook.com\/playembedded\">Facebook<\/a> to be updated on our articles. Anyway, at this moment you could refer to the PWM-ICU demo under <em>testhal<\/em> to give it a try.<\/p>\n<p>PWM is used by many devices and we wrote many article about them. For sure the quick reference could be:<\/p>\n<ul>\n<li><a href=\"https:\/\/playembedded.org\/getting-started-with-mikroe-buzz-click-using-chibioshal\/\">Getting started with mikroe BUZZ Click using ChibiOS\/HAL<\/a><\/li>\n<\/ul>\n<h3 id=\"14_Previous_and_next\" class=\"level_1\">Previous and next<\/h3>\n<p>This article is part of a series of articles which are meant to be tutorials. I have composed them to be read in sequence. Here the previous and next article of this series:<\/p>\n<ul>\n<li><a href=\"https:\/\/playembedded.org\/stm32-adc-chibios\/\">Using STM32 ADC with ChibiOS ADC Driver (Next)<\/a><\/li>\n<li><a href=\"https:\/\/playembedded.org\/stm32-spi-chibios\/\">Using STM32 SPI with ChibiOS (Previous)<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Important notice Please note that the information contained in this article may no longer be accurate or useful due to changes in the ChibiOS codebase and ChibiStudio. Unfortunately, at the moment there is no direct replacement for this article. However, we are in the process of rewriting the series with updated information, a better writing [&hellip;]<\/p>\n","protected":false},"author":3,"featured_media":6203,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1287],"tags":[],"coauthors":[241],"class_list":["post-6113","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-getting-started-obsoleted"],"views":39707,"jetpack_featured_media_url":"https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2018\/07\/PWM.jpg","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/posts\/6113","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/comments?post=6113"}],"version-history":[{"count":0,"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/posts\/6113\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/media\/6203"}],"wp:attachment":[{"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/media?parent=6113"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/categories?post=6113"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/tags?post=6113"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/coauthors?post=6113"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}