用单片机并行驱动HUB75全彩单元板#HUB75并行驱动#P2.5全彩单元板驱动#DMA+PWM+GPIO

1.前言

用单片机驱动HUB75接口的全彩单元板,需要单片机与HUB75接口连接的控制引脚一直维持HUB75时序,通俗来说就是这种 点阵屏幕 需要单片机引脚一直刷新才能正常显示。我们知道,绝大多数单片机都是没有HUB75这个外设的,那么要想让单片机的GPIO一直产生HUB75时序,就需要CPU不断的操作GPIO,这样非常占用单片机资源,并且无法驱动分辨率更大的屏幕。本文浅谈用单片机的GPIO+DMA+PWM来模拟出HUB75这个外设,用这种方法驱动HUB75全彩单元板,相当于单片机有了一个专门的HUB75外设来维持HUB75时序,这样CPU就得到了解放,可以去做更多的事情。

2.P2.5全彩单元板介绍

市面上常见的P2.5全彩单元板,分辨率都是128×64,但是单元板上的控制芯片分为普刷和高刷两种,高刷屏幕虽然也是HUB75接口,但是控制时序更复杂,且各家时序都不一样(并且不公开),普刷屏幕一般来说是通用的,本文只研究普刷P2.5全彩单元板。

单元板控制接口如下图(HUB75接口):

行地址选择引脚:A、B、C、D、E

        当A、B、C、D、E 分别为 0、0、0、0、0时选中的是屏幕的第1行和第33行

        当A、B、C、D、E 分别为 1、0、0、0、0时选中的是屏幕的第2行和第34行

        依次类推直到

        当A、B、C、D、E 分别为 1、1、1、1、1时选中的是屏幕的第32行和第64行

颜色数据引脚:R1、G1、B1、R2、G2、B2

        R1、G1、B1负责上半屏的颜色(第1行~第32行),R2、G2、B2负责下半屏的颜色(第33行~第64行)

数据锁存引脚:LAT

        每传输完1行的颜色数据,产生一个脉冲信号,将颜色数据锁存,然后开始下1行的颜色数据传输(实际上,同一时刻是有2行的颜色数据在传输的,因为行地址选择引脚一次性是选中两行,比如当A、B、C、D、E 分别为 0、0、0、0、0时选中的是屏幕的第1行和第33行)

显示使能引脚:OE

        低电平开启显示。这种普刷单元板上的每一个像素都是一个RGB灯珠,灯珠的控制引脚只能输出高电平,或者低电平,不能向DAC一样输出一个可变的电压,这样就决定了R、G、B三个颜色的亮度是不能控制的,只能全灭或者全亮,这就意味着只能实现七彩显示RGB111。通过这个OE引脚能实现全彩显示实现RGB555或者RGB888。

时钟引脚:CLK

        每传输完一个像素点的颜色数据,产生一个脉冲信号。(同上面一样,同一时刻也是有两个像素点在传输数据的)

以上就是普刷P2.5全彩单元板HUB75接口介绍。

3.P2.5全彩单元板驱动

本文重点是并行驱动,为了更好的理解,先介绍一下串行驱动,也就是用单片机操作GPIO来模拟HUB75时序。

3.1.串行驱动

串行驱动步骤如下

	static uint8_t Gray,j=0;

	for(j=0;j<32;){//整个屏幕64行,一次刷新2行,32次刷完整个屏幕

		Display_OE(1);//关闭显示,消影

        /* 行选 */
		Gray=(j>>1)^j;
        Display_E(Gray&0x10);//操作GPIO引脚
		Display_D(Gray&0x08);
		Display_C(Gray&0x04);
		Display_B(Gray&0x02);
		Display_A(Gray&0x01);
        
        /* 每一行有128个像素点 */
		for(uint8_t i=0;i<128;i++)
		{
			Display_R1(Display_ScanBuf[i][31-Gray]&0x1);//操作GPIO引脚
			Display_G1(Display_ScanBuf[i][31-Gray]&0x2);
			Display_B1(Display_ScanBuf[i][31-Gray]&0x4);
			Display_R2(Display_ScanBuf[i][15-Gray]&0x1);
			Display_G2(Display_ScanBuf[i][15-Gray]&0x2);
			Display_B2(Display_ScanBuf[i][15-Gray]&0x4);	
			Display_CLK(1);
			Display_CLK(0);//CLK脉冲
		}
		j++;

		Display_LAT(1);
		Display_LAT(0);//锁存数据
		Display_OE(0);//打开显示
	}

