PScript – Part 9 – Streams

Both Ethernet, UART, USART, USB, SPI, I2C, I2S and CAN might be used as a serial 2-way, buffered byte stream. This is an important abstraction as it allow for an easy, uniform usage of byte streams regardless of underlying technology. To support this I create the same interface on all technologies and an object “stream” that contain the buffers.

open Open a stream.
close Close a stream.
send Send bytes through the stream.
receive Receive Bytes from the stream.
Event onreceive Receive bytes through event.

The open function dictate if the stream is continuous bytes or packages. CAN (as an example) have max 8 byte packages on the messages, but it is easy enough to create virtual byte streams or larger packages if that is what you want.

CAN also have IDE, RTR, Standard and extended ID that is part of layer 1 header. I need to allow the user to control those and at the same time abstract away from CAN. This is what stream does. If you want to access raw CAN you access the CAN1 or CAN2 objects, but declaring a stream on CAN1 convert that to a buffered, 2-way byte stream where the details of the underlying CANbus is hidden from us.

Examples

Stream1.Open(CAN1, 115200)
Stream1.Send("Hello World")
Stream1.Close()

In this case I connect Stream1 to CAN1, but I could have connected it to any object capable of serial stream support. Receiving bytes can be done in two ways. You can call “receive” as follows:

uint8 buf[40]
Stream1.Open(CAN1, 115200)
int x = Stream1.Receive(buf)
Stream1.Close()

This will receive a maximum of 40 bytes from Stream1. But, a more elegant way of receiving is using the event as follows:

Event Stream1.OnReceive(uint8 bytes[])
   ...
End

I sneaked in empty brackets here to indicate that this is an array of uint8 passed as a parameter. In effect we will only pass a hidden pointer to the location in the buffer. This location will be destroyed as the event finish, so we need to copy the bytes to a local storage that survive the scope of the event.

uint8 myCopy[20]
Event Stream1.OnReceive(reference uint8 bytes[])
      myCopy = bytes
End

The empty brackets are used to indicate that we receive an array of bytes. The keyword reference indicate that this is a reference to an array, not the array itself. I do however still have one issue here – I need to tell stream1 how many bytes I accepted.

As I end the event, Stream1 will by default mark all bytes that was in the reference as received. This is ok if you receive packets or want to receive everything, but it is not ok if you need to examine the stream and receive only some of the bytes now, waiting for more to arrive later.

uint8 myCopy[20]
Event Stream1.OnReceive(reference uint8 bytes[]) : uint16 bytesReceived
     myCopy = bytes
     bytesReceived = myCopy.length;
End

The change above solves that. I add a return value to the event that is bytesReceived and set this default to the same number as inserted allowing the event to specify a different number if needed.

myCopy = bytes will in effect transfer max 20 bytes, but it might transfer less.

Again, I need to experiment a little with syntax, but I think this starts to look good.

PScript – Part 8 – Functions

Functions are a critical part of any modern language, also in PScript. Functions must be declared before they area used. A PScript will start executing the first global statement it find, but it will parse and tag functions on it’s way – so a general good rule is to put the global PScript code at bottom of a file.

Func MyFunc(uint32 v1, uint32 v2) : uint32
   return v1*v2
end

uint32 x = MyFunc(2,2)
print (x)

This example should print 4.

The return value is optional.

3-Phase Motor Driver w/Hall Sensors – 60V/50A

Many of you have seen this before – it’s my 60V/50A 3-Phase Motor Driver “Thunderstick”. It was a messy first assembly with greece coming through PCB holes, but I am all in all very happy with this design and will be using three of these controllers on the lawn mower. These are quite advanced drivers and similar to the Vedder (VESC) design so we can borrow that code – except that I will be using the Hall Sensors, so I need to verify if these works.

  1. RS485 Interface. I am seriously considering replacing that with a 2nd CAN interface.
  2. Terminator for RS485.
  3. CAN HS interface.
  4. Terminator jumper for CAN.
  5. STM32F405RG
  6. IO port
  7. IO port
  8. SWD. This is compatible with my other SWD ports, but it is a weird design that I will not use again.
  9. Power lane – designed so I can add a wire to take more current In.
  10. MOSFET’s.
  11. Ground Power In.
  12. Current Shunts. This only have 2 current sensors.
  13. Mounting holes.
  14. Ground power lane.
  15. Temperature sensors.
  16. +60V Power In.
  17. DRV8301 – 3-Phase driver.
  18. PSU + Buick Converter. DRV8301 contains a Buick Converter that gives 5V and we use SPX3819 to deliver 3.3V.
  19. Crystal.
  20. Hall Sensors w/5V Output.

