尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于STM32F103的电子血压计:从外设配置到示波法算法全解析

基于STM32F103的电子血压计:从外设配置到示波法算法全解析 简介基于STM32设计的电子血压计完整项目定位为嵌入式毕设/课设/竞赛可借鉴的工程源码面向单片机学习者、项目开发者与参加大创、实训等赛事的学生解决血压测量、数据处理与显示等完整功能实现问题。压缩包共161个文件以C语言源码为主35个.c、34个.h另含Keil工程配置、编译生成的hex/axf/map等文件并包含备份与工程说明整体仅2.21MB结构清晰、便于直接烧录与二次开发。源码涵盖GPIO、USART、EXTI、TIM、ADC、RTC等STM32常用外设驱动并包含FSMC、BKP等模块配置可帮助理解血压信号采集、滤波处理与显示交互的完整流程对系统时钟、串口通信和中断等底层设计也有直接体现。目前已有114人学习/下载配套说明与工程文件齐备既适合快速复刻演示也能在现有模块基础上扩展更多功能是一份实用性与完整性兼备的嵌入式软硬件项目资源。1. 为什么选择STM32F103做电子血压计电子血压计这个项目表面上是“测个压力”真正做起来瓶颈在算法你得在气泵放气的几十秒里从压力传感器混叠的直流分量上剥离出脉搏波再根据包络曲线的特征点反推收缩压和舒张压。STM32F103在这类场景里很合适——ADC采样率不用太高定时器能触发DMAFSMC接屏不占CPURTC掉电后还能保存校准参数。这套基于STM32设计的电子血压计资料包含完整源码、工程文件和说明外设覆盖GPIO、USART、EXTI、FSMC、PWR、BKP、TIM、ADC、RTC正好对应一套“采集—算法—显示—存储—通信”的完整链路。如果你不会画PCB也可以用面包板加杜邦线把模块连起来烧录源码就能复现。2. 外设初始化从GPIO到FSMC的配置顺序2.1 硬件框架与引脚规划一个典型的示波法血压计硬件由三部分组成压力传感器负责把袖带压力变成模拟电压气泵和电磁阀负责充放气MCU负责采样、运算和显示。压力传感器可以用MPXV5050GP或HS20这类量程0~300mmHg的器件输出模拟电压先经过分压或运放送进ADC气泵和电磁阀不能直接用引脚驱动要经过MOS管或ULN2003。我一般会把两路ADC分别给“袖带压力”和“参考压力”避免电源电压波动串进采样值。引脚规划如下表功能引脚外设说明压力传感器输出PA0/PA1ADC1_IN0/IN1两路通道可DMA交替采集气泵控制PB0GPIO输出高电平开气泵电磁阀控制PB1GPIO输出高电平放气按键PA2/PA3EXTI2/EXTI3启动/停止测量USART1PA9/PA10USART1_TX/RX115200-8N1显示屏PD0~PD15 PE0~PE5FSMC_NE1/FSMC_A0/A1Bank1 NE1映射地址0x6000 0000配置顺序有讲究先打开SYSCFG/AFIO时钟配置引脚复用再初始化外设时钟最后开中断。我见过不少人先把USART初始化放在GPIO之前导致TX脚输出电平不对标准库里RCC_APB2PeriphClockCmd这一步漏掉也会直接死机。2.2 GPIO、USART与EXTI的基础代码先给出一段可复用的初始化代码void BSP_Periph_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; EXTI_InitTypeDef EXTI_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; /* 开启外设时钟GPIOA、GPIOB、USART1、AFIO */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_USART1 | RCC_APB2Periph_AFIO, ENABLE); /* PA9: TX 复用推挽输出PA10: RX 浮空输入 */ GPIO_InitStructure.GPIO_Pin GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); /* USART1 配置115200, 8位数据, 1停止位, 无校验 */ USART_InitStructure.USART_BaudRate 115200; USART_InitStructure.USART_WordLength USART_WordLength_8b; USART_InitStructure.USART_StopBits USART_StopBits_1; USART_InitStructure.USART_Parity USART_Parity_No; USART_InitStructure.USART_Mode USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, USART_InitStructure); USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); USART_Cmd(USART1, ENABLE); /* 按键PA2接EXTI2下降沿触发 */ GPIO_InitStructure.GPIO_Pin GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource2); EXTI_InitStructure.EXTI_Line EXTI_Line2; EXTI_InitStructure.EXTI_Mode EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger EXTI_Trigger_Falling; EXTI_InitStructure.EXTI_LineCmd ENABLE; EXTI_Init(EXTI_InitStructure); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitStructure.NVIC_IRQChannel EXTI2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority 1; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); }逻辑说明这段代码把串口和启动测量按键放在一起初始化。RCC_APB2PeriphClockCmd一次把多个外设时钟打开避免后面访问寄存器时处于未复位状态GPIO_Mode_AF_PP是复用推挽输出USART1的TX必须这样配否则输出位是死的GPIO_Mode_IPU加上外部机械按键内部上拉保证信号高电平稳定。GPIO_EXTILineConfig是把PA2映射到EXTI2这一步在使用AFIO功能时必须做否则中断永远不触发。2.3 ADC与TIM的同步采样血压测量要在一个固定的采样率下工作比如1kHz。直接在主循环里调ADC_GetConversionValue()会让CPU被拖死。更好的做法是让定时器触发ADCADC转换完成后用DMA搬运到内存数组。void ADC_TIM_Sample_Init(uint16_t *buf, uint16_t len) { DMA_InitTypeDef DMA_InitStructure; ADC_InitTypeDef ADC_InitStructure; TIM_TimeBaseInitTypeDef TIM_InitStructure; /* 用TIM2_CH1触发ADC1DMA1_Channel1传输 */ RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); DMA_DeInit(DMA1_Channel1); DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)ADC1-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)buf; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize len; DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode DMA_Mode_Circular; DMA_InitStructure.DMA_Priority DMA_Priority_High; DMA_InitStructure.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_Channel1, DMA_InitStructure); /* ADC1 配置为扫描模式软件独立触发右对齐 */ ADC_InitStructure.ADC_Mode ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode DISABLE; ADC_InitStructure.ADC_ContinuousConvMode DISABLE; ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_T2_CC2; ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel 1; ADC_Init(ADC1, ADC_InitStructure); RCC_ADCCLKConfig(RCC_PCLK2_Div6); ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5); ADC_DMACmd(ADC1, ENABLE); ADC_Cmd(ADC1, ENABLE); /* 等待转换稳定后再校准 */ ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); TIM_InitStructure.TIM_Period 999; /* 72MHz / 72 / 1000 1kHz */ TIM_InitStructure.TIM_Prescaler 71; TIM_InitStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_InitStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, TIM_InitStructure); }逻辑说明这里用TIM2的更新事件启动ADC。TIM_Prescaler71TIM_Period999APB1时钟72MHz最终定时器频率是1kHz也就是每次更新事件触发一次ADC采样。DMA设置成循环模式转换结果自动写进buf主循环只需要读数组里的最新值不需要等转换。ADC_SampleTime_239Cycles5是为了降低信号源阻值带来的采样误差压力传感器输出阻抗一般比较大采样时间太短会充不进采样电容。ADC外部触发源选择ADC_ExternalTrigConv_T2_CC2所以TIM2的CH2必须是输出比较状态实际使用中如果发现不触发要检查TIM2的CCER寄存器里CC2E是否使能以及GPIO复用是否配置不必真的用引脚输出PWM只需把内部通道打开。2.4 FSMC与RTC初始化的注意事项FSMC接LCD屏时很多人喜欢在速度不够时去调FSMC_Bank1_Timing里的ADDSET和数据建立时间。以ILI9341为例FSMC_NORSRAMInitTypeDef FSMC_InitStructure; FSMC_NORSRAMTimingInitTypeDef p; p.FSMC_AddressSetupTime 1; p.FSMC_AddressHoldTime 0; p.FSMC_DataSetupTime 3; p.FSMC_BusTurnAroundDuration 0; p.FSMC_CLKDivision 0; p.FSMC_DataLatency 0; p.FSMC_AccessMode FSMC_AccessMode_A; FSMC_InitStructure.FSMC_Bank FSMC_Bank1_NORSRAM1; FSMC_InitStructure.FSMC_DataAddressMux FSMC_DataAddressMux_Disable; FSMC_InitStructure.FSMC_MemoryType FSMC_MemoryType_SRAM; FSMC_InitStructure.FSMC_MemoryDataWidth FSMC_MemoryDataWidth_16b; FSMC_InitStructure.FSMC_BurstAccessMode FSMC_BurstAccessMode_Disable; FSMC_InitStructure.FSMC_WaitSignalPolarity FSMC_WaitSignalPolarity_Low; FSMC_InitStructure.FSMC_WrapMode FSMC_WrapMode_Disable; FSMC_InitStructure.FSMC_WaitSignalActive FSMC_WaitSignalActive_BeforeWaitState; FSMC_InitStructure.FSMC_WriteOperation FSMC_WriteOperation_Enable; FSMC_InitStructure.FSMC_WaitSignal FSMC_WaitSignal_Disable; FSMC_InitStructure.FSMC_ExtendedMode FSMC_ExtendedMode_Disable; FSMC_InitStructure.FSMC_WriteBurst FSMC_WriteBurst_Disable; FSMC_InitStructure.FSMC_ReadWriteTimingStruct p; FSMC_InitStructure.FSMC_WriteTimingStruct p; FSMC_NORSRAMInit(FSMC_InitStructure); FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM1, ENABLE);AddressSetupTime1表示地址建立1个HCLK周期DataSetupTime3是数据建立3个周期。如果屏出现雪花或第一行错位优先加DataSetupTime不要一开始就改主频。RTC部分资料包里带了stm32f10x_rtc.c它的坑在BKP和PWRRTC的访问必须先开启PWR和BKP时钟然后使能备份区写保护。断电后再上电如果RTC不走多半是32768Hz晶振的负载电容没算好我一般用6~8pF电容配对。下面这行是使能BKP写保护的标准代码PWR_BackupAccessCmd(ENABLE); BKP_WriteBackupRegister(BKP_DR1, cal_data);写完后如果要防止误改写再调用PWR_BackupAccessCmd(DISABLE)。BKP_DR1在备份域只要VBAT有电复位不会清掉。电子血压计的校准参数压力偏移、K值放这里很合适。3. 血压算法基于脉搏波振荡的收缩压/舒张压判据3.1 为什么示波法比听诊法更适合单片机听诊法需要人判断柯氏音自动化实现要加麦克风而且抗噪能力差。示波法只需要分析袖带放气过程中压力传感器的振荡包络。袖带压力从高于收缩压逐步下降到低于舒张压心脏搏动引起的袖带内压力波动幅度会经历一个“小—大—小”的过程最大振荡幅度对应的袖带压力就是平均动脉压MAP。收缩压和舒张压不在包络波形的固定“特征高度”上不同身材、不同年龄段系数会有差异所以算法要做比例系数修正。具体判据常用的是“幅度比值法”收缩压对应上升沿包络幅度到达峰值的0.450.57舒张压对应下降沿到达峰值的0.750.82。这两个比值不是拍脑袋定的是从大量实测数据拟合出来的。做课程设计时先用固定值比如0.5/0.8能跑通再根据袖带结构和传感器特性做线性查表修正。参数初始值调整方向收缩压比例0.50测量值偏高时调大舒张压比例0.78测量值偏低时调小放气速率4mmHg/s波动太强时减慢滑动平均窗口8噪声大时增大3.2 滑动平均滤波与脉搏波包络提取压力传感器的原始信号是慢变的直流压力叠加一个几十到几百微伏的交流脉搏波不能直接做差分否则脉冲噪声会被放大。我常做的第一步是滑动平均滤波#define SAMPLE_BUF_SIZE 512 #define WINDOW_SIZE 8 uint16_t raw_buf[SAMPLE_BUF_SIZE]; uint16_t smooth_buf[SAMPLE_BUF_SIZE]; void SlidingAverage_Filter(uint16_t *in, uint16_t *out, uint16_t len) { uint32_t sum 0; uint16_t i; /* 先把窗口填满后续每个点是前WINDOW_SIZE个采样的平均 */ for (i 0; i len; i) { sum in[i]; if (i WINDOW_SIZE) { sum - in[i - WINDOW_SIZE]; } out[i] (uint16_t)(sum / WINDOW_SIZE); } }参数说明WINDOW_SIZE8在1kHz采样率下相当于把截止频率压低到约125Hz能滤掉电源工频和电机噪声又不会把脉搏波主峰抹平。求和用uint32_t因为8个uint16_t累加最大不超过524280不会溢出。out数组是滑动平均后的直流压力也就是袖带压力曲线。要得到脉搏波用原始值和平滑值相减int16_t pulse_buf[SAMPLE_BUF_SIZE]; for (i 0; i SAMPLE_BUF_SIZE; i) { pulse_buf[i] (int16_t)raw_buf[i] - (int16_t)smooth_buf[i]; }这个差值是高频交流分量但要继续做包络检测不能直接求绝对值就完事因为机械振动和运动伪迹也会产生幅值。常见做法是再对pulse_buf取绝对值后做第三次滑动平均窗口取1632。窗口太小包络抖动严重窗口太大峰值会被拉平导致MAP偏高。我一般先用32跑出来的MAP比标准血压计高5mmHg时降到16。3.3 收缩压和舒张压的状态机判断实时测量过程中需要一个状态机来记录当前在充气还是放气typedef enum { STATE_IDLE, STATE_INFLATE, STATE_MEASURE, STATE_DEFLATE, STATE_ERROR } measure_state_t; measure_state_t bp_state STATE_IDLE; static uint16_t peak_env 0; static uint16_t map_pressure 0; void BP_StateMachine(uint16_t cuff_pressure, uint16_t env_amp) { switch (bp_state) { case STATE_IDLE: if (Measure_Start_Flag) { Pump_On(); bp_state STATE_INFLATE; } break; case STATE_INFLATE: /* 袖带压力超过220mmHg或比MAP高30mmHg后停止充气 */ if (cuff_pressure 220 || cuff_pressure map_pressure 30) { Pump_Off(); Valve_SlowRelease_On(); bp_state STATE_MEASURE; } break; case STATE_MEASURE: if (env_amp peak_env) { peak_env env_amp; map_pressure cuff_pressure; } if (cuff_pressure 40) { Valve_FastRelease_On(); bp_state STATE_DEFLATE; } break; default: break; } }代码里的map_pressure就是当前抓到的MAPenv_amp是3.2节得到的包络幅值。这个状态机的关键在于充气阶段的结束条件必须冗余判断。如果只按固定值遇到高压患者可能还没到足够压力就停了如果只按相对MAP第一次测量时MAP未知。所以先设定一个动态初值比如从上次测量结果取第一次默认120mmHg。收缩压和舒张压的计算不需要单独状态而是在放气过程中记录“包络幅值首次超过峰值一半”和“下降沿最后一次超过峰值八成”的位置。典型实现是uint16_t sys_pressure 0, dia_pressure 0; uint8_t sys_found 0, dia_found 0; for (i 1; i measure_len; i) { if (!sys_found env[i] peak_env * BP_SYS_RATIO env[i-1] peak_env * BP_SYS_RATIO) { sys_pressure cuff[i]; sys_found 1; } if (env[i] peak_env * BP_DIA_RATIO) { dia_pressure cuff[i]; /* 持续更新取最后一个过线点 */ } }参数BP_SYS_RATIO用0.50BP_DIA_RATIO用0.78放气速度控制在每秒3~5mmHg。放气太快每心跳只采到两三个点包络不连续导致血压值跳变太慢则患者不舒服而且手臂淤血影响下一轮测量。把这些参数放到一个头文件里调试时不用改逻辑代码。3.4 数据处理中的几个典型错误很多人把原始ADC值直接当压力值算血压这是不对的。传感器输出的电压与压力成线性关系但每个传感器零位偏移不同必须在初始化时测量一次“零压值”然后每个采样点减去零位再乘以比例系数。mmHg (adc_value - zero_offset) * 0.018这样的系数要标定。我一般用5点线性标定0、50、100、150、200mmHg记录ADC值再最小二乘拟合出斜率和截距。资料包里如果只给你一个示例系数竞速复现时可以先抄但实际演示前一定要做一次两点重采样。时钟和定时器配合也要注意血压包络峰值出现的时间点要和袖带压力值对齐。如果ADC和压力采样不同步比如ADC用的是TIM2触发而压力值在另一个定时器中断里读取那么静态压力会滞后于脉搏波包络结果就是MAP偏高或偏低。统一用同一组DMA数据里的“均值”和“差分”来算不要分两个外设采集。4. 用USART把测量结果送出去用PWRBKP保存校准参数4.1 串口调试协议怎么定自己玩可以直接打印字符串但做课设或竞赛时上位机有解析程序建议定义固定帧字节偏移内容说明0帧头0xAA固定1功能码0x010x01表示血压数据2收缩压单位mmHg3舒张压单位mmHg4心率单位bpm5校验和前5字节累加取低8位发送代码void BP_SendResult(uint8_t sys, uint8_t dia, uint8_t hr) { uint8_t frame[6]; uint8_t sum 0; frame[0] 0xAA; frame[1] 0x01; frame[2] sys; frame[3] dia; frame[4] hr; for (uint8_t i 0; i 5; i) { sum frame[i]; } frame[5] sum; for (uint8_t i 0; i 6; i) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); USART_SendData(USART1, frame[i]); } }为什么用累加和而不是CRC因为血压数据里没有不可靠场景后面有主机做解析累加和够用代码也不复杂。USART_FLAG_TXE表示发送数据寄存器为空这个判断比直接延时可靠。如果加了RTOS或中断发送前关中断会减少冲突但串口波特率115200一帧6字节不到0.7ms裸机不用太担心。4.2 掉电保存与备份寄存器电子血压计每次测量完会自动关机。如果校准参数放Flash每次写Flash都会消耗擦写寿命频繁校准不值当。用BKP寄存器更合适。上一章提到过PWRBKP这里给完整封装void BKP_SaveCalib(int16_t zero_offset, float pressure_slope) { PWR_BackupAccessCmd(ENABLE); BKP_WriteBackupRegister(BKP_DR1, (uint16_t)zero_offset); /* 把浮点斜率乘100存储读取时再除100 */ BKP_WriteBackupRegister(BKP_DR2, (uint16_t)(pressure_slope * 100.0f)); PWR_BackupAccessCmd(DISABLE); } void BKP_LoadCalib(int16_t *zero_offset, float *pressure_slope) { if (BKP_ReadBackupRegister(BKP_DR1) ! 0xFFFF) { *zero_offset (int16_t)BKP_ReadBackupRegister(BKP_DR1); *pressure_slope (float)BKP_ReadBackupRegister(BKP_DR2) / 100.0f; } }BKP寄存器的数量有限STM32F103有42个16位寄存器存个零位和斜率足够。注意BKP只有VBAT或VDD供电时才有效如果电池完全拆掉数据会丢失。工程里应该加一个标志位上电时读BKP_DR1如果是0xFFFF就当首次使用强制重新校准。4.3 FSMC显示缓冲和波形绘制如果你是接TFT屏FSMC的另一个好处是“屏显不影响采样”。把DMA采集的波形数组放到FSMC外设SRAM区域之外数据总线完全独立。画脉搏波时只需要把smooth_buf和pulse_buf映射到一个坐标数组然后用FSMC写命令和数据寄存器LCD的DCLK再快也抢不到CPU的ADC采样。这个项目里FSMC相关的源码能直接看到怎么写Bank1地址#define LCD_REG (*(volatile uint16_t *)0x60000000) #define LCD_RAM (*(volatile uint16_t *)0x60020000)0x60000000对应FSMC Bank1 NE1片选A0地址线作为命令/数据区分所以数据区偏移0x20000A01。如果你的屏是8位数据模式宏定义要改成uint8_t否则高字节会读到0xFF颜色就会错。这个地址映射关系是裸机改屏最常见的坑试用FSMC前先写一个“全屏刷色”测试函数。4.4 上位机联调时的注意点很多人在串口助手看到乱码第一反应是改波特率。事实上STM32标准库的SystemInit已经把时钟配到72MHz但如果外部晶振不是8MHz而是12MHzUSART的波特率寄存器算出来的实际波特率就会偏。检查方法很简单用示波器看TX引脚测一个字符位宽没有示波器的话把波特率改成4800看能不能出可读字符。如果还是不行的把stm32f10x.h里的HSE_VALUE改成12MHz或者干脆换回8MHz晶振。5. 编译下载与调参从Keil5到ST-LINK的最后一公里5.1 工程配置与启动文件选择用Keil5打开工程后第一件事是确认芯片型号。当前资料包大概率是STM32F103C8T6或RCT6区别只在Flash和RAM。标准库里启动文件startup_stm32f10x_md.s对应中容量如果你在Device里选了HD系列但启动文件还是md程序跑到某个外设就不动了。可以在魔术棒里把宏定义改成STM32F10X_MD或STM32F10X_HD必须和实际型号一致。芯片包安装不完整的现象是工程打开后出现一堆“Unknown”外设定义解决办法是打开Pack Installer补装。5.2 ST-LINK Utility烧录要点使用ST-LINK Utility烧录时如果总是提示“Cannot connect to target”先看BOOT0是不是拉低了。BOOT00从主Flash启动才能正常下载BOOT01会进入系统存储器ST-LINK也能连上但烧完不复位运行。下载完之后按一下复位键不要用调试器的硬件复位代替。另一个常见问题是下载器驱动版本太旧无法识别新芯片更新驱动后会自动恢复。5.3 晶振电容和ADC校准的现场调参技巧最近被问得最多的两个现象RTC不走和血压值整体偏高。RTC不走基本都是32768Hz晶振的负载电容不对我这里的经验是外壳电容6~8pF不要用15pF。用示波器探针点到OSC32_IN脚会造成停振最好用频率计或观察秒中断。血压值偏高则优先检查ADC采集到零位是否漂移在代码里把零位采集放到上电后1秒等传感器稳定再采。还有一个技巧STM32的ADC自带自校准功能初始化时执行ADC_StartCalibration比软件上反复调整偏移快得多。最后如果你把泵和阀的控制频率调快气泵噪声会通过电源串进ADC我一般会在电机驱动引脚和芯片VDD之间加一个100nF去耦电容这不是玄学。本文还有配套的精品资源点击获取
返回列表