ECE 4760 Project: Kendo Sword Trainer

Introduction

For our final project, we built a system to aid in practicing kendo sword strikes by providing feedback to a kendo practitioner for improving their form. A set of three piezoelectric sensors on a helmet were used to detect the location and strength of the strike. Thus, the set of piezoelectric sensors would help find the strikable area, which is hard to find by human eyes, but that alone can not decide whether a strike is good. An LSM9DS1 integrated circuit (IC) device possessing a 3-axis gyroscope and 3-axis accelerometer was attached to the kendo sword to sample the direction of the sword during a strike attempt using a combination of the two sensors’ feedback. A strike attempt is identified and started by the gravitational force that the LSM9DS1’s accelerometer reading is used to calculate, which ensures that a strike has the required strength to be considered a viable strike. Combining the direction and hitting area of the strike, we evaluated whether or not a sword strike hit the helmet and how well a sword strike was executed on a scoring scale of 0-5. A visual design of the score along with relevant statistics and graphics was outputted to the TFT LCD display.

High Level Design

Rationale for Project Idea:

Kendo is a Japanese style of fencing that uses a bamboo sword called a shinai. This martial art is not only a practice of physical form, but also a training of spirit. As a “way of sword”, it teaches people how to be sharp and decisive, just as a sword would cut through its enemy. For beginners, however, practice with the right form is an important step to realize the spiritual meaning behind this martial art. In kendo, the will to “cut through” is important for strikes. That is, you are not just hitting your target with a stick but rather intending to cut through it with your sword. From a physical perspective, that means the strike needs to be swift, precise, and straight in movement during the swing. In addition, only specific areas are considered valid targets for strikes. On the head, there are striking areas known as men, sayu-men, and yoko-men, which are the upper, left, and right side of the head. From this background, two aspects of hardware were designed to pick up signals and feedback that can be used to decide how correct a strike is: piezoelectric sensors and a combination of a gyroscope and accelerometer acting as a direction sensor. These sensors are shown with the full high level design setup in Figure 1 below:

Figure 1: High Level Design

Background Math:

Using trilateration, by knowing the location of three points x1, x2, and x3, as well as their relative distance to a fourth point x4, we can get the location of x4 by finding the intersection of the three circles centered at x1, x2, and x3 with radius r1, r2, and r3 respectively. This is depicted in Figure 2 below:

Figure 2: Trilation Point Estimation

By setting two points on the x-axis (0,0) and (0,U), we can get the two points’ potential destination at (x,y) such that the following holds:

Calculating the distance to the third point will eliminate a wrong point, leaving us with the desired contact point. However, with our ADC scanning setup, we couldn’t make a precise decision on the contact point. Instead, we give a rating on left/right/center based on the information we gathered.

Details of the direction calculation based on the gyroscope and accelerometer from the LSM9DS1 is discussed in the Software Design section.

Logical Structure:

Figure 3: Logical Structure of Design

Hardware Design

The hardware used in this project includes the following:

  • 1 LSM9DS1-9 DOF Sensor
  • 3 Piezoelectric Sensors
  • 3 Filter Circuits consisting of:
    • 4-MΩ Resistor
    • 1-pF Capacitor
    • Diode (1N4001)
    • Buffer (MCP6242)

LSM9DS1:

We used the LMS9DS1 gyroscope and accelerometer sensors to detect the direction of the swing. The LMS9DS1 is attached on the kendo sword with velcro. This chip has 9 outputs, which include the angular speed, acceleration of direction, and magnetic field for the x, y, and z axes of motion. For our project, we only used the angular speed and acceleration information from the gyroscope and accelerometer respectively.

This chip can use both SPI and I2C communication. After doing a little research, we were able to find an I2C library for this chip. Thus, we decided to use I2C communication for our project to accelerate progress. We used the I2C1 peripheral on the PIC32 to connect to the I2C port on the LSM9DS1 chip, as shown in Figure 4 below:

Figure 4: I2C Connection Diagram

A total of 4 wires are connected between the Big Board with the PIC32 and the LSM9DS1: RB9(SDA1)/SDA, RB8(SCL1)/SCL, 5V/Vin, GND/Gnd. The I2C communication works using a read/write register on the slave device. In our case, a signal from the PIC32 was sent to initiate the communication with the LSM9DS1, and the LSM9DS1 responded with 9 pieces of output data. We modified two parts of the control register shown in Figure 5 to fit our whole project.

Figure 5: LSM9DS1 CTRL_REG1_G Register Bits

We modified the CTRL_REG1_G register to change the output data rate to 119 samples/sec and the internal cutoff frequency of the LSM9DS1 reading to 31 Hz using Table 47 of the LSM9DS1 datasheet shown in Figure 6 below. This is because we wanted to have ADC sampling running at the highest frequency possible.

Figure 6: LSM9DS1 CTRL_REG1_G Register Operation Modes

Piezoelectric Sensor:

We used three piezoelectric sensors attached to a hard helmet to estimate the hit location of the sword strike. The sensor itself, when bent by vibration, outputs a pulse signal that has a range of roughly ±5V. Before attaching the piezoelectric sensor to ADC ports of the PIC32, we first wanted to modify the piezoelectric signal for better detection. Without a filter circuit, the signal response appeared to be in both the positive and negative direction. To achieve an easier detection of the piezoelectric signal, we eliminated the negative part of the signal.

Figure 7: Voltage Clamper and Low-Pass Filter

The circuit in Figure 7 above is what we used to eliminate and prolong the signal (R = 4MΩ, C = 1 pF). The diode only allows current to its right, which eliminate the negative part of the signal. At the same time, the RC circuit on the right has a very low pole frequency, which slows down the decrease of the signal. With this setup, we can prolong the signal. After the signal is processed, we use a buffer to pass the signal to the ADC reading port. The piezoelectric sensor has a very large output resistance. Even with the RC circuits in the middle, the output resistance is still too large to be directly read by the ADC, so we built a buffer with the MCP642 dual operational amplifier to get rid of the large output resistance.

From our research, the speed of a vibration traveling through a hard object is roughly 4500 m/s. The PIC32MX supports ADC sampling at a rate of up to 500 kHz. With three ADC readings, we estimated the sample rate to be 150 kHz. Thus:

So, if we scan the ADC port at the highest frequency, the vibration will travel approximately 3 cm between each sample. With that information, we can determine the hit point of each strike.

You can view the full hardware schematic in Figure 8 below or in a larger format in Appendix C. Pictures can also be found below for the Kendo sword with a closer look at how the LSM9DS1 device is attached, as well as the helmet setup with a closer look at what the piezoelectric sensor electric taped to it looks like.

Figure 8: Complete Project Schematic

Software Design

User Interface:

For a kendo trainer, our user interface is designed to naturally detect a strike and give feedback on the result of the swing. You can view the final user interface in the Results section.

At the beginning, the TFT screen shows a waiting message and displays a “side view” of the sword based on gathered direction value. When a strike happens, a huge change in the accelerometer will be detected. Upon detection, a red “flash” will display on the screen, to mimic the motion of the swing. At the same time, the system starts to record a set of 5 direction vectors, which are used later to decide the score of the sword strike. After entering this stage, a hit on the hard helmet (adc_reading > threshold) will cause the system to enter the next stage, causing the recording of direction value to stop. More CPU power is then dedicated to ADC peak detection. The screen will enter the hit stage, and a score will be calculated based on the 5 direction vectors’ values that were measured during the swing. Then, a counter is set in the ADC thread that increases with each sample taken, and an adc_counterX value will be set based on a general sample counter used to track when a peak is detected or updated, where X is each of the ADC channels used, namely 0, 1, and 5.

With all the information gathered, we start to calculate the score. The score has a range of 0 to 5. If a hit left/right is detected, the score is limited to 0-2. If you hit the center, your rating is either 3, 4 or 5. We use the recorded direction vectors to further differentiate the scores. In our case, the most important part of the direction vectors was Ry, which indicates whether the sword is straight or not. Thus, we set our criteria to be based on how many of the 5 direction samples have a Ry value smaller than a threshold. In our case, the threshold was 0.3 for a normalized R. Figure 9 below shows the scoring criteria source code:

Figure 9: Source Code for Scoring Criteria

The TFT LCD screen then displays the score, a graphical representation of the left/right/center information, and feedback like “Hit! See your score below!” or “Missed! Focus!”. The system will then go back to the idle stage while resetting all the value we gathered for the previous swing. See Figure 10 below for the complete state diagram:

Figure 10: TFT LCD User Interface State Diagram

ADC Reading Logic:

To capture the hit area of a sword strike, an ADC thread is created to read the ADC value. As described earlier, our goal is to compare the time difference of the vibration’s arrival to each piezoelectric sensor. At first, we tried to set up a threshold to record the arrival time of each of the signal. However, we noticed that because the amplitude of each signal was not the same, using amplitude threshold could not precisely capture the difference between the time of arrival of two signals.

Instead, we decided to capture the peak of the signals received. An adc_max value is initiated and reset to zero when the system goes to ideal state. Each ADC value is compared with the adc_max and causes the adc_max to update if the captured value is larger than the old maximum, with a “timestamp” recorded when the peak happened. We were able to improve the detection of hit points after this software implementation change, which is shown in Figure 11 below.

Figure 11: Source Code for ADC Reading Logic

Ideally we would like to compute the exact point of contact with three piezoelectric sensors, but with the direction computation taking lots of CPU resources, our ADC sample rate could potentially have intervals that are not recording value appropriately. At the same time, with noise in the circuit, sometimes the timestamp of a “peak” can be inaccurate. Thus, we decided to instead just show whether a hit with the sword was made at the left, right or center of the helmet. In retrospect, it would have been better if we used a separate PIC32 to solely capture the ADC value, or used other methods for capture.

