tsb: vendor the fixed-baud assembly oracle as the size/feature bar
The Seed Robotics native-UART fixed-baud TinySafeBoot (GPLv3), reference only — not built. Assembles to 500 B with the full feature set, proving ≤512 B and full feature parity are simultaneously reachable. Also drops the stale empty stk500v2/ leftover. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
47
oracle/README.md
Normal file
47
oracle/README.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Oracle — the hand-written TinySafeBoot assembly
|
||||
|
||||
`tsb-fixedbaud.asm` is the reference implementation this port is measured
|
||||
against: the **native-UART, fixed-baud** TinySafeBoot bootloader, hand-written
|
||||
in AVR assembly. It is the size-and-feature bar for the port's `tsb_asm` tier.
|
||||
|
||||
- **Source**: <https://github.com/seedrobotics/tinysafeboot>
|
||||
(`firmware_ASM/latest_stable_release/20200727-fixedbaud/main.asm`), the Seed
|
||||
Robotics fixed-baud fork of Julien Thomas' TinySafeBoot.
|
||||
- **License**: GPLv3 (see the header in the file). It is vendored here **only as
|
||||
a reference oracle** — it is not compiled, linked, or distributed as part of
|
||||
the MIT-licensed port. Mere aggregation.
|
||||
|
||||
## Why this variant
|
||||
|
||||
The user chose the fixed-baud, hardware-UART variant deliberately: it is the one
|
||||
whose feature set the port must match. It fits the **complete** TSB feature set
|
||||
into the 512-byte ATmega boot section:
|
||||
|
||||
| Feature | Oracle routine |
|
||||
|---|---|
|
||||
| Watchdog-reset bail straight to the app | `RESET` (WDRF check) |
|
||||
| One-wire half-duplex (RX/TX shorted): RXEN/TXEN toggled per direction, TX turnaround guard | `SetRX` / `SetTX` / `TransmitByte` |
|
||||
| Activation timeout read from the config page, with a lockout-proof minimum | `WRX1To` (uses `utimeoutH`) |
|
||||
| 3×`@` activation knock | `ActCharRcvd` |
|
||||
| Password gate; wrong byte hangs (still draining the UART) | `CheckPassword` |
|
||||
| Emergency erase on password `\0` + double-confirm — wipes flash, EEPROM and the config page | `EmergencyErase` |
|
||||
| Device-info block (16 bytes) | `SendDeviceInfo` / `DEVICEINFO` |
|
||||
| App-flash read/write (`f`/`F`), EEPROM read/write (`e`/`E`), config read/write (`c`/`C`) | `CheckCommands` |
|
||||
|
||||
## Assembled size (the bar)
|
||||
|
||||
Assembled for the ATmega328P with `avra`:
|
||||
|
||||
```
|
||||
avra -I /usr/share/avra tsb-fixedbaud.asm # after uncommenting .include "m328Pdef.inc"
|
||||
# Code : 250 words (500 bytes) — the whole loader, all features, in the 512 B section
|
||||
```
|
||||
|
||||
**500 bytes with every feature** — the proof that ≤512 B and full feature parity
|
||||
are simultaneously reachable. The port's `tsb_asm` tier matches this bar; `tsb_pure`
|
||||
and `tsb_tricks` implement the same protocol at larger sizes in the 1 KB section,
|
||||
trading bytes for readability.
|
||||
|
||||
The oracle targets 20 MHz / 33333 baud; the port targets 16 MHz / 115200 baud
|
||||
(what the simavr protocol test drives). Baud and geometry differ, code size and
|
||||
feature set do not.
|
||||
776
oracle/tsb-fixedbaud.asm
Normal file
776
oracle/tsb-fixedbaud.asm
Normal file
@@ -0,0 +1,776 @@
|
||||
;***********************************************************************
|
||||
;***********************************************************************
|
||||
;***********************************************************************
|
||||
; TinySafeBoot - The Universal Bootloader for AVR ATmegas
|
||||
;***********************************************************************
|
||||
;***********************************************************************
|
||||
;***********************************************************************
|
||||
;
|
||||
;-----------------------------------------------------------------------
|
||||
; 2020 - Version using native UART, Fixed Baud by Seed Robotics in 2020
|
||||
;-----------------------------------------------------------------------
|
||||
; meant for use on ATMEGA devices only (with native UART - UART0)
|
||||
;
|
||||
; Main differences to Regular TSB Bootloader:
|
||||
; - Uses a native UART (UART0); therefore not compatible with ATTINY
|
||||
; - Baud rate is fixed (set by a macro in the code). No auto bauding.
|
||||
; - Disables TX while not transmitting to allow for one wire flashing
|
||||
; (where RX and TX are shorted, for a multi drop bus)
|
||||
; - Also works with separate RX and TX; however an external pull up
|
||||
; on TX _may_ be required; alternatively you can modify the code
|
||||
; in the ReceiveByte routine so that it won't disable TX.
|
||||
; - FIXES:
|
||||
; - situations where booting onto a bus with active communication could
|
||||
; lock the autobauding feature
|
||||
; - times out and boots to application code if the host stops interacting
|
||||
; with the bootloader
|
||||
;
|
||||
;-----------------------------------------------------------------------
|
||||
; Extended by Seed Robotics from 2017
|
||||
;-----------------------------------------------------------------------
|
||||
; Seed Robotics contributions are available from the Github
|
||||
; repository github.com/seedrobotics
|
||||
; The License and conditions remain as stated below, in the
|
||||
; original notice.
|
||||
;
|
||||
;
|
||||
;-----------------------------------------------------------------------
|
||||
; Written in 2011-2015 by Julien Thomas
|
||||
;
|
||||
; This program is free software; you can redistribute it and/or
|
||||
; modify it under the terms of the GNU General Public License
|
||||
; as published by the Free Software Foundation; either version 3
|
||||
; of the License, or (at your option) any later version.
|
||||
; This program is distributed in the hope that it will be useful,
|
||||
; but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
; of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
; See the GNU General Public License for more details.
|
||||
; You should have received a copy of the GNU General Public License
|
||||
; along with this program; if not, see:
|
||||
; http://www.gnu.org/licenses/
|
||||
;-----------------------------------------------------------------------
|
||||
;
|
||||
;
|
||||
;
|
||||
;***********************************************************************
|
||||
; OVERVIEW
|
||||
;***********************************************************************
|
||||
;
|
||||
; TSB assembly source is organized in 4 segments (approx. line numbers)
|
||||
;
|
||||
; ~ 50 ... Global definitions
|
||||
; ~ ... TSB for ATmegas
|
||||
;
|
||||
;***********************************************************************
|
||||
; ADJUSTMENTS FOR INDIVIDUAL ASSEMBLY
|
||||
;***********************************************************************
|
||||
;
|
||||
; This Sourcecode is directly compatible to: AVRASM2, GAVRASM
|
||||
;
|
||||
.nolist
|
||||
;
|
||||
;-----------------------------------------------------------------------
|
||||
; SPECIFY TARGET AVR
|
||||
;-----------------------------------------------------------------------
|
||||
;
|
||||
; Comment in and provide def.inc file for target device
|
||||
;
|
||||
; [Examples]
|
||||
;
|
||||
;.include "tn2313def.inc"
|
||||
;.include "tn85def.inc"
|
||||
;.include "m8515def.inc"
|
||||
;.include "m168def.inc"
|
||||
;.include "m161def.inc"
|
||||
;.include "m324Adef.inc"
|
||||
;.include "m328Pdef.inc"
|
||||
;.include "tn441def.inc"
|
||||
;.include "tn167def.inc"
|
||||
;.include "tn861def.inc"
|
||||
;.include "tn841def.inc"
|
||||
;.include "tn84def.inc"
|
||||
;.include "m8def.inc"
|
||||
;.include "m644PAdef.inc"
|
||||
;.include "m644def.inc"
|
||||
;.include "tn167def.inc"
|
||||
;.include "tn25def.inc"
|
||||
;
|
||||
; [...]
|
||||
;
|
||||
;
|
||||
.list
|
||||
;
|
||||
;-----------------------------------------------------------------------
|
||||
; BUILD INFO
|
||||
;-----------------------------------------------------------------------
|
||||
; YY = Year - MM = Month - DD = Day
|
||||
.set YY = 21
|
||||
.set MM = 12
|
||||
.set DD = 21
|
||||
;
|
||||
.set BUILDSTATE = $F3 ; F1 fixed baud, pull up, derived from original (modified for fixed baud)
|
||||
; F2 fixed baud, pull up, guaranteed minimum activation timeout in case of userpage data corruption
|
||||
; F3 adds a CONSTANT with clock speed (Mhz) as word in the last page of memory (clock speed our defined CONSTANT)
|
||||
|
||||
;
|
||||
;-----------------------------------------------------------------------
|
||||
; TSB / TSB-INSTALLER SWITCH
|
||||
;-----------------------------------------------------------------------
|
||||
; 0 = Regular assembly to target address
|
||||
; Other value = NOT SUPPORTED
|
||||
;
|
||||
.set TSBINSTALLER = 0
|
||||
;
|
||||
;-----------------------------------------------------------------------
|
||||
; F_CPU and Baud rate setting
|
||||
;-----------------------------------------------------------------------
|
||||
.equ F_CPU = 20000000
|
||||
.equ BAUD = 33333 ; baudrate (notice some possible wrong cals: example for 56K, it is actually 55,555, so for BAUD_PRESC give an INT result of 8, we must set BAUD to 55500)
|
||||
|
||||
.equ BAUD_PRESCx10 = (F_CPU * 10/16/BAUD) - 10 ; baud prescale (regular formula = F_CPU * 10/16/BAUD - 1 but we do it x10 to check the rounding)
|
||||
|
||||
; arredondar acima se necesssario
|
||||
.if BAUD_PRESCx10 - ( (BAUD_PRESCx10 / 10) * 10 ) >= 5 ; calculate the remainder: we rely on the fact these are integer divisions. Therefore, dividing by 10, rounds DOWN in integer division
|
||||
.equ BAUD_PRESC = (F_CPU/16/BAUD)
|
||||
.warning "Incrementing default BAUD_PRESC formula by 1 due to rounding."
|
||||
|
||||
.else
|
||||
.equ BAUD_PRESC = (F_CPU/16/BAUD) - 1
|
||||
.warning "Using default BAUD_PRESC formula (no rounding up)"
|
||||
.endif
|
||||
|
||||
.if BAUD_PRESC > 255
|
||||
.error "ERROR: BAUD RATE TOO LOW. WE ONLY WRITE THE UBRRL REGISTER, SO UBRR MUST BE <255 FOR THIS CLOCK FREQ AND BAUD"
|
||||
.endif
|
||||
|
||||
|
||||
;***********************************************************************
|
||||
; AUTO-ADJUST FOR DIFFERENT ASSEMBLY OPTIONS
|
||||
;***********************************************************************
|
||||
;
|
||||
; Always set TINYMEGA=1 bc this code only supports ATMEGA
|
||||
|
||||
.equ TINYMEGA=1
|
||||
|
||||
.if FLASHEND > ($7fff)
|
||||
.error "SORRY! DEVICES OVER 64 KB NOT SUPPORTED YET."
|
||||
.exit
|
||||
.endif
|
||||
|
||||
;-----------------------------------------------------------------------
|
||||
; Workarounds for devices with renamed or missing definitions
|
||||
;-----------------------------------------------------------------------
|
||||
;
|
||||
.ifndef SPMCSR ; SPMEN / PGERS / ...
|
||||
.equ SPMCSR = SPMCR
|
||||
.endif
|
||||
|
||||
.ifndef MCUSR ; PORF / EXTRF / BORF / WDRF
|
||||
.equ MCUSR = MCUCSR
|
||||
.endif
|
||||
|
||||
; Detect Attiny441/841 to amend missing pagesize and apply 4-page mode
|
||||
|
||||
.set FOURPAGES = 0
|
||||
|
||||
.if ((SIGNATURE_000 == $1E) && (SIGNATURE_002 == $15) && (SIGNATURE_001 == $92))
|
||||
.equ PAGESIZE = 32
|
||||
.set FOURPAGES = 1
|
||||
.message "ATTINY441: 4-PAGE-ERASE MODE"
|
||||
.endif
|
||||
|
||||
.if ((SIGNATURE_000 == $1E) && (SIGNATURE_002 == $15) && (SIGNATURE_001 == $93))
|
||||
.equ PAGESIZE = 32
|
||||
.set FOURPAGES = 1
|
||||
.message "ATTINY841: 4-PAGE-ERASE MODE"
|
||||
.endif
|
||||
|
||||
;-----------------------------------------------------------------------
|
||||
; Universal Constants and Registers
|
||||
;-----------------------------------------------------------------------
|
||||
|
||||
.equ REQUEST = '?' ; request / answer / go on
|
||||
.equ CONFIRM = '!' ; confirm / attention
|
||||
|
||||
; Current bootloader date coded into 16-bit number
|
||||
.equ BUILDDATE = YY * 512 + MM * 32 + DD
|
||||
|
||||
; Other
|
||||
.equ INFOLEN = 8 ; *Words* of Device Info
|
||||
.equ BUFFER = SRAM_START
|
||||
|
||||
; Registers (in use by TSB-Firmware and TSB-Installer for ATtinys)
|
||||
.def avecl = r4 ; application vector temp low
|
||||
.def avech = r5 ; application vector temp high
|
||||
.def tmp1 = r16 ; these are
|
||||
.def tmp2 = r17 ; universal
|
||||
.def tmp3 = r18 ; temporary
|
||||
.def tmp4 = r19 ; registers
|
||||
.def bcnt = r20 ; page bytecounter
|
||||
.def cntr1 = r21 ; timeout counter
|
||||
.def rxen = r22 ; check if RX enabled (meaning TX disabled)
|
||||
.def utimeoutH = r23 ; user timeout High byte
|
||||
; special purpose registers start at R26
|
||||
;
|
||||
;
|
||||
;***********************************************************************
|
||||
;***********************************************************************
|
||||
;***********************************************************************
|
||||
; START OF TSB FOR ATMEGAS
|
||||
;***********************************************************************
|
||||
;***********************************************************************
|
||||
;***********************************************************************
|
||||
;
|
||||
; TSB for ATmegas is always coded directly to target address.
|
||||
|
||||
.if TINYMEGA == 1
|
||||
|
||||
.message "ASSEMBLY OF TSB FOR ATMEGA"
|
||||
|
||||
.equ BOOTSTART = (FLASHEND+1)-256 ; = 512 Bytes
|
||||
.equ LASTPAGE = BOOTSTART - PAGESIZE ; = 1 page below TSB!
|
||||
|
||||
.org BOOTSTART
|
||||
|
||||
RESET:
|
||||
cli
|
||||
|
||||
in tmp4, MCUSR ; check reset condition
|
||||
sbrc tmp4, WDRF ; in case of a Watchdog reset
|
||||
rjmp APPJUMP ; immediately leave TSB
|
||||
|
||||
ldi tmp1, low (RAMEND) ; write ramend low
|
||||
out SPL, tmp1 ; into SPL (stackpointer low)
|
||||
.ifdef SPH
|
||||
ldi tmp1, high(RAMEND) ; write ramend high for ATtinys
|
||||
out SPH, tmp1 ; with SRAM > 256 bytes
|
||||
.message "PROVIDING FOR STACK BIGGER THAN 256 BYTES"
|
||||
.endif
|
||||
|
||||
.ifndef DDRD2
|
||||
.equ DDRD2 = DDD2
|
||||
.endif
|
||||
|
||||
;-----------------------------------------------------------------------
|
||||
; ACTIVATION CHECK
|
||||
;-----------------------------------------------------------------------
|
||||
; Configure UART; no autobauding in this version
|
||||
|
||||
ldi tmp1,BAUD_PRESC ; load baud prescale
|
||||
sts UBRR0L,tmp1 ; set baud prescale
|
||||
; ldi tmp2,HIGH(bpsc) ; save code by not loading UBBRH
|
||||
;sts UBRRH,tmp2 ; to UBRR0
|
||||
;ldi tmp2,( (1<<RXEN0) ) ; enable transmiter and receiver
|
||||
;sts UCSR0B,tmp2
|
||||
|
||||
; Enable Pull up on Port D2 (PD2)
|
||||
cbi DDRD, DDRD2
|
||||
sbi PORTD, PORTD2
|
||||
|
||||
; we will enable RNEN/TXEN in the ReceiveByte and TransmitByte routines
|
||||
|
||||
rcall ZtoLASTPAGE ; set Z to start'o'LASTPAGE
|
||||
adiw zl, 2 ; skip first 2 bytes (APPJUMP)
|
||||
lpm utimeoutH, z+ ; load TIMEOUT byte and store for use in RX byte timeout
|
||||
ori utimeoutH, (F_CPU / 1000000); prevent bootloader lockout due if it gets an invalid (to small) timeout setting
|
||||
; this ensures value is at least the clock rate, which shoudl give about 40ms
|
||||
clr tmp2 ; apparently at times this is not set to 0 on boot? (seen while in debugWire)
|
||||
clr rxen ; same as above
|
||||
|
||||
WRX1To:
|
||||
; we'll check the X register which is where ReceibeByte controls the timeout
|
||||
; the overall timeout of receive byte is the timeout set by the user
|
||||
; therefore, if we get characters while X> 0 we're attempting to activate bootloader;
|
||||
; if not, if X=0 we timedout and go to app start
|
||||
rcall ReceiveByte
|
||||
brcs WRX2To ; if X got to 0 (i.e. carry set), assume we timed out
|
||||
cpi tmp1, '@' ; did we get an activation char = "@"
|
||||
breq ActCharRcvd
|
||||
WRX2To:
|
||||
rjmp APPJUMP ; not an activation char goto APPJUMP in LASTPAGE
|
||||
|
||||
|
||||
ActCharRcvd:
|
||||
inc tmp2
|
||||
cpi tmp2, 3
|
||||
brne WRX1To ; branch if not yet at 3;
|
||||
; otherwise fall through to password check
|
||||
|
||||
;-----------------------------------------------------------------------
|
||||
; CHECK PASSWORD / EMERGENCY ERASE
|
||||
;-----------------------------------------------------------------------
|
||||
; we use the user timeout (utimeoutH) register for COMM timeout
|
||||
; when we don't get valid data
|
||||
; increase this value to a fixed one now, to cope
|
||||
; with cases where the user timeout is set so low that we don't have time to
|
||||
; do anything
|
||||
ldi utimeoutH, (F_CPU / 78500) ; this should result in 255 for 20Mhz and proportionally
|
||||
; less for lower Clocks, so that we get the same time approx. 2.4sec
|
||||
|
||||
CheckPassword:
|
||||
|
||||
chpw0: ser tmp4 ; tmp4 = 255 enables comparison
|
||||
chpw1: lpm tmp3, z+ ; load pw character from Z
|
||||
and tmp3, tmp4 ; if tmp4 = 0 disables comparison, for wrong password scenarios
|
||||
cpi tmp3, 255 ; byte value 255 indicates
|
||||
breq chpwx ; end of password -> success
|
||||
chpw2: rcall Receivebyte ; else receive next character
|
||||
cpi tmp1, 0 ; rxbyte = 0 will branch
|
||||
breq chpwee ; to confirm emergency erase
|
||||
cp tmp1, tmp3 ; compare password with rxbyte
|
||||
breq chpw0 ; if equal check next character
|
||||
clr tmp4 ; tmp4 = 0 to loop forever
|
||||
rjmp chpw1 ; and smoothen power profile
|
||||
chpwee:
|
||||
; Fix for ISSUE #1: only check for Emergency Erase if we haven't
|
||||
; gotten a wrong password; if we got a wrong password
|
||||
; then we should stay in loop and not escape to Emergency
|
||||
; Erase
|
||||
cpi tmp4, 0 ; if tmp4=0 we are set to loop forever
|
||||
breq chpw1
|
||||
rcall RequestConfirm ; request confirm
|
||||
brts chpa ; not confirmed, leave
|
||||
rcall RequestConfirm ; request 2nd confirm
|
||||
brts chpa ; can't be mistake now
|
||||
rcall EmergencyErase ; go, emergency erase!
|
||||
rjmp Mainloop
|
||||
chpa:
|
||||
rjmp APPJUMP ; start application
|
||||
chpwx:
|
||||
; rjmp SendDeviceInfo ; go on to SendDeviceInfo
|
||||
|
||||
;-----------------------------------------------------------------------
|
||||
; SEND DEVICEINFO
|
||||
;-----------------------------------------------------------------------
|
||||
|
||||
SendDeviceInfo:
|
||||
ldi zl, low (DEVICEINFO*2) ; load address of deviceinfo
|
||||
ldi zh, high(DEVICEINFO*2) ; low and highbyte
|
||||
ldi bcnt, INFOLEN*2
|
||||
rcall SendFromFlash
|
||||
|
||||
;-----------------------------------------------------------------------
|
||||
; MAIN LOOP TO RECEIVE AND EXECUTE COMMANDS
|
||||
;-----------------------------------------------------------------------
|
||||
|
||||
Mainloop:
|
||||
clr zl ; clear Z pointer
|
||||
clr zh ; which is frequently used
|
||||
rcall SendConfirm ; send CONFIRM via RS232
|
||||
rcall Receivebyte ; receive command via RS232
|
||||
rcall CheckCommands ; check command letter
|
||||
rjmp Mainloop ; and loop on
|
||||
|
||||
;-----------------------------------------------------------------------
|
||||
; CHANGE USER DATA IN LASTPAGE
|
||||
;-----------------------------------------------------------------------
|
||||
|
||||
ChangeSettings:
|
||||
rcall GetNewPage ; get new LASTPAGE contents
|
||||
brtc ChangeS0 ; from Host (if confirmed)
|
||||
ret
|
||||
ChangeS0:
|
||||
rcall ZtoLASTPAGE ; re-write LASTPAGE
|
||||
rcall EraseFlashPage
|
||||
rcall WritePage ; erase and write LASTPAGE
|
||||
|
||||
;-----------------------------------------------------------------------
|
||||
; SEND USER DATA FROM LASTPAGE
|
||||
;-----------------------------------------------------------------------
|
||||
|
||||
ControlSettings:
|
||||
rcall ZtoLASTPAGE ; point to LASTPAGE
|
||||
; rcall SendPageFromFlash
|
||||
|
||||
;-----------------------------------------------------------------------
|
||||
; SEND DATA FROM FLASH MEMORY
|
||||
;-----------------------------------------------------------------------
|
||||
|
||||
SendPageFromFlash:
|
||||
ldi bcnt, low (PAGESIZE*2) ; whole Page to send
|
||||
SendFromFlash:
|
||||
rcall SPMwait ; (re)enable RWW read access
|
||||
lpm tmp1, z+ ; read directly from flash
|
||||
rcall Transmitbyte ; and send out to RS232
|
||||
dec bcnt ; bcnt is number of bytes
|
||||
brne SendFromFlash
|
||||
ret
|
||||
|
||||
;-----------------------------------------------------------------------
|
||||
; READ APPLICATION FLASH
|
||||
;-----------------------------------------------------------------------
|
||||
; read and transmit application flash area (pagewise)
|
||||
|
||||
ReadAppFlash:
|
||||
RAF0:
|
||||
rcall RwaitConfirm
|
||||
brts RAFx
|
||||
rcall SendPageFromFlash
|
||||
RAF1:
|
||||
cpi zl, low (LASTPAGE*2) ; count up to last byte
|
||||
brne RAF0 ; below LASTPAGE
|
||||
cpi zh, high(LASTPAGE*2)
|
||||
brne RAF0
|
||||
RAFx:
|
||||
ret
|
||||
|
||||
;-----------------------------------------------------------------------
|
||||
; WRITE APPLICATION FLASH
|
||||
;-----------------------------------------------------------------------
|
||||
; Write Appflash pagewise, don't modify anything for ATmegas
|
||||
|
||||
WriteAppFlash:
|
||||
rcall EraseAppFlash ; Erase whole app flash
|
||||
Flash2:
|
||||
rcall GetNewPage ; get next page from host
|
||||
brts FlashX ; stop on user's behalf
|
||||
Flash3:
|
||||
rcall WritePage ; write page data into flash
|
||||
Flash4:
|
||||
cpi zh, high(LASTPAGE*2-1) ; end of available Appflash?
|
||||
brne Flash2 ; if Z reached last location
|
||||
cpi zl, low (LASTPAGE*2-1) ; then we are finished
|
||||
brne Flash2 ; else go on
|
||||
FlashX:
|
||||
ret ; we're already finished!
|
||||
|
||||
;-----------------------------------------------------------------------
|
||||
; WRITE FLASH PAGE FROM BUFFER, VERIFYING AND VERIFY-ERROR-HANDLING
|
||||
;-----------------------------------------------------------------------
|
||||
|
||||
WritePage:
|
||||
rcall YtoBUFFER ; Y=BUFFER, bcnt=PAGESIZE*2
|
||||
WrPa1:
|
||||
ld r0, y+ ; fill R0/R1 with word
|
||||
ld r1, y+ ; from buffer position Y / Y+1
|
||||
ldi tmp1, 0b00000001 ; set only SPMEN in SPMCSR
|
||||
out SPMCSR, tmp1 ; to activate page buffering
|
||||
spm ; store word in page buffer
|
||||
adiw zl, 2 ; and forward to next word
|
||||
subi bcnt, 2
|
||||
brne WrPa1
|
||||
; Z = start of next page now
|
||||
subi zl, low (PAGESIZE*2) ; point back Z to
|
||||
sbci zh, high(PAGESIZE*2) ; start of current page
|
||||
; Z = back on current page's start
|
||||
WrPa2:
|
||||
ldi tmp1, 0b00000101 ; enable PRWRT + SPMEN
|
||||
out SPMCSR, tmp1 ; in SPMCSR
|
||||
spm ; write whole page to flash
|
||||
WrPa3:
|
||||
in tmp1, SPMCSR ; wait for flash write finished
|
||||
sbrc tmp1, 0 ; skip if SPMEN (bit0) cleared
|
||||
rjmp WrPa3 ; ITS BEEN WRITTEN
|
||||
subi zl, low (-PAGESIZE*2) ; same effect as
|
||||
sbci zh, high(-PAGESIZE*2) ; Z = Z + PAGESIZE*2
|
||||
ret
|
||||
|
||||
;-----------------------------------------------------------------------
|
||||
; CHECK COMMANDS
|
||||
;-----------------------------------------------------------------------
|
||||
|
||||
CheckCommands:
|
||||
cpi tmp1, 'c' ; read LASTPAGE
|
||||
breq ControlSettings
|
||||
cpi tmp1, 'C' ; write LASTPAGE
|
||||
breq ChangeSettings
|
||||
cpi tmp1, 'f' ; read Appflash
|
||||
breq ReadAppFlash
|
||||
cpi tmp1, 'F' ; write Appflash
|
||||
breq WriteAppFlash
|
||||
cpi tmp1, 'e' ; read EEPROM
|
||||
breq EepromRead
|
||||
cpi tmp1, 'E' ; write EEPROM
|
||||
breq EEpromWrite
|
||||
rjmp APPJUMP ; else start application
|
||||
|
||||
;-----------------------------------------------------------------------
|
||||
; EEPROM READ/WRITE ACCESS
|
||||
;-----------------------------------------------------------------------
|
||||
|
||||
EepromWrite:
|
||||
EEWr0:
|
||||
rcall GetNewPage ; get EEPROM datablock
|
||||
brts EERWFx ; or abort on host's demand
|
||||
EEWr1:
|
||||
rcall YtoBUFFER ; Y = Buffer and Bcnt = blocksize
|
||||
EEWr2:
|
||||
ld tmp1, y+ ; read EEPROM byte from buffer
|
||||
rcall EEWriteByte
|
||||
dec bcnt ; count down block byte counter
|
||||
brne EEWr2 ; loop on if block not finished
|
||||
rjmp EeWr0
|
||||
|
||||
;-----------------------------------------------------------------------
|
||||
|
||||
EEpromRead:
|
||||
EeRe1:
|
||||
rcall RwaitConfirm ; wait to confirm
|
||||
brts EERWFx ; else we are finished
|
||||
ldi bcnt, low(PAGESIZE*2) ; again PAGESIZE*2 is blocksize
|
||||
EERe2:
|
||||
out EEARL, zl ; current EEPROM address low
|
||||
.ifdef EEARH
|
||||
out EEARH, zh ; current EEPROM address high
|
||||
.endif
|
||||
sbi EECR, 0 ; set EERE - EEPROM read enable
|
||||
in tmp1, EEDR ; read byte from current address
|
||||
rcall Transmitbyte ; send out to RS232
|
||||
adiw zl,1 ; count up EEPROM address
|
||||
dec bcnt ; count down block byte counter
|
||||
brne EERe2 ; loop on if block not finished
|
||||
rjmp EERe1
|
||||
EERWFx:
|
||||
ret
|
||||
|
||||
;-----------------------------------------------------------------------
|
||||
|
||||
EEWriteByte:
|
||||
out EEDR, tmp1 ; write to EEPROM data register
|
||||
out EEARL, zl ; current EEPROM address low
|
||||
.ifdef EEARH
|
||||
out EEARH, zh ; high EEARH for some attinys
|
||||
.endif
|
||||
sbi EECR, 2 ; EEPROM master prog enable
|
||||
sbi EECR, 1 ; EEPE initiate prog cycle
|
||||
EeWB:
|
||||
sbic EECR, 1 ; wait write cycle to complete
|
||||
rjmp EeWB ; before we can go on
|
||||
adiw zl,1 ; count up EEPROM address
|
||||
ret
|
||||
|
||||
;-----------------------------------------------------------------------
|
||||
; GET NEW PAGE
|
||||
;-----------------------------------------------------------------------
|
||||
|
||||
GetNewPage:
|
||||
rcall RequestConfirm ; check for Confirm
|
||||
brts GNPx ; abort if not confirmed
|
||||
GNP0:
|
||||
rcall YtoBUFFER ; Y = BUFFER, bcnt = PAGESIZE*2
|
||||
GNP1:
|
||||
rcall ReceiveByte ; receive serial byte
|
||||
st y+, tmp1 ; and store in buffer
|
||||
dec bcnt ; until full page loaded
|
||||
brne GNP1 ; loop on
|
||||
GNPx:
|
||||
ret ; finished
|
||||
;-----------------------------------------------------------------------
|
||||
; REQUEST TO CONFIRM / AWAIT CONFIRM COMMAND
|
||||
;-----------------------------------------------------------------------
|
||||
|
||||
RequestConfirm:
|
||||
ldi tmp1, REQUEST ; send request character
|
||||
rcall Transmitbyte ; prompt to confirm (or not)
|
||||
|
||||
RwaitConfirm:
|
||||
rcall ReceiveByte ; get host's reply
|
||||
clt ; set T=0 for confirmation
|
||||
cpi tmp1, CONFIRM ; if host HAS sent CONFIRM
|
||||
breq RCx ; return with the T=0
|
||||
set ; else set T=1 (NOT CONFIRMED)
|
||||
RCx:
|
||||
ret ; whether confirmed or not
|
||||
|
||||
;-----------------------------------------------------------------------
|
||||
; FLASH ERASE TOP-TO-BOTTOM ( (BOOTSTART-1) ... $0000)
|
||||
;-----------------------------------------------------------------------
|
||||
|
||||
EraseAppFlash:
|
||||
rcall ZtoLASTPAGE ; point Z to LASTPAGE, directly
|
||||
EAF0:
|
||||
subi zl, low (PAGESIZE*2)
|
||||
sbci zh, high(PAGESIZE*2)
|
||||
rcall EraseFlashPage
|
||||
brne EAF0 ; until first page reached
|
||||
EAFx: ret ; and leave with Z = $0000
|
||||
|
||||
;-----------------------------------------------------------------------
|
||||
; EMERGENCY ERASE OF FLASH / EEPROM / USERDATA
|
||||
;-----------------------------------------------------------------------
|
||||
|
||||
EmergencyErase:
|
||||
rcall EraseAppFlash ; erase Application Flash
|
||||
ser tmp1 ; byte value for EEPROM writes
|
||||
EEE0:
|
||||
rcall EEWriteByte ; write EEPROM byte, Z = Z + 1
|
||||
cpi zh, high(EEPROMEND+1)+2 ; EEPROMEND
|
||||
brne EEE0 ; and loop on until finished
|
||||
|
||||
rcall ZtoLASTPAGE ; LASTPAGE is to be erased
|
||||
; rcall EraseFlashPage
|
||||
|
||||
;-----------------------------------------------------------------------
|
||||
; ERASE ONE FLASH PAGE
|
||||
;-----------------------------------------------------------------------
|
||||
|
||||
EraseFlashPage:
|
||||
ldi tmp1, 0b00000011 ; enable PGERS + SPMEN
|
||||
out SPMCSR, tmp1 ; in SPMCSR and erase current
|
||||
spm ; page by SPM (MCU halted)
|
||||
|
||||
; Waiting for SPM to be finished is *obligatory* on ATmegas!
|
||||
SPMwait:
|
||||
in tmp1, SPMCSR
|
||||
sbrc tmp1, 0 ; wait previous SPMEN
|
||||
rjmp SPMwait
|
||||
ldi tmp1, 0b00010001 ; set RWWSRE and SPMEN
|
||||
out SPMCSR, tmp1
|
||||
spm
|
||||
ret
|
||||
|
||||
;-----------------------------------------------------------------------
|
||||
; OTHER SUBROUTINES
|
||||
;-----------------------------------------------------------------------
|
||||
|
||||
YtoBUFFER:
|
||||
ldi yl, low (BUFFER) ; reset pointer
|
||||
ldi yh, high(BUFFER) ; to programming buffer
|
||||
ldi bcnt, low(PAGESIZE*2) ; and often needed
|
||||
ret
|
||||
|
||||
;-----------------------------------------------------------------------
|
||||
|
||||
ZtoLASTPAGE:
|
||||
ldi zl, low (LASTPAGE*2) ; reset Z to LASTPAGE start
|
||||
ldi zh, high(LASTPAGE*2)
|
||||
ret
|
||||
|
||||
;-----------------------------------------------------------------------
|
||||
; RS232 RECEIVE BYTE
|
||||
;-----------------------------------------------------------------------
|
||||
|
||||
; uses: tmp1 (received data byte), cntr1 (for timeout)
|
||||
; also uses utimeoutH which holds the default timeout defined by the user
|
||||
; and X which is actually used to count down
|
||||
SetRX:
|
||||
ldi tmp1,(1<<RXEN0) ; enable receiver (Transmitter disabled)
|
||||
sts UCSR0B,tmp1
|
||||
ser rxen
|
||||
|
||||
ReceiveByte:
|
||||
sbrs rxen, 0
|
||||
rjmp SetRX
|
||||
|
||||
; outer counter
|
||||
mov xh, utimeoutH
|
||||
;ldi xl, 128
|
||||
|
||||
ReceiveByteShortTimeout:
|
||||
ser cntr1 ; inner counter reset
|
||||
|
||||
ReceiveByteShortTimeout1:
|
||||
lds tmp1, UCSR0A ; load UART status register A
|
||||
sbrc tmp1, RXC0 ; if not RXComplete, skip
|
||||
rjmp LoadRXByte
|
||||
dec cntr1 ; if counter not zero
|
||||
brne ReceiveByteShortTimeout1 ; cycle again; else fall through
|
||||
sbiw xl, 1 ; dec outter counter
|
||||
brcc ReceiveByteShortTimeout ; continue of outter counetr still active
|
||||
;ret ;
|
||||
|
||||
LoadRXByte:
|
||||
lds tmp1, UDR0 ; load received character even if RXC is not set
|
||||
ret ; (it loads 0 and UDR FIFO should recover for next char)
|
||||
|
||||
;-----------------------------------------------------------------------
|
||||
; RS232 SEND CONFIRM CHARACTER
|
||||
;-----------------------------------------------------------------------
|
||||
|
||||
SendConfirm:
|
||||
ldi tmp1, CONFIRM
|
||||
rjmp Transmitbyte
|
||||
|
||||
;-----------------------------------------------------------------------
|
||||
; RS232 TRANSMIT BYTE
|
||||
;-----------------------------------------------------------------------
|
||||
; uses: tmp1 (transmit byte will be shifted out), tmp2 (bitcounter)
|
||||
;
|
||||
|
||||
SetTX:
|
||||
ldi tmp2,(1<<TXEN0) ; enable transmitter (Receiver disabled)
|
||||
sts UCSR0B,tmp2
|
||||
clr rxen
|
||||
|
||||
; wait some guard time to allow receiving devices ot transition
|
||||
; from TX t RX state
|
||||
ser cntr1 ; inner counter reset
|
||||
SetTXShortTimeout:
|
||||
nop
|
||||
dec cntr1 ; if counter not zero
|
||||
brne SetTXShortTimeout ; cycle again; else fall through
|
||||
|
||||
|
||||
TransmitByte:
|
||||
sbrc rxen, 0
|
||||
rjmp SetTX
|
||||
|
||||
; no need to wait for UDRE bc we will wait for TXC on
|
||||
; every char transmitted. TXC occurs later that UDRE
|
||||
; so UDRE should be asserted when TXC asserts
|
||||
sts UDR0, tmp1
|
||||
|
||||
WaitForTXC:
|
||||
lds tmp2, UCSR0A ; wait for TXC (and not UDRE)
|
||||
sbrs tmp2, TXC0 ; bc after this char we may transition
|
||||
rjmp WaitForTXC ; to receiving chars and we want to make sure we get a clean transition
|
||||
; we need to write a 1 to clear the TXC flag; otherwise the flag won't clear
|
||||
sts UCSR0A, tmp2 ; tmp2 should contain an asserted TXC bit
|
||||
ret
|
||||
|
||||
|
||||
;-----------------------------------------------------------------------
|
||||
; ATMEGA APPJUMP = SIMPLE JUMP TO $0000 (ORIGINAL RESET VECTOR)
|
||||
;-----------------------------------------------------------------------
|
||||
; Boot Reset Vector (BOOTRST) must be activated for TSB on ATmegas.
|
||||
; After timeout or executing commands, TSB for ATmegas will simply
|
||||
; handover to the App by a (relative or absolute) jump to $0000.
|
||||
|
||||
APPJUMP:
|
||||
rcall SPMwait ; make sure everything's done
|
||||
|
||||
.if FLASHEND >= ($1fff)
|
||||
jmp $0000 ; absolute jump
|
||||
.else
|
||||
rjmp $0000 ; relative jump
|
||||
.endif
|
||||
|
||||
|
||||
DEVICEINFO:
|
||||
.message "DEVICE INFO BLOCK FOR ATMEGA"
|
||||
.db "TSB", low (BUILDDATE), high (BUILDDATE), BUILDSTATE
|
||||
.db SIGNATURE_000, SIGNATURE_001, SIGNATURE_002, low (PAGESIZE)
|
||||
.dw BOOTSTART-PAGESIZE
|
||||
.dw EEPROMEND
|
||||
.db $AA, $AA
|
||||
|
||||
|
||||
;-----------------------------------------------------------------------
|
||||
; DEVICE INFO BLOCK = PERMANENT DATA
|
||||
;-----------------------------------------------------------------------
|
||||
|
||||
; set last word with the clock speed
|
||||
.org FLASHEND
|
||||
.dw (F_CPU/1000000)
|
||||
|
||||
//.message "SAVING CLOCK SPEED IN LAST BYTE AS " (F_CPU/1000000) " Mhz"
|
||||
|
||||
.message "ASSEMBLY OF TSB FOR ATMEGA SUCCESSFULLY FINISHED!"
|
||||
|
||||
.endif ; closing TSB for ATmega sourcecode;
|
||||
|
||||
;***********************************************************************
|
||||
; END OF TSB FOR ATMEGAS
|
||||
;***********************************************************************
|
||||
|
||||
.exit
|
||||
|
||||
;***********************************************************************
|
||||
;***********************************************************************
|
||||
;***********************************************************************
|
||||
; END OF CONDITIONAL ASSEMBLY SOURCE OF TSB FOR ATTINYS AND ATMEGAS
|
||||
;***********************************************************************
|
||||
;***********************************************************************
|
||||
;***********************************************************************
|
||||
|
||||
|
||||
Reference in New Issue
Block a user