Skip to content

Carbot/370 Redux – Now With Added Computers

This is largely a cheat – all of the navigation code is running in the arduino. The Raspi is just telling it when to start and stop based on commands sent from my windows machine over ssh and relaying telemetry from the sensors. There’s not a whiff of 370 code in the mix and hardly any pi. The vision is to have the arduino doing the close in work while the 370/raspi sets broad parameters like speed and object avoidance patterns and maybe looks for stall situations.

The current code is trying to keep within a range of distances to the right hand wall and makes a hard left when the clearance in front is too small.

The robot car platform was a christmas gift. It had an ultrasonic sensor that i’m using for the wall distance and i’ve connected the sharp infrared sensor as the forward distance measurement. There are four DC motors on the wheels controlled by a custom motor shield on an arduino clone. It also has a line following sensor, a bluetooth add-on, and an infrared remote control. Either the bluetooth or infrared remotes could do what the Pi is doing but I need the pi in the loop for the telemetry and future considerations. The kit is the Elegoo Smart Robot Car Kit V3.0 and it has a ton of function built in.

#!/usr/bin/python
import sys, time
#import difflib
import pigpio
import termios, fcntl, sys, os

def get_char_keyboard_nonblock():
  fd = sys.stdin.fileno()

  oldterm = termios.tcgetattr(fd)
  newattr = termios.tcgetattr(fd)
  newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO
  termios.tcsetattr(fd, termios.TCSANOW, newattr)

  oldflags = fcntl.fcntl(fd, fcntl.F_GETFL)
  fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)

  c = None

  try:
    c = sys.stdin.read(1)
  except IOError: pass

  termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
  fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)

  return c

RX=25 #input 4 of the explorer phat
GO=6  #output 1 of the explorer phat
print "trying bb_serial"
#from https://www.rs-online.com/designspark/raspberry-pi-2nd-uart-a-k-a-bit-banging-a-k-a-software-serial
try:
        pi = pigpio.pi()
        pi.set_mode(RX, pigpio.INPUT)
        pi.bb_serial_read_open(RX, 9600, 8)
        pi.set_mode(6, pigpio.OUTPUT) # GPIO 6 is output 1 of explorer phat
        print "Car Controller:"
        pi.write(6,0)
        ch=get_char_keyboard_nonblock() #does not wait for input - returns a char or none
        while ch!='q':
                if ch=='f': #forward
                    pi.write(6,0)
                    #print hex(ord(ch)),1,
                elif ch=='h': #halt
                    pi.write(6,1)
                    #print hex(ord(ch)),0,
                (count, data) = pi.bb_serial_read(RX)
                if count:
                        sys.stdout.write(data)
                        #for character in data:
                        #  print chr(character), #hex(character)
                ch=get_char_keyboard_nonblock()
        pi.write(6,1)
finally:
        pi.write(6,1)
        print "done"
        pi.bb_serial_read_close(RX)
        pi.stop()
//www.elegoo.com
#include 
#include   //servo library
Servo myservo;      // create servo object to control servo

#define ENB 5   // Left  wheel speed
#define IN1 7   // Left  wheel forward
#define IN2 8   // Left  wheel reverse
#define IN3 9   // Right wheel reverse
#define IN4 11  // Right wheel forward
#define ENA 6   // Right wheel speed
#define carSpeed 220  // initial speed of car >=0 to oldwdist) {//but going the right way
        analogWrite(pwmleft,basespeed);analogWrite(pwmright,basespeed); //proceed
        Serial<<" wf-1\n";
    }
    else{
        analogWrite(pwmleft,lowspeed);analogWrite(pwmright,basespeed); //bear left
        Serial<<" wvlft\n";
    }
}
void toofar(){//if we are further than minwdist from the wall on our right side
    if (wdist<oldwdist) {//but going the right way
        analogWrite(pwmleft,basespeed);analogWrite(pwmright,basespeed); //proceed
        Serial<<" wf-2\n";
    }
    else{
        analogWrite(pwmleft,basespeed);analogWrite(pwmright,lowspeed); //bear right
        Serial<<" wvrt\n";
    }
}

void inthezone(){ //if we are between minwdist and maxwdist
    analogWrite(pwmleft,basespeed);analogWrite(pwmright,basespeed); //proceed
    Serial<<" wf-3\n";
}

void hardleft(){
  analogWrite(ENA, carSpeed);
  analogWrite(ENB, carSpeed);
  digitalWrite(IN1, LOW);
  digitalWrite(IN2, HIGH);
  digitalWrite(IN3, LOW);
  digitalWrite(IN4, HIGH);
  Serial.println(" HL ");
}

//Ultrasonic distance measurement Sub function
int getDistance() { //returns distance in cm
    digitalWrite(Trig, LOW);
    delayMicroseconds(2);
    digitalWrite(Trig, HIGH);
    delayMicroseconds(10);
    digitalWrite(Trig, LOW);
    return (int)pulseIn(Echo, HIGH) / 58;
}
void cruise(){
  if (0!=wdist) oldwdist=wdist; //track the wall distance after the first time
  wdist=getDistance(); //get the wall distance in cm
  Serial<<"cw "<<wdist<<" ";
   if (wdistmaxwdist){
      toofar();
  }else{
      inthezone();
  }
  digitalWrite(IN1, HIGH);
  digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW);
  digitalWrite(IN4, HIGH);
  delay(60);
}
int sharpy(){
  int aval=analogRead(0);
  if (aval<85) aval=85;
  int dist=(6787/(aval-3))-4;
   return dist;
}
void loop() {
    if (digitalRead(goPin)){
        fdist=sharpy();
        Serial<<millis()<<" f="<<fdist<minfdist){
           cruise();
       }else{
           hardleft(); //begin left turn
           Serial<<" L\n";
           while(fdist<=minfdist){ //until we're clearing the wall
              delay(1);
              fdist=sharpy();
           }
           Serial<<"K f="<<fdist<<"\n";
           stop();
         }
    }else{
      stop();
    }
 }

https://pinout.xyz/pinout/explorer_phat

https://www.raspberrypi.org/forums/viewtopic.php?t=106410

Advertisements

This Little Pi-guy Went To Florida But Its IP Connection Stayed Home


I brought a couple of Pi’s with me to play with over the winter months including the Olduino/370 dressed in its mini-370 case. Both were used regularly on my home wifi. I updated the wpa_supplicant.conf to reflect the apartment’s wifi but then got stuck. I’m used to having to fumble around to find the IP address on new networks but this setup seems resistant to my fumbling. I don’t have a password for the router so i can’t look at its tables and all of the scanners i tried come up dry showing only my own PC’s address. I finally resorted to lighting up a hotspot access point on my windows box using the same ssid and password as my home router(yes, clever, i know) and the Pi which normally uses a static IP at home popped up although with a dhcp-assigned address.

So now i have one Pi up on my faux-home win10 hosted network and another one that won’t connect to either my faux-home or the apartment network. Possibly:
-finger problems overall
-the apartment router won’t serve the pi an ip address but windows will
-something stops the PI’s from using the wpa_supplicant.conf that i put in the boot directory of the SD card. **Seems very likely – see below**

As an extra bit of fun i don’t find ping works on the apartment network. I can’t ping one windows laptop from another, ping my phone etc.

UPDATE:

  • The basic problem is that the router has “Access Point Isolation” activated.  This means that one IP on the local network can’t communicate with the others.  Hence I can’t ping anything other than the router from windows and none of the IP scanners work.
  • I had the bright idea to activate a hotspot on my laptop with the same ssid and password as my home router.  The Pi was assigned an IP but I couldn’t get through to it because it thought it’s IP was 192.168.0.203 and windows was routing addresses in the 192.168.137.xx range.
  • I finally found a very nice piece of software called “Linux File System For Windows” that happily let me access the linux partition on the SD card from windows and take out the static IP setting.
  • I saw a post somewhere about a setup where a guy had et up his Pi to first try for an IP from his laptop and failing that go for a static IP(or something like that) that would be a good plan.  Also something where the Pi advertises its IP address.  I had various ways of doing that but the static address was so convenient at home that i dropped them all.