首先控制OE引脚关闭显示;

然后控制A、B、C、D、E这五个引脚选中2行;

再控制R1、G1、B1引脚将颜色数据传给上半屏选中的行,同时控制R2、G2、B2将颜色数据传给下半屏选中的行,如此循环128次,直至被选中的行的128个像素点都获得颜色数据;

要想保持显示,单片机需要如此往复一直执行,这也只是勉强实现了RGB111。

3.2.DMA+GPIO并行驱动

串行驱动是按一定顺序先后控制与HUB75接口连接的14个信号引脚(一次只能控制一个引脚),并行驱动就是一次性控制与HUB75接口连接的14个信号引脚(一次控制14个引脚),虽然一次性控制14个引脚能提高效率,但是仍然需要CPU来执行。怎么才能将CPU从刷屏中彻底释放出来呢?

我们可以用 定时器的更新事件 来触发 DMA 从内存中搬运数据到GPIO外设的ODR寄存器中,整个过程没有产生任何中断,所以不需要CPU来干预,我们只需要提前初始化好内存中的数据,就能使GPIOx端口的16个引脚产生特定时序的电平,这个时序可以是绝大多数方波信号的时序,比如II2、SPI等,也可以是HUB75时序。

先简单模拟一下上述方案是否可行,这里以HC32F4A0PITB单片机为例:

先对DMA 初始化,我们需要设置DMA1通道3的触发源为TIM2的更新事件;然后设置目标地址为GPIOD端口的ODR寄存器、设置源地址为一个16bit的数组;设置目标地址不递增、源地址递增具体程序如下:

//源地址
uint16_t u32SrcBuf[2] = {0x0,0xffff};
//目标地址
//CM_GPIO->PODRD
static void DmaInit(void){
    stc_dma_init_t stcDmaInit;
    stc_dma_repeat_init_t stcDmaRepeatInit;
	/* DMA/AOS/TMR2 FCG enable */
    FCG_Fcg0PeriphClockCmd(FCG0_PERIPH_DMA1|FCG0_PERIPH_AOS, ENABLE);//开DMA、AOS时钟
    AOS_SetTriggerEventSrc(DMA_TRIGGER_CH, EVT_SRC_TMR2_4_CMP_A);//设置DMA触发源
    (void)DMA_StructInit(&stcDmaInit);
    stcDmaInit.u32IntEn      = DMA_INT_DISABLE;
    stcDmaInit.u32BlockSize  = DMA_BC;//块的大小
    stcDmaInit.u32TransCount = DMA_TC;//传输次数,0为无限传输
    stcDmaInit.u32DataWidth  = DMA_DW;//数据大小16bit
    stcDmaInit.u32DestAddr   = (uint32_t)(&CM_GPIO->PODRD);//目标地址
    stcDmaInit.u32SrcAddr    = (uint32_t)(u32SrcBuf);//源地址
    stcDmaInit.u32SrcAddrInc = DMA_SRC_ADDR_INC;//源地址递增
    stcDmaInit.u32DestAddrInc = DMA_DEST_ADDR_FIX;//目标地址不递增
    (void)DMA_Init(DMA_UNIT, DMA_CH, &stcDmaInit);

    (void)DMA_RepeatStructInit(&stcDmaRepeatInit);
    stcDmaRepeatInit.u32Mode     = DMA_RPT_SRC;//源地址重复,目标地址不重复
	stcDmaRepeatInit.u32SrcCount = 2;
    (void)DMA_RepeatInit(DMA_UNIT, DMA_CH, &stcDmaRepeatInit);
	/* DMA module enable */
    DMA_Cmd(DMA_UNIT, ENABLE);
	/* DMA channel enable */
    (void)DMA_ChCmd(DMA_UNIT, DMA_CH, ENABLE);
}

然后初始化TIM2,使其更新事件发生的频率为1MHZ

