Now that I took care of the hardware, it's time that I'll work my toolchain.
As mentioned, I want to use an open-source toolchain, and SDCC looks like a good choice. The suite has an assembler called SDAS, a linker, and some other stuff. As I want to use assembler, I must tackle SDAS.
SDAS is said to be based on the ASXXXX suite of assemblers which supports a hell lot of architectures. Still, I found little to no examples of use, and as it raises errors for sources that work on vanilla 8051 assemblers such as A51, I had to find other kinds of information.
For more information, I found a webpage with documentation for the original ASXXXX assembler. Specifically, I found the directives page very enlighting. But alone it's not enough for me to write an assembly code from scratch.
One thing I did was to compile a C file using SDCC and look at the output .asm file. So I've written a basic blink that looks somewhat like this:
#include <stdint.h>
#include <8051.h>
void main() {
uint16_t i;
while(1){
for (i = 0; i == 0xFFFF; i++){}
P3 ^= 0x04;
}
}
This code was able to compile, but the resulting .hex file did not blink the LED. I probably haven't done it right, as SFRs may need special attention.
Lets look at the resulting assembly:
;--------------------------------------------------------
; File Created by SDCC : free open source ANSI-C Compiler
; Version 3.9.0 #11195 (MINGW64)
;--------------------------------------------------------
.module blink
.optsdcc -mmcs51 --model-small
;--------------------------------------------------------
; Public variables in this module
;--------------------------------------------------------
.globl _main
.globl _CY
.globl _AC
.globl _F0
...
The first thing is defining a module. After it is some special comand for sdcc. Then there are a whole lot of global variables, corresponding to special bits and registers. Note how directives start with a dot sign, unlike vanilla assemblers.
Then came this:
... .globl _SP .globl _P0 ;-------------------------------------------------------- ; special function registers ;-------------------------------------------------------- .area RSEG (ABS,DATA) .org 0x0000 _P0 = 0x0080 _SP = 0x0081 _DPL = 0x0082 _DPH = 0x0083 _PCON = 0x0087 _TCON = 0x0088 ...
It declares something as an area, probably calling it a registers segment, with the ABS and DATA parameters. The ABS flag means, as I have learned later, using absolute locations for the code, thus the .org 0x0000 directive after it means that this segment of code starts at 0th address. Dunno about the DATA flag though. However it doesn't seem important, as this part only looks like a '#define' section.
Lets move on. The following lines contain an awful lot of these directives, without any real code, until we find the interrupt vector:
;-------------------------------------------------------- ; interrupt vector ;-------------------------------------------------------- .area HOME (CODE) __interrupt_vect: ljmp __sdcc_gsinit_startup ;-------------------------------------------------------- ; global & static initialisations ;-------------------------------------------------------- .area HOME (CODE) .area GSINIT (CODE) .area GSFINAL (CODE) .area GSINIT (CODE) .globl __sdcc_gsinit_startup .globl __sdcc_program_startup .globl __start__stack .globl __mcs51_genXINIT .globl __mcs51_genXRAMCLEAR .globl __mcs51_genRAMCLEAR .area GSFINAL (CODE) ljmp __sdcc_program_startup ;-------------------------------------------------------- ; Home ;-------------------------------------------------------- .area HOME (CODE) .area HOME (CODE) __sdcc_program_startup: ljmp _main ; return from main will return to caller
Behold, a reset vector! It makes an LJMP to initialisations, which came out null for this piece of code. When it's done, it LJMPs us to the '__sdcc_program_startup' label which directly jumps us to the main function. This is probably akin to the '_start()' function of GCC.
Note how this time all the .area directives say CODE, rather than DATA.
Here comes the real fun:
;-------------------------------------------------------- ; code ;-------------------------------------------------------- .area CSEG (CODE) ;------------------------------------------------------------ ;Allocation info for local variables in function 'main' ;------------------------------------------------------------ ;i Allocated to registers r6 r7 ;------------------------------------------------------------ ; blink.c:7: void main() { ; ----------------------------------------- ; function main ; ----------------------------------------- _main: ar7 = 0x07 ar6 = 0x06 ar5 = 0x05 ar4 = 0x04 ar3 = 0x03 ar2 = 0x02 ar1 = 0x01 ar0 = 0x00 ; blink.c:14: for (i = 0; i == 0xFFFF; i++){ 00111$: mov r6,#0x00 mov r7,#0x00 00106$: cjne r6,#0xff,00101$ cjne r7,#0xff,00101$ inc r6 cjne r6,#0x00,00106$ inc r7 sjmp 00106$ 00101$: ; blink.c:17: P3 ^= 0x04; mov r6,_P3 mov r7,#0x00 xrl ar6,#0x04 mov _P3,r6 ; blink.c:20: } sjmp 00111$ .area CSEG (CODE) .area CONST (CODE) .area XINIT (CODE) .area CABS (ABS,CODE)
Code! finally. It looks quite ugly though, using these MOVs and XORs rather than a CPL P3.2 opcode. However, the code in this assembler looks kinda like you'd expect assembly to be looking like - nothing too different than other assemblers.
I also found a single piece of source code for this thing, in the archive of what seems to be a university lab private mailing list archive. It makes a little FM synth called usynth, and seems to be written directly in assembly. This isn't the first place I'd look for information at, but I'll take whatever I can right now. It looks similar to what I already know from looking on SDCC output. Notably this part looks all familiar:
.area INTV (ABS)
.org 0x0000
_int_reset:
ljmp _start
.org 0x0003
_int_ex0:
reti
.org 0x000b
_int_t0:
ljmp T0_ISR
.ds 5
.area CSEG (ABS,CON)
.org 0x0080
_start:
clr IE.7
This is pretty much all I need it seems. Good enough, I guess I know now how to use the assembler now. Lets get going to writing our own code :)
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.
>It looks quite ugly though, using these MOVs and XORs rather than a CPL P3.2 opcode.
To get SDCC to generate the optimal cpl code you have to specify the bit in the port, not the whole port, i.e.
P3_2 ^= 1;
See https://hackaday.io/project/162267/logs
Are you sure? yes | no
Thank you for your input, I'll definitely check your tuner project :D
Though I must say that I'd still expect SDCC to optimize it into a CPL, given a corresponding flag at compilation time.
Are you sure? yes | no
It's not that smart. And anyway operating on the bit expresses your intent better, no need to convert the bit position into a mask.
Are you sure? yes | no