Digital IO Port access from C using Z88DK

504 views
Skip to first unread message

David Mortimer

unread,
Apr 28, 2020, 6:23:12 PM4/28/20
to retro-comp
Hello,

apologies if this is a simple question but how are you meant to access digital IO ports under CPM please?

I am running ROMWBW, CP/M 2.2 - trying to access a SC129 digital IO card.

But I am missing something as the old CPM BDOS call function doesn't seem to work?

Z88DK within stdio.h includes cpm.h ( when I use the +cpm I believe using zcc ) but then claims:

{output}
Error at file 'pushio.c' line 30: symbol '_CPM_SIOB' not defined
Error at file 'pushio.c' line 37: symbol '_bdos' not defined
Errors in source file C:\emitrom\z80\z88dk-win32-latest/z88dk/lib/config\..\..\\libsrc\_DEVELOPMENT\target\cpm\cpm_crt.asm:
Error at file 'pushio.c' line 30: symbol '_CPM_SIOB' not defined
                   ^ ---- (null)Error at file 'pushio.c' line 37: symbol '_bdos' not defined
                   ^ ---- (null)
{output}

But I can see that "libsrc\_DEVELOPMENT\target\cpm\cpm_crt.asm" does exist in that location already - so I assume already compiled and optimised ready for use?

My code is very simple ( maybe too simple? ):
{code}
#include <stdio.h>

int main()
{
int value = 8;
int port = 13;
    printf("Outputting to: %d !\n",);
    int result = bdos(CPM_SIOB, port, value);
    return 0;
}
{code}

Should I be looking at IOBYTE instead?  Or something else?

Or do I need a patch/different library/compile a different library to make this function work?

Thank you for any pointers!
David.

Phillip Stevens

unread,
Apr 28, 2020, 6:45:49 PM4/28/20
to retro-comp
David Mortimer wrote:
Hello,

apologies if this is a simple question but how are you meant to access digital IO ports under CPM please?

I am running ROMWBW, CP/M 2.2 - trying to access a SC129 digital IO card.

But I am missing something as the old CPM BDOS call function doesn't seem to work?

Z88DK within stdio.h includes cpm.h ( when I use the +cpm I believe using zcc ) but then claims:

Or do I need a patch/different library/compile a different library to make this function work?

David, could you post the compile incantation that you're using, please?

First reading is (without testing) that is should work, if you're using the "classic" library.
If you're using the "new" library, you may need to add the #include <cpm.h> file specifically. And the BDOS function name is then cpm_bdos(func,arg), or cpm_bdos_hl(func,arg).

There's a bit of a description here on RC2014 that goes into the differences. It might be useful to have a look at that, if you've not already found it. Look for Libraries.

Cheers, P.


David Mortimer

unread,
Apr 29, 2020, 3:37:28 AM4/29/20
to retro-comp
Thanks P,

typically I tried to include all relevant details and missed the important one!!!  :-O

Anyway my incantation as you call it is:

zcc +cpm -vn -O3 -startup=0 -clib=new pushio.c -o pushio -lm -create-app

which seems pretty standard and vanilla to me - but please correct if I am missed something.

Ahh, dinosaur brain slowly waking up to old world here - do I need to do -o on cpm_crt.asm or is the object already included??

Nope - or have a missed a compile stage that i need to do when including +cpm ?

Thanks again,
David.

Phillip Stevens

unread,
Apr 29, 2020, 7:20:25 AM4/29/20
to retro-comp
David Mortimer wrote:
Anyway my incantation as you call it is:

zcc +cpm -vn -O3 -startup=0 -clib=new pushio.c -o pushio -lm -create-app

Hi David,
yes. It is now clear what's happening.

Using zcc is correct.

-vn means you don't get to see what's going on. I'd only do that if you're recompiling things that you know work, and you don't want to wear out your phosphorus (liquid crystal). Start with -v, so you can see problems.

+cpm is your target. There are (for better or worse) two versions of CP/M as a target. One in the classic library (where you read the include files from), and one in the new library.