This show my drone motor that is perfect for the grass cutter.

This shows the larger 3KW Scooter motor with hall sensors. The picture says 190KV, but I have 2 x 280KV. Will be running them at either 18V or 36V so I can use standard – off the shelf battery packages for DIY tools. These should fit perfectly with the wheel frames I have ordered.

I will need to make a revision of this driver and port it to KiKad in the process. At this point I also need to consider 4 or even 6 layers + I need to consider galvanic isolation as I add 3 motor controllers, several sensors and a main controller into a network.

3D Position System

A module like ZED-F9P cost 148.- EUR and cover all these with an accuracy of 10 cm.

The cost of thus module is currently a limitations, but cost will come down. I am more interested in the fact that it announce 10cm accuracy which open up a lot of usability options. The classic 2.5 meters are ok for many applications, but not for a lawn mover.

With two of these units you can also detect the direction of the unit. But, I am planning to add two or three “3D sensors” on my lawn mower. Satellite position is only one option here as I can add Ultra sound /LIDAR to detect surroundings, 9 DOF to detect acceleration, gyroscope and compass signals as well as temperature, humidity, pressure etc.

The last trick is to fix reference signals on house corners using ultrasound, light or rf signals. The idea is that the robot will detect these and be able to detect difference between the signals. I need to dig a bit into this, but it should be doable.

I have so far focused on using Raspberry PI Hat format on many of my modules and I believe this still is optional for a 3D module since it might be advantageous to actually add a RPI with camera and more advanced position algorithms.

3D sensors like Acceleration, gyro and compass will help tracking relative movement once they have a reference position. This is why a 2m accuracy on GPS still can be workable. To compensate for errors I can add multiple units + I plan to test multiple cameras to see if I can reference IR light positions. Cameras also have the option that we can teach the robot to actually see and recognize it’s surroundings – that said the later is complicated and require a bit of work.

I think position accuracy looks doable, but it will be some work. I also think multiple systems is a must together with the capability to detect/reject errors.

Building my own Robotic Lawn Mover

I recently studied a robotic lawn mover and realized that I can easily build one of those myself.

To get started I need two wheels with separate motors and the picture above is the driving mechanism for an electric scateboard. The wheels are 200mm in diameter and entire construction is ca 400mm wide. It is perfect as base for a lawn mover. It will cost ca 300 USD. The motors on this picture is 350W 3-phase and I would prefer stepper motors or motors with hall sensors in this case, so I will try to buy components separate + I am not fuzzed about that scateboard mounting in the middle.

To make this work I can either add a 3rd supporting wheel or simply attempt a balancing robot design. The later would be cool, but I am not sure how stable it would be with the cutter below, so I will probably use a 3 wheel design. A 4 wheel design would also be cool and add some value as my garden is large and have several levels.

The cutter itself is dead easy – most robots use a rotating plate with 4 loose blades that cut the grass. You can buy those blades in most DIY shops now, and making a round plate fixed to a motor that can spin with some speed is not difficult. I can also make the bottom of the robot flat to avoid grass coming in everywhere – with only the cutter sticking out below.

To box this I can easily find a plastic storage unit of correct size and mount it over the design.

Batteries are no challenge as you can buy tool batteries in most shops. I accidentally bought 36V/4Ah today for 70.- USD – this is for standard DIY tools and I can make a fitting for these. I can just add as many batteries I want or even build my own using 18650 or similar cells. I like the idea of having these replaceable.

The next is the sensors. A standard GPS have ca 2.5meter accuracy so that alone is not sufficient. I can buy separate kits to put down cables, but as I have a large and complicated garden I would like an easier system where I teach the robot where to cut and in what pattern. To do so I need a position system accurate down to ca 100mm. I need to return to this part.

