疫情蔓延,在家无事,看着别人玩动态测温挺好玩,自己也试着玩一把,160*120的红外热电堆太贵,就拿MLX90640试试吧,终于,在一个星期忙碌后,完成了基于MLX90640和STM32的动态测温,包括上位机的调试,在这里给大家分享下。
我采用的是STM32F103的核心版,买来很早了,便宜,把传感器插到I2C1上,PB6&PB7,vcc和GND 也分别接上去,就可以调试了,在此比较郁闷的是MLX90640的资料太少了,没办法只能看英文,勉勉强强搞定了,推介大家如果STM32 用的版本比较老,还是用固件好一些,STM32CUBEMX太坑了,我差点死在USB驱动上,后来才发现,还是串口好用。。。接下来就分享下代码吧:
int main(void)
{
int i;
static uint16_t eeMLX90640[832];
uint16_t frame[834];
float Ta,tr;
float emissivity=0.95;
static float mlx90640To[768];
u32 CheckCode;
paramsMLX90640 mlx90640;
char szMsg[64];
/test led/
LEDFlash(2);
/end test led/
//UART1_PutStr(USART1, “@Start to Run…\r\n”);
MLX90640_SetRefreshRate(MLX_I2C_ADDR, RefreshRate);
//UART1_PutStr(USART1, "@1..\r\n");
MLX90640_SetChessMode(MLX_I2C_ADDR);
//UART1_PutStr(USART1, "@2..\r\n");
MLX90640_DumpEE(MLX_I2C_ADDR, eeMLX90640);
//UART1_PutStr(USART1, "@3..\r\n");
MLX90640_ExtractParameters(eeMLX90640, &mlx90640);
//UART1_PutStr(USART1, "@4..\r\n");
for(i=0;i<3;i++)
{
MLX90640_GetFrameData(MLX_I2C_ADDR, frame);
delay_ms(500);
}
delay_ms(1000);
while (1)
{
MLX90640_GetFrameData(MLX_I2C_ADDR, frame);
Ta = MLX90640_GetTa(frame, &mlx90640);
tr = Ta - TA_SHIFT;
MLX90640_CalculateTo(frame, &mlx90640, emissivity, tr, mlx90640To);
//LSB first, MSB behind
CheckCode=0x5A5A;
CheckCode+=0x0602;
for(i=0;i<768;i++)
{
UsartBuff[i*2+4]= (u16)(mlx90640To[i]*100)&0xFF;
UsartBuff[i*2+5]= ((u16)(mlx90640To[i]*100)>>8)&0xFF;
CheckCode+=(u16)(mlx90640To[i]*100);
}
UsartBuff[1540]= (u16)(Ta*100)&0xFF;
UsartBuff[1541]= ((u16)(Ta*100)>>8)&0xFF;
CheckCode+=(u16)(Ta*100);
UsartBuff[1542]= (u16)CheckCode&0xFF;
UsartBuff[1543]= ((u16)CheckCode>>8)&0xFF;
//sprintf(szMsg, "%.02f %.02f %.02f %.02f %.02f\r\n",
//mlx90640To[178],mlx90640To[180],mlx90640To[188],mlx90640To[188], mlx90640To[199]);
UART1_PutBytes(USART1, UsartBuff, 1544);
LEDFlash(1);
}
}
这里要说明的是,MLX90640的各类模式还是要搞清楚, 比较复杂,我也还没有完全吸收,只是看懂怎么用了。
本来想用C++写的,但碍于c#太省心了,花了两个小时搞定,但里面的滑头也不少,用温度值的成像,把3224 做成360300效果一直不是非常好,后来调整了好久好久好久,才调整到现在的样子,因为物体大好处理,关键是如何把人和环境都放到32*24里,再插值出来得到好效果,我把AI的均值算法和用上了,感觉做的效果已经不容易了,代码分享如下:
private SerialPort sp = null;
public static Byte[] receiveBuffer = new byte[115200];
public static int index = 0;
public static Bitmap KiResizeImage(Bitmap bmp, int newW, int newH)
{
try
{
Bitmap b = new Bitmap(newW, newH);
Graphics g = Graphics.FromImage(b);
// 插值算法的质量
g.InterpolationMode = InterpolationMode.Bilinear;
g.DrawImage(bmp, new Rectangle(0, 0, newW, newH), new Rectangle(0, 0, bmp.Width, bmp.Height), GraphicsUnit.Pixel);
g.Dispose();
return b;
}
catch
{
return null;
}
}
public static int ReadData(MemoryStream curStream, int startPosition, int length)
{
int result = -1;
byte[] tempData = new byte[length];
curStream.Position = startPosition;
curStream.Read(tempData, 0, length);
result = BitConverter.ToInt32(tempData, 0);
return result;
}
public static void WriteData(MemoryStream curStream, int startPosition, int length, int value)
{
curStream.Position = startPosition;
curStream.Write(BitConverter.GetBytes(value), 0, length);
}
public static Bitmap CreateBitmap(ushort[] originalImageData, int originalWidth, int originalHeight)
{
Bitmap resultBitmap = new Bitmap(originalWidth, originalHeight, System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
MemoryStream curImageStream = new MemoryStream();
resultBitmap.Save(curImageStream, System.Drawing.Imaging.ImageFormat.Bmp);
curImageStream.Flush();
int curPadNum = ((originalWidth * 8 + 31) / 32 * 4) - originalWidth;
int bitmapDataSize = ((originalWidth * 8 + 31) / 32 * 4) * originalHeight;
int dataOffset = ReadData(curImageStream, 10, 4);
int paletteStart = 54;
int paletteEnd = dataOffset;
int color = 0;
for (int i = paletteStart; i < paletteEnd; i += 4)
{
byte[] tempColor = new byte[4];
tempColor[0] = (byte)color;
tempColor[1] = (byte)color;
tempColor[2] = (byte)color;
tempColor[3] = (byte)0;
color++;
curImageStream.Position = i;
curImageStream.Write(tempColor, 0, 4);
}
byte[] destImageData = new byte[bitmapDataSize];
int destWidth = originalWidth + curPadNum;
for (int originalRowIndex = originalHeight - 1; originalRowIndex >= 0; originalRowIndex--)
{
int destRowIndex = originalHeight - originalRowIndex - 1;
for (int dataIndex = 0; dataIndex < originalWidth; dataIndex++)
{
destImageData[destRowIndex * destWidth + dataIndex] = (byte)(originalImageData[originalRowIndex * originalWidth + dataIndex]);
}
}
curImageStream.Position = dataOffset;
curImageStream.Write(destImageData, 0, bitmapDataSize);
curImageStream.Flush();
resultBitmap = new Bitmap(curImageStream);
return resultBitmap;
}
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
sp = new SerialPort();
sp.PortName = "COM6";
sp.BaudRate = 115200;
sp.StopBits = StopBits.One;
sp.DataBits = 8;
sp.Parity = Parity.None;
sp.ReadTimeout = -1;
sp.RtsEnable = true;
sp.DataReceived += new SerialDataReceivedEventHandler(sp_DataReceived);
sp.Open();
button1.Enabled = false;
}
private void sp_DataReceived(object sender, EventArgs e)
{
System.Threading.Thread.Sleep(50); //延迟100ms等待接收完成数据
this.Invoke((EventHandler)(
delegate
{
Byte[] readBytes = new Byte[sp.BytesToRead];
sp.Read(readBytes, 0, readBytes.Length);
if (readBytes.Length>=2 && readBytes[0]==0x5A && readBytes[1] == 0x5A) //Start
{
index = 0;
}
readBytes.CopyTo(receiveBuffer, index);
index += readBytes.Length;
if (index >= 1543)
{
ushort[] tempdata = new ushort[768];
for (int i=0;i<768;i++)
{
tempdata[i] = (ushort)(receiveBuffer[2 * i + 5] * 256 + receiveBuffer[2 * i + 4]);
}
long mean = 0;
long max = 0;
long min = 0;
int maxi = 0;
for (int i=0;i<768;i++)
{
mean += tempdata[i];
if (tempdata[i] > max)
{
max = tempdata[i];
maxi = i;
}
if (tempdata[i] < min) min = tempdata[i];
}
mean = mean / 768;
long scale = 0;
if ((max - mean) > (mean - min))
scale = max - mean;
else scale = mean - min;
ushort[] imgdata = new ushort[768];
for (int j=0; j<768;j++)
{
imgdata[j] = (ushort)((tempdata[j] - mean)*255.0/scale);
}
System.Drawing.Bitmap bmptemp = new System.Drawing.Bitmap(32, 24);
bmptemp = CreateBitmap(imgdata, 32, 24);
System.Drawing.Bitmap bmptemp1 = new System.Drawing.Bitmap(pictureBox1.Width, pictureBox1.Height);
bmptemp1 = KiResizeImage(bmptemp, pictureBox1.Width, pictureBox1.Height);
int ix = (int)(maxi* pictureBox1.Width / (32.0*32));
int iy = (int)(maxi % 32* (pictureBox1.Height/24.0));
Graphics g = Graphics.FromImage(bmptemp1);
g.DrawEllipse(new Pen(Color.Red, 1), iy - 5, ix - 5, 10, 10);
Font drawFont = new Font("Arial", 16);
SolidBrush drawBrush = new SolidBrush(Color.Red);
g.DrawString(String.Format("{0:00.00}", max/100.0+3.9), drawFont, drawBrush, iy, ix);
g.Flush();
pictureBox1.Image = bmptemp1;
index = 0;
}
}
));
}
}
}
实话讲,MLX90640的精度还是很不错的,不比lepton3.5差,应该和国产的差不多,一开始动态变化较大, 距离变化的时候动态变化也大,其实这一点,只要是红外非制冷的,应该都一样,所以,用40度的水,在不同距离,不同运行时间,做个二维修正表,一般都可以到0.5以内的,要想0.1-0.3, 还是要标温动态演算。
有问题或交流可以随时联系~