Is AI making embedded software developers more productive

Futurist

Joined Apr 8, 2025
792
Its a poor workman who blames his tools:

1756409209953.png

So in those simple examples I gave I didn't tell it hardware, 32/64 bit, endianness, CPU type, time constraints, arg validation rules, etc but we can add that iteratively.
 

Futurist

Joined Apr 8, 2025
792
Here's my last and final example:

1756409580166.png

Few of us could write that without access to the internet, unless you've worked on that specific processor a great deal and have internalized a lot.
 

Futurist

Joined Apr 8, 2025
792
Wow, I just asked Copilot this:

write me a C program that when executed, generates text output that the same as the program's source code.

It "thinks" for a period then generates code. I posted that C into a simple empty Windows C project and it wasn't quite correct, so I then asked this:

that seems wrong, the generated line beginning "const char" is a single long line, whereas in the source its a series of short lines.

and you guys need to see what it does, I urge to try this when your next at your desks...

Oh, it generated this:


C:
#include <stdio.h>
int main(void){
    const char *s = "#include <stdio.h>%cint main(void){%c    const char *s = %c%s%c;%c    printf(s,10,10,34,s,34,10,10,10,10);%c    return 0;%c}%c";
    printf(s,10,10,34,s,34,10,10,10,10);
    return 0;
}
 

WBahn

Joined Mar 31, 2012
32,933
Wow, I just asked Copilot this:

write me a C program that when executed, generates text output that the same as the program's source code.

It "thinks" for a period then generates code. I posted that C into a simple empty Windows C project and it wasn't quite correct, so I then asked this:

that seems wrong, the generated line beginning "const char" is a single long line, whereas in the source its a series of short lines.

and you guys need to see what it does, I urge to try this when your next at your desks...

Oh, it generated this:


C:
#include <stdio.h>
int main(void){
    const char *s = "#include <stdio.h>%cint main(void){%c    const char *s = %c%s%c;%c    printf(s,10,10,34,s,34,10,10,10,10);%c    return 0;%c}%c";
    printf(s,10,10,34,s,34,10,10,10,10);
    return 0;
}
This is actually something that can be found with a simple Google search, as it has been a common trick, challenge, or curiosity problem for decades.

Asking Google "C program whose output is the program itself"

Returned, among MANY hits, the following page:

https://docs.vultr.com/clang/examples/display-its-own-source-code-as-output

Which has the following:

Code:
#include <stdio.h>

int main() {
    char *s = "#include <stdio.h>\\n\\nint main() {\\n    char *s = %c%s%c;\\n    printf(s, 34, s, 34);\\n    return 0;\\n}";
    printf(s, 34, s, 34);
    return 0;
}
Another common approach, which I think is cheating, it to have the program print out it's own source code file. But that's pretty trivial. I think it needs to be an executable file that, when executed all by itself, prints out the contents of the source file that was used to produce it.
 

Futurist

Joined Apr 8, 2025
792
This is actually something that can be found with a simple Google search, as it has been a common trick, challenge, or curiosity problem for decades.

Asking Google "C program whose output is the program itself"

Returned, among MANY hits, the following page:

https://docs.vultr.com/clang/examples/display-its-own-source-code-as-output

Which has the following:

Code:
#include <stdio.h>

int main() {
    char *s = "#include <stdio.h>\\n\\nint main() {\\n    char *s = %c%s%c;\\n    printf(s, 34, s, 34);\\n    return 0;\\n}";
    printf(s, 34, s, 34);
    return 0;
}
Another common approach, which I think is cheating, it to have the program print out it's own source code file. But that's pretty trivial. I think it needs to be an executable file that, when executed all by itself, prints out the contents of the source file that was used to produce it.
Yes it is well known, but you should see the way the "thoughts" are rendered as the AI system is processing the problem, like a sci-fi movie
 

simozz