static void Tmr2Config(void){
    stc_tmr2_init_t stcTmr2Init;
    /* 1. Enable Timer2 peripheral clock. */
    FCG_Fcg2PeriphClockCmd(TMR2_PERIPH_CLK, ENABLE);
    /* 2. Set a default initialization value for stcTmr2Init. */
    (void)TMR2_StructInit(&stcTmr2Init);
    /* 3. Modifies the initialization values depends on the application. */
    stcTmr2Init.u32ClockSrc     = TMR2_CLK_SRC;
    stcTmr2Init.u32ClockDiv     = TMR2_CLK_DIV;
    stcTmr2Init.u32Func         = TMR2_FUNC_CMP;//功能选择
    stcTmr2Init.u32CompareValue = TMR2_CMP_VAL;
    (void)TMR2_Init(TMR2_UNIT, TMR2_CH, &stcTmr2Init);
}

最后开启TIM2

    /* 开启TMR2触发DMA传输 */
	TMR2_Start(TMR2_UNIT, TMR2_CH);

这样DMA就会以1MHZ的频率将u32SrcBuf这个数组中的值一个一个的搬运到GPIOD的ODR寄存器中,在DMA初始化中,我们有设置源地址重复次数为2,意思就是DMA每搬运2次数据,地址就重复为之前设置的源地址,这样只要TIM2在运行,DMA就会一直循环往复的更新GPIOD的ODR寄存器中的值。于是在没有CPU干预的情况下,GPIOD端口的16个引脚上都产生了一个频率为500KHZ的方波信号(GPIOD端口不要忘记初始化),为什么是500KHZ而不是1MHZ,这个我就不解释了,细细想一下就能想明白。

如果我们将上述程序中源地址数组中的值改为

uint16_t u32SrcBuf[4] = {0x0000,0x0001,0x8000,0x8001};

然后运行程序,现象是PD1引脚上产生一个500KHZ的方波,PD15引脚上产生一个250KHZ的方波,其他引脚都是低电平。

到这里,如果你能搞懂上述现象产生的原因,那么你便能理解 可以通过控制源地址数组的长度和数组中的值,来模拟出HUB75时序;关于源地址数组的初始化到后面再讲,因为里面涉及到全彩(RGB555)如何实现。

所以先来说一下如何在这个理论上只能实现七彩(RGB111)显示的屏幕上实现RGB555;上文有说到,每个像素灯珠 三个颜色的控制引脚都只能输出高低电平,不能控制R、G、B三个颜色的亮度,只能全灭或者全亮;但是有一个OE引脚,可以控制屏幕是否显示,所以我们可以通过控制OE引脚的占空比来控制整个屏幕显示的亮度,因为OE是低电平使能,所以低电平的占空比越大屏幕显示就越亮。我们在极短时间内连续将整个屏幕刷新五次,使第一次刷新时亮度最低 为1、第二次亮度为2、第三次亮度为4、第四次亮度为8、第五次亮度最亮 为16,这样在这极端时间内呈现出来的画面肉眼看上去就是RGB555的全彩画面。也就是完整的一帧画面,需要将屏幕刷新五遍,如下图所示

RGB555完整的一帧由RGB555_Bit0、RGB555_Bit1、RGB555_Bit2、RGB555_Bit3、RGB555_Bit4组成,每个RGB555_Bit都是将整个屏幕刷新一遍,不同的Bit亮度不一样(控制OE来实现)。

接下来开始讲解如何 初始化源地址数组 

首先我们得确定源地址数组的长度,HUB75时序中最基本的就是CLK脉冲,其他信号都是伴随CLK脉冲产生的,所以需要的CLK脉冲个数就决定了这个源地址数组的长度,产生一个脉冲,DMA需要搬运两次数据,第一次搬运使CLK为低电平,第二次搬运使CLK为高电平,这样两次才能产生一个脉冲,所以需要的数组长度就等于 将整个屏幕完整的刷新一帧所需要的CLK脉冲个数 乘以2。

上面说到,要实现RGB555的话,将整个屏幕完整的刷新一帧,需要将整个屏幕刷新5次,屏幕总共64行,分32次刷完,因为一次刷新2行,每一行有128个像素点,刷新一个像素点就需要一个脉冲,所以将整个屏幕刷新一次需要128*32个CLK脉冲,所以实现一帧RGB555需要128*32*5个CLK脉冲,所以数组的长度就等于 128*32*5*2 = 40960;因为数组是uint16_t类型的,所以需要的RAM大小就是40960*2=81920字节=80kb(DMA+GPIO的方法需要占用80kb的RAM,后面讲到的DMA+GPIO+PWM的方法可以节省一半内存)

