1. 为什么我要用STM32加DDS做一台信号发生器手头没有信号发生器调试音频电路、滤波器或者传感器激励的时候只能拿手机放个正弦波凑合频率不准、幅度不可调稍微讲究一点的场景就完全不够用。市面上的任意波形发生器能到几MHz带宽、带调制功能的价格基本四位数起步。而一块STM32最小系统板加一片DAC成本不到五十块做出来的东西在音频到几十kHz这个区间里精度和稳定性完全够用。这就是我动手做这台DDS信号发生器的直接动机。DDS全称Direct Digital Synthesis直接数字频率合成。它的核心思路很朴素把一张正弦表存在存储器里用一个相位累加器按固定步长去“读”这张表读得快波形频率就高读得慢频率就低。相比传统的模拟振荡器比如文氏电桥、LC振荡DDS的频率切换是瞬时的、相位是连续的、分辨率可以做到毫赫兹级别而且频率稳定度完全取决于晶振不受温度和元件老化影响。这套机制在通信、雷达、测试仪器里已经用了几十年现在把它塞进一颗几块钱的MCU里完全是可行的。这篇文章面向的是有一定STM32基础、想动手做一个完整项目的朋友。不管你是刚学完GPIO和定时器想找个练手项目还是正在做毕业设计需要一个能写进论文的完整方案又或者你只是单纯想搞明白DDS到底是怎么算出来的这篇内容都能给你一条从原理到代码的完整路径。我会把相位累加器的位宽选择、频率控制字的计算、DAC的配置细节、定时器触发链路的搭建、以及实际调试中踩过的坑全部摊开讲清楚。代码部分基于STM32F103和STM32H7两个平台给出前者适合入门后者适合追求更高输出频率和精度的场景。需要提前说明的是DDS的输出质量高度依赖DAC的性能和重建滤波器的设计。STM32F103自带的DAC是12位、最高1MSPS左右输出正弦波在10kHz以内THD总谐波失真表现尚可超过20kHz后阶梯效应会明显恶化。如果你需要更高质量的输出要么外挂专用DAC芯片比如AD9767、AD9744要么换用STM32H7系列配合外部高速DAC。这个取舍我会在后面的章节里详细展开。2. DDS核心原理拆解与关键参数计算2.1 相位累加器到底是怎么“合成”频率的DDS的数学本质其实就是一个溢出计数器。假设相位累加器是N位系统时钟频率是f_clk频率控制字是FTWFrequency Tuning Word那么输出频率f_out的公式是f_out (FTW × f_clk) / 2^N这个公式的推导过程值得说清楚因为理解了它后面所有参数选择就都有依据了。相位累加器每个时钟周期累加一次FTW累加到2^N就会溢出归零完成一个完整的周期。所以输出一个完整正弦波需要的时钟周期数是2^N / FTW取倒数再乘以时钟频率就得到了上面的公式。举个具体例子。假设N32f_clk1MHz我想要输出1kHz的正弦波那么FTW (f_out × 2^N) / f_clk (1000 × 4294967296) / 1000000 4294967.296取整后FTW4294967实际输出频率为4294967 × 1000000 / 4294967296 ≈ 999.9998Hz误差不到0.0002Hz。这就是DDS频率分辨率极高的原因——32位累加器在1MHz时钟下的频率分辨率是f_clk/2^N ≈ 0.000233Hz也就是0.233mHz。但这里有个关键问题相位累加器的输出不能直接拿去查表因为32位的地址空间太大了。实际做法是取累加器的高位比如高12位作为正弦表的索引地址。这就引入了相位截断误差也是DDS输出杂散的主要来源之一。截断位数越多杂散越大但表越小。对于12位DAC正弦表取4096点、用累加器高12位查表是一个比较平衡的选择。2.2 频率控制字的计算与精度取舍在实际代码里FTW的计算不能直接用浮点数每次算那样太慢。通常的做法是预先算好一个基准然后用整数运算。比如系统时钟1MHz、累加器32位的情况下每Hz对应的FTW值约为4294.967可以近似为4295。但这样会引入累积误差更好的做法是用64位整数做中间运算// 计算频率控制字freq单位Hzclk单位Hz uint32_t DDS_CalcFTW(uint32_t freq, uint32_t clk) { uint64_t temp (uint64_t)freq 32; // freq * 2^32 return (uint32_t)(temp / clk); }这里用uint64_t做中间变量是关键。如果直接用uint32_tfreq 32会溢出。这个细节我在第一次写的时候踩过坑输出频率总是莫名其妙偏低查了半天才发现是整数溢出。关于累加器位宽的选择我整理了一个对比表累加器位宽频率分辨率1MHz时钟资源消耗适用场景24位0.0596 Hz低对分辨率要求不高的场合32位0.000233 Hz中通用场景推荐48位3.55e-9 Hz高需要极低频率或极高精度的场合对于绝大多数应用32位是甜点。48位在STM32F103上做64位除法会比较吃力除非用H7系列或者有硬件除法器。2.3 正弦表的生成与存储优化正弦表的质量直接决定输出波形的纯度。生成方式有两种一种是运行时用sinf()函数算另一种是预先在PC上算好存成数组。我强烈推荐后者因为sinf()在MCU上执行一次要几百个周期4096个点全算一遍要好几毫秒而且占用Flash空间反而更大代码比数据大。表的点数选择也有讲究。理论上点数越多量化噪声越小但受限于DAC分辨率和查表速度。对于12位DAC4096点表刚好匹配每个点对应一个DAC码值。如果表点数少于DAC分辨率会出现明显的阶梯多于DAC分辨率则没有额外收益。生成正弦表的Python脚本很简单import math N 4096 table [] for i in range(N): val int(2047 * math.sin(2 * math.pi * i / N) 2048) table.append(val) # 输出为C数组格式 print(const uint16_t sin_table[%d] { % N) for i in range(0, N, 16): print(,.join(str(v) for v in table[i:i16]) ,) print(};)这里加2048是因为DAC输出范围是0到4095正弦波要偏置到中间。2047是幅度留一点余量避免削顶。如果你用的是外部DAC比如AD9767输出是双极性电流就不需要偏置。注意正弦表一定要用const修饰并放在Flash里不要放RAM。4096个uint16_t占8KBSTM32F103C8T6只有20KB RAM放RAM里太浪费了。3. STM32平台上的DDS实现方案3.1 硬件选型F103还是H7STM32F103C8T6是最经典的入门选择72MHz主频、自带12位DAC、价格便宜。但它的DAC最高转换速率受限于APB1时钟实际能稳定工作的更新率在1MSPS左右。这意味着输出正弦波的理论最高频率按每周期至少10个点算大约100kHz但实际因为阶梯效应和重建滤波器限制实用上限在20kHz到50kHz之间。STM32H7系列就完全是另一个级别了。480MHz主频、DAC更新率可以到3.6MSPS以上配合DMAMUX双缓冲和DDS技术可以做到几百kHz的高精度输出。H7还有硬件三角函数加速器CORDIC可以直接算sin/cos省掉查表。但H7的配置复杂度也高得多时钟树、DMA、DMAMUX都要仔细配。我的建议是如果你刚入门先用F103把DDS的原理跑通输出1kHz正弦波用示波器看波形、调频率、调幅度。等这套流程完全理解了再上H7做高性能版本。不要一上来就啃H7很容易在时钟配置上卡住。3.2 定时器触发DAC的链路搭建DDS的核心是“按固定速率更新DAC输出”。这个“固定速率”由定时器提供。STM32的DAC支持多种触发源包括TIM2、TIM4、TIM5、TIM6、TIM7、EXTI等。我一般用TIM6因为它是基本定时器没有PWM输出功能专门用来做DAC触发最合适不会和其他外设抢引脚。配置链路是这样的TIM6按设定频率产生更新事件UEV这个事件通过内部触发线连接到DAC的触发输入DAC在收到触发后启动一次转换转换完成后产生DMA请求DMA把下一个采样值从缓冲区搬到DAC数据寄存器。整个链路是硬件自动完成的CPU只需要在DMA中断里更新缓冲区指针或者计算下一个周期的数据。TIM6的配置关键参数void TIM6_Config(uint32_t sample_rate) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); TIM_TimeBaseInitTypeDef TIM_InitStruct; // 72MHz / sample_rate - 1 TIM_InitStruct.TIM_Prescaler 0; TIM_InitStruct.TIM_Period (72000000 / sample_rate) - 1; TIM_InitStruct.TIM_CounterMode TIM_CounterMode_Up; TIM_InitStruct.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInit(TIM6, TIM_InitStruct); // 使能更新事件作为触发输出 TIM_SelectOutputTrigger(TIM6, TIM_TRGOSource_Update); TIM_Cmd(TIM6, ENABLE); }这里TIM_Period的计算要注意72MHz是TIM6的时钟源APB1定时器时钟如果系统时钟配置不同这个值要相应调整。另外TIM_Prescaler设为0意味着不分频定时器每个时钟周期计数一次。如果sample_rate很低比如1kHz那Period就是71999在16位范围内没问题。但如果sample_rate低于约1.1kHzPeriod会超过65535就必须用预分频器了。3.3 DMA双缓冲机制与数据流管理DMA双缓冲是DDS实现里最精妙的部分。它的作用是当DMA正在搬运缓冲区A的数据时CPU可以往缓冲区B里填下一批数据搬完A自动切到B如此循环。这样CPU和DMA并行工作不会出现数据断流。STM32F103的DMA1通道3对应DAC通道1的TX请求。配置要点void DAC_DMA_Config(uint16_t *buf, uint32_t len) { DMA_InitTypeDef DMA_InitStruct; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_DeInit(DMA1_Channel3); DMA_InitStruct.DMA_PeripheralBaseAddr (uint32_t)(DAC-DHR12R1); DMA_InitStruct.DMA_MemoryBaseAddr (uint32_t)buf; DMA_InitStruct.DMA_DIR DMA_DIR_PeripheralDST; DMA_InitStruct.DMA_BufferSize len; DMA_InitStruct.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStruct.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStruct.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; DMA_InitStruct.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; DMA_InitStruct.DMA_Mode DMA_Mode_Circular; DMA_InitStruct.DMA_Priority DMA_Priority_High; DMA_InitStruct.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_Channel3, DMA_InitStruct); DMA_Cmd(DMA1_Channel3, ENABLE); }注意DMA_PeripheralBaseAddr指向的是DAC-DHR12R1也就是DAC通道1的12位右对齐数据保持寄存器。这个地址在STM32F103参考手册里有明确说明。如果你用的是DAC通道2地址要改成DHR12R2DMA通道也要相应调整。DMA_Mode_Circular是关键它让DMA在搬完整个缓冲区后自动回到开头重新开始不需要CPU干预。但这也意味着缓冲区里的数据是固定的输出频率就固定了。如果要动态改变频率有两种方案一是停止DMA重新填缓冲区再启动二是用双缓冲模式在中断里切换缓冲区并填入新数据。前者简单但有断点后者复杂但连续。对于需要频率扫描或者调制的场景我推荐用双缓冲。STM32F103的DMA支持双缓冲模式DMA_Mode_Circular配合DMA_MemoryBaseAddr和DMA_MemoryBaseAddr的切换但配置起来比较绕。更简单的做法是用两个独立的DMA通道交替工作或者用定时器中断在每次周期结束时更新频率控制字。4. 完整代码实现与关键环节剖析4.1 主循环与DDS数据生成逻辑整个DDS信号发生器的软件架构可以分成三层底层是定时器和DAC的硬件配置中间层是DDS相位累加器和查表逻辑上层是用户交互按键调频、串口命令、LCD显示等。这里重点讲中间层因为这是DDS的灵魂。核心的DDS生成函数长这样#define SIN_TABLE_SIZE 4096 #define PHASE_ACC_BITS 32 #define PHASE_ACC_MASK 0xFFFFFFFF extern const uint16_t sin_table[SIN_TABLE_SIZE]; volatile uint32_t phase_acc 0; volatile uint32_t ftw 0; // 在DMA传输完成中断或定时器中断中调用 void DDS_GenerateBuffer(uint16_t *buf, uint32_t len) { uint32_t i; for (i 0; i len; i) { // 取相位累加器的高12位作为查表索引 uint16_t index (phase_acc (PHASE_ACC_BITS - 12)) 0x0FFF; buf[i] sin_table[index]; phase_acc ftw; } }这段代码有几个细节值得展开。第一phase_acc (PHASE_ACC_BITS - 12)是把32位累加器右移20位得到高12位。为什么是12位因为正弦表是4096点需要12位地址。第二 0x0FFF是保险操作确保索引不会越界虽然理论上右移20位后高12位最大就是0xFFF。第三phase_acc ftw是32位加法溢出自动回绕这正是我们想要的——溢出对应一个完整周期的结束。ftw的计算前面已经讲过用64位中间变量。假设系统采样率是1MHz要输出1kHzftw DDS_CalcFTW(1000, 1000000); // 结果约为4294967验证一下4294967 × 1000000 / 4294967296 999.9998Hz误差可以忽略。4.2 频率与幅度动态调整的实现频率调整相对简单重新计算ftw即可。但要注意如果在DMA传输过程中直接改ftw相位累加器的连续性会被破坏输出会出现相位跳变。对于大多数应用这无所谓但如果你在做频率扫描或者FSK调制就需要在相位累加器归零的时刻更新ftw。幅度调整有两种方式数字方式和模拟方式。数字方式是在查表后乘以一个系数buf[i] (uint16_t)((uint32_t)sin_table[index] * amp_factor 12);这里amp_factor是0到4095的整数右移12位相当于除以4096。这种方式简单但会损失分辨率而且乘法在循环里执行会拖慢生成速度。更好的做法是预先算好不同幅度下的正弦表或者用DAC的参考电压引脚做模拟调幅。模拟方式是通过改变DAC的参考电压来实现。STM32F103的DAC参考电压是VDDA通常接3.3V。如果你在VREF引脚上接一个可调电压源就能直接调幅度。但大多数最小系统板VREF和VDDA是连在一起的没法单独调。所以实际项目中数字调幅更常用。4.3 波形扩展方波、三角波、锯齿波DDS的框架不只能生成正弦波只要改查表逻辑就能输出任意波形。方波最简单直接根据相位累加器的最高位判断buf[i] (phase_acc 0x80000000) ? 4095 : 0;三角波可以用相位累加器的高位做线性映射uint16_t phase_high phase_acc 20; // 12位 if (phase_high 2048) { buf[i] phase_high * 2; } else { buf[i] (4095 - phase_high) * 2; }锯齿波更简单buf[i] phase_acc 20;这些波形生成方式都不需要查表直接对相位累加器做位运算速度极快。我在实际项目里做了一个波形选择菜单用按键切换正弦、方波、三角、锯齿四种波形切换时重新填充DMA缓冲区即可。实操心得切换波形时一定要先停止DMA和定时器填完缓冲区再重新启动。否则会出现半个周期的杂波听起来像“咔哒”声。5. 调试过程中踩过的坑与排查技巧5.1 输出波形频率不对先查时钟树我第一次调DDS的时候设置输出1kHz示波器上显示的是1.2kHz左右。查了半天代码没发现问题最后用示波器测TIM6的更新事件输出发现频率是1.2MHz而不是1MHz。原因是系统时钟配置成了72MHz但APB1预分频器设的是2所以TIM6的时钟是36MHz而不是72MHz。我按72MHz算的Period值实际时钟是36MHz输出频率就偏了。这个坑的教训是永远不要假设定时器时钟等于系统时钟。在STM32F103里APB1的最大时钟是36MHz如果APB1预分频系数不为1定时器时钟会是APB1时钟的2倍。具体来说如果APB1预分频是2TIM2/3/4/5/6/7的时钟就是72MHz如果APB1预分频是1定时器时钟就是36MHz。这个规则在参考手册的时钟树章节有详细说明但很容易被忽略。排查方法很简单在TIM6的更新中断里翻转一个GPIO用示波器测GPIO频率和预期值对比。如果不对就去查RCC配置。5.2 波形失真严重检查DAC缓冲和重建滤波器DAC输出的是阶梯波不是平滑的正弦波。如果你直接用示波器看DAC引脚会看到明显的阶梯。这是正常的需要加一个低通滤波器把高频谐波滤掉。滤波器的截止频率应该设在输出频率的2到3倍但不要超过采样率的一半奈奎斯特频率。我一开始没加滤波器用耳机听DAC输出听到的是刺耳的高频噪声叠加在正弦波上。加了一个简单的RC低通滤波器R1kΩC10nF截止频率约16kHz后声音立刻干净了。但RC滤波器的滚降特性只有-20dB/十倍频对高频谐波的抑制不够。如果对波形纯度要求高建议用二阶或四阶有源滤波器比如巴特沃斯或切比雪夫拓扑。另外STM32F103的DAC输出缓冲器可以开启或关闭。开启后输出驱动能力更强但会引入额外的失调和噪声。关闭后输出阻抗高需要外部运放做缓冲。我在实际使用中如果后级有运放就关闭DAC缓冲如果直接驱动负载就开启。5.3 DMA传输断流缓冲区大小和优先级设置DMA传输断流的表现是输出波形每隔一段时间出现一个“缺口”或者幅度跳变。原因通常是DMA缓冲区太小或者DMA优先级被其他外设抢占。STM32F103的DMA1通道3默认优先级是Medium如果同时有ADC、SPI、UART在用DMA可能会被抢占。解决方法是在DMA_InitStruct.DMA_Priority里设为DMA_Priority_VeryHigh。另外缓冲区大小建议至少是正弦表点数的两倍比如8192个uint16_t这样DMA有足够的时间在CPU填数据之前完成搬运。还有一个隐蔽的坑如果DMA缓冲区定义在栈上局部数组函数返回后内存会被回收DMA访问的就是野指针。缓冲区必须定义成全局数组或者用malloc分配在堆上。我见过有人把缓冲区定义在main函数里结果DMA传输到一半数据就乱了查了一整天才发现是栈溢出。5.4 常见问题速查表现象可能原因排查方法解决方案输出频率偏差大定时器时钟配置错误测TIM更新事件频率检查RCC和APB预分频波形有阶梯未加重建滤波器示波器看DAC引脚加RC或LC低通滤波器波形有缺口DMA断流看DMA传输完成标志增大缓冲区、提高优先级频率切换有杂音相位不连续示波器看切换瞬间在相位归零时更新FTW输出幅度小DAC缓冲未开测DAC输出阻抗开启DAC输出缓冲高频段失真采样率不足计算奈奎斯特频率提高采样率或降低输出频率6. 从F103到H7性能升级的路径与取舍6.1 H7的DMAMUX双缓冲与更高采样率STM32H7的DAC性能比F103强太多。以STM32H743为例DAC最高转换速率可达3.6MSPS12位模式配合480MHz主频和DMAMUXDMA请求复用器可以实现非常灵活的数据流控制。DMAMUX的好处是你可以把任意DMA请求路由到任意DMA通道不再像F103那样受固定通道映射的限制。H7上实现DDS的代码结构和F103类似但有几个关键差异。第一H7的DAC数据寄存器是DAC-DHR12R1但DMA请求要通过DMAMUX配置。第二H7的定时器时钟更高TIM6可以跑到240MHz采样率可以做到更高。第三H7有CORDIC协处理器可以直接算sin值不需要查表省掉8KB的Flash空间。不过H7的配置复杂度也高得多。时钟树有多个PLL、多个域DMA有多个控制器和DMAMUXCache和MPU也要考虑。如果你从F103直接跳到H7建议先用CubeMX生成一个DACDMATIM的工程框架再往里填DDS逻辑不要从零手写寄存器配置。6.2 外部高速DAC的选型与接口如果你需要输出1MHz以上的正弦波STM32自带的DAC就不够用了。这时候需要外挂专用DAC芯片。常见的选择有型号分辨率最高更新率接口适用场景AD976714位125MSPS并行高速任意波形AD974414位210MSPS并行通信、雷达DAC855216位200kSPSSPI高精度低速MCP492212位500kSPSSPI低成本入门AD9767是双通道14位DAC最高125MSPS用FPGA或者高速MCU驱动可以输出几十MHz的波形。但它的接口是并行14位需要14根数据线加时钟和控制线STM32F103的GPIO不够用至少要F407以上或者用FSMC总线。如果你用H7的FMC灵活内存控制器接AD9767可以做到很高的更新率。对于大多数STM32项目我建议先用自带DAC把DDS原理跑通确认需求后再决定是否外挂DAC。很多时候20kHz以内的信号用自带DAC加个好滤波器效果已经足够好了。6.3 频率分辨率与杂散性能的平衡DDS的杂散主要来自三个地方相位截断误差、DAC量化噪声、以及DAC的非线性。相位截断误差前面讲过增加累加器位宽或者增加正弦表点数可以改善。DAC量化噪声由分辨率决定12位DAC的理论SNR约74dB实际因为非线性会更差。如果你对杂散要求很高比如做射频信号源那DDS的杂散可能不够低。这时候可以考虑用DDS专用芯片比如AD9850、AD9833它们内部集成了DAC和滤波器杂散性能比MCU加DAC好得多。但专用芯片的灵活性不如MCU方案不能随意改波形、加调制。我在实际项目里的体会是STM32加DAC的DDS方案胜在灵活和低成本适合音频到几十kHz的测试场景。如果追求高性能要么用专用DDS芯片要么用FPGA加高速DAC。不要指望一颗F103加个RC滤波器就能做出实验室级信号源那不现实。7. 项目扩展与实用建议7.1 加个LCD和旋转编码器做交互一个光秃秃的DDS板子只能靠串口调频率用起来很不方便。加一个0.96寸OLED和旋转编码器就能做成一个独立的小型信号发生器。OLED显示当前频率、波形类型、幅度旋转编码器调频率按键切换波形。这套交互逻辑不复杂但能让项目的实用性提升一个档次。OLED用I2C接口STM32F103的I2C1接PB6/PB7。旋转编码器用两个GPIO做正交解码或者用定时器的编码器模式。我一般用定时器的编码器模式硬件自动计数CPU只需要读计数值。按键用外部中断消抖用定时器做20ms延时。7.2 串口命令控制与上位机联动如果你不想加屏幕用串口命令控制也很方便。定义一个简单的协议比如FREQ 1000设置频率为1kHzWAVE SIN设置正弦波AMP 50设置幅度50%。上位机可以用Python写个简单的GUI通过串口发送命令。这样你可以在PC上做频率扫描、波形切换甚至做自动测试。串口命令解析的代码很简单用strtok分割字符串然后atoi转数字。但要注意串口接收中断里不要做太重的处理把接收到的字符存到缓冲区在主循环里解析。7.3 实际使用中的几个小技巧第一个技巧输出端加一个射极跟随器或者运放缓冲。DAC的输出阻抗比较高直接接负载会导致幅度下降和失真。用一个OPA2340或者NE5532做电压跟随器输出阻抗降到毫欧级带负载能力大大增强。第二个技巧电源去耦要做好。DAC的参考电压对电源噪声非常敏感VDDA引脚旁边一定要放100nF和10uF的电容越近越好。如果电源纹波大输出波形上会叠加噪声用示波器看很明显。第三个技巧正弦表可以用对称性压缩。正弦波有对称性只需要存1/4周期的数据查表时根据象限做符号和地址变换。这样4096点的表只需要1024个uint16_t省3/4的Flash空间。对于Flash紧张的F103C8T6来说这个优化很有价值。第四个技巧频率切换时做淡入淡出。直接切换频率会有“咔哒”声如果在切换时把幅度逐渐降到零再升回来听感会好很多。实现方法是在DMA中断里逐步调整幅度系数每次变化一点点几十毫秒内完成过渡。这些技巧都是我在实际调试中一点点积累的有些是看书学来的有些是踩坑踩出来的。DDS这个技术本身不复杂但要把输出质量做好细节非常多。从相位累加器的位宽选择到DAC的配置到滤波器的设计到PCB布局每一个环节都会影响最终结果。希望这篇内容能帮你少走一些弯路顺利做出自己的信号发生器。
企业数字化 ERP 产品动态
相关推荐
CH379M双SDMMC读卡芯片深度解析:集成电源与USB 3.2设计实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 6:17:16
STM32驱动SG90舵机实战:PWM原理、硬件隔离与HAL库避坑指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 6:17:10
不会代码也能搞?2026最新我想做个旅游网站怎么做 不会代码也能搞?2026最新我想做个旅游网站怎么做 很多人觉得搞个旅游网站得会写代码,其实真不用。哪怕你是纯小白,只要按对路子,也能在2026年这个节点把站搭起来。别被那些复杂的后端逻辑吓退,现在的工具链已经足够友好,让你避开最头疼的开发环… · 2026/9/27 6:17:04
mykernel 实验指导(操作系统是如何工作的) 姓名: 李令琪 原创作品转载请注明出处 课程:《Linux 内核分析》MOOC 课程地址: http://mooc.study.163.com/course/USTC-1000029000 1. 实验环境与步骤
实验使用实验楼提供的 Linux 虚拟机,内核源码版本为 Linux 3.9.4。首先进入… · 2026/9/27 6:55:34
Jetpack - Hilt 官方文章
参考文章
一、概念 依赖管理不仅可以很好地解决对象繁琐的初始化逻辑,还可以很好的实施控制反转的编码思想。 类中使用的某个对象不是在这个类中实例化的(如汽车类内部创建轮子对象),而是通过外部注入(在外… · 2026/9/27 6:55:21
Manim 中文显示问题报错解决办法 使用Manim直接制作这种动画视频的话是不支持中文的,需要进行一些修改,效果如图所示。 但是修改的方案都太坑,经过自己不断的研究找到了一个比较简单实用的方法。 文章目录 源文件修改 代码修改 源文件修改
实用everuthing找到ctex_template.tex文件。
我这里的路径是D:\M… · 2026/9/27 6:55:21
佛山网站建设服务器避坑指南:3个实战案例教你省下5万块 佛山网站建设服务器避坑指南:3个实战案例教你省下5万块 找佛山网站建设服务,最怕的就是被忽悠买高价服务器。我见过太多老板,花两万块买了个配置过剩的机器,结果网站打开还是慢得让人想摔手机。别急着掏钱,先看这三个 实战案例… · 2026/9/27 6:54:51
AI专业大学生学习之路的开始 我的第一篇CSDN博客 人工智能专业编程学习之路
大家好!这是我入驻CSDN的第一篇原创博客,也是我正式记录自己编程学习、AI探索之路的开始。作为一名人工智能专业的在校大学生,从此我会在这里持续分享学习笔记、踩坑经验、项目实战心得&… · 2026/9/27 6:54:39
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现 简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01
汕头网站建设制作厂家避坑指南:5大注意事项救急 汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习 简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现 简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01
汕头网站建设制作厂家避坑指南:5大注意事项救急 汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习 简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01