Joined Jul 23, 2017
170
Few of us could write that without access to the internet, unless you've worked on that specific processor a great deal and have internalized a lot.
Sorry but if you don't have an internet connection nowadays I think you will not download any TRM neither buy any board... You are out without any internet access.

Did you use the AI tool offline, without internet access?

Also, the TRM Is something to study a bit to know how the device is organised and how to use the library provided by the manufacturer, if they do. I checked in 30s the device ID 96 bits value in the TRM just searching into the TRM of that device.

Also, it is not needed to convert the value to a char string. I would never use a printf/sprintf on a MCU..

Also, where the characters are supposed to be printed to? Is it using a serial interface to stream them? Since no header include to UART/SPI etc. the AI tool might supposes that the ST is running an OS... This is very dangerous for someone without proper knowledge because it "teaches" bad practices, IMHO.
 

be80be

Joined Jul 5, 2008
2,395
This is more useful code that runs on ESP32
Code:
import machine
import time

# 1. Create an object to access the Real-Time Clock (RTC)
rtc = machine.RTC()

print("Starting clock. Press Ctrl+C to stop.")

try:
    # 2. Create an infinite loop to continuously display the time
    while True:
        # 3. Get the current time from the RTC.
        # It returns a tuple: (year, month, day, weekday, hour, minute, second, microsecond)
        current_time = rtc.datetime()

        # 4. Format the tuple into a nice, readable string
        # The :02d adds a leading zero if the number is less than 10 (e.g., 09:05:03)
        time_string = f"{current_time[0]}-{current_time[1]:02d}-{current_time[2]:02d} " \
                      f"{current_time[4]:02d}:{current_time[5]:02d}:{current_time[6]:02d}"

        # 5. Print the formatted time string
        print(time_string)

        # 6. Wait for one second before the next update
        time.sleep(1)

except KeyboardInterrupt:
    # This allows you to stop the loop cleanly by pressing Ctrl+C
    print("\nClock stopped.")
This code pulls time from the net and sets RTC then shows time on a ESP32 with a ST7789 LCD

Code:
import time
import network
import ntptime
from machine import Pin, SPI
import st7789py as st7789
import vga1_16x32 as font

# --- Your Wi-Fi Credentials ---
WIFI_SSID = "YourSsid"
WIFI_PASSWORD = "Your_Password"

# --- Timezone Configuration ---
# EDT is UTC-4. The offset is in seconds.
TIMEZONE_OFFSET = -4 * 3600

# --- Your Display Configuration ---
tft = st7789.ST7789(
    SPI(1, baudrate=30000000, sck=Pin(18), mosi=Pin(23)),
    135,
    240,
    reset=Pin(4, Pin.OUT),
    cs=Pin(15, Pin.OUT),
    dc=Pin(2, Pin.OUT),
    backlight=Pin(32, Pin.OUT),
    rotation=1  # Set to landscape
)

def connect_wifi():
    """Connects the ESP32 to the Wi-Fi network."""
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    if not wlan.isconnected():
        print('Connecting to network...')
        tft.text(font, "Connecting...", 10, 50, st7789.YELLOW)
        wlan.connect(WIFI_SSID, WIFI_PASSWORD)
        while not wlan.isconnected():
            time.sleep(1)
    print('Network config:', wlan.ifconfig())
    tft.fill(st7789.BLACK) # Clear the screen

def set_time():
    """Sets the ESP32's real-time clock using NTP."""
    print("Setting time from NTP server...")
    tft.text(font, "Syncing Time...", 10, 50, st7789.CYAN)
    # This sets the internal RTC to the current UTC time
    ntptime.settime()
    print("Time synced!")
    tft.fill(st7789.BLACK)