Reference:
here‘s the site i got the static ip info from.

interface wlan0

static ip_address=192.168.0.203/24
static routers=192.168.0.1
static domain_name_servers=192.168.0.1 8.8.8.8

 

ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1

network={
    ssid="therowes24"
    psk="********"
    scan_ssid=1
}

Yippee – In Which I Improve My Workflow With “cat”

yikes

I’ve been doing a lot of compile/link/test sequences with wiringMVS.  The workflow consists of:

  • editing the C program along with its JCL and submitting it to MVS from textpad
  • switching over to putty and watching the MVS console to guess whether the compile worked based on: how long it runs, number of lines produced etc.
  • if i think it failed switching to winscp and reloading the printer output screen
  • switching to textpad again to bumble through multiple jobs output looking for error messages
  • rinse and repeat

I had the idea that if i could compile/assemble/link on windows it would be more interactive.  GCCMVS runs well enough and i can get the C errors out of the way but copying the assembler output into a job stream and submitting that is a bit painful and then i’m guessing again because sometimes i’ll get assembler errors and missing functions can show up in the link step.  I’ve been pursuing the Z390 assembler as an alternative but i’m not there yet.

In the meantime i had a bit of a brainstorm.  I set up a Hercules printer as a pipe to “cat”.  An MVS job sends output to the printer, hercules pipes it to “cat”, and cat sends it back to its stdout which Hercules displays on the console.  Bingo – instant interactive output!

I haven’t done it yet but it should be easy to direct the assembler and lked diagnostic steps to the same “printer”. I could even allocate it to JES2 and establish a “print to console” class!

I also added a couple of crude notification job steps at the end: YIKESC runs only if the compiler step fails and sends a message to the console, and YIPPEE runs only if all steps succeed and again sends a message. I sould clean them up with a stored PROC but the overall result is pretty gratifying.

//GPIOMULE JOB CLASS=A,MSGCLASS=A,REGION=4096K,
// MSGLEVEL=(1,1),
// USER=HERC01,PASSWORD=CUL8TR
//S1 EXEC GCCCLF,COS1='-S',
// PARM.ASM='DECK,NOLIST,TEST,TERM,NOXREF,NOMLOGIC,NORLD',
// PARM.LKED='TERM,TEST'
//COMP.INCLUDE DD DSN=HERC01.WIRINGMV.INCLUDE,DISP=SHR
//SYSIN DD *
pigs=undef(); //deliberate error
  ...
  ...
  rest of program
*/
//COMP.SYSPRINT DD DUMMY
//COMP.SYSTERM DD UNIT=30F
//LKED.SYSLMOD DD DSN=HERC01.LOAD(GPIO3),DISP=SHR
//YIPPEE EXEC PGM=IKJEFT01,PARM='SEND ''YIPPEE''',
// COND=((0,NE,S1.COMP),(0,NE,S1.ASM),(0,NE,S1.LKED))
//SYSPRINT DD DUMMY
//SYSOUT   DD DUMMY
//SYSTSPRT DD DUMMY
//SYSIN    DD DUMMY
//SYSTSIN  DD DUMMY
//YIKESC EXEC PGM=IKJEFT01,PARM='SEND ''YIKES THE COMPILER''',
// COND=((0,EQ,S1.COMP))
//SYSPRINT DD DUMMY
//SYSOUT   DD DUMMY
//SYSTSPRT DD DUMMY
//SYSIN    DD DUMMY
//SYSTSIN  DD DUMMY

MK48Z02 Non-Volatile RAM for the Boyd Calculator

My last effort with the Boyd used the onboard 64 byte ram memory with a 2732 eprom in an adapter.  I wasn’t hitting the ram limit but i thought using a non-volatile ram chip as primary memory would give me more flexibility.  I got an old signetics part from eBay to try out and today i got around to comparing the pinouts to the 2532 that’s original to the Boyd.

As usual the data and address lines are more-or-less ok.  The 4K 2532 has one extra address line A11 which would hit /CE on the nvram but that’s ok because it would just deselect the nvram for addresses above 2K.  The PD/PGM on the 2532 seems ominous but it really just acts as a low-going chip enable and connects to /G which is /output enable on the nvram.  again VPP sounds ominous but i bet it’s tied to +V in the Boyd – it connects to /write on the nvram.

So to use the nvram fully, I would need to connect /mwrite to pin 21 and /mread to pin 20.  I don’t think either of those would interfere with the normal 2532 chip.

I currently have a 2732 adapter in the boyd.  I should look hard at the 2732 pinout but it looks like i would have to remove A11 from pin 21 and supply /mwrite instead.  this is a bit simpler but the adapter is already high and putting the tall nvram on top of it would probably break the camel’s back.

18-12-11 TMS2732

Carbot/370 State of Play

As of today the Carbot hardware and software are basically functional, if rough.  The control program written in C runs on the MVS operating system on a simulated IBM 370 computer under the Hercules emulator running on a raspberry Pi Zero W.  The Pi is housed in a miniature mainframe case with an infrared rangefinder mounted on it and rides on a converted radio control car chassis.  The car’s motors are controlled by a pimoroni explorer pHAT mounted on the Pi.

I’m missing the side facing sensor needed for wall following and I’m using direct drive for the motors rather than using PWM to modulate them.  I do think it’s funny to see the mini-mainframe darting around getting stuck under the furniture.

I had been worried that running the motors from the same 5V supply as the Pi would be a problem but it seems not to be an issue so far.

 The Carbot software is pretty simple at the moment it’s meant to be run as a TSO command or a batch job and it does one action per run as controlled by parameters on the command line for example “carbot f” as a TSO command or // EXEC PGM=CARBOT,PARM=’F’ will each start the motors going forward. In the demo video “carbot g” starts a “Go” routine that runs forward and turns left when it sees an obstacle.

void go(int ttl){
    int maxprox=7500,tprox=7000;
    int fprox,now,start=millis();
    fprox=analogRead(0); //read front sensor
    printf("Go starts at %d\n",start);
    while (now-start< ttl){
    	if (fproxtprox) &&(now-start<ttl)){
    	    	drive(1,0,0,1); //hardleft
	    	now=millis();
	    	fprox=analogRead(0);
    	    }
    	    drive(0,0,0,0);
    	}
    	now=millis();
    	fprox=analogRead(0);
    }
    drive(0,0,0,0);
}

The "drive(r1,r2,l1,l2)" routine used above controls the right and left motors such that drive(1,0,1,0) starts both motors going forward while drive(1,0,0,1) runs lthe right motor full forward and the left motor full reverse so we get a hard left turn.

//CARBOT01 JOB CLASS=A,MSGCLASS=A,REGION=4096K,MSGLEVEL=(0,0),
// USER=HERC01,PASSWORD=CUL8TR
//S1 EXEC GCCCL,COS1='-S',
// PARM.ASM='DECK,NOLIST,TEST,NOXREF,NOMLOGIC,NORLD',
// PARM.LKED='TEST'
//COMP.INCLUDE DD DSN=HERC01.WIRINGMV.INCLUDE,DISP=SHR
//SYSIN DD *
#include 
#include 
#include "wiring.h"
int rfw=24,lfw=25; // forward right and left wheels
int rrv=28,lrv=29; // reverse right and left wheels
void init(){
  printf("init\n");
  pinMode(rfw,OUTPUT);pinMode(lfw,OUTPUT);
  pinMode(rrv,OUTPUT);pinMode(lrv,OUTPUT);
}
void drive(int rf,int rr,int lf,int lr){
    digitalWrite(rfw,rf);digitalWrite(rrv,rr);
    digitalWrite(lfw,lf);digitalWrite(lrv,lr);
}
void keepaway(int ttl){
    int minprox=5000, maxprox=8000;
    int fprox,now,start=millis();
    fprox=analogRead(0); //read front sensor
    printf("keepaway starts at %d\n",start);
    while (now-start< ttl){
    	//printf("keepaway at t=%d, prox is %d\n",now,fprox);
    	if (fproxmaxprox){
    	    drive(0,1,0,1);
    	} else {
    	    drive(0,0,0,0);
    	}
    	delay(100);
    	now=millis();
    	fprox=analogRead(0);
    }
    drive(0,0,0,0);
}
void go(int ttl){
    int maxprox=7500,tprox=7000;
    int fprox,now,start=millis();
    fprox=analogRead(0); //read front sensor
    printf("Go starts at %d\n",start);
    while (now-start< ttl){
    	if (fproxtprox) &&(now-start<ttl)){
    	    	drive(1,0,0,1); //hardleft
	    	now=millis();
	    	fprox=analogRead(0);
    	    }
    	    drive(0,0,0,0);
    	}
    	now=millis();
    	fprox=analogRead(0);
    }
    drive(0,0,0,0);
}

