At a high level, you would turn your main into functions the same as if it were in a high level language, or pseudo code. A function is a unit of abstraction, parameter passing is one key to the abstraction of functions. Sometimes, however, we create a function that either is used only once or doesn't take any parameter (a weak abstraction), just to keep the main code a bit less cluttered.
As far as assembly language goes: You'll need a calling convention, as guidance at least.
In assembly we can customize the calling convention for each routine (though some consideration is required for function pointers, a feature you may not be using).
The calling convention will say what registers and/or stack to pass parameters, and how to receive the return value. One convention is pass all parameters on the stack, but some others pass (some) arguments in registers, which is generally preferred, I think. When passing parameters on the stack, you have the issue of who (caller or callee) deallocates them (pop them off the stack).
On some machines, parameters are passed in global variables (memory) instead of on the stack. I think this is generally inefficient (and also makes the functions non-re-entrant, not capable of recursion).
Another approach is the very old "inline" parameter passing approach, where a parameter block of constants or pointers are placed immediately after the call instruction, and the callee picks them up from the code of the caller, and returns to after the parameter block. This is great for the original FORTRAN where parameters are always passed by reference.
A calling convention will also say if there are any call-preserved registers. On a machine with so limited registers, this is a two-edged sword, in that, if you use some registers as call-preserved, then you cannot use them for parameter passing, but callers will be able to keep some values in registers across a call. A caller that wants to use a call-preserved register will assume that register is already in use by some function further up in the call chain, and so will push the register in function prologue and pop to restore that register in epilogue, so that the call-preserved register can be temporarily repurposed for this function.
Again, in assembly, we can customize the calling convention per routine, knowing the implementation of that routine and the implementation of the caller, you should be able to establish a fairly efficient approach to parameter passing. Suggest for each routine then, documenting both parameters (i.e. in registers), return values, and also what registers are clobbered vs. which are preserved. (At some point, you may find that a common convention is more useful than customizing for each routine.)