Archive for the 'Software' Category

TWANG32 – An ESP32 Port of TWANG

 

I ported the TWANG game over to ESP32. I wanted to do this for several reasons.

  • Cost: The ESP32 is generally lower cost than the Arduino Mega
  • Speed: The ESP32 has a dual core 80MHz processor vs. the 16MHz Arduino Mega
  • Memory: The ESP32 has much more RAM and program space to allow more features, levels and audio files.
  • Physical Size: A Mega is very big. This creates a large enclosure that takes a while to print. The smaller enclosure is also more portable.
  • DAC Pins: The audio capabilities on the Mega are very crude and basically limited to square wave tones. A DAC can output digital audio. Currently I am just using a similar square wave tone to the Mega, but it works much better for adjusting the volume.
  • Wireless: The ESP32 has Wifi and Bluetooth. This will allow easier (smartphone) interfacing for options (brightness, volume) and level pack uploads. I also want to consider dual player battle type games with linked controllers.

The Audio

The original TWANG bit banged audio directly from I/O to a speaker. This was super simple, but the volume at max was not loud enough for noisy environments. The ESP32 I/O is a lower voltage (3.3v) and less current, so something needed to be done. I prototyped with a PAM8403 based amplifier (~$4 on Amazon). This worked great, so I added that I.C. to my shield. The volume is controlled by the amplitude of the DAC.

The Shield

I made a shield to simply the wiring and provide a stable way to mount the ESP32. I used a NodeMCU 32S development board for the ESP32. Under the ESP32 is a the audio circuitry. I should have some extra boards to sell on Tindie soon. I will publish the source files soon.

The Firmware

The code is on Github. The port was relatively easy, but I had to rewrite a few libraries. They were designed so the main “setup” and “loop” parts did not change much. Currently the serial port based setup from the Mega version was not ported. This probably won’t be used. A wireless version is in the works.

Enclosure

The new enclosure is smaller. It prints quicker and is easier to fit in my backpack. The size is still big enough to hold comfortably while playing. The files will be uploaded to Thingiverse within the week.

Next Steps

  • Wireless control: I want a simple way to read the game statistics (average score, levels played, boos kills, etc) and tweak simple settings like audio volume and brightness. I think the easiest way is to make it a wifi access point with a simple web server. This eliminates having to write any client side apps and any smartphone or computer can hope on easily.
  • Levels: Make some way to edit or upload levels via wifi.
  • Multiplayer: I should be able to link multiple controllers. If I can think of a good dual player game idea, I might try to add that.
  • Python: It might be a fun challenge to write the game in Micro Python. This might open up the development to more people.

If you want to be notified of future blog posts, please subscribe.

 

Game Audio for the ESP32

I have been working on some games for the ESP32 and needed some decent quality audio with a minimum number of additional components.  I was bouncing between using the DAC and using the I2S bus. The DAC requires less external parts, so I went that way. I ended up creating a very simple library for use in he Arduino IDE. (Note: This only works with ESP32)

DACs

The ESP32 has (2) internal 8 bit DACs. DACs are Digital to Analog Converters. You give them an 8 bit value and they output an analog voltage. The voltage range of the 0-255 8-bit value is roughly Gnd to 3.3V on the ESP32. If you digitize an audio file, you can then play it back through the DAC. The DAC can’t directly drive a basic speaker, but you can connect it to an amplifier or amplified external speakers. The 8-bit quality is not great, but more than good enough for simple games.

The sound data comes from wave (.wav) files. This uses the 8 bit, unsigned, uncompressed format.  Wave files can be saved with any sampling rate you want. To get a decent sound you need to sample the  analog files at thousands of times per second. I have found that 4000 Hz is about the minimum for anything musical or spoken. 8000 sounds pretty good for most stuff. For reference, CDs are sampled a 44100 Hz (also 16-bit not 8-bit). This means there is a lot of data even for short duration audio. The typical ESP32 board has 2 to 4MB. At an 8000Hz sampling rate that gives you 125 seconds per MB. That is plenty for what I am working on, so I decided to store the data on the ESP32. If that is not good enough, you could use an SD card.

Here is a reference of the wave format. Only a few of the values are used.

ByteLengthDescription
04Always "RIFF"
44File size in Bytes
84Always "WAVE"
124Always "fmt " with a null
164Length of format
202Audio format (1=PCM)
222Number of channels
244Sample rate
284Byte Rate
322Block Align
342Bits per sample (8,16)
364Alaways "data"
404File size
44...Start of data

References

I found some good references to help me with this library.

I started with the Xtronical XT_DAC_Audio library, but found it got be very unstable when I added it to my game. It often crashed when seemingly unrelated code was added. I think the problem was due to trying to use floating point math in the interrupt. As soon as I took that out, it got very stable. They make reference to the issue in the code and suggest it could get fixed via compiler updates.

How it works

The wave data is stored in large char arrays. An interrupt reads the data and feeds that to the DAC. Using an interrupt allows the audio to play “in the background” while the game is running. The Xtronical library uses a fixed interrupt rate of 50kHz. It does some floating math in the interrupt to adjust to the actual sample rate of the wav file. I decided to change the interrupt rate to the actual sample rate of the wav data.

Here is a snippet of what wave data array looks like.

