十年前接触生物电子让我对电子产生浓厚的兴趣,让我感到电子科技的博大精深无所不能。最近用stm32和C#实现心电监测,分享给大家一起探讨,我也把这些技术资料整理下。
原理图
心电前端采集电路采用仪表放大器,仪表放大器对于共模干扰有很强的抑制力,适合做心电采集前端电路。传输部分采用USB实现虚拟串口和上位机对接,具体电路如下所示
PCB
上位机程序
实物如下
单片机采用stm32内部AD采集,关键程序如下
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91#include "sys.h" #include "delay.h" #include "led.h" #include "usb_lib.h" #include "hw_config.h" #include "usb_pwr.h" #include "usb_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 usbstatus=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(usbstatus!=bDeviceState)//USBÁ¬½Ó״̬·¢ÉúÁ˸ıä. { usbstatus=bDeviceState;//¼Ç¼ÐµÄ״̬ } } }
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53void 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系统应用程序非常方便,程序如下
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182using 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 sb = 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<int> DataList = new List<int>();//数据结构----线性链表 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(); //刷新显示 sb.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]); } } } }
最后
以上就是震动柜子最近收集整理的关于stm32实现心电监测-原理图单片机程序C#上位机程序的全部内容,更多相关stm32实现心电监测-原理图单片机程序C#上位机程序内容请搜索靠谱客的其他文章。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复