This leaves the charger – I am thinking of making an inductive charger – where one coil is in my house wall and the other on the unit – not exactly rocket science.

As for control system I can use 3 x Thunder sticks and a rack of Hat’s as needed – this is actually a very cool project and parts have been ordered. It would have been cheaper to just buy a ready to go lawn mover, but I would have needed 5 of them to cover my entire garden. This one I hope to be able to program to cover it all.  It will take me a while to get parts, assemnble and program this unit, but it will be fun.

The position system on the lawn mover is critical. I would like an accurate position system so I can teach the robot the limits of my garden.

The first and obvious is to use 2 x GPS modules located in each end of the robot. They will only be 40 cm apart, but that together with the standard set of sensors (3 axis gyro, 3 axis accelerometer, 3 axis magnetometer, temperature, humidity, pressure) will all give me info on position.

The next is to add “eyes” in form of ultrasonic sensors as well as lasers and obviously cameras. I want one of these at each corner mounted of a pan & tilt camera unit so it can do a 180 degree coverage on each corner. Lasers and ultrasonic can detect surrounding terrain, but camera can detect IR leds I put on the house and based on them triangulate it’s own position. This means I need to set up IR senders that send a blink sequence. That blink sequence is reasonable to detect in a picture and by measuring the distance to other signals you should be able to triangulate it’s position. The blink sequence identify the sender and thus it’s reference position.

I can also put up IR senders on places in the garden if I need to + I can use BLE sensors. Needless to say, my robot will use Wifi to connect to a control unit inside the house.

I am just spinning ideas here, so I need to work a bit on the position system.

A normal lawn mover work more or less as an old fasioned “bump & go” car – it runs in one direction until the sensor detect a cable at which point it turn in a rando direction and continue. But, they are able to find their “home”. More expensive units can do more, but to really do this properly you need an accurat position system. If you got a position system then the rest is easy.

PScript – Part 7 – Practical limitations

With PScript being an interpreter running on the embedded device we need to be realistic about how much we can get in. Plain is a complex language in full, so we need to limit and adjust content with Flasgh size of the package in mind. One example is CAN on STM32. CAN is ca 50 registers with up to 32 bit fields each. To enable all this in detail to PScript would require a table with up to 1600 entries. This is a typical area where I have to accept that while this would be nice to have, it will use to much space.

So to access CAN I end up with only an abstracted interface of a few functions. I believe this is a good compromize and a needed one.

 

PScript – Part 6 – Datatypes

I have decided on a set of datatypes that I want to support.

bit Bit is a 1 bit data variable. Used as a separate variable it will minimum be 8 bits, but used inside a record it get bit packed.
uint8 8 bit unsigned integer
int8 8 bit signed integer
uint16 16 bit unsigned integer
int16 16 bit signed integer
uint32 32 but unsigned integer
int32 32 bit signed integer
real32 32 bit float
real64 32 bit double
string Text string. Text strings have a dynamic size.
numeric Number with exact size/precision. Unlike reals this uses BCD to encode exact decimal numbers.
blob Binary Large Object. A binary object of dynamic size and with a few static header variables like size etc. The objective is that this is often needed and as a consequence implemented special purpose in code. I believe declaring a blob datatype will save some space.
class reserved for a possible object oriented version of PScript later.
record Record is a data structure that hold a selected address in memory. It is a collection of variables that is bit packed on that address. In this context it is designed to overlay registers creating a way for PScript to access registers directly.

 

PScript – Part 5 – Arrays

uint8 myArray[50]

The statement above is an array of 50 bytes. The first element is 0 while the last is 49.

uint8 myArray[1..50]

This is the same array of 50 elements, but now with the first element as 1 and the last as 50.

foreach uint8 x through myArray
   ...
end

The foreach statement above will iterate through the array with x increasing. I will look into using “in” rather than “through”, meaning that x is the variable rather than the index, but I believe the through concept is easier to implement and more powerfully as it sometimes are needed to know if your the first or last entry.

I also need functions to return start-index, end-index and length of an array. In addition we need more dimensions:

uint8 myArray[50][50]...

It is a few things I want to add, but I also need to weight that against the size of the PScript package.

