A unique situation in which I wanted to dump the memory of a program running on a microcontroller – the program can send data through serial port, but for it to make sense for the programmer it has to be dumped in a readable format. And another challenge was that the controller was programmable only from MS Windows. So this dumping program must be able to run on Win64.
I chose Golang for this purpose as I don’t know the serial port reading API of Win32/64 but I can build for Win64 using Golang on Linux and a nice Serial library was available for Golang as well.
package main import ( "fmt" "github.com/tarm/serial" "bufio" "os" "strconv" "time" "encoding/hex" "bytes" "encoding/binary" "sync" ) type mt_var struct { u32 uint32 i32 int32 f32 float32 choice int64 } func (v *mt_var) Parse(varvalue_b []byte) { reader := bytes.NewReader(varvalue_b) switch v.choice { case 1: if err := binary.Read(reader, binary.BigEndian, &v.u32); err != nil { fmt.Println("Error occurred while parsing bytes to uint32: ", err) } case 2: if err := binary.Read(reader, binary.BigEndian, &v.i32); err != nil { fmt.Println("Error occurred while parsing bytes to int32: ", err) } case 3: if err := binary.Read(reader, binary.BigEndian, &v.f32); err != nil { fmt.Println("Error occurred while parsing bytes to float: ", err) } } } func (v mt_var) String() (ret string) { switch v.choice { case 1: ret = fmt.Sprintf("%v", v.u32) case 2: ret = fmt.Sprintf("%v", v.i32) case 3: ret = fmt.Sprintf("%v", v.f32) } return } func main() { scanner := bufio.NewScanner(os.Stdin) defer func() { fmt.Println("Press any key to exit") scanner.Scan() }() fmt.Print("Enter serial port name: ") scanner.Scan() portname := scanner.Text() fmt.Print("Enter baud rate: ") scanner.Scan() baud_s := scanner.Text() baud, err := strconv.ParseInt(baud_s, 10, 64) if err != nil { fmt.Println("Unable to parse baud rate: ", err) return } fmt.Print("Enter serial port timeout in seconds: ") scanner.Scan() timeout_s := scanner.Text() timeout, err := strconv.ParseInt(timeout_s, 10, 64) if err != nil { fmt.Println("Unable to parse timeout value: ", err) return } fmt.Print("Select data type of variables (1 - uint32, 2 - int32, 3 - float32): ") scanner.Scan() dt_var_s := scanner.Text() dt_var, err := strconv.ParseInt(dt_var_s, 10, 64) if err != nil { fmt.Println("Unable to parse choice value for data type: ", err) return } else if dt_var < 1 || dt_var > 3 { fmt.Println("Invalid choice ", dt_var, " for data type selection") return } fmt.Print("Enter number of variables to be read per timestamp: ") scanner.Scan() num_vars_s := scanner.Text() num_vars, err := strconv.ParseInt(num_vars_s, 10, 64) if err != nil { fmt.Println("Unable to parse num vars value: ", err) return } fmt.Print("Enter number of bytes per variable: ") scanner.Scan() num_bytes_per_var_s := scanner.Text() num_bytes_per_var, err := strconv.ParseInt(num_bytes_per_var_s, 10, 64) if err != nil { fmt.Println("Unable to parse num bytes per var value: ", err) return } fmt.Print("Enter total number of timestamps: ") scanner.Scan() num_timestamps_s := scanner.Text() num_timestamps, err := strconv.ParseInt(num_timestamps_s, 10, 64) if err != nil{ fmt.Println("Unable to parse num timestamps value: ", err) return } fmt.Print("Enter DAQ sample time: ") scanner.Scan() sampletime_s := scanner.Text() sampletime, err := strconv.ParseInt(sampletime_s, 10, 64) if err != nil { fmt.Println("Unable to parse sample time value: ", err) return } c := &serial.Config{Name: portname, Baud: int(baud), ReadTimeout: time.Second * time.Duration(timeout)} port, err := serial.OpenPort(c) if err != nil { fmt.Println("Unable to open serial port: ", err) return } defer func() { fmt.Println("Closing serial port") port.Close() }() var wg sync.WaitGroup var stop_serial_writer = make(chan bool) fmt.Println("Press any key to start reading serial port") scanner.Scan() wg.Add(1) go serial_writer(port, stop_serial_writer, &wg) fmt.Println("Starting to read port") writer_channel := make(chan []byte, 1000) wg.Add(1) go writer(writer_channel, num_vars, &wg, sampletime, num_bytes_per_var, dt_var) bytes_per_timestamp := num_vars * num_bytes_per_var var bytecount int outer: for i := int64(1); i <= num_timestamps; i++ { buf := make([]byte, bytes_per_timestamp) for j := int64(0); j < bytes_per_timestamp; j++ { if n, err := port.Read(buf[j:j+1]); err != nil { fmt.Println("Error occurred ", err) break outer } else { bytecount += n fmt.Printf("Read %d bytes\r", bytecount) } } writer_channel <- buf } fmt.Println() close(writer_channel) stop_serial_writer <- true fmt.Println("waiting for all goroutines to exit") wg.Wait() } func writer(channel chan []byte, num_vars int64, wg *sync.WaitGroup, sampletime int64, bytes_per_var int64, dt_var int64) { defer func() { wg.Done() }() home_directory, err := os.UserHomeDir() if err != nil { fmt.Println("Unable to fetch user home directory: ", err) return } time_str := time.Now().Format("2006-01-02-15-04-05") filename_raw := fmt.Sprintf("%s%cserial2csv_raw_%s.txt", home_directory, os.PathSeparator, time_str) filename_csv := fmt.Sprintf("%s%cserial2csv_%s.csv", home_directory, os.PathSeparator, time_str) f_raw, err := os.Create(filename_raw) if err != nil { fmt.Println("Unable to create file ", filename_raw, ": ", err) return } defer f_raw.Close() f_csv, err := os.Create(filename_csv) if err != nil { fmt.Println("Unable to create file ", filename_csv, ": ", err) return } defer f_csv.Close() bytes_written := 0 var o_sampletime int64 for data := range channel { hexdata := make([]byte, hex.EncodedLen(len(data))) hex.Encode(hexdata, data) if n, err := f_raw.Write(hexdata); err != nil { bytes_written += n fmt.Println("Error occurred while writing to file ", filename_raw, ": ", err) } fmt.Fprintf(f_csv, "%v,", o_sampletime) o_sampletime += sampletime for varnumber := int64(1); varnumber <= num_vars; varnumber++ { var err error var n int varvalue_b := data[varnumber * bytes_per_var - bytes_per_var:varnumber * bytes_per_var] var varvalue = mt_var{choice: dt_var} varvalue.Parse(varvalue_b) if varnumber == num_vars { n, err = fmt.Fprintln(f_csv, varvalue) } else { n, err = fmt.Fprintf(f_csv, "%v,", varvalue) } if err != nil { fmt.Println("Error while writing to file ", filename_csv, ": ", err) } else { bytes_written += n } } } fmt.Println("Total bytes written to files ", bytes_written) fmt.Println("RAW Data: ", filename_raw) fmt.Println("CSV Data: ", filename_csv) } func serial_writer(port *serial.Port, stop chan bool, wg *sync.WaitGroup) { fmt.Println("Starting serial port writer - sending 1 continuously") defer wg.Done() data := []byte{1} zero := []byte{0} outer_one: for { select { case <-stop: fmt.Println("stopping to send 1 on serial") break outer_one default: port.Write(data) } } zero_stop_timer := time.NewTimer(2 * time.Second) outer_zero: for { select { case <-zero_stop_timer.C: fmt.Println("stopping to send 0 on serial") break outer_zero default: port.Write(zero) } } zero_stop_timer.Stop() }
The same is available on my Github.
Building this program on Linux, for Win64 is pretty easy (my Linux box is amd64):
GOOS=windows go build serial2csv_sync_mdt.go
You may require some changes in the build command if building for 32 bit or from a 32 bit system.