int main(int argc, char *argv[]){   
    int retval=42;
    int fprox=43;
    printf("Oh Hello - Carbot Here\n");
    init();
    switch(argv[1][0]){
    case 'G': case 'g': //"Go" for 3 seconds
        go(3000);    
        break;
    case 'K': case 'k': //play keepaway for 10 seconds
        keepaway(10000);    
        break;
    case '?': //read the front sensor
        fprox=analogRead(0);    
        printf("front sensor says %d\n",fprox);
        break;
    case 'T': case 't':
        drive(1,0,0,1);    
        break;
    case 'F': case 'f':
        drive(1,0,1,0);    
        break;
    case 'R': case 'r':
        drive(0,1,0,1);    
        break;
    case 'X': case 'x': //stop
        drive(0,0,0,0);
        break;
    default:
        printf("not doing %s(%x %x) \n",argv[1],argv[1][0],argv[1][1]);
    }
    //asm(" WTO 'OK Bye' ");
    return retval;
}
//LKED.SYSLMOD DD DSN=HERC01.LOAD(CARBOT),DISP=SHR
//* DISP=(,CATLG),
//* UNIT=SYSDA,SPACE=(TRK,(10,5,5)),
//* DCB=(RECFM=U,BLKSIZE=19069)
//*     EXEC PGM=*.S1.LKED.SYSLMOD,
//*     PARM='write 0 low'
//*SYSPRINT DD SYSOUT=*
//*SYSTERM DD SYSOUT=*
//*SYSIN DD DUMMY
//*     EXEC PGM=*.S1.LKED.SYSLMOD,
//*     PARM='read 0'
//*SYSPRINT DD SYSOUT=*
//*SYSTERM DD SYSOUT=*
//*SYSIN DD DUMMY

I had to update the SVC intercept hook code in Hercules and the wiring.h GCCMVS macros to support the millis() call.

//INCLUDE JOB CLASS=A,MSGCLASS=A,REGION=4096K,
// USER=HERC01,PASSWORD=CUL8TR
//*ALLOC EXEC PGM=IEFBR14
//*SYSUT2   DD  DSNAME=HERC01.WIRINGMV.INCLUDE,DISP=(,CATLG),
//*             UNIT=SYSDA,SPACE=(TRK,(10,5,5)),
//*             DCB=(RECFM=VB,LRECL=255,BLKSIZE=6233)
//LOAD EXEC PGM=IEBGENER
//SYSPRINT DD SYSOUT=A
//SYSIN DD DUMMY
//SYSUT2 DD DSNAME=HERC01.WIRINGMV.INCLUDE(WIRING),DISP=SHR
//SYSUT1 DD *
  //18-12-06 including millis() code 9
#define INPUT 0
#define OUTPUT 1
#define LOW 0
#define HIGH 1
int adcSetup(){
    int retval='s';
    asm(" LA 0,X'42' FLAG FOR SVC 255 ");
    asm(" LA 1,7 MCP3003 ADC SETUP ");
    asm(" LA 2,0 ");
    asm(" SVC 255 ISSUE SVC\n"
        " LR %0,15\n"
        : "=r" (retval));
    if((retval<0)){
        printf("mcp3002Setup return value is %d\n",retval);
    }
    return retval;
}
int millis(){
    int retval='R';
    asm(" LA 2,0 \n"
        " LA 1,9\n" //code for analog read
        " LA 0,X'42' FLAG FOR SVC 255 \n"
        " SVC 255 ISSUE SVC\n"
        " LR %0,15\n"
        : "=r" (retval) //returned value
        :); //no inputs to asm()
    return retval;
}
int analogRead(int pin){
    int retval='R';
    asm(" LR 2,%1 \n"
        " LA 1,8\n" //code for analog read
        " LA 0,X'42' FLAG FOR SVC 255 \n"
        " SVC 255 ISSUE SVC\n"
        " LR %0,15\n"
        : "=r" (retval) //returned value
        : "r" (pin));
    return retval;
}
int setup(){
    int retval='s';
    asm(" LA 0,X'42' FLAG FOR SVC 255 ");
    asm(" LA 1,6 SETUP ");
    asm(" LA 2,0 ");
    asm(" SVC 255 ISSUE SVC\n"
        " LR %0,15"
        : "=r" (retval));
    if((retval!=0) && (retval!=180923)){
        printf("setup return value is %d\n",retval);
    }
    return retval;
}
int digitalWrite(int pin, int value){
    asm(" LR 2,%0 \n"
        " LR 1,%1\n"
        " LA 0,X'42' FLAG FOR SVC 255 \n"
        " SVC 255 ISSUE SVC"
        : /*no outputs*/
        : "r" (pin), "r" (value+2));//((value!=0)+2));
}
int digitalRead(int pin){
    int retval='R';
    asm(" LR 2,%0 \n"
        " LA 1,4\n" //code for read
        " LA 0,X'42' FLAG FOR SVC 255 \n"
        " SVC 255 ISSUE SVC\n"
        " LR %0,15\n"
        : "=r" (retval) //returned value
        : "r" (pin));
    return retval;
}
int pinMode(int pin, int value){
    asm(" LR 2,%0 \n" //pin
        " LR 1,%1\n"  //value
        " LA 0,X'42' FLAG FOR SVC 255 \n"
        " SVC 255 ISSUE SVC"
        : /*no outputs*/
        : "r" (pin), "r" ((value!=0)));
}
void delay(int value){
    asm(" LR 2,%0 \n" //ddelay time in MS
        " LA 1,5\n"   //code for delay
        " LA 0,X'42' FLAG FOR SVC 255 \n"
        " SVC 255 ISSUE SVC"
        : /*no outputs*/
        : "r" (value));
}
//INCLUDE JOB CLASS=A,MSGCLASS=A,REGION=4096K,
// USER=HERC01,PASSWORD=CUL8TR
//*ALLOC EXEC PGM=IEFBR14
//*SYSUT2   DD  DSNAME=HERC01.WIRINGMV.INCLUDE,DISP=(,CATLG),
//*             UNIT=SYSDA,SPACE=(TRK,(10,5,5)),
//*             DCB=(RECFM=VB,LRECL=255,BLKSIZE=6233)
//LOAD EXEC PGM=IEBGENER
//SYSPRINT DD SYSOUT=A
//SYSIN DD DUMMY
//SYSUT2 DD DSNAME=HERC01.WIRINGMV.INCLUDE(WIRING),DISP=SHR
//SYSUT1 DD *
  //18-12-06 including millis() code 9
