Skip to content

Snake on an OLED

I’m quite pleased with this. I came across a snake game played with an attiny85 on Hackaday. The code is written in c++ so i grabbed it and whittled it down to basic C for LCC1802. I had the oled from previous efforts and i found a sort-of mini-joystick in a bargain bin of Radio Shack stuff.

The combination is actually pretty playable. painting the whole screen over spi is a bit slow but individual moves are fast enough that the gameplay is fine.

The code is quite long but the basic game loop is readable:

    while(1){
		reset();
		placedot();
		while(!gameover()){
			changeNextMove();
			moveSnake();
		   // check if you hit the dot
			if ((snake[0] == dot[0]) && (snake[1] == dot[1]))
			{
			  snakeLen++;
			  placeDot();
			}
			changeNextMove();
			delay(100);
		}
		// blink the screen to indicate game over
		delay(300);
		oled18blinkScreen(3);
		delay(100);
		// display the user's score
		oled18drawImage(SCORE,0);
		oled18displayScore(snakeLen - 3);
		delay(3000);
	}

Just as an example, the placeDot() routine calls a routine to place a pixel on the oled which in turn calls an old LCD routine to do the spi write. All of them are shown below. The rand routine is one Marcel Tongren wrote for the crosslib games.

// put the dot on a random place on the board
void placeDot()
{
  int i;
  // select a new random place for the dot
  //srand(millis());
  uint8_t x = (rand() % 14) + 1;
  uint8_t y = (rand() % 6) + 1;
  for (i = 0; i < snakeLen; ++i)
  {
    // check if the randomly selected dot is not a snake block
    if ((snake[2 * i] == x) && (snake[(2 * i) + 1] == y))
    {
      placeDot();
      return;
    }

  }
  dot[0] = x; dot[1] = y;

  // place the dot on the screen
  oled18drawPixel(dot[0], dot[1]);
}

/// x in range <0, 16), y in range <0, 8) - fills a 8x8 pixel block on the specified index
void oled18drawPixel(uint8_t x, uint8_t y)
{
  uint8_t j;
  x *= NUM_PAGES;
  oled18setPageAddr(y, y);
  oled18setColAddr(x + 1, x + 7);
  for (j = 0; j < 6; j++)
  {
    LcdWrite(LCD_D,0x7E); // do not fill it completely, make a 1 pixel wide border around it
  }
}

void LcdWrite(unsigned char dc, unsigned char payload) //flag for data vs command, byte to send out
{
	register unsigned char dummy;
	digitalWrite(lcdcmd,dc); //set data or command
	spisend(payload);
}

The new WordPress editor won’t let me collapse the code blocks so i won’t paste the whole code in here. if i can figure out how to do it i’ll come back and edit the post.

Does this look like a timing problem to you? Because it looks like a timing problem to me.

I got excited about a post on hackaday with a snake game on an oled and i dragged out my spi oled to try but the darned thing gets garbled graphics. Although i would swear that this used to work i feel like something is overrunning something else. In fact, if i change the code to not send consecutive bytes it’s fine(well, it’s upside down but it’s fine). I’m suspicious that maybe it’s the version of olduino board or code that i grabbed won’t support the 4mhz 1802’s spi speed.

Update: Yup I went back to V6 of the olduinoisp code that runs in the AVR and the display is fine. V7 was meant to do a virtual load mode of the 1806. There shouldn’t be any impact on the spi setup but If I ever go back to that i’ll have to figure out what’s up.

More Long Branch Elimination

I’ve flogged this horse out onto some pretty thin ice. The goal is to eliminate long branches, not for efficiency but because they upset the 1861 video chip timing. I came up with a scheme that eliminated the branches using a subroutine mechanism but with fairly horrendous overhead. To reduce the subroutine overhead I figured I would try dedicating a register to the branch routines which would take it out f the variable pool. So instead of a long branch being replaced by a 5 byte sequence like:
SEP 4 BRANCHER,TARGET it gets replaced by a 3 byte sequence:
SEP 7 TARGET
That’s fine for unconditional branches but for conditional branches you still need 5 bytes – say for BNZ TARGET:
PAGEFIT 6
BZ +
SEP 7 TARGET
+:
This has the added advantage that if the branch is not taken there’s no overhead at all. The BZ above would not be safe on its own so it’s protected by a new invention, the PAGEFIT nn macro which makes sure there’s nn bytes left on the page(it’s 6 rather than 5 to make sure that the instruction sequence leaves room for another byte to be the branch target).

