大概6-7年前,在网上看到过一篇用STM32F1的DMA控制GPIO输出高速数字波形的帖子。觉得很有意思,就自己试了试:控制GPIO输出波形翻转的速度最高只能达到3-4MHz,且容易受到STM32F1的APB2总线其他设备读写的影响,输出的方波不稳定。由于问题较多,对高速实时性提升不大,感觉基本不实用,就没有再进一步研究。

前几天在研究STM32F4和STM32F1的区别时,发现STM32F4进行了两项升级:1、把GPIO连接从APB2总线改到AHB总线上,极大的改善了GPIO输入、输出的实时性和时序控制能力。2、增加DMA stream的概念(参见拙作https://www.cnblogs.com/helesheng/p/18167026),使得DMA传输请求的来源更明确。

DMA控制器的上述升级,使得STM32F1上比较“鸡肋”的DMA高速并行同步传输能力得到了较大提升,具备了一定的实用性。我尝试用STM32F407VE的DMA2在TIM1的触发配合下,实现了对并行接口的流水线型ADC的控制和读取。理论读取速度可达40MSPS以上,实测读取速度可超过AD9200E的理论上限——20MSPS。设计思路、程序和电路如下,供大家参考和指正。

以下原创内容欢迎网友转载,但请注明出处:https://www.cnblogs.com/helesheng

一、DMA控制高速同步并行数据传输的原理分析

本文基于DMA的GPIO高速并行读写程序,主要利用了STM32F4系列DMA的以下关键特性:

1)STM32的DMA传输可分为外设和内存之间,以及内存之间两种传输模式。这两种模式的最重要区别是:外设和内存之前的DMA传输必须由其他外部请求信号触发;而内存和内存之间的DMA传输则不会等待其他触发,它会在上一次传输进行完之后自动进行下一次传输。 在STM32的嵌入式系统中,除去少数只追求传输带宽的纯数据传输任务以外,大部分与硬件相关的DMA传输,还要要求在恰当的时刻完成传输。以本文要完成的高速A/D数据读取为例,读写数据的时刻必须收到采样间隔的严格控制。也就要求由定时器TIM来实现采样间隔的定时,即由TIM产生DMA传输的请求信号,而数据则通过DMA在GPIO数据寄存器和内存之间进行传输

2)外设和内存之间的DMA传输的请求信号,可以是本次DMA传输的源或目的的外设,也可以是其他外设。STM32F4的DMA传输请求信号由DMA的流(stream)和请求信号(或称通道channel)共同决定;但传输源和目的外设种类却由源和目的地址决定。二者不能混为一谈。

3)STM32F4的两个DMA控制器的两个端口被指定为特定的数据源。具体连接关系如下图所示。以GPIO为例,STM32F4把它连接到AHB1总线上,由下图可知连接到AHB1外设的只有蓝色和黑色两种颜色的线,也就意味着,左侧的两个DMA控制器中只有连接了蓝色和黑色的DMA2可以实现与GPIO之间的DMA传输。

图1 STM32F4系列两个DMA控制器端口(源和目标)连接的外设/存储器种类

通过查询STM32F4的数据手册中的DMA请求信号表可知,可用于请求DMA2的定时器只有TIM1和TIM8两个高级定时器。我选择了TIM1的更新事件TIM1_UP来请求DMA传输。

图2 STMF4系列DMA2控制器的外设传输请求表

另外,控制高速ADC还要求单次DMA传输耗时要小于采样间隔,而STM32F4把GPIO连接到AHB1总线的意义也就在于此——相比之前将GPIO连接到APB2总线的STM32F1系列,STM32F4将能够更快速的对GPIO进行读写,从而提高与所控制ADC的数据读取速率。

最后,流水线型ADC还需要一个采样同步时钟;由于数据读取也是在该时钟的同步下进行,自然只能由TIM1时基部分同时产生该时钟。一种合理的解决办法是用TIM1的输出比较(OC)功能电路来产生。这也意味着该时钟只能由TIM1的某个通道(CHx)产生,从而只能在某些管脚上输出,这一点必须在硬件设计时加以注意。

二、高速并行接口ADC读取的程序和电路设计

1、硬件设计

下图是我采用的ADI公司的标称转换速率为20MSPS的流水线型A/D转换器——AD9200E(10bits分辨率)的工作时序图。

图3 AD9200的读取时序