确定好数组长度后,开始初始化数组的内容;首先确定一下引脚分配,如下表(如果引脚分配不一样,则初始化数组的方法就需要改动)

第一步:将数组中的数据清零

    //:数据全部置0
    for(uint32_t i=0;i<hub75_data_size;i++)HUB75_SrcBuf[i] = 0;

第二步:初始化CLK引脚的时序

	//:初始化CLK-》PD0 时序
	n = 0;
	for(uint32_t i=0;i<hub75_data_size;i++){
		if(i%2)
			HUB75_SrcBuf[i] &=~(1<<n);
		else HUB75_SrcBuf[i] |= (1<<n);
	}

因为CLK引脚连接在PD0上,所以HUB75_SrcBuf数组中的每一个数据的第0位决定CLK引脚的电平

第三步:初始化A、B、C、D、E 时序

	//:初始化A,B,C,D,E -》PD1,PD2,,,PD5 时序
	n = 1;
	for(uint32_t i=0;i<hub75_data_size;i++){
		HUB75_SrcBuf[i] |= ((((i/256)+31)% 32)<<n);
	}

因为在引脚分配时,A、B、C、D、E引脚分别对应GPIOD端口的bit1、bit2、bit3、bit4、bit5,是连续的,所以初始化时就比较容易实现;需要注意的是A、B、C、D、E这5个地址线是每128个像素点加1,而在数组中,128个像素点需要256个数据,这也是上述公式中"/256"而不是"/128"的原因

第四步:初始化LAT时序

	//:初始化LAT -》PD6 时序
	n = 6;
	for(uint32_t i=0;i<hub75_data_size;i++){
		if(i%256) HUB75_SrcBuf[i] &= ~(1<<n);
		else{
			HUB75_SrcBuf[i] |= (1<<n);
		}
	}

跟地址线类似,LAT引脚也是128个像素点产生一次高电平,对应到数组中就是256个数据;因为LAT连接在PD6上,所以n=6。

第五步:初始化OE 时序

OE的时序至关重要,因为RGB555就是通过OE来模拟出来的,所以OE的时序就决定了RGB555的显示效果,调的好,显示效果就好。

//:初始化OE -》PD7 时序
n = 7;
uint32_t cont = 0;
uint16_t a = 0;
for(uint8_t i=0;i<5;i++)
	for(uint16_t ii=0;ii<32;ii++)
		for(uint16_t iii=0;iii<256;iii++){
			if(i == 0){
				a = 127+123;
					if((iii < (256-a/2)) && (iii>a/2) )HUB75_SrcBuf[cont] &=~ (1<<n);
					else HUB75_SrcBuf[cont] |= (1<<n);
			}else if(i == 1){
				a = 127+100;
					if((iii < (256-a/2)) && (iii>a/2) )HUB75_SrcBuf[cont] &=~ (1<<n);
					else HUB75_SrcBuf[cont] |= (1<<n);
			}else if(i == 2){
				a = 127+80;
					if((iii < (256-a/2)) && (iii>a/2) )HUB75_SrcBuf[cont] &=~ (1<<n);
					else HUB75_SrcBuf[cont] |= (1<<n);
			}else if(i == 3){
				a = 117+50;
					if((iii < (256-a/2)) && (iii>a/2) )HUB75_SrcBuf[cont] &=~ (1<<n);
					else HUB75_SrcBuf[cont] |= (1<<n);
			}else if(i == 4){
				a = 20+15;
					if((iii < (256-a/2)) && (iii>a/2) )HUB75_SrcBuf[cont] &=~ (1<<n);
					else HUB75_SrcBuf[cont] |= (1<<n);
			}
			cont++;
		}

调整变量a的值,就能调整RGB555中第1遍、第2遍、、、第5遍的亮度;

if((iii < (256-a/2)) && (iii>a/2) )这个判断是为了使OE的低电平保持在时序的中间,从而达到更好的显示效果。

到这里HUB75的时序就已经出来了,但是我们还不知道精准的控制每一个像素点的颜色。

第六步:打点函数的实现

一个RGB555颜色数据是没法直接放到源地址数组中的,需要对颜色数据逐位处理,如果该位为一,就将源地址数组中对应的数据置1,否则就置0

void drawPoint(uint16_t x,uint16_t y,uint16_t color___){
	uint32_t addr = ((x+1)<<1 ) + ((y%32)<<8);
	addr %= hub75_data_size;
	uint8_t ii=0;
	uint32_t var = 0;
	for(uint8_t i=0;i<5;i++){
		if(y<31){//亮度梯度01234
			var = addr + 8192*i;
			if((color___ & 0x400) != 0)(HUB75_SrcBuf[var] |= 0x2000);else (HUB75_SrcBuf[var] &= ~0x2000);//R1->PD13
			if((color___ & 0x20) != 0)(HUB75_SrcBuf[var] |= 0x1000);else (HUB75_SrcBuf[var] &= ~0x1000);//G1->PD12
			if((color___ & 0x1) != 0)(HUB75_SrcBuf[var] |= 0x800);else (HUB75_SrcBuf[var] &= ~0x800);//B1->PD11
		}else if(y==31){//亮度梯度12340
			ii = i-1;
			if(ii>4) ii = 4;
			var = addr + 8192*ii;
			if((color___ & 0x400) != 0)(HUB75_SrcBuf[var] |= 0x2000);else (HUB75_SrcBuf[var] &= ~0x2000);//R1->PD13
			if((color___ & 0x20) != 0)(HUB75_SrcBuf[var] |= 0x1000);else (HUB75_SrcBuf[var] &= ~0x1000);//G1->PD12
			if((color___ & 0x1) != 0)(HUB75_SrcBuf[var] |= 0x800);else (HUB75_SrcBuf[var] &= ~0x800);//B1->PD11
		}else if(y<63){//亮度梯度01234
			var = addr + 8192*i;
			if(((color___)&0x400) != 0)(HUB75_SrcBuf[var] |= 0x400);else (HUB75_SrcBuf[var] &= ~0x400);//R2->PD10
			if(((color___)&0x20) != 0)(HUB75_SrcBuf[var] |= 0x200);else (HUB75_SrcBuf[var] &= ~0x200);//G2->PD9
			if(((color___)&0x1) != 0)(HUB75_SrcBuf[var] |= 0x100);else (HUB75_SrcBuf[var] &= ~0x100);//B2->PD8
		}else {//亮度梯度12340
			ii = i-1;
			if(ii>4) ii = 4;
			var = addr + 8192*ii;
			if((color___ & 0x400) != 0)(HUB75_SrcBuf[var] |= 0x400);else (HUB75_SrcBuf[var] &= ~0x400);//R2->PD10
			if((color___ & 0x20) != 0)(HUB75_SrcBuf[var] |= 0x200);else (HUB75_SrcBuf[var] &= ~0x200);//G2->PD9
			if((color___ & 0x1) != 0)(HUB75_SrcBuf[var] |= 0x100);else (HUB75_SrcBuf[var] &= ~0x100);//B2->PD8
		}
		color___ >>= 1;
	}
}

至此就实现了DMA+GPIO并行驱动P2.5全彩单元板,CPU不需要任何刷屏操作,只需要调用打点函数就行了。

附一张HUB75抓波图

驱动效果

3.3.DMA+PWM+GPIO并行驱动

上一种方法是用定时器的更新事件来触发DMA搬运数据,CLK引脚的脉冲信号也由GPIOD端口的引脚产生,所以就导致一个CLK脉冲需要DMA搬运两次数据才能产生;既然用到了定时器,那么肯定可以在触发DMA搬运的同时,在定时器的一个通道上产生PWM波(可能有些垃圾单片机上实现不了),我们将这个PWM波连到CLK引脚上,这样定时器产生一次更新事件的同时,就能产生一个CLK脉冲,同时触发DMA搬运一次数据,所以需要的源地址数组长度就减小了一般,只需要128*32*5 = 20480,所以就只需要所20480*2=40960字节=40kb RAM。

初始化源地址数组

第一步:将数组中的数据清零

	//:数据全部置0
	for(i=0;i<hub75_data_size;i++)HUB75_SrcBuf[i] = 0;

第二步:初始化CLK引脚的时序

//CLK时序已经不需要初始化了,由定时器的PWM通道产生