This worked much better than the subroutine mechanism but it left the compiler with only one register for variables(R6). It occurred to me that i could maybe steal one of the temporary registers if i never used long arithmetic. This is very true. I know I need at least 3 registers so the compiler can, say, add two registers with the result in a third. If I don’t define at least three integer temps the compiler just folds and quits with a code 1. If I do define three temps but use ANY long arithmetic, the compiler actually loops! There are dragons on the thin ice!

So, at the moment, I have several levels of increasing trickiness to choose from. I don’t mind giving up long arithmetic but the setup feels fragile. The basic dedicated register thing is solid though and the pagefit macro is a useful addition. Thanks to Ted Rossin for the innovation.

The pagefit macro and long branch routine look like the following:

pagefit: macro bytesneeded	
	if ($#256)>(255-bytesneeded)
	    align 256,0xe2
	endif
	endm

;************************long branch assist routine follows*********************
  IF	LCCPX=2		;long branch assist routine
  	pagefit 10
$$RET:
	SEP	R3
$$BRSUB:
	lda	R3
	phi	memaddr
	lda	R3
	plo	R3
	ghi	memaddr
	phi	R3
	br	$$RET
  ENDIF

Long Branch Elimination In LCC1802

The compiler output is littered with long branches. These are a problem only when using the 1861 pixie video. I had an approach that would pull out most long branches but because we’re trying to adapt some of the crosslib games to pixie targets, I needed a complete solution.

My solution was to run a python script on the compiler output running my earlier long-branch reduction process but, where the branch can’t be shortened, I replace it with a perversion based on the subroutine call mechanism. In the simplest case LBR <target> gets replaced by XBR <target> which generates “CALL XBRSUB” followed by “DW <target>”. The XBRSUB routine picks up the target address and puts it into the main program counter before returning. The more complicated case like XBZSUB either does the same if D is 0 or just increments the return address to bypass the target. These branches are bulkier – 5 bytes vs 3, but i replace most of the 3 byte branches with short ones so that’s probably not a big loss. Worse though, we’re executing probably 20-30 instructions extra for each long branch. Possibly I could shorten that by not using the standard return routine but we’ll see how bad it is.

One extra fillip, i had some long skips in the arithmetic routines, I’ve provisionally replaced them with guarded short branches as in:
LSNF
XRI 01
becomes
ALIGN 4,0xE2
BNF +
XRI 01
+:
I’m hopeful that will work but there is some odd thing that happens when you’re right near the end of a page. if it’s a problem the assembler will catch it.

xbr:	macro	target
	sep	RCALL
	dw	xbrsub
	dw	target
	endm
xbz:	macro	target
	sep	RCALL
	dw	xbzsub
	dw	target
	endm
xbnz:	macro	target
	sep	RCALL
	dw	xbnzsub
	dw	target
	endm
xbdf:	macro	target
	sep	RCALL
	dw	xbdfsub
	dw	target
	endm
xbnf:	macro	target
	sep	RCALL
	dw	xbnfsub
	dw	target
	endm

	align 32
xbrsub:
	lda	r6
	phi	memaddr
	lda	r6
	plo	r6
	ghi	memaddr
	phi	r6
	sep	rret
xbzsub:
	bz	xbrsub
nbrsub:
	inc r6
	inc r6
	sep	rret
xbnzsub:
	bnz	xbrsub
	br	nbrsub
xbdfsub:
	bdf	xbrsub
	br	nbrsub
xbnfsub:
	bnf	xbrsub
	br	nbrsub

Slow Progress on the 8 Bit Version Is Still Progress

I spent a couple of days tracking down a problem in parameter passing which is the most convoluted part of the compiler. I finally came across a note to myself from the previous time i shot the same bug! I had done an ugly workaround by forcing alignment of 32 bit variables and when I “fixed” that it resurfaced the bug. Anyway, the first few selftest programs that it tried seem fine. I note that i get weird errors sometimes that i have to correct with casting for example in the above I had to put the (int16_t) ahead of __FILE__ or the compiler generated a reference to RL6. I’ll need to track that down.

My note from the bottom of the .md file

strncmp() is an 8 bit Champion!

It’s not typical but the strncmp() routine that i dug into first shows big advantages in setting the LCC integer size to 8 bits. It went down from 136 bytes to 113 bytes and that’s purely fewer instructions. That came at the expense of needing to convert all int declarations to int16_t including some implicit declarations of literals.

Huh, My Eight Bit Theory is Not Completely Wrong!

The first C procedure I’m looking at is strncmp() and the poster boy for the improvement is is the compare/return sequence above. On the left it’s done in 26 fewer instructions. Further down the advantage gets blunted by a sign extension in a return statement but i’ll look at that tomorrow.

Hello From the 8 bit Side

I started work on a version of LCC1802 that sets the basic int type as 8 bits(long is 16, long long is 32) to see if i can generate more efficient code by eliminating the automatic promotion of arguments to 16 bits. This means foing through the code and using int16_t for int uint16_t for unsigned int etc. Today I converted olduino.h and .c and nstdlib.h and .c and managed to compile both blink and hello world. They work at least in the emulators.

I am not really expecting any code improvement at this stage. In fact, i expect it to be a bit worse because a lot of my optimization rules that won’t fire for 8 bit ints. Hello World now reports as:
Deduced address range: 0x00000000-0x000018F2
helloworld.p==>>a.hex (5587 Bytes)

where with xr18CX it reports:
Deduced address range: 0x00000000-0x000018F2
helloworld7.p==>>a.hex (5580 Bytes)

which is what i’d expect at this stage – i’ll have a better look at the generated code tomorrow but it’s encouraging that it still works and is only a touch bigger.


LCC1802 With 8 Bit Ints

One of the abiding inconveniences of generating 1802-friendly code from C is the standard’s insistence on promoting chars to ints before doing anything with them – including assigning them to another char. So a c statement like c1=c2 could generate a sequence like:

ld1 R11,<address where c2 is stored>
zext R11 ;zero extend
st1 R11,<address where C1 is stored>

Similarly if i add two chars it will extend both to 16 bits and add them. A lot of the peephole optimizer rules are basically removing unnecessary promotions. It’s not necessarily easy either because in that example above, for all i know, the compiler could be doing something else with R11 which really would need the sign extension. One big benefit of doing live variable analysis is being able to confidently remove the promotions.

It occurred to me (belatedly) that i could maybe save the grief by telling the compiler that ints are 8 bits so a 16 bit int would be a long and 32 bits would be long long. That’s easy enough to do, as it happens. There’s a section of the machine description file that explicitly calls out the sizes of the various C types. I made a new description file xr188B.md, changed the integer type sizes, and rebuilt the compiler:

Interface xr188BIR = {	//each entry represents size, alignment, out-of-line literals needed
        1, 1, 0,  /* char */
        1, 1, 0,  /* short */
        1, 1, 0,  /* int */
        4, 4, 0,  /* long jan 20*/
        4, 4, 0,  /* long long  jan 20*/
        4, 4, 1,  /* float  jan 20*/
        4, 4, 1,  /* double  jan 20*/
        4, 4, 1,  /* long double  jan 20*/
        2, 1, 0,  /* pointer */
        ...

This worked fine as far as it went. If I compile c1=c2; I don’t get the extend

;	c1=c2;
	ldaD R11,_c1; reg:acon
	ldaD R10,_c2; reg:acon
	ldn R10
	str R11; ASGNU1(indaddr,INDIRU1(indaddr))J2020-1

Mind you, given the fuss required to load and store the variables the extends are the least of my worries!

Now I would have to create dozens of new code generation rules and probably a few macros for 8 bit arithmetic. Any program would have to strictly use synthetic types like uint16_t for standard integers and so on. It may be worth it for things like the crosslib games that can use the speed and space savings.

“-Wf-g,;” Considered Harmful

I routinely pass that operand to LCC to get the c source code interlisted with the assembly. In some peephole rules i count on seeing the ; in column one to signify a new statement so i can consider temporaries dead. It never occurred to me that there would be different code generated, but there seems to be.

The -g seems to change the way the temporaries are used and maybe stops them carrying values across statements.

The LLVM C Back End

I have been looking with envy at LLVM-based compilers for years. They generate fabulous code compared to LCC1802. LLVM is tragically difficult to write a back end for though – well beyond my skill level. There is a back end for the Z80 which does work and has the optimizations I envy but it’s back level now and would be tough to adapt.

There used to be a path through the LLVM system that produced C code (the C Back End) but it was deprecated a long time ago. It has been revived by the Julia project so i eagerly built it to try. The instructions on github were close enough that i got it to build. My first example is below:

/*input blink.c*/
int adder(int a, int b){
	return a+b;
}

int main(){
	return adder(35,7);
}

/*Generates LLVM intermediate code */
; ModuleID = 'blink.c'
source_filename = "blink.c"
target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv6k-unknown-linux-gnueabihf"

; Function Attrs: norecurse nounwind readnone
define dso_local i32 @adder(i32, i32) local_unnamed_addr #0 {
  %3 = add nsw i32 %1, %0
  ret i32 %3
}

; Function Attrs: norecurse nounwind readnone
define dso_local i32 @main() local_unnamed_addr #0 {
  ret i32 42
}