-clib=new means that you picked the CP/M libraries out of the new library (not the classic library). It also infers that you're using the sccz80 (former small C) compiler. To use the new library with sdcc you can use -clib=sdcc_iy. To use the classic library with sccz80 do nothing. To use the classic library with sdcc use -compiler=sdcc.

-O3 is not ideal for sccz80 (as it adds calls for simple things that should be in-lined). It prefers to use -O2. But not so sdcc, which prefers -SO3 to get the best results. Writing Optimal Code.

-lm means with the new library to use the math48 system. But with the classic library you'll get slower genmath maths, and to get math48 you need to use -math48. To use math32 (IEEE standard) use the --math32 linkage. Math Libraries.

which seems pretty standard and vanilla to me - but please correct if I am missed something.

Ahh, dinosaur brain slowly waking up to old world here - do I need to do -o on cpm_crt.asm or is the object already included??

Nope. For the CP/M target you don't do anything like that. It is a simple platform, so there is only one startup option (and you don't need to mention it).
 
Nope - or have a missed a compile stage that i need to do when including +cpm ?

No everything is good.

Assuming that you want to use the classic library, and sccz80 as your compiler, then your incantation would look like this.

zcc +cpm -v -O2 -lmath48 pushio.c -o pushio -create-app

That should work.

Let me know if it doesn't.
 
First reading is (without testing) that is should work, if you're using the "classic" library.
If you're using the "new" library, you may need to add the #include <cpm.h> file specifically. And the BDOS function name is then cpm_bdos(func,arg), or cpm_bdos_hl(func,arg).

There's a bit of a description here on RC2014 that goes into the differences. It might be useful to have a look at that, if you've not already found it. Look for Libraries.

Good luck, Phillip 

David Mortimer

unread,
Apr 29, 2020, 10:20:47 AM4/29/20
to retro-comp
Hello,

Thanks Steven but I have messed up here....

Just reread the documentation. That won't let me do the equivalent of "out port,value" as in basic will it as part of the disk operating system!

Muppet!

Is there not a simple function already in CPM.h for writing a byte to a given digital Io port number? Or do they assume everyone accessing it will do so from assembly??

Is it wrong I am enjoying not knowing and learning again???!! :-)

Thanks,
David

David Richards

unread,
Apr 29, 2020, 3:04:26 PM4/29/20
to retro-comp
Greetings David,
here is a link to a small program I wrote which performs I/O using a C library function, the method may be applicable to your system too.
Kind regards, David.

David Mortimer

unread,
Apr 29, 2020, 3:26:25 PM4/29/20
to retro-comp
Thank you sounds exactly what I am after.

But could you double check the link please as it fails and doesn't work for me?

Thank you,
David

David Richards