可以发现,数据的更新发生在上升沿后25ns左右。如果输出比较OC电路采用时基计数的前段输出高电平,比较翻转的后输出低电平的模式,就会使得输出PWM信号的上升沿发生在TIM1更新时。而如前所述STM32F4的DMA2数据传输则发生在TIM1更新事件后,这就有由于高速数字电路的竞争与冒险造成读取时序不收敛。

我曾在某论坛看到过有人对AD9226做类似尝试,仅在16MSPS以上时就出现采样点读取错误的问题(https://blog.csdn.net/cusichidouren/article/details/126002742),我猜测就是由于这个原因。合理的解决方案其实也不复杂:对OC电路输出的PWM信号反相,使其在下降沿时触发DMA2传输请求。幸运的是STM32的OC输出支持负逻辑的PWM输出,不需要附加进一步的门电路。具体配置代码请参见软件设计部分。

具体GPIO选择方面,我用了PE0~16号端口来实现对AD9200的控制和读取。其中,PE口中PE11管脚可以配置为TIM1的通道2(CH2),可以作为AD9200的转换时钟(INPUT CLOCK)。而AD9200的10根数据线则用PE0~9负责读取,低位对齐的做法也有利于后续的数据读取和整理。AD9200的钳位控制(CLAMP)、溢出指示(OTR)、低功耗待机(STBY)等管脚则连接到PE口的其他管脚。原理图太简单,这里就不贴出来了,放一张实物图。注意:AD9200的模拟驱动应使用一个高压摆率的宽带运放,我使用了低成本的AD8052。

图4 实验系统实物图

2、软件设计

正如本文前面“原理分析”介绍的,DMA传输的通道、流、数据源/目标、传输请求信号如下图所示。

图5 DMA工作原理示意图

 其中,DMA控制器的代码如下所示:
  DMA_InitTypeDef  DMA_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2时钟使能
DMA_DeInit(DMA2_Stream5); //如果用TIM1更新触发,则使用DMA2_S5_CH6
while (DMA_GetCmdStatus(DMA2_Stream5) != DISABLE){}//等待DMA可配置
/* 配置 DMA Stream */
DMA_InitStructure.DMA_Channel = DMA_Channel_6; //通道选择为6通道
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&(GPIOE->IDR));
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)DST_DATA;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//DMA_DIR_MemoryToMemory;//存储器到外设模式
DMA_InitStructure.DMA_BufferSize = SAMPLE_LEN;//数据传输量
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//外设数据长度:16位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//存储器数据长度:16位
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;// 使用循环模式
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;//高优先级
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//存储器突发单次传输
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外设突发单次传输
DMA_Init(DMA2_Stream5, &DMA_InitStructure);////如果用TIM1更新触发,则使用DMA2_S5_CH6
//开启DMA中断
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=DMA2_Stream5_IRQn; //
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x00; //抢占优先级1
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
DMA_ITConfig(DMA2_Stream5,DMA_IT_TC,ENABLE); //

其中DMA2_Stream5的Channel_6是TIM1更新事件可以请求的DMA流,DST_DATA则是指向DMA缓存转换结果数据的片上SRAM区域的指针。DMA中断则用于在中断服务程序中读取转换结果(当然本程序对读取效率没有要求,因此也没有使用该中断服务程序功能)。

TIM1则需要一方面向外产生AD9200的转换工作时钟,另一方面用自动重装的更新信号触发DMA2的数据传输(如图5所示),因此TIM1的配置代码如下。

 1     TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
2 TIM_OCInitTypeDef TIM_OCInitStructure;
3 RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE); ///使能TIM1时钟
4 TIM_DeInit(TIM1);
5 TIM_TimeBaseInitStructure.TIM_Period = 8-1; //自动重装载值
6 TIM_TimeBaseInitStructure.TIM_Prescaler= 1-1; //定时器分频
7 TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
8 TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
9 TIM_TimeBaseInit(TIM1,&TIM_TimeBaseInitStructure);//初始化TIM1时基
10 ////初始化TMR1的PWM输出,作为同步时钟/////
11 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
12 TIM_OCInitStructure.TIM_OutputState =TIM_OutputState_Enable;// TIM_OutputState_Enable;//正向输出使能
13 TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable;//反向输出禁止
14 //输出极性:TIM输出比较极性低,保证在下降沿之后,读取或写出数据
15 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
16 TIM_OCInitStructure.TIM_Pulse = 0;
17 TIM_OC2Init(TIM1, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM9 OC2
18 TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable); //使能TIM1在CCR2上的预装载寄存器
19 TIM_CCxCmd(TIM1, TIM_Channel_2, TIM_CCx_Enable);//禁止TIM1-CH2 输出 需要单独使用 TIM1-CH2 时 此处因设置为 禁止TIM1-CH2N 输出
20 TIM_ARRPreloadConfig(TIM1,ENABLE);//ARPE使能
21 TIM_Cmd(TIM1, ENABLE); //使能TIM1
22 TIM_CtrlPWMOutputs(TIM1, ENABLE);
23 TIM_SetCompare2(TIM1, 4);
24 //选择DMA触发信号
25 TIM_DMACmd(TIM1, TIM_DMA_Update, ENABLE);//如果用TIM1更新触发,则使用DMA2_S5_CH6