#define INPUT 0
#define OUTPUT 1
#define LOW 0
#define HIGH 1
int adcSetup(){
    int retval='s';
    asm(" LA 0,X'42' FLAG FOR SVC 255 ");
    asm(" LA 1,7 MCP3003 ADC SETUP ");
    asm(" LA 2,0 ");
    asm(" SVC 255 ISSUE SVC\n"
        " LR %0,15\n"
        : "=r" (retval));
    if((retval<0)){
        printf("mcp3002Setup return value is %d\n",retval);
    }
    return retval;
}
int millis(){
    int retval='R';
    asm(" LA 2,0 \n"
        " LA 1,9\n" //code for analog read
        " LA 0,X'42' FLAG FOR SVC 255 \n"
        " SVC 255 ISSUE SVC\n"
        " LR %0,15\n"
        : "=r" (retval) //returned value
        :); //no inputs to asm()
    return retval;
}
int analogRead(int pin){
    int retval='R';
    asm(" LR 2,%1 \n"
        " LA 1,8\n" //code for analog read
        " LA 0,X'42' FLAG FOR SVC 255 \n"
        " SVC 255 ISSUE SVC\n"
        " LR %0,15\n"
        : "=r" (retval) //returned value
        : "r" (pin));
    return retval;
}
int setup(){
    int retval='s';
    asm(" LA 0,X'42' FLAG FOR SVC 255 ");
    asm(" LA 1,6 SETUP ");
    asm(" LA 2,0 ");
    asm(" SVC 255 ISSUE SVC\n"
        " LR %0,15"
        : "=r" (retval));
    if((retval!=0) && (retval!=180923)){
        printf("setup return value is %d\n",retval);
    }
    return retval;
}
int digitalWrite(int pin, int value){
    asm(" LR 2,%0 \n"
        " LR 1,%1\n"
        " LA 0,X'42' FLAG FOR SVC 255 \n"
        " SVC 255 ISSUE SVC"
        : /*no outputs*/
        : "r" (pin), "r" (value+2));//((value!=0)+2));
}
int digitalRead(int pin){
    int retval='R';
    asm(" LR 2,%0 \n"
        " LA 1,4\n" //code for read
        " LA 0,X'42' FLAG FOR SVC 255 \n"
        " SVC 255 ISSUE SVC\n"
        " LR %0,15\n"
        : "=r" (retval) //returned value
        : "r" (pin));
    return retval;
}
int pinMode(int pin, int value){
    asm(" LR 2,%0 \n" //pin
        " LR 1,%1\n"  //value
        " LA 0,X'42' FLAG FOR SVC 255 \n"
        " SVC 255 ISSUE SVC"
        : /*no outputs*/
        : "r" (pin), "r" ((value!=0)));
}
void delay(int value){
    asm(" LR 2,%0 \n" //ddelay time in MS
        " LA 1,5\n"   //code for delay
        " LA 0,X'42' FLAG FOR SVC 255 \n"
        " SVC 255 ISSUE SVC"
        : /*no outputs*/
        : "r" (value));
}

It Phits! (In which I Cram Everything In)

img_3008
Here’s Carbot/370 more or less slung together. All the wiring is there except the power and the only component missing is the sideways looking sensor. When complete the Carbot will be able navigate a simple arena like a wall-following racer. This is the same as previous incarnations of the Carbot except that the navigation and motor control will be done by MVS batch jobs.

Conceptually this is a variant of the IBM 370/158M (mobile edition). The self driving capability might have been used within a city but for longer moves I’m sure FEMA would have used trucks.

From the rear you can see the Pimoroni Explorer phat with the Raspberry Pi Zero W underneath it. The IR distance sensor on top is jumper wired to the phat analog inputs and the motor wires go to the phat’s motor control outputs. The OLED screen is connected to the I2C pins of the Pi although I had to solder to the stubs left exposed of the phat’s header which otherwise hides those pins.

Phinally! (In Which I Get the Pimoroni Explorer PHAT working)

3018-03
A while ago I bought a little circuit board that seemed perfect for the Olduino/370 Carbot.  It fits onto the Raspberry Pi Zero and provides motor drivers, analog-digital conversion, and buffered 5v i/o.  I put it aside because it started to seem like too much trouble to work with: it uses chips i’m not familiar with, all the examples and libraries are in python rather than C, and it sits over all the GPIO pins making it hard to connect other peripherals. (I’m a fan of python but i’m integrating with the C code base of Hercules and wiringPi so its’ just an added headache to convert).

Today, though, everything clicked into place.  The interfaces I need are:

  • The motor driver pins
  • The ADC converter

I got the motor driver pins figured out finally by running a python example and checking the state of the likely seeming pins.  I was quickly(well, not quickly) able to identify the left hand motor pins as 24&28, and the right hand pins as 25&29. i.e. if 24 is high and 28 low, the left motor runs forward full speed; reversing those causes the motor to run backwards; and pwm-cycling the high pin can modulate the speed.  These results are solid but they’ll have to be adjusted for final wiring.

#!/usr/bin/env python
import sys
import explorerhat as eh
eh.motor.one.invert()
def setmotor(m2,m1):
  if (m1>=0):
    eh.motor.one.forward(m1)
  else:
    eh.motor.one.backward(-1*m1)
  if (m2>=0):
    eh.motor.two.forward(m2)
  else:
    eh.motor.two.backward(-1*m2)
line=sys.stdin.readline()
while(line != ''):
  if line.startswith('0'):
    setmotor(0,0)
  elif line.startswith('1'):
    setmotor(0,100)
  elif line.startswith('2'):
    setmotor(100,0)
  elif line.startswith('3'):
    setmotor(100,100)
  elif line.startswith('-'):
    setmotor(0,-100)
  elif line.startswith('!'):
    setmotor(-100,0)
  line=sys.stdin.readline()
 #print("bye")

I’m reading the ADS1015 analog-digital-converter through an extension of wiringPi meant for the very similar ADS1115. The trickiest bit is that the 12 bits returned by the ADC get munged by the wiringPi code which expects 16 bits. I forget how i came to this but the answer was to readjust the result provided by wiringPi after the fact. Also, the ADS1015 is 4 channels but I’ve only found the channels labelled “1” and “4” available as analogRead(3) and (0) respectively.

//ads1015 test raspi adc from explorer phat
#include 
#include 
#include 
#define MY_BASE 2222
void main(){
  float v;
  ads1115Setup (MY_BASE, 0x48) ;
  int ch0 = analogRead (MY_BASE + 0) ;
  int ch3 = analogRead (MY_BASE + 3) ;
  printf("ch0 sez %d\n",ch0);
  printf("ch3 sez %d\n",ch3);
  if (ch3>0x7ff0) ch3-=0x10000;
  v=((float)(ch3>>4)/0x7ff)*5.0;
  if (ch0>0x7ff0) ch0-=0x10000;
  printf("channel 0 voltage is %f\n",((float)(ch0>>4)/0x7ff)*5.0);
  printf("channel 3 voltage is %f\n",((float)(ch3>>4)/0x7ff)*5.0);
}

Bundling this for execution meant simple changes to the wiringMVS.c code that gets copied into general2.c of the Hercules emulator.

//18-11-23 remove automatic inline setup calls
//         comment out most verbose msgs
//18-11-30 changed analogread setup and code to use the ads1115 extension rather than MCP3002
#define SPI_CHAN        0
#define MY_PIN_BASE     5555    // Anything >= 64
int wiringMVS(int function, int parameter){//implements wiringPi functions for MVS
	static int mcp3002_pinbase=0,wiringPiSetuped=0;
	int result=0;
	//printf("wiringMVS sez: function=%x,parameter=%x, setup@%X=%d\n",function,parameter,&wiringPiSetuped,wiringPiSetuped);
	switch (function){
		case 0:	//pinmode INPUT
			pinMode(parameter,INPUT);
			break;
		case 1: //pinmode output
			pinMode(parameter,OUTPUT);
			break;
		case 2: //digitalWrite(LOW)
			digitalWrite(parameter,LOW);
			break;
		case 3: //digitalWrite(HIGH)
			digitalWrite(parameter,HIGH);
			break;
		case 4: //digitalRead
			result=digitalRead(parameter);
			//printf("digitalRead(%d) sez %d\n",parameter,result);
			break;
		case 5: //
			delay(parameter);
			break;
		case 6: // setup()
			result=wiringPiSetup ();
			if (result!=0)
 				printf("wiringMVS Setup() sez %d\n",result);
			break;
		case 7: // mcp3002Setup()
			result=ads1115Setup (MY_PIN_BASE,0x48);
			if (result0x7ff0) result-=0x10000;
			break;
	}
	//printf("wiringMVS sez %d\n",result);
	return result;
}