unread,
Apr 29, 2020, 3:43:42 PM4/29/20
to retro-comp
Ummm, the link work for me, perhaps because I subscribe to the RC2014-Z80 group.
here is the program, there is some useful discussion in the post as well though.
/*
* Arfon speach synthesizer driver
* uses RC2014 sound card i/o ports
 * Compiled to .com file with z88dk - sdcc
* PS C:\z88dk\rc2014> zcc +rc2014 -subtype=hbios -v --list -m -SO3 --c-code-in-asm --opt-code-size -clib=sdcc_iy  --max-allocs-per-node200000 iotest.c -o iotest -create-app
*  
* D. Richards, Feb 2020
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <z80.h>


char * spk_dictionary[] =
{
 "Digitalker",               /* 0 */
 "One",                      /* 1 */
 "Two",                      /* 2 */
 "Three",                    /* 3 */
 "Four",                     /* 4 */
 "Five",                     /* 5 */
 "Six",                      /* 6 */
 "Seven",                    /* 7 */
 "Eight",                    /* 8 */
 "Nine",                     /* 9 */

  "Ten",                      /* 10 */
 "Eleven",                   /* 11 */
 "Twelve",                   /* 12 */
 "Thirteen",                 /* 13 */
 "Fourteen",                 /* 14 */
 "Fifteen",                  /* 15 */
 "Sizteen",                  /* 16 */
 "Seventeen",                /* 17 */
 "Eighteen",                 /* 18 */
 "Ninteen",                  /* 19 */

  "Twenty",                   /* 20 */
 "Thirty",                   /* 21 */
 "Fourty",                   /* 22 */
 "Fifty",                    /* 23 */
 "Sixty",                    /* 24 */
 "Seventy",                  /* 25 */
 "Eighty",                   /* 26 */
 "Ninety",                   /* 27 */
 "Hundred",                  /* 28 */
 "Thousand",                 /* 29 */

  "Million",                  /* 30 */
 "Zero",                     /* 31 */
 "A",                        /* 32 */
 "B",                        /* 33 */
 "C",                        /* 34 */
 "D",                        /* 35 */
 "E",                        /* 36 */
 "F",                        /* 37 */
 "G",                        /* 38 */
 "H",                        /* 39 */

  "I",                        /* 40 */
 "J",                        /* 41 */
 "K",                        /* 42 */
 "L",                        /* 43 */
 "M",                        /* 44 */
 "N",                        /* 45 */
 "O",                        /* 46 */
 "P",                        /* 47 */
 "Q",                        /* 48 */
 "R",                        /* 49 */

  "S",                        /* 50 */
 "T",                        /* 51 */
 "U",                        /* 52 */
 "V",                        /* 53 */
 "W",                        /* 54 */
 "X",                        /* 55 */
 "Y",                        /* 56 */
 "Z",                        /* 57 */
 "Again",                    /* 58 */
 "Ampere",                   /* 59 */

  "And",                      /* 60 */
 "At",                       /* 61 */
 "Cancel",                   /* 62 */
 "Case",                     /* 63 */
 "Cent",                     /* 64 */
 "400Hz",                    /* 65 */
 "80Hz",                     /* 66 */
 " ",                        /* 67 */
 "  ",                       /* 68 */
 "   ",                      /* 69 */

  "    ",                     /* 70 */
 "     ",                    /* 71 */
 "Centi",                    /* 72 */
 "Check",                    /* 73 */
 "Comma",                    /* 74 */
 "Control",                  /* 75 */
 "Danger",                   /* 76 */
 "Degree",                   /* 77 */
 "Dollar",                   /* 78 */
 "Down",                     /* 79 */

  "Equal",                    /* 80 */
 "Error",                    /* 81 */
 "Feet",                     /* 82 */
 "Flow",                     /* 83 */
 "Fuel",                     /* 84 */
 "Gallon",                   /* 85 */
 "Go",                       /* 86 */
 "Gram",                     /* 87 */
 "Great",                    /* 88 */
 "Greater",                  /* 89 */

  "Have",                     /* 90 */
 "High",                     /* 91 */
 "Higher",                   /* 92 */
 "Hour",                     /* 93 */
 "In",                       /* 94 */
 "Inches",                   /* 95 */
 "Is",                       /* 96 */
 "It",                       /* 97 */
 "Kilo",                     /* 98 */
 "Left",                     /* 99 */


  "Less",                     /* 100 */
 "Lesser",                   /* 101 */
 "Limit",                    /* 102 */
 "Low",                      /* 103 */
 "Lower",                    /* 104 */
 "Mark",                     /* 105 */
 "Meter",                    /* 106 */
 "Mile",                     /* 107 */
 "Milli",                    /* 108 */
 "Minus",                    /* 109 */

  "Minute",                   /* 110 */
 "Near",                     /* 111 */
 "Number",                   /* 112 */
 "Of",                       /* 113 */
 "Off",                      /* 114 */
 "On",                       /* 115 */
 "Out",                      /* 116 */
 "Over",                     /* 117 */
 "Parenthesis",              /* 118 */
 "Percent",                  /* 119 */

  "Please",                   /* 120 */
 "Plus",                     /* 121 */
 "Point",                    /* 122 */
 "Pound",                    /* 123 */
 "Pulses",                   /* 124 */
 "Rate",                     /* 125 */
 "Re",                       /* 126 */
 "Ready",                    /* 127 */
 "Right",                    /* 128 */
 "s",                        /* 129 */

  "Second",                   /* 130 */
 "Set",                      /* 131 */
 "Space",                    /* 132 */
 "Speed",                    /* 133 */
 "Star",                     /* 134 */
 "Start",                    /* 135 */
 "Stop",                     /* 136 */
 "Than",                     /* 137 */
 "The",                      /* 138 */
 "Time",                     /* 139 */

  "Try",                      /* 140 */
 "Up",                       /* 141 */
 "Volt",                     /* 142 */
 "Weight"                    /* 143 */

};

