Exploring ChatGPT for Embedded Systems

Sree Harsha Angara
8 min readDec 9, 2022

--

Generated Artwork from StableDiffusion 2.0 with Prompt “AI Conversing with Humans, highly detailed, pop art”

Yes, yes ChatGPT is all the rage these days with an unending stream of articles showing up everywhere. ChatGPT is in open beta right now with unlimited useage, so definitely check it out here.

But my real questions were, can it write embedded code for microcontrollers? Can it teach me complex concepts within these systems? Can it give me reasonable starter code? Am I out of a job 🤣 ?

Part-2 is of this series is also up, where I run through more in-depth experiments for those who want a more detailed view

There’s also a short talk by me at the Embedded Online Conference(this specific talk should be free) which covers a lot of this material as well.

Can you tell me how to blink an LED?

The classic blink LED example, to make it a little more complicated I decided to pick a non-arduino IDE and asked,

Prompt:

I need code to initialize a PSoC6 microcontroller with a PWM enabled to blink an LED

ChatGPT Response:

#include "cy_pdl.h"

// The pin number of the LED
const int LED_PIN = 0;

int main(void)
{
// Initialize the device and peripherals
cy_rslt_t result = cybsp_init();
if (result != CY_RSLT_SUCCESS)
{
// Handle error
}

// Enable the LED pin as a PWM output
cy_rslt_t result = cyhal_pwm_init(CYBSP_D0, CY_PWM_FREQ_1KHZ, CY_PWM_DUTY_CYCLE_MAX, CY_PWM_POLARITY_HIGH);
if (result != CY_RSLT_SUCCESS)
{
// Handle error
}

// Start the PWM
cyhal_pwm_start(&pwm);

for (;;)
{
// Blink the LED
cyhal_pwm_set_duty_cycle(&pwm, 0);
cyhal_system_delay_ms(500);
cyhal_pwm_set_duty_cycle(&pwm, CY_PWM_DUTY_CYCLE_MAX);
cyhal_system_delay_ms(500);
}
}

The main loop in this code blinks the LED by alternating the duty cycle of the PWM between 0% and 100% every 500 milliseconds.

Note that this code is just an example and may need to be modified to work with your specific PSoC6 microcontroller and LED. You may need to change the pin number and other settings to match your hardware setup.

Verdict — Surprisingly Decent 👌:

Color me impressed, it actually did better than expected. But, it’s funny how it ‘blinked’ the LED. It setup the PWM and varied the duty cycle in the main loop rather than just reducing the frequency. Maybe if I try a slightly modified prompt.

Prompt:

I need code to initialize a PSoC6 microcontroller with a PWM enabled to blink an LED without using delays

ChatGPT Response:

#include "cy_pdl.h"

// The pin number of the LED
const int LED_PIN = 0;

// The blink period of the LED in milliseconds
const int BLINK_PERIOD_MS = 500;

int main(void)
{
// Initialize the device and peripherals
cy_rslt_t result = cybsp_init();
if (result != CY_RSLT_SUCCESS)
{
// Handle error
}

// Enable the LED pin as a PWM output
cy_rslt_t result = cyhal_pwm_init(CYBSP_D0, CY_PWM_FREQ_1KHZ, CY_PWM_DUTY_CYCLE_MAX, CY_PWM_POLARITY_HIGH);
if (result != CY_RSLT_SUCCESS)
{
// Handle error
}

// Start the PWM
cyhal_pwm_start(&pwm);

// Blink the LED by toggling the PWM duty cycle
uint32_t lastToggleTime = millis();
bool ledIsOn = false;
while (true)
{
uint32_t currentTime = millis();
if (currentTime - lastToggleTime >= BLINK_PERIOD_MS)
{
// Toggle the LED
ledIsOn = !ledIsOn;
cyhal_pwm_set_duty_cycle(&pwm, ledIsOn ? CY_PWM_DUTY_CYCLE_MAX : 0);
lastToggleTime = currentTime;
}
}
}

In this code, the cybsp_init() function initializes the PSoC6 microcontroller and its peripherals. The cyhal_pwm_init() function initializes the LED pin as a PWM output, and the cyhal_pwm_start() function starts the PWM.

The main loop in this code blinks the LED by toggling the duty cycle of the PWM between 0% and 100% at regular intervals. The millis() function is used to measure the time since the PSoC6 was powered on, and the code uses this time to determine when to toggle the LED. This approach avoids using a delay, which can block the microcontroller from performing other tasks while the delay is running.

Note that this code is just an example and may need to be modified to work with your specific PSoC6 microcontroller and LED. You may need to change the pin number and other settings to match your hardware setup.

Verdict — Meh 😑:

This isn’t wrong, but still isn’t great. What’s interesting is that atleast its non-blocking though. I’m sure if I asked this in the right order, this may eventually work. But now I want to try something more complex.

Teach me FreeRTOS concepts with samples?

Prompt:

Explain to me how to use a mutex in a FreeRTOS-based system to share a I2C master with a code example

ChatGPT Response:

A mutex is a synchronization object that can be used to protect shared resources from being accessed by multiple tasks at the same time. In a FreeRTOS-based system, a mutex can be used to share an I2C master among multiple tasks. Here’s an example of how you can use a mutex to share an I2C master in a FreeRTOS-based system:

#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "i2c_master.h"

// The I2C master instance
i2c_master_instance_t i2cMaster;

// The mutex that protects access to the I2C master
SemaphoreHandle_t i2cMutex;

// Task 1 that uses the I2C master
void task1(void *arg)
{
// Take the mutex to gain access to the I2C master
if (xSemaphoreTake(i2cMutex, portMAX_DELAY) == pdTRUE)
{
// Use the I2C master to perform some I2C operations

// Release the mutex when finished
xSemaphoreGive(i2cMutex);
}
}