Direction Computation with Gyroscope and Accelerometer:

For the LSM9DS1 to communicate with the PIC32, the LSM9DS1 datasheet shown in the References section must be carefully examined to be able to implement the required communication code successfully. While we did utilize the datasheet to improve the LSM9DS1’s performance as discussed in the Hardware Design section, this part of the source code was primarily based off of an open-source library for I2C communication specifically between the PIC32 and LSM9DS1, which is referenced in the References section and referred to in our C source code as an include. More time could be allocated this way on software design for the application of kendo training.

An LSM9DS1 thread is dedicated primarily to reading the gyroscope and accelerometer values from the LSM9DS1 and computing with both readings a combined direction vector at a 20-ms interval, which is derived from the output data rate of the chip being set at 119 samples/s. The acceleration reading, because of the presence of gravitational force, is a good indication of direction when the gyroscope is still. When the gyroscope is moving, in our case, when swinging the sword, the force applied on the sword causes the direction reading using only acceleration to be off. At the same time, during motion, the gyroscope’s reading of angular velocity is a good indication of how the direction has changed. Thus, we needed to combine the gyroscope and accelerometer reading together to reconstruct the direction information more accurately. Initially, we tried to average samples of just the gyroscope readings to determine motion, and this was found to not be viable quickly after some testing and research.

To combine the angular velocity and acceleration value, we needed to calculate the direction using the acceleration reading while applying the change shown by gyroscope angular velocity reading.

Figure 12: LSM9DS1 Accelerometer Axes of Motion

With the acceleration reading (ax, ay, az), we can reconstruct a vector R, as shown in Figure 12 above, which represents gravity if the gyroscope is not moving. After normalizing the acceleration vector Ra, we can calculate three angles Azr, Axr and Ayr by applying the trigonometric formula below (corresponding variables replaced as needed):

With Axr, Ayr, and Azr calculated, we can apply the gyroscope reading to the direction calculation.

Figure 13: LSM9DS1 Gyroscope Axes of Motion

As shown in Figure 13 above, the gyroscope reading is rate of change of the three angles mentioned. Thus, by applying the gyroscope reading to Axr, Ayr, and Azr and reconstructing the normalized vector Rg, we can get a prediction of R.

After getting the two vectors Ra and Rg, we needed one last formula to decide the actual direction vector. Since Ra is right when the gyroscope is still, where Rg is right when the gyroscope is moving, we make a combined guess

where values from 5-20 for wGyro, representing how much the gyroscope is weighted into the direction calculation, can give a viable result. In the end, we decided to choose the value 10 through testing of what value seemed to provide accurate direction sensing. The direction calculation implementation has a lot of guidance from a guide on Starlino Electronics that is linked in the References section.

With all the calculations needed for the direction calculation, we worried about whether or not we could keep the ADC sample rate high enough to detect a difference in sword strike area. Starting with floating type values for direction calculation, we found that the system stopped working as expected because of the delay caused by the arithmetic calculation. We then decided to switch to _Accum type that provides fixed point computation, which saved us a lot of CPU power. After switching to _Accum, the system went from staggering to having an ADC detection with almost the same accuracy as before the direction calculation’s algorithm was implemented. You can see our software implementation for the direction calculation in Figure 14 below:

Figure 14: Source Code for Direction Calculation

Design Results

For the hit point detection, our results were mixed. We figured out with a special way of hitting that the system had a fairly good detection accuracy, where the hit area detected was almost always correct. With a normal swing, we did a 20-swing test towards the right side of the helmet, where 16 of the test trials had a correct detection of right or center as expected depending on how close to the center each strike was. The other 4 test trials gave us the opposite detection of left from what was expected. From this level of accuracy and our experience with testing our direction calculation with the TFT as shown below, we found that the LSM9DS1 was performing to our expectations and that other aspects of our design were contributors to inaccuracy in scoring sword strikes. You can also find our final TFT LCD user interface below as well, which proved to be very responsive as we expected during demonstration.

We figured there were two aspects to our design that hindered our accuracy. First, the ADC thread was not always running in our software implementation with threading, making multiple tasks going on in our software execution pose a continuity issue for the ADC detection due to the time used for direction calculation in a separate thread. If we were to continue this project, a separate PIC32 or a better recording method would improve the hit point detection, and we could proceed to implementing the triangulation method described in our High Level Design section that we originally planned to use.

With the piezoelectric sensors, the wires connecting to the sensors had issues at the beginning. When the sensor had a large resistance, a movement of the connecting wire could potentially cause a large signal shift in the ADC reading. We solved this by intertwining the wires such that they spun around each other, as well as electric taping wires in necessary areas to improve wire management and overall safety of the project. This solution reduced the effect, but this artifact still happens when someone approach the helmet. A better solution would be to add a resistor between the + and – connector pins of the piezoelectric sensor. However, with the large internal resistance, this method reduced the ADC signal. In the end, we decided the wire management had compressed this problem to an acceptable level and that we were willing to keep this artifact in of our design as a tradeoff for not allowing our analog signal range to be sacrificed. Our final project setup is shown below with the final signal response filter circuit soldered and packaged in a blue container.