#define SPK_DICTIONARY_COUNT ( sizeof( spk_dictionary ) / sizeof( char * )  )

void init(void)
{
 z80_outp(0xd8,0x07); // a & b outputs
 z80_outp(0xd0,0xc0);

  z80_outp(0xd8,0x0f); // clear b
 z80_outp(0xd0,0x00);

  z80_outp(0xd8,0x0e); // clear a
 z80_outp(0xd0,0x00);
}

void wait(void) // wait for Int after speach is finished
{
 int wd=0;
 int ia;

  z80_outp(0xd8,0x07); // select ddr register
 z80_outp(0xd0,0x80); // set a inputs, b outputs

  for(wd=0; wd<32000; wd++)
 {
   z80_outp(0xd8,0x0e);        // select a
   ia=z80_inp(0xd8);           // read a

    if(ia&0x80)                 // check for int high
   {
     break;
   }    
  }
 z80_outp(0xd8,0x07); // restor a & b as outputs
 z80_outp(0xd0,0xc0);
 return;
}

void say(int i) // send speach index, then strobe
{
 z80_outp(0xd8,0x0e);  // select a
 z80_outp(0xd0,0x00);  // set low

  z80_outp(0xd8,0x0f);  // select b
 z80_outp(0xd0,i);     // set key

  z80_outp(0xd8,0x0e);  // select a
 z80_outp(0xd0,0x01);  // set strobe

  wait();
}

int find(char * inword, int *key) // lookup index for word to say
{
 int i;
 int value;
 for(i=0; i<SPK_DICTIONARY_COUNT; i++)
 {
   // try to convert text to non zero integer
   value = (int) strtol(inword, (char **)NULL, 10);      
   if(value != 0)
   {
       *key = value;
       return 1;
   }
   // try to find word in dictionary  
   if(strcmp(spk_dictionary[i], inword)==0)
   {
     *key = i;
     return 1;
   }
 }
 return 0;
}

void main(void)
{
 char buffer[80];
 char * pch;
 int key=-1;

  init();              
  do
  {
    printf("Words? ");
    scanf ("%s",buffer);
    pch = strtok (buffer,", .-");
    while (pch != NULL)
    {  
       if(find(pch, &key) == 1)
       {
         say(key);
       }
       pch = strtok (NULL, ", .-");
    }
 } while (key != 0);
       
}


Kindregards, David.

David Mortimer

unread,
Apr 29, 2020, 3:54:45 PM4/29/20
to retro-comp
Hi David,

it didn't work on my phone browser was all!  Went to the lappie and alls well - thank you for the steer - exactly what I was after!!

Just trying some simple C to get my head back into it.

Thanks,
David.

Phillip Stevens

unread,
Apr 29, 2020, 6:56:45 PM4/29/20
to retro-comp
David Richards wrote:
Ummm, the link work for me, perhaps because I subscribe to the RC2014-Z80 group.
here is the program, there is some useful discussion in the post as well though.
/*
* Arfon speach synthesizer driver
* uses RC2014 sound card i/o ports
 * Compiled to .com file with z88dk - sdcc
* PS C:\z88dk\rc2014> zcc +rc2014 -subtype=hbios -v --list -m -SO3 --c-code-in-asm --opt-code-size -clib=sdcc_iy  --max-allocs-per-node200000 iotest.c -o iotest -create-app
*  
* D. Richards, Feb 2020
*/

Hi David, for interest,

When compiling small programs using the  --opt-code-size option leads to slowness and complexity in the code.
This is particularly relevant if you're trying to read the resulting compiled assembly code with --list --c-code-in-asm .

This is because every small code fragment that is more than the cost of a call and ret (4 Bytes) is translated into a function. You'll just see a bunch of obtusely named functions, and IMHO that makes the code very difficult to read.

