Table of Contents
Introduction
After many years of blood, sweat and tears I can finally say that I have figured out how to use Visual Studio Code to compile C/C++ code for embedded devices. I've almost always been a late starter, or a laggard when it comes to trying out new things and VS Code is no different. So, I'm rather happy with this achievement.
Mind you, things have got a heck of lot easier since the trail blazing days of Platform IO for Arduino (something I've still not used in anger). In fact, Raspberry Pi have jumped in too and have started to develop their own extension for the Raspberry Pi Pico (current version of this extension is 0.17.5.), which is the topic of this blog.
In this blog, I am just going to cover the basics and demonstrate just how easy it is to get set up and use VS Code for Pico C/C++ code development. I am also going to try something new by using an Artificial Intelligence derived Large Language model (or AI LLM) to generate the driver library for my project by just specifying the chip I wish to use and the platform where the code will be used.
In my case I simply used Google's Gemini AI tool and I must say, I was pleasantly surprised by the result.
Setup
I'm using the official Raspberry Pi Pico Getting started document as my guide.
Chapter 2 covers the installation process for Visual Studio Code. I'm skipping that.
Chapter 3 is the interesting part. This covers how to install the Raspberry Pi Pico VS Code Extension and the required dependencies. This only takes a minute or two.
Now the fun can begin.
Create a C application using an example
What you may have noticed, if doing this for the first time, is that nothing much happened when you installed the extension. I discovered that the SDK only gets installed on your computer when you create your first project. To illustrate, I've captured the process on video.
The first video is has no audio and simply captures the steps taken to compile one of the Raspberry Pico examples. I've sped it up in parts but it should still give you an indication of the time taken to get your first hello work type application up and running.
The second video demonstrates the flash process and also shows that the standard blink example works on a Pico W board without any code modification
Developing a simple driver for the MCP23017 I2C chip using AI
The next part of this blog actually originated from me not reading the product manuals for the RP2350.
I wanted to see if I could expand my RP2040 capacitive sensing project by porting it across to RP2350. I had assumed this would be straightforward and so went straight into developing a custom prototype board for this.
The schematic and the PCB board design did not take long, and so I dived in further and ordered a couple of PCB's using a low cost PCBA service. A week later I had two base boards (minus the Pico dev boards) on my desk.
The reason for using the MCP23017 on this design was that I had run out of RP2350 GPIO's to drive some LED's (one for each touch pad).
Initial Arduino firmware
As the original RP2040 capacitance touch sensing software was all developed using the Arduino IDE I decided to start with this.
But I quickly discovered that the RP2350 could not be used with the official Arduino Core library for the RP2040. I had to use the alternative Raspberry Pi Pico Arduino core software from Earle F. Philhower, III.
This meant adapting different parts of my original code and this didn't too long, but it was fiddly through a bit of trial and error.
I still did not have any firmware for the MCP23017. Thankfully there was already two Arduino libraries (DFRobot and Adafruit) available for the MCP23017, but it was not immediately obvious which was the more suitable. This meant quite a bit of reading to figure out how it worked and plenty of trial and error testing.
Eventually, I had the DFRobot library working and I was able to blink the LED's on the PCB using a Pico W board. At least this proved that the board design for MCP23017 worked.
I was also able to use my original RP2040 software to detect when I touched any of the pads. Of course, with the RP2040 architecture the max number of touchpads I could use was 8 (4 for each PIO).
I then switched to the Pico 2 W (RP2350) dev board but I couldn't get the capacitance touch sensing to work.
After a fair bit of attempts I gave up and went to the Raspberry Pi forum to seek answers. It turns out that there's a hardware flaw found in the RP2350 chip. This is referenced as Errata-E9 (https://forums.raspberrypi.com/viewtopic.php?t=385560). So, I'm having to shelve this project for now.
Switching to VS Code and the Pico SDK
But the experience got me thinking.
Although I could not get touch pads to work using my Pico 2 W board, I wanted to play with the MCP23017 chip using something other than the Arduino IDE. There had to be a quick way to develop of my firmware, especially if I wanted to use the Pico SDK.
Then I remembered an Adafruit video I had seen on YouTube where they have been using Claude 3.7 to develop base level code for many of their new IC's. They were amazed by the speed at which you could generate some initial code.
So, I thought to give this a go just, but as I did not know anything about Claude 3.7 I decided to use Google's Gemini AI tool, as that is what I had available on my computer.
And this is what happened...
Within seconds of asking Gemini for code, I had a template driver that I could copy into a Pico C application.
Don't believe me... well I had captured my first time VS Code experience using the Pico SDK on video and here it is (apologies for poor audio quality and all the rambling, mumbling and plenty of ers n ums, but this was unscripted).
I was able to get just the right amount of code in a format that was compatible with the Pico SDK, and flash it onto my Pico 2 W in only a couple of minutes, having spent little to no time understanding how the MCP23017 worked - all I had was the Arduino experience and that took considerably longer.
And, here is the code:
// I2C Code Template as automatically generated by the official VS Code extension for Raspberry Pi Pico development // This example will use I2C0 on GPIO8 (SDA) and GPIO9 (SCL) running at 400KHz. // Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments // Code modified for the MCP23017 using functions automatically generated by Google Gemini AI tool #include <stdio.h> #include "pico/stdlib.h" #include "hardware/i2c.h" #include "hardware/uart.h" #define I2C_PORT i2c0 #define I2C_SDA 0 #define I2C_SCL 1 // UART defines // By default the stdout UART is `uart0`, so we will use the second one #define UART_ID uart1 #define BAUD_RATE 115200 // Use pins 4 and 5 for UART1 // Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments #define UART_TX_PIN 4 #define UART_RX_PIN 5 // ********* Google's Gemini Code generated functions *************** // I2C defines #define I2C_PORT i2c0 #define MCP23017_ADDR 0x27 // Default address (A0, A1, A2 grounded) #define MCP23017_IODIRA 0x00 // I/O direction register A #define MCP23017_IODIRB 0x01 // I/O direction register B #define MCP23017_GPIOA 0x12 // Port A register #define MCP23017_GPIOB 0x13 // Port B register // Function to write to MCP23017 register void mcp23017_write_reg(uint8_t reg, uint8_t value) { uint8_t buf[] = {reg, value}; i2c_write_blocking(I2C_PORT, MCP23017_ADDR, buf, 2, false); } // Function to read from MCP23017 register uint8_t mcp23017_read_reg(uint8_t reg) { uint8_t value; i2c_write_blocking(I2C_PORT, MCP23017_ADDR, ®, 1, true); // true to keep master control i2c_read_blocking(I2C_PORT, MCP23017_ADDR, &value, 1, false); return value; } // Function to initialize MCP23017 void mcp23017_init() { // Set all pins as outputs mcp23017_write_reg(MCP23017_IODIRA, 0x00); mcp23017_write_reg(MCP23017_IODIRB, 0x00); } // Function to set a pin's output state void mcp23017_set_pin(uint8_t pin, bool state) { uint8_t port; uint8_t reg; uint8_t bit; if (pin < 8) { port = MCP23017_GPIOA; bit = pin; } else { port = MCP23017_GPIOB; bit = pin - 8; } uint8_t current_state = mcp23017_read_reg(port); if (state) { current_state |= (1 << bit); } else { current_state &= ~(1 << bit); } mcp23017_write_reg(port, current_state); } // ********* End of Google's Gemini Code generated functions *************** int main() { stdio_init_all(); // I2C Initialisation. Using it at 400Khz. i2c_init(I2C_PORT, 400*1000); gpio_set_function(I2C_SDA, GPIO_FUNC_I2C); gpio_set_function(I2C_SCL, GPIO_FUNC_I2C); gpio_pull_up(I2C_SDA); gpio_pull_up(I2C_SCL); // For more examples of I2C use see https://github.com/raspberrypi/pico-examples/tree/master/i2c // Initialize MCP23017 mcp23017_init(); // Set up our UART uart_init(UART_ID, BAUD_RATE); // Set the TX and RX pins by using the function select on the GPIO // Set datasheet for more information on function select gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART); gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART); // Use some the various UART functions to send out data // In a default system, printf will also output via the default UART // Send out a string, with CR/LF conversions uart_puts(UART_ID, " Hello, UART!\n"); // For more examples of UART use see https://github.com/raspberrypi/pico-examples/tree/master/uart while (true) { printf("Hello, let's set pin 10 (Port B, bit 2) high!\n"); // Example: Set pin 10 (Port B, bit 2) high, then low mcp23017_set_pin(3, false); mcp23017_set_pin(10, true); sleep_ms(1000); printf("Now, setting pin 3 (Port A, bit 3) high!\n"); mcp23017_set_pin(10, false); mcp23017_set_pin(3, true); sleep_ms(1000); } }
And here is another video showing it working on the custom PCB: