├── 1443config.cfg ├── LICENSE ├── README.md └── readData_IWR1443.py /1443config.cfg: -------------------------------------------------------------------------------- 1 | % *************************************************************** 2 | % Created for SDK ver:01.02 3 | % Created using Visualizer ver:2.0.0.2 4 | % Frequency:77 5 | % Platform:xWR14xx 6 | % Scene Classifier:best_range_res 7 | % Azimuth Resolution(deg):15 + Elevation 8 | % Range Resolution(m):0.047 9 | % Maximum unambiguous Range(m):2.41 10 | % Maximum Radial Velocity(m/s):1 11 | % Radial velocity resolution(m/s):0.13 12 | % Frame Duration(msec):50 13 | % Range Detection Threshold (dB):15 14 | % Range Peak Grouping:disabled 15 | % Doppler Peak Grouping:disabled 16 | % Static clutter removal:enabled 17 | % *************************************************************** 18 | sensorStop 19 | flushCfg 20 | dfeDataOutputMode 1 21 | channelCfg 15 7 0 22 | adcCfg 2 1 23 | adcbufCfg 0 1 0 1 24 | profileCfg 0 77 284 7 40 0 0 100 1 64 2000 0 0 30 25 | chirpCfg 0 0 0 0 0 0 0 1 26 | chirpCfg 1 1 0 0 0 0 0 4 27 | chirpCfg 2 2 0 0 0 0 0 2 28 | frameCfg 0 2 16 0 50 1 0 29 | lowPower 0 0 30 | guiMonitor 1 0 0 0 0 0 31 | cfarCfg 0 2 8 4 3 0 1195 32 | peakGrouping 1 0 0 1 56 33 | multiObjBeamForming 1 0.5 34 | clutterRemoval 1 35 | calibDcRangeSig 0 -5 8 256 36 | compRangeBiasAndRxChanPhase 0.0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 37 | measureRangeBiasAndRxChanPhase 0 1.5 0.2 38 | CQRxSatMonitor 0 3 4 99 0 39 | CQSigImgMonitor 0 31 4 40 | analogMonitor 1 1 41 | sensorStart -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ibai Gorordo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IWR1443 Read Data (Python 3) 2 | 3 | Python program to read and plot the data in real time from the **AWR1443** and **IWR1443** mmWave radar boards (Texas Instruments). The program has been tested with Windows and Raspberry Pi and is based on the Matlab demo from Texas Instruments. 4 | 5 | First, the program configures the Serial ports and sends the CLI commands defined in the configuration file to the radar. Next, the data comming from the radar is parsed to extract the 2D position, range and doppler velocity of the reflected points. Finally, the 2D position of the reflected points is shown in a scatter plot. 6 | 7 | Although it works correctly, the program is still in development, so if you have comments or questions, please feel free to comment. 8 | 9 | ## Required Python packages 10 | * **numpy**: For the array calculations. 11 | * **serial**: To read the serial data from the radar. 12 | * **time**: To wait until more data is generated. 13 | * **pyqtgraph**: For the scatter plot showing the 2D position of the reflected points. 14 | 15 | ## Program Functions 16 | * **serialConfig()**: Configures the serial ports and sends the CLI commands to the radar. It outputs the serial objects for the data and CLI ports. 17 | * **parseConfigFile()**: Parses the configuration file to extract the configuration parameters. It returns the **configParameters** dictionary with the extracted parameters. 18 | * **readAndParseData16xx()**: It reads the data from the data serial port and parses the recived buffer to extract the data from the **Detected objects** package only. Othe package types (range profile, range-azimuth heat map...) could be done similarly but have not been implemented yet. This functions returns a boolean variable (**dataOK**) that stores if the data has been correctly, the frame number and the **detObj** dictionary with the number of detected objects, range (m), doppler velocity (m/s), peak value and 3D position (m). 19 | * **update()**: Runs the **readAndParseData16xx()** function to read the current data and if the data has been read correctly the scatter with the 2D position is updated. 20 | 21 | ## HOW TO USE 22 | * Download the required packages. 23 | * Change the name of the configuration file (.cfg). 24 | * Change the serial ports. 25 | * If **not all the antennas** are being used, then change the value of **numRxAnt** and **numTxAnt**. 26 | * Run the program. 27 | * The data of each frame with the position and velocities of the reflected points is stored in the **detObj** dictionary. Each frame data is stored in the **frameData** dictionary array. 28 | -------------------------------------------------------------------------------- /readData_IWR1443.py: -------------------------------------------------------------------------------- 1 | import serial 2 | import time 3 | import numpy as np 4 | import pyqtgraph as pg 5 | from pyqtgraph.Qt import QtGui 6 | 7 | # Change the configuration file name 8 | configFileName = '1443config.cfg' 9 | 10 | CLIport = {} 11 | Dataport = {} 12 | byteBuffer = np.zeros(2**15,dtype = 'uint8') 13 | byteBufferLength = 0; 14 | 15 | 16 | # ------------------------------------------------------------------ 17 | 18 | # Function to configure the serial ports and send the data from 19 | # the configuration file to the radar 20 | def serialConfig(configFileName): 21 | 22 | global CLIport 23 | global Dataport 24 | # Open the serial ports for the configuration and the data ports 25 | 26 | # Raspberry pi 27 | #CLIport = serial.Serial('/dev/ttyACM0', 115200) 28 | #Dataport = serial.Serial('/dev/ttyACM1', 921600) 29 | 30 | # Windows 31 | CLIport = serial.Serial('COM3', 115200) 32 | Dataport = serial.Serial('COM4', 921600) 33 | 34 | # Read the configuration file and send it to the board 35 | config = [line.rstrip('\r\n') for line in open(configFileName)] 36 | for i in config: 37 | CLIport.write((i+'\n').encode()) 38 | print(i) 39 | time.sleep(0.01) 40 | 41 | return CLIport, Dataport 42 | 43 | # ------------------------------------------------------------------ 44 | 45 | # Function to parse the data inside the configuration file 46 | def parseConfigFile(configFileName): 47 | configParameters = {} # Initialize an empty dictionary to store the configuration parameters 48 | 49 | # Read the configuration file and send it to the board 50 | config = [line.rstrip('\r\n') for line in open(configFileName)] 51 | for i in config: 52 | 53 | # Split the line 54 | splitWords = i.split(" ") 55 | 56 | # Hard code the number of antennas, change if other configuration is used 57 | numRxAnt = 4 58 | numTxAnt = 3 59 | 60 | # Get the information about the profile configuration 61 | if "profileCfg" in splitWords[0]: 62 | startFreq = int(float(splitWords[2])) 63 | idleTime = int(splitWords[3]) 64 | rampEndTime = float(splitWords[5]) 65 | freqSlopeConst = float(splitWords[8]) 66 | numAdcSamples = int(splitWords[10]) 67 | numAdcSamplesRoundTo2 = 1; 68 | 69 | while numAdcSamples > numAdcSamplesRoundTo2: 70 | numAdcSamplesRoundTo2 = numAdcSamplesRoundTo2 * 2; 71 | 72 | digOutSampleRate = int(splitWords[11]); 73 | 74 | # Get the information about the frame configuration 75 | elif "frameCfg" in splitWords[0]: 76 | 77 | chirpStartIdx = int(splitWords[1]); 78 | chirpEndIdx = int(splitWords[2]); 79 | numLoops = int(splitWords[3]); 80 | numFrames = int(splitWords[4]); 81 | framePeriodicity = int(splitWords[5]); 82 | 83 | 84 | # Combine the read data to obtain the configuration parameters 85 | numChirpsPerFrame = (chirpEndIdx - chirpStartIdx + 1) * numLoops 86 | configParameters["numDopplerBins"] = numChirpsPerFrame / numTxAnt 87 | configParameters["numRangeBins"] = numAdcSamplesRoundTo2 88 | configParameters["rangeResolutionMeters"] = (3e8 * digOutSampleRate * 1e3) / (2 * freqSlopeConst * 1e12 * numAdcSamples) 89 | configParameters["rangeIdxToMeters"] = (3e8 * digOutSampleRate * 1e3) / (2 * freqSlopeConst * 1e12 * configParameters["numRangeBins"]) 90 | configParameters["dopplerResolutionMps"] = 3e8 / (2 * startFreq * 1e9 * (idleTime + rampEndTime) * 1e-6 * configParameters["numDopplerBins"] * numTxAnt) 91 | configParameters["maxRange"] = (300 * 0.9 * digOutSampleRate)/(2 * freqSlopeConst * 1e3) 92 | configParameters["maxVelocity"] = 3e8 / (4 * startFreq * 1e9 * (idleTime + rampEndTime) * 1e-6 * numTxAnt) 93 | 94 | return configParameters 95 | 96 | # ------------------------------------------------------------------ 97 | 98 | # Funtion to read and parse the incoming data 99 | def readAndParseData14xx(Dataport, configParameters): 100 | global byteBuffer, byteBufferLength 101 | 102 | # Constants 103 | OBJ_STRUCT_SIZE_BYTES = 12; 104 | BYTE_VEC_ACC_MAX_SIZE = 2**15; 105 | MMWDEMO_UART_MSG_DETECTED_POINTS = 1; 106 | MMWDEMO_UART_MSG_RANGE_PROFILE = 2; 107 | maxBufferSize = 2**15; 108 | magicWord = [2, 1, 4, 3, 6, 5, 8, 7] 109 | 110 | # Initialize variables 111 | magicOK = 0 # Checks if magic number has been read 112 | dataOK = 0 # Checks if the data has been read correctly 113 | frameNumber = 0 114 | detObj = {} 115 | 116 | readBuffer = Dataport.read(Dataport.in_waiting) 117 | byteVec = np.frombuffer(readBuffer, dtype = 'uint8') 118 | byteCount = len(byteVec) 119 | 120 | # Check that the buffer is not full, and then add the data to the buffer 121 | if (byteBufferLength + byteCount) < maxBufferSize: 122 | byteBuffer[byteBufferLength:byteBufferLength + byteCount] = byteVec[:byteCount] 123 | byteBufferLength = byteBufferLength + byteCount 124 | 125 | # Check that the buffer has some data 126 | if byteBufferLength > 16: 127 | 128 | # Check for all possible locations of the magic word 129 | possibleLocs = np.where(byteBuffer == magicWord[0])[0] 130 | 131 | # Confirm that is the beginning of the magic word and store the index in startIdx 132 | startIdx = [] 133 | for loc in possibleLocs: 134 | check = byteBuffer[loc:loc+8] 135 | if np.all(check == magicWord): 136 | startIdx.append(loc) 137 | 138 | # Check that startIdx is not empty 139 | if startIdx: 140 | 141 | # Remove the data before the first start index 142 | if startIdx[0] > 0 and startIdx[0] < byteBufferLength: 143 | byteBuffer[:byteBufferLength-startIdx[0]] = byteBuffer[startIdx[0]:byteBufferLength] 144 | byteBuffer[byteBufferLength-startIdx[0]:] = np.zeros(len(byteBuffer[byteBufferLength-startIdx[0]:]),dtype = 'uint8') 145 | byteBufferLength = byteBufferLength - startIdx[0] 146 | 147 | # Check that there have no errors with the byte buffer length 148 | if byteBufferLength < 0: 149 | byteBufferLength = 0 150 | 151 | # word array to convert 4 bytes to a 32 bit number 152 | word = [1, 2**8, 2**16, 2**24] 153 | 154 | # Read the total packet length 155 | totalPacketLen = np.matmul(byteBuffer[12:12+4],word) 156 | 157 | # Check that all the packet has been read 158 | if (byteBufferLength >= totalPacketLen) and (byteBufferLength != 0): 159 | magicOK = 1 160 | 161 | # If magicOK is equal to 1 then process the message 162 | if magicOK: 163 | # word array to convert 4 bytes to a 32 bit number 164 | word = [1, 2**8, 2**16, 2**24] 165 | 166 | # Initialize the pointer index 167 | idX = 0 168 | 169 | # Read the header 170 | magicNumber = byteBuffer[idX:idX+8] 171 | idX += 8 172 | version = format(np.matmul(byteBuffer[idX:idX+4],word),'x') 173 | idX += 4 174 | totalPacketLen = np.matmul(byteBuffer[idX:idX+4],word) 175 | idX += 4 176 | platform = format(np.matmul(byteBuffer[idX:idX+4],word),'x') 177 | idX += 4 178 | frameNumber = np.matmul(byteBuffer[idX:idX+4],word) 179 | idX += 4 180 | timeCpuCycles = np.matmul(byteBuffer[idX:idX+4],word) 181 | idX += 4 182 | numDetectedObj = np.matmul(byteBuffer[idX:idX+4],word) 183 | idX += 4 184 | numTLVs = np.matmul(byteBuffer[idX:idX+4],word) 185 | idX += 4 186 | 187 | # UNCOMMENT IN CASE OF SDK 2 188 | #subFrameNumber = np.matmul(byteBuffer[idX:idX+4],word) 189 | #idX += 4 190 | 191 | # Read the TLV messages 192 | for tlvIdx in range(numTLVs): 193 | 194 | # word array to convert 4 bytes to a 32 bit number 195 | word = [1, 2**8, 2**16, 2**24] 196 | 197 | # Check the header of the TLV message 198 | tlv_type = np.matmul(byteBuffer[idX:idX+4],word) 199 | idX += 4 200 | tlv_length = np.matmul(byteBuffer[idX:idX+4],word) 201 | idX += 4 202 | 203 | # Read the data depending on the TLV message 204 | if tlv_type == MMWDEMO_UART_MSG_DETECTED_POINTS: 205 | 206 | # word array to convert 4 bytes to a 16 bit number 207 | word = [1, 2**8] 208 | tlv_numObj = np.matmul(byteBuffer[idX:idX+2],word) 209 | idX += 2 210 | tlv_xyzQFormat = 2**np.matmul(byteBuffer[idX:idX+2],word) 211 | idX += 2 212 | 213 | # Initialize the arrays 214 | rangeIdx = np.zeros(tlv_numObj,dtype = 'int16') 215 | dopplerIdx = np.zeros(tlv_numObj,dtype = 'int16') 216 | peakVal = np.zeros(tlv_numObj,dtype = 'int16') 217 | x = np.zeros(tlv_numObj,dtype = 'int16') 218 | y = np.zeros(tlv_numObj,dtype = 'int16') 219 | z = np.zeros(tlv_numObj,dtype = 'int16') 220 | 221 | for objectNum in range(tlv_numObj): 222 | 223 | # Read the data for each object 224 | rangeIdx[objectNum] = np.matmul(byteBuffer[idX:idX+2],word) 225 | idX += 2 226 | dopplerIdx[objectNum] = np.matmul(byteBuffer[idX:idX+2],word) 227 | idX += 2 228 | peakVal[objectNum] = np.matmul(byteBuffer[idX:idX+2],word) 229 | idX += 2 230 | x[objectNum] = np.matmul(byteBuffer[idX:idX+2],word) 231 | idX += 2 232 | y[objectNum] = np.matmul(byteBuffer[idX:idX+2],word) 233 | idX += 2 234 | z[objectNum] = np.matmul(byteBuffer[idX:idX+2],word) 235 | idX += 2 236 | 237 | # Make the necessary corrections and calculate the rest of the data 238 | rangeVal = rangeIdx * configParameters["rangeIdxToMeters"] 239 | dopplerIdx[dopplerIdx > (configParameters["numDopplerBins"]/2 - 1)] = dopplerIdx[dopplerIdx > (configParameters["numDopplerBins"]/2 - 1)] - 65535 240 | dopplerVal = dopplerIdx * configParameters["dopplerResolutionMps"] 241 | #x[x > 32767] = x[x > 32767] - 65536 242 | #y[y > 32767] = y[y > 32767] - 65536 243 | #z[z > 32767] = z[z > 32767] - 65536 244 | x = x / tlv_xyzQFormat 245 | y = y / tlv_xyzQFormat 246 | z = z / tlv_xyzQFormat 247 | 248 | # Store the data in the detObj dictionary 249 | detObj = {"numObj": tlv_numObj, "rangeIdx": rangeIdx, "range": rangeVal, "dopplerIdx": dopplerIdx, \ 250 | "doppler": dopplerVal, "peakVal": peakVal, "x": x, "y": y, "z": z} 251 | 252 | dataOK = 1 253 | 254 | 255 | # Remove already processed data 256 | if idX > 0 and byteBufferLength > idX: 257 | shiftSize = totalPacketLen 258 | 259 | byteBuffer[:byteBufferLength - shiftSize] = byteBuffer[shiftSize:byteBufferLength] 260 | byteBuffer[byteBufferLength - shiftSize:] = np.zeros(len(byteBuffer[byteBufferLength - shiftSize:]),dtype = 'uint8') 261 | byteBufferLength = byteBufferLength - shiftSize 262 | 263 | # Check that there are no errors with the buffer length 264 | if byteBufferLength < 0: 265 | byteBufferLength = 0 266 | 267 | 268 | return dataOK, frameNumber, detObj 269 | 270 | # ------------------------------------------------------------------ 271 | 272 | # Funtion to update the data and display in the plot 273 | def update(): 274 | 275 | dataOk = 0 276 | global detObj 277 | x = [] 278 | y = [] 279 | 280 | # Read and parse the received data 281 | dataOk, frameNumber, detObj = readAndParseData14xx(Dataport, configParameters) 282 | 283 | if dataOk and len(detObj["x"]) > 0: 284 | #print(detObj) 285 | x = -detObj["x"] 286 | y = detObj["y"] 287 | 288 | s.setData(x,y) 289 | QtGui.QApplication.processEvents() 290 | 291 | return dataOk 292 | 293 | 294 | # ------------------------- MAIN ----------------------------------------- 295 | 296 | # Configurate the serial port 297 | CLIport, Dataport = serialConfig(configFileName) 298 | 299 | # Get the configuration parameters from the configuration file 300 | configParameters = parseConfigFile(configFileName) 301 | 302 | # START QtAPPfor the plot 303 | app = QtGui.QApplication([]) 304 | 305 | # Set the plot 306 | pg.setConfigOption('background','w') 307 | win = pg.GraphicsWindow(title="2D scatter plot") 308 | p = win.addPlot() 309 | p.setXRange(-0.5,0.5) 310 | p.setYRange(0,1.5) 311 | p.setLabel('left',text = 'Y position (m)') 312 | p.setLabel('bottom', text= 'X position (m)') 313 | s = p.plot([],[],pen=None,symbol='o') 314 | 315 | 316 | # Main loop 317 | detObj = {} 318 | frameData = {} 319 | currentIndex = 0 320 | while True: 321 | try: 322 | # Update the data and check if the data is okay 323 | dataOk = update() 324 | 325 | if dataOk: 326 | # Store the current frame into frameData 327 | frameData[currentIndex] = detObj 328 | currentIndex += 1 329 | 330 | time.sleep(0.033) # Sampling frequency of 30 Hz 331 | 332 | # Stop the program and close everything if Ctrl + c is pressed 333 | except KeyboardInterrupt: 334 | CLIport.write(('sensorStop\n').encode()) 335 | CLIport.close() 336 | Dataport.close() 337 | win.close() 338 | break 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | --------------------------------------------------------------------------------