其中,自动重装值8-1决定了AD9200的转换时钟为168/8=21MHz,这略微超过了AD9200手册规定的上线20MHz。主要原因是高速ADC比较贵,我手头有的只有AD9200,希望测试到它的工作极限。实测效果显示AD9200在21MSPS转换速度下还是能稳定工作点,如果用本方案做实际项目,还是要注意这个问题。

输出比较OC寄存器的值被设置为4,以保证输出的AD9200转换时钟占空比为1:1。

相关管脚的配置代码如下:

 1   GPIO_InitTypeDef  GPIO_InitStructure;
2 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB时钟
3 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);//使能GPIOA时钟
4 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);//使能GPIOE时钟
5 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);//使能GPIOC时钟
6 //PWM管脚输出ADC工作时钟
7 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; //GPIOE11
8 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
9 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度100MHz
10 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
11 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;//上拉
12 GPIO_Init(GPIOE,&GPIO_InitStructure);
13 GPIO_PinAFConfig(GPIOE,GPIO_PinSource11,GPIO_AF_TIM1); //GPIOE11复用位定时器1
14 //AD9200的数字输入,通过GPIO来读取
15 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9;
16 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;//普通输入模式
17 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
18 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;//浮空输入
19 GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化GPIO
20 ///PE15作为AD9200待机模式控制端,高电平进入待机模式
21 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
22 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
23 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
24 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
25 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
26 GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化GPIO
27 GPIO_ResetBits(GPIOE,GPIO_Pin_15);//低电平输出ad9200进入正常工作模式
28 //led管脚输出
29 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;//LED0和LED1对应IO口
30 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
31 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
32 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
33 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
34 GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO
35 GPIO_ResetBits(GPIOB,GPIO_Pin_8 | GPIO_Pin_9);//
36 //按键输入管脚
37 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;
38 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;//普通输入模式
39 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
40 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
41 GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化GPIO

其中最关键的是用于读取AD9200转换结果的PE0-9,共10个管脚,它们被配置为普通浮空输入模式,内部的GPIO数据寄存器IDR根据外部输入更新的速率就是AHB1总线的工作时钟84MHz。这也限制了使用这种基于DMA和GPIO读取并行数据的方法的带宽上限。

PE11复用功能中有作为TIM1CH2的功能,将其复用给TIM1:GPIO_PinAFConfig(GPIOE,GPIO_PinSource11,GPIO_AF_TIM1);

主程序的功能时等待按键,并在按键后将DMA的目标SRAM中的内容发送给PC显示,其代码如下所示:

 1 extern unsigned short DST_DATA[SAMPLE_LEN];
2 int main(void)
3 {
4 unsigned short i;
5 delay_init(168); //初始化延时函数
6 Pin_Init(); //初始化LED端口
7 uart_init(115200);//串口初始化配置
8 DMA_Config();//初始化DMA控制器
9 USART_GetFlagStatus(USART1, USART_FLAG_TC); //这句可以防止第一个字节丢失
10 while(1)
11 {
12 if(KEYD == 0)
13 {
14 delay_ms(20);
15 if(KEYD == 0)
16 {
17 DMA_Cmd(DMA2_Stream5, ENABLE); //启动DMA传输
18 delay_ms(5);
19 for(i = 0 ; i < SAMPLE_LEN ; i++)
20 {
21 f2c.float_data = (DST_DATA[i] & 0x03ff) * 2.00 / 1024 ;
22 USART1_Putc(f2c.char_data[0]);
23 USART1_Putc(f2c.char_data[1]);
24 USART1_Putc(f2c.char_data[2]);
25 USART1_Putc(f2c.char_data[3]);
26 //固定内容的帧尾输出
27 USART1_Putc(0x00);
28 USART1_Putc(0x00);
29 USART1_Putc(0x80);
30 USART1_Putc(0x7F);
31 }
32 LED0 = 0;//灭灯表示数据传输完成
33 }
34 while(KEYD == 0);//等待到按键释放
35 delay_ms(10);
36 }
37 delay_ms(1);
38 }
39 }

