Compare commits
22 Commits
ab9078ab3f
...
master
Author | SHA1 | Date | |
---|---|---|---|
aae8970584 | |||
a80db635cc | |||
073f6e2549 | |||
7d40995934 | |||
276ae61783 | |||
62dadc65d0 | |||
6d8b9bc65e | |||
c9c31ebd23 | |||
75664c433e | |||
3cec3eacc6 | |||
7ca0c605b8 | |||
664e402080 | |||
329f7e1284 | |||
a637486847 | |||
04b642776d | |||
882cd55ec1 | |||
9f4a61a7ec
|
|||
d35c88e38f | |||
cf04037f9d | |||
2a88835da5 | |||
aa84a2c03b | |||
52a232f1e9 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
/target
|
||||
tulflash
|
||||
|
@@ -3,6 +3,10 @@ name = "tulflash"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[profile.release]
|
||||
opt-level = "s"
|
||||
strip = true
|
||||
|
||||
[dependencies]
|
||||
argp = "0.4.0"
|
||||
crossterm = "0.29.0"
|
||||
|
13
README.md
13
README.md
@@ -2,29 +2,36 @@
|
||||
|
||||
## Installation instructions
|
||||
|
||||
No need to clone this repo manually. You'll just need to install the Rust toolchain and SDCC.
|
||||
No need to clone this repo manually. You'll just need to install the Rust toolchain.
|
||||
|
||||
```bash
|
||||
# All OSes
|
||||
cargo install --git https://git.zumepro.cz/michal.prochazka/tulflash
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> For macOS/Linux users: remember to add `$HOME/.cargo/bin` to your `$PATH` if you have not done so already!
|
||||
|
||||
## Running the example program
|
||||
|
||||
To compile the example program using tulflash, you will need to install SDCC.
|
||||
|
||||
```bash
|
||||
# Generate the example program (only needed to run once):
|
||||
tulflash example
|
||||
|
||||
# Compile, flash and monitor the example program:
|
||||
tulflash run example.c:
|
||||
tulflash run example.c
|
||||
|
||||
# note: `tulflash run example.c` is the thing as `tulflash write --cmd "sdcc example.c" --monitor example.ihx`
|
||||
# note: `tulflash run example.c` is the same thing as `mkdir "tulflash/" && tulflash write --cmd "sdcc example.c" --cmd-path "tulflash/" --monitor example.ihx`
|
||||
```
|
||||
|
||||
If the flash fails, try to add `--slow` before `run` (should not be needed though).
|
||||
|
||||
## Manually flashing Intel HEX binaries
|
||||
|
||||
If you use an external toolchain (such as MCU8051IDE with SDCC) and only want to use this program just for flashing the generated HEX file, just run the following:
|
||||
|
||||
```bash
|
||||
# All OSes, automatically detects the correct serial port
|
||||
tulflash write <path-to-hex>
|
||||
|
33
example.c
33
example.c
@@ -8,19 +8,12 @@
|
||||
//**********************************************************************
|
||||
//**********************************************************************
|
||||
|
||||
#include <at89c55.h>
|
||||
#include <at89c51ed2.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define TICK_RATE 100
|
||||
#define NPER (0x10000 - (20000000 / 12 / TICK_RATE))
|
||||
|
||||
// Missing port definitions
|
||||
__sbit __at (0xC4) P4_4;
|
||||
__sbit __at (0xC3) P4_3;
|
||||
__sbit __at (0xC2) P4_2;
|
||||
__sbit __at (0xC1) P4_1;
|
||||
__sbit __at (0xC0) P4_0;
|
||||
#define NPER (0x10000 - (20000000 / 6 / TICK_RATE))
|
||||
|
||||
// I/O definitions
|
||||
#define BUTTON_1 P3_2
|
||||
@@ -71,8 +64,7 @@ uint8_t lcd_pos;
|
||||
|
||||
// Quick'n'dirty CPU pause, just enough for the LCD to stabilize.
|
||||
void lcd_pause(void) {
|
||||
int i = 30;
|
||||
while(i--);
|
||||
__asm__ ("nop");
|
||||
}
|
||||
|
||||
// Reads the status register from the LCD.
|
||||
@@ -170,11 +162,8 @@ void lcd_init(void) {
|
||||
// Puts a character to the LCD. Correctly handles '\n'.
|
||||
void lcd_putchar(char c) {
|
||||
if(c == 10) {
|
||||
while((lcd_pos != 0) && (lcd_pos != 40)) {
|
||||
lcd_send_data(32);
|
||||
lcd_pos++;
|
||||
if(lcd_pos == 80) lcd_pos=0;
|
||||
}
|
||||
if(lcd_pos < 40) lcd_pos = 40;
|
||||
else lcd_pos = 0;
|
||||
} else {
|
||||
lcd_send_data(c);
|
||||
lcd_pos++;
|
||||
@@ -184,6 +173,8 @@ void lcd_putchar(char c) {
|
||||
|
||||
// Initializes the background tick timer (for delays).
|
||||
void timer_init(void) {
|
||||
CKCON0 = X2; // Enable 2x clock mode
|
||||
|
||||
// Initialize timer 0 as 16-bit countdown, timer 1 as 8-bit auto-reload countdown
|
||||
|
||||
TMOD = 0x21;
|
||||
@@ -196,7 +187,8 @@ void timer_init(void) {
|
||||
|
||||
// Enable interrupts only for timer 0
|
||||
|
||||
IE = 0x82;
|
||||
EA = 1;
|
||||
ET0 = 1;
|
||||
}
|
||||
|
||||
// Initializes the serial port (along with timer 2).
|
||||
@@ -204,7 +196,7 @@ void serial_init(void) {
|
||||
// Initialize timer 2 as 312500 Hz as the baud rate generator (for tulflash)
|
||||
|
||||
RCAP2H = 0xFF;
|
||||
RCAP2L = 0xFE;
|
||||
RCAP2L = 0xFC;
|
||||
T2CON = 0x34;
|
||||
|
||||
// Initialize serial I/O
|
||||
@@ -217,6 +209,7 @@ void serial_init(void) {
|
||||
void serial_putchar(char c) {
|
||||
while(TI == 0);
|
||||
SBUF = c;
|
||||
TI = 0;
|
||||
}
|
||||
|
||||
__bit stdout_to_lcd = 0;
|
||||
@@ -296,6 +289,8 @@ void main(void) {
|
||||
lcd_init();
|
||||
serial_init();
|
||||
|
||||
delay(1);
|
||||
|
||||
stdout_to_serial = 1;
|
||||
stdout_to_lcd = 1;
|
||||
|
||||
@@ -303,7 +298,7 @@ void main(void) {
|
||||
|
||||
while(1) {
|
||||
lcd_clear();
|
||||
printf("hellOwOrld! %d\n", i);
|
||||
printf("Hellorld! %d\n", i);
|
||||
led_bar_set(i);
|
||||
|
||||
LED_RED = !(i & 1);
|
||||
|
10
src/args.rs
10
src/args.rs
@@ -61,6 +61,10 @@ pub struct ArgWrite {
|
||||
#[argp(description = "Command to run before writing the file. Handy for compiling the program and running it at the same time.")]
|
||||
pub cmd: Option<OsString>,
|
||||
|
||||
#[argp(option)]
|
||||
#[argp(description = "Where to run the command. If no command is specified, this option is ignored.")]
|
||||
pub cmd_path: Option<std::path::PathBuf>,
|
||||
|
||||
#[argp(option, default = "0")]
|
||||
#[argp(description = "Start address where to write from. Only for raw binaries, defaults to 0.")]
|
||||
pub start: usize,
|
||||
@@ -78,7 +82,7 @@ pub struct ArgWrite {
|
||||
pub skip_verify: bool,
|
||||
|
||||
#[argp(switch)]
|
||||
#[argp(description = "Write individual sections to Flash rather than performing a single write. Only applies to Intel HEX.")]
|
||||
#[argp(description = "Write individual sections to Flash rather than performing a single write. Only applies to Intel HEX. Can in some very specific cases speed up the writing process.")]
|
||||
pub write_individual: bool,
|
||||
|
||||
#[argp(switch)]
|
||||
@@ -97,12 +101,12 @@ pub struct ArgErase {}
|
||||
pub struct ArgMonitor {}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[argp(description = "Handy shorthand for `write --cmd \"sdcc <srcpath>\" --monitor <hexpath>`.")]
|
||||
#[argp(description = "Handy shorthand for `write --cmd \"sdcc <srcpath>\" --cmd-path \"<srcparentpath>/tulflash\" --monitor <hexpath>`.")]
|
||||
#[argp(subcommand, name = "run")]
|
||||
pub struct ArgRun {
|
||||
#[argp(positional)]
|
||||
#[argp(description = "Path to the source C file.")]
|
||||
pub path: OsString
|
||||
pub path: std::path::PathBuf
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
|
59
src/main.rs
59
src/main.rs
@@ -204,16 +204,23 @@ fn write_chip_data(args: &ArgWrite, target: &mut PhysicalTarget) -> Result<(), E
|
||||
fn write_chip(args: ArgWrite, target: &mut PhysicalTarget) -> Result<(), Error> {
|
||||
loop {
|
||||
if let Some(cmd) = args.cmd.as_ref() {
|
||||
let cmd_path = &args.cmd_path;
|
||||
let mut args = shlex::bytes::split(cmd.as_encoded_bytes())
|
||||
.ok_or(Error::other("Error parsing commandline arguments"))?
|
||||
.into_iter()
|
||||
.map(|x| unsafe { OsString::from_encoded_bytes_unchecked(x) });
|
||||
|
||||
if let Some(cmd_path) = cmd_path {
|
||||
if !cmd_path.is_dir() {
|
||||
Err(Error::other("invalid cmd path (not a dir)"))?
|
||||
}
|
||||
}
|
||||
let Some(name) = args.next() else {
|
||||
Err(Error::other("No command provided"))?
|
||||
};
|
||||
|
||||
let out = std::process::Command::new(name)
|
||||
.current_dir(cmd_path.as_ref().map(|p| p.as_path()).unwrap_or(&std::path::Path::new(".")))
|
||||
.args(args)
|
||||
.output()?;
|
||||
|
||||
@@ -257,11 +264,20 @@ fn monitor_chip(_args: ArgMonitor, target: &mut PhysicalTarget) -> Result<(), Er
|
||||
}
|
||||
|
||||
fn run_chip(args: ArgRun, target: &mut PhysicalTarget) -> Result<(), Error> {
|
||||
let path = PathBuf::from(&args.path).with_extension("ihx").into_os_string();
|
||||
let cmd = Some(OsString::from(format!("sdcc \"{}\"", unsafe { String::from_utf8_unchecked(args.path.into_encoded_bytes()) })));
|
||||
let build_dir = args.path.parent().unwrap().join("tulflash");
|
||||
|
||||
let source_file_name = args.path.file_name().ok_or(Error::other("invalid path to compilation source"))?;
|
||||
let source_file = build_dir.join(&source_file_name);
|
||||
let output_file = source_file.with_extension("ihx");
|
||||
|
||||
let cmd = Some(OsString::from(format!("sdcc \"{}\"", args.path.canonicalize()?.to_string_lossy())));
|
||||
let cmd_path = Some(build_dir.clone());
|
||||
|
||||
std::fs::create_dir_all(build_dir)?;
|
||||
|
||||
write_chip(ArgWrite {
|
||||
path,
|
||||
path: output_file.into_os_string(),
|
||||
cmd_path,
|
||||
cmd,
|
||||
start: 0,
|
||||
end: 65535,
|
||||
@@ -301,9 +317,21 @@ fn main() {
|
||||
let start = SystemTime::now();
|
||||
|
||||
let port = args.port.or_else(|| {
|
||||
for port in serial_enumerator::get_serial_list() {
|
||||
if let Some(vendor) = port.vendor && vendor.as_str() == "TUL" {
|
||||
return Some(port.name.into())
|
||||
let ports = serial_enumerator::get_serial_list();
|
||||
|
||||
// First try TUL (works on Linux and macOS)...
|
||||
|
||||
for port in &ports {
|
||||
if let Some(vendor) = &port.vendor && vendor.as_str() == "TUL" {
|
||||
return Some(port.name.clone().into())
|
||||
}
|
||||
}
|
||||
|
||||
// ...then try FTDI (which Windows overrides)
|
||||
|
||||
for port in &ports {
|
||||
if let Some(vendor) = &port.vendor && vendor.as_str() == "FTDI" {
|
||||
return Some(port.name.clone().into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,6 +341,25 @@ fn main() {
|
||||
let Some(port) = port else {
|
||||
println!("Could not find any compatible serial port. Please make sure that the target is connected.");
|
||||
println!("If the issue persists, please specify the port manually with the `--port ...` argument.");
|
||||
println!();
|
||||
|
||||
let ports = serial_enumerator::get_serial_list();
|
||||
|
||||
if ports.is_empty() {
|
||||
println!("No usable ports detected.");
|
||||
}
|
||||
|
||||
if !ports.is_empty() {
|
||||
println!("Possible ports:");
|
||||
|
||||
for port in serial_enumerator::get_serial_list() {
|
||||
println!(" - {} ({}, {})",
|
||||
port.name,
|
||||
port.vendor.unwrap_or_else(|| "unknown vendor".to_string()),
|
||||
port.product.unwrap_or_else(|| "unknown product".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
std::process::exit(1);
|
||||
};
|
||||
|
||||
|
@@ -92,10 +92,9 @@ impl Monitor {
|
||||
if key == KeyCode::Char('r') && mods == KeyModifiers::CONTROL {
|
||||
stdout.write_all(b"\r\n*** Reboot ***\r\n")?;
|
||||
port.set_dtr(true)?;
|
||||
port.set_rts(true)?;
|
||||
port.set_rts(false)?;
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
port.set_dtr(false)?;
|
||||
port.set_rts(false)?;
|
||||
continue
|
||||
}
|
||||
|
||||
|
@@ -37,7 +37,6 @@ impl PhysicalTarget {
|
||||
}
|
||||
|
||||
pub fn resync_target(&mut self) -> Result<(), Error> {
|
||||
self.port.discard_buffers()?;
|
||||
self.port.set_dtr(true)?;
|
||||
self.port.set_rts(true)?;
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
@@ -45,10 +44,12 @@ impl PhysicalTarget {
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
|
||||
for i in (0..5).rev() {
|
||||
self.port.discard_buffers()?;
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
self.port.write(b"U")?;
|
||||
|
||||
let mut buf = [0u8; 1];
|
||||
self.port.read(&mut buf)?;
|
||||
let _ = self.port.read(&mut buf);
|
||||
|
||||
if buf[0] == b'U' { break }
|
||||
|
||||
@@ -243,10 +244,9 @@ impl Target for PhysicalTarget {
|
||||
impl Drop for PhysicalTarget {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.port.set_dtr(true);
|
||||
let _ = self.port.set_rts(true);
|
||||
let _ = self.port.set_rts(false);
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
let _ = self.port.set_dtr(false);
|
||||
let _ = self.port.set_rts(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user