第三步:初始化A、B、C、D、E 时序

	//:初始化A,B,C,D,E -》PD1,PD2,,,PD5 时序
	n = 1;
	for(i=0;i<hub75_data_size;i++){
		HUB75_SrcBuf[i] |= ((((i/128)+30)% 32)<<n);
	}

第四步:初始化LAT时序

	//:初始化LAT -》PD6 时序
	n = 6;
	for(i=0;i<hub75_data_size;i++)
	{
		if(i%128) //256
			HUB75_SrcBuf[i] &= ~(1<<n);
		else
		 {
			HUB75_SrcBuf[i] |= (1<<n);
		 }
	}

第五步:初始化OE 时序

	//:初始化OE -》PD7 时序
	n = 7;
	for(i=0;i<5;i++)
	{
		for(ii=0;ii<16;ii++)
	  {
			for(iii=0;iii<256;iii++)  //128
			{
				if(i == 0)
				 {
					a = (127+123+4*(255-cTotalBrightness)/255)/1;
					//a = 124*2;   亮度会降低,电流降低
					if((iii < (256-a/2)) && (iii>a/2) )HUB75_SrcBuf[cont] &=~ (1<<n);
					else HUB75_SrcBuf[cont] |= (1<<n);
				 }
				else if(i == 1)
				 {
					a = (127+100+23*(255-cTotalBrightness)/255)/1;
					//a = 120*2; 
					if((iii < (256-a/2)) && (iii>a/2) )HUB75_SrcBuf[cont] &=~ (1<<n);
					else HUB75_SrcBuf[cont] |= (1<<n);
				 }
			  else if(i == 2)
				 {
					a = (127+80+40*(255-cTotalBrightness)/255)/1;
					 //a = 112*2;
					if((iii < (256-a/2)) && (iii>a/2) )HUB75_SrcBuf[cont] &=~ (1<<n);
					else HUB75_SrcBuf[cont] |= (1<<n);
				 }
				else if(i == 3)
				 {
					a = (127+40+75*(255-cTotalBrightness)/255)/1;
					//a =  96*2;
					if((iii < (256-a/2)) && (iii>a/2) )HUB75_SrcBuf[cont] &=~ (1<<n);
					else HUB75_SrcBuf[cont] |= (1<<n);
				 }
				else if(i == 4)
				 {
					a = (20+15+190*(255-cTotalBrightness)/255)/1;
					//a =  32*2;
					if((iii < (256-a/2)) && (iii>a/2) )HUB75_SrcBuf[cont] &=~ (1<<n);
					else HUB75_SrcBuf[cont] |= (1<<n);
				 }
				cont++; // if(cont == 128) cont = 0;
			}
		}
	}

第六步:打点函数的实现

