本帖最后由 gandonggandong 于 2019-12-24 08:26 编辑
十年前接触生物电子让我对电子产生浓厚的兴趣,让我感到电子科技的博大精深无所不能。最近用STM32和C#实现心电监测,分享给大家一起探讨,我也把这些技术资料整理下。
原理图
心电前端采集电路采用仪表放大器,仪表放大器对于共模干扰有很强的抑制力,适合做心电采集前端电路。传输部分采用USB实现虚拟串口和上位机对接,具体电路如下所示
PCB
上位机程序
单片机采用stm32内部AD采集,关键程序如下
- #include "sys.h"
- #include "delay.h"
- #include "led.h"
- #include "u***_lib.h"
- #include "hw_config.h"
- #include "u***_pwr.h"
- #include "u***_prop.h"
- #include "bsp_usart.h"
- #include "bsp_adc.h"
-
- extern char USB_TX_data[512],USB_RX_data[512];
- extern u8 USB_Tx_Counter,USB_Rx_Counter,USB_TX_flag,USB_RX_flag;
- extern char U1_TX_data[512],U1_RX_data[512];
- extern u8 U1_Tx_Counter,U1_Rx_Counter,U1_TX_flag,U1_RX_flag;
-
- extern short AD_BUF[1024];
- extern unsigned int tiM3_count;
- extern char TIM3_flag;
-
- void RCC_HSI_Configuration(void)
- {
- RCC_DeInit();//??? RCC?????????
-
- RCC_HSICmd(ENABLE);//??HSI
- while(RCC_GetFlagStatus(RCC_FLAG_HSIRDY) == RESET)//??HSI????
- {
- }
-
- if(1)
- {
- FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
- FLASH_SetLatency(FLASH_Latency_2);
-
- RCC_HCLKConfig(RCC_SYSCLK_Div1);
- RCC_PCLK1Config(RCC_HCLK_Div2);
- RCC_PCLK2Config(RCC_HCLK_Div1);
- RCC_PLLConfig(RCC_PLLSource_HSI_Div2, RCC_PLLMul_12);
- RCC_PLLCmd(ENABLE);//??PLL???????,????????
- while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
- {
- }
- RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
- while(RCC_GetSYSCLKSource() != 0x08)
- {
- }
- }
- }
- int main(void)
- {
- u16 i;
- u16 temp;
- u8 u***status=0;
- RCC_HSI_Configuration();
- delay_init(); //ÑÓʱº¯Êý³õʼ»¯
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //ÉèÖÃNVICÖжϷÖ×é2:2λÇÀÕ¼ÓÅÏȼ¶£¬2λÏìÓ¦ÓÅÏȼ¶
- LED_Init();
- USART1_Config();
- ADC_Config();
-
- delay_ms(10000);
- USB_Port_Set(0);
- delay_ms(50);
- USB_Port_Set(1);
- Set_USBClock();
- USB_Interrupts_Config();
- USB_Init();
-
- while(1)
- {
- if(TIM3_flag==1)
- {
- GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_SET);
- TIM3_flag=0;
- temp=ADC_GetConversionValue(ADC1);
- USB_USART_SendData(0x55);//ÒÔ×Ö½Ú·½Ê½,·¢Ë͸øUSB
- USB_USART_SendData(0xaa);//ÒÔ×Ö½Ú·½Ê½,·¢Ë͸øUSB
- USB_USART_SendData(temp>>8);//ÒÔ×Ö½Ú·½Ê½,·¢Ë͸øUSB
- USB_USART_SendData(temp);//ÒÔ×Ö½Ú·½Ê½,·¢Ë͸øUSB
- USB_USART_RX_STA=0;
- GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_RESET);
- }
- if(USB_RX_flag==1)
- {
- USB_RX_flag=0;
- }
- if(u***status!=bDeviceState)//USBÁ¬½Ó״̬·¢ÉúÁ˸ıä.
- {
- u***status=bDeviceState;//¼Ç¼ÐµÄ״̬
- }
- }
- }
复制代码
- void ADC_Config(void)
- {
- GPIO_InitTypeDef GPIO_InitStructure;
- NVIC_InitTypeDef NVIC_InitStructure;
- ADC_InitTypeDef ADC_InitStructure;
- TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
-
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1 , ENABLE );
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
- RCC_ADCCLKConfig(RCC_PCLK2_Div6);
- RCC_APB1PeriphClockCmd( RCC_APB1Periph_TIM3 , ENABLE);
-
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//Ä£ÄâÊäÈë
- GPIO_Init(GPIOA, &GPIO_InitStructure);
-
- ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
- ADC_InitStructure.ADC_ScanConvMode = ENABLE;
- ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
- ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
- ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
- ADC_InitStructure.ADC_NbrOfChannel = 1;
- ADC_Init(ADC1, &ADC_InitStructure);
-
- ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_239Cycles5);
-
- ADC_Cmd(ADC1, ENABLE);
-
- ADC_ResetCalibration(ADC1);
- while(ADC_GetResetCalibrationStatus(ADC1));
- ADC_StartCalibration(ADC1);
- while(ADC_GetCalibrationStatus(ADC1));
-
- ADC_SoftwareStartConvCmd(ADC1, ENABLE);
-
- TIM_DeInit(TIM3); //½«ÍâÉèTIM3¼Ä´æÆ÷ÖØÉèΪȱʡֵ
- TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1 ; //ÉèÖÃÁËʱÖÓ·Ö¸î(Tck_tim)
- TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up ; //Ñ¡ÔñÁ˼ÆÊýÆ÷ģʽ(TIMÏòÉϼÆÊýģʽ)
- TIM_TimeBaseInitStruct.TIM_Period = 999 ; //É趨¼ÆÊýÆ÷×Ô¶¯ÖØ×°Öµ,È¡Öµ·¶Î§0x0000~0xFFFF
- TIM_TimeBaseInitStruct.TIM_Prescaler = 47 ; //ÉèÖÃÓÃÀ´×÷ΪTIM3ʱÖÓƵÂʳýÊýµÄÔ¤·ÖƵֵΪ(7199+1),È¡Öµ·¶Î§0x0000~0xFFFF
- TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct ) ;
-
- TIM_ClearFlag(TIM3, TIM_FLAG_Update); //Çå³ýTIM3µÄ´ý´¦Àí±ê־λ
- TIM_ITConfig(TIM3, TIM_IT_Update,ENABLE); //ʹÄÜTIM3ÖжÏ
- TIM_Cmd(TIM3, ENABLE); //ʹÄÜTIM3ÍâÉè
-
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //NVIC_Group:ÏÈÕ¼ÓÅÏȼ¶2룬´ÓÓÅÏȼ¶2λ
- NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //ÅäÖÃΪTIM3ÖжÏ
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //ÏÈÕ¼ÓÅÏȼ¶Îª1
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //´ÓÓÅÏȼ¶Îª2
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //ʹÄÜÖжÏͨµÀ
- NVIC_Init(&NVIC_InitStructure);
- }
复制代码
上位机采用C#,C#开发window系统应用程序非常方便,程序如下
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Data;
- using System.Drawing;
- using System.IO.Ports;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
- using System.Windows.Forms;
-
- namespace WindowsFormsApplication4
- {
- public partial class Form1 : Form
- {
- private StringBuilder *** = new StringBuilder(); //为了避免在接收处理函数中反复调用,依然声明为一个全局变量
- long AD_num = 12;
- long LD_num = 1;
- long MD_num = 11;
- long QP_num = 0;
- int QP_flag = 0;
- long uart_count = 0;
- private const int Unit_length = 32;//单位格大小
- private const int X_End = 1024+512+256+48;//Y轴最大数值
- private const int Y_End = 512+256+128;//Y轴最大数值
- private const int X_Start = 48;//Y轴最大数值
- private const int Y_Start = 128;//Y轴最大数值
- private const int MaxStep = 33;//绘制单位最大值
- private const int MinStep = 1;//绘制单位最小值
- private const int StartPrint = 100;//点坐标偏移量
- private List DataList = new List();//数据结构----线性链表
- private Pen TablePen = new Pen(Color.FromArgb(0x80, 0x00, 0x00));//轴线颜色
- private Pen LinesPen = new Pen(Color.FromArgb(0x00, 0x80, 0x80));//波形颜色
- public Form1()
- {
- this.SetStyle(ControlStyles.DoubleBuffer | ControlStyles.UserPaint |
- ControlStyles.AllPaintingInWmPaint,
- true);//开启双缓冲
- this.UpdateStyles();
- InitializeComponent();
- System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;
- TablePen.DashStyle = System.Drawing.Drawing2D.DashStyle.DashDotDot;
- SearchAndAddSerialToComboBox(serialPort1, comboBox1);
- }
- private void SearchAndAddSerialToComboBox(SerialPort MyPort, ComboBox MyBox)
- { //将可用端口号添加到ComboBox
- string Buffer; //缓存
- comboBox1.Items.Clear(); //清空ComboBox内容
- //int count = 0;
- for (int i = 1; i < 30; i++) //循环
- {
- try //核心原理是依靠try和catch完成遍历
- {
- Buffer = "COM" + i.ToString();
- MyPort.PortName = Buffer;
- MyPort.Open(); //如果失败,后面的代码不会执行
- // MyString[count] = Buffer;
- comboBox1.Items.Add(Buffer); //打开成功,添加至下俩列表
- MyPort.Close(); //关闭
- }
- catch
- {
- }
- }
- }
- private void button1_Click(object sender, EventArgs e)
- {
- try
- {
- //将可能产生异常的代码放置在try块中
- //根据当前串口属性来判断是否打开
- if (serialPort1.IsOpen)
- {
- //串口已经处于打开状态
- serialPort1.Close(); //关闭串口
- button1.Text = "打开串口";
- button1.BackColor = Color.ForestGreen;
- comboBox1.Enabled = true;
- uart_count = 0;
- }
- else
- {
- //串口已经处于关闭状态,则设置好串口属性后打开
- comboBox1.Enabled = false;
- serialPort1.PortName = comboBox1.Text;
- serialPort1.BaudRate = 115200;
- serialPort1.DataBits = 8;
- serialPort1.Parity = System.IO.Ports.Parity.None;
- serialPort1.StopBits = System.IO.Ports.StopBits.One;
- serialPort1.Open(); //打开串口
- button1.Text = "关闭串口";
- button1.BackColor = Color.Firebrick;
- }
- }
- catch (Exception ex)
- {
- //捕获可能发生的异常并进行处理
-
- //捕获到异常,创建一个新的对象,之前的不可以再用
- serialPort1 = new System.IO.Ports.SerialPort();
- //刷新COM口选项
- comboBox1.Items.Clear();
- comboBox1.Items.AddRange(System.IO.Ports.SerialPort.GetPortNames());
- //响铃并显示异常给用户
- System.Media.SystemSounds.Beep.Play();
- button1.Text = "打开串口";
- button1.BackColor = Color.ForestGreen;
- MessageBox.Show(ex.Message);
- }
- }
-
- private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
- {
- int num = serialPort1.BytesToRead;
- byte[] received_buf = new byte[num];
- int[] show_buf = new int[num];
- float show_data = 1;
-
- serialPort1.Read(received_buf, 0, num);
- if (num == 4)
- {
- uart_count = uart_count + 4;
-
- show_data = ((long)(received_buf[2] << 8) + (long)(received_buf[3]))*768/4096;
-
-
- show_buf[0] = (int)show_data;
- DataList.Add(show_buf[0]);//链表尾部添加数据
- Invalidate(); //刷新显示
- ***.Clear();
- try
- {
- //因为要访问UI资源,所以需要使用invoke方式同步ui
- this.Invoke((EventHandler)(delegate
- {
- textBox1.Clear();
- textBox1.AppendText(uart_count.ToString("F2"));
- }
- )
- );
-
- }
- catch (Exception ex)
- {
- //响铃并显示异常给用户
- System.Media.SystemSounds.Beep.Play();
- MessageBox.Show(ex.Message);
-
- }
- }
- }
- private void Form1_Paint(object sender, PaintEventArgs e)//画
- {
- String Str = "";
- System.Drawing.Drawing2D.GraphicsPath gp = new System.Drawing.Drawing2D.GraphicsPath();
- e.Graphics.FillRectangle(Brushes.White, e.Graphics.ClipBounds);
- //Draw Y 纵向轴绘制
- for (int i = 0; i <= (X_End - X_Start) / Unit_length; i++)
- {
- e.Graphics.DrawLine(TablePen, X_Start + i * Unit_length, Y_Start, X_Start + i * Unit_length, Y_End);//画线
- gp.AddString(i.ToString(), this.Font.FontFamily, (int)FontStyle.Regular, 12, new RectangleF(X_Start + i * Unit_length - 7, Y_End + 4, 400, 50), null);//添加文字
- }
- //Draw X 横向轴绘制
- for (int i = 0; i <= (Y_End - Y_Start) / Unit_length; i++)
- {
- e.Graphics.DrawLine(TablePen, X_Start, Y_Start + i * Unit_length, X_End, Y_Start + i * Unit_length);//画线
- // if (i == 17) break;
- gp.AddString((((12 - i) * Unit_length).ToString() ), this.Font.FontFamily, (int)FontStyle.Regular, 14, new RectangleF(X_Start - 50, Y_Start + i * Unit_length - 8, 400, 50), null);//添加文字
- }
- e.Graphics.DrawPath(Pens.Black, gp);//写文字
- if (DataList.Count - 1 >= (X_End - X_Start))//如果数据量大于可容纳的数据量,即删除最左数据
- {
- DataList.RemoveRange(0, DataList.Count - (X_End - X_Start) - 1);
- }
- for (int i = 0; i < DataList.Count - 1; i++)//绘制
- {
- e.Graphics.DrawLine(LinesPen, X_Start + i, Y_End - DataList[i], X_Start + (i + 1), Y_End - DataList[i + 1]);
- }
-
- }
- }
- }
复制代码
|