In many applications you have to know obstacles in the vicinity and the distance to these obstacles. In this project I developed a 360° radar, based on eight HC-SR04 ultrasonic rangefinders. With an update rate of up to 5 Hz it determines the distance from obstacles around it with an accuracy of about 2 cm in eight sectors (0°, 45°, 90°, 135°, 180° 225° 270° and 315°). The maximum measurement distance is about 400 cm, what should be enough for indoor navigation. The radar data can be used inside the Kinetis Board or with a companion computer connected via USB (serial mode).
With this project I wanted to learn more about FlexIO and how to use FlexIO. I decided not only to use FlexIO driver provided by the SDK, such as UART, I2C, SPI or Camera, but to develop two new FlexIO driver by my own.
HardwarePrepare two Prototype PCBs (50mm x 70mm) with four HC-SR04 sensors (90° shifted to each other). Mount these PCBs 45° shifted to each other. The spacer between the PCBs is provided as STL File and can be printed with a 3D printer. Use superglue to fix the spacer to the PCB. Connect all eight HC-SR04 sensors to Kinetis Board, see schematic for wiring. 74HC4050 ICs are used for 5V to 3.3V level shifting because the echo signal of the HC-SR04 is 5V and the Kinetis Board input voltage is 3.3V max. You can drill a 4mm hole in the middle of the sensor PCB, then you are able to pass the cables through the spacer.
Mount the WS2812 LED Ring on top of the sensors. WS2812 LED 0 point to the 180° HC-SR04 sensor. The spacer between the Sensor PCB and the LED Ring is provided with as STL file with this project. Connect the LED Ring to Kinetis Board, see schematic for wiring.
Schematic are done with the Open Source Tool KiCad, all sources are available in the Project GitHub repository.
The radar provides serial data output. With an LED ring you can easily determine the status of the system. Connect the Kinetis Board via USB to your PC and send commands with a Terminal or application. It is also possible to extend the Kinetis Firmware and use the Radar data inside the Kinetis Board.
Serial interfaceThis project uses the Kinetis USB-Port as serial interface. When connected to a PC a serial interface should appear. On my Ubuntu Notebook it is called /dev/ttyACM0
.
Serial interface parameter:
- Baudrate: 115200 bit/s
- Data bits: 8
- Parity: none
- Stop bits: 1
Serial commands:
- s test serial interface, should return
Serial test
on your terminal.
- p start one measurement (eight sectors) and return values to serial interface and status LEDs. The values are separated by , start with 0°, 45°, 90°... value in cm. Example
6,8,10,12,14,16,18,20
is 6 cm at 0°, 8 cm at 45°, 10 cm 90 °...
- r turns all eight LEDs on (red)
- g turns all eight LEDs on (green)
- b turns all eight LEDs on (blue)
- d turns all eight LEDs off
To test the system I have written a small Python program that visualizes the radar data in real-time.
You can execute this program on an Ubuntu 16.04 computer by:
python ultrasonicradar.py
This program is part of my Project GitHub repository and is located in the gui
directory.
The HC-SR04 is an inexpensive (about 1$) ultrasonic range finder module. It can measure a distance between 1 cm and about 400 cm. The interface to use the HC-SR04 is quite simple. There is a short trigger signal to start the measurement and an echo signal. The duration of the echo signal is related to the distance to the detected object.
Eight WS2812 LED are used to show the distance for each HC-SR04 sensor. Blue flash show that measurement is in progress. Green means no object detected, the color is turning into red related to the distance to the detected object.
FlexIO Timer and Shifter are used to read the distance from the HC-SR04 sensors and to drive eight WS2812 status LEDs. I developed two FlexIO driver for this project.
- HC-SR04 FlexIO driver
- WS2812 FlexIO driver
The HC-SR04 FlexIO driver uses two FlexIO Timers, four FlexIO Shifters, eight FlexIO pins and eight GPIO Pins. The first FlexIO Timer (one tick = 2cm) generates the shift clock, the second FlexIO Timer generates the "capture" clock. A GPIO pins start the measurement. Four FlexIO Shifters are connected together, they capture the HC-SR04 echo signal. The HS-SR04 echo signal starts the shift clock, after 128 bits shifted in, the shift clock timer stop and the result can will be captured. Each captured bit means 2cm distance. To determine the distance, the bits have to be counted and multiplied by 2 to get the distance in cm.
How the driver can be used:
Step 1: Just call uint16_t FLEXIO_HCSR_Ping(uint8_t echo, GPIO_Type *base, uint32_t pin). echo
is the FlexIO Pin number where the echo signal is connected to. *base
is the GPIO base of the trigger signal. pin
is the GPIO pin number of the trigger signal.
The return value is the distance in cm.
Example: FLEXIO_HCSR_Ping(0, GPIOA, 5)
start measurement with trigger pin at GPIOA pin 5 and echo signal connected to FlexIO pin 0.
The WS2812 FlexIO driver uses two FlexIO Timers, one FlexIO Shifter and one FlexIO Pin. The first FlexIO Timer (350ns) generates the shift clock, the second FlexIO Timer is the reload timer 32bit (* 350ns) for the FlexIO Shifter. The FlexIO Shifter shifts bit by bit to the WS2812 LEDs data line.
Before the data can be shifted out by the FlexIO Shifter, the data has be generated by the driver.
WS2812 data order: 8 bit green value , 8 bit red value, 8 bit blue value.
WS2812 bitorder: High bit sent first.
WS2812 0 bit signal: 350ns (+/- 150ns) high signal followed by 800ns (+/- 150ns) low signal.
WS2812 1 bit signal: 700ns (+/- 150ns) high signal followed by 600ns (+/- 150ns) low signal.
WS2812 send of transmission signal: More than 50us low signal.
I made the decision to use a 350ns base clock (first FlexIO Timer) for the WS2812 FlexIO driver.
WS2812 driver 0 bit signal: 350ns (diff 0ns) high signal followed by 700ns (diff -100ns but inside +/- 150ns range) low signal.
WS2812 driver 1 bit signal: 700ns (diff 0ns) high signal followed by 700ns (diff +100ns but inside +/- 150ns range) low signal.
How the driver works:
Step 1: The FlexIO Timer and FelxIO Shifters have to be initialized with FLEXIO_WS2812_Init()
. It took some time to figure out which is the right configuration for the FlexIO Timer and Shifter, but this is the one which does it the right way.
void FLEXIO_WS2812_Init() {
flexio_timer_config_t timerConfig;
flexio_shifter_config_t shifterConfig;
memset(&timerConfig, 0, sizeof(timerConfig));
memset(&shifterConfig, 0, sizeof(shifterConfig));
/* Timer for shifter load */
timerConfig.triggerSelect = FLEXIO_TIMER_TRIGGER_SEL_TIMn(5);
timerConfig.triggerPolarity = kFLEXIO_TimerTriggerPolarityActiveLow;
timerConfig.triggerSource = kFLEXIO_TimerTriggerSourceInternal;
timerConfig.pinConfig = kFLEXIO_PinConfigOutput;
timerConfig.pinSelect = 10;
timerConfig.pinPolarity = kFLEXIO_PinActiveLow;
timerConfig.timerMode = kFLEXIO_TimerModeSingle16Bit;
timerConfig.timerOutput = kFLEXIO_TimerOutputOneNotAffectedByReset;
timerConfig.timerDecrement = kFLEXIO_TimerDecSrcOnTriggerInputShiftTriggerInput;
timerConfig.timerReset = kFLEXIO_TimerResetNever;
timerConfig.timerDisable = kFLEXIO_TimerDisableOnTimerCompare;
timerConfig.timerEnable = kFLEXIO_TimerEnableOnTriggerRisingEdge;
timerConfig.timerStop = kFLEXIO_TimerStopBitDisabled;
timerConfig.timerStart = kFLEXIO_TimerStartBitDisabled;
timerConfig.timerCompare = 62U;
FLEXIO_SetTimerConfig(FLEXIO0, 4, &timerConfig);
/* Timer for shift clock (350ns) */
timerConfig.triggerSelect = 0;
timerConfig.triggerPolarity = kFLEXIO_TimerTriggerPolarityActiveHigh;
timerConfig.triggerSource = kFLEXIO_TimerTriggerSourceInternal;
timerConfig.pinConfig = kFLEXIO_PinConfigOutputDisabled;
timerConfig.pinSelect = 10;
timerConfig.pinPolarity = kFLEXIO_PinActiveLow;
timerConfig.timerMode = kFLEXIO_TimerModeSingle16Bit;
timerConfig.timerOutput = kFLEXIO_TimerOutputZeroNotAffectedByReset;
timerConfig.timerDecrement = kFLEXIO_TimerDecSrcOnFlexIOClockShiftTimerOutput;
timerConfig.timerReset = kFLEXIO_TimerResetNever;
timerConfig.timerDisable = kFLEXIO_TimerDisableNever;
timerConfig.timerEnable = kFLEXIO_TimerEnabledAlways;
timerConfig.timerStop = kFLEXIO_TimerStopBitDisabled;
timerConfig.timerStart = kFLEXIO_TimerStartBitDisabled;
timerConfig.timerCompare = 20U;
FLEXIO_SetTimerConfig(FLEXIO0, 5, &timerConfig);
/* Shifter for WS2812 data */
shifterConfig.timerSelect = 4;
shifterConfig.timerPolarity = kFLEXIO_ShifterTimerPolarityOnNegitive;
shifterConfig.pinConfig = kFLEXIO_PinConfigOutput;
shifterConfig.pinSelect = 9;
shifterConfig.pinPolarity = kFLEXIO_PinActiveHigh;
shifterConfig.shifterMode = kFLEXIO_ShifterModeTransmit;
shifterConfig.inputSource = kFLEXIO_ShifterInputFromPin;
shifterConfig.shifterStop = kFLEXIO_ShifterStopBitDisable;
shifterConfig.shifterStart = kFLEXIO_ShifterStartBitDisabledLoadDataOnShift;
FLEXIO_SetShifterConfig(FLEXIO0, 7, &shifterConfig);
}
Step 2: Create an array of struct FLEXIO_WS2812_LED
and set r
g
b
values.
struct FLEXIO_WS2812_LED {
uint8_t r;
uint8_t g;
uint8_t b;
};
Step 3: Iterate trough the LED array and call FLEXIO_WS2812_Update(const struct FLEXIO_WS2812_LED *led)
for each LED.
void FLEXIO_WS2812_Update(const struct FLEXIO_WS2812_LED *led) {
/* Send g, r and b byte, see WS2812 datasheet */
FLEXIO_WS2812_SendByte(led->g);
FLEXIO_WS2812_SendByte(led->r);
FLEXIO_WS2812_SendByte(led->b);
}
This method calls FLEXIO_WS2812_SendByte
for g
r
b
value which translate the bytes into bits as described above.
void FLEXIO_WS2812_SendByte(uint8_t byte) {
/* WS2812 byte coding, see WS2812 datasheet */
for(uint8_t i = 0; i < 8; i++) {
if(byte & 1 << (7 - i)) {
FLEXIO_WS2812_InsertBuffer(1);
FLEXIO_WS2812_InsertBuffer(1);
FLEXIO_WS2812_InsertBuffer(0);
FLEXIO_WS2812_InsertBuffer(0);
} else {
FLEXIO_WS2812_InsertBuffer(1);
FLEXIO_WS2812_InsertBuffer(0);
FLEXIO_WS2812_InsertBuffer(0);
}
}
}
For each bit FLEXIO_WS2812_InsertBuffer(uint8_t vaule)
is called. This method put the bits into 32bit values that can be transferred into the FlexIO Shifter.
void FLEXIO_WS2812_InsertBuffer(uint8_t value) {
/* Check if bit is set */
if(value) {
/* Insert bit */
flexio_ws2812_buffer[flexio_ws2812_bufferindex] |= 1 << flexio_ws2812_bufferiindex;
}
/* Increase index for bit position */
flexio_ws2812_bufferiindex++;
/* If 32bit reached switch to next 32bit buffer */
if(flexio_ws2812_bufferiindex >= 32) {
flexio_ws2812_bufferindex++;
flexio_ws2812_bufferiindex = 0;
}
}
Step 4: If this is done for every LED you have to call FLEXIO_WS2812_TransmitBuffer()
to start the transfer via FlexIO. This sends the data to the LEDs and also send an end of transfer signal.
void FLEXIO_WS2812_TransmitBuffer() {
for(uint8_t i = 0; i < FLEXIO_WS2812_BUFFER_SIZE; i++) {
/* Check if next 32bit can be transfered */
while(!(FLEXIO_GetShifterStatusFlags(FLEXIO0) & (1 << 7)))
{
;
}
/* Transfer local buffer into shift buffer */
FLEXIO0->SHIFTBUF[7] = flexio_ws2812_buffer[i];
/* Clear buffer when send */
flexio_ws2812_buffer[i] = 0U;
}
/* Clear index when all bits are send */
flexio_ws2812_bufferindex = 0;
flexio_ws2812_bufferiindex = 0;
}
DevelopmentThe development was done on an Ubuntu 16.04 LTS 64bit Notebook with Kinetis Design Studio Version 3.2.0 and KSDK 2.0.0.
Howto setup a development environment on Ubuntu 16.04 LTS 64bit:Step 1: Create account at nxp.com
Step 2: Downloads for Kinetis Design Studio for Linux 64-bit (DEB)
Step 3: Install kinetis-design-studio_3.2.0-1_amd64.deb
on our Ubuntu Computer:
sudo apt install ./kinetis-design-studio_3.2.0-1_amd64.deb
Step 4: Build an SDK at http://kex.nxp.com/en/welcome
Step 5: Download SDK at http://kex.nxp.com/en/vault
Step 6: Unpack SDK to a location of your choice.
Step 7: Start Kinetis Design Studio
cd /opt/Freescale/KDS_v3/eclipse
./kinetis-design-studio
Step 8: You can now start to develop by clicking File -> New -> Kinetis SDK 2.X Project. In the dialog box you have to set the Kinetis SDK folder, this is the folder you choose in Step 6.
Comments