My suggestion would be to not use --opt-code-size generally unless your program is really pushing up against the size available in your target.

Another suggestion would be to use the const declaration for the strings in your program. This allows the compiler to keep the strings in the rodata section (read only data), which is not copied up to to the data section when the program is initialised. The strings stay in situ in "ROM" (even if you're not using ROM).


Hi David,

There's another reference on performing I/O in z88dk which is worth reading. Essentially the I/O port is declared as a special function register. There are two types (8-bit and 16-bit). I think you need the 8-bit version.

__sfr __at 13 port; // 8-bit i/o port at 13

int main(void) {
   
volatile unsigned char a;
    a
= port; // read port
    port
= 100; // write port
    ret
0;
}

This will get you faster / better code than calling the C function to do the same thing.

Good luck, Phillip


David Richards

unread,
Apr 30, 2020, 5:57:37 AM4/30/20
to retro-comp
Hi Philip, 

Thanks for the useful suggestions. I was hoping to find a pointer style I/O method but gave up looking when I found a function to access the ports. Quite correct to use const for the text arrays too, may bad for not using it.

I'll make a new demo to drive a display of some kind using the pointer I/O method and post it here together with the compiler invocation.

Kind regards, David.

David Richards

unread,
May 1, 2020, 4:41:25 PM5/1/20
to retro-comp
Here is the arfon speech card functions re-written to use Philips suggestions.
Note this is work in progress, the plan is to make a program to convert a number given on the command line into a spoken number.
I havent done any of the conversion code yet, this version simply sits ina loop asking for a word to speak, silently ignoring anything it doesnt recognise.
/*
 * Arfon speach synthesizer driver
 * uses RC2014 sound card i/o ports
 
 * Compiled to .com (.bin) file with z88dk - sdcc
 * PS C:\z88dk\rc2014> zcc +rc2014 -subtype=hbios -v --list -m -SO3 --c-code-in-asm                 -clib=sdcc_iy  --max-allocs-per-node200000 arfon.c  -o arfon  -create-app
 *  
 * D. Richards, May 2020
*/

#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <z80.h>

// 8 bit I/O ports
#define IO_SR0 0xd0
#define IO_SR8 0xd8

__sfr __at IO_SR0 s0;
__sfr __at IO_SR8 s8;

const char * const spk_dictionary[] = 
  s8=0x07; // a & b outputs
  s0=0xc0;
  s8=0x0f; // clear b
  s0=0x00;
  s8=0xe0;
  s0=0x00;
}

void wait(void) // wait for Int after speach is finished
{
  volatile int ia;
  int wd=0;

  s8=0x07; // select ddr register
  s0=0x80; // set a inputs, b outputs

  for(wd=0; wd<32000; wd++)
  {
    s8=0x0e; // select a
    ia=s8; // read a
    if(ia&0x80)                 // check for int high
    {
      break;
    }     
  }  
  s8=0x07; // restor a & b as outputs
  s0=0xc0;
  return;
}

void say(int i) // send speach index, then strobe
{
  s8=0x0e; // select a
  s0=0x00; // set low

  s8=0x0f; // select b
  s0=i; // set key

  s8=0x0e; // select a
  s0=0x01; // set strobe

  wait();
}

int strcicmp(char const *a, char const *b)
{
    for (;; a++, b++) {
        int d = tolower((unsigned char)*a) - tolower((unsigned char)*b);
        if (d != 0 || !*a)
            return d;
    }
}

int find(char * inword, int *key) // lookup index for word to say
{
  int i;
  int value;
  for(i=0; i<SPK_DICTIONARY_COUNT; i++)
  {

    // try to convert text to non zero integer
    value = (int) strtol(inword, (char **)NULL, 10);      
    if(value != 0)
    {
        *key = value;
        return 1;
    }

    // try to find word in dictionary  
    if(strcicmp(spk_dictionary[i], inword)==0)
    {
      *key = i;
//      printf("key %d, Word %s\n",i,spk_dictionary[i]);
      return 1;
    }
  }
  return 0;
}