Oh, also, I’m normally an SVN user on windows but i decided to try git for tracking changes to the olduino/370 components. For the ads1015c test module it was as simple as
git init
git add ads1015.c
git commit ads1015.c
This is not very sophisticated source code management but it saves me keeping multiple copies of my work for backing off.
 

In which Big Brother Gets His Eyes On

Here I’ve got the forward distance sensor mounted on top of the Rogue 370.

img_2982

When running the Carbot I will need a second sensor mounted on the car body.

img_2981

img_2980

The pi is actually inside there along with the mcp3002 adc chip and the motor driver although none of the motor wiring is in place.

img_2984img_2988

wiringMvs State of Play

“wiring” is the project that spawned the Arduino.  It is where constructions like digitalWrite(), digitalRead(), delay(), etc. came from.  wiringMVS is my attempt to port those sorts of functions to programs running under the MVS operating system from the 1970’s.  My instance of MVS is running on the Hercules emulator using the Tk4- Tur(n)key setup which i heartily recommend.  Because my emulator is running on a Raspberry Pi I am using the excellent wiringPi as the basis for my work.

So far I have implemented digitalRead, AnalogRead, digitalWrite, and delay functions.  I have not tried it yet but I believe analog(pwm)Write should be fairly easy.  These combined should be enough to let me program a car type robot driven by a MVS batch job. It will monitor it’s environment with a sharp infrared distance sensor. 

There are a number of parts to the work

  1. The “hook” in Hercules that invokes wiringPi functions
  2. A set of C functions that implement digitalRead() etc
  3. An MVS program called “gpio” that can exercise and test the functions.