attributes #0 = { norecurse nounwind readnone "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="mpcore" "target-features"="+armv6k,+dsp,+strict-align,+vfp2,-thumb-mode" "unsafe-fp-math"="false" "use-soft-float"="false" }

!llvm.module.flags = !{!0, !1}
!llvm.ident = !{!2}

!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{i32 1, !"min_enum_size", i32 4}
!2 = !{!"clang version 7.0.1-8+rpi3+deb10u2 (tags/RELEASE_701/final)"}

/* which gets converted to blink.cbe.c by the back end*/
/* Provide Declarations */
#include <stdarg.h>
#include <setjmp.h>
#include <limits.h>
#include <stdint.h>
#include <math.h>
#ifndef __cplusplus
typedef unsigned char bool;
#endif

#ifndef _MSC_VER
#define __forceinline __attribute__((always_inline)) inline
#endif

#if defined(__GNUC__)
#define  __ATTRIBUTELIST__(x) __attribute__(x)
#else
#define  __ATTRIBUTELIST__(x)  
#endif

#ifdef _MSC_VER  /* Can only support "linkonce" vars with GCC */
#define __attribute__(X)
#endif



/* Global Declarations */

/* Types Declarations */

/* Function definitions */

/* Types Definitions */

/* Function Declarations */
uint32_t adder(uint32_t, uint32_t) __ATTRIBUTELIST__((nothrow, const));
uint32_t main(void) __ATTRIBUTELIST__((nothrow, const));