为了方便的查看DMA读取的数据是否正确,我将数据用UART口发送到PC端开源的VOFA+软件上,通行的VOFA+数据格式为JustFloat。因此首先把数据从10位的二进制数转换为浮点数后,将IEEE754格式的单精度浮点数在分解为4个字节后由函数USART1_Putc();发出。浮点数分解为四个字节的方式是采用联合体(union)f2c来实现。f2c的定义如下:

1 union float2char//浮点数向可发送的字节转换的结构体
2 {
3 unsigned char char_data[4];
4 float float_data;
5 }f2c;  

3、遇到的几个“坑”

尽管STM32F4是非常成熟的MCU产品线,但本文所述的“基于DMA的高速并行GPIO读写”并不是常见的功能,因此在调试过程中我还是遇到并克服了一些问题。个人觉得是芯片本身以及标准外设库的一些小问题造成的,但也可能是由于我才疏学浅、考虑不周的原因。罗列与此,供大家参考和指正。

1)官方提供的标准外设库高速外部晶振频率不匹配问题

我使用了ST官方提供的标准外设库作为开发平台,其中配置的高速外部晶振HSE的频率(HSE_VALUE)为25MHz。而我实际使用的晶振为8MHz,这除了导致UART通信的波特率不准之外,更重要的是还会导致TIM1输出比较OC电路输出的时钟频率不对。在固件库stm32f4xx.h中搜索宏“HSE_VALUE”,将其改为8000000即可解决问题。

2)PA8管脚复用为TIM1_CH1输出比较功能时无输出的问题

我最初进行硬件设计时,曾想用管脚PA8复用的TIM1_CH1功能输出AD9200所需的转换时钟。但折腾了很长时间都无法让PA8管脚输出所需时钟(PWM)信号,在网上搜索后发现问题是STM32芯片的一个“顽疾”(www.openedv.com/posts/list/49738.htm)——只要使能复用在PA8、PA8、PA10等管脚上的USART1功能,就会导致PA8上的TIM1_CH1无法输出PWM信号。修补这个BUG也不困难:我改成使用TIM1_CH2(在PE11管脚上)输出PWM波,就很好的解决了这个问题。

三、测试结果

下图所示的是VOFA+软件显示的采集信号

图6 AD9200采集的信号波形(采样率为21MS,输入正弦信号为1MHz,采样长度为512点)

相关问题分析如下:

四、DMA控制高速并行ADC/DAC的弊端和问题

 1、采样触发信号问题及其解决办法

1) 用STM32这样的MCU代替FPGA来控制高速ADC,最大的问题在于MCU软件的实时性远远赶不上硬件控制的FPGA。例如,前面提供的主程序代码中,用检测按键的方式触发DMA实现采样。显然无论是检测按键的程序的时间精度还是程序调用外设库启动DMA传输的时间精度都远远低于ADC采样的100ns数量级的时间精度。致使采样触发信号的实时性只能满足对“时间平稳信号”分析的需要,无法达到对非平稳信号进行时域分析的需求。

2)另外,从图6中可以发现信号刚开始的一段信号是混乱的,造成混乱的原因有二:

  其一:在高速传输条件下,在传输刚开始的一段时间STM32的DMA控制器无法及时的响应传输请求,从而造成DMA只能在TIM1的采样请求已经发出一段时间后才读取ADC的输出数据,结果自然不正确。

  其二:流水线型ADC的数据传输和采样值之间存在延迟。从图3给出的时序图也可以发现,当前读取的数据是四个时钟之前“潜伏”在ADC的流水线中的,从而造成了缓冲区中开始一段的信号错误。

以上两个原因,都可以通过丢掉缓冲器中开始的一段数据的方法掩盖,但这无疑也是对采样触发信号的实时性的进一步降低。

2、TIM1输出采样时钟抖动问题及其解决办法