In general, the project was very useable as shown by our demonstration video on YouTube, which you can see below!

Conclusion

Based on our results, we were able to successfully meet several of the essential sensing capability goals that were set at the start of the project for helping a kendo practitioner train using relevant sensors and a microcontroller, as well as create a display interface that adequately scored sword strikes based on our two sensing parameters that were developed. In general, we were able to follow the schedule that we initially planned, but allocating more time towards the end for performance operation and software debugging would have made the process even smoother.

While our software implementation for determining which side of the helmet was struck was fairly consistent, it sometimes could not identify the correct side of the helmet if the sword strike was not close enough to the piezoelectric sensor corresponding to a particular side. Putting more time into understanding the properties of the vibrations when striking the helmet would have helped us improve our software logic to improve our strike area identification accuracy. Potentially, more piezoelectric sensors could be used to collect more ADC data if needed. In terms of direction sampling, our scoring criteria could take into account more requirements of the strike than just being straight on the y-axis of direction, which could have been determined with more research on what makes for a good sword strike given more time. Capturing the force of the sword strike could have been an additional parameter to consider for our scoring system using the LSM9DS1 accelerometer. In this application, the criteria is that it can be neither too weak nor too strong. A heavy blow that doesn’t “cut through” as intended in Kendo can ruin the sword. Capturing voice using a microphone could have been yet another parameter to consider for helping kendo practitioners train since it is common to use a shout known as a kiai to express fighting spirit when striking, which could be evaluated for accuracy.

Ethical Considerations:

Based on the IEEE Code of Ethics on the IEEE website, we followed the 10 stated ethics rules mentioned throughout the project to the best of our knowledge. As rule 3 reinforces in regards to honesty with available data, we made sure to explain where all our relevant sources of information in this project came from to give credit where it is due, as well as critically analyze any shortcomings of the project at its completion regardless of how well it was demonstrated.

Intellectual Property Considerations:

To gear the project’s time towards figuring out how to utilize the LSM9DS1 for determining the quality of a Kendo practitioner’s sword strike, we reused and modified an I2C connection open-source library for using the PIC32 to communicate effectively with the LSM9DS1 for our application. This library was provided on a GitHub repository affiliated with Northwestern University and is mentioned in our References section below. Our software algorithm for calculating the direction of the sword strike was based on a high-level mathematical explanation on Starlino Electronics’ website for using a gyroscope and accelerometer together for this purpose. The online guide with the explanation is also listed in the References section below.

This project could potentially be polished for publication in a martial arts magazine since martial arts practitioners would be excited about finding technological developments that can help them improve their form in training.

Safety Considerations:

Aside from the Kendo sword itself, which is mainly made out of bamboo, the project does not have any outstanding safety issues in its technical aspects. It is important, particularly as a martial artist, to be wary of your surroundings while training.

Source Code

/*
* File: sword.c (Kendo Sword Trainer with Dummy)
*
* Author: Iman Nandi and Weichen Zhou
* For use with Sean Carroll’s Big Board
* http://people.ece.cornell.edu/land/courses/ece4760/PIC32/target_board.html
* Target PIC: PIC32MX250F128B
* IDE: MPLAB X IDE v3.05 or v5.25
* Compiler: XC32 v1.40
*/

////////////////////////////////////
// clock AND protoThreads configure!
// You MUST check this file!
#include “config_1_3_2.h”
// threading library
#include “pt_cornell_1_3_2.h”

////////////////////////////////////
// graphics libraries
// SPI channel 1 connections to TFT
#include “tft_master.h”
#include “tft_gfx.h”

// I2C 2-wire connections to LSM9DS1 (Reference: https://github.com/guiklink/Sparkfun_LSM9DS1_PIC32)
#include “SparkFun_LSM9DS1.h”
#include “LSM9DS1_Registers.h”

// need for rand function
#include <stdlib.h>
// need for sin function
#include <math.h>
#include <plib.h>

// lock out timer 2 interrupt during spi communication to port expander
// This is necessary if you use the SPI2 channel in an ISR.
// The ISR below runs the DAC using SPI2
#define start_spi2_critical_section INTEnable(INT_T2, 0)
#define end_spi2_critical_section INTEnable(INT_T2, 1)

// === the fixed point macros ========================================
typedef signed int fix16 ;
#define multfix16(a,b) ((fix16)(((( signed long long)(a))*(( signed long long)(b)))>>16)) //multiply two fixed 16:16
#define float2fix16(a) ((fix16)((a)*65536.0)) // 2^16
#define fix2float16(a) ((float)(a)/65536.0)
#define fix2int16(a) ((int)((a)>>16))
#define int2fix16(a) ((fix16)((a)<<16))
#define divfix16(a,b) ((fix16)((((signed long long)(a)<<16)/(b))))
#define sqrtfix16(a) (float2fix16(sqrt(fix2float16(a))))
#define absfix16(a) abs(a)

