一、前言
二、电路设计
三、程序设计
四、总结
五、参考资料
一、前言最近闲着没事,搞了个“旋转LED”的小电路板,自己设计的电路板,上面有64个贴片LED排成一排显示,本文要介绍的是用定时器触发+DMA传输的方式在IO口上产生74HC573和74HC238的控制时序,完成循环点亮64个LED的功能。记录下调试的过程。
二、电路设计
用的单片机是STM32F103C8T6,直接用单片机引脚连接每个LED肯定是不够用的,因为也就只有30多个控制引脚,想了一下用了8个74HC573来控制,用PA0到PA7这8个IO统一连接到8个573芯片的输入端,如下图所示:
用一片74HC238芯片来控制8个573芯片的LE脚,如下图,74HC238和74HC138芯片功能是类似的,只不过74HC238的有效输出电平是高电平而已,和74HC138正好相反。
64个LED的连接电路如下:
三、程序设计
LED显示并没有采用while循环使劲刷新LED状态的方法,而是采用定时器触发DMA传输数据到GPIO口来改变GPIO口输出状态的方式来达到刷新LED显示状态的目的,整个时序刷新过程不需要CPU的干预,原理如下:
我们划分出一块连续的RAM内存区域作为“显存”,用定时器来产生固定周期频率的定时器溢出事件,然后将DMA的传输与定时器溢出事件进行绑定,DMA模块就可以循环往复的将“显存”内的数据送往GPIOA,让GPIOA的16个IO引脚根据“显存”输出不同的电平状态,也就产生了特定的控制时序,达到控制外围芯片的目的。
定时器触发DMA传输的代码如下:
WORD wLedStateBuf[64 + 8];
void TIM3_Init()
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
/* 开启定时器3时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
TIM_ITConfig(TIM3, TIM_IT_Update, DISABLE ); //关闭定时器更新中断
TIM_TimeBaseInitStructure.TIM_Period = 1000;
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200-1;
TIM_TimeBaseInitStructure.TIM_ClockDivision = 0;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
TIM_Cmd(TIM3,ENABLE);
/* 设置NVIC参数 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_DMACmd(TIM3, TIM_DMA_Update, ENABLE); //定时器更新事件触发DMA传输
}
void DMA1_Init() //DMA初始化
{
DMA_InitTypeDef DMA_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
DMA_DeInit(DMA1_Channel3);
DMA_InitStructure.DMA_PeripheralBaseAddr =(u32)&GPIOA->ODR;//DMA外设地址
DMA_InitStructure.DMA_MemoryBaseAddr=(u32)wLedStateBuf;//DMA内存地址
DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralDST;//外设作为数据传输的来源
DMA_InitStructure.DMA_BufferSize=64 + 8;//指定DMA通道的DMA缓存的大小
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_High;//DMA通道x拥有高优先级
DMA_InitStructure.DMA_M2M=DMA_M2M_Disable;//DMA通道x没有设置为内存到内存传输
DMA_Init(DMA1_Channel3,&DMA_InitStructure); //TIM3更新事件在DMA1通道3内
DMA_Cmd(DMA1_Channel3,ENABLE);//使能DMA1通道3
DMA_Cmd(DMA1_Channel3,ENABLE);
}
100ms触发一次DMA传输,具体的DMA通道不是随意选择的,需要查表看定时器3的更新事件映射在DMA1的哪个通道。
LED初始化和main函数如下:
void LED_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); /* 开启GPIO时钟 */
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 \
| GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIOA->ODR &= ~((uint16_t) 0x0FFF);
}
int main()
{
int i;
//给显存赋值,内容为64个LED循环依次点亮
for(i=0; i<64 + 8; i++)
{
if(i % 9 == 8)
{
wLedStateBuf[i] = (uint32_t)(i / 9 + 1) << 8;
}
else
{
//利用8、9、10引脚选中某个573芯片,0~7引脚操作要亮的LED
wLedStateBuf[i] = ((uint16_t)(i / 9) << 8) | ((uint16_t)1 << (i % 9));
}
}
LED_Init();
TIM3_Init();
DMA1_Init();
while(1)
{
//处理其他事情
}
}
用这种方式不需要CPU去不断的刷时序了,这些都由DMA去完成了。CPU要做的只是根据需要去改变“显存”里的东西,腾出时间给CPU去处理其他事情。
用逻辑分析仪抓取各个GPIO引脚的时序图如下:
四、总结
定时器触发+DMA传输的方式可以完成很多操作,就如本文所述,用这种方式来产生74HC573和74HC238的控制时序。还有比如我们要产生16路甚至更多路的精确的PWM波时,用定时器产生的话通道数不够而且必须在特定的引脚上才能产生,使用本文所述这种方式同样不需要占用CPU的处理时间而且输出引脚任意选择非常方便。再比如还有定时器+DMA传输+DAC输出的方式可以用来输出各种各样的波形,非常好用。
五、参考资料
《STM32F10xxx中文参考手册_V10》
链接:https://pan.baidu.com/s/14NGfTZkjhRfx9AXFhT8W5Q 提取码:8g4w
心城追梦
原创文章 22获赞 29访问量 1万+
关注
私信
展开阅读全文
作者:心城追梦