// Task 2 that uses the I2C master
void task2(void *arg)
{
// Take the mutex to gain access to the I2C master
if (xSemaphoreTake(i2cMutex, portMAX_DELAY) == pdTRUE)
{
// Use the I2C master to perform some I2C operations

// Release the mutex when finished
xSemaphoreGive(i2cMutex);
}
}

int main(void)
{
// Initialize the I2C master
i2c_master_init(&i2cMaster);

// Create the mutex that protects access to the I2C master
i2cMutex = xSemaphoreCreateMutex();

// Create the tasks that use the I2C master
xTaskCreate(task1, "Task1", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL);
xTaskCreate(task2, "Task2", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL);

// Start the FreeRTOS scheduler
vTaskStartScheduler();

// The program should never reach here
return 0;
}

In this code, the i2cMaster variable represents the I2C master instance, and the i2cMutex variable is a mutex that is used to protect access to the I2C master. The task1() and task2() functions are tasks that use the I2C master.

To use the I2C master in a task, the task first takes the mutex by calling the xSemaphoreTake() function. This function blocks until the mutex is available, at which point the task gains exclusive access to the I2C master. The task can then use the I2C master to perform I2C operations. When the task is finished using the I2C master, it releases the mutex by calling the `

Verdict — Impressive 🔥🔥:

The end cuts off abruptly; probably because the token length was limited. That said, this is fairly impressive. Heck, I would have loved to have something like this a few years back when I was reading through FreeRTOS docs and copy/pasting to try things out.

Teach me what is Secure Boot ?

Prompt:

Can you explain to me how Secure Boot works using ECC keys in a IoT System?

ChatGPT Response:

Secure Boot is a security feature that ensures that only trusted software is executed on a device. In a system that uses ECC (Elliptic Curve Cryptography) keys, Secure Boot typically works as follows:

  1. The device manufacturer generates a pair of ECC keys, a private key and a public key. The private key is kept secret and used to sign software that the manufacturer trusts. The public key is included in the device’s hardware and used to verify the signatures of the trusted software.
  2. When the device is powered on, it checks the signature of the bootloader, which is the first piece of software that runs on the device. The bootloader is signed by the manufacturer using the private key, and its signature is verified using the public key. If the signature is valid, the bootloader is executed.
  3. The bootloader checks the signature of the operating system (OS) kernel, which is the next piece of software to run on the device. The OS kernel is also signed by the manufacturer using the private key, and its signature is verified using the public key. If the signature is valid, the OS kernel is executed.
  4. The OS kernel checks the signatures of any additional trusted software that is installed on the device, such as device drivers and system libraries. These software components are also signed by the manufacturer using the private key, and their signatures are verified using the public key. If the signatures are valid, the trusted software is executed.
  5. If the device tries to execute any untrusted software, such as malware, the signature of the untrusted software will not be verified by the public key, and the software will not be executed. This prevents the untrusted software from running on the device and potentially causing harm.

By using ECC keys and Secure Boot, a device can ensure that only trusted software is executed, and untrusted software is prevented from running on the device. This helps to protect the device and the data it processes from security threats

Verdict — Impressive 🔥🔥:

Damn, that’s pretty good. Great surface level explanation for understanding of concepts.

Write me a basic fixed-point filter with a cut-off frequency?

Prompt:

I need code for a low pass filter with a 50Hz cut-off in embedded C-code using fixed point math.

ChatGPT Response:

int32_t lowpass_filter(int32_t input, int32_t previous_output, int16_t alpha)
{
int32_t output;
// Filter equation: y[n] = alpha * x[n] + (1 - alpha) * y[n-1]
output = (alpha * input) + ((1 - alpha) * previous_output);

return output;
}

// Example usage: int32_t input = 500; int32_t previous_output = 0; int16_t alpha = 0.99926; // Cut-off frequency of 50Hz and sampling rate of 1000Hz

int32_t filtered_output = lowpass_filter(input, previous_output, alpha);

Note that the value of alpha will need to be adjusted based on the desired cut-off frequency and the sampling rate of the input signal. In this example, we used the following formula to calculate alpha:

alpha = 2 * pi * fc / fs

where fc is the cut-off frequency (50Hz in this case) and fs is the sampling rate (1000Hz in this case). This formula assumes that the filter is being implemented using a first-order low pass filter.

Verdict — Bat Poo 💩:

Erm… this isn’t fixed point. Worse; alpha is a decimal and its typecasting to a int16 🤦🏽. Form is generally correct atleast, I like the simple one-pole nature. The equations aren’t bad either, but it missed the point.

Closing thoughts

I’ll be honest, I didn’t expect ChatGPT to be this coherent. It’s almost unreal how close to accurate these answers are and it feels like an absolutely amazing way to learn high level concepts.

Imagine if you could slap on generative voice/video AI models which would narrate these concepts to you(please give me a Stephen Fry voice with a pixar animated figure!). Even more amazing would be if someone used WhisperAI to translate these to different languages to help kids in underprivileged areas around the world.

The big challenge however is understanding ‘when’ its telling the truth, and more subtle nuances for advanced concepts. It isn’t at the point where it could replace professional engineers(yet), but it is a great little helper tool.

While I’m genuinely looking forward to OpenAI going past beta to add a level of expert validation to make this more robust, I’m most looking forward to folks making Open Source implementations of the same concept. I vastly prefer implementations like Stable Diffusion over Dall-E as it encourages far more flexible implementations, cost and freedom.

--

--

Sree Harsha Angara

Embedded system tinkerer and IoT Security Applications expert