大家好,在学习STM32和RTOS(uC/OS-II)的过程中遇到了很多问题,这里把本人的学习例程分享给大家,希望对学习STM32的同学有所启示。最后祝您学业有成,工作顺利。
首先感谢我要自学网黄琦龙老师设计的PCB,这块PCB上很多例程都没有给出,在这里补充一下。
先把原理图和layout图截图放一下,给大家了解下PCB上提供的资源。
1.核心版+底板,提供的资源有STM32C8T6和晶振
2.外设资源LCD+LED+Key+Beep+USART接口+SWD调试接口
PCB的尺寸不大,很适合做智能家居类产品,黄老师说要用这个开发空气净化器不知道做成功了没有。
- 首先蜂鸣器黄老师的资料里是没给出来的,我测了一下是PA6引脚连的三极管,IO通过三极管的开关作用驱动蜂鸣器。
void BEEP_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/*使能GPIOA的RCC时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
/*配置GPIOA引脚,并初始化*/
GPIO_InitStructure.GPIO_Mode= GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Pin= GPIO_Pin_6; //BEEP-->PA.6 端口配置
GPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //根据参数初始化GPIOA.6
BEEP_OFF();
}
//在BEEP.h文件里可以增加位带操作
#define BEEP_ON() do{GPIO_SetBits(GPIOA, GPIO_Pin_6);}while(0);
#define BEEP_OFF() do{GPIO_ResetBits(GPIOA, GPIO_Pin_6);}while(0);
#define BEEP_Status PAout(6)// PA6蜂鸣器 位带操作
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //输入
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
#define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
#define GPIOA_IDR_Addr (GPIOA_BASE+8) //0x40010808
ADC没有给出相应例程,底板侧边的PA1可以作为ADC1的channel1模拟信号输入,这里补充初始化函数和读模拟量函数
void Adc_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_ADC1, ENABLE ); //使能ADC1通道时钟
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
ADC_DeInit(ADC1); //恢复默认
//PA1 作为模拟通道输入引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入引脚
GPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_DeInit(ADC1); //复位ADC1
ADC_InitStructure.ADC_Mode=ADC_Mode_Independent; //ADC工作模式:独立模式
ADC_InitStructure.ADC_ScanConvMode=DISABLE; //模数转换工作在单通道模式
ADC_InitStructure.ADC_ContinuousConvMode=DISABLE; //模数转换工作在单次转换模式
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right; //ADC数据右对齐
ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None; //转换由软件而不是外部触发启动
ADC_InitStructure.ADC_NbrOfChannel=1; //顺序进行规则转换的ADC通道的数目
ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器
ADC_Cmd(ADC1, ENABLE); //使能指定的ADC1
ADC_ResetCalibration(ADC1); //使能复位校准
while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束
ADC_StartCalibration(ADC1); //开启AD校准
while(ADC_GetCalibrationStatus(ADC1)); //等待校准结束
}
u16 Get_Adc(u8 ch) //ch代表ADC哪个通道
{
//设置指定ADC的规则组通道,一个序列,采样时间
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5); //ADC1,ADC通道,采样时间为239.5周期
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能指定的ADC1的软件转换启动功能
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束
return ADC_GetConversionValue(ADC1); //返回最近一次ADC1规则组的转换结果
}
u16 Get_Adc_Average(u8 ch,u8 times) //ch代表通道,times是采样次数
{
u32 temp_val=0;
u8 t;
for(t=0;t<times;t++)
{
temp_val+=Get_Adc(ch);
delay_ms(5);
}
return temp_val/times;
}
DMA增加相应例程,本例程设置用DMA1通道4从全局变量SendBuff往USART1传数据
DMA_InitTypeDef DMA_InitStructure;
u16 DMA1_MEM_LEN;//保存DMA每次数据传送的长度
//DMA1的各通道配置
//这里的传输形式是固定的,这点要根据不同的情况来修改
//从存储器->外设模式/8位数据宽度/存储器增量模式
//DMA_CHx:DMA通道CHx
//cpar:外设地址
//cmar:存储器地址
//cndtr:数据传输量
void MYDMA_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA传输
DMA_DeInit(DMA_CHx); //将DMA的通道1寄存器重设为缺省值
DMA1_MEM_LEN=cndtr;
DMA_InitStructure.DMA_PeripheralBaseAddr = cpar; //DMA外设基地址
DMA_InitStructure.DMA_MemoryBaseAddr = cmar; //DMA内存基地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //数据传输方向,从内存读取发送到外设
DMA_InitStructure.DMA_BufferSize = cndtr; //DMA通道的DMA缓存的大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //数据宽度为8位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
DMA_Init(DMA_CHx, &DMA_InitStructure); //根据DMA_InitStruct中指定的参数初始化DMA的通道USART1_Tx_DMA_Channel所标识的寄存器
}
//开启一次DMA传输
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx)
{
DMA_Cmd(DMA_CHx, DISABLE ); //关闭USART1 TX DMA1 所指示的通道
DMA_SetCurrDataCounter(DMA_CHx,DMA1_MEM_LEN);//DMA通道的DMA缓存的大小
DMA_Cmd(DMA_CHx, ENABLE); //使能USART1 TX DMA1 所指示的通道
}
键值读取函数原例程用的是外部中断,太浪费资源,改成扫码键值的架构
#define KEY0_State GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) //读取按键0
#define KEY1_State GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12) //读取按键1
#define KEY2_State GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13) //读取按键2
#define KEY3_State GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) //读取按键3
#define KEY0_PRES 1 //KEY1按下
#define KEY1_PRES 2 //KEY1按下
#define KEY2_PRES 3 //KEY2按下
#define KEY3_PRES 4 //KEY3按下
#define NoKEY_PRES 0 //无按键按下
#define NoPress 1
#define IsPress 0
u8 KEY_Scan(u8 mode)
{
static u8 key_up=NoPress; //按键按松开标志
if(mode)key_up=NoPress; //支持连按
if(key_up && (KEY0_State==IsPress||KEY1_State==IsPress||KEY2_State==IsPress||KEY3_State==IsPress))
{
delay_ms(10); //延迟10ms
key_up=IsPress;
if(KEY0_State==IsPress)return KEY0_PRES;
else if(KEY1_State==IsPress)return KEY1_PRES;
else if(KEY2_State==IsPress)return KEY2_PRES;
else if(KEY3_State==IsPress)return KEY3_PRES;
}
else if(KEY0_State==NoPress&&KEY1_State==NoPress&&KEY2_State==NoPress&&KEY3_State==NoPress)
key_up=NoPress;
return NoKEY_PRES;
}
原例程RTC配置写的不够好,参考正点原子的修改,计算日历的函数太长这里就不给出,感兴趣可去看正点原子的PDF
u8 RTC_Init(void)
{
//检查是不是第一次配置时钟
u8 temp=0;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟
PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问
if (BKP_ReadBackupRegister(BKP_DR1) != 0x5050) //从指定的后备寄存器中读出数据:读出了与写入的指定数据不相乎
{
BKP_DeInit(); //复位备份区域
RCC_LSEConfig(RCC_LSE_ON); //设置外部低速晶振(LSE),使用外设低速晶振
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET&&temp<250) //检查指定的RCC标志位设置与否,等待低速晶振就绪
{
temp++; //判断外部晶振是否Ready 2.5s
delay_ms(10);
}
if(temp>=250)return 1;//初始化时钟失败,晶振有问题
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //设置RTC时钟(RTCCLK),选择LSE作为RTC时钟
RCC_RTCCLKCmd(ENABLE); //使能RTC时钟
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
RTC_WaitForSynchro(); //等待RTC寄存器同步
RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能RTC秒中断
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
RTC_SetPrescaler(32767); //设置RTC预分频的值
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
RTC_EnterConfigMode();/// 允许配置
RTC_Set(2019,1,23,0,5,55); //设置时间
RTC_ExitConfigMode(); //退出配置模式
BKP_WriteBackupRegister(BKP_DR1, 0X5050); //向指定的后备寄存器中写入用户程序数据
}
else//系统继续计时
{
RTC_WaitForSynchro(); //等待最近一次对RTC寄存器的写操作完成
RTC_ITConfig(RTC_IT_SEC|RTC_IT_ALR, ENABLE); //使能RTC秒和闹钟中断
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
}
RTC_NVIC_Config();//RCT中断分组设置
RTC_Get();//更新时间
return 0; //ok
}
void RTC_IRQHandler(void)
{
if (RTC_GetITStatus(RTC_IT_SEC) != RESET)//秒钟中断
{
RTC_Get();//更新时间
}
if(RTC_GetITStatus(RTC_IT_ALR)!= RESET)//闹钟中断
{
RTC_ClearITPendingBit(RTC_IT_ALR); //清闹钟中断
RTC_Get(); //更新时间
RTC_Alarm_Flag=ENABLE;
}
RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW); //清秒中断
RTC_WaitForLastTask();
}
延时函数也做修改,原例程用Systick中断实现,比较浪费资源,这里可以参考@newsteinguo写的三种延时函数
下一讲说明如何用UCOSII去多线程调用这些功能