int main(int argc, char* argv[])
{
  char buffer[80];
  char * pch;
  int key=-1;
  int i;

  init();               

  if(argc>1)
  {
/*        
    for(i=1; i<argc; i++)
    {
      printf("argc %d, argv=[%s]\n", i, argv[i]);
    }
*/  
    for(i=1; i<argc; i++)
    {
      if(find(argv[i], &key))
      {
        say(key);
//         printf("key %d = %s\n", key, spk_dictionary[key]);
      }
        
    }

  } 
  else 
  {
  
    while (key!=0) 
    {
      printf("\nWords? ");
      scanf ("%s",buffer);

      pch = strtok (buffer,", .-");
      while (pch != NULL)
      {  
        if(find(pch, &key) == 1)
        {
          say(key);
        }
        pch = strtok (NULL, ", .-");
      }
    }
  }
  return 0;
}
I have tested it on my RC2015 romwbw system and it is working.Enter code here.
Note const used twice in the text table declaration, once for the array and again for the texts.
N.b. The program uses the I/O port of the Ed's sound card, I want to keep the digital I/O card for debug etc.
I really must remove the magic numbers from the i/o asignments, I cant remember what the bit mean now.
The sound chip document has all the definitions and explanations which I used to write the program.
The compiler output is a .bin file which needs to be renamed to .com, does anybody know if there is a way to force the creation of a .com using a command line override?

Kind regards, David

Phillip Stevens

unread,
May 3, 2020, 8:01:00 PM5/3/20
to retro-comp
David Richards wrote:
The compiler output is a .bin file which needs to be renamed to .com, does anybody know if there is a way to force the creation of a .com using a command line override?

It lies in the calling of the appmake tool, which collects all the binaries into an output for use on the target. You're invoking appmake through the -createapp option on the command line.

Try using appmake from the command line, and you can see all the options it supports. Generally the +rom or +hex options are mostly used. But for some complex things +glue is used. Their roles tend to overlap, as people have added specific functionality as they need it.

Options are passed to appmake through -Cz on the command line. Here are the rc2014 defaults for example, using +rom.

TBH, I stopped worrying about this because I'm using xmodem to transfer my ".COM" or .bin files anyway, so I can name them correctly on the destination z80 machine. But it would be worth understanding why they're named differently, so the default could be fixed.

Cheers, Phillip

David Richards

unread,
May 4, 2020, 11:56:56 AM5/4/20
to retro-comp
Thanks Philip,
the defaults are not quite right for me to not have to rename the output whichever options I use.
CP/M output is in upper case (my transfer tool doesnt like this, it does the conversion and expects lower case)
binary output is .bin and not .com - no matter I can live with these and rebuild the tools if neccesary.

Attached is a re-written Arfon number speaking program, it takes a value on the command line and convers it to a spoken number.

I may try something similar on one of the SP0256A-AL2 chips just for fun one day.
Next project though will be a T6963 240x64 mixed mode graphic and alphanumeric display with ccf backlight.
It really benefits from status polling, I'll port the library I made some time ago for it.

Kind regards, David.

arfon.c

Phillip Stevens

unread,
May 4, 2020, 11:59:32 PM5/4/20
to retro-comp
David Richards wrote:
Attached is a re-written Arfon number speaking program, it takes a value on the command line and convers it to a spoken number.

There is a repository for C programs compiled and made for z88dk, if you're happy to contribute.

Just do a PR on the repo.  z88dk external

The rules (around contribution) are pretty simple...
  • Applications are compilable or assembleable through zcc and use z88dk libraries where possible and relevant.
  • Applications should have example zcc incantations in comments in the source, or in a separate readme, to make the chance of success close to 100%.
  • There needs to be a licence file or licence header associated with each application.
Cheers, Phillip

Richard Deane

unread,
May 5, 2020, 3:32:39 AM5/5/20
to retro-comp
I think you are confusing BDOS calls (13 is disk reset) with something not handled by BDOS
There is no cp/m bdos call for arbitrary IO ports as CP/M knows nothing of them.
Many C implemementations will support C access to IO ports, no need for BDOS

BDOS covers access through CP/M to DISK, KBD,CONSOLE and Serial / Parallel (when implemented in BIOS)

Richard
Reply all
Reply to author
Forward
0 new messages