/* LLVM Intrinsic Builtin Function Bodies */
static __forceinline uint32_t llvm_add_u32(uint32_t a, uint32_t b) {
  uint32_t r = a + b;
  return r;
}

/* Function Bodies */
uint32_t adder(uint32_t llvm_cbe_tmp__1, uint32_t llvm_cbe_tmp__2) {
  return (llvm_add_u32(llvm_cbe_tmp__2, llvm_cbe_tmp__1));
}


uint32_t main(void) {
  return 42;
}


This is not as bad as it looks. There is a lot of cruft but the only executable statement is the “return 42;” in main which has optimized away a function call. Remains to be seen whether with a larger program the cruft exceeds the benefit.

For the record the compile commands are as follows:

pi@astropi:~/olduino $ clang -S -emit-llvm -O3 blink.c
pi@astropi:~/olduino $ ~/llvm-project/llvm/projects/llvm-cbe/build/tools/llvm-cbe/llvm-cbe blink.ll

Programming the AVR for the 1802 Olduino

I had a tough time doing this the other day because I had switched laptops and forgotten the process. I went through it clean today to document it for myself.

I’m starting with a fresh ATMEGA328 chip. I don’t need a bootloader but i need the fuses set and the easiest way is to get the arduino IDE to burn a bootloader. I put the chip in an arduino and hook it up to my Pololu PGM03A(which identifies as an stk500v2 type programmer). Note the awkward cabling. The key thing is getting the red stripe on the cable lined up with the +V on the boards.

The programmer is showing up as COM8

In the ide tools menu set the chip type, speed to 20mhz, and burn the bootloader.

Load the sketch (OlduinoII_ISPV7 available here) and upload using the programmer.

Move the chip from the arduino to the olduino board and test it with any of the example – like “blink”

UPDATE: Going back through old posts I found this one where I had a minor epiphany about the cable connection. I sort of remembered this the other day but i was afraid to try it. I’ll dig the stuff out again and verify it.

16-03-07 better

Crosslib -CrossShoot/CrossChase

I’ve been working for a while with Fabrizio Caruso and Marcel van Tongren on 1802 support for Fabrizio’s cross platform game framework Crosslib. Fabrizio has the games working on literally hundreds of vintage computers. It’s written in C so for the 1802 it uses the LCC1802 compiler. Marcel is a whizbang 1802 programmer and he has been doing the low level coding while i wrestle the compiler. He also maintains the Emma 02 compiler which i’ve been using to emulate cosmac game systems like the Telmac who’s screen is shown above.

The games are enormous, generating 20-30K bytes of code so they’re a good optimization target for the compiler.

Pixie Dust

One of the defining elements of the original 1802 systems was “Pixie Graphics”. This used a CDP1861 chip to stream bits out of the 1802’s memory onto a TV screen. Resolution was a maximum of 64X128 monochrome pixels but more typically 64X64 or 64X32. Here I’m using a library developed by a fellow on the Cosmac Elf mailing list with the screen in 64X64.

In the Beginning Was the Command Line – So Where is the D*mned Thing Now?

I’m a longtime windows command line user – I was used to starting it from the file explorer by right clicking on a directory but with Windows 10 the option disappeared from the context menu. I knew you could drag a directory icon onto a command line window to change to that directory but I found a couple of better solutions

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

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));
}