def main():
    """Main function to run the display logic."""
    tft.fill(st7789.BLACK)
    
    # 1. Connect to the internet
    connect_wifi()
    
    # 2. Get the current time
    set_time()
    
    last_second = -1

    # 3. Start the clock display loop
    while True:
        # Get the current time from the RTC and apply the timezone offset
        current_time_utc = time.time()
        local_time_tuple = time.localtime(current_time_utc + TIMEZONE_OFFSET)
        
        # Unpack the tuple
        year, month, day, hour, minute, second, _, _ = local_time_tuple

        # Only redraw the screen if the second has changed
        if second != last_second:
            last_second = second
            
            # Format the time into a HH:MM:SS string
            # The :02d ensures that single-digit numbers get a leading zero (e.g., 09)
            time_string = f"{hour:02d}:{minute:02d}:{second:02d}"
            
            # Format the date into a MM/DD/YYYY string
            date_string = f"{month:02d}/{day:02d}/{year}"
            
            # Erase the previous time by drawing a black rectangle
            tft.fill_rect(10, 50, 220, 80, st7789.BLACK)
            
            # Draw the date and time strings
            tft.text(font, date_string, 10, 50, st7789.WHITE)
            tft.text(font, time_string, 10, 90, st7789.GREEN)
            
        time.sleep_ms(100) # Small delay to prevent the loop from running too fast

main()
 
Last edited:

be80be

Joined Jul 5, 2008
2,395
Here XC8 code doing something useful on a 18f1822
Code:
#include <xc.h>

// Configuration bits
#pragma config FOSC = INTOSCIO_EC // Internal oscillator, I/O function on OSC2
#pragma config WDTE = OFF // Watchdog timer disabled
#pragma config PWRTE = ON // Power-up timer enabled
#pragma config BOREN = ON // Brown-out reset enabled
#pragma config LVP = ON // Low-voltage programming enabled

// Define ADC channel and resolution
#define ADC_CHANNEL 0 // Channel 0
#define ADC_RESOLUTION 10 // 10-bit resolution

// UART Definitions
#define BAUD_RATE 9600 // Desired baud rate
#define _XTAL_FREQ 8000000 // Microcontroller's clock frequency (8MHz internal oscillator)

// Function to initialize ADC
void init_adc() {
    // Set RA0 as analog input
    ANSELAbits.ANSA0 = 1;
    // Set RA0 as input pin
    TRISAbits.TRISA0 = 1;

    // ADCON1: Vref is Vdd and Vss, right-justified result
    ADCON1 = 0x01; // ADCS = FOSC/8, ADCON1 = 0x01: ADCS = FOSC/8
    
    // ADCON0: select channel, ADC on
    ADCON0 = 0x00;
    ADCON0bits.CHS = ADC_CHANNEL; // Select ADC channel
    ADCON0bits.ADON = 1; // Turn on ADC
}

// Function to read ADC value
unsigned int read_adc() {
    __delay_us(10); // Wait for acquisition time
    ADCON0bits.GO_nDONE = 1; // Start conversion
    while (ADCON0bits.GO_nDONE); // Wait for conversion to complete
    return (ADRESH << 8) + ADRESL; // Read 10-bit result
}

// Function to initialize UART
void init_uart() {
    // Set UART TX and RX pins
    // For 12F1822, RC4 is TX and RC5 is RX (if using PPS)
    // Here we'll configure it as per the default
    TRISAbits.TRISA4 = 0; // Set RA4 as output for TX
    APFCONbits.TXCKSEL = 1; // Map TX to RA4 (required for this specific pin)
    
    // Baud rate setup
    TXSTAbits.BRGH = 1; // High speed baud rate
    BAUDCONbits.BRG16 = 1; // 16-bit baud rate generator
    SPBRGL = ((_XTAL_FREQ / BAUD_RATE) / 4) - 1; // Low byte of baud rate
    SPBRGH = (((_XTAL_FREQ / BAUD_RATE) / 4) - 1) >> 8; // High byte of baud rate

    // Enable serial port and TX
    RCSTAbits.SPEN = 1; // Enable serial port
    TXSTAbits.TXEN = 1; // Enable transmit
}

