Free RAM on every board - #99
Conversation
AT_CMD_MAXLEN is 69 rather than 16 on CPU_SI1030 boards. The only command that long is AT&E= with a 64 character AES key, which is inside #ifdef INCLUDE_AES, so ask for that instead of the CPU. It sizes both at_cmd and the remote command buffer in tdm.c, so on mro900, rfd900p/pe and rfd900u/ue this frees 53 bytes of the 256 byte pdata page and 53 bytes of xdata, with the generated code unchanged. AES builds keep the long buffer.
vprintfl() always calls __ultoa()/__ltoa(), whatever the format string. SDCC's versions carry their own 32 byte buffer, which in --model-large is statically allocated in xdata, 49 bytes in total, and printfl already has a 12 byte buffer of its own for exactly this. Emit the digits backwards into that buffer instead. Uppercase hex and the minus sign for radix 10 match what SDCC's versions do, and the longest result, -2147483648, still fits. Frees 49 bytes of xdata and 257 bytes of flash on every board.
encrypt_buff_start/end and encrypt_insert/remove are declared for CPU_SI1030, but every use of them is inside #ifdef INCLUDE_AES, apart from a reset in serial_init() that does nothing without them. Ask for the same thing the users do. Frees 8 bytes of the 256 byte pdata page on mro900, rfd900p/pe and rfd900u/ue.
rx_buf and tx_buf are ring buffers, written before they are read, and
their insert and remove indices are set in serial_init(). Initialising
them to {0} only moves them from XSEG to XISEG and puts a matching all
zero image of 2495 bytes (2048 on CPU_SI1030 boards) in flash, which the
startup code then copies. RAM use is the same either way.
Frees 2495 bytes of flash on hm_trp and the other 4KB boards, 2048 on the
8KB ones.
remote_at_cmd only ever goes through memcpy() and strlen(), so it does not need to be in the 256 byte pdata page that the CPU can address more cheaply. On mro900, rfd900p/pe and rfd900u/ue that page is the binding limit, while xdata has kilobytes to spare; on the 4KB boards the two share the same memory, so nothing changes there. The generated code is identical.
Previous review (2026-09-20)Automated review note — AI-generated (Claude), validated against the live diff (Claude + Codex cross-checked). Please sanity-check before acting. Full report: https://uav.tridgell.net/DevCallReviews/2026_09_20/devcall_pr_reviews.html#prSiK-99 Verdict: COMMENT at head The savings, rebuilt independently
The absolutes agree with your table exactly on the 1060 group (pdata 191 → 174, free RAM 13 → 62) and sit 4 bytes tighter elsewhere — sdcc 4.5.0 against your 4.2.0. Every delta is identical on every board: −17 pdata and −49 bytes of xdata end on the 4 KB boards, −78 pdata on the 8 KB ones, 2761 and 2342 bytes of flash. Worth noting how tight master already is on this toolchain: the five CPU_SI1030 boards have 5 bytes of the 256-byte pdata page left, which is the same page #97 overflows at 259/256 — so this gives that one 78 bytes of headroom. ISSUE —
|
-val is undefined when val is the most negative long. It does the right thing on sdcc, and the __ltoa() this replaced negated the same way, but the unsigned form costs nothing.
check_code.py looks for the XISEG area, which holds initialised xdata. A firmware without any is perfectly normal, and then the area is missing from the map, the loop body never runs, and the check silently passes without even printing the memory summary the build log has always shown. Look at XSEG as well and take whichever area ends higher.
|
Thanks, and thanks for rebuilding every board independently.
The AT command limit on the CPU_SI1030 boards. Added to the description, including the part I had not spelled out: an over-long local command does not truncate, it drops the radio out of AT mode back into passthrough ( One note from your numbers that matters for the order of these PRs: on sdcc 4.5.0 master leaves 5 bytes of the pdata page on those boards, and #97 needs 8, so #97 wants this PR in front of it. I will rebase #97 onto master once this lands, rather than stacking them. |
|
Automated review note — AI-generated (Claude), cross-checked by an independent Codex pass against the live diff. Please sanity-check before acting. Follow-up report: https://uav.tridgell.net/DevCallReviews/followups/2026_09_21_0847/devcall_pr_reviews.html#prSiK-99 · Also refreshed in the AIReview report Re-reviewed at head Verdict: APPROVE — all three points from last round are addressedMoving this off COMMENT. I rebuilt all twelve boards at three revisions rather than reading the diff for it — sdcc 4.5.0 #15242, The
Restored numbers are 62/62/80/62/4614/80/80/67/4610/4610/4617/4617, matching your table. The error path works too: doctoring
A correction I owe youMy previous table gave the 8 KB boards' xdata free as "4446+ → 4556+". That head figure was wrong. The measured values are 4610 / 4614 / 4617 — a gain of 164 bytes, not 110. The 164 is 79 bytes of the xdata high-water mark moving down because PSEG shrank (pdata lives inside the xram address space on these parts) plus 85 bytes of actual xdata content. The master column was right, as were all the 4 KB numbers. Sorry — that one was mine, and no action is needed from you. Two notes, neither asking for a change
An independent cold Codex review — diff and head checkout only, no sight of any of the above — also returned APPROVE, having modelled the formatter over 262,144 16-bit cases plus the 32-bit boundaries and exercised the RAM-check function against synthetic XSEG-only, XISEG-only, combined and overflow maps. |
|
@tridge any objections against getting this one in as a first step? |
Summary
Five small changes that free RAM on every board, without changing behaviour on the air or the parameters. RAM is the binding constraint on this firmware: before these, the 8KB boards had 9 of their 256 byte pdata page left, and 3dr1060/hb1060/ism01a had 13 bytes of XRAM.
Measured from
obj/<board>/radio~<board>/radio~<board>.memwith sdcc 4.2.0, before and after. All 12 boards build andtests/test_build.shpasses.The changes
at: the long AT command buffer is only for AES builds.AT_CMD_MAXLENis 69 instead of 16 on CPU_SI1030 boards, for the 64 character key ofAT&E=, which is inside#ifdef INCLUDE_AES. Asking for that instead of the CPU shrinksat_cmdand the remote command buffer together: 53 bytes of pdata and 53 of xdata on the 8KB boards, identical generated code. AES builds keep the long buffer.printfl: convert longs in place.vprintfl()always calls__ultoa()/__ltoa(), whose 32 byte buffer is statically allocated in xdata in--model-large: 49 bytes, while printfl already has a 12 byte buffer for the same purpose. Uppercase hex and the minus sign for radix 10 match SDCC's behaviour, and-2147483648still fits. 49 bytes of xdata and 257 of flash on every board, which is what the three tightest boards needed.serial: the encrypt ring pointers are only for AES builds. They are declared for CPU_SI1030 but every use is inside#ifdef INCLUDE_AES. 8 bytes of pdata on the 8KB boards.serial: no zeroed image of the serial buffers.= {0}on two ring buffers that are written before they are read only moves them to XISEG and puts an all zero image in flash for the startup code to copy. Same RAM either way, 2495 bytes of flash on the 4KB boards and 2048 on the others.tdm: the remote AT command buffer leaves the pdata page. It only goes throughmemcpy()andstrlen(), so it does not need the cheaper addressing. 17 bytes of pdata on the 8KB boards, where that page is the limit; no change on the 4KB boards, where pdata and xdata share the same memory.The AT command limit is user visible on the five CPU_SI1030 boards: it drops from 69 to 16 characters, since
INCLUDE_AESis never defined to the compiler today. Nothing overflows (at_input()bounds the write,handle_at_command()rejects an over-long remote command), but a local command longer than the limit drops the radio out of AT mode back into passthrough. The longest commands in the tree areAT&UPDATEandAT&T=RSSIat 9 characters andATS15=4294967295at exactly 16.Nothing here removes AES code; the AES paths keep their buffers and are only guarded by
#ifdef INCLUDE_AESas they already were elsewhere. Note thatINCLUDE_AESis currently never defined to the compiler (include/rules.mkonly adds the sources to the include path), so those paths are not built today and CI does not exercise them.Noted while looking, not changed here
tools/check_code.pyonly checks XISEG against XRAM_SIZE. Thepdata_canaryinat.ccatches an overflow at runtime, but only becauseat.relhappens to link first. An explicit check would be worth adding.average_duty_cycleintdm.cis the only float in the firmware and drags in the float library, about 1 KB of flash for 2 bytes of RAM. An integer filter would replace it, but it changes the duty cycle arithmetic, so it is not in this PR.last_received(252 bytes,packet.c) with a CRC for the duplicate check. That trades a 1 in 65536 chance of dropping a genuine resent packet, so it wants its own discussion.