void drawPoint(uint16_t x1,uint16_t y1,uint16_t color___){
	uint16_t i = 0,ii = 0;
	uint16_t x =x1;
	uint16_t y =y1;
	uint32_t addr = ((x+1)<<0 ) + (y%32<<7);
	addr %= hub75_data_size;
	for(i=0;i<5;i++)
	{//亮度梯度01234
		if(y<31){  //条件 ? 表达式1 : 表达式2        条件成立跳转表达式1 ,否则跳转到表达式2
			(color___ & 0x400) != 0?(HUB75_SrcBuf[addr + ADDR_INC_MUL*i] |= (1<<13)):(HUB75_SrcBuf[addr + ADDR_INC_MUL*i] &= ~(1<<13));  //R1->PB13    0x400  红色最低亮度
			(color___ & 0x20) != 0?(LedPixelData[addr + ADDR_INC_MUL*i] |= (1<<12)):(HUB75_SrcBuf[addr + ADDR_INC_MUL*i] &= ~(1<<12));   //G1->PB12    0x20   绿色最低亮度  
			(color___ & 0x1) != 0?(LedPixelData[addr + ADDR_INC_MUL*i] |= (1<<11)):(HUB75_SrcBuf[addr + ADDR_INC_MUL*i] &= ~(1<<11));    //B1->PB11    0x01   蓝色最低亮度
		}else if(y==31){//亮度梯度12340
			ii = i-1;
			if(ii>4) ii = 4;
			(color___ & 0x400) != 0?(HUB75_SrcBuf[addr + ADDR_INC_MUL*ii] |= (1<<13)):(HUB75_SrcBuf[addr + ADDR_INC_MUL*ii] &= ~(1<<13));//R1->PB13
			(color___ & 0x20) != 0?(HUB75_SrcBuf[addr + ADDR_INC_MUL*ii] |= (1<<12)):(HUB75_SrcBuf[addr + ADDR_INC_MUL*ii] &= ~(1<<12)); //G1->PB12
			(color___ & 0x1) != 0?(HUB75_SrcBuf[addr + ADDR_INC_MUL*ii] |= (1<<11)):(HUB75_SrcBuf[addr + ADDR_INC_MUL*ii] &= ~(1<<11));  //B1->PB11
		}else if(y<63){//亮度梯度01234
			((color___)&0x400) != 0?(HUB75_SrcBuf[addr + ADDR_INC_MUL*i] |= (1<<10)):(HUB75_SrcBuf[addr + ADDR_INC_MUL*i] &= ~(1<<10));  //R2->PB10
			((color___)&0x20) != 0?(HUB75_SrcBuf[addr + ADDR_INC_MUL*i] |= (1<<9)):(HUB75_SrcBuf[addr + ADDR_INC_MUL*i] &= ~(1<<9));     //G2->PB9
			((color___)&0x1) != 0?(HUB75_SrcBuf[addr + ADDR_INC_MUL*i] |= (1<<8)):(HUB75_SrcBuf[addr + ADDR_INC_MUL*i] &= ~(1<<8));      //B2->PB8
		}else {//亮度梯度12340
			ii = i-1;
			if(ii>4) ii = 4;
			(color___ & 0x400) != 0?(HUB75_SrcBuf[addr + ADDR_INC_MUL*ii] |= (1<<10)):(HUB75_SrcBuf[addr + ADDR_INC_MUL*ii] &= ~(1<<10));//R2->PB10
			(color___ & 0x20) != 0?(HUB75_SrcBuf[addr + ADDR_INC_MUL*ii] |= (1<<9)):(HUB75_SrcBuf[addr + ADDR_INC_MUL*ii] &= ~(1<<9));   //G2->PB9
			(color___ & 0x1) != 0?(HUB75_SrcBuf[addr + ADDR_INC_MUL*ii] |= (1<<8)):(HUB75_SrcBuf[addr + ADDR_INC_MUL*ii] &= ~(1<<8));    //B2->PB8
		}
		color___ >>= 1;
	}
}

3.4.别用HC32F4PATB单片机来模拟HUB75时序

HC32F4PATB这个单片机的DMA功能设计的非常灵活,能实现在搬运的时候进行地址偏移,能灵活的控制一次传所输搬运的数据块的大小等等,但是用来控制DMA地址重复次数的数据位宽只有10bit,如下图

也就是我们想要的,搬运20480次或者40960次后重复为源地址,这个无法在这个单片机上实现。

对应的程序如下

static void DmaInit(void){
    stc_dma_repeat_init_t stcDmaRepeatInit;
	stc_dma_init_t stcDmaInit;
	/* DMA/AOS/TMR2 FCG enable */
    FCG_Fcg0PeriphClockCmd(FCG0_PERIPH_DMA1|FCG0_PERIPH_AOS, ENABLE);//开DMA、AOS时钟
    AOS_SetTriggerEventSrc(DMA_TRIGGER_CH, EVT_SRC_TMR2_4_CMP_A);//设置DMA触发源
    (void)DMA_StructInit(&stcDmaInit);
    stcDmaInit.u32IntEn      = DMA_INT_DISABLE;//关闭中断
    stcDmaInit.u32BlockSize  = 1;//块的大小
    stcDmaInit.u32TransCount = 0;//重复传输,只要有触发信号就能一直传输
    stcDmaInit.u32DataWidth  = DMA_DW;//数据大小16bit
    stcDmaInit.u32DestAddr   = (uint32_t)(&CM_GPIO->PODRD);//目标地址
    stcDmaInit.u32SrcAddr    = (uint32_t)(HUB75_SrcBuf);//源地址
    stcDmaInit.u32SrcAddrInc = DMA_SRC_ADDR_INC;//源地址递增
    stcDmaInit.u32DestAddrInc = DMA_DEST_ADDR_FIX;//目标地址不递增
    (void)DMA_Init(DMA_UNIT, DMA_CH, &stcDmaInit);
    (void)DMA_RepeatStructInit(&stcDmaRepeatInit);
    stcDmaRepeatInit.u32Mode     = DMA_RPT_SRC;//源地址重复,目标地址不重复
	stcDmaRepeatInit.u32SrcCount = 40960;//源地址每40960次就重复一次
    (void)DMA_RepeatInit(DMA_UNIT, DMA_CH, &stcDmaRepeatInit);
	/* DMA module enable */
    DMA_Cmd(DMA_UNIT, ENABLE);
	/* DMA channel enable */
    (void)DMA_ChCmd(DMA_UNIT, DMA_CH, ENABLE);
}