下图是用20G采样率的示波器DSOX6004A采集到的TIM1_CH2(PE11)输出的采样时钟信号。

图7 TIM1的PWM功能产生的采样时钟的孔径抖动(触发后100us处)

为了测试采样时钟信号的孔径抖动情况,我将观察窗对准采样触发后100us的地方(触发-采样延迟如图中红色圈内数据所示),可以发现该处时钟上升沿的抖动达到了5ns左右(图中示波器横轴每个为5ns,如图中黄色圈所示)。这表明TIM1的OC模块产生的采样时钟孔径抖动品质较差,大大降低了采样信号的信噪比。

为解决这个问题,可以使用片外模拟锁相环PLL输出的时钟信号作为ADC的采样时钟。至于STM32F407的TIM1_CH2则由输出比较模式(OC)变为输入捕获模式(IC),由外部锁相环产生的时钟信号作为TIM1_CH2的捕获(IC)对象。 下图是我使用单独的模拟锁相环PLL芯片Si5351产生相同的21MHz信号,同样在采样触发后100us的地方观察时钟抖动情况。可以明显的看到锁相环芯片产生的时钟的孔径抖动性能明显由于定时器输出比较模式输出的时钟。

图8 模拟锁相环芯片Si5351产生的采样时钟的孔径抖动(触发后100us处)

3、同步传输速率被限制在10MSPS左右

从本质上讲,DMA是与CPU内核共享片上的总线资源的,当使用DMA高速传输并行数据时必然挤占CPU读取指令和数据的总线时间。如果DMA传输的是SRAM中的数据,某一笔传输由于总线被占用而延期并不会影响传输整体的正确性。但对于ADC和DAC这样的高速数据传输,某一笔数据的延迟就有可能造成采样的错误。

经过测试,我整体的感觉是:即使把DMA传输数据的优先级设为非常高(DMA_Priority_VeryHigh),且在采集期间不执行中断服务(ISR)等可能打断DMA的程序,当把DMA同步传输速率提升到10MSPS以上就很难保证每一笔传输的可靠实时性了。当然在极限情况下,同步传输速率是可以达到40MSPS的,但不建议大家在产品中使用。

4、单端数字信号,抗干扰能力弱于差分数据线

当数字信号在PCB上传输的速率达到10MSPS以上时,STM32中使用的单端3.3V CMOS在很多情况下就有可能出现传输错误,一般的解决方案是使用LVDS等差分传输标准。但STM32F4系列中没有类似硬件配置,导致同步传输速率达到10MSPS以上时系统的抗干扰能力和传输正确率都会有所下降。