---Near the top of the file
#include
#if !defined(_GENERAL2_C_)
#define _GENERAL2_C_
#include  //WJR
#include  //WJR
#include  //WJR
#endif
---Near line 1335
/*-------------------------------------------------------------------*/
/* 0A   SVC   - Supervisor Call                                 [RR] */
/*-------------------------------------------------------------------*/
DEF_INST(supervisor_call)
{
BYTE    i;                              /* Instruction byte 1        */
PSA    *psa;                            /* -> prefixed storage area  */
RADR    px;                             /* prefix                    */
int     rc;                             /* Return code               */

    RR_SVC(inst, regs, i);
//BEGINNING OF WIRINGPI CODE BY WJR 18-09-21
#if 1 
/* if we have an SVC 255 and R0 is set to a magic number(0x42), then
we invoke the wiringpi code for MVS */
if ((i == 255) && (regs->GR_L(0) == 0x00000042))
	{
	regs->GR_L(15)=wiringMVS(regs->GR_L(1),regs->GR_L(2));
    	PERFORM_SERIALIZATION (regs);
    	PERFORM_CHKPT_SYNC (regs);
    	RETURN_INTCHECK(regs);
	}
#endif
//END OF WIRINGPI CODE BY WJR 18-09-21

//18-11-23 remove automatic inline setup calls
//         comment out most verbose msgs
#define SPI_CHAN        0
#define MY_PIN_BASE     5555    // Anything >= 64
int wiringMVS(int function, int parameter){//implements wiringPi functions for MVS
	static int mcp3002_pinbase=0,wiringPiSetuped=0;
	int result=0;
	switch (function){
		case 0:	//pinmode INPUT
			pinMode(parameter,INPUT);
			break;
		case 1: //pinmode output
			pinMode(parameter,OUTPUT);
			break;
		case 2: //digitalWrite(LOW)
			digitalWrite(parameter,LOW);
			break;
		case 3: //digitalWrite(HIGH)
			digitalWrite(parameter,HIGH);
			break;
		case 4: //digitalRead
			result=digitalRead(parameter);
			//printf("digitalRead(%d) sez %d\n",parameter,result);
			break;
		case 5: //
			delay(parameter);
			break;
		case 6: // setup()
			wiringPiSetuped=1;
			result=wiringPiSetup ();
			if (result!=0)
 				printf("wiringMVS Setup() sez %d\n",result);
			break;
		case 7: // mcp3002Setup()
			mcp3002_pinbase=MY_PIN_BASE;
			result=mcp3002Setup (MY_PIN_BASE,SPI_CHAN);
			if (result<0)
 				printf("wiringMVS mcp3002Setup() sez %d\n",result);
			break;
		case 8: //analogRead
			result=analogRead(MY_PIN_BASE+parameter);
			//printf("analogRead sez %d\n",result);
			break;
	}
	//printf("wiringMVS sez %d\n",result);
	return result;
}

The C functions get placed in an MVS dataset: herc01.include(wiringMVS) and are copied into a program compiled with GCCMVS by #include “wiringmvs.h”.

//INCLUDE JOB CLASS=A,MSGCLASS=A,REGION=4096K,
// USER=HERC01,PASSWORD=CUL8TR
//*ALLOC EXEC PGM=IEFBR14
//*SYSUT2   DD  DSNAME=HERC01.WIRINGMV.INCLUDE,DISP=(,CATLG),
//*             UNIT=SYSDA,SPACE=(TRK,(10,5,5)),
//*             DCB=(RECFM=VB,LRECL=255,BLKSIZE=6233)
//LOAD EXEC PGM=IEBGENER
//SYSPRINT DD SYSOUT=A
//SYSIN DD DUMMY
//SYSUT2 DD DSNAME=HERC01.WIRINGMV.INCLUDE(WIRING),DISP=SHR
//SYSUT1 DD *
#define INPUT 0
#define OUTPUT 1
#define LOW 0
#define HIGH 1
int mcpSetup(){
    int retval='s';
    asm(" LA 0,X'42' FLAG FOR SVC 255 ");
    asm(" LA 1,7 MCP3003 ADC SETUP ");
    asm(" LA 2,0 ");
    asm(" SVC 255 ISSUE SVC\n"
        " LR %0,15\n"
        : "=r" (retval));
    if((retval<0)){
        printf("mcp3002Setup return value is %d\n",retval);
    }
    return retval;
}
int analogRead(int pin){
    int retval='R';
    asm(" LR 2,%1 \n"
        " LA 1,8\n" //code for analog read
        " LA 0,X'42' FLAG FOR SVC 255 \n"
        " SVC 255 ISSUE SVC\n"
        " LR %0,15\n"
        : "=r" (retval) //returned value
        : "r" (pin));
    return retval;
}
int setup(){
    int retval='s';
    asm(" LA 0,X'42' FLAG FOR SVC 255 ");
    asm(" LA 1,6 SETUP ");
    asm(" LA 2,0 ");
    asm(" SVC 255 ISSUE SVC\n"
        " LR %0,15"
        : "=r" (retval));
    if((retval!=0) && (retval!=180923)){
        printf("setup return value is %d\n",retval);
    }
    return retval;
}
int digitalWrite(int pin, int value){
    asm(" LR 2,%0 \n"
        " LR 1,%1\n"
        " LA 0,X'42' FLAG FOR SVC 255 \n"
        " SVC 255 ISSUE SVC"
        : /*no outputs*/
        : "r" (pin), "r" (value+2));//((value!=0)+2));
}
int digitalRead(int pin){
    int retval='R';
    asm(" LR 2,%0 \n"
        " LA 1,4\n" //code for read
        " LA 0,X'42' FLAG FOR SVC 255 \n"
        " SVC 255 ISSUE SVC\n"
        " LR %0,15\n"
        : "=r" (retval) //returned value
        : "r" (pin));
    return retval;
}
int pinMode(int pin, int value){
    asm(" LR 2,%0 \n" //pin
        " LR 1,%1\n"  //value
        " LA 0,X'42' FLAG FOR SVC 255 \n"
        " SVC 255 ISSUE SVC"
        : /*no outputs*/
        : "r" (pin), "r" ((value!=0)));
}
void delay(int value){
    asm(" LR 2,%0 \n" //ddelay time in MS
        " LA 1,5\n"   //code for delay
        " LA 0,X'42' FLAG FOR SVC 255 \n"
        " SVC 255 ISSUE SVC"
        : /*no outputs*/
        : "r" (value));
}

The demo program GPIO is compiled into sys1.linklib to make it easily accessible. It can run as a batch job or TSO command. It responds to a set of commands to exercise wiringMVS

  • gpio read
  • gpio write high/low/0/1
  • gpio analogread
  • gpio delay

It is really only checking the first letter of the command so “m” will do instead of mcp3002setup.  The two setup calls have to be made before anything else will work. If nothing else, this is a good illustration of how to compile and run GCCMVScode.

//GPIOMULE JOB CLASS=A,MSGCLASS=A,REGION=4096K,
// USER=HERC01,PASSWORD=CUL8TR
//S1 EXEC GCCCL,COS1='-S',
// PARM.ASM='DECK,LIST,TEST,NOXREF,NOMLOGIC,NORLD',
// PARM.LKED='TEST'
//COMP.INCLUDE DD DSN=HERC01.WIRINGMV.INCLUDE,DISP=SHR
//SYSIN DD *
#include 
#include 
#include "wiring.h"

int main(int argc, char *argv[]){   
    int retval=42;
    int pin,hilo, delaytime;
    switch(argv[1][0]){
    case 'D': case 'd':
        delaytime=atoi(argv[2]);
        delay(delaytime);    
        break;
    case 'w': case 'W': //write
        pin=atoi(argv[2]);
        pinMode(pin,OUTPUT);
        switch(argv[3][0]){
        case 'h': case 'H': case '1':
            hilo=HIGH;
            break;
        case 'l': case 'L': case '0':
            hilo=LOW;
            break;
        default:
            return 12;
        }
        printf("setting %d to %d\n",pin,hilo);
        digitalWrite(pin,hilo);
        break;
    case 'R': case 'r':
        pin=atoi(argv[2]);
        retval=digitalRead(pin);    
        printf("pin %d reads %d\n",pin,retval);
        break;
    case 'A': case 'a':
        pin=atoi(argv[2]);
        retval=analogRead(pin);    
        printf("pin %d analogReads %d\n",pin,retval);
        break;
    case 'S': case 's':
        retval=setup();
        break;
    case 'M': case 'm':
        retval=mcpSetup();
        break;
    default:
        printf("not doing %s\n",argv[1]);
    }
    return retval;
}
//LKED.SYSLMOD DD DSN=SYS1.LINKLIB(GPIO),DISP=SHR
//*     EXEC PGM=*.S1.LKED.SYSLMOD,
//*     PARM='write 0 low'
//*SYSPRINT DD SYSOUT=*
//*SYSTERM DD SYSOUT=*
//*SYSIN DD DUMMY
//*     EXEC PGM=*.S1.LKED.SYSLMOD,
//*     PARM='read 0'
//*SYSPRINT DD SYSOUT=*
//*SYSTERM DD SYSOUT=*
//*SYSIN DD DUMMY

Initializing wiringMvs/wiringPi

Right now my code has a couple of explicit initialization paths in it (cases 6 and 7 below)

int wiringMVS(int function, int parameter){//implements wiringPi functions for MVS
	int result=0;

	switch (function){
		case 0:	//pinmode INPUT
			pinMode(parameter,INPUT);
			break;
		.
		.
		.
		case 6: // setup()
			result=wiringPiSetup ();
			if (result!=0)
 				printf("wiringMVS Setup() sez %d\n",result);
			break;
		case 7: // mcp3002Setup()
			result=mcp3002Setup (MY_PIN_BASE,SPI_CHAN);
			if (result<0)
 				printf("wiringMVS mcp3002Setup() sez %d\n",result);
			break;
		.
		.
		.
	}
	return result;
}

I would like to replace them with automatic initialization using static variables as below:

int wiringMVS(int function, int parameter){//implements wiringPi functions for MVS
	static int init=0;
	int result=0;
	if (!init){
		init=1;
		result=wiringPiSetup ();
		if (result!=0) printf("wiringMVS Setup() sez %d\n",result);
	}
	switch (function){
		case 0:	//pinmode INPUT
			pinMode(parameter,INPUT);
			break;
		.
		.
		.
		case 7: // mcp3002Setup()
			result=mcp3002Setup (MY_PIN_BASE,SPI_CHAN);
			if (result<0)
 				printf("wiringMVS mcp3002Setup() sez %d\n",result);
			break;
		.
		.
		.
	}
	return result;
}

UPDATE: I decided this wasn't a good idea and I kept the explicit initialization. I need to arrange for something to run when MVS starts to make the init cals.

It’s Alive! Olduino/370 Carbot Spins its Wheels!


I was amazed that the MVS part of this worked so easily. I had made some simple C wrappers for the wiringMVS pinMode, digitalWrite, and delay functions. Once I got the Pi hooked correctly to the motor driver board I was easily able to run a batch job to access the robot car wheel motors.

I need now a good solid mount for the Pi as well as getting the input functions working. I’m using the adapter board from the 1802 carbot because it’s handy but it’s striking how the Pi needs many of the same crutches as the 1802 did including software PWM, an outboard analog-digital conversion, and extra resistors to access the ultrasonic range finder.
img_2783
In the still image, the pi is in the lower left with eight leads going up to the adapter board. The motor power pack is below and behind it. The car body on the right came from an RC car with two independent rear motors, the four leads going left to the motor driver board are motor power, the two on the right are connected to the logic power pack which isn’t hooked to anything.

For each motor, the driver board takes three inputs, for the right side it’s PWMA, AIN1, and AIN2. AIN1 and AIN2 determine the direction the motor rotates and PWMA determines how hard it spins. In this case I’m just setting the PWM to full on but I can cycle it off and on to control speed.

The adapter board also has spots to attach an ultrasonic sideways sensor and an IR forward distance sensor with an ADC converter. It has a spot for a boost converter to generate 5V for the logic from two AA batteries. Probably the simplest approach is to make something similar but smaller and more suited to the Pi’s form factor. If it can fit into the 370 case, great.

//MOTOMULE JOB CLASS=A,MSGCLASS=A,REGION=4096K
//S1 EXEC GCCCLG,COS1='-S',PARM.ASM='DECK,LIST'
//SYSIN DD *
#include 
#define INPUT 0
#define OUTPUT 1
#define LOW 0
#define HIGH 1
int setup(){
	int retval='s';
	asm(" LA 0,X'42' FLAG FOR SVC 255 ");
	asm(" LA 1,6 SETUP ");
	asm(" SVC 255 ISSUE SVC\n" 
	    " LR %0,15"
	    : "=r" (retval));
	printf("setup return value is %d\n",retval);
	return retval;
}
int digitalWrite(int pin, int value){
	asm(" LR 2,%0 \n"
	    " LR 1,%1\n"
	    " LA 0,X'42' FLAG FOR SVC 255 \n"
	    " SVC 255 ISSUE SVC" 
	    : /*no outputs*/
	    : "r" (pin), "r" ((value!=0)+2));
}
int pinMode(int pin, int value){
	asm(" LR 2,%0 \n" //pin
	    " LR 1,%1\n"  //value
	    " LA 0,X'42' FLAG FOR SVC 255 \n"
	    " SVC 255 ISSUE SVC" 
	    : /*no outputs*/
	    : "r" (pin), "r" ((value!=0)));
}
void delay(int value){
	asm(" LR 2,%0 \n" //ddelay time in MS
	    " LA 1,5\n"   //code for delay
	    " LA 0,X'42' FLAG FOR SVC 255 \n"
	    " SVC 255 ISSUE SVC" 
	    : /*no outputs*/
	    : "r" (value));
}
int main(int argc, char *argv[])
{	
	int retval=43;
	//int pwmb=0,bin1=2,bin2=3; //speed and direction control for left side motor
	//int pwma=1,ain1=4,ain2=3; //speed and direction control for right side motor
	asm(" WTO 'Hello Moto!' ");
	retval=setup();
	pinMode(0,OUTPUT);pinMode(2,OUTPUT);pinMode(3,OUTPUT); //Left side motor
	digitalWrite(0,LOW);
	pinMode(1,OUTPUT);pinMode(4,OUTPUT);pinMode(5,OUTPUT); //Right side motor
	digitalWrite(1,LOW);

	//drive left side forward for 1.5 sec
	digitalWrite(2,LOW);digitalWrite(3,HIGH);
	digitalWrite(1,HIGH);
	delay(1500);
	digitalWrite(1,LOW);

	asm(" WTO 'OK Bye' ");
   	return retval;
}

3D Printed Case For the Olduino/370

Like everything else in the world, 3D printing is a lot more trouble than you’d think. Anyway, here’s the rogue /158 complete with bogus IPL messages. The Pi is actually hidden behind the chassis although there’s certainly room inside it.

Just For the Record – the OLED

18-10-12 oled
This is based on code from: https://github.com/iliapenev/ssd1306_i2c compied with
gcc pipetext.c ssd1306_i2c.c -lwiringPi -o pipetext
and tested with
echo “Hello World!” | ./pipetext

to use it with Hercules I’ll attach it to a printer as I did with the led pipe.

I’ll use it to pipe output from a hercules printer to the display – i’m imagining something that will echo the stepname and completion code for the last job run.

/******
Pipe text to the ssd1306 i2c driver for  Raspberry Pi
******/
#include 
#include 

#include "ssd1306_i2c.h"

void main() {
        printf("printer pipe to ssd1306\n");

        ssd1306_begin(SSD1306_SWITCHCAPVCC, SSD1306_I2C_ADDRESS);

        ssd1306_display(); //Adafruit logo is visible

  while(!feof(stdin))
  {
        char line[200];
        char ss[200];
        fgets(line, 200, stdin);
        printf("%d: %2x %2x %2x\n",strlen(line),line[0],line[1],line[2]);
        ssd1306_clearDisplay();
        //char* text = "Hello World!";
        ssd1306_drawString(line);
        ssd1306_display();
  }
}

In Which WiringMVS Punches Through TK4- Hercules


Ok. I now have Hercules built to run the TK4- environment properly and I have ported my SVC 255 bodge to that version. This is a change to the emulation of the supervisor call instruction that causes it to execute my C++ code instead of running an MVS operating system routine if a program calls SVC 255 with 0x42 in register 0. My C++ code interprets registers 1 and 2 to call the appropriate wiringPi routine e.g. R1=0 R2=1 is equivalent to pinMode(1,INPUT); R1=5 R2=12345 is a delay of 12345 MS.

Below is the jobstream submitted in the demo video and the MVS console after the job ends. There’s nothing very exciting in these baby steps but I am closing in on submitting a batch job and having the rogue 370 driving a robot car.

Olduino/370 Goes Rogue

I’m planning to use the olduino/370 in a couple of projects where it will have to be mobile. It needs a case, and I think it would be funny to have a robot car running around with a miniature mainframe on top.

Accordingly, I am retcon-ing the IBM 370/158 mobile edition. This would’ve been a special purpose mainframe optimized for battery power and use in the field. It has the full 370 instructions set but only limited I/O and it’s meant to be set up in something like a FEMA field camp for keeping track of people and possessions.

The real /158 had a dedicated 3270-type terminal on a separate table and didn’t have much of a control panel. My reimagined version has the screen built into the front of the CPU and an IPL control panel in the bottom right. Only the power button is set up high to keep kids from fiddling with it. Once it’s IPL’d and running the control panel is disabled with a key switch. There’s a running status display on the integrated 3270 but all user interaction is through separate terminals connected through packet radio and the arpanet.

The original is 61 inches high 32 inches wide and quite deep. I am aiming for something like three inches high, 1.5 inches wide, and 1.5 deep mostly so it will be sort of proportional with a little oled display set into the front. The control panel is shown larger than life because I want at least some of the buttons to actually be functional and maybe show some lights behind it.

There would be bags of room inside for a Pi zero and batteries and probably a solderless breadboard for mounting things like the motor controller for the robot car. Not sure about mounting sensors but may be a blue coloured breadboard on top wouldn’t be crazy.

I did this with paper because I have access to a colour printer and it was easy, I am hoping I can find a 3-D printable raspberry pi case that I can adapt to my dimensions.

I found some good pictures and size information for the /148 on the Computer History Museum web site. Below there’s an image of a full /148 computer room and of a museum artifact that shows the outlines of it. I wouldn’t mind profiling the front the way the 148 panel is fitted.

MVS On the Pi 3B

img_2584
The Pi 3 B is faster than the Zero by a fair margin. 1.2MHz vs 700MHz. It’s also four cores vs the single core Zero although i wouldn’t expect it to make a difference running my single instance of Hercules/MVS. I suspect that there are details that make the single core performance better though. The Pi 3 B cranked out 100,000 Dhrystone passes in about 10 seconds. 10K/sec is 5-6 MIPS vs the Zero’s 2 MIPS.
It’s noticeably faster running the work as well and more responsive on TSO.

Starting Fresh

18-09-11 frontpanel
18-09-11 baseline version
I’m starting a fresh build with a new SD card in a Raspberry Pi 3 model B
This involves:

  • the latest raspbian zip file(2018-06-27-raspbian-stretch-lite.zip)
    from https://www.raspberrypi.org/downloads/raspbian/
  • burned to a 16GB SD card with etcher
  • an empty file named ssh and a complete wpa_supplicant.conf in the boot partition
  • find the ip address with arp-scan from another pi (192.168.1.246)
  • logon and change the pi password with “sudo passwd”
  • download the current TK4- with
    “wget http://wotho.ethz.ch/tk4-/tk4-_v1.00_current.zip&#8221;
  • unzip with “unzip tk4-_v1.00_current.zip”
  • start mvs with ./mvs

I got the version info above by accessing 192.168.1.246:8038 from chrome on windows

My Own Private Hercules – Rebuilding Hercules for TK4-

18-09-11 frontpanel
18-09-13 my version
I got instructions from the developer responsible for the TK4- package for rebuilding it as follows:
18-09-07 buildinst
I got a good description of the recommended process from this web site: https://robots.thoughtbot.com/the-magic-behind-configure-make-make-install

There were only 3 files in the Architecture_dependencies folder that had to be copied into the source folder as below:

pi@raspberrypi:~/mvs4mod/hercules/source4mods$ ls
Architecture_dependencies  Hercules_for_TK4-_Update_08
pi@raspberrypi:~/mvs4mod/hercules/source4mods$ cd Hercules_for_TK4-_Update_08/
pi@raspberrypi:~/mvs...$ cp /Architecture_dependencies/32-bit_dyn75.c dyn75.c
pi@raspberrypi:~/mvs...$ cp /Architecture_dependencies/32-bit_tcpip.c tcpip.c
pi@raspberrypi:~/mvs...$ cp /Architecture_dependencies/ARM_cmpsctst.h cmpsctst.h

I then had to run automake –add-missing before ./autogen.sh would work


pi@raspberrypi:~/mvs...$ automake --add-missing
pi@raspberrypi:~/mvs...$ ./autogen.sh

aclocal...    OK.  (25% done)
autoheader... OK.  (50% done)
automake...   OK.  (75% done)
autoconf...   OK.  (100% done)
All processing sucessfully completed.
You may now run ./configure in order to create a custom Makefile
that is suitable for your platform and environment.

I was then able to run ./configure which generated makefiles.

The generated Makefiles caused a compile error with GCC to do with the CPU type. I reran make as “make -j 4 CFLAGS=”-W -w -O3 -frename-registers”

Because the make file expected to delete some files I had to create nl.gmo and de.gmo (“touch nl.gmo de.gmo”) to get it to run.

After that, “make install” moved the generated binaries into /usr/local/bin and /usr/local/lib.

To get TK4- started with my binaries i edited the MVS batch script (saving it as MVS2) and forced those directories into PATH and LD_LIBRARY_PATH as follows(around line 20)
case $system in
linux)
force_arch=
export PATH=/usr/local/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/lib/hercules:$LD_LIBRARY_PATH