// Function to send a single character via UART
void putch(char data) {
    while (!TXSTAbits.TRMT); // Wait for transmit shift register to be empty
    TXREG = data; // Load data to transmit register
}

// Function to print ADC value to serial
void print_adc_value(unsigned int value) {
    // A simple function to print the integer value as a string
    char buffer[6]; // Buffer to hold string
    sprintf(buffer, "%u\r\n", value);
    int i = 0;
    while (buffer[i] != '\0') {
        putch(buffer[i]);
        i++;
    }
}

// Main function
void main() {
    // Disable all analog inputs first for cleaner setup
    ANSELA = 0x00;

    // Initialize the system
    init_uart();
    init_adc();

    while (1) {
        // Read ADC value
        unsigned int adc_value = read_adc();

        // Print ADC value to serial
        print_adc_value(adc_value);
        
        // Wait for a second before the next reading
        __delay_ms(1000);
    }
}
 

be80be

Joined Jul 5, 2008
2,395
Here is Copilit code
Code:
#include <xc.h>
#include <stdio.h>

// CONFIGURATION BITS
#pragma config FOSC = INTOSC    // Internal oscillator
#pragma config WDTE = OFF       // Watchdog Timer disabled
#pragma config PWRTE = OFF
#pragma config MCLRE = ON
#pragma config CP = OFF
#pragma config BOREN = ON
#pragma config CLKOUTEN = OFF
#pragma config IESO = OFF
#pragma config FCMEN = OFF

#define _XTAL_FREQ 8000000

void UART_Init(void) {
    TRISC6 = 0; // TX pin as output
    TRISC7 = 1; // RX pin as input
    SPBRG = 51; // Baud rate 9600 for 8MHz
    TXSTAbits.BRGH = 1; // High speed
    RCSTAbits.SPEN = 1; // Enable serial port
    TXSTAbits.TXEN = 1; // Enable transmitter
}

void UART_Write(char data) {
    while (!TXSTAbits.TRMT); // Wait until buffer is empty
    TXREG = data;
}

void UART_Write_Text(const char *text) {
    while (*text)
        UART_Write(*text++);
}

void ADC_Init(void) {
    ADCON0 = 0x01; // ADC ON, select channel AN0
    ADCON1 = 0x80; // Right justify result, Vref = Vdd/Vss
    TRISA0 = 1;    // Set RA0/AN0 as input
}

unsigned int ADC_Read(void) {
    __delay_ms(2); // Acquisition time
    ADCON0bits.GO_nDONE = 1; // Start conversion
    while (ADCON0bits.GO_nDONE); // Wait for completion
    return ((ADRESH << 8) + ADRESL); // Combine result
}

void main(void) {
    OSCCON = 0x72; // Set internal oscillator to 8MHz
    UART_Init();
    ADC_Init();

    char buffer[10];
    unsigned int adcValue;

    while (1) {
        adcValue = ADC_Read();
        sprintf(buffer, "%u\r\n", adcValue); // Convert to string
        UART_Write_Text(buffer);
        __delay_ms(500);
    }
}
 

nsaspook

Joined Aug 27, 2009
16,348
And I imagine few would use such heavy-handed functions as sprintf() and its brethren on an MCU.
It depends on where it's at in the programming scheme. I use the printf 'safe n' family to convert complex math results into standard formats like json/CSV on 8-bit machines where the full json libs are too heavy.
C:
const char log_format[] = "^,%3.1f,%3.2f,%3.2f,%3.2f,%3.2f,%3.2f,%3.2f,%d.%01d,%d.%01d,%d.%01d,%d,%d,%d,1957,~EOT
snprintf((char*) log_buffer, MAX_B_BUF, log_format, LOG_VARS);
I've timed and profiled the compiler results to be just about as good as custom code to mangle things into shape.