// LSM9DS1 sensor variables for receiving feedback over I2C connection
int lsmSetup = 0;
signed short int gyro[3], accel[3], magn[3];

// Direction calculation variables
_Accum gForce;
_Accum gForceOld;
_Accum gForceNormalized[3];
_Accum directionEstimate[3];
_Accum directionEstimateOld[3];
_Accum aXZ;
_Accum aXZOld;
_Accum aYZ;
_Accum aYZOld;
_Accum RGyro[3];
_Accum signDirectionEstimateZ;
_Accum directionFinal[3];
_Accum directionFinalNormalized;
_Accum directionVal[3];
_Accum directionSample[5][3];
_Accum weightGyro = 10; // Between 5 and 20 for calculating direction accurately
unsigned int directionSampleSize = 5; // Change based on size of directionSample array below
unsigned int directionSampleScore = 0;

// Flags for identifying when events occurred
unsigned int hitStart = 0;
unsigned int hitFlash = 0;
unsigned int hitComplete = 0;

// Variables for delays between TFT display events
int hitTime = -5;
int potentialMissTime = 0;
unsigned int potentialMissed = 0;
unsigned int potentialMissEndTime = 7;

unsigned int hitCounter = 0; // Keeps track of number of hits to helmet

// Variables for scoring system and feedback
unsigned int rating;
char* ratingComment = “”;

// Variables for threading
static struct pt pt_timer, pt_adc, pt_lsm, pt_tft ;

//ADC Variables
int adc_0, adc_1, adc_5;
float V_0, V_5, V_11;
int adc_counter0 = 0;
int adc_counter1 = 0;
int adc_counter5 = 0;
int sample_counter = 0;
int adcThresh = 50;
int adc_max_0 = 0;
int adc_max_1 = 0;
int adc_max_5 = 0;

// string buffer
char buffer[60];

// Keeps track of time elapsed in timer thread
int sys_time_seconds ;

// === LSM9DS1 (Gyroscope and Accelerometer) Thread =================================================
static PT_THREAD (protothread_lsm(struct pt *pt))
{
PT_BEGIN(pt);
static int lsmTime = 20;
static int i, j;

while(1) {
PT_YIELD_TIME_msec(lsmTime);

tft_fillRoundRect(0,210,320,60,1,ILI9340_BLACK); // Clear draw line section for LSM9DS1 direction display of sword

// Get new values for accelerometer and gyroscope values
get_accel(accel);
get_gyro(gyro);

// Compute direction with both gyro and accelerometer
gForceOld = gForce;
gForce = sqrt(accel[0]*accel[0] + accel[1]*accel[1] + accel[2]*accel[2]);
for(i=0; i<3; i++) {
gForceNormalized[i] = accel[i] / gForce;
}

for(i=0; i<3; i++) {
directionEstimate[i] = accel[i];
}

aXZOld = atan2(directionEstimateOld[0], directionEstimateOld[2]);
aXZ = aXZOld + (gyro[1]+20)*0.00875/6.28 * (lsmTime/1000.0);

aYZOld = atan2(directionEstimateOld[1], directionEstimateOld[2]);
aYZ = aYZOld + (gyro[0]-390)*0.00875/6.28 * (lsmTime/1000.0);

RGyro[0] = sin(aXZ) / sqrt(1 + cos(aXZ)*cos(aXZ)*tan(aYZ)*tan(aYZ));
RGyro[1] = sin(aYZ) / sqrt(1 + cos(aYZ)*cos(aYZ)*tan(aXZ)*tan(aXZ));

if(directionEstimateOld[2]>=0) signDirectionEstimateZ = 1;
else signDirectionEstimateZ = -1;

RGyro[2] = signDirectionEstimateZ * sqrt(1 – RGyro[0]*RGyro[0] – RGyro[1]*RGyro[1]);

for(i=0; i<3; i++) {
directionFinal[i] = (gForceNormalized[i] + RGyro[i] * weightGyro) / (1 + weightGyro);
}

directionFinalNormalized = sqrt(directionFinal[0]*directionFinal[0] + directionFinal[1]*directionFinal[1] + directionFinal[2]*directionFinal[2]);
for(i=0; i<3; i++) {
directionVal[i] = directionFinal[i] / directionFinalNormalized;
}

aXZOld = aXZ;
aYZOld = aYZ;
for(i=0; i<3; i++) {
directionEstimateOld[i] = directionEstimate[i];
}

// Pick up a sample every cycle
if(hitStart && !hitComplete) {
// Store samples of direction with sliding window
for (i=0; i<directionSampleSize-1; i++) // Sample number
{
for (j=0; j<3; j++) // X, Y, and Z
{
directionSample[i][j] = directionSample[i+1][j];;
}
}
for (j=0; j<3; j++) // Get new sample at end of window
{
directionSample[directionSampleSize-1][j] = directionVal[j];
}
}

if(abs(gForce – gForceOld) > 10000 && !hitStart) // Indicator that it started
{
hitStart = 1; // This will reset to 0 after rating determined
adc_max_0 = 0;
adc_max_1 = 0;
adc_max_5 = 0;
adc_counter0 = 0;
adc_counter1 = 0;
adc_counter5 = 0;
sample_counter =0;

if(!potentialMissed) {
potentialMissTime = sys_time_seconds;
potentialMissed = 1;
}

hitFlash = 1;
}

// Check which ADC counter is the smallest to determine which side of the helmet was hit.
// This section will occur NEXT cycle after hitComplete flag is triggered
if((((adc_counter5-14) < adc_counter1 && (adc_counter5-14) < adc_counter0) || abs(adc_counter1-adc_counter0) == 100) && hitComplete) {
ratingComment = “Right on target!”;
hitCounter++; // Increase hit counter

// Check if directionSample is good
for (i=0; i<directionSampleSize; i++) // Sample number
{
if (fabs((float)directionSample[i][1]) <= 0.3) directionSampleScore++;
}
if(directionSampleScore > (directionSampleSize – 2)) rating = 5;
else if(directionSampleScore > (directionSampleSize – 3)) rating = 4;
else rating = 3;
directionSampleScore = 0; // Reset directionSampleCounter for next scoring
hitStart = 0;
hitComplete = 0;
potentialMissed = 0;
}
else if (hitComplete) {
// Check which side you hit on
if (adc_counter0 < adc_counter1 && adc_counter0 < adc_counter5) ratingComment = “Too far left”;
else if (adc_counter1 < adc_counter0 && adc_counter1 < adc_counter5) ratingComment = “Too far right”;
hitCounter++; // Increase hit counter

// Check if directionSample is good
for (i=0; i<directionSampleSize; i++) // Sample number
{
if (fabs((float)directionSample[i][1]) <= 0.3) directionSampleScore++;
}
if(directionSampleScore > (directionSampleSize – 2)) rating = 2;
else if(directionSampleScore > (directionSampleSize – 3)) rating = 1;
else rating = 0;
directionSampleScore = 0; // Reset directionSampleCounter for next scoring
hitStart = 0;
hitComplete = 0;
potentialMissed = 0;
}

// Check ADC reading on helmet passed thresholds
if((adc_max_0 > 10 || adc_max_5 > 22 || adc_max_1 > 10) && !hitComplete && hitStart)
{
hitComplete = 1; // This will reset to 0 after rating is determined
hitTime = sys_time_seconds; // Record time of hit
}

tft_drawLine( 120,240, (int)(120+directionVal[0]*30),(int)(240+directionVal[1]*30),ILI9340_BLUE);

// NEVER exit while
} // END WHILE(1)
PT_END(pt);
}

// === ADC Thread =============================================
static PT_THREAD (protothread_adc(struct pt *pt))
{
PT_BEGIN(pt);

while(1) {
// yield time 1 second
PT_YIELD(pt);

//read selected ANx pins in order, AN0 first
adc_0 = ReadADC10(0); // AN0 (RA0)
adc_1 = ReadADC10(1); // AN1 (RA1)
adc_5 = ReadADC10(2); // AN5 (RB2)

//sample counter to see sample rate/s
sample_counter++;

// Detect ADC peaks
if(adc_max_0 < adc_0 && hitStart)
{
adc_max_0 = adc_0;
adc_counter0 = sample_counter;
}

if(adc_max_1 < adc_1 && hitStart)
{
adc_max_1 = adc_1;
adc_counter1 = sample_counter;
}

if(adc_max_5 < adc_5 && hitStart)
{
adc_max_5 = adc_5;
adc_counter5 = sample_counter;
}

// NEVER exit while
} // END WHILE(1)
PT_END(pt);
} // animation thread

// === print a line on TFT =====================================================
// Utilities to print a line on the TFT
// Predefined colors definitions (from tft_master.h)
//#define ILI9340_BLACK 0x0000
//#define ILI9340_BLUE 0x001F
//#define ILI9340_RED 0xF800
//#define ILI9340_GREEN 0x07E0
//#define ILI9340_CYAN 0x07FF
//#define ILI9340_MAGENTA 0xF81F
//#define ILI9340_YELLOW 0xFFE0
//#define ILI9340_WHITE 0xFFFF