I spent some time dinking around “touch”ing various things co convince myself my build was running and finally changed version.c to include a line of my own around line 225
“BUILT BY BILL!”,

Now it works and, although it identifies itself as the base Hercules rather than the version for TK4- I can see my BUILT text. It ipls MVS, shows its console on :8038, and accepts and runs batch jobs which is all i really wanted.

Faster But Flakier – Punching Through Hercules

I hacked the SVC processing code of the Hercules Emulator to let me get at the hardware of the Raspberry Pi. I’m using the WiringPi library so it’s not super fast but with minimal processing on the 370 side I can flip the GPIO in around 1.4 uS which is probably fine. Some function of Hercules is generating an interrupt every 10mS which takes control for 300 uS or so. You wouldn’t want to be driving a servo or reading an ultrasonic sensor but it should be fine for general use. I assume I can use native Pi or WiringPi functions in those cases.
The images blow show the interruption every 10mS and the fine-grained square wave in between.

I’m pretty sure that the interruption is in hercules code rather than linux because it always happens right after a 370 branch instruction is processed.

My 370 test code is very hard core – just a dozen instructions banged into low memory. I’m doing this because it’s kind of fun and also because the version of hercules that i’ve modified doesn’t run MVS TK4- properly yet. Once I get that figured out i’ll develop some C wrappers for the SVC calls.
18-08-19 dwon
I have to say i’m a bit amazed that i can program 370 machine language 40 years after my stint as a system programmer. Not just to remember that the opcode for LOAD Address is 41 but that 41 10 0002 would load a 2 into register 1 or that an unconditional branch is 47F0xxxx.