I know when it's good and bad, like using goto. This understanding of coding is where these 'helper' programs fail.

That sort of stuff is easy, the tricky part is to GROK the embedded register data to the bit, nibble and byte data formats to create standard data types for useful processing of that register data.
C:
void state_mx_log_cb(void)
{
    BM.log.volts_peak = (int16_t) cbuf[5];
    BM.log.day = (int16_t) cbuf[14];
    BM.log.kilowatt_hours = (int16_t) (((uint16_t) (cbuf[3] & 0xF0) >> 4) | (uint16_t) (cbuf[4] << 4));
    BM.log.kilowatts_peak = (int16_t) (((uint16_t) (cbuf[13] & 0xFC) >> 2) | (uint16_t) (cbuf[12] << 6));
    BM.log.bat_max = (int16_t) (((uint16_t) (cbuf[2] & 0xFC) >> 2) | (uint16_t) ((cbuf[3] & 0x0F) << 6));
    BM.log.bat_min = (int16_t) (((uint16_t) (cbuf[10] & 0xC0) >> 6) | (uint16_t) ((cbuf[11] << 2) | ((cbuf[12] & 0x03) << 10)));
    BM.log.amps_peak = (int16_t) (cbuf[1] | ((cbuf[2] & 0x03) << 8));
    BM.log.amp_hours = (int16_t) (cbuf[9] | ((cbuf[10] & 0x3F) << 8));
    BM.log.absorb_time = (int16_t) (cbuf[6] | ((cbuf[7] & 0x0F) << 8));
    BM.log.float_time = (int16_t) (((cbuf[7] & 0xF0) >> 4) | (cbuf[8] << 4));

    cmd_mx_log[5] = BM.log.select;
    cmd_mx_log[7] = 0x16 + BM.log.select; // update the checksum

    state = state_mx_status;
}
I do find the programs useful for documentation of my code, after it's working.
1756438538908.png
Google 'AI'
 
Last edited:

Futurist

Joined Apr 8, 2025
792
Sorry but if you don't have an internet connection nowadays I think you will not download any TRM neither buy any board... You are out without any internet access.

Did you use the AI tool offline, without internet access?

Also, the TRM Is something to study a bit to know how the device is organised and how to use the library provided by the manufacturer, if they do. I checked in 30s the device ID 96 bits value in the TRM just searching into the TRM of that device.

Also, it is not needed to convert the value to a char string. I would never use a printf/sprintf on a MCU..

Also, where the characters are supposed to be printed to? Is it using a serial interface to stream them? Since no header include to UART/SPI etc. the AI tool might supposes that the ST is running an OS... This is very dangerous for someone without proper knowledge because it "teaches" bad practices, IMHO.
The internet connection remark was simply saying few people could write that code purely from memory, like Copilot we too need to look stuff up.

The sprintf was shown because I wanted to see what it would generate and would it be correct.

Finally, who said anything about printing? you aren't assuming things are you?
 

Futurist

Joined Apr 8, 2025
792
It depends on where it's at in the programming scheme. I use the printf 'safe n' family to convert complex math results into standard formats like json/CSV on 8-bit machines where the full json libs are too heavy.
C:
const char log_format[] = "^,%3.1f,%3.2f,%3.2f,%3.2f,%3.2f,%3.2f,%3.2f,%d.%01d,%d.%01d,%d.%01d,%d,%d,%d,1957,~EOT
snprintf((char*) log_buffer, MAX_B_BUF, log_format, LOG_VARS);
I've timed and profiled the compiler results to be just about as good as custom code to mangle things into shape.

I know when it's good and bad, like using goto. This understanding of coding is where these 'helper' programs fail.

