feat: implement flash functionality
This commit is contained in:
@@ -42,6 +42,7 @@ import (
|
|||||||
"github.com/goplus/llgo/internal/crosscompile"
|
"github.com/goplus/llgo/internal/crosscompile"
|
||||||
"github.com/goplus/llgo/internal/env"
|
"github.com/goplus/llgo/internal/env"
|
||||||
"github.com/goplus/llgo/internal/firmware"
|
"github.com/goplus/llgo/internal/firmware"
|
||||||
|
"github.com/goplus/llgo/internal/flash"
|
||||||
"github.com/goplus/llgo/internal/mockable"
|
"github.com/goplus/llgo/internal/mockable"
|
||||||
"github.com/goplus/llgo/internal/packages"
|
"github.com/goplus/llgo/internal/packages"
|
||||||
"github.com/goplus/llgo/internal/typepatch"
|
"github.com/goplus/llgo/internal/typepatch"
|
||||||
@@ -341,7 +342,7 @@ func Do(args []string, conf *Config) ([]Package, error) {
|
|||||||
case ModeInstall:
|
case ModeInstall:
|
||||||
// Native already installed in linkMainPkg
|
// Native already installed in linkMainPkg
|
||||||
if conf.Target != "" {
|
if conf.Target != "" {
|
||||||
err = flash(ctx, finalApp, verbose)
|
err = flash.Flash(ctx.crossCompile, finalApp, ctx.buildConf.Port, verbose)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -353,7 +354,7 @@ func Do(args []string, conf *Config) ([]Package, error) {
|
|||||||
} else if conf.Emulator {
|
} else if conf.Emulator {
|
||||||
err = runInEmulator(ctx.crossCompile.Emulator, finalApp, pkg.Dir, pkg.PkgPath, conf, mode, verbose)
|
err = runInEmulator(ctx.crossCompile.Emulator, finalApp, pkg.Dir, pkg.PkgPath, conf, mode, verbose)
|
||||||
} else {
|
} else {
|
||||||
err = flash(ctx, finalApp, verbose)
|
err = flash.Flash(ctx.crossCompile, finalApp, ctx.buildConf.Port, verbose)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -796,16 +797,6 @@ func convertFormat(ctx *context, inputFile string, outputCfg *OutputCfg) (string
|
|||||||
return app, nil
|
return app, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func flash(ctx *context, app string, verbose bool) error {
|
|
||||||
// TODO: Implement device flashing logic
|
|
||||||
if verbose {
|
|
||||||
fmt.Fprintf(os.Stderr, "Flashing %s to port %s\n", app, ctx.buildConf.Port)
|
|
||||||
}
|
|
||||||
fmt.Printf("conf: %#v\n", ctx.buildConf)
|
|
||||||
fmt.Printf("crosscompile: %#v\n", ctx.crossCompile)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func linkObjFiles(ctx *context, app string, objFiles, linkArgs []string, verbose bool) error {
|
func linkObjFiles(ctx *context, app string, objFiles, linkArgs []string, verbose bool) error {
|
||||||
buildArgs := []string{"-o", app}
|
buildArgs := []string{"-o", app}
|
||||||
buildArgs = append(buildArgs, linkArgs...)
|
buildArgs = append(buildArgs, linkArgs...)
|
||||||
|
|||||||
278
internal/flash/flash.go
Normal file
278
internal/flash/flash.go
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
package flash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/goplus/llgo/internal/crosscompile"
|
||||||
|
"github.com/goplus/llgo/internal/env"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Flash flashes firmware to a device based on the crosscompile configuration
|
||||||
|
func Flash(crossCompile crosscompile.Export, app string, port string, verbose bool) error {
|
||||||
|
if verbose {
|
||||||
|
fmt.Fprintf(os.Stderr, "Flashing %s to port %s\n", app, port)
|
||||||
|
}
|
||||||
|
|
||||||
|
method := crossCompile.Flash.Method
|
||||||
|
if method == "" {
|
||||||
|
method = "command"
|
||||||
|
}
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
fmt.Fprintf(os.Stderr, "Flashing %s using method: %s\n", app, method)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch method {
|
||||||
|
case "command":
|
||||||
|
return flashCommand(crossCompile.Flash, app, port, verbose)
|
||||||
|
case "openocd":
|
||||||
|
return flashOpenOCD(crossCompile.OpenOCD, app, verbose)
|
||||||
|
case "msd":
|
||||||
|
return flashMSD(crossCompile.MSD, app, verbose)
|
||||||
|
case "bmp":
|
||||||
|
return flashBMP(app, verbose)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported flash method: %s", method)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// flashCommand handles command-based flashing
|
||||||
|
func flashCommand(flash crosscompile.Flash, app string, port string, verbose bool) error {
|
||||||
|
if flash.Command == "" {
|
||||||
|
return fmt.Errorf("flash command not specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build environment map for template variable expansion
|
||||||
|
envs := buildFlashEnvMap(app, port)
|
||||||
|
|
||||||
|
// Expand template variables in command
|
||||||
|
expandedCommand := expandEnv(flash.Command, envs)
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
fmt.Fprintf(os.Stderr, "Flash command: %s\n", expandedCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split command into parts for exec
|
||||||
|
parts := strings.Fields(expandedCommand)
|
||||||
|
if len(parts) == 0 {
|
||||||
|
return fmt.Errorf("empty flash command after expansion")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle 1200bps reset if required
|
||||||
|
if flash.Flash1200BpsReset && port != "" {
|
||||||
|
if err := reset1200bps(port, verbose); err != nil {
|
||||||
|
if verbose {
|
||||||
|
fmt.Fprintf(os.Stderr, "Warning: 1200bps reset failed: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Wait for bootloader
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute flash command
|
||||||
|
cmd := exec.Command(parts[0], parts[1:]...)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// flashOpenOCD handles OpenOCD-based flashing
|
||||||
|
func flashOpenOCD(openocd crosscompile.OpenOCD, app string, verbose bool) error {
|
||||||
|
if openocd.Interface == "" {
|
||||||
|
return fmt.Errorf("OpenOCD interface not specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"-f", "interface/" + openocd.Interface + ".cfg",
|
||||||
|
}
|
||||||
|
|
||||||
|
if openocd.Transport != "" {
|
||||||
|
args = append(args, "-c", "transport select "+openocd.Transport)
|
||||||
|
}
|
||||||
|
|
||||||
|
if openocd.Target != "" {
|
||||||
|
args = append(args, "-f", "target/"+openocd.Target+".cfg")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add programming commands
|
||||||
|
args = append(args,
|
||||||
|
"-c", "init",
|
||||||
|
"-c", "reset init",
|
||||||
|
"-c", fmt.Sprintf("flash write_image erase %s", app),
|
||||||
|
"-c", "reset",
|
||||||
|
"-c", "shutdown",
|
||||||
|
)
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
fmt.Fprintf(os.Stderr, "OpenOCD command: openocd %s\n", strings.Join(args, " "))
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command("openocd", args...)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// flashMSD handles Mass Storage Device flashing
|
||||||
|
func flashMSD(msd crosscompile.MSD, app string, verbose bool) error {
|
||||||
|
if len(msd.VolumeName) == 0 {
|
||||||
|
return fmt.Errorf("MSD volume names not specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
if msd.FirmwareName == "" {
|
||||||
|
return fmt.Errorf("MSD firmware name not specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the MSD volume
|
||||||
|
var mountPoint string
|
||||||
|
for _, volumeName := range msd.VolumeName {
|
||||||
|
// Try common mount points on different platforms
|
||||||
|
candidates := []string{
|
||||||
|
filepath.Join("/Volumes", volumeName), // macOS
|
||||||
|
filepath.Join("/media", os.Getenv("USER"), volumeName), // Linux
|
||||||
|
filepath.Join("/mnt", volumeName), // Linux alternative
|
||||||
|
volumeName + ":", // Windows (assuming drive letter)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, candidate := range candidates {
|
||||||
|
if _, err := os.Stat(candidate); err == nil {
|
||||||
|
mountPoint = candidate
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mountPoint != "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if mountPoint == "" {
|
||||||
|
return fmt.Errorf("MSD volume not found. Expected volumes: %v", msd.VolumeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy firmware to MSD
|
||||||
|
destPath := filepath.Join(mountPoint, msd.FirmwareName)
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
fmt.Fprintf(os.Stderr, "Copying %s to %s\n", app, destPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return copyFile(app, destPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// flashBMP handles Black Magic Probe flashing
|
||||||
|
func flashBMP(app string, verbose bool) error {
|
||||||
|
// BMP typically uses GDB for flashing
|
||||||
|
args := []string{
|
||||||
|
"-ex", "target extended-remote /dev/ttyACM0", // Default BMP port
|
||||||
|
"-ex", "monitor swdp_scan",
|
||||||
|
"-ex", "attach 1",
|
||||||
|
"-ex", "load",
|
||||||
|
"-ex", "compare-sections",
|
||||||
|
"-ex", "kill",
|
||||||
|
"-ex", "quit",
|
||||||
|
app,
|
||||||
|
}
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
fmt.Fprintf(os.Stderr, "BMP command: arm-none-eabi-gdb %s\n", strings.Join(args, " "))
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command("arm-none-eabi-gdb", args...)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildFlashEnvMap creates environment map for template expansion
|
||||||
|
func buildFlashEnvMap(app string, port string) map[string]string {
|
||||||
|
envs := make(map[string]string)
|
||||||
|
|
||||||
|
// Basic paths
|
||||||
|
envs["root"] = env.LLGoROOT()
|
||||||
|
envs["tmpDir"] = os.TempDir()
|
||||||
|
|
||||||
|
// Port information
|
||||||
|
if port != "" {
|
||||||
|
envs["port"] = port
|
||||||
|
}
|
||||||
|
|
||||||
|
// File paths based on extension
|
||||||
|
ext := strings.ToLower(filepath.Ext(app))
|
||||||
|
switch ext {
|
||||||
|
case ".hex":
|
||||||
|
envs["hex"] = app
|
||||||
|
case ".bin":
|
||||||
|
envs["bin"] = app
|
||||||
|
case ".elf":
|
||||||
|
envs["elf"] = app
|
||||||
|
case ".uf2":
|
||||||
|
envs["uf2"] = app
|
||||||
|
case ".zip":
|
||||||
|
envs["zip"] = app
|
||||||
|
case ".img":
|
||||||
|
envs["img"] = app
|
||||||
|
default:
|
||||||
|
// Default to binary for unknown extensions
|
||||||
|
envs["bin"] = app
|
||||||
|
}
|
||||||
|
|
||||||
|
return envs
|
||||||
|
}
|
||||||
|
|
||||||
|
// expandEnv expands template variables in a string
|
||||||
|
// Supports variables like {port}, {hex}, {bin}, {root}, {tmpDir}, etc.
|
||||||
|
func expandEnv(template string, envs map[string]string) string {
|
||||||
|
if template == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
result := template
|
||||||
|
|
||||||
|
// Replace named variables
|
||||||
|
for key, value := range envs {
|
||||||
|
if key != "" {
|
||||||
|
result = strings.ReplaceAll(result, "{"+key+"}", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset1200bps performs 1200bps reset for Arduino-compatible boards
|
||||||
|
func reset1200bps(port string, verbose bool) error {
|
||||||
|
if verbose {
|
||||||
|
fmt.Fprintf(os.Stderr, "Performing 1200bps reset on %s\n", port)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a simplified implementation
|
||||||
|
// In practice, this would need platform-specific serial port handling
|
||||||
|
// For now, just try to touch the port to trigger reset
|
||||||
|
_, err := os.Stat(port)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyFile copies a file from src to dst
|
||||||
|
func copyFile(src, dst string) error {
|
||||||
|
sourceFile, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer sourceFile.Close()
|
||||||
|
|
||||||
|
destFile, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer destFile.Close()
|
||||||
|
|
||||||
|
_, err = destFile.ReadFrom(sourceFile)
|
||||||
|
return err
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user