用STM32F4的DMA实现高速、实时的同步并行通信——以读取高速ADC为例[原创www.cnblogs.com/helesheng]的更多相关文章

  1. STM32的SPI口的DMA读写[原创www.cnblogs.com/helesheng]

    SPI是我最常用的接口之一,连接管脚仅为4根:在常见的芯片间通信方式中,速度远优于UART.I2C等其他接口.STM32的SPI口的同步时钟最快可到PCLK的二分之一,单个字节或字的通信时间都在us以 ...

  2. 为树莓派添加一个强实时性前端[原创cnblogs.com/helesheng]

    树莓派是最近流行嵌入式平台,其自由的开源特性以及低廉的价格,吸引了来 自全球的大量极客和计算机大咖的关注.来自各大树莓派社区的幕后英雄,无私地在这个开源硬件平台上做了大量的工作,将其打造成了世界上通用 ...

  3. unison + inotify 实现文件实时双向同步部署步骤

    unison + inotify 实现文件实时双向同步部署步骤 一. Unison简介 Unison是Windows.Linux以及其他Unix平台下都可以使用的文件同步工具,它能使两个文件夹(本地或 ...

  4. MySQL和MsSQL实时自动同步---SyncNavigator 数据库同步软件

    需要MySQL数据库支持的狐友们有福了,MySQL和MsSQL实时自动同步---SyncNavigator 数据库同步软件   使用SyncNavigator轻松实现数据库异地同步.断点续传.异构同步 ...

  5. TiDB 作为 MySQL Slave 实现实时数据同步

    由于 TiDB 本身兼容绝大多数的 MySQL 语法,所以对于绝大多数业务来说,最安全的切换数据库方式就是将 TiDB 作为现有数据库的从库接在主 MySQL 库的后方,这样对业务方实现完全没有侵入性 ...

  6. MySQL数据实时增量同步到Kafka - Flume

    转载自:https://www.cnblogs.com/yucy/p/7845105.html MySQL数据实时增量同步到Kafka - Flume   写在前面的话 需求,将MySQL里的数据实时 ...

  7. sersync+rsync实时数据同步

    sersync+rsync实时数据同步 1.相关背景介绍 前面有关文章配置实现了rsync增量同步以及配置为定时同步,但是在实际生产环境中需要实时的监控数据从而进行同步(不间断同步),可以采取inot ...

  8. Linux学习系列之Inotify+Rsync实现实时数据同步

    Inotify简介 inotify介绍 inotify是一种强大的.异步的文件系统监控机制,linux内核从2.6.13起,加入了inotify的支持,通过inotify可以监控文件系统中添加.删除. ...

  9. rsync+inotify实时数据同步多目录实战

    rsync+inotify实时数据同步多目录实战       inotify配置是建立在rsync服务基础上的配置过程 操作系统 主机名 网卡eth0 默认网关 用途 root@58server1 1 ...

  10. rsync+inotify实时数据同步单目录实战

    rsync+inotify实时数据同步单目录实战   rsync+inotify实时数据同步单目录实战 inotify是一个强大的.细粒度的.异步的文件系统事件监控机制,linux内核从2.6.13起 ...

随机推荐

  1. 深度剖析 Spring 框架在 Java 应用开发中的优势与应用

    Spring 是用于企业 Java 应用程序开发的最流行的应用程序开发框架.全球数百万开发人员使用 Spring Framework 创建高性能.易于测试和可重用的代码.Spring Framewor ...

  2. MySQL学习路线一条龙

    引言 在当前的IT行业,无论是校园招聘还是社会招聘,MySQL的重要性不言而喻. 面试过程中,MySQL相关的问题经常出现,这不仅因为它是最流行的关系型数据库之一,而且在日常的软件开发中,MySQL的 ...

  3. 国密 SM2 的非对称签名验签过程

    国密 SM2 的非对称签名验签过程 介绍 非对称加密确保了消息传输中的保密性,但是由于使用公钥加密,而公钥是分发出去的,可能泄露,谁都可以使用公钥加密发送消息. 因此为了保证收到的消息是由对应的发送者 ...

  4. sql 语句系列(更新系列)[八百章之第六章]

    使用另一个表更新记录 有时候我们的数据不会立即去更新,而是存在另外一张表中等待更新,这是在日常开发中常见的操作. update e set e.SAL=ns.SAL+e.SAL, e.COMM=ns. ...

  5. 重新整理.net core 计1400篇[四] (.net core 修改sdk )

    前言 可能有些人还不知道什么是sdk,software development kit,中文是软件开发包的意思. 然后什么是软件开发包? 软件开发工具包是一些被软件工程师用于为特定的软件包.软件框架. ...

  6. Pytorch-tensor的激活函数

    1.激活函数 激活函数的作用是能够给神经网络加入一些非线性因素,使得神经网络可以更好地解决较为复杂的问题.因为很多问题都不是线性的,你只有给它加入一些非线性因素,就能够让问题更好的解决. 函数1:RE ...

  7. 技术干货丨云企业网CEN2.技术揭秘

    ​简介:随着企业数字化转型的加速,越来越多的企业选择了将业务部署在云上,这其中有超过20%的企业有全球组网的需求,这就使得云上网络的规模越来越大,复杂度也越来越高,为了应对这些变化,阿里云推出了升级版 ...

  8. [FAQ] mogodb Robo3T 客户端全屏后 怎么退出全屏

    mongodb 的 Robo3T 客户端: 如果是不小心全屏了,不用担心,按 F11 恢复. 如果想要全屏,也是按 F11. Link:https://www.cnblogs.com/farwish/ ...

  9. [Go] godoc 打开本地文档, windows 同样适用

    godoc 提供了在无网环境下 浏览官方文档的便利. 示例: $ go get golang.org/x/tools/cmd/godoc $ godoc -http=localhost:6060 Li ...

  10. 使用ssh连接远程仓库的方法(github)

    使用ssh连接远程仓库的方法 但是当我登录虚拟机想提交csapp的代码时,我发现需要验证我的账号密码,感觉每次提交都要输入这个很麻烦.然后就在网上查询了下为何提交代码需要输入账号密码. 使用 HTTP ...