lupyuen.org/articles/led.html

619 lines
No EOL
37 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="rustdoc">
<title>Control PineCone BL602 RGB LED with GPIO and PWM</title>
<!-- Begin scripts/articles/*-header.html: Article Header for Custom Markdown files processed by rustdoc, like chip8.md -->
<meta property="og:title"
content="Control PineCone BL602 RGB LED with GPIO and PWM"
data-rh="true">
<meta property="og:description"
content="Explore the BL602 GPIO and PWM Demo Firmware... And how they call the GPIO and PWM Hardware Abstraction Layer"
data-rh="true">
<meta property="og:image"
content="https://lupyuen.github.io/images/led-title.jpg">
<meta property="og:type"
content="article" data-rh="true">
<link rel="canonical" href="https://lupyuen.org/articles/led.html" />
<!-- End scripts/articles/*-header.html -->
<!-- Begin scripts/rustdoc-header.html: Header for Custom Markdown files processed by rustdoc, like chip8.md -->
<link rel="alternate" type="application/rss+xml" title="RSS Feed for lupyuen" href="/rss.xml" />
<link rel="stylesheet" type="text/css" href="../normalize.css">
<link rel="stylesheet" type="text/css" href="../rustdoc.css" id="mainThemeStyle">
<link rel="stylesheet" type="text/css" href="../dark.css">
<link rel="stylesheet" type="text/css" href="../light.css" id="themeStyle">
<link rel="stylesheet" type="text/css" href="../prism.css">
<script src="../storage.js"></script><noscript>
<link rel="stylesheet" href="../noscript.css"></noscript>
<link rel="shortcut icon" href="../favicon.ico">
<style type="text/css">
#crate-search {
background-image: url("../down-arrow.svg");
}
</style>
<!-- End scripts/rustdoc-header.html -->
</head>
<body class="rustdoc">
<!--[if lte IE 8]>
<div class="warning">
This old browser is unsupported and will most likely display funky
things.
</div>
<![endif]-->
<!-- Begin scripts/rustdoc-before.html: Pre-HTML for Custom Markdown files processed by rustdoc, like chip8.md -->
<!-- Begin Theme Picker -->
<div class="theme-picker" style="left: 0"><button id="theme-picker" aria-label="Pick another theme!"><img src="../brush.svg"
width="18" alt="Pick another theme!"></button>
<div id="theme-choices"></div>
</div>
<!-- Theme Picker -->
<!-- End scripts/rustdoc-before.html -->
<h1 class="title">Control PineCone BL602 RGB LED with GPIO and PWM</h1>
<nav id="rustdoc"><ul>
<li><a href="#control-rgb-led-with-gpio" title="Control RGB LED with GPIO">1 Control RGB LED with GPIO</a><ul></ul></li>
<li><a href="#gpio-exercise-for-the-reader" title="GPIO Exercise for The Reader">2 GPIO Exercise for The Reader</a><ul></ul></li>
<li><a href="#how-it-works-bl602-gpio" title="How It Works: BL602 GPIO">3 How It Works: BL602 GPIO</a><ul>
<li><a href="#enable-gpio" title="Enable GPIO">3.1 Enable GPIO</a><ul></ul></li>
<li><a href="#read-and-write-gpio" title="Read and Write GPIO">3.2 Read and Write GPIO</a><ul></ul></li>
<li><a href="#gpio-interrupts" title="GPIO Interrupts">3.3 GPIO Interrupts</a><ul></ul></li>
<li><a href="#gpio-device-tree" title="GPIO Device Tree">3.4 GPIO Device Tree</a><ul></ul></li></ul></li>
<li><a href="#from-gpio-to-pulse-width-modulation-pwm" title="From GPIO to Pulse Width Modulation (PWM)">4 From GPIO to Pulse Width Modulation (PWM)</a><ul></ul></li>
<li><a href="#control-rgb-led-with-pwm" title="Control RGB LED with PWM">5 Control RGB LED with PWM</a><ul></ul></li>
<li><a href="#how-it-works-bl602-pwm" title="How It Works: BL602 PWM">6 How It Works: BL602 PWM</a><ul>
<li><a href="#initialise-pwm" title="Initialise PWM">6.1 Initialise PWM</a><ul></ul></li>
<li><a href="#pwm-frequency-and-duty-cycle" title="PWM Frequency and Duty Cycle">6.2 PWM Frequency and Duty Cycle</a><ul></ul></li>
<li><a href="#pwm-operation" title="PWM Operation">6.3 PWM Operation</a><ul></ul></li>
<li><a href="#pwm-device-tree" title="PWM Device Tree">6.4 PWM Device Tree</a><ul></ul></li></ul></li>
<li><a href="#bl602-pwm-internals" title="BL602 PWM Internals">7 BL602 PWM Internals</a><ul></ul></li>
<li><a href="#whats-next" title="Whats Next">8 Whats Next</a><ul></ul></li>
<li><a href="#appendix-fix-bl602-demo-firmware-for-macos" title="Appendix: Fix BL602 Demo Firmware for macOS">9 Appendix: Fix BL602 Demo Firmware for macOS</a><ul></ul></li></ul></nav><p><img src="https://lupyuen.github.io/images/led-title.jpg" alt="PineCone BL602 RISC-V Evaluation Board connected to Pinebook Pro" /></p>
<p><em>PineCone BL602 RISC-V Evaluation Board connected to Pinebook Pro</em></p>
<p>📝 <em>6 Jan 2021</em></p>
<p>Today we shall take control of <strong>PineCones Onboard RGB LED</strong> in two ways…</p>
<ol>
<li>
<p><strong>GPIO</strong></p>
</li>
<li>
<p><strong>Pulse Width Modulation (PWM)</strong></p>
</li>
</ol>
<p>Well do this with the <strong>GPIO and PWM Demo Firmware</strong> from the <a href="https://github.com/lupyuen/bl_iot_sdk"><strong>BL602 IoT SDK</strong></a>.</p>
<p>Through the Demo Firmware we shall learn to call <strong>BL602s Hardware Abstraction Layer</strong> in C to perform GPIO and PWM Functions.</p>
<p>If youre new to PineCone BL602, check out my article…</p>
<ul>
<li><a href="https://lupyuen.github.io/articles/pinecone"><strong>“Quick Peek of PineCone BL602 RISC-V Evaluation Board”</strong></a></li>
</ul>
<p><img src="https://lupyuen.github.io/images/led-rgb.png" alt="PineCone RGB LED Schematic" /></p>
<p><em>PineCone RGB LED Schematic</em></p>
<h1 id="control-rgb-led-with-gpio"><a class="doc-anchor" href="#control-rgb-led-with-gpio">§</a>1 Control RGB LED with GPIO</h1>
<p>According to the <a href="https://github.com/pine64/bl602-docs/blob/main/mirrored/Pine64%20BL602%20EVB%20Schematic%20ver%201.1.pdf">PineCone Schematics</a>, the onboard RGB LED is connected to these GPIO Pins…</p>
<div><table><thead><tr><th style="text-align: left">LED</th><th style="text-align: left">GPIO Pin</th></tr></thead><tbody>
<tr><td style="text-align: left">Blue</td><td style="text-align: left">GPIO 11</td></tr>
<tr><td style="text-align: left">Red</td><td style="text-align: left">GPIO 17</td></tr>
<tr><td style="text-align: left">Green</td><td style="text-align: left">GPIO 14</td></tr>
</tbody></table>
</div>
<p>Lets flash the <strong>GPIO Demo</strong> from the BL602 IoT SDK and interact with the above GPIO Pins…</p>
<ol>
<li>
<p>Download the <strong>BL602 Demo Firmware Binaries</strong></p>
<ul>
<li><a href="https://github.com/lupyuen/bl_iot_sdk/releases/download/v1.0.0/customer_app.zip"><strong>BL602 Demo Firmware Binaries</strong>: <code>customer_app.zip</code></a></li>
</ul>
</li>
<li>
<p>Unzip <code>customer_app.zip</code>. Look for the file…</p>
<div class="example-wrap"><pre class="language-text"><code>sdk_app_gpio/build_out/sdk_app_gpio.bin</code></pre></div></li>
<li>
<p>Flash <code>sdk_app_gpio.bin</code> to PineCone. Follow the instructions in the article…</p>
<ul>
<li><a href="https://lupyuen.github.io/articles/flash"><strong>“Flashing Firmware to PineCone BL602”</strong></a></li>
</ul>
<p>After flashing, flip the <strong>PineCone Jumper (IO 8)</strong> to the <strong><code>L</code> Position</strong> <a href="https://lupyuen.github.io/images/pinecone-jumperl.jpg">(Like this)</a></p>
<p>Press the Reset Button.</p>
</li>
<li>
<p>Connect to PineCone…</p>
<p><strong>For Linux:</strong></p>
<div class="example-wrap"><pre class="language-bash"><code>screen /dev/ttyUSB0 2000000</code></pre></div>
<p><strong>For macOS:</strong> Use CoolTerm (<a href="https://lupyuen.github.io/articles/flash#watch-the-firmware-run">See this</a>)</p>
<p><strong>For Windows:</strong> Use <code>putty</code> (<a href="https://lupyuen.github.io/articles/flash#watch-the-firmware-run">See this</a>)</p>
<p><strong>Alternatively:</strong> Use the Web Serial Terminal (<a href="https://lupyuen.github.io/articles/flash#watch-the-firmware-run">See this</a>)</p>
<p><a href="https://lupyuen.github.io/articles/flash#watch-the-firmware-run">More details on connecting to BL602</a></p>
</li>
<li>
<p>Press the <strong>RST Button</strong> on PineCone to restart the firmware.</p>
<p>We should see this…</p>
<p><img src="https://lupyuen.github.io/images/led-gpio1.png" alt="BL602 GPIO Demo" /></p>
</li>
<li>
<p>Press <code>Enter</code> to reveal the command prompt.</p>
<p>Enter <code>help</code> to see the commands…</p>
<p><img src="https://lupyuen.github.io/images/led-gpio2.png" alt="BL602 GPIO Demo Commands" /></p>
</li>
<li>
<p>Enter these commands to set GPIO 11 (Blue), 14 (Green), 17 (Red) to output (no pullup, no pulldown)…</p>
<div class="example-wrap"><pre class="language-bash"><code>gpio-func 11 0 0 0
gpio-func 14 0 0 0
gpio-func 17 0 0 0</code></pre></div></li>
<li>
<p>Switch off the 3 LEDs (1 = Off)…</p>
<div class="example-wrap"><pre class="language-bash"><code>gpio-set 11 1
gpio-set 14 1
gpio-set 17 1</code></pre></div></li>
<li>
<p>Switch on and off each of the 3 LEDs: Blue, Green, Red (0 = On, 1 = Off)…</p>
<div class="example-wrap"><pre class="language-bash"><code>gpio-set 11 0
gpio-set 11 1
gpio-set 14 0
gpio-set 14 1
gpio-set 17 0
gpio-set 17 1</code></pre></div></li>
<li>
<p>To exit <code>screen</code>, press <code>Ctrl-A</code> then <code>k</code> then <code>y</code></p>
</li>
</ol>
<p><a href="https://youtu.be/yaXsfM1ne4w">Watch the GPIO Demo Video on YouTube</a></p>
<p><img src="https://lupyuen.github.io/images/led-jumper.png" alt="PineCone Jumper Schematic" /></p>
<p><em>PineCone Jumper Schematic</em></p>
<h1 id="gpio-exercise-for-the-reader"><a class="doc-anchor" href="#gpio-exercise-for-the-reader">§</a>2 GPIO Exercise for The Reader</h1>
<p>According to the <a href="https://github.com/pine64/bl602-docs/blob/main/mirrored/Pine64%20BL602%20EVB%20Schematic%20ver%201.1.pdf">PineCone Schematics</a>, the onboard jumper is connected to GPIO 8.</p>
<p>Can we use this command to read the jumper?</p>
<div class="example-wrap"><pre class="language-bash"><code>gpio-get 8</code></pre></div>
<p>Flip the jumper and check whether the value changes.</p>
<p>Remember to use this command to configure GPIO 8…</p>
<div class="example-wrap"><pre class="language-bash"><code>gpio-func 8 1 PULLUP PULLDOWN</code></pre></div>
<ul>
<li><code>8</code> is the GPIO Number</li>
<li><code>1</code> to configure the GPIO for Input (instead of output)</li>
<li><code>PULLUP</code> is <code>0</code> for No Pullup, <code>1</code> for Pullup</li>
<li><code>PULLDOWN</code> is <code>0</code> for No Pulldown, <code>1</code> for Pulldown</li>
</ul>
<p>Please lemme know!</p>
<h1 id="how-it-works-bl602-gpio"><a class="doc-anchor" href="#how-it-works-bl602-gpio">§</a>3 How It Works: BL602 GPIO</h1>
<p>The GPIO Demo Firmware calls the GPIO Functions provided by the <strong>BL602 Hardware Abstraction Layer (HAL)</strong>.</p>
<p>Lets look at the BL602 GPIO Functions called by the GPIO Demo Firmware: <a href="https://github.com/lupyuen/bl_iot_sdk/tree/master/customer_app/sdk_app_gpio"><code>sdk_app_gpio.bin</code></a></p>
<h2 id="enable-gpio"><a class="doc-anchor" href="#enable-gpio">§</a>3.1 Enable GPIO</h2>
<p>To designate a GPIO Pin for input or output, we call these GPIO HAL Functions: <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/bl_gpio.h"><code>bl_gpio.h</code></a></p>
<div class="example-wrap"><pre class="language-c"><code>int bl_gpio_enable_output(uint8_t pin, uint8_t pullup, uint8_t pulldown);
int bl_gpio_enable_input( uint8_t pin, uint8_t pullup, uint8_t pulldown);</code></pre></div>
<ul>
<li>
<p><code>pin</code> is the GPIO Pin Number, so <code>pin=0</code> refers to GPIO 0.</p>
</li>
<li>
<p><code>pullup</code> is set to 1 if the pin should be pulled up electrically, 0 otherwise.</p>
</li>
<li>
<p><code>pulldown</code> is set to 1 if the pin should be pulled down electrically, 0 otherwise.</p>
</li>
</ul>
<p>Check out this sample code for GPIO Output…</p>
<ul>
<li>
<p><a href="https://lupyuen.github.io/articles/rust#bl602-blinky-in-c"><strong>“BL602 Blinky in C”</strong></a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io/articles/spi#configure-chip-select-pin-as-gpio-output-pin"><strong>“Configure GPIO Output Pin”</strong></a></p>
</li>
</ul>
<h2 id="read-and-write-gpio"><a class="doc-anchor" href="#read-and-write-gpio">§</a>3.2 Read and Write GPIO</h2>
<p>To read or write a GPIO Pin, we call these GPIO HAL Functions: <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/bl_gpio.h"><code>bl_gpio.h</code></a></p>
<div class="example-wrap"><pre class="language-c"><code>int bl_gpio_output_set(uint8_t pin, uint8_t value);
int bl_gpio_input_get( uint8_t pin, uint8_t *value);
int bl_gpio_input_get_value(uint8_t pin);</code></pre></div>
<ul>
<li>
<p><code>pin</code> is the GPIO Pin Number.</p>
</li>
<li>
<p><code>value</code> is the value to be read or written (0=Low, 1=High).</p>
</li>
<li>
<p><code>bl_gpio_input_get</code> stores the value read at the pointer passed in.</p>
</li>
</ul>
<p>Check out this sample code for writing to GPIO…</p>
<ul>
<li>
<p><a href="https://lupyuen.github.io/articles/spi#set-chip-select-to-low"><strong>“Set GPIO To Low”</strong></a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io/articles/spi#set-chip-select-to-high"><strong>“Set GPIO To High”</strong></a></p>
</li>
</ul>
<h2 id="gpio-interrupts"><a class="doc-anchor" href="#gpio-interrupts">§</a>3.3 GPIO Interrupts</h2>
<p>To allow a GPIO Pin to trigger interrupts (like when a button is pressed), we call these GPIO HAL Functions: <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/bl_gpio.h"><code>bl_gpio.h</code></a></p>
<div class="example-wrap"><pre class="language-c"><code>int bl_gpio_int_clear( uint8_t gpioPin, uint8_t intClear);
void bl_gpio_intmask( uint8_t gpiopin, uint8_t mask);
void bl_set_gpio_intmod(uint8_t gpioPin, uint8_t intCtrlMod, uint8_t intTrgMod);
void bl_gpio_register(gpio_ctx_t *pstnode);</code></pre></div>
<p>Check the following for details on GPIO Interrupts…</p>
<ul>
<li>
<p><a href="https://lupyuen.github.io/articles/lora2#bl602-gpio-interrupts"><strong>“BL602 GPIO Interrupts”</strong></a></p>
</li>
<li>
<p><a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/bl_gpio.c"><strong>GPIO HAL Source Code: <code>bl_gpio.c</code></strong></a></p>
</li>
</ul>
<p>To see the above GPIO HAL Functions in action, check out the GPIO Demo Source Code…</p>
<ul>
<li><a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_gpio/sdk_app_gpio/demo.c"><strong>GPIO Demo Source Code: <code>demo.c</code></strong></a></li>
</ul>
<h2 id="gpio-device-tree"><a class="doc-anchor" href="#gpio-device-tree">§</a>3.4 GPIO Device Tree</h2>
<p>There is an alternative set of functions for controlling GPIO…</p>
<ul>
<li><strong>GPIO Device Tree</strong>: <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_gpio.h"><strong><code>hal_gpio.h</code></strong></a>, <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_gpio.c"><strong><code>hal_gpio.c</code></strong></a></li>
</ul>
<p>These functions are meant to be used with the <strong>BL602 Device Tree</strong>.</p>
<p><a href="https://lupyuen.github.io/articles/flash#device-tree">More about BL602 Device Tree</a></p>
<h1 id="from-gpio-to-pulse-width-modulation-pwm"><a class="doc-anchor" href="#from-gpio-to-pulse-width-modulation-pwm">§</a>4 From GPIO to Pulse Width Modulation (PWM)</h1>
<p><em>How many colours can we show on the RGB LED through GPIO?</em></p>
<p>Each GPIO Pin is binary… Either On or Off. Lets flip each LED and count the colours…</p>
<div><table><thead><tr><th style="text-align: center">Red</th><th style="text-align: center">Green</th><th style="text-align: center">Blue</th><th style="text-align: left">Colour</th></tr></thead><tbody>
<tr><td style="text-align: center">Off</td><td style="text-align: center">Off</td><td style="text-align: center">Off</td><td style="text-align: left"><strong>Black</strong></td></tr>
<tr><td style="text-align: center">ON</td><td style="text-align: center">Off</td><td style="text-align: center">Off</td><td style="text-align: left"><strong>Red</strong></td></tr>
<tr><td style="text-align: center">Off</td><td style="text-align: center">ON</td><td style="text-align: center">Off</td><td style="text-align: left"><strong>Green</strong></td></tr>
<tr><td style="text-align: center">ON</td><td style="text-align: center">ON</td><td style="text-align: center">Off</td><td style="text-align: left"><strong>Yellow</strong></td></tr>
<tr><td style="text-align: center">Off</td><td style="text-align: center">Off</td><td style="text-align: center">ON</td><td style="text-align: left"><strong>Blue</strong></td></tr>
<tr><td style="text-align: center">ON</td><td style="text-align: center">Off</td><td style="text-align: center">ON</td><td style="text-align: left"><strong>Magenta</strong></td></tr>
<tr><td style="text-align: center">Off</td><td style="text-align: center">ON</td><td style="text-align: center">ON</td><td style="text-align: left"><strong>Cyan</strong></td></tr>
<tr><td style="text-align: center">ON</td><td style="text-align: center">ON</td><td style="text-align: center">ON</td><td style="text-align: left"><strong>White</strong></td></tr>
</tbody></table>
</div>
<p><em>Only 8 colours?! Thats not a Full Colour RGB LED!</em></p>
<p>GPIO Pins are binary (not analogue)… So are LEDs. This will let us switch each LED On and Off, nothing in between (no 50 shades of grey)…</p>
<p><img src="https://lupyuen.github.io/images/led-off-on.jpg" alt="Switching LED on and off with GPIO" /></p>
<p>But what if we strobe or <strong>blink the LEDs very quickly</strong> (a thousand times a second)…</p>
<p><img src="https://lupyuen.github.io/images/led-wave1.jpg" alt="Blink the LED very quickly" /></p>
<p>Aha! Well see something thats neither On nor Off… Its <strong>halfway between Light and Dark</strong>!</p>
<p>Now what if we <strong>tweak the spacing</strong> between the On and Off parts (keeping the same blinking frequency)…</p>
<p><img src="https://lupyuen.github.io/images/led-wave2.jpg" alt="Blink the LED with spacing" /></p>
<p>Well get <strong>many, many shades of grey</strong>! (&gt;50 yes!)</p>
<p>And if we apply this nifty trick to each of the RGB LEDs, well get our Full Colour RGB LED!</p>
<p><em>How shall we program the rapid blinking? Call the GPIO Functions in a loop?</em></p>
<p>Not a good idea, because our microcontroller will become very busy blinking the LEDs. No time for reading sensors or transmitting data!</p>
<p>Thankfully we have <strong>Pulse Width Modulation (PWM)</strong>… Our BL602 Microcontroller (and many others) will happily strobe the LED pins for us, without coding any loops.</p>
<p>Heres the schematic for PineCones RGB LED…</p>
<p><img src="https://lupyuen.github.io/images/led-rgb.png" alt="PineCone RGB LED Schematic" /></p>
<p><em>What are CH1, CH2 and CH4?</em></p>
<p>CH1, CH2 and CH4 are <strong>PWM Channels</strong>. Each PWM Channel will let us strobe the output on one pin. (Hence we need 3 PWM Channels)</p>
<p>Lets match the 3 GPIO Pins and 3 PWM Channels to the Pin Mapping Table: <a href="https://github.com/bouffalolab/bl_docs/tree/main/BL602_RM/en">BL602 Reference Manual</a> (Page 27)</p>
<p><img src="https://lupyuen.github.io/images/led-pins.png" alt="BL602 Pin Mapping" /></p>
<p>The table says that <strong>GPIO 11, 17 and 14</strong> may be mapped to <strong>PWM Channels 1, 2 and 4</strong> (by calling the PWM HAL Functions). Perfect!</p>
<p>Remember that we tweaked the spacing of the blinking to get many levels of brightness?</p>
<p>We call this the <strong>Duty Cycle</strong> in PWM.</p>
<p>Lets experiment with the RGB LED on PWM…</p>
<h1 id="control-rgb-led-with-pwm"><a class="doc-anchor" href="#control-rgb-led-with-pwm">§</a>5 Control RGB LED with PWM</h1>
<p>Now well switch PineCone to the <strong>Modified PWM Demo</strong> from the BL602 IoT SDK.</p>
<p>(The firmware was modified to run without a Device Tree. <a href="https://github.com/lupyuen/bl_iot_sdk/pull/1">More details</a>)</p>
<ol>
<li>
<p>Download the <strong>BL602 Demo Firmware Binaries</strong></p>
<ul>
<li><a href="https://github.com/lupyuen/bl_iot_sdk/releases/download/v1.0.0/customer_app.zip"><strong>BL602 Demo Firmware Binaries</strong>: <code>customer_app.zip</code></a></li>
</ul>
</li>
<li>
<p>Unzip <code>customer_app.zip</code>. Look for the file…</p>
<div class="example-wrap"><pre class="language-text"><code>sdk_app_pwm/build_out/sdk_app_pwm.bin</code></pre></div></li>
<li>
<p>Flash <code>sdk_app_pwm.bin</code> to PineCone. Follow the instructions in the article…</p>
<ul>
<li><a href="https://lupyuen.github.io/articles/flash"><strong>“Flashing Firmware to PineCone BL602”</strong></a></li>
</ul>
<p>After flashing, flip the <strong>PineCone Jumper (IO 8)</strong> to the <strong><code>L</code> Position</strong> <a href="https://lupyuen.github.io/images/pinecone-jumperl.jpg">(Like this)</a></p>
<p>Press the Reset Button.</p>
</li>
<li>
<p>Connect to PineCone…</p>
<p><strong>For Linux:</strong></p>
<div class="example-wrap"><pre class="language-bash"><code>screen /dev/ttyUSB0 2000000</code></pre></div>
<p><strong>For macOS:</strong> Use CoolTerm (<a href="https://lupyuen.github.io/articles/flash#watch-the-firmware-run">See this</a>)</p>
<p><strong>For Windows:</strong> Use <code>putty</code> (<a href="https://lupyuen.github.io/articles/flash#watch-the-firmware-run">See this</a>)</p>
<p><strong>Alternatively:</strong> Use the Web Serial Terminal (<a href="https://lupyuen.github.io/articles/flash#watch-the-firmware-run">See this</a>)</p>
<p><a href="https://lupyuen.github.io/articles/flash#watch-the-firmware-run">More details on connecting to BL602</a></p>
</li>
<li>
<p>Press the <strong>RST Button</strong> on PineCone to restart the firmware. Ignore the errors.</p>
</li>
<li>
<p>Press <code>Enter</code> to reveal the command prompt.</p>
</li>
<li>
<p>Assign GPIO 11 (Blue), 17 (Red), 14 (Green) to <strong>PWM Channels</strong> 1, 2 and 4.</p>
<p>Set the <strong>PWM Frequency</strong> to 2 kHz. (Each LED will blink at 2,000 cycles per second)</p>
<div class="example-wrap"><pre class="language-bash"><code>pwm_init 1 11 2000
pwm_init 2 17 2000
pwm_init 4 14 2000</code></pre></div></li>
<li>
<p>Set <strong>PWM Duty Cycle</strong> for all 3 PWM Channels to 100%.</p>
<p>Which means that 100% of the time, the 3 PWM Channels will be set to 1 (High).</p>
<p>Which means total darkness: All 3 LEDs will be switched off 100% of the time.</p>
<div class="example-wrap"><pre class="language-bash"><code>pwm_duty_set 1 100
pwm_duty_set 2 100
pwm_duty_set 4 100</code></pre></div></li>
<li>
<p>Start the PWM Output for all 3 PWM Channels…</p>
<div class="example-wrap"><pre class="language-bash"><code>pwm_start 1
pwm_start 2
pwm_start 4</code></pre></div></li>
<li>
<p>Gradually decrease the PWM Duty Cycle for PWM Channel 1 (Blue) from 100% to 0%.</p>
<p>This means the Blue LED will gradually get brighter.</p>
<div class="example-wrap"><pre class="language-bash"><code>pwm_duty_set 1 75
pwm_duty_set 1 50
pwm_duty_set 1 25
pwm_duty_set 1 0</code></pre></div></li>
<li>
<p>To exit <code>screen</code>, press <code>Ctrl-A</code> then <code>k</code> then <code>y</code></p>
</li>
</ol>
<p><a href="https://youtu.be/66h2rXXc6Tk">Watch the PWM Demo Video on YouTube</a></p>
<h1 id="how-it-works-bl602-pwm"><a class="doc-anchor" href="#how-it-works-bl602-pwm">§</a>6 How It Works: BL602 PWM</h1>
<p>Now we look at the BL602 PWM HAL Functions called by the PWM Demo Firmware: <a href="https://github.com/lupyuen/bl_iot_sdk/tree/master/customer_app/sdk_app_pwm"><code>sdk_app_pwm.bin</code></a></p>
<h2 id="initialise-pwm"><a class="doc-anchor" href="#initialise-pwm">§</a>6.1 Initialise PWM</h2>
<p>To designate a GPIO PIN as a PWM Channel, we call this PWM HAL Function: <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/bl_pwm.h"><code>bl_pwm.h</code></a></p>
<div class="example-wrap"><pre class="language-c"><code>int32_t bl_pwm_init(uint8_t id, uint8_t pin, uint32_t freq);</code></pre></div>
<ul>
<li>
<p><code>id</code> is the PWM Channel ID (0 to 4). BL602 supports 5 PWM Channels: PWM 0 to PWM 4.</p>
</li>
<li>
<p><code>pin</code> is the GPIO Pin Number, so <code>pin=0</code> refers to GPIO 0.</p>
</li>
<li>
<p><code>freq</code> is the PWM Frequency (in Hz / Cycles Per Second). So <code>freq=2000</code> means that the PWM Channel will be blinked 2,000 cycles every second. <code>freq</code> must be between 2,000 and 800,000 (inclusive).</p>
</li>
<li>
<p><strong>UPDATE:</strong> To set the effective PWM Frequency below 2,000 Hz, we may use the <strong>PWM Channel Clock Divider</strong> (which defaults to 1).</p>
<p>For instance, to achieve a clock frequency of 50 Hz on the PWM, we may set an initial frequency of 6,400 Hz on channel 1, then use…</p>
<div class="example-wrap"><pre class="language-c"><code>PWM_Channel_Set_Div(1, 128)</code></pre></div>
<p>Which sets channel 1s clock divider to 128, making the effective PWM Frequency 50 Hz. <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/bl602/bl602_std/bl602_std/StdDriver/Src/bl602_pwm.c#L223-L241">(PWM_Channel_Set_Div comes from the PWM Standard Driver)</a></p>
<p>(Many thanks to Chandler Jearls for the tip!)</p>
</li>
</ul>
<p>Not all GPIO Pins may be assigned to a PWM Channel. Check “Table 3.1: Pin description” (Page 27) in <a href="https://github.com/bouffalolab/bl_docs/tree/main/BL602_RM/en">BL602 Reference Manual</a>.</p>
<h2 id="pwm-frequency-and-duty-cycle"><a class="doc-anchor" href="#pwm-frequency-and-duty-cycle">§</a>6.2 PWM Frequency and Duty Cycle</h2>
<p>We set the Frequency and Duty Cycle on a PWM Channel by calling these PWM HAL Functions: <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/bl_pwm.h"><code>bl_pwm.h</code></a></p>
<div class="example-wrap"><pre class="language-c"><code>int32_t bl_pwm_set_freq(uint8_t id, uint32_t freq);
int32_t bl_pwm_set_duty(uint8_t id, float duty);</code></pre></div>
<ul>
<li>
<p><code>id</code> is the PWM Channel ID (0 to 4).</p>
</li>
<li>
<p><code>freq</code> is the PWM Frequency (in Hz / Cycles Per Second). <code>freq</code> must be between 2,000 and 800,000 (inclusive).</p>
</li>
<li>
<p><code>duty</code> is the PWM Duty Cycle (0 to 100). When <code>duty=25</code>, it means that in every PWM Cycle…</p>
<ul>
<li>PWM Ouput is 1 (High) for the initial 25% of the PWM Cycle</li>
<li>Followed by PWM Output 0 (Low) for the remaining 75% of the PWM Cycle</li>
</ul>
</li>
</ul>
<p>To get the Duty Cycle for a PWM Channel, we call this function…</p>
<div class="example-wrap"><pre class="language-c"><code>int32_t bl_pwm_get_duty(uint8_t id, float *p_duty);</code></pre></div>
<ul>
<li><code>bl_pwm_get_duty</code> stores the Duty Cycle at the pointer passed in <code>p_duty</code>.</li>
</ul>
<h2 id="pwm-operation"><a class="doc-anchor" href="#pwm-operation">§</a>6.3 PWM Operation</h2>
<p>We start and stop a PWM Channel by calling these PWM HAL Functions: <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/bl_pwm.h"><code>bl_pwm.h</code></a></p>
<div class="example-wrap"><pre class="language-c"><code>int32_t bl_pwm_start(uint8_t id);
int32_t bl_pwm_stop( uint8_t id);</code></pre></div>
<ul>
<li><code>id</code> is the PWM Channel ID (0 to 4).</li>
</ul>
<p>The above PWM HAL Functions are defined here…</p>
<ul>
<li><a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/bl_pwm.c"><strong>PWM HAL Source Code: <code>bl_pwm.c</code></strong></a></li>
</ul>
<p>To see the above PWM HAL Functions in action, check out the PWM Demo Source Code…</p>
<ul>
<li><a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_pwm/sdk_app_pwm/main.c"><strong>PWM Demo Source Code: <code>main.c</code></strong></a></li>
</ul>
<h2 id="pwm-device-tree"><a class="doc-anchor" href="#pwm-device-tree">§</a>6.4 PWM Device Tree</h2>
<p>There is an alternative set of functions for controlling PWM…</p>
<ul>
<li><strong>PWM Device Tree</strong>: <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_pwm.h"><strong><code>hal_pwm.h</code></strong></a>, <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_pwm.c"><strong><code>hal_pwm.c</code></strong></a></li>
</ul>
<p>These functions are meant to be used with the <strong>BL602 Device Tree</strong>.</p>
<p><a href="https://lupyuen.github.io/articles/flash#device-tree">More about BL602 Device Tree</a></p>
<h1 id="bl602-pwm-internals"><a class="doc-anchor" href="#bl602-pwm-internals">§</a>7 BL602 PWM Internals</h1>
<p>This helpful diagram from the <a href="https://github.com/bouffalolab/bl_docs/tree/main/BL602_RM/en">BL602 Reference Manual</a> (Page 158) explains the internals of BL602s PWM…</p>
<p><img src="https://lupyuen.github.io/images/led-pwm.png" alt="BL602 Pulse Width Modulation" /></p>
<p><em>BL602 Pulse Width Modulation</em></p>
<ol>
<li>
<p>BL602s PWM uses an <strong>Internal Counter</strong> to generate a Sawtooth Wave</p>
</li>
<li>
<p>Each cycle of the Sawtooth Wave has a duration (<strong>PWM Period</strong>) thats determined by the <strong>PWM Frequency</strong> (PWM Period = 1 / PWM Frequency)</p>
</li>
<li>
<p>The PWM Channel outputs 0 or 1 by comparing the Internal Counter with two values: <strong>PWM Threshold1</strong> (the lower limit) and <strong>PWM Threshold2</strong> (the upper limit)</p>
</li>
<li>
<p>We assume that <strong>PWM Threshold1 (the lower limit) is always 0</strong>. Thats because the BL602 PWM HAL Function <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/bl_pwm.c#L126-L140"><code>bl_pwm_set_duty</code></a> always sets Threshold1 to 0.</p>
</li>
<li>
<p>Whats the value of PWM Threshold2 (the upper limit)? Thats computed based on the PWM Period and <strong>PWM Duty Cycle</strong>: <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/bl_pwm.c#L126-L140"><code>bl_pwm_set_duty</code></a></p>
<div class="example-wrap"><pre class="language-c"><code>// The Duty Cycle `duty` is between 0 to 100
threshold2 = ( period / 100 ) * duty;</code></pre></div>
<p>So when we increase the Duty Cycle, Threshold2 gets higher.</p>
</li>
<li>
<p>Heres the PWM Output logic…</p>
<ul>
<li>
<p>When the <strong>Internal Counter is below Threshold2</strong>, the PWM Channel outputs <strong>1</strong>.</p>
</li>
<li>
<p>And when the <strong>Internal Counter is above Threshold2</strong>, the PWM Channel outputs <strong>0</strong>.</p>
</li>
</ul>
</li>
<li>
<p>What happens when we <strong>increase the Duty Cycle</strong>?</p>
<p>Threshold2 gets higher, hence the PWM Channel <strong>outputs 1 more often</strong>.</p>
</li>
<li>
<p>Thats precisely the definition of Duty Cycle…</p>
<p><strong>Duty Cycle</strong> is the percentage of time (0 to 100) within a Cycle thats spent Working. (“Working” means Output=1)</p>
<p>Outside of the Duty Cycle, our PWM Channel is Idle. (Output=0)</p>
</li>
<li>
<p>Note that the Working vs Idle definition is <strong>flipped for our LED</strong></p>
<ul>
<li>
<p><strong>Working</strong> (Output=1) switches the <strong>LED OFF</strong></p>
</li>
<li>
<p><strong>Idle</strong> (Output=0) switches the <strong>LED ON</strong></p>
</li>
</ul>
</li>
<li>
<p>Which explains this odd behaviour weve seen earlier…</p>
<ul>
<li>
<p>Higher Duty Cycle decreases our LED Brightness</p>
</li>
<li>
<p>Lower Duty Cycle increases our LED Brightness</p>
</li>
</ul>
<p>(Yep the Duty Cycle is Inversely Proportional to the LED Brightness)</p>
</li>
</ol>
<h1 id="whats-next"><a class="doc-anchor" href="#whats-next">§</a>8 Whats Next</h1>
<p>Today we have we have explored the GPIO and PWM HAL Functions through the BL602 IoT SDK.</p>
<p>Alternatively we may access BL602 GPIO Functions through another embedded operating system: <strong>Apache NuttX</strong></p>
<ul>
<li><a href="https://lupyuen.github.io/articles/nuttx#gpio-demo"><strong>“GPIO on NuttX”</strong></a></li>
</ul>
<p>Stay tuned for more NuttX!</p>
<ul>
<li>
<p><a href="https://lupyuen.github.io/articles/sponsor">Sponsor me a coffee</a></p>
</li>
<li>
<p><a href="https://www.reddit.com/r/RISCV/comments/krkm6g/control_pinecone_bl602_rgb_led_with_gpio_and_pwm/?utm_source=share&amp;utm_medium=web2x&amp;context=3">Discuss this article on Reddit</a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io/articles/book">Read “The RISC-V BL602 Book”</a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io">Check out my articles</a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io/rss.xml">RSS Feed</a></p>
</li>
</ul>
<p><em>Got a question, comment or suggestion? Create an Issue or submit a Pull Request here…</em></p>
<p><a href="https://github.com/lupyuen/lupyuen.github.io/blob/master/src/led.md"><code>lupyuen.github.io/src/led.md</code></a></p>
<h1 id="appendix-fix-bl602-demo-firmware-for-macos"><a class="doc-anchor" href="#appendix-fix-bl602-demo-firmware-for-macos">§</a>9 Appendix: Fix BL602 Demo Firmware for macOS</h1>
<p><em>On macOS, why doesnt <code>screen</code> work for accessing the BL602 Demo Firmware?</em></p>
<p>BL602 Demo Firmware configures the UART Port for 2 Mbps. (Which is not a standard POSIX baud rate)</p>
<p>This causes problems for POSIX serial apps (like <code>screen</code>) that dont call macOS IOKit. <a href="https://twitter.com/madushan1000/status/1345352779502669824">See this</a></p>
<p>To fix this, use a newer serial app like <strong>CoolTerm</strong></p>
<ol>
<li>
<p><a href="https://freeware.the-meiers.org/"><strong>Download CoolTerm</strong></a></p>
</li>
<li>
<p>Click <strong><code>Options</code></strong></p>
</li>
<li>
<p>Set <strong><code>Port</code></strong> to <strong><code>usbserial-1420</code></strong></p>
</li>
<li>
<p>Set <strong><code>Baudrate</code></strong> to <strong><code>2000000</code></strong> (2 Mbps)</p>
</li>
<li>
<p>Click <strong><code>Connect</code></strong></p>
</li>
</ol>
<p><img src="https://lupyuen.github.io/images/led-coolterm.png" alt="CoolTerm Options" /></p>
<p><a href="https://twitter.com/Kongduino/status/1358557946670551040">(Many thanks to @Kongduino)</a></p>
<p><em>What if we really really want to use POSIX serial apps like <code>screen</code>?</em></p>
<p>This is NOT recommended… But to support POSIX serial apps with macOS, we need to lower the UART baud rate from 2 Mbps to 230.4 kbps. (Which is a POSIX baud rate)</p>
<ol>
<li>
<p>In the BL602 Demo Firmware, edit the <code>main.c</code> source file, like…</p>
<ul>
<li>
<p><a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_gpio/sdk_app_gpio/main.c#L266"><code>sdk_app_gpio/main.c</code></a></p>
</li>
<li>
<p><a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_pwm/sdk_app_pwm/main.c#L599"><code>sdk_app_pwm/main.c</code></a></p>
</li>
<li>
<p><a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_helloworld/sdk_app_helloworld/main.c#L80"><code>sdk_app_helloworld/main.c</code></a></p>
</li>
</ul>
</li>
<li>
<p>Look for this line that configures the UART port for 2 Mbps…</p>
<div class="example-wrap"><pre class="language-c"><code>bl_uart_init(0, 16, 7, 255, 255, 2 * 1000 * 1000);</code></pre></div>
<p>Change it to 230.4 kbps…</p>
<div class="example-wrap"><pre class="language-c"><code>bl_uart_init(0, 16, 7, 255, 255, 230400);</code></pre></div></li>
<li>
<p>Rebuild the firmware.</p>
</li>
<li>
<p>Edit the BL602 Device Tree: <a href="https://github.com/bouffalolab/BLOpenFlasher/blob/main/bl602/device_tree/bl_factory_params_IoTKitA_40M.dts"><code>bl_factory_params_IoTKitA_40M.dts</code></a></p>
<p>Look for…</p>
<div class="example-wrap"><pre class="language-text"><code>uart {
#address-cells = &lt;1&gt;;
#size-cells = &lt;1&gt;;
uart@4000A000 {
status = &quot;okay&quot;;
id = &lt;0&gt;;
compatible = &quot;bl602_uart&quot;;
path = &quot;/dev/ttyS0&quot;;
baudrate = &lt;2000000&gt;;</code></pre></div>
<p>Change <code>baudrate</code> to…</p>
<div class="example-wrap"><pre class="language-text"><code> baudrate = &lt;230400&gt;;</code></pre></div></li>
<li>
<p>Compile the Device Tree with BLOpenFlasher.</p>
<p>Copy the compiled Device Tree <code>ro_params.dtb</code> to <code>blflash</code></p>
<p>Flash the firmware to PineCone with <code>blflash</code></p>
<p><a href="https://lupyuen.github.io/articles/flash#blflash-vs-blopenflasher">More details</a></p>
</li>
<li>
<p>After flashing, set the PineCone Jumper IO8 to <code>L</code> Position. Press the Reset Button.</p>
<p>We should be able to access the Demo Firmware at 230.4 kbps…</p>
<div class="example-wrap"><pre class="language-bash"><code>screen /dev/tty.usbserial-1420 230400 </code></pre></div></li>
</ol>
<!-- Begin scripts/rustdoc-after.html: Post-HTML for Custom Markdown files processed by rustdoc, like chip8.md -->
<!-- Begin Theme Picker and Prism Theme -->
<script src="../theme.js"></script>
<script src="../prism.js"></script>
<!-- Theme Picker and Prism Theme -->
<!-- End scripts/rustdoc-after.html -->
</body>
</html>