The code below is my first crack at letting a 370 program access the underlying Raspberry Pi. This is inserted in module general2.c around line 1300 where the Supervisor Call is emulated. Normally an SVC would cause the emulated CPU to pick up the address of some 370 code from fixed 370 memory locations and branch to that code but with this mod the emulator directly performs some function based on the 370 register contents and returns to the emulated program. It’s nasty but it gets the job done. As long i don’t expect my modfied hercules to work anywhere other than on the Pi it should be fine.

int wiringMVS(int function, int parameter){//implements wiringPi functions for MVS
	static int wiringPiIsSetup=0;
	static FILE* wiringMVSLog;
	int result=0;
	if (!wiringPiIsSetup){
 		printf("Setup() sez %d\n",wiringPiSetup ());
		wiringPiIsSetup=1;
	}
	switch (function){
		case 0:	//pinmode INPUT
			pinMode(parameter,INPUT);
			break;
		case 1: //pinmode output
			pinMode(parameter,OUTPUT);
			break;
		case 2: //digitalWrite(LOW)
			digitalWrite(parameter,LOW);
			break;
		case 3: //digitalWrite(HIGH)
			digitalWrite(parameter,HIGH);
			break;
		case 4: //digitalRead
			result=digitalRead(parameter);
		case 5: //
			delay(parameter);
			break;
		case 6: //
			if (wiringPiIsSetup!=1){
 				printf("Setup() sez %d\n",wiringPiIsSetup,wiringPiSetup ());
				wiringPiIsSetup=1;
			}
break;
	}
	return result;
}

 

---Near the top of the file
#include
#if !defined(_GENERAL2_C_)
#define _GENERAL2_C_
#include "wiringMVS.c" //WJR
#endif
---Near line 1335
/*-------------------------------------------------------------------*/
/* 0A   SVC   - Supervisor Call                                 [RR] */
/*-------------------------------------------------------------------*/
DEF_INST(supervisor_call)
{
BYTE    i;                              /* Instruction byte 1        */
PSA    *psa;                            /* -> prefixed storage area  */
RADR    px;                             /* prefix                    */
int     rc;                             /* Return code               */

    RR_SVC(inst, regs, i);
//following 12 lines per wjr 18-07-28
#if 1
//printf("bingo! i=%x, regs->GR_L(0)=%x\n",i,regs->GR_L(0));
/* if we have an SVC 255 and R0 is set to a magic number, then
we invoke the wiringpi code for MVS */
if ((i == 255) && (regs->GR_L(0) == 0x00000042))
	{
	regs->GR_L(15)=wiringMVS(regs->GR_L(1),regs->GR_L(2));
    	PERFORM_SERIALIZATION (regs);
    	PERFORM_CHKPT_SYNC (regs);
    	RETURN_INTCHECK(regs);
	}
#endif

In addition to those source code changes I had to install wiringPi itself from http://wiringpi.com/download-and-install/ and change the LIBS line in the makefile (around line 823) to
LIBS = -lrt -lz -lresolv -lnsl -lm -ldl -pthread -lwiringPi
Obviously I’d rather no diddle the generated makefile but I’m not clear how to pass that from the command line.

Whoah That’s Slow! – Just How Bad Was My Blinker

A while ago I perpetrated a LED blinker under MVS. The MVS program was writing to an emulated printer on unit 30F. Hercules was piping the output to a program called leder.c that would write the passed data to a raspberry pi LED via the file system. I figured it would be too slow for practical use but i didn’t really know. I tried it today and found that it took 10+ seconds to cycle the LED 1000 times. To avoid a startup effect i tried it 2000 times and it took 20 seconds. I got the same sort of timing when i directed the output to a real file without the pipe and even when i sent it to /dev/null. If I change the MVS JCL to DD DUMMY(the MVS equivalent of /dev/null) the time goes to near zero which is more what i would expect. It’s actually pretty stunningly slow. I’m not sure whether the time is being spent emulating 370 instructions or just running hercules I/O on the Pi.

It’s not really straightforward to access the GPIO’s from C code on the Pi. One option is wiringPi which implements an arduino type environment.

I’m able to build hercules from source although it’s not the version used by the TK4- turnkey MVS setup. I can run simple code in it from the console but i haven’t tried to bring up MVS.

As an experiment i’ve goobered the SVC instruction to blink my LED using wiringPi. I’m hacking at hyperion/general2.c changing code around line 1335 as below:

/*-------------------------------------------------------------------*/
/* 0A   SVC   - Supervisor Call                                 [RR] */
/*-------------------------------------------------------------------*/
DEF_INST(supervisor_call)
{
BYTE    i;                              /* Instruction byte 1        */
PSA    *psa;                            /* -> prefixed storage area  */
RADR    px;                             /* prefix                    */
int     rc;                             /* Return code               */

    RR_SVC(inst, regs, i);
//following 18 lines per wjr 18-07-28
#if 1
//printf("bingo! i=%x, regs->GR_L(0)=%x\n",i,regs->GR_L(0));
/* if we have an SVC 255 and R0 is set to a magic number, then
we have ignition */
if ((i == 255) && (regs->GR_L(0) == 0x00000042))
	{
	//printf("bango! i=%x, regs->GR_L(0)=%x\n",i,regs->GR_L(0));
	if (~wiringPiIsSetup){
 		printf("Setup() sez %d\n",wiringPiSetup ());
                wiringPiIsSetup=1;
	}
  	pinMode (1, OUTPUT) ;         // aka BCM_GPIO pin 18
	digitalWrite (1, 1) ;       // On
	delay(5000);
    	digitalWrite (1, 0) ;       // Off
    	PERFORM_SERIALIZATION (regs);
    	PERFORM_CHKPT_SYNC (regs);

    	RETURN_INTCHECK(regs);
	}
#endif

To get that to compile and link i had to manually edit build/cmakecache.txt changing line 83 to CMAKE_EXE_LINKER_FLAGS:STRING=”-lwiringPi” Then in the build directory the usual cmake –build . did the rest. Changing the cache caused a full rebuild but i’ve made a couple of simple changes since and they just rebuilt the affected modules.

This is pretty workable although it means goobering code that would be used by mainline MVS and it takes it out of the I/O realm altogether. It’s not completely clear to me whether my one-time initialization test is working or not but it doesn’t blow up, so that’s something.

I guess to continue this path I would need a little protocol and a “safe” svc – also a way of only compiling this for the raspberry Pi architecture.

I guess pinMode, digitalWrite, digitalRead would be a start.