Kuroneko, I don't think that code quite works, but that is almost certainly due to my explanation not being clear.
The test code first sets a value in hub location 100 to a certain number. The cog then is attempting to overwrite that number with a new number passed in par.
In pseudo code
put a number into par
move the number from par into a temporary variable in the cog
move the number from the temporary cog to a location in hub ram
There are a few things that need to all work before this works.
I am pretty sure that this code works, but I'm less sure after doing quite a number of tests. Maybe Ross can answer that:
unsigned long par_cogject[] = { 70,71,72 }; // data to pass to cog - ignore if not used
_coginit((int)par_cogject>>2, (int)mycogject_array>>2, cognumber); // array name built from spin file name
I think this means that par will point to the location in hub that contains ascii 70 in a long.
Next part - move par to a temporary variable. I thought this was done with MOV but I'm suddenly very unsure about that after reading the Propeller manual on page 380 where it says "MOV - Set a register to a value". I thought MOV was also used to move the contents of one register to another register. Maybe it is too but the text doesn't quite read like that.
Finally, move the contents of the temporary register to hub. wrbyte does this, and it gets me every time but it is source to destination which is sort of the opposite of MOV
So, this code works correctly and returns 80
DAT
org 0
cogstart mov testvalue,#80 ' for testing
wrbyte testvalue,testaddress
jmp #cogstart
testaddress long 100 ' a hub memory location, could also use a global constant
testvalue long 65 ' ascii A
fit 496
but this code is supposed to return the value that was passed in the par array, and instead it returns 32 or 40
DAT
org 0
cogstart
mov testvalue,par
wrbyte testvalue,testaddress
jmp #cogstart
testaddress long 100 ' a hub memory location, could also use a global constant
testvalue long 65 ' ascii A
fit 496
and kuroneko's code returns the original value that was in the hub ram before the cog was started, which is what I think it ought to do but this is not what is intended
DAT
org 0
cogstart
mov testaddress,par
wrbyte testvalue,testaddress
jmp #cogstart
testaddress long 100 ' a hub memory location, could also use a global constant
testvalue long 65 ' ascii A
fit 496
So I am starting to wonder if the value is not getting into par in the first place? Is there a way of testing what catalina is putting in par?
Now we have some hybrid C and PASM code that passes not only the data to the cog, but also the location in hub ram. This means the cog becomes totally independent. I think this is the missing link needed to created self contained .cog files that a C program can load from an sd card, then reload.
This is hybrid code that needs to be compiled by a compiler that knows how to strip out the pasm part, convert it to a C array and then paste it back in. (all command line programs that already exist).
I'm rather excited about this as I managed to pass some array data from one function to another using C. Maybe I am starting to understand pointers now!
/* PASM demo for use with Catalina IDE Precompiler and Compiler */
#include <stdio.h>
/* PASM Code for compilation using SpinC and inclusion as a data file
PASM Start mycogject.spin
CON
' add your values here
_clkfreq = 80_000_000 ' 5Mhz Crystal
_clkmode = xtal1 + pll16x ' x 16
PUB Main
coginit(1,@cogstart,0) ' cog 1, cogstart, dummy value
DAT
org 0
cogstart
mov temp2, par ' parameters array
rdlong testaddress,temp2 ' hub location
add temp2,#4 ' increment counter by 4 bytes = 1 long
rdlong temp, temp2 ' get data byte
wrlong temp, testaddress ' store to hub
add temp2,#4
add testaddress,#4
rdlong temp, temp2 ' get data byte
wrlong temp, testaddress
add temp2,#4
add testaddress,#4
rdlong temp, temp2 ' get data byte
wrlong temp, testaddress
finish
jmp #finish
testaddress long 0 ' hub memory location
temp long 0 ' temp variable
temp2 long 0 ' temp variable
fit 496
PASM End
*/
void mycogject(int cognumber, unsigned long *parameters_array) // this name copied from the .spin name in the pasm section - names must match eg void mycogject matches mycogject.spin. Also first code after this must be the .h array file. Put your code after the };
{
/**
* @file mycogject_array.h
* Created with spin.binary PASM to C Array Converter.
* Copyright (c) 2011, John Doe
*/
unsigned long mycogject_array[] =
{
0xa0bc21f0, 0x08bc1c10, 0x80fc2004, 0x08bc1e10,
0x083c1e0e, 0x80fc2004, 0x80fc1c04, 0x08bc1e10,
0x083c1e0e, 0x80fc2004, 0x80fc1c04, 0x08bc1e10,
0x083c1e0e, 0x5c7c000d, 0x00000000, 0x00000000,
0x00000000
};
_coginit((int)parameters_array>>2, (int)mycogject_array>>2, cognumber); // array name built from spin file name
}
void clearscreen() // white text on dark blue background
{
int i;
for (i=0;i<40;i++)
{
t_setpos(0,0,i); // move cursor to next line
t_color(0,0x08FC); // RRGGBBxx eg dark blue background 00001000 white text 11111100
}
}
void sleep(int milliseconds) // sleep function
{
_waitcnt(_cnt()+(milliseconds*(_clockfreq()/1000))-4296);
}
char peek(int address) // function implementation of peek
{
return *((char *)address);
}
void poke(int address, char value) // function implementation of poke
{
*((char *)address) = value;
}
void main ()
{
char c;
int i;
unsigned long hubaddress=100;
unsigned long parameters_array[20]; // for passing parameters to the cog
parameters_array[0]=hubaddress; // hub address
parameters_array[1]=80; // some data to pass
parameters_array[2]=81;
parameters_array[3]=82;
clearscreen();
printf("Clock speed %u \n",_clockfreq()); // see page 28 of the propeller manual for other useful commands
printf("Catalina running in cog number %i \n",_cogid()); // integer
i=hubaddress; // location to peek and poke
poke(i,66); // poke a B, the cog should change this
printf("load cogject into cog 7\n");
mycogject(7,parameters_array); // run in cog 7
c=peek(i); // peek byte at this location
printf("Peek value at %i = character %c Ascii value %d \n",i,c,c);
i=i+4;
c=peek(i);
printf("Peek value at %i = character %c Ascii value %d \n",i,c,c);
i=i+4;
c=peek(i);
printf("Peek value at %i = character %c Ascii value %d \n",i,c,c);
while (1); // Prop reboots on exit from main()
}
Now we have some hybrid C and PASM code that passes not only the data to the cog, but also the location in hub ram. This means the cog becomes totally independent. I think this is the missing link needed to created self contained .cog files that a C program can load from an sd card, then reload.
Congrats, Dr_A. Easier integration between C and PASM is indeed one of the "missing links"!
I'll have a play with your tools when I get some time (i.e. once I get release 3.0 safely out the door).
A quick question - can Catalina run on Linux and/or a Mac?
If so, how does the directory structure work on those other computers (ie the equivalent of installing in c:\program files\catalina)
Hi Dr_A,
All Catalina releases are tested and 100% functional on both Windows and Linux. I don't have a Mac, so I don't test on that platform but some people have reported successfully building Catalina for a Mac from source.
On Linux everything is much the same except the default Catalina directory is /usr/local/lib/catalina
Thanks Ross - that is very helpful to know when thinking about an IDE.
I'm working on porting a spin object to C and I'm currently working on the standard fullduplexserial object. I know this can be done in other ways, but it is a demonstration to show loading and unloading cog code.
This is the spin code
VAR
long cog 'cog flag/id
long rx_head '9 contiguous longs
long rx_tail
long tx_head
long tx_tail
long rx_pin
long tx_pin
long rxtx_mode
long bit_ticks
long buffer_ptr
byte rx_buffer[16] 'transmit and receive buffers
byte tx_buffer[16]
...
PUB start(rxpin, txpin, mode, baudrate) : okay
'' Start serial driver - starts a cog
'' returns false if no cog available
''
'' mode bit 0 = invert rx
'' mode bit 1 = invert tx
'' mode bit 2 = open-drain/source tx
'' mode bit 3 = ignore tx echo on rx
stop
longfill(@rx_head, 0, 4)
longmove(@rx_pin, @rxpin, 3)
bit_ticks := clkfreq / baudrate
buffer_ptr := @rx_buffer
okay := cog := cognew(@entry, @rx_head) + 1
and in C I have global variables (I think that is the right way to put the variable in hub?)
unsigned long rx_head; // 9 contiguous longs
unsigned long rx_tail;
unsigned long tx_head;
unsigned long tx_tail;
unsigned long rx_pin;
unsigned long tx_pin;
unsigned long rxtx_mode;
unsigned long bit_ticks;
unsigned long buffer_ptr;
char rx_buffer[16]; // transmit and receive buffers
char tx_buffer[16];
and for the start function
unsigned long start(unsigned long rxpin,unsigned long txpin,unsigned long mode, unsigned long baudrate)
{
unsigned long okay;
rx_head = 0; // longfill(@rx_head, 0, 4)
tx_head = 0;
rx_tail = 0;
tx_tail = 0;
rx_pin = rxpin; // longmove(@rx_pin, @rxpin, 3)
tx_pin = txpin;
rxtx_mode = mode;
bit_ticks = _clockfreq() / baudrate; // bit_ticks := clkfreq / baudrate
buffer_ptr = rx_buffer[0]; // buffer_ptr := @rx_buffer
printf("Buffer is at %u \n",buffer_ptr);
return okay;
}
However, that last line is printing out zero for the buffer_ptr location. I am trying to translate this line:
buffer_ptr := @rx_buffer
Do I need to be using integers instead of unsigned longs to determine the location of an array?
... and in C I have global variables (I think that is the right way to put the variable in hub?)
No. I really must write a brief technical note about this.
Global variables in Catalina (more correctly, any variables with file scope) will be in Hub RAM only in the LMM (-x0) and XMM SMALL (-x3) memory models. In the XMM LARGE (-x5) memory model, they will be in XMM RAM.
To force a variable to be in Hub RAM in all memory models, it must be declared as a local. If you want to share it around, then declare it as a local in the 'main' function, and pass it's address to the other functions that need it. If you don't want to pass it as a parameter, you can instead store the local address in a global variable. For example (note I am at work and can't run this, but it should work!):
you can instead store the local address in a global variable.
Yes I think that is the answer. The aim is to try to replicate spin code without too many changes. So if a variable is declared in a VAR section in spin code, then it is global for that object and C should be too, otherwise too much code needs to be changed.
I have tested this as you suggest:
1) put unsigned long buffer_ptr; at the beginning of the program under stdio
2) declare the circular buffers in Main and get the pointer to them
No. I really must write a brief technical note about this.
Global variables in Catalina (more correctly, any variables with file scope) will be in Hub RAM only in the LMM (-x0) and XMM SMALL (-x3) memory models. In the XMM LARGE (-x5) memory model, they will be in XMM RAM.
To force a variable to be in Hub RAM in all memory models, it must be declared as a local. If you want to share it around, then declare it as a local in the 'main' function, and pass it's address to the other functions that need it. If you don't want to pass it as a parameter, you can instead store the local address in a global variable. For example (note I am at work and can't run this, but it should work!):
This places the variable my_buffer in the ".hub" section which my linker script then places in hub memory. This works in ZOG to declare global variables in hub memory. Maybe you could adopt a similar scheme with Catalina.
Yes, it turned out this is not so easy. This object declares its variables in a VAR section and it consists of three parts that it passes to the cog:
long rx_head '9 contiguous longs
long rx_tail
long tx_head
long tx_tail
long rx_pin
long tx_pin
long rxtx_mode
long bit_ticks
long buffer_ptr
byte rx_buffer[16] 'transmit and receive buffers
byte tx_buffer[16]
Part 1 - a list of 8 variables, in contiguous order
Part 2 - a pointer to the beginning of the circular buffers
Part 3 - the circular buffers.
When the cog is started there is a reference to the start of part 1 viz okay := cog := cognew(@entry, @rx_head) + 1
In spin the compiler assumes the variables are global, so things like setting these values are done in functions. eg bit_ticks := clkfreq / baudrate is in the Start PUB.
But - in C, if these are declared in the Main routine, they are not global, so it gets more complicated as you have to pass a reference to the list.
And I just found something else by printing out the pointers to these variables - the values are decreasing as you go down the list, 27140, 27136, 27132. However, in Spin, the values increase, and this is important because the cog expects these values to be in the right order.
So the order of all variables and arrays is going to have to be reversed!
I will need to think about this some more. It seems that a lot of spin code is treating contiguous variables as arrays, exploiting the fact that the compiler lists them in ascending order. If we can't use global variables, and the list is in the wrong order, I am wondering if it might not be easier just to explicitly create an array of variables that encompasses everything. Then you can pass that array to functions in C with just one parameter.
For example, the fullduplexserial object is 9 longs and 32 longs for the buffer. Make that a 41 long array? Ideally, setting all the values should happen in a function rather than in the main, otherwise the main is going to fill up with a lot of code. The concept of 'objects' kind of goes out the window when the main() needs so much code added. Or maybe the idea would be to only declare hub variables in the main (as arrays), then do things like splitting them up into individual variables and swapping the order and setting values in individual functions?
I guess that is allowed, since spin does tricky things like
longfill(@rx_head, 0, 4)
which is actually setting four variables to zero, even those variables are not in an array.
So - instead of 9 longs and two arrays, we have one array -
unsigned long serial[40]
and instead of variables like rx_head, we reference that with
serial[40]
and rx_tail is serial[39]
which takes into account the order reversal. Then it is just a matter of good commenting to describe how and why the spin code has been changed this way.
This way we can pass then entire block to any functions so that all the variables in this code become array values
PUB rxcheck : rxbyte
'' Check if byte received (never waits)
'' returns -1 if no byte received, $00..$FF if byte
rxbyte--
if rx_tail <> rx_head
rxbyte := rx_buffer[rx_tail]
rx_tail := (rx_tail + 1) & $F
This places the variable my_buffer in the ".hub" section which my linker script then places in hub memory. This works in ZOG to declare global variables in hub memory. Maybe you could adopt a similar scheme with Catalina.
I may look at doing something like this for a subsequent release, although I'm always horrified by such non-standard constructs. Others have suggested using the "volatile" or "static" keywords for this type of thing, but while this makes the syntax standard, it is non-standard semantics. In a way that's even worse - at least with the GNU approach you know your code will be non-portable.
However, while I agree it would simplify life for the programmer, in reality it is not a big problem since the main reason for needing hub variables is to interact with other languages - and this is always going to be non-trivial.
...
But - in C, if these are declared in the Main routine, they are not global, so it gets more complicated as you have to pass a reference to the list.
And I just found something else by printing out the pointers to these variables - the values are decreasing as you go down the list, 27140, 27136, 27132. However, in Spin, the values increase, and this is important because the cog expects these values to be in the right order.
So the order of all variables and arrays is going to have to be reversed!
You should never depend on a specific memory layout of unrelated variables in any language. In C, some variables may end up in registers and not be present in memory at all!
if you want to force a specific memory layout in C you are supposed to declare a structure. You can then pass a pointer to that instead. So you would say something like:
typedef struct {
long rx_head;
long rx_tail;
long tx_head;
long tx_tail;
long rx_pin;
long tx_pin;
long rxtx_mode;
long bit_ticks;
long buffer_ptr;
char rx_buffer[16];
char tx_buffer[16];
} struct_t;
void my_function (struct_t *my_pointer) {
/* do something to struct here */
}
void main() {
struct_t my_struct;
my_function(&my_struct);
}
I vote for known non portable. Different semantics for valid syntax are just hard for people unaware. On the other hand, seeing something stick out is hard too, but there it is, easy to see, known to need work.
I do emphasize what Ross. Do not rely on data declarations in C ending up in memory in the same order you wrote them in the code.
I just had a problem with Zog like this. I had two arrays defined consecutively, mirroring what I have in a Spin version of the same idea. Turns out that when I increased the optimization level the compiler decided to put these two arrays in memory in reverse order and everything failed.
Use structures to enforce ordering.
Also I'm with potatohead. Non portable is better. Having different semantics for the same syntax is horrible. Besides we normally use funky constructs like this when building device drivers, and platform specific stuff so portability is not such a big issue.
A highly technical discussion here for the Catalina fans (and also thinking out loud).
Ok, what I am doing is rewriting a Spin object for use in C. There is a 'tiny' mode for C that fits easily into the internal memory, but my programs tend to be larger and I ran out of space ages ago. So - given a propeller with external memory (and we have quite a smorgasbord now!), I think it could be very useful to have reloadable objects.
In general terms, hub ram is more valuable than external ram. (Hub ram is faster and it can be accessed by cogs).
So, looking at a typical object, we have two types of variables that are passed to cogs - setup values, and permanent values. For example, in a serial driver, a setup value might be the baud rate, and a permanent value might be the circular buffer. For a video object, a setup value might be the screen size, and a permanent value might be the video buffer.
Permanent values might number from zero, right up to an array that fills most of the hub ram.
Setup values tend to be less in number, maybe 10 or so.
Setup values waste space in hub ram. This becomes more relevant when multiple copies are loaded or reloaded.
Because most Spin code objects were not written for external memory there never has been a need to differentiate between these types of variables. Indeed, I'm not even sure Spin as a language would be able to explicitly place one variable in hub and the next in external memory. (One would have to use a custom PUB to do this, and these exist, both in Spin and PASM).
But C is able to determine where variables are stored (hub variables in the Main, external variables defined at the beginning of the program).
So this leads to a new generic Start for objects. Instead of passing one list of values to the PAR, we need to pass two. Setup values and permanent values.
To make these neater, one could put one list after the other, but this is not necessary. Indeed, in order to save rewriting PASM code, it probably is easier to leave the order in whatever order the pasm code happens to be.
Let's take a look at the SimpleSerial object, with new comments added as to the nature of a variable (we can determine if a variable is setup or permanent by doing a search for that variable name)
long rx_head ' permanent, and pointer to start of the variable block
long rx_tail ' permanent, used in rxcheck
long tx_head ' permanent, used in tx
long tx_tail ' permanent, used in tx
long rx_pin ' setup
long tx_pin ' setup
long rxtx_mode ' permanent, used once in tx when checking for echo
long bit_ticks ' setup
long buffer_ptr ' setup (I think?)
byte rx_buffer[16] 'permanent
byte tx_buffer[16] ' permanent
So this order stays the same as this is the pasm order. What we need to do in our C program is pass these variables in the correct way.
Thinking about external memory management means looking at a C program in a slightly different way. For instance, where do you define the setup value "rx_pin"
1) As a constant (probably not the best option when one might be loading multiple copies of an object)
2) As a variable at the beginning of the program, underneath the #includes. This stores the value in external memory.
3) In the Main program (possibly where one usually might store this, but not the best place in this instance as it wastes a hub memory location
4) In a local function. The value will be disposed of when the function ends. On the other hand, it might be harder to find the value in amongst the code.
5) As a number in the function call eg serialstart(31,30)
6) As a constant read from a list where many constants are defined, eg at the beginning just under the #includes, serialporttx1 = 31, serialporttx2 = 29, serialporttx3=27
Option 6 may be the closest to existing spin, but I'm still thinking about this.
For 'permanent' values, one might have one list for an object, but one might also have multiple lists. I took a look at structures. These do look a good way of passing values in a group, though one minor disadvantage might be the dot notation, which in C refers to variables and in Spin refers to objects.
So - we define the setup values as either constants or variables just under the #includes
We define a group of hub memory locations in the Main program, possibly with multiple instances when using multiple cog objects
Then we pass these to the start function for the object. The start function may or may not rearrange the order of variables.
What I am thinking is whether you split the start process into two parts. One looks very similar to the spin start for that object eg
PUB start(rxpin, txpin, mode, baudrate)
and the other is a generic loader.
So in Spin pseudocode, the PUB above becomes
PUB start(rxpin, txpin, mode, baudrate,serial1permenentarray)
where serial1permenentarray contains things like the buffer, and the buffer location.
this Start then goes and rearranges the order splices these setup and permanent values together into a new array, as the order is going to be different. It then calls the generic loader with all the values in order for use by PAR. This array is disposed when the Start function ends.
There may also be a need to pass 'setup' values as well. I need to check the syntax of a few objects and see how they are doing things.
The serial1permanentarray is also passed to other methods eg
PUB rxcheck : rxbyte
'' Check if byte received (never waits)
'' returns -1 if no byte received, $00..$FF if byte
rxbyte--
if rx_tail <> rx_head
rxbyte := rx_buffer[rx_tail]
rx_tail := (rx_tail + 1) & $F
changes to
PUB rxcheck(arraypointer)
(or a structure) and from arraypointer this function can extract rx_tail and all the other variables it needs.
All objects need to have something passed to them, which is going to make the code look a bit different to spin, as in spin, many objects are "void" eg
PUB rxflush
but they are not really void, as there are variables which are global to the object (ie those declared in the VAR section).
So - many things need to change in the code to create reloadable objects. These changes will also need to be made in Spin once BigSpin is being used more. One needs to think a lot more about what code is using what memory.
This is some C code I am using to test this out
/* test passing unsigned longs to functions */
#include <stdio.h>
unsigned long setup_value = 30; // setup values stored in external ram
void cogloader(unsigned long pararray[])
{
printf("%d \n",pararray[0]);
printf("%d \n",pararray[1]);
printf("%d \n",pararray[2]);
}
void serial_start(unsigned long s, unsigned long hub_serial[]) // pass setup value(s) and permanent hub values here
{
unsigned long cogpar[30]; // parameter array for the cog
cogpar[0] = hub_serial[4]; // rearrange order
cogpar[1] = s; // splice in a setup value
cogpar[2] = hub_serial[3];
cogloader(cogpar); // pass to generic cog loader
}
void main()
{
unsigned long hub_serial[10]; // create the array in hub ram
hub_serial[3] = 7; // put in an initial value
hub_serial[4] = 8;
serial_start(setup_value,hub_serial); // pass array and other values
while (1); // Prop reboots on exit from main()!
}
as an aside, the Catalina IDE makes it so much easier when you can test fragments like this with Shift F10 for the TinyC compiler, prior to using F10 for catalina. Instant compiling makes debugging *much* easier. In this respect Catalina is so far ahead of Spin. The downside is that it is not going to be easy to have 'objects' and you can't drop in blocks of code with copy/paste. Hopefully the changes are not too many though. Just a matter of working out which Spin code is permanent and which is temporary and where things should go.
At the moment it looks like an spin object will be split into several parts
1) the pasm code (which can be separately compiled to a .cog binary object, or converted to an array for inclusion in C)
2) The methods, each of which will be a function in C
3) The setup values, which are placed at the beginning of the C program so they are in hub
4) Declarations of the functions if one wants the main at the top of the program
5) The list of variables and arrays, to be copied to the start of the 'main'
Hmm - not quite as neat as the concept of Spin objects. Then again, reloadable spin objects for external memory would also require a lot of custom copy/paste as well.
I have a pre-release of Catalina 3.0 almost ready to go. It completes the SPI FLASH functionality for the C3, plus a few other things.
Here is a quick summary:
The new caching SPI driver has been integrated. Cache sizes of 1k,2k,4k or 8k are supported.
Two new memory modes have been added specically for executing from SPI FLASH (which requires the new caching SPI driver):
-x3 puts all read-only segments in SPI FLASH and all read/write segments in SPI RAM (the stack is still in Hub).
-x4 puts the code segment in SPI FLASH and all data segments in Hub (the SPI RAM is not used).
These memory models are fully supported by the Catalyst program loader and the Payload serial loader. There are also new related command-line options (-P and -R) that can be used to relocate the read/write and read/only segments.
There is a new flash_boot utility program that can be programmed into EEPROM to boot a program already loaded into FLASH
There are fixes for bugs in setjmp/longjmp on some platforms.
All the HMI plugins are now ANSI compliant (There are "legacy" options that can be used for programs that depend on the current HMI plugin behaviour, but it is recommended to modify all programs to expect ANSI behaviour for things like backspace, newline and carriage return processing).
I've only tested the Windows versions of most things so far, so I need to know if either of you are using Linux.
I'll package up something for release - probably tomorrow or the day after. If I can make it small enough to be a patch release that can be applied to 2.9 then I'll email it to you - otherwise I'll post it on sourceforge (with suitable disclaimers! - there's still a heap of testing on other platforms left to do).
The Linux testing is not onerous, it is just tedious to do it all over again. It's mainly the various scripts and makefiles that have to be tested. But you're welcome to do some if you want!
void main ()
{
unsigned long serial_parameters[40]; // reserve hub space for buffer, head tail pointers
printf("serial_parameters array is at %u \n",(unsigned long)&serial_parameters[0]);
printf("end of serial_parameters array is at %u \n",(unsigned long)&serial_parameters[40]);
serial_start(31,30,0,1200,serial_parameters); // start serial cog pins 31,30, mode 0, 1200 baud
while (1); // Prop reboots on exit from main()
}
and now I am very confused as the order seems to now be swapped around. The start of the array is at 27068 and the finish at 27228. This is now the same order as Spin, which will actually make things easier, but I am sure I had code earlier that was in reverse. It probably does not matter, but my only concern is that something different in the code has the effect of swapping the order. Do you have any knowledge of the internals of how catalina assigns array and variable space?
void main ()
{
unsigned long serial_parameters[40]; // reserve hub space for buffer, head tail pointers
printf("serial_parameters array is at %u \n",(unsigned long)&serial_parameters[0]);
printf("end of serial_parameters array is at %u \n",(unsigned long)&serial_parameters[40]);
serial_start(31,30,0,1200,serial_parameters); // start serial cog pins 31,30, mode 0, 1200 baud
while (1); // Prop reboots on exit from main()
}
and now I am very confused as the order seems to now be swapped around. The start of the array is at 27068 and the finish at 27228. This is now the same order as Spin, which will actually make things easier, but I am sure I had code earlier that was in reverse. It probably does not matter, but my only concern is that something different in the code has the effect of swapping the order. Do you have any knowledge of the internals of how catalina assigns array and variable space?
Hi Dr_A,
Arrays are always allocated this way. Your previous case was a bunch of consecutive long variables, but they weren't part of any array or structure. In that case, Catalina (or any C compiler) is free to lay them out in memory any way it likes. Catalina will tend to reverse them because it allocates them consecutively in the local stack frame, and the stack grows downwards in memory.
Yep, see my post #319 here. You can't rely on a C compiler placing memory for different declarations in the same order as you write them. If you want to do that you have to wrap all the declarations up into a structure.
By the way you should be aware that
&serial_parameters[0]
By can be written simply as:
serial_parameters
The name of an array is a pointer to the first element of the array. Makes the source look a bit neater.
Thanks guys, that makes sense. Though I might stick with the & pointing to the array element 0, just to remind me this is an array, not a variable. I hope you don't mind one of my other personal C quirky styles as well, where I put { on a line by itself.
I didn't quite understand structures - particularly how they are decoded in the receiving function. But I think it is going to be simpler to pass one array and then to make sure it is well commented as to which array element corresponds with which spin variable.
Anyway - a eureka moment. We have a C cog object being loaded and it transmits data out of the serial port.
This is a big milestone. The IDE is taking the pasm part (which exists as a comment block in C), compiling it, pasting it back into C as an array, and the C part is interacting with this. The pasm part is self contained, so it could just as easily have been loaded off an SD card.
The spin code is the standard serial object (I'll post this as there is a question at the bottom of this)
''***************************************
''* Full-Duplex Serial Driver v1.1 *
''* Author: Chip Gracey *
''* Copyright (c) 2006 Parallax, Inc. *
''* See end of file for terms of use. *
''***************************************
VAR
long cog 'cog flag/id
long rx_head '9 contiguous longs
long rx_tail
long tx_head
long tx_tail
long rx_pin
long tx_pin
long rxtx_mode
long bit_ticks
long buffer_ptr
byte rx_buffer[16] 'transmit and receive buffers
byte tx_buffer[16]
PUB start(rxpin, txpin, mode, baudrate) : okay
'' Start serial driver - starts a cog
'' returns false if no cog available
''
'' mode bit 0 = invert rx
'' mode bit 1 = invert tx
'' mode bit 2 = open-drain/source tx
'' mode bit 3 = ignore tx echo on rx
stop
longfill(@rx_head, 0, 4)
longmove(@rx_pin, @rxpin, 3)
bit_ticks := clkfreq / baudrate
buffer_ptr := @rx_buffer
okay := cog := cognew(@entry, @rx_head) + 1
PUB stop
'' Stop serial driver - frees a cog
if cog
cogstop(cog~ - 1)
longfill(@rx_head, 0, 9)
PUB rxflush
'' Flush receive buffer
repeat while rxcheck => 0
PUB rxcheck : rxbyte
'' Check if byte received (never waits)
'' returns -1 if no byte received, $00..$FF if byte
rxbyte--
if rx_tail <> rx_head
rxbyte := rx_buffer[rx_tail]
rx_tail := (rx_tail + 1) & $F
PUB rxtime(ms) : rxbyte | t
'' Wait ms milliseconds for a byte to be received
'' returns -1 if no byte received, $00..$FF if byte
t := cnt
repeat until (rxbyte := rxcheck) => 0 or (cnt - t) / (clkfreq / 1000) > ms
PUB rx : rxbyte
'' Receive byte (may wait for byte)
'' returns $00..$FF
repeat while (rxbyte := rxcheck) < 0
PUB tx(txbyte)
'' Send byte (may wait for room in buffer)
repeat until (tx_tail <> (tx_head + 1) & $F)
tx_buffer[tx_head] := txbyte
tx_head := (tx_head + 1) & $F
if rxtx_mode & %1000
rx
PUB str(stringptr)
'' Send string
repeat strsize(stringptr)
tx(byte[stringptr++])
PUB dec(value) | i
'' Print a decimal number
if value < 0
-value
tx("-")
i := 1_000_000_000
repeat 10
if value => i
tx(value / i + "0")
value //= i
result~~
elseif result or i == 1
tx("0")
i /= 10
PUB hex(value, digits)
'' Print a hexadecimal number
value <<= (8 - digits) << 2
repeat digits
tx(lookupz((value <-= 4) & $F : "0".."9", "A".."F"))
PUB bin(value, digits)
'' Print a binary number
value <<= 32 - digits
repeat digits
tx((value <-= 1) & 1 + "0")
DAT
'***********************************
'* Assembly language serial driver *
'***********************************
org
'
'
' Entry
'
entry mov t1,par 'get structure address
add t1,#4 << 2 'skip past heads and tails
rdlong t2,t1 'get rx_pin
mov rxmask,#1
shl rxmask,t2
add t1,#4 'get tx_pin
rdlong t2,t1
mov txmask,#1
shl txmask,t2
add t1,#4 'get rxtx_mode
rdlong rxtxmode,t1
add t1,#4 'get bit_ticks
rdlong bitticks,t1
add t1,#4 'get buffer_ptr
rdlong rxbuff,t1
mov txbuff,rxbuff
add txbuff,#16
test rxtxmode,#%100 wz 'init tx pin according to mode
test rxtxmode,#%010 wc
if_z_ne_c or outa,txmask
if_z or dira,txmask
mov txcode,#transmit 'initialize ping-pong multitasking
'
'
' Receive
'
receive jmpret rxcode,txcode 'run a chunk of transmit code, then return
test rxtxmode,#%001 wz 'wait for start bit on rx pin
test rxmask,ina wc
if_z_eq_c jmp #receive
mov rxbits,#9 'ready to receive byte
mov rxcnt,bitticks
shr rxcnt,#1
add rxcnt,cnt
:bit add rxcnt,bitticks 'ready next bit period
:wait jmpret rxcode,txcode 'run a chuck of transmit code, then return
mov t1,rxcnt 'check if bit receive period done
sub t1,cnt
cmps t1,#0 wc
if_nc jmp #:wait
test rxmask,ina wc 'receive bit on rx pin
rcr rxdata,#1
djnz rxbits,#:bit
shr rxdata,#32-9 'justify and trim received byte
and rxdata,#$FF
test rxtxmode,#%001 wz 'if rx inverted, invert byte
if_nz xor rxdata,#$FF
rdlong t2,par 'save received byte and inc head
add t2,rxbuff
wrbyte rxdata,t2
sub t2,rxbuff
add t2,#1
and t2,#$0F
wrlong t2,par
jmp #receive 'byte done, receive next byte
'
'
' Transmit
'
transmit jmpret txcode,rxcode 'run a chunk of receive code, then return
mov t1,par 'check for head <> tail
add t1,#2 << 2
rdlong t2,t1
add t1,#1 << 2
rdlong t3,t1
cmp t2,t3 wz
if_z jmp #transmit
add t3,txbuff 'get byte and inc tail
rdbyte txdata,t3
sub t3,txbuff
add t3,#1
and t3,#$0F
wrlong t3,t1
or txdata,#$100 'ready byte to transmit
shl txdata,#2
or txdata,#1
mov txbits,#11
mov txcnt,cnt
:bit test rxtxmode,#%100 wz 'output bit on tx pin according to mode
test rxtxmode,#%010 wc
if_z_and_c xor txdata,#1
shr txdata,#1 wc
if_z muxc outa,txmask
if_nz muxnc dira,txmask
add txcnt,bitticks 'ready next cnt
:wait jmpret txcode,rxcode 'run a chunk of receive code, then return
mov t1,txcnt 'check if bit transmit period done
sub t1,cnt
cmps t1,#0 wc
if_nc jmp #:wait
djnz txbits,#:bit 'another bit to transmit?
jmp #transmit 'byte done, transmit next byte
'
'
' Uninitialized data
'
t1 res 1
t2 res 1
t3 res 1
rxtxmode res 1
bitticks res 1
rxmask res 1
rxbuff res 1
rxdata res 1
rxbits res 1
rxcnt res 1
rxcode res 1
txmask res 1
txbuff res 1
txdata res 1
txbits res 1
txcnt res 1
txcode res 1
{{
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ TERMS OF USE: MIT License │
├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation │
│files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, │
│modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software│
│is furnished to do so, subject to the following conditions: │
│ │
│The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.│
│ │
│THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE │
│WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR │
│COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, │
│ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
}}
and this is the complete C program
/* PASM demo for use with Catalina IDE Precompiler and Compiler */
#include <stdio.h>
/* PASM Code for compilation using SpinC and inclusion as a data file
PASM Start mycogject.spin
CON
' add your values here
_clkfreq = 80_000_000 ' 5Mhz Crystal
_clkmode = xtal1 + pll16x ' x 16
' Start of C hub constants
' End of C constants
PUB Main
coginit(1,@entry,0) ' cog 1, cogstart, dummy value
DAT
'***********************************
'* Assembly language serial driver *
'***********************************
org
'
'
' Entry
'
entry mov t1,par 'get structure address
add t1,#4 << 2 'skip past heads and tails
rdlong t2,t1 'get rx_pin
mov rxmask,#1
shl rxmask,t2
add t1,#4 'get tx_pin
rdlong t2,t1
mov txmask,#1
shl txmask,t2
add t1,#4 'get rxtx_mode
rdlong rxtxmode,t1
add t1,#4 'get bit_ticks
rdlong bitticks,t1
add t1,#4 'get buffer_ptr
rdlong rxbuff,t1
mov txbuff,rxbuff
add txbuff,#16
test rxtxmode,#%100 wz 'init tx pin according to mode
test rxtxmode,#%010 wc
if_z_ne_c or outa,txmask
if_z or dira,txmask
mov txcode,#transmit 'initialize ping-pong multitasking
'
'
' Receive
'
receive jmpret rxcode,txcode 'run a chunk of transmit code, then return
test rxtxmode,#%001 wz 'wait for start bit on rx pin
test rxmask,ina wc
if_z_eq_c jmp #receive
mov rxbits,#9 'ready to receive byte
mov rxcnt,bitticks
shr rxcnt,#1
add rxcnt,cnt
:bit add rxcnt,bitticks 'ready next bit period
:wait jmpret rxcode,txcode 'run a chuck of transmit code, then return
mov t1,rxcnt 'check if bit receive period done
sub t1,cnt
cmps t1,#0 wc
if_nc jmp #:wait
test rxmask,ina wc 'receive bit on rx pin
rcr rxdata,#1
djnz rxbits,#:bit
shr rxdata,#32-9 'justify and trim received byte
and rxdata,#$FF
test rxtxmode,#%001 wz 'if rx inverted, invert byte
if_nz xor rxdata,#$FF
rdlong t2,par 'save received byte and inc head
add t2,rxbuff
wrbyte rxdata,t2
sub t2,rxbuff
add t2,#1
and t2,#$0F
wrlong t2,par
jmp #receive 'byte done, receive next byte
'
'
' Transmit
'
transmit jmpret txcode,rxcode 'run a chunk of receive code, then return
mov t1,par 'check for head <> tail
add t1,#2 << 2
rdlong t2,t1
add t1,#1 << 2
rdlong t3,t1
cmp t2,t3 wz
if_z jmp #transmit
add t3,txbuff 'get byte and inc tail
rdbyte txdata,t3
sub t3,txbuff
add t3,#1
and t3,#$0F
wrlong t3,t1
or txdata,#$100 'ready byte to transmit
shl txdata,#2
or txdata,#1
mov txbits,#11
mov txcnt,cnt
:bit test rxtxmode,#%100 wz 'output bit on tx pin according to mode
test rxtxmode,#%010 wc
if_z_and_c xor txdata,#1
shr txdata,#1 wc
if_z muxc outa,txmask
if_nz muxnc dira,txmask
add txcnt,bitticks 'ready next cnt
:wait jmpret txcode,rxcode 'run a chunk of receive code, then return
mov t1,txcnt 'check if bit transmit period done
sub t1,cnt
cmps t1,#0 wc
if_nc jmp #:wait
djnz txbits,#:bit 'another bit to transmit?
jmp #transmit 'byte done, transmit next byte
'
'
' Uninitialized data
'
t1 res 1
t2 res 1
t3 res 1
rxtxmode res 1
bitticks res 1
rxmask res 1
rxbuff res 1
rxdata res 1
rxbits res 1
rxcnt res 1
rxcode res 1
txmask res 1
txbuff res 1
txdata res 1
txbits res 1
txcnt res 1
txcode res 1
PASM End
*/
void mycogject(int cognumber, unsigned long *parameters_array) // this name copied from the .spin name in the pasm section - names must match eg void mycogject matches mycogject.spin. Also first code after this must be the .h array file. Put your code after the };
{
/**
* @file mycogject_array.h
* Created with spin.binary PASM to C Array Converter.
* Copyright (c) 2011, John Doe
*/
unsigned long mycogject_array[] =
{
0xa0bca9f0, 0x80fca810, 0x08bcaa54, 0xa0fcb201,
0x2cbcb255, 0x80fca804, 0x08bcaa54, 0xa0fcbe01,
0x2cbcbe55, 0x80fca804, 0x08bcae54, 0x80fca804,
0x08bcb054, 0x80fca804, 0x08bcb454, 0xa0bcc05a,
0x80fcc010, 0x627cae04, 0x617cae02, 0x689be85f,
0x68abec5f, 0xa0fcc833, 0x5cbcbc64, 0x627cae01,
0x613cb3f2, 0x5c640016, 0xa0fcb809, 0xa0bcba58,
0x28fcba01, 0x80bcbbf1, 0x80bcba58, 0x5cbcbc64,
0xa0bca85d, 0x84bca9f1, 0xc17ca800, 0x5c4c001f,
0x613cb3f2, 0x30fcb601, 0xe4fcb81e, 0x28fcb617,
0x60fcb6ff, 0x627cae01, 0x6cd4b6ff, 0x08bcabf0,
0x80bcaa5a, 0x003cb655, 0x84bcaa5a, 0x80fcaa01,
0x60fcaa0f, 0x083cabf0, 0x5c7c0016, 0x5cbcc85e,
0xa0bca9f0, 0x80fca808, 0x08bcaa54, 0x80fca804,
0x08bcac54, 0x863caa56, 0x5c680033, 0x80bcac60,
0x00bcc256, 0x84bcac60, 0x80fcac01, 0x60fcac0f,
0x083cac54, 0x68fcc300, 0x2cfcc202, 0x68fcc201,
0xa0fcc40b, 0xa0bcc7f1, 0x627cae04, 0x617cae02,
0x6ce0c201, 0x29fcc201, 0x70abe85f, 0x7497ec5f,
0x80bcc658, 0x5cbcc85e, 0xa0bca863, 0x84bca9f1,
0xc17ca800, 0x5c4c004d, 0xe4fcc446, 0x5c7c0033
};
_coginit((int)parameters_array>>2, (int)mycogject_array>>2, cognumber); // array name built from spin file name
}
void clearscreen() // white text on dark blue background
{
int i;
for (i=0;i<40;i++)
{
t_setpos(0,0,i); // move cursor to next line
t_color(0,0x08FC); // RRGGBBxx eg dark blue background 00001000 white text 11111100
}
}
void sleep(int milliseconds) // sleep function
{
_waitcnt(_cnt()+(milliseconds*(_clockfreq()/1000))-4296);
}
char peek(int address) // function implementation of peek
{
return *((char *)address);
}
void poke(int address, char value) // function implementation of poke
{
*((char *)address) = value;
}
unsigned long serial_start(unsigned long rxpin,unsigned long txpin,unsigned long mode, unsigned long baudrate, unsigned long par[])
{
/*
PUB start(rxpin, txpin, mode, baudrate) : okay
'' Start serial driver - starts a cog
'' returns false if no cog available
''
'' mode bit 0 = invert rx
'' mode bit 1 = invert tx
'' mode bit 2 = open-drain/source tx
'' mode bit 3 = ignore tx echo on rx
stop
longfill(@rx_head, 0, 4)
longmove(@rx_pin, @rxpin, 3)
bit_ticks := clkfreq / baudrate
buffer_ptr := @rx_buffer
okay := cog := cognew(@entry, @rx_head) + 1
*/
unsigned long okay;
unsigned long bit_ticks;
unsigned long buffer_ptr;
par[0] = 0; // rx_head longfill(@rx_head, 0, 4)
par[1] = 0; // rx_tail
par[2] = 0; // tx_head
par[3] = 0; // tx_tail
par[4] = rxpin; // longmove(@rx_pin, @rxpin, 3)
par[5] = txpin; // note - if rewrite the pasm code could save a couple of hub longs here
par[6] = mode; // as rxpin and txpin are not used anywhere else
bit_ticks = _clockfreq() / baudrate; // bit_ticks := clkfreq / baudrate
par[7] = bit_ticks;
buffer_ptr = (unsigned long)&par[9]; // buffer_ptr := @rx_buffer points to start of circular buffer
par[8] = buffer_ptr; // pointer to the start of the circular buffers
// rx buffer is 9 to 12 and tx buffer is 13 to 16 (16 bytes =4 longs)
mycogject(7,par); // pass the packaged up array
// okay returns the cog number or -1 if a fail page 119 manual. Ignored here
printf("par array is at %u \n",(unsigned long)&par[0]);
printf("par array entry 1 is at %u \n",(unsigned long)&par[1]);
printf("par array entry 7 is at %u \n",(unsigned long)&par[7]);
printf("rx_head is at %u \n",(unsigned long)&par[9]);
printf("buffer_ptr is %u \n",par[8]);
return okay;
}
void serial_tx(char tx,unsigned long par[])
{
/*
PUB tx(txbyte)
'' Send byte (may wait for room in buffer)
repeat until (tx_tail <> (tx_head + 1) & $F)
tx_buffer[tx_head] := txbyte
tx_head := (tx_head + 1) & $F
if rxtx_mode & %1000
rx
*/
unsigned long tx_head;
int address;
while ( par[3] == (par[2] + 1 ) & 0xF) // par[2] is tx_head, par[3] is tx_tail
{
}
poke(address,tx); // poke the tx byte value to hub ram
tx_head = par[2]; // get the head value
address = par[8] + 16 + tx_head; // location of rx buffer plus 16 to get tx buffer plus the head value
poke(address,tx); // poke the tx byte value to hub ram
tx_head = tx_head + 1; // add one
tx_head = tx_head & 0xF; // logical and with 15
par[2] = tx_head; // store it back again
// need to add the echo mode?
}
unsigned long serial_rxcheck(unsigned long par[])
{
/*
PUB rxcheck : rxbyte
'' Check if byte received (never waits)
'' returns -1 if no byte received, $00..$FF if byte
rxbyte--
if rx_tail <> rx_head
rxbyte := rx_buffer[rx_tail]
rx_tail := (rx_tail + 1) & $F
*/
unsigned long rxbyte; // actually is a long, so can return -1 FFFFFFFF if nothing and 0-FF if a byte
int address; // hub address
rxbyte = rxbyte - 1; // return ffffffff if nothing
if (par[1] != par[0])
{
address = par[8] + par[1]; // par[8] is the rx buffer, par[1] is rx_tail
rxbyte = peek(address); // get the return byte from the buffer
par[1] = (par[1] +1) & 0xF; // add one to tail
}
return rxbyte;
}
unsigned long serial_rx(unsigned long par[])
{
/*
PUB rx : rxbyte
'' Receive byte (may wait for byte)
'' returns $00..$FF
repeat while (rxbyte := rxcheck) < 0
*/
unsigned long rxbyte; // actually is a long, not a byte
// while (rxbyte = serial_rxcheck(par)) < 0
// {
// }
return rxbyte; // return the value
}
void main ()
{
int i;
unsigned long serial_parameters[16]; // reserve hub space for buffer, head tail pointers
clearscreen();
printf("Clock speed %u \n",_clockfreq()); // see page 28 of the propeller manual for other useful commands
printf("Catalina running in cog number %i \n",_cogid()); // integer
serial_start(31,30,0,1200,serial_parameters); // start serial cog pins 31,30, mode 0, 1200 baud
printf("Started serial driver\n");
for(i=0; i<40; i++)
{
serial_tx(65+i,serial_parameters); // test sending a byte
sleep(1000);
}
while (1); // Prop reboots on exit from main()
}
I'm now working on the receive part. I haven't tested rxcheck, but I think it is ok.
The line that has me stumped at the moment is the line from "rx" in spin
repeat while (rxbyte := rxcheck) < 0
This actually contains several things. It is a repeat loop. It is calling the function "rxcheck". It is checking if the return value is -1. And if the return value is not -1, it is also returning the value of the returned value.
I know how to do some of that in C, but not how to do all of it in one line.
You can see my attempt in the code. There is a slight complication in that all the "common" spin variables like "rx_head" are array variables in C as they can't be common variables, so I need to pass them from the main function to the rx function, and then from the rx function to the rxcheck function. I'm using "par" and I may or may not need to use different array names here??
I'm not sure if this can be done in one line like in Spin? If not, maybe it needs splitting up into several lines (which I did with some of the other code too, as it made debugging easier).
The single line is because this is what Spin uses. Makes it easier to translate if it is a "line to line" translation.
Two minor issues:
1) in "serial_rxcheck, if I don't explicitly set rxbyte to zero at the beginning of the function, it decrements each time. I thought C set a variable to zero when the variable was defined? Maybe it is good programming practice to set variables to zero anyway?
2) in the line kuroneko posted,
while ((rxbyte = serial_rxcheck(par)) == -1) {} // 0xffffffff and -1 works, but " < 0" gives a compiler error
I get a compiler error when using <0, but 0xffffffff and -1 works fine. It doesn't really matter but it might make it easier translating spin.
Anyway, the program below takes input from a terminal program and adds one and returns the character. Type "a" and it returns "b".
I've very excited about this. It is the first working reloadable "cogject".
/* PASM demo for use with Catalina IDE Precompiler and Compiler */
#include <stdio.h>
/* PASM Code for compilation using SpinC and inclusion as a data file
PASM Start mycogject.spin
CON
' add your values here
_clkfreq = 80_000_000 ' 5Mhz Crystal
_clkmode = xtal1 + pll16x ' x 16
' Start of C hub constants
' End of C constants
PUB Main
coginit(1,@entry,0) ' cog 1, cogstart, dummy value
DAT
'***********************************
'* Assembly language serial driver *
'***********************************
org
'
'
' Entry
'
entry mov t1,par 'get structure address
add t1,#4 << 2 'skip past heads and tails
rdlong t2,t1 'get rx_pin
mov rxmask,#1
shl rxmask,t2
add t1,#4 'get tx_pin
rdlong t2,t1
mov txmask,#1
shl txmask,t2
add t1,#4 'get rxtx_mode
rdlong rxtxmode,t1
add t1,#4 'get bit_ticks
rdlong bitticks,t1
add t1,#4 'get buffer_ptr
rdlong rxbuff,t1
mov txbuff,rxbuff
add txbuff,#16
test rxtxmode,#%100 wz 'init tx pin according to mode
test rxtxmode,#%010 wc
if_z_ne_c or outa,txmask
if_z or dira,txmask
mov txcode,#transmit 'initialize ping-pong multitasking
'
'
' Receive
'
receive jmpret rxcode,txcode 'run a chunk of transmit code, then return
test rxtxmode,#%001 wz 'wait for start bit on rx pin
test rxmask,ina wc
if_z_eq_c jmp #receive
mov rxbits,#9 'ready to receive byte
mov rxcnt,bitticks
shr rxcnt,#1
add rxcnt,cnt
:bit add rxcnt,bitticks 'ready next bit period
:wait jmpret rxcode,txcode 'run a chuck of transmit code, then return
mov t1,rxcnt 'check if bit receive period done
sub t1,cnt
cmps t1,#0 wc
if_nc jmp #:wait
test rxmask,ina wc 'receive bit on rx pin
rcr rxdata,#1
djnz rxbits,#:bit
shr rxdata,#32-9 'justify and trim received byte
and rxdata,#$FF
test rxtxmode,#%001 wz 'if rx inverted, invert byte
if_nz xor rxdata,#$FF
rdlong t2,par 'save received byte and inc head
add t2,rxbuff
wrbyte rxdata,t2
sub t2,rxbuff
add t2,#1
and t2,#$0F
wrlong t2,par
jmp #receive 'byte done, receive next byte
'
'
' Transmit
'
transmit jmpret txcode,rxcode 'run a chunk of receive code, then return
mov t1,par 'check for head <> tail
add t1,#2 << 2
rdlong t2,t1
add t1,#1 << 2
rdlong t3,t1
cmp t2,t3 wz
if_z jmp #transmit
add t3,txbuff 'get byte and inc tail
rdbyte txdata,t3
sub t3,txbuff
add t3,#1
and t3,#$0F
wrlong t3,t1
or txdata,#$100 'ready byte to transmit
shl txdata,#2
or txdata,#1
mov txbits,#11
mov txcnt,cnt
:bit test rxtxmode,#%100 wz 'output bit on tx pin according to mode
test rxtxmode,#%010 wc
if_z_and_c xor txdata,#1
shr txdata,#1 wc
if_z muxc outa,txmask
if_nz muxnc dira,txmask
add txcnt,bitticks 'ready next cnt
:wait jmpret txcode,rxcode 'run a chunk of receive code, then return
mov t1,txcnt 'check if bit transmit period done
sub t1,cnt
cmps t1,#0 wc
if_nc jmp #:wait
djnz txbits,#:bit 'another bit to transmit?
jmp #transmit 'byte done, transmit next byte
'
'
' Uninitialized data
'
t1 res 1
t2 res 1
t3 res 1
rxtxmode res 1
bitticks res 1
rxmask res 1
rxbuff res 1
rxdata res 1
rxbits res 1
rxcnt res 1
rxcode res 1
txmask res 1
txbuff res 1
txdata res 1
txbits res 1
txcnt res 1
txcode res 1
PASM End
*/
void mycogject(int cognumber, unsigned long *parameters_array) // this name copied from the .spin name in the pasm section - names must match eg void mycogject matches mycogject.spin. Also first code after this must be the .h array file. Put your code after the };
{
/**
* @file mycogject_array.h
* Created with spin.binary PASM to C Array Converter.
* Copyright (c) 2011, John Doe
*/
unsigned long mycogject_array[] =
{
0xa0bca9f0, 0x80fca810, 0x08bcaa54, 0xa0fcb201,
0x2cbcb255, 0x80fca804, 0x08bcaa54, 0xa0fcbe01,
0x2cbcbe55, 0x80fca804, 0x08bcae54, 0x80fca804,
0x08bcb054, 0x80fca804, 0x08bcb454, 0xa0bcc05a,
0x80fcc010, 0x627cae04, 0x617cae02, 0x689be85f,
0x68abec5f, 0xa0fcc833, 0x5cbcbc64, 0x627cae01,
0x613cb3f2, 0x5c640016, 0xa0fcb809, 0xa0bcba58,
0x28fcba01, 0x80bcbbf1, 0x80bcba58, 0x5cbcbc64,
0xa0bca85d, 0x84bca9f1, 0xc17ca800, 0x5c4c001f,
0x613cb3f2, 0x30fcb601, 0xe4fcb81e, 0x28fcb617,
0x60fcb6ff, 0x627cae01, 0x6cd4b6ff, 0x08bcabf0,
0x80bcaa5a, 0x003cb655, 0x84bcaa5a, 0x80fcaa01,
0x60fcaa0f, 0x083cabf0, 0x5c7c0016, 0x5cbcc85e,
0xa0bca9f0, 0x80fca808, 0x08bcaa54, 0x80fca804,
0x08bcac54, 0x863caa56, 0x5c680033, 0x80bcac60,
0x00bcc256, 0x84bcac60, 0x80fcac01, 0x60fcac0f,
0x083cac54, 0x68fcc300, 0x2cfcc202, 0x68fcc201,
0xa0fcc40b, 0xa0bcc7f1, 0x627cae04, 0x617cae02,
0x6ce0c201, 0x29fcc201, 0x70abe85f, 0x7497ec5f,
0x80bcc658, 0x5cbcc85e, 0xa0bca863, 0x84bca9f1,
0xc17ca800, 0x5c4c004d, 0xe4fcc446, 0x5c7c0033
};
_coginit((int)parameters_array>>2, (int)mycogject_array>>2, cognumber); // array name built from spin file name
}
void clearscreen() // white text on dark blue background
{
int i;
for (i=0;i<40;i++)
{
t_setpos(0,0,i); // move cursor to next line
t_color(0,0x08FC); // RRGGBBxx eg dark blue background 00001000 white text 11111100
}
}
void sleep(int milliseconds) // sleep function
{
_waitcnt(_cnt()+(milliseconds*(_clockfreq()/1000))-4296);
}
char peek(int address) // function implementation of peek
{
return *((char *)address);
}
void poke(int address, char value) // function implementation of poke
{
*((char *)address) = value;
}
unsigned long serial_start(unsigned long rxpin,unsigned long txpin,unsigned long mode, unsigned long baudrate, unsigned long par[])
{
/*
PUB start(rxpin, txpin, mode, baudrate) : okay
'' Start serial driver - starts a cog
'' returns false if no cog available
''
'' mode bit 0 = invert rx
'' mode bit 1 = invert tx
'' mode bit 2 = open-drain/source tx
'' mode bit 3 = ignore tx echo on rx
stop
longfill(@rx_head, 0, 4)
longmove(@rx_pin, @rxpin, 3)
bit_ticks := clkfreq / baudrate
buffer_ptr := @rx_buffer
okay := cog := cognew(@entry, @rx_head) + 1
*/
unsigned long okay;
unsigned long bit_ticks;
unsigned long buffer_ptr;
par[0] = 0; // rx_head longfill(@rx_head, 0, 4)
par[1] = 0; // rx_tail
par[2] = 0; // tx_head
par[3] = 0; // tx_tail
par[4] = rxpin; // longmove(@rx_pin, @rxpin, 3)
par[5] = txpin; // note - if rewrite the pasm code could save a couple of hub longs here
par[6] = mode; // as rxpin and txpin are not used anywhere else
bit_ticks = _clockfreq() / baudrate; // bit_ticks := clkfreq / baudrate
par[7] = bit_ticks;
buffer_ptr = (unsigned long)&par[9]; // buffer_ptr := @rx_buffer points to start of circular buffer
par[8] = buffer_ptr; // pointer to the start of the circular buffers
// rx buffer is 9 to 12 and tx buffer is 13 to 16 (16 bytes =4 longs)
mycogject(7,par); // pass the packaged up array
// okay returns the cog number or -1 if a fail page 119 manual. Ignored here
printf("par array is at %u \n",(unsigned long)&par[0]);
printf("par array entry 1 is at %u \n",(unsigned long)&par[1]);
printf("par array entry 7 is at %u \n",(unsigned long)&par[7]);
printf("rx_head is at %u \n",(unsigned long)&par[9]);
printf("buffer_ptr is %u \n",par[8]);
return okay;
}
void serial_tx(char tx,unsigned long par[])
{
/*
PUB tx(txbyte)
'' Send byte (may wait for room in buffer)
repeat until (tx_tail <> (tx_head + 1) & $F)
tx_buffer[tx_head] := txbyte
tx_head := (tx_head + 1) & $F
if rxtx_mode & %1000
rx
*/
unsigned long tx_head;
int address;
while ( par[3] == (par[2] + 1 ) & 0xF) // par[2] is tx_head, par[3] is tx_tail
{
}
poke(address,tx); // poke the tx byte value to hub ram
tx_head = par[2]; // get the head value
address = par[8] + 16 + tx_head; // location of rx buffer plus 16 to get tx buffer plus the head value
poke(address,tx); // poke the tx byte value to hub ram
tx_head = tx_head + 1; // add one
tx_head = tx_head & 0xF; // logical and with 15
par[2] = tx_head; // store it back again
// need to add the echo mode?
}
unsigned long serial_rxcheck(unsigned long par[])
{
/*
PUB rxcheck : rxbyte
'' Check if byte received (never waits)
'' returns -1 if no byte received, $00..$FF if byte
rxbyte--
if rx_tail <> rx_head
rxbyte := rx_buffer[rx_tail]
rx_tail := (rx_tail + 1) & $F
*/
unsigned long rxbyte; // actually is a long, so can return -1 FFFFFFFF if nothing and 0-FF if a byte
int address; // hub address
rxbyte = 0; // set explicitly to zero
rxbyte = rxbyte - 1; // return ffffffff if nothing
// printf("rx check value %u \n",rxbyte);
// printf("rx head %u \n",par[0]);
// printf("rx tail %u \n",par[1]);
if (par[1] != par[0])
{
address = par[8] + par[1]; // par[8] is the rx buffer, par[1] is rx_tail
rxbyte = peek(address); // get the return byte from the buffer
par[1] = (par[1] +1) & 0xF; // add one to tail
}
return rxbyte;
}
unsigned long serial_rx(unsigned long par[])
{
/*
PUB rx : rxbyte
'' Receive byte (may wait for byte)
'' returns $00..$FF
repeat while (rxbyte := rxcheck) < 0
*/
unsigned long rxbyte; // actually is a long, not a byte
while ((rxbyte = serial_rxcheck(par)) == -1) {} // 0xffffffff and -1 works, but " < 0" gives a compiler error
return rxbyte; // return the value
}
void main ()
{
int i;
unsigned long received_byte;
unsigned long serial_parameters[16]; // reserve hub space for buffer, head tail pointers
clearscreen();
printf("Clock speed %u \n",_clockfreq()); // see page 28 of the propeller manual for other useful commands
printf("Catalina running in cog number %i \n",_cogid()); // integer
serial_start(31,30,0,1200,serial_parameters); // start serial cog pins 31,30, mode 0, 1200 baud
printf("Started serial driver\n");
for(i=0; i<10; i++)
{
serial_tx(65+i,serial_parameters); // test sending a byte
sleep(500);
printf("send byte %u \n",65+i);
}
printf("type some characters, will return that character plus 1 \n");
for (i=0;i<19;i++) // test 19 times, so tests buffer restarting
{
received_byte = serial_rx(serial_parameters); // get a byte
received_byte = received_byte + 1; // add one and send it back
serial_tx(received_byte,serial_parameters);
printf("sent back byte %u \n",received_byte);
}
printf("program finished \n");
while (1); // Prop reboots on exit from main()
}
Comments
Kuroneko, I don't think that code quite works, but that is almost certainly due to my explanation not being clear.
The test code first sets a value in hub location 100 to a certain number. The cog then is attempting to overwrite that number with a new number passed in par.
In pseudo code
put a number into par
move the number from par into a temporary variable in the cog
move the number from the temporary cog to a location in hub ram
There are a few things that need to all work before this works.
I am pretty sure that this code works, but I'm less sure after doing quite a number of tests. Maybe Ross can answer that:
I think this means that par will point to the location in hub that contains ascii 70 in a long.
Next part - move par to a temporary variable. I thought this was done with MOV but I'm suddenly very unsure about that after reading the Propeller manual on page 380 where it says "MOV - Set a register to a value". I thought MOV was also used to move the contents of one register to another register. Maybe it is too but the text doesn't quite read like that.
Finally, move the contents of the temporary register to hub. wrbyte does this, and it gets me every time but it is source to destination which is sort of the opposite of MOV
So, this code works correctly and returns 80
but this code is supposed to return the value that was passed in the par array, and instead it returns 32 or 40
and kuroneko's code returns the original value that was in the hub ram before the cog was started, which is what I think it ought to do but this is not what is intended
So I am starting to wonder if the value is not getting into par in the first place? Is there a way of testing what catalina is putting in par?
Thanks kuroneko, that works.
Now we have some hybrid C and PASM code that passes not only the data to the cog, but also the location in hub ram. This means the cog becomes totally independent. I think this is the missing link needed to created self contained .cog files that a C program can load from an sd card, then reload.
This is hybrid code that needs to be compiled by a compiler that knows how to strip out the pasm part, convert it to a C array and then paste it back in. (all command line programs that already exist).
I'm rather excited about this as I managed to pass some array data from one function to another using C. Maybe I am starting to understand pointers now!
Congrats, Dr_A. Easier integration between C and PASM is indeed one of the "missing links"!
I'll have a play with your tools when I get some time (i.e. once I get release 3.0 safely out the door).
Ross.
If so, how does the directory structure work on those other computers (ie the equivalent of installing in c:\program files\catalina)
Hi Dr_A,
All Catalina releases are tested and 100% functional on both Windows and Linux. I don't have a Mac, so I don't test on that platform but some people have reported successfully building Catalina for a Mac from source.
On Linux everything is much the same except the default Catalina directory is /usr/local/lib/catalina
Ross.
I'm working on porting a spin object to C and I'm currently working on the standard fullduplexserial object. I know this can be done in other ways, but it is a demonstration to show loading and unloading cog code.
This is the spin code
and in C I have global variables (I think that is the right way to put the variable in hub?)
and for the start function
However, that last line is printing out zero for the buffer_ptr location. I am trying to translate this line:
Do I need to be using integers instead of unsigned longs to determine the location of an array?
Next little thing is to get a number under 32768, ie a location in hub. I think that has something to do with where the variable is declared.
No. I really must write a brief technical note about this.
Global variables in Catalina (more correctly, any variables with file scope) will be in Hub RAM only in the LMM (-x0) and XMM SMALL (-x3) memory models. In the XMM LARGE (-x5) memory model, they will be in XMM RAM.
To force a variable to be in Hub RAM in all memory models, it must be declared as a local. If you want to share it around, then declare it as a local in the 'main' function, and pass it's address to the other functions that need it. If you don't want to pass it as a parameter, you can instead store the local address in a global variable. For example (note I am at work and can't run this, but it should work!):
Ross.
P.S. Kuroneko is correct - but I think you can also just write
Yes I think that is the answer. The aim is to try to replicate spin code without too many changes. So if a variable is declared in a VAR section in spin code, then it is global for that object and C should be too, otherwise too much code needs to be changed.
I have tested this as you suggest:
1) put unsigned long buffer_ptr; at the beginning of the program under stdio
2) declare the circular buffers in Main and get the pointer to them
And then in any function
and I get a value now of 27132.
This is great - it means I can now pass that to the cog.
The GNU linker allows a syntax like this:
This places the variable my_buffer in the ".hub" section which my linker script then places in hub memory. This works in ZOG to declare global variables in hub memory. Maybe you could adopt a similar scheme with Catalina.
Yes, it turned out this is not so easy. This object declares its variables in a VAR section and it consists of three parts that it passes to the cog:
Part 1 - a list of 8 variables, in contiguous order
Part 2 - a pointer to the beginning of the circular buffers
Part 3 - the circular buffers.
When the cog is started there is a reference to the start of part 1 viz okay := cog := cognew(@entry, @rx_head) + 1
In spin the compiler assumes the variables are global, so things like setting these values are done in functions. eg bit_ticks := clkfreq / baudrate is in the Start PUB.
But - in C, if these are declared in the Main routine, they are not global, so it gets more complicated as you have to pass a reference to the list.
And I just found something else by printing out the pointers to these variables - the values are decreasing as you go down the list, 27140, 27136, 27132. However, in Spin, the values increase, and this is important because the cog expects these values to be in the right order.
So the order of all variables and arrays is going to have to be reversed!
I will need to think about this some more. It seems that a lot of spin code is treating contiguous variables as arrays, exploiting the fact that the compiler lists them in ascending order. If we can't use global variables, and the list is in the wrong order, I am wondering if it might not be easier just to explicitly create an array of variables that encompasses everything. Then you can pass that array to functions in C with just one parameter.
For example, the fullduplexserial object is 9 longs and 32 longs for the buffer. Make that a 41 long array? Ideally, setting all the values should happen in a function rather than in the main, otherwise the main is going to fill up with a lot of code. The concept of 'objects' kind of goes out the window when the main() needs so much code added. Or maybe the idea would be to only declare hub variables in the main (as arrays), then do things like splitting them up into individual variables and swapping the order and setting values in individual functions?
I guess that is allowed, since spin does tricky things like
longfill(@rx_head, 0, 4)
which is actually setting four variables to zero, even those variables are not in an array.
So - instead of 9 longs and two arrays, we have one array -
unsigned long serial[40]
and instead of variables like rx_head, we reference that with
serial[40]
and rx_tail is serial[39]
which takes into account the order reversal. Then it is just a matter of good commenting to describe how and why the spin code has been changed this way.
This way we can pass then entire block to any functions so that all the variables in this code become array values
if (serial[39] != serial[40]) {
I may look at doing something like this for a subsequent release, although I'm always horrified by such non-standard constructs. Others have suggested using the "volatile" or "static" keywords for this type of thing, but while this makes the syntax standard, it is non-standard semantics. In a way that's even worse - at least with the GNU approach you know your code will be non-portable.
However, while I agree it would simplify life for the programmer, in reality it is not a big problem since the main reason for needing hub variables is to interact with other languages - and this is always going to be non-trivial.
Ross.
You should never depend on a specific memory layout of unrelated variables in any language. In C, some variables may end up in registers and not be present in memory at all!
if you want to force a specific memory layout in C you are supposed to declare a structure. You can then pass a pointer to that instead. So you would say something like:
I just had a problem with Zog like this. I had two arrays defined consecutively, mirroring what I have in a Spin version of the same idea. Turns out that when I increased the optimization level the compiler decided to put these two arrays in memory in reverse order and everything failed.
Use structures to enforce ordering.
Also I'm with potatohead. Non portable is better. Having different semantics for the same syntax is horrible. Besides we normally use funky constructs like this when building device drivers, and platform specific stuff so portability is not such a big issue.
Ok, what I am doing is rewriting a Spin object for use in C. There is a 'tiny' mode for C that fits easily into the internal memory, but my programs tend to be larger and I ran out of space ages ago. So - given a propeller with external memory (and we have quite a smorgasbord now!), I think it could be very useful to have reloadable objects.
In general terms, hub ram is more valuable than external ram. (Hub ram is faster and it can be accessed by cogs).
So, looking at a typical object, we have two types of variables that are passed to cogs - setup values, and permanent values. For example, in a serial driver, a setup value might be the baud rate, and a permanent value might be the circular buffer. For a video object, a setup value might be the screen size, and a permanent value might be the video buffer.
Permanent values might number from zero, right up to an array that fills most of the hub ram.
Setup values tend to be less in number, maybe 10 or so.
Setup values waste space in hub ram. This becomes more relevant when multiple copies are loaded or reloaded.
Because most Spin code objects were not written for external memory there never has been a need to differentiate between these types of variables. Indeed, I'm not even sure Spin as a language would be able to explicitly place one variable in hub and the next in external memory. (One would have to use a custom PUB to do this, and these exist, both in Spin and PASM).
But C is able to determine where variables are stored (hub variables in the Main, external variables defined at the beginning of the program).
So this leads to a new generic Start for objects. Instead of passing one list of values to the PAR, we need to pass two. Setup values and permanent values.
To make these neater, one could put one list after the other, but this is not necessary. Indeed, in order to save rewriting PASM code, it probably is easier to leave the order in whatever order the pasm code happens to be.
Let's take a look at the SimpleSerial object, with new comments added as to the nature of a variable (we can determine if a variable is setup or permanent by doing a search for that variable name)
So this order stays the same as this is the pasm order. What we need to do in our C program is pass these variables in the correct way.
Thinking about external memory management means looking at a C program in a slightly different way. For instance, where do you define the setup value "rx_pin"
1) As a constant (probably not the best option when one might be loading multiple copies of an object)
2) As a variable at the beginning of the program, underneath the #includes. This stores the value in external memory.
3) In the Main program (possibly where one usually might store this, but not the best place in this instance as it wastes a hub memory location
4) In a local function. The value will be disposed of when the function ends. On the other hand, it might be harder to find the value in amongst the code.
5) As a number in the function call eg serialstart(31,30)
6) As a constant read from a list where many constants are defined, eg at the beginning just under the #includes, serialporttx1 = 31, serialporttx2 = 29, serialporttx3=27
Option 6 may be the closest to existing spin, but I'm still thinking about this.
For 'permanent' values, one might have one list for an object, but one might also have multiple lists. I took a look at structures. These do look a good way of passing values in a group, though one minor disadvantage might be the dot notation, which in C refers to variables and in Spin refers to objects.
So - we define the setup values as either constants or variables just under the #includes
We define a group of hub memory locations in the Main program, possibly with multiple instances when using multiple cog objects
Then we pass these to the start function for the object. The start function may or may not rearrange the order of variables.
What I am thinking is whether you split the start process into two parts. One looks very similar to the spin start for that object eg
PUB start(rxpin, txpin, mode, baudrate)
and the other is a generic loader.
So in Spin pseudocode, the PUB above becomes
PUB start(rxpin, txpin, mode, baudrate,serial1permenentarray)
where serial1permenentarray contains things like the buffer, and the buffer location.
this Start then goes and rearranges the order splices these setup and permanent values together into a new array, as the order is going to be different. It then calls the generic loader with all the values in order for use by PAR. This array is disposed when the Start function ends.
There may also be a need to pass 'setup' values as well. I need to check the syntax of a few objects and see how they are doing things.
The serial1permanentarray is also passed to other methods eg
changes to
PUB rxcheck(arraypointer)
(or a structure) and from arraypointer this function can extract rx_tail and all the other variables it needs.
All objects need to have something passed to them, which is going to make the code look a bit different to spin, as in spin, many objects are "void" eg
PUB rxflush
but they are not really void, as there are variables which are global to the object (ie those declared in the VAR section).
So - many things need to change in the code to create reloadable objects. These changes will also need to be made in Spin once BigSpin is being used more. One needs to think a lot more about what code is using what memory.
This is some C code I am using to test this out
as an aside, the Catalina IDE makes it so much easier when you can test fragments like this with Shift F10 for the TinyC compiler, prior to using F10 for catalina. Instant compiling makes debugging *much* easier. In this respect Catalina is so far ahead of Spin. The downside is that it is not going to be easy to have 'objects' and you can't drop in blocks of code with copy/paste. Hopefully the changes are not too many though. Just a matter of working out which Spin code is permanent and which is temporary and where things should go.
At the moment it looks like an spin object will be split into several parts
1) the pasm code (which can be separately compiled to a .cog binary object, or converted to an array for inclusion in C)
2) The methods, each of which will be a function in C
3) The setup values, which are placed at the beginning of the C program so they are in hub
4) Declarations of the functions if one wants the main at the top of the program
5) The list of variables and arrays, to be copied to the start of the 'main'
Hmm - not quite as neat as the concept of Spin objects. Then again, reloadable spin objects for external memory would also require a lot of custom copy/paste as well.
I have a pre-release of Catalina 3.0 almost ready to go. It completes the SPI FLASH functionality for the C3, plus a few other things.
Here is a quick summary:
- All the HMI plugins are now ANSI compliant (There are "legacy" options that can be used for programs that depend on the current HMI plugin behaviour, but it is recommended to modify all programs to expect ANSI behaviour for things like backspace, newline and carriage return processing).
I've only tested the Windows versions of most things so far, so I need to know if either of you are using Linux.Ross.
Thanks.
That sounds great! I'll look forward to trying it out.
I'll package up something for release - probably tomorrow or the day after. If I can make it small enough to be a patch release that can be applied to 2.9 then I'll email it to you - otherwise I'll post it on sourceforge (with suitable disclaimers! - there's still a heap of testing on other platforms left to do).
The Linux testing is not onerous, it is just tedious to do it all over again. It's mainly the various scripts and makefiles that have to be tested. But you're welcome to do some if you want!
Ross.
I'm testing this little code fragment
and now I am very confused as the order seems to now be swapped around. The start of the array is at 27068 and the finish at 27228. This is now the same order as Spin, which will actually make things easier, but I am sure I had code earlier that was in reverse. It probably does not matter, but my only concern is that something different in the code has the effect of swapping the order. Do you have any knowledge of the internals of how catalina assigns array and variable space?
Hi Dr_A,
Arrays are always allocated this way. Your previous case was a bunch of consecutive long variables, but they weren't part of any array or structure. In that case, Catalina (or any C compiler) is free to lay them out in memory any way it likes. Catalina will tend to reverse them because it allocates them consecutively in the local stack frame, and the stack grows downwards in memory.
Ross.
Yep, see my post #319 here. You can't rely on a C compiler placing memory for different declarations in the same order as you write them. If you want to do that you have to wrap all the declarations up into a structure.
By the way you should be aware that
By can be written simply as:
The name of an array is a pointer to the first element of the array. Makes the source look a bit neater.
I didn't quite understand structures - particularly how they are decoded in the receiving function. But I think it is going to be simpler to pass one array and then to make sure it is well commented as to which array element corresponds with which spin variable.
Anyway - a eureka moment. We have a C cog object being loaded and it transmits data out of the serial port.
This is a big milestone. The IDE is taking the pasm part (which exists as a comment block in C), compiling it, pasting it back into C as an array, and the C part is interacting with this. The pasm part is self contained, so it could just as easily have been loaded off an SD card.
The spin code is the standard serial object (I'll post this as there is a question at the bottom of this)
and this is the complete C program
I'm now working on the receive part. I haven't tested rxcheck, but I think it is ok.
The line that has me stumped at the moment is the line from "rx" in spin
This actually contains several things. It is a repeat loop. It is calling the function "rxcheck". It is checking if the return value is -1. And if the return value is not -1, it is also returning the value of the returned value.
I know how to do some of that in C, but not how to do all of it in one line.
You can see my attempt in the code. There is a slight complication in that all the "common" spin variables like "rx_head" are array variables in C as they can't be common variables, so I need to pass them from the main function to the rx function, and then from the rx function to the rxcheck function. I'm using "par" and I may or may not need to use different array names here??
I'm not sure if this can be done in one line like in Spin? If not, maybe it needs splitting up into several lines (which I did with some of the other code too, as it made debugging easier).
This is all very exciting!
Where does this single line requirement come from? Auto-translation?
The single line is because this is what Spin uses. Makes it easier to translate if it is a "line to line" translation.
Two minor issues:
1) in "serial_rxcheck, if I don't explicitly set rxbyte to zero at the beginning of the function, it decrements each time. I thought C set a variable to zero when the variable was defined? Maybe it is good programming practice to set variables to zero anyway?
2) in the line kuroneko posted,
I get a compiler error when using <0, but 0xffffffff and -1 works fine. It doesn't really matter but it might make it easier translating spin.
Anyway, the program below takes input from a terminal program and adds one and returns the character. Type "a" and it returns "b".
I've very excited about this. It is the first working reloadable "cogject".