PScript – Part 4 – Built-In Functions

To adapt PScript to the current platform I add an interface to add “built-in” content and a C++ library with some content prepared. This is to make PScript as scalable as possible as built-in content needs to be platform specific and depending on how much Flash/SRAM that is available.

Records

Records are bit-packed and can have fixed addresses making them excellent to overlay IO registers. This open for two types of interface to hardware – one there PScript go low level through pre-defined records and a more abstract interface through functions.

PScript is not object oriented, but to access HW IO we use record names as prefix separated with a “.” (dot) as we would have done if record was object variables.

Example:

GPIOA.pinMode(1, OUTPUT, PULLDOWN)

while true
    GPIOA.digitalWrite(0, HIGH)
    Sleep(500)
    GPIOA.digitalWrite(0, LOW)
    Sleep(500)
end

This example will toggle GPIOA, pin 0 high/low at ca 1Hz frequency.

To support this I will need (1) a method to add constants like OUTPUT, PULLDOWN etc, (2) a method to add records and a method to add functions with and without object reference. I will return to this as I discuss PScript API. Focusing on records and functions I plan the following list. Arduino have a well designed interface, so using that as a starting guideline will be good. This proposal is based on STM32F405RG, so low level content like registers will differ from MCU to MCU.

Records

GPIOA, GPIOB, GPIOC, GPIOD, GPIOE, GPIOF etc GPIO ports with member variables to match registers on MCU.
ADC1, ADC2, ADC3 Analogue to digital conversion.
Serial1, Serial2, Serial3, Serial4, Serial5, Serial6 Serial ports.
SPI1, SPI2, SPI3 SPI ports
I2C1, I2C2, I2C3 I2C ports
DAC1, DAC2 Digital to Analogue conversion.
RTC Real Time Clock
TIM1 to TIM14 HW Timers
USB USB
CAN1, CAN2 CAN ports
I2S1, I2S3 I2S ports
RNG Random Number Generator
CRC CRC
ETC Elapsed Timer

The only records I am holding back on are some of the core content like clock, dma and wathdogs etc as I will cover those differently. I need to evaluate a bit as I go.

The records are as mentioned MCU specific due to their content, but the interface functions are designed to be portable with one exception – pinMode will be MCU specific. The list below is a brief intro – I will describe syntax and modules in more details later.

pinMode Define what we use a specific pin for.
digitalWrite Set GPIO pins
digitalRead Read GPIO pins
analogRead ADC
analogWrite DAC
serial1.Open Open a serial port. Both USB, Serial, SPI, I2C, CAN and I2S can be treated as serial ports with a bytes in/out concept.
serial1.Close| Close a serial port
serial1.Send Send bytes on a serial port
serial1.Receive Receive bytes on a serial port
tim1.start Start a timer
tim1.stop Stop a timer
rtc.SetTime Set time
rtc.GetTime Get current time
rtc.SetAlarm Set alarm clock
etc.millis Get elapsed time in ms.
etc.micros Get elapsed time in micro seconds.
rtos.StartThread Start a function as a thread.
rtos.StopThread Stop a function as a thread
rtos.Lock Lock processing to this thread
rtos.Unlock Unlock so threads/events can execute
print, println Print and print line
math Math functions like cos, tan, sin etc.

The list will be evaluated and subject for changes. The objective is to provide some functionality to PScript, but at the same time avoid making the package to large.

PScript – Part 3 – Events

Using GPIO as an example I would like to be notified then an input pin change. To do this in C I use an interrupt function so I need a similar event reception in PScript. The most logical is that the framework check for events in between statements, and if one found execute an event function.

event <event-name>

end

Example 1:

event GPIOA[1].PIN_CHANGE

….

end

This should be called if GPIOA, pin 1 change.

Example 2:

event ADC1[4].Threshold_High(1014)

end

This should be called if In4 of ADC1 goes higher than value 1014.

The second feature here is that I would like a capability simulate events from SW as well.

raise GPIOA[1].PIN_CHANGE

I need to return with a list of supported events. We can debate wherever to use On, On Event or Event, but I think “Event” will do just fine. But, the syntax and details here will be subject for changes.