上述程序中的下面行是实现不了的,因为它最大不能超过1023,为0时,代表最大1024

stcDmaRepeatInit.u32SrcCount = 40960;//源地址每40960次就重复一次

后面又继续深入研究了一下这个DMA的地址偏移功能,发现也是无法实现我们想要的功能。

所以用这个单片机无法实现CPU不干预的情况下实现HUB75时序

要想用这个单片机实现HUB75时序,只能加入DMA传输完成中断,每传输完40960或者20480个数据后,触发中断,在中断函数中重新开启DMA传输,这样CPU就需要处理一个中断才能实现HUB75时序,配置如下

stc_dma_init_t stcDmaInit;
void DMA1_CH3_TransEnd_IrqCallback(void){
	DMA_Init(DMA_UNIT, DMA_CH, &stcDmaInit);
	DMA_ChCmd(DMA_UNIT, DMA_CH, ENABLE);
}
static void DmaInit(void){
	stc_irq_signin_config_t stcIrqSignConfig;
	/* DMA/AOS/TMR2 FCG enable */
    FCG_Fcg0PeriphClockCmd(FCG0_PERIPH_DMA1|FCG0_PERIPH_AOS, ENABLE);//开DMA、AOS时钟
    AOS_SetTriggerEventSrc(DMA_TRIGGER_CH, EVT_SRC_TMR2_4_CMP_A);//设置DMA触发源
    (void)DMA_StructInit(&stcDmaInit);
    stcDmaInit.u32IntEn      = DMA_INT_ENABLE;//中断使能
    stcDmaInit.u32BlockSize  = 1;//块的大小
    stcDmaInit.u32TransCount = 40960;//40960个触发信号后,传输停止,需要重新打开
    stcDmaInit.u32DataWidth  = DMA_DW;//数据大小16bit
    stcDmaInit.u32DestAddr   = (uint32_t)(&CM_GPIO->PODRD);//目标地址
    stcDmaInit.u32SrcAddr    = (uint32_t)(HUB75_SrcBuf);//源地址
    stcDmaInit.u32SrcAddrInc = DMA_SRC_ADDR_INC;//源地址递增
    stcDmaInit.u32DestAddrInc = DMA_DEST_ADDR_FIX;//目标地址不递增
    (void)DMA_Init(DMA_UNIT, DMA_CH, &stcDmaInit);
    stcIrqSignConfig.enIntSrc   = DMA_INT_SRC;
    stcIrqSignConfig.enIRQn     = DMA_IRQn;
    stcIrqSignConfig.pfnCallback = &DMA1_CH3_TransEnd_IrqCallback;
    (void)INTC_IrqSignIn(&stcIrqSignConfig);
    DMA_ClearTransCompleteStatus(DMA_UNIT, DMA_FLAG_TC_CH3);
    /* NVIC setting */
    NVIC_ClearPendingIRQ(DMA_IRQn);
    NVIC_SetPriority(DMA_IRQn, DDL_IRQ_PRIO_03);//中断优先级设置
    NVIC_EnableIRQ(DMA_IRQn);
	/* DMA module enable */
    DMA_Cmd(DMA_UNIT, ENABLE);
	/* DMA channel enable */
    (void)DMA_ChCmd(DMA_UNIT, DMA_CH, ENABLE);
}

最后附一篇先楫半导体使用DMA+GPIO的推文:经验分享 | HPMicro定制化波形–DMA推GPIO指南

作者:qq_51178659

物联沃分享整理
物联沃-IOTWORD物联网 » 用单片机并行驱动HUB75全彩单元板#HUB75并行驱动#P2.5全彩单元板驱动#DMA+PWM+GPIO

发表回复