That sort of stuff is easy, the tricky part is to GROK the embedded register data to the bit, nibble and byte data formats to create standard data types for useful processing of that register data.
C:
void state_mx_log_cb(void)
{
    BM.log.volts_peak = (int16_t) cbuf[5];
    BM.log.day = (int16_t) cbuf[14];
    BM.log.kilowatt_hours = (int16_t) (((uint16_t) (cbuf[3] & 0xF0) >> 4) | (uint16_t) (cbuf[4] << 4));
    BM.log.kilowatts_peak = (int16_t) (((uint16_t) (cbuf[13] & 0xFC) >> 2) | (uint16_t) (cbuf[12] << 6));
    BM.log.bat_max = (int16_t) (((uint16_t) (cbuf[2] & 0xFC) >> 2) | (uint16_t) ((cbuf[3] & 0x0F) << 6));
    BM.log.bat_min = (int16_t) (((uint16_t) (cbuf[10] & 0xC0) >> 6) | (uint16_t) ((cbuf[11] << 2) | ((cbuf[12] & 0x03) << 10)));
    BM.log.amps_peak = (int16_t) (cbuf[1] | ((cbuf[2] & 0x03) << 8));
    BM.log.amp_hours = (int16_t) (cbuf[9] | ((cbuf[10] & 0x3F) << 8));
    BM.log.absorb_time = (int16_t) (cbuf[6] | ((cbuf[7] & 0x0F) << 8));
    BM.log.float_time = (int16_t) (((cbuf[7] & 0xF0) >> 4) | (cbuf[8] << 4));

    cmd_mx_log[5] = BM.log.select;
    cmd_mx_log[7] = 0x16 + BM.log.select; // update the checksum

    state = state_mx_status;
}
I do find the programs useful for documentation of my code, after it's working.
View attachment 355018
Google 'AI'
I asked Copilot if the function state_mx_log_cb could be simplified or made more readable, here's what it suggests:

1756477754934.png

Which version do people prefer?
 

nsaspook

Joined Aug 27, 2009
16,348
Screw macros, they are a pit of hell for low-level embedded programming, they don't help in a lot of cases, it only provides an obfuscation of what's important in that section of code. I don't want it to be abstracted, I want the source to be expansive, clear and Naked as a jaybird on each line because there will be only one parsing routine to do the data extraction. They have a time and place, just not here IMO. It's a style of code, not critical for correctness. Correctness is what's critical and that's what's missing to make embedded more productive, not hints on coding styles.

Let the compiler optimizer make it better. I trust the compilers output, I don't trust the stupid program 'AI' to give bad advice because it has no understanding of why, only the rote answers given to rookies in every CS class.
 
Last edited:

Futurist

Joined Apr 8, 2025
792
Screw macros, they are a pit of hell for low-level embedded programming, they don't help in a lot of cases, it only provides an obfuscation of what's important in that section of code. I don't want it to be abstracted, I want the source to be expansive, clear and Naked as a jaybird on each line because there will be only one parsing routine to do the data extraction. They have a time and place, just not here IMO. It's a style of code, not critical for correctness. Correctness is what's critical and that's what's missing to make embedded more productive, not hints on coding styles.

Let the compiler optimizer make it better. I trust the compilers output, I don't trust the stupid program 'AI' to give bad advice because it has no understanding of why, only the rote answers given to rookies in every CS class.
Well its pretty impressive that it could look at you code for a couple seconds and even spot the opportunity for a macro, that's impressive refactoring skill.

Macros are heavily used part of the C language.

I ran some of your public code from GitHub through Copilot, it found this interesting detail:

1756483685982.png

I know zero about Linux so can't say anything about that myself, but it might be relevant.

https://github.com/nsaspook/Q84vtouch/blob/q84/linux/mqtt_cl/main.c
 
Last edited:

simozz

Joined Jul 23, 2017
170
The internet connection remark was simply saying few people could write that code purely from memory
This is not the way to work. You download the TRM and save it to your HD to review it when needed...

Finally, who said anything about printing? you aren't assuming things are you?
Is the code using a printf into an MCU to print something to the display? Good luck with AI code.
 
Last edited:
Top