unsigned char PROGMEM pacman[33850] = {
  0x52, 0x49, 0x46, 0x46, 0x32, 0x84, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45,
  0x66, 0x6D, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00,
  0x40, 0x1F, 0x00, 0x00, 0x40, 0x1F, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00,
  0x64, 0x61, 0x74, 0x61, 0xC6, 0x83, 0x00, 0x00, 0x7F, 0x7F, 0x7F, 0x7F,
  0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F,
  0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F,
  0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7E, 0x80, 0x7E, 0x80, 0x7E, 0x80,
  0x7E, 0x80, 0x7D, 0x84, 0xA2, 0x9F, 0xAF, 0xC0, 0xCE, 0xE1, 0xED, 0xF1,
  0xED, 0xE8, 0xDC, 0xCB, 0xB5, 0xA4, 0x95, 0x92, 0x91, 0x94, 0x9D, 0xB1,
  0xB6, 0xB5, 0xB9, 0xBB, 0x99, 0x7D, 0x66, 0x51, 0x64, 0x65, 0x5E, 0x74,
  0x9D, 0xAF, 0xC9, 0xC4, 0xB8, 0xC1, 0xBB, 0xA3, 0x89, 0x73, 0x54, 0x3B,
  0x2E, 0x30, 0x35, 0x42, 0x56, 0x70, 0x7F, 0x89, 0x8E, 0x8C, 0x86, 0x77,
  0x5E, 0x49, 0x3E, 0x34, 0x31, 0x42, 0x5A, 0x72, 0x91, 0xB1, 0xC2, 0xC1,
  ...... 

 

The sample rate is stored in the header of the wav data. When you play the file, it sets the interrupt rate to match the sample rate, so no math is required. You can use a mix of sample rates in the waves you use. It will adjust as needed before it is played back. It allows 2000Hz to 50000Hz sample rates. If you try to go outside that range it will use 2000 Hz. You will easily know there is a problem if you hear that.

Game Related Features

I added some features that might be needed when using with a game.

  • Check to see if the audio player is currently playing anything.
  • Interrupt The Current Wave – If something happens like an explosion you need to immediately switch to that sound. This is an option when you send a command to play a wave.
  • Change the Frequency – I had some sounds that needed to change based on game speed. There is an option speed up r slow down the play back speed by a multiplier to give.

Using the library

  • Download the zip file.
  • Install the library using the Sketch…Include Library…Add .ZIP Library menus.
  • It will also add an example. Open that using the File…Examples…Game_Audio menus.
  • Create a character array of the wav data. See this video from XTronical for how to do that.
  • Give the XTronical video a thumbs up and a nice comment 🙂
  • Create a Game_Audio_Class object. The first parameter is the DAC pin number you want to use. It should be 25, 26. The second parameter is the timer number to use. Example: Game_Audio_Class GameAudio(25,0);
  • Create a Game_Audio_Wav_Class object. The only parameter is name of the char array of you wave data. Example: Game_Audio_Wav_Class pmDeath(pacmanDeath);

Here is the demo that installs with the library

#include "SoundData.h";
#include "Game_Audio.h";

Game_Audio_Class GameAudio(25,0);

Game_Audio_Wav_Class pmDeath(pacmanDeath); // pacman dyingsound
Game_Audio_Wav_Class pmWav(pacman); // pacman theme

void setup() {
Serial.begin(115200);
Serial.println("Begin...");
}

void loop() {

// ------------- Play 2 wavs one after the other ----------------

Serial.print("Pacman Theme Sample Rate (Hz):");
Serial.println(pmWav.getSampleRate());
Serial.print("Duration (secs):");
Serial.println(pmWav.getDuration());

GameAudio.PlayWav(&pmWav, false, 1.0);

// wait until done
while(GameAudio.IsPlaying()){
}

delay(200);

GameAudio.PlayWav(&pmDeath, false, 1.0);

// wait until done
while(GameAudio.IsPlaying())
{
}

// -------- Play a wav then interrupt it after 600ms ------------

GameAudio.PlayWav(&pmWav, false, 1.0);
delay(600); // this is less than the duration of the last wav

// this wav will not play because the other is playing and interruptCurrent is false
GameAudio.PlayWav(&pmWav, false, 1.0);
// this will interrupt the playing file
GameAudio.PlayWav(&pmDeath, true, 1.0);

// --------- Play a wav with increacing sample rate (speeds up playback)
for (int i=0; i<10; i += 2)
{
while(GameAudio.IsPlaying()){// wait for wav to complete
}
GameAudio.PlayWav(&pmWav, false, (1.0) + (float)i / 10.0);
}

while (true) { // stop here program is done...but anything still playing will complete
}

}

Video

Next Steps

  • I’ll am working on some games, so new features might be added.
  • I’ll probably add the option to use I2S audio rather than the DAC.
  • I might add the option to pull files from an SD card.

If you want to be notified of future blog posts, please subscribe.

ESP32: Step Pulse Experiments with Timers

(Edit: Also check out my better “RMT” way to send the pulse)

I have been playing with the ESP32 microcontroller to see how well it would perform as a small scale CNC controller. The low cost and high performance as well as the built in Wifi and Bluetooth make it very attractive.

One of the challenges is step pulse timing. Most stepper drivers work with a direction signal and step signal. The step signals are a short pulse for each step. If they are too short, the driver will not detect them. If they are too long, it limits the rate at which you can send them.

You first set the direction signal high or low depending on the direction you want the motors to spin. You then send the step pulse. The direction signal has to be stable for a short period of time before the step signal is sent. The process is…

  1. Set direction
  2. Wait a bit (if it changed)
  3. Turn on the step pulse signal.
  4. Wait a bit
  5. Turn off the step pulse signal.

The timing is critical and varies by motor driver. Here is a typical spec.

Here are the specs for a few of the stepper drivers I regularly use.

 Allegro A4988TI DRV8825Toshiba TB6600
Direction Delay200ns650ns?
Step Pulse Delay1us1.9us2.2us

Test Firmware

Typically the firmware motion planner determines when to take a step, then sets an interrupt to occur at that time in the future. This allows the firmware to do other things like interacting with the user and planning future moves while it is waiting for the interrupt.

To simulate a stream of pulses, I created a timer interrupt that would case steps to occur at a constant 5kHz rate. That is onStepperDriverTimer() in the code.

In that interrupt service routine I first set the direction pin. Normally you only need to set it when the direction changes, but it will be easier to see this on a logic analyzer if I change it every time for this test. I then need to wait a little time before setting the step pulse pin. I could use another interrupt to do this, but the time is so short at about 750ns, that it is better to just waste a few cycles. In the CNC software I will only need to do this when the direction changes. That will be at the few beginning of the acceleration when the step rate is the slowest. I do this delay with a few NOP()s. The are “no operations”.

I then setup the the interrupt to end the pulse. That is onStepPulseOffTimer() in the code. I set the step pin after this because those instructions take clock cycles too. I can use those as part of my delay.

When that interrupt occurs, I turn off the step pulse signal. I also turn off the direction in this example. I am only doing it here so I can see that change on the logic analyzer. Normal CNC frmware would just leave it alone because there are typically thousands of steps before the direction is likely to change.

I wrote a program to simulate some CNC firmware so I could play with step pulse timing.

// create the hardware timers */
hw_timer_t * stepperDriverTimer = NULL;  // The main stepper driver timer
hw_timer_t * stepPulseOffTimer = NULL;  // This turns the step pulse off after xx uSeconds

// define the gpio pins
#define STEP_PIN 17
#define DIR_PIN 16

// the step pulse interrupt service routine. 
void IRAM_ATTR onStepperDriverTimer()
{
  // if ... the direction changed from last time (not in this demo)
  digitalWrite(DIR_PIN, HIGH);  // in actual CNC firmware this will go high or low
  for(uint8_t i=0; i<10; i++)
  {
    NOP();  // do nothing for one cycle
  }
  // end if

  // setup the pulse off timer
  timerWrite(stepPulseOffTimer, 0);
  timerAlarmWrite(stepPulseOffTimer, 22, false);  // the alarm point is found by looking at logic analyzer
  timerAlarmEnable(stepPulseOffTimer);  
  
  digitalWrite(STEP_PIN, HIGH); // put it after the timer setup to include the timeto do that
}


// 
void IRAM_ATTR onStepPulseOffTimer()
{
  digitalWrite(STEP_PIN, LOW); // end step pulse 
  digitalWrite(DIR_PIN, LOW); // only here for dem program CNC firmware would leave this until direction change
}


void setup() {  

 pinMode(DIR_PIN, OUTPUT);
 pinMode(STEP_PIN, OUTPUT);

    
 stepperDriverTimer = timerBegin(0, 4, true); // 80Mhz / 4  = 20Mhz// setup stepper timer interrupt ... this will simulate a flow of steps
 stepPulseOffTimer = timerBegin(1, 1, true); // 

 // attach the interrupts
 timerAttachInterrupt(stepperDriverTimer, &onStepperDriverTimer, true);// attach the interrupttimerAttachInterrupt(directionDelayTimer, &onDirectionDelayTimer, true);// attach the interrupt
 timerAttachInterrupt(stepPulseOffTimer, &onStepPulseOffTimer, true);// attach the interrupt
 
// setup the time for the 
 timerAlarmWrite(stepperDriverTimer, 4000, true);  // 20Mhz / 4000 = 5kHz rate ... this is the only one that auto repeats  
 timerAlarmEnable(stepperDriverTimer); 
 
}

void loop() {
  // no loop code required.:
  
}

Results

Here is a picture of my setup.

This is screen shot of what the logic analyzer captured. The upper line is the step signal and the lower line is the direction signal. The direction signal comes on first and then the step pulse signal comes on 0.75us later. The step pulse then lasts for about 2.5us before turning off.

Next Steps

  • I’ll go forward this method to see how well it works in actual CNC firmware.
  • I have been programming in the Arduino-ESP32 environment. This is an easy way to learn about the peripherals and do some quick tests. I may switch to the ESP-IDF  in the future.
  • I would like to investigate the RMT features of the ESP32. It is designed for Remote Controls, but I have heard it is quite flexible and might help with pulse generation.

 


If you want to be notified of future blog posts, please subscribe.

Using the HC-05 Bluetooth Module

In a previous post I showed how to use the HC-06 Bluetooth module. In this post we will be using the HC-05. They are very similar, but the HC-05 has more features. The ability to be a master is the main feature. A master can create a connection with other Bluetooth devices. In this post we will only being using it as a slave.

The basic module looks like this.

It is typically purchased soldered to carrier PCB with some additional electronics. The HC-05 typically has a 6 pin header, rather than the 4 pin HC-06 header. The two extra pins are state and enable (en). State gives the state of the Bluetooth connection and enable can power down the module by turning off the power regulator. I will not be using the enable. I will use the state to allow programming of the Arduino via Bluetooth.

 

Here is a schematic of the carrier board. Not all carrier boards are the same, though.

The parts on the carrier PCB are pretty basic

  • 3.3V Low Dropout Regulator, which allows you to power it from 3.6V to 6V.
  • An LED to show the mode.
    • Fast Blink = Waiting for Bluetooth connection3.6
    • Slow Blink = In AT command mode
    • Double Blink = Connected via Bluetooth
  • A button to enter AT Command Mode
  • A diode, probably for reverse voltage protection.
  • Various Pull Up/Down resistors and bypass capacitors.

Configuring the HC-05

Like the HC-06, the HC-05 has a AT command mode, but the commands are a little different. The HC-05 is put in the AT command mode by holding in the switch while applying power. It will do a slow blink when in AT mode. AT Mode accepts commands at 38400 baud , N,8,1 using the Rx and Tx pins. You should level shift the Tx out of your Arduino to 3.3V using a resistor divider. Commands are sent with line feed and carriage return appended to the end. Most serial monitors can do this for you including the Arduino Serial Monitor.

Any command that sets a parameter can also get it.

  • Set commands are in this format “AT+CMD=PARAM” like … AT+NAME=FRED to set the name to FRED. Some commands have multiple parameters that are separated by commas.
  • Get commands are in this format AT+CMD?” like AT+PSWD? to get the password. Weirdly, they all seem to work except AT+NAME?.

Here are the commands you needs for slave mode. Remember, each is followed by a line feed and carriage return.

  • AT (This is just a way to test the connection. It will respond “OK”)
  • AT+VERSION? (This returns firmware version info)
  • AT+ROLE=x (for x use 0 =Slave role, 1 = Master role, 2 = Slave-Loop role default = 0)
  • AT+NAME=xxxxx (to change name to xxxxx default=HC-05″)
  • AT+PSWD=nnnn (to change password to 4 digit nnnn default = 1234″)
  • AT+UART=nnnn,s,p (nnnn=Baud, s=stop bits (0=1, 1=2), p=parity (0=None, 1=Odd, 2=Even) Example AT+UART=115200,0,0
  • AT+POLAR=a,b (a=PIO8 (LED), b=PIO9 for both 0=low turn on, 1 = high turn on. (see below for how we use this)
  • AT+ORGL (reset all parameters to defaults)
  • AT+RESET (restarts the HC-05. Will not be in AT mode afterward unless button held”)

Using an Arduino to program the HC-05

We need some hardware to talk to the HC-05. An Arduino will easily do that. Here is a diagram and sketch to do this using an Arduino UNO.

This is the hardware diagram. I show an UNO, but virtually any hardware (Nano, Mega, etc) will work. The HC-05 is a 3.3V device so we need to level shift the Arduino 5V Tx signal down to 3.3V.  The diagram uses a resistor divider to do this. The Arduino should have no trouble reading the 3.3V Tx signal from the HC-05, so we don’t need to shift that.

The State connection through the capacitor is optional. This will force a reboot of the Arduino when a Bluetooth connection is made. More on that later.

BTW: A lot of people don’t bother to level shift and it appears to work fine, at least in the short term 🙂

The Arduino Sketch

Here is the sketch I use. We will be setting up 2 serial links. One link will be from the PC to the Arduino to send the commands from the keyboard over USB.  We also need a serial connection from the Arduino the HC-05. We will use a software serial port for this and can use any remaining pins to do this. HC-05 uses 38400 baud for AT commands, regardless of the what you set it to for Bluetooth operation.  I used 115200 for the PC to Arduino connection. Set the Serial monitor like this.

You can then type AT commands in the Sereial Monitor.

Here is the sketch…

#include <SoftwareSerial.h>

#define SOFT_RX 11
#define SOFT_TX 12

SoftwareSerial hcSerial(SOFT_RX, SOFT_TX); // RX, TX

String fromPC = &quot;&quot;;

void setup() { 
  Serial.begin(115200); // hardware serial for the USB-PC
  hcSerial.begin(38400);  // software serial Arduino to HC-06 (38400 is default)

  // print instructions
  Serial.println(&quot;HC-05 AT Command Programmer V1.2&quot;);

  Serial.print(&quot;For Arduino Rx use pin &quot;);
  Serial.println(SOFT_RX);
  
  Serial.print(&quot;For Arduino Tx use pin &quot;);
  Serial.println(SOFT_TX);  
  
  Serial.println(&quot; -- Command Reference ---&quot;);
  Serial.println(&quot;To Read use '?', Like AT+PSWD?&quot;);
  Serial.println(&quot;AT (simply checks connection)&quot;);
  Serial.println(&quot;AT+VERSION (requests the firmware verison)&quot;);
  Serial.println(&quot;AT+ROLE=x (0 =Slave role, 1 =  Master role, 2 = Slave-Loop role  default = 0)&quot;);
  Serial.println(&quot;AT+NAME=xxxxx (to change name to xxxxx default=HC-05&quot;);
  Serial.println(&quot;AT+PSWD=nnnn (to change password to 4 digit nnnn default = 1234&quot;);
  Serial.println(&quot;AT+UART=nnnn,s,p (nnnn=Baud, s=stop bits (0=1, 1=2), p=parity (0=None, 1=Odd, 2=Even)&quot;);
  Serial.println(&quot;AT+POLAR=a,b (a=PIO8 (LED), b=PIO9 for both 0=low turn on, 1 = high turn on.&quot;);  
  Serial.println(&quot;AT+ORGL (reset all parameters to defaults)&quot;);
  Serial.println(&quot;AT+RESET (restarts the HC-05. Will not be in AT mode afterward unless button held&quot;);
  
  
}

void loop() {
  // Read from HC-05
  if (hcSerial.available()) {
    while(hcSerial.available()) { // While there is more to be read, keep reading.
      Serial.print((char)hcSerial.read()); // send it to the PC
      }   
  }
  
  // Read from PC
  if (Serial.available()){
    delay(10); //     
    fromPC = (char)Serial.read();    
 
    
      hcSerial.print(fromPC); // show the HC-05 responce
      Serial.print(fromPC); // echo it back to the PC
    
  }
}

Arduino Programming over Bluetooth.

Arduinos are programmed over serial via a bootloader. A bootloader is program that runs for a few seconds whenever the Arduino is started. It looks for someone trying to program it. It runs in one part of the Arduino’s memory. If it does not detect an attempt to program the Arduino it switches to the part of memory where the last program (sketch) resides. If it does detect an attempt to program the Arduino, it reads the incoming program instructions over the serial port and writes them to that other part of memory where normal programs (sketches) reside. Once the upload is complete it switches to that program and runs it.

Therefore, in order to program the Arduino over a serial connection, you need to trigger a reboot. The Arduino USB creates a full RS232 connection. In addition to Rx and Tx is has other control lines like DTR (Data Terminal Ready). The Arduino uses the DTR signal to force a reset. To reset an Arduino you pull the reset line to ground. The DTR signal out of the USB chip is high when there is no connection and low (ground) when there is a connection.

If we directly connect DTR to the reset pin, the Arduino will be stuck in permanent reset mode whenever a serial connection is open. To correct that, a capacitor is inserted in the circuit. Capacitors block a continuous signal, but pass a quick transition. Therefore the the change from high to low will look like a quick pulse to ground at the reset pin. That pulse is exactly what is needed to reboot run the bootloader.

Here is what that circuit looks likes on an Arduino Nano schematic. The length of the pulse depend on the value of the capacitor and characteristics of the high to low transition.

The HC-05 state pin will work for this. In its normal mode it is high during a connection. We need that to be low (ground). Fortunately the HC-05 has the Polar command. That allows you to flip that logic. AT+POLAR=1,0 will do the trick. The first parameter is for the LED. We leave that at 1. The second parameter is the state and we switch that from the default of 1 to 0.

I found that the typical 0.1uF capacitor would not generate an adequate pulse to ground, so I bumped it up to 1.0uF. It occasionally does not work when uploading. I think a little less capacitance might be better. The Arduino uses the hardware serial connections for programming, so you use those pins. When programming the Arduino use the virtual serial port you got when pairing the Bluetooth. Do not use Bluetooth and the USB serial port at the same time. Both would be connected to the hardware Rx and Tx and conflict with each other and possibly cause damage.

 

Other Reset Features

You may not care about uploading code over Bluetooth, but some of your applications may expect that reboot on connect behavior. I have found this with some GCode senders. They open the serial port and expect to see the text Grbl spits out at startup. Without seeing this text, the program wrongly assumes there is a problem and closes the connection.

Video

Useful Links

 


If you want to be notified of future blog posts, please subscribe.

Polar Pen Machine Kinematics

When you have a round work piece like a drink coaster, it makes sense to have a round work area.  A round work area works best with a polar coordinate system. A polar coordinate system uses an angle and a distance from a center point to define a point in 2D.

The problem is that most drawing and CAM programs work in Cartesian (X,Y,Z) coordinate systems. My machine controller firmware, Grbl, also works in normal linear X,Y, and Z. The process of converting one system to another uses Kinematics.

 

The Firmware

The firmware is side is actually quite easy. I defined the X axis as the distance in mm from the center (the radius). The Y axis will control the angle. The Y axis is setup so that millimeters will equal degrees. If I tell the Y to move 360mm, it will actually rotate the work area by 360°.  I could have used radians, but my brain works a lot slower in radians.

The machine will only need to home on the X axis. It needs to know where the exact center of the work area is. The starting angle does not matter because the coaster is a circle.

The conversion from X, Y to polar is probably won’t fit in into the firmware, so the X, Y conversion is done in a preprocessor software program. The X,Y gcode is output from normal CAM programs, then run through a conversion program.

The Conversion Program

The program reads the X,Y gcode, converts any X or Y coordinates into polar coordinates and outputs a new gcode file. The sender simply sends the new files.  The math is actually quite simple.

Typical Gcode sends line data by giving the end points of lines. You simply draw from one point to the next, unfortunately this creates a few problems with a non linear machine.

The basic non-linearity problem

If we were trying to draw the green square centered on the work area, the generated gcode would basically send the corner points. Each corner point has an equal radius to the center. Therefore, the pen will never change radius when going to the next point. This will result in a circle. We want the green square, but we get the red circle.

We need to calculate each point along the way to stay on the desired path. The preprocessor divides the line into tiny segments. Each segment has the same problem, but at a scale you won’t be able to see.

The Spiral Problem

If we are drawing a shape that crosses the 0° angle we don’t want the angle to spin the wrong way. If a point is at 350° and the next point is 10° (crosses over 0) we don’t want it to spin backwards from 350° to 10°. We want it to go to 370°.  It happens anywhere the angle difference between 2 points is greater than 180°. The program will choose the shortest direction even if that means going above 360° or below 0° degrees.

The Feed Rate Problem

Feed rate, in CNC terms, is the speed of the tool across the material. The CAM software is setting the feed rate as if this were a Cartesian machine. On this machine, if you were drawing a circle, you would simply move 360 units in Y. Without compensating feed rate, the pen would move across the work piece faster for larger diameter circles. I want to do some sort of compensation to help with this. The coasters are very absorbent, so the  lines look thicker if the speed is slower. A consistent speed will help the quality of the work.

Since the lines are all very short, the easiest way to compensate for feed rate is to use the current radius. With a simple circle, Grbl thinks the machine moved 360mm. The real distance is easy to to calculate from the perimeter of that circle.

We can compare it to the 360mm (full circle) and apply the ratio to the desired feed rate from the CAM program.

polarFeedrate = cartesianFeedrate * 360 / (2 * pi * radius)

 

Video

Here is a video of it. The machine is rerunning a file to see the repeatability.

Next Steps.

I would like to automate the preprocessor.  I think a Raspberry pi, might be an easy way to do this.  It would sit between the sender and the controller.

 

 

 

Step Pulse Extender – PSoC Style

The TB6600 stepper drivers I have in the X-Controller have a “Torque” feature.  You set the motor current with a reference voltage and the torque feature allows you to easily switch between that current and 1/3 third of it. This is typically used for an idle current reduction feature at the system level.

tq_schm

Why Idle Reduction?

Stepper motors have a lot of hold torque, but that torque quickly falls off with speed.   Therefore you typically size a stepper motor and set the current for your maximum cut or rapid speed.  This means your motors will have excess torque when idle and will tend to run hottest at idle.  You basically the the current as high as possible until the motors get too hot.  If you could reduce the current at idle, you would reduce the temperature and could set the current higher than normal when spinning.

This is great, but the machine will never be in idle during a long job.  At least one of the motors should always be running. If you could figure out when each individual motor was idle, you handle each motor independently.  That is not easy in firmware, but there are tricks to do it in hardware.  You could tie the feature to the step pulse.  Whenever the step pulse is active, the full torque could be active.  That has two problems. The step pulse is extremely short, in the range of a few microseconds.  The other is you might want the current high for a a short bit after the motor goes idle just to make sure the machine is stable in the new position.

The trick is to use the step pulse, but extend it to the desired duration.  It should stay on through all the step pulses and extend the last pulse.

Discrete Hardware Solution

The X-Controller uses a discrete logic chip to do this. It uses a retriggerable monostable vibrator (74HC123D).  The R/C circuit on the right of the schematic snippet sets the duration. It works great, but this adds a lot of parts and things are locked down and not easily adjustable. If you needed to override this function, you have to break out the soldering iron.

PSoC Solution

With PSoC, when you hear “discrete logic” you should know there is probably a good way to do it on the chip. In this case I designed a custom component using verilog.

The verilog code is quite simple.  The best part is none of this is done on the CPU, so there is no impact on the motion control performance. What the video to see the details.

 

X-Controller-Controller (X-Controller minus controller)

MakeMag_0527

I am very happy with the X-Controller.  It packs everything you need to run Grbl into a clean little package.  It is super easy to hook up and move between machines. With that said, I had a quite a bit different idea in mind when I began the design.

The X-Controller was designed to be the motion controller for the X-Carve. The “X” in X-Carve was meant to signify that it was sold through a configurator and there were a lot of options. The X-controller was going to follow the same concept. It would support Grbl, Beaglebone Machinekit, Smoothy, and others. Additionally, alternate stepper driver PCBs might be developed.

To enable the configurability, the stepper driver section would be separated from the controller section. Every feature the stepper drivers supported would be available to the controller. The plug in controller PCB would control the features via firmware or jumpers and pots, depending on the power of the controller. The current X-controller has 4 stepper drivers, but (2) are wired together. In the split concept the controller card would decide how that was done.

At the time Easel was starting to get some real traction and Easel only supports the Grbl protocol. We decided that it was best to pick the easiest solution for our customers and make the X-Controller Grbl only.

My experiments in Beaglebone and PSoC have been such tangled messes of wires. I always wished I had that disconnected stepper PCB. I finally decided to make one.

xcc1

The XCC Stepper Driver PCB uses the same Toshiba TB6600 drivers as the X-Controller. It fits in the X-controller just like a stock PCB, but it is quite a bit shorter.  The interface side of the PCB has (1) 2×5 right angle header connector for each axis. Brought out the the connector are…

  • Step
  • Direction
  • Torque  (high=full current, low=1/3 current)
  • Enable
  • Micro-stepping selection
  • VRef (sets the motor current)
  • Ground and VMot

xcc2

 

For this version, I put a current selection pot and micro step selection jumpers for each axis to simply testing.  These function should be on the controller board, so most of these will be built without those installed.   The PCB also needs 12VDC to 40VDC power for the motors.  Each driver has a small 5VDC supply built in, so an external source is not needed.

xcc3

Here is a snapshot of the schematic.  This is just 1 of the 4 identical sections.

 

schem

Here is snapshot of the layout. I was able to get everything on 2 layers.

layout

 

It fits into the X-Controller great.  I used a small piece of black acrylic to fit the gap due to the shorter length.  It is working perfectly.  I have been testing it with my PSoC port manually wired in.  A PSoC5 controller will probably be the first controller card I will have made.

xcc4

xcc5

 

 

 

Yet Another Way to do the Kinematics

image1

Paul Kaplan, originator of the Easel project, came up with another way to do the kinematics for the Line-us Clone. My method used intersecting circles. His method uses the Law of Cosines.

The Law of Cosines relates the lengths of the sides of a triangle to the cosine of one of its angles.

Triangle_with_notations_2.svg

lawofcosines

 

This can be used to find the angles of the servo arms.

(Click on the images if you want a larger view)

The Goal

goal

The goal is to find the two angles, A1 and A2, of the servo arms

Known Values

  • Px is the desired X location of the pen
  • Py is the desired Y location of the pen
  • L1 is the length of the upper servo arm (50mm)
  • L2 is the length of the end of the Pen Arm (50mm)

Step 1

Find the distance “D” of the pen to hub using the Pythagorean Theroem and the angle T1 using arctangent.

Px2 + Py2 = D2

rewritten … D = Sqrt(Px2 + Py2)

step1

 

T1 can be found using the arctangent or inverse tangent formula. Note: When programming use the atan2(x,y) function to preserve the quadtrant.

T1 = atan2(Py,Px)

Step 2

step2

 

Find T2 using the Law of Cosines

L12 + D2 – L22 = 2 * L1 * D *cos(T2)

rewritten …  T2 = acos( (L12 + D2 – L22) / (2 * L1 * D))

Step 3

step3

 

Find T3 using the Law of Cosines. We want the left one of the two T3 angles, but since the linkages form a parallelogram that same angle shows occurs in several places.  We will use the right one and the dimensions associated with it.

L12 + L22 – D2 = 2 * L1 * L3 * cos(T3)

rewritten … T3 = acos( (L12 + L22 – D2) / (2 * L1 * L2))

Step 4

step4

 

Determine A1 and A2 from the angles we figured out.

A1 = T1 + T2

A2 = A1 + T3

Conclusion

I think I will switch the code to use this method. I think I can optimize it better in C code. The speed of the code is important.  The faster it runs, the most times per second we can run it.  The more often we run it, the smoother it will run.

 

Hobby/RC Servo Control in PSoC

psoc_setup

 

The PSoC family is my go to line of processors for prototyping.  It is like having a breadboard full of digital and analog circuits that you can wire up on the fly. I have been doing some stuff with hobby servos lately so I needed to figure out how to do it on the PSoC.

Hobby Servos

Wikipedia

From Wikipedia

servo_timing

Image from Adafruit

 

Hobby servos set their rotation based on the length or a repeating pulse. The pulse should be 1ms to 2ms long and repeat every 20ms.  One end of the rotation is at 1ms and the other is at 2ms.

The PSoC PWM  Component

PWM_Comp

The PWM component is perfect for this job.  The PWM component can be setup to have a period and an on time.  The period should be 20ms and the on time would be between 1ms and 2ms.  The component uses a clock and two counter values.  The component will count on every clock pulse.  It resets the counters after the period count has been reached and the CMP value determines how long the pulse is logic high.

The PWM output goes to the servo control line.  Here is the configuration dialog box for the PWM component. The graph at the top is a good reference for what the output will look like.

pwm_setup

The goal is to have a pretty decent resolution to set the 1ms to 2ms pulse.  I chose a 2MHz clock.  I picked the fastest clock that would still fit within the 16bit (65535) limit of the control.  PSoC clocks are derived from system clocks, so you need to pick values easily divided down from them.  The IDE helps with creation of these clocks.  At 2Mhz the period (repeat rate) should be set to 40,000.  The equation is the clock * period(in second) = period counts (2,000,000 counts/sec * 0.02 secs = 40,000 counts).

The CMP Value is how many counts the high pulse should last.  The equation is the same. For 1ms the count would be (2,000,000 cnts/sec * 0.001secs =  2,000 counts) and for 2ms the counts would be 4,000.  The range is 2,000 to 4,000 (2,000 count resolution).  This is better than most hobby servos can do.

The Code

The IDE will generate a bunch of functions, a custom API, for each component used when the application is built. There are two PWM Component functions we need to use for this application .

  • PWM_Servo_Start() This will initialize the component and get it running. This is called once at the beginning of the program.
  • PWM_Servo_WriteCompare(val) This sets the CMP Value that will be used to set the pulse length.

I also wrote a function the can set the value by degrees.

void setServo(float degrees)
{
unsigned int val;
// convert degrees to compare value
// 2000 to 4000 = 0 to 180
// value is
val = (degrees / 180.0 * 2000.0) + 2000;

PWM_Servo_WriteCompare(val);
}

The Results

Here is a screen shot of my logic analyzer. The output was set for 1/2 rotation. The pulse is 1.51ms and the period is 20.14ms.  That is close enough for me.  It is likely the clock speed is different between the PSoC and  and the analyzer.

capture1

 

Typically you will have to tune the to the actual servos used.  Just tweak the endpoint values until you get the rotation you want.

PSoC 5 Port Of the Grbl 1.1 CNC Controller

Image from Cypress

Image from Cypress

Grbl

Grbl Logo 250px

 

Grbl is a high performance CNC controller.  It is used on a lot of small scale CNC machines and is the motion control code behind a lot of 3D printers.  It was originally targeted at the Arduino 328p hardware (UNO). It is developed  by Sungeun “Sonny” Jeon. He is a good friend.  He is always very helpful and this port would not have been possible without the quality of his code and his advice.

PSoC Mixed Signal Controller

I love working with the PSoC (Programmable System on Chip) family of micro controllers.  You can configure them on the fly with many analog and digital components.  The analog components are not basic ADCs and DACs, you have OpAmps, PGAs,  filters, MUXs and more.  The digital blocks includes basic logic gates, all the way up to FPGA like components you program yourself in Verilog..  There are over 200 ready to use components you can wire together on the chip.

I have always used them for small prototype projects, but wanted to test my skills by porting a major project like Grbl.  At the same time I wanted to take advantage of the features of the PSoC. The dev board I used was the CY8CKIT-059.  This has ARM Cortex M3 processor a lot of I/O and costs less than $10! It has a built in programmer and debugger.

OLYMPUS DIGITAL CAMERA

PSoC Advantages

Here is a comparison between the the ATMega 328p (Arduino UNO) and the PSOC5

PSoc 5

ATMega328p (UNO)

CPU

32 bit

(ARM Cortex M3)

8 bit

Speed

Up to 80MHz

16MHz Typ.

Flash (program size)

256k

32k

RAM

64k

2k

EEPROM

2k

1k

I/O

up to 62

14

Flexibility

Grbl’s flexibility allows you to tailor it to your hardware.  With a few limitations, you can move the pins around and change things like whether switches are active low or high.  This is all done using #define values in configuration files.  That is great, but the code gets a little messy every time you access hardware. It has to do a little logic gymnastics each time.

With PSoC you can do all of that in a visual schematic and pin wiring feature.  Here is a PDF of my schematic.  Have you ever swapped transmit and receive on a UART? In PSoC you can just swap the pins on the schematic.

Here is an example of the difference in firmware code.

Grbl on 328p

uint8_t limits_get_state()
{
 uint8_t limit_state = 0;
 uint8_t pin = (LIMIT_PIN & LIMIT_MASK);

 if (bit_isfalse(settings.flags,BITFLAG_INVERT_LIMIT_PINS)) { pin ^= LIMIT_MASK; }

 if (pin) {  
   uint8_t idx;
   for (idx=0; idx<N_AXIS; idx++) {
     if (pin & get_limit_pin_mask(idx)) { limit_state |= (1 << idx); }
   }
 }
 return(limit_state);
}

Grbl on PSoC

uint8_t limits_get_state(){  
 return Status_Limit_Read();
}

 

Special Hardware Usage

I used some special features to move functions out of code and onto the hardware.  One of them was the step pulse.  Stepper drivers typically require a pulse of a minimum length to take a step.  In normal hardware you have to raise the pin, then figure out a way to turn it off after a given period of time.  This is typically done via an interrupt.  It works fine, but the code is messy and interrupts can cause timing issues.  PSoC  control registers have a pulse feature that automates this. You attach a clock and the clock determines the length of the pulse.  The code sets it and the hardware clears it.  It looks like this on the schematic.

step_pulse

 

Another feature I used was hardware switch debouncing.  This can be done completely in hardware.  See the image below.  The clock sets the debounce time.  The debouncers are all fed into a status register where they are read  as a single value.  There are digital “nots” after the debouncers because my switches close to ground.  The firmware could invert the logic, but it is so much easier to read on the schematic. It then feeds an interrupt.

switch_debounce

 

If you would rather do this with an analog filter, you can design custom filters in the hardware.  You could fine tune the filter right from your keyboard.

 LCD

lcd

PSoC has a built in character LCD Component that makes using and LCD  very easy.  The code for the LCD is in the main loop and not an interrupt.  This allows the time critical  stuff to have higher priority.  I used an interrupt to just set a flag so the LCD does not update every time through the main loop.  I found the LCD to be an awesome debugging tool.  I could display stuff while the code is running.

lcd_update

 

I also used a hardware Quadrature Decoder for the LCD rotary knob. This works great to monitor the encoder in hardware.  I just need to read the value in the LCD update routine.  The clock feature on the QuadDec is a debouncer, which helped debounce my mechanical encoder.

encoder

Next Steps

I have been testing for a while and so far it is working great. I also have some plans to use the extra power on some cool projects.

Here is the code on GitHub

Here is a picture of my test setup.

0213172059_HDR