// === TFT LCD Thread =================================================
static PT_THREAD (protothread_tft(struct pt *pt))
{
PT_BEGIN(pt);

while(1) {
PT_YIELD_TIME_msec(1000);

// Flash screen when a hit has occurred
if(hitFlash) {
tft_fillScreen(ILI9340_RED);
tft_fillScreen(ILI9340_BLACK);
hitFlash = 0;
}

tft_fillRoundRect(3,285, 230, 15, 1, ILI9340_BLACK); // Clear DEBUG line
tft_fillRoundRect(3,300, 230, 15, 1, ILI9340_BLACK); // Clear DEBUG line

// Clear TFT display lines as necessary for design
tft_fillRoundRect(5,30, 240, 15, 1, ILI9340_BLACK);
tft_fillRoundRect(3,70, 230, 15, 1, ILI9340_BLACK);
tft_fillRoundRect(3,90, 230, 15, 1, ILI9340_BLACK);
tft_fillRoundRect(3,130, 230, 15, 1, ILI9340_BLACK);

// TITLE
tft_setCursor(10, 0);
tft_setTextSize(3);
tft_setTextColor(ILI9340_BLUE);
sprintf(buffer,”KENDO TRAINER”);
tft_writeString(buffer);

if(hitStart && !hitComplete && (sys_time_seconds – potentialMissTime) > 3 && (sys_time_seconds – potentialMissTime) < potentialMissEndTime) {
tft_setCursor(5, 30);
tft_setTextSize(2);
tft_setTextColor(ILI9340_RED);
sprintf(buffer,”You missed! Focus!”);
tft_writeString(buffer);

tft_setCursor(3, 90);
tft_setTextSize(2);
tft_setTextColor(ILI9340_GREEN);
sprintf(buffer,”Score: TBD”);
tft_writeString(buffer);
}
else if((sys_time_seconds – hitTime) < 5) {
tft_setCursor(5, 30);
tft_setTextSize(2);
tft_setTextColor(ILI9340_GREEN);
sprintf(buffer,”Hit! See your score!”);
tft_writeString(buffer);

tft_setCursor(3, 90);
tft_setTextSize(2);
tft_setTextColor(ILI9340_GREEN);
sprintf(buffer,”Score: %d”, rating);
tft_writeString(buffer);
}
else {
tft_setCursor(5, 30);
tft_setTextSize(2);
tft_setTextColor(ILI9340_YELLOW);
sprintf(buffer,”Waiting for hit…”);
tft_writeString(buffer);

tft_setCursor(3, 90);
tft_setTextSize(2);
tft_setTextColor(ILI9340_GREEN);
sprintf(buffer,”Score: TBD”);
tft_writeString(buffer);
}

if((sys_time_seconds – potentialMissTime) == potentialMissEndTime) {
potentialMissed = 0; // Reset potential miss flag
hitStart = 0;
}

tft_setCursor(3, 70);
tft_setTextSize(2);
tft_setTextColor(ILI9340_GREEN);
sprintf(buffer,”Hit Count: %d”, hitCounter);
tft_writeString(buffer);

tft_setCursor(3, 130);
tft_setTextSize(2);
tft_setTextColor(ILI9340_YELLOW);
sprintf(buffer,”%s”, ratingComment);
tft_writeString(buffer);

// Display direction values that are calculated in lsm thread
tft_setCursor(3, 285);
tft_setTextSize(1);
tft_setTextColor(ILI9340_YELLOW);
sprintf(buffer,”DIR: %f %f %f”, (float)directionVal[0], (float)directionVal[1],(float)directionVal[2]);
tft_writeString(buffer);

// Display ADC values of counter to see which ADC counter is the smallest when hit
// Helps to identify how a certain helmet area was identified as being hit
tft_setCursor(3, 300);
tft_setTextSize(1);
tft_setTextColor(ILI9340_YELLOW);
sprintf(buffer,”ADC: L-%d M-%d R-%d”, adc_counter0, adc_counter5, adc_counter1);
tft_writeString(buffer);

// NEVER exit while
} // END WHILE(1)
PT_END(pt);
}

// === Timer Thread =================================================
// system 1 second interval tick
// prints on TFT
static PT_THREAD (protothread_timer(struct pt *pt))
{
PT_BEGIN(pt);

while(1){
PT_YIELD_TIME_msec(1000) ;
tft_fillRoundRect(3, 270, 230, 15, 1, ILI9340_BLACK);
sys_time_seconds++;
tft_setCursor(3, 270);
tft_setTextColor(ILI9340_YELLOW);
tft_setTextSize(1);
sprintf(buffer, “Time=%d”, sys_time_seconds);
tft_writeString(buffer);

// NEVER exit while
} // END WHILE(1)

PT_END(pt);
} // timer thread

