完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
相关推荐
1个回答
|
|
十年前接触生物电子让我对电子产生浓厚的兴趣,让我感到电子科技的博大精深无所不能。最近用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 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, X_Start + (i + 1), Y_End - DataList[i + 1]); } } } } |
|
|
|
只有小组成员才能发言,加入小组>>
3323 浏览 9 评论
3000 浏览 16 评论
3498 浏览 1 评论
9073 浏览 16 评论
4093 浏览 18 评论
1194浏览 3评论
613浏览 2评论
const uint16_t Tab[10]={0}; const uint16_t *p; p = Tab;//报错是怎么回事?
603浏览 2评论
用NUC131单片机UART3作为打印口,但printf没有输出东西是什么原因?
2342浏览 2评论
NUC980DK61YC启动随机性出现Err-DDR是为什么?
1899浏览 2评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-30 01:05 , Processed in 1.244918 second(s), Total 78, Slave 59 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号