// === Main ======================================================
void main(void) {
//SYSTEMConfigPerformance(PBCLK);

ANSELA = 0; ANSELB = 0;

// set up DAC on big board
// timer interrupt //////////////////////////
// Set up timer2 on, interrupts, internal clock, prescalar 1, toggle rate
// at 40 MHz PB clock
// 40,000,000/Fs = 909 : since timer is zero-based, set to 908
OpenTimer2(T2_ON | T2_SOURCE_INT | T2_PS_1_1, 600);

// divide Fpb by 2, configure the I/O ports. Not using SS in this example
// 16 bit transfer CKP=1 CKE=1
// possibles SPI_OPEN_CKP_HIGH; SPI_OPEN_SMP_END; SPI_OPEN_CKE_REV
// For any given peripherial, you will need to match these
// clk divider set to 4 for 10 MHz
SpiChnOpen(SPI_CHANNEL2, SPI_OPEN_ON | SPI_OPEN_MODE16 | SPI_OPEN_MSTEN | SPI_OPEN_CKE_REV | SPICON_FRMEN | SPICON_FRMPOL, 2);
// end DAC setup

// SCK2 is pin 26
// SDO2 (MOSI) is in PPS output group 2, could be connected to RB5 which is pin 14
PPSOutput(4, RPB10, SS2);

PPSOutput(2, RPB5, SDO2);

// === config threads ==========
// turns OFF UART support and debugger pin, unless defines are set
PT_setup();

// === setup system wide interrupts ========
INTEnableSystemMultiVectoredInt();

// the ADC ///////////////////////////////////////
// configure and enable the ADC
CloseADC10(); // ensure the ADC is off before setting the configuration

// define setup parameters for OpenADC10
// Turn module on | ouput in integer | trigger mode auto | enable autosample
// ADC_CLK_AUTO — Internal counter ends sampling and starts conversion (Auto convert)
// ADC_AUTO_SAMPLING_ON — Sampling begins immediately after last conversion completes; SAMP bit is automatically set
// ADC_AUTO_SAMPLING_OFF — Sampling begins with AcquireADC10();
#define PARAM1 ADC_FORMAT_INTG16 | ADC_CLK_AUTO | ADC_AUTO_SAMPLING_ON //

// define setup parameters for OpenADC10
// ADC ref external | disable offset test | disable scan mode | do 3 samples | use single buf | alternate mode off
#define PARAM2 ADC_VREF_AVDD_AVSS | ADC_OFFSET_CAL_DISABLE | ADC_SCAN_ON | ADC_SAMPLES_PER_INT_3 | ADC_ALT_BUF_OFF | ADC_ALT_INPUT_OFF

// Define setup parameters for OpenADC10

// use peripherial bus clock | set sample time | set ADC clock divider
// ADC_CONV_CLK_Tcy2 means divide CLK_PB by 2 (max speed)
// ADC_SAMPLE_TIME_5 seems to work with a source resistance < 1kohm
#define PARAM3 ADC_CONV_CLK_PB | ADC_SAMPLE_TIME_15 | ADC_CONV_CLK_Tcy

// set AN0 (RA0 / left side), AN1 (RA1 / middle side), AN0 (RB3 / right side) as analog inputs
#define PARAM4 ENABLE_AN5_ANA | ENABLE_AN1_ANA | ENABLE_AN0_ANA

// Do not skip the channels you want to scan
#define PARAM5 SKIP_SCAN_AN2 | SKIP_SCAN_AN3 | SKIP_SCAN_AN4 | SKIP_SCAN_AN6 | SKIP_SCAN_AN7 | SKIP_SCAN_AN8 | SKIP_SCAN_AN9 | SKIP_SCAN_AN10 | SKIP_SCAN_AN11 | SKIP_SCAN_AN12 | SKIP_SCAN_AN13 | SKIP_SCAN_AN14 | SKIP_SCAN_AN15

// use ground as neg ref for A
// actual channel number is specified by the scan list
SetChanADC10( ADC_CH0_NEG_SAMPLEA_NVREF); //
OpenADC10( PARAM1, PARAM2, PARAM3, PARAM4, PARAM5 ); // configure ADC using the parameters defined above

EnableADC10(); // Enable the ADC
///////////////////////////////////////////////////////

// init the threads
PT_INIT(&pt_timer);
PT_INIT(&pt_adc);
PT_INIT(&pt_tft);
PT_INIT(&pt_lsm);

// init the display
tft_init_hw();
tft_begin();
tft_fillScreen(ILI9340_BLACK);
//240×320 vertical display
tft_setRotation(0); // Use tft_setRotation(1) for 320×240

// LSM9DS1 (Gyroscope and Accelerometer) setup
lsmSetup = i2c_master_setup();
config_gyro_accel_default();

// round robin scheduler for threads
while(1) {
PT_SCHEDULE(protothread_timer(&pt_timer));
PT_SCHEDULE(protothread_adc(&pt_adc));
PT_SCHEDULE(protothread_tft(&pt_tft));
PT_SCHEDULE(protothread_lsm(&pt_lsm));
}

} // main

// === end ======================================================

Schematics

Source: ECE 4760 Project: Kendo Sword Trainer

About The Author

Muhammad Bilal

I am a highly skilled and motivated individual with a Master's degree in Computer Science. I have extensive experience in technical writing and a deep understanding of SEO practices.