自己在博客发的,自己转过来,不知道贴地址是否违规,如果违规,麻烦版主帮忙编辑掉,原地址:http://teczm.blog.163.com/blog/static/2884553772017112683638308/?newFollowBlog.下面直接复制原文喽,希望大家多多交流。
功能在标题中都体现出来了,具体的我自己会大概总结一下,然后总体上的流程大概是下面这个样子(其中plat_init 做系统初始化以及各模块的初始化,以及任务的创建,musicPlay 这个函数主要是根据时间来改变pwm的周期和占空比):
/*@brief: 主函数,工程入口.
*@param: None.
*@return: None.
*/
int main(void)
{
plat_init();
while(1)
{
musicPlay();
}
} /*@brief: Platform init.
*@param: None.
*@return: None.
*/
void plat_init(void)
{
stm32_sys_init(); //系统初始化:设置系统时钟
uart_config();
uart_printf("Platform initialized,program start!\r\n");
led_config(); //LED初始化
key_config(led_modeset);
w25q64_config(rtc_set);
rtc_config(w25q64_record_time);
pwm_config();
ds18b20_config();
stm32_create_task(key_scan,TASK_PRIO_HIGH,10);
stm32_create_task(led_indicate,TASK_PRIO_LOW,200);
stm32_create_task(rtc_broadcast,TASK_PRIO_LOW,1000*10);
stm32_create_task(ds18b20_read_temper,TASK_PRIO_LOW,1000*60);
stm32_timer_init(); //定时器初始化
} /*brief: 演奏乐曲
*param: None.
*return: None.
*/
void musicPlay(void)
{
static uint32_t music_tick = 0;
static uint16_t i=0;
if(music_htys.mTime != 0)
{
if(stm32_get_time_ms() >= music_tick)
{
music_tick = stm32_get_time_ms() + music_htys.mTime;
pwm_update(music_htys[i++].mName);
}
}
else
{
i = 0;
}
} 初始化部分,有一些模块用到了函数回调,注册了事件函数,比如按键模块,触发事件的时候执行对应的动作,做的很粗鄙,算是一个思想吧,有时间自己再整理整理。比如下面的按键模块,按键按下的时候会调用对应的按键事件,跑对应的流水灯效果和串口输出指示对应事件的产生(这个地方可能会有点绕,因为我在对应的事件处理程序中只设置了灯的模式,而点灯的动作是留到初始化时创建的任务里面去做的):
/*@breief: Key config.
*@param: None.
*@return: None.
*/
void key_config(key_evt_handler phandler)
{
RCC->APB2ENR |= (Bit2_En|Bit6_En); //打开GPIOA和GPIOE的时钟
GPIOA->ODR |= ((uint32_t)0<<KEY_UP_Pos);
GPIOA->CRL &= ((~((uint32_t)0x03<<(KEY_UP_Pos<<2)))&(~((uint32_t)0x03<<((KEY_UP_Pos<<2)+2))));
GPIOA->CRL |= ((uint32_t)0x2<<((KEY_UP_Pos<<2)+2)); //PA0输入,下拉
GPIOE->ODR |= ((1<<KEY_DOWN_Pos)|(1<<KEY_LEFT_Pos)|(1<<KEY_RIGHT_Pos));
GPIOE->CRL &= (((~((uint32_t)0x03<<(KEY_DOWN_Pos<<2)))&(~((uint32_t)0x03<<((KEY_DOWN_Pos<<2)+2))))
&((~((uint32_t)0x03<<(KEY_LEFT_Pos<<2)))&(~((uint32_t)0x03<<((KEY_LEFT_Pos<<2)+2))))
&((~((uint32_t)0x03<<(KEY_RIGHT_Pos<<2)))&(~((uint32_t)0x03<<((KEY_RIGHT_Pos<<2)+2)))));
GPIOE->CRL |= (((uint32_t)0x2<<((KEY_DOWN_Pos<<2)+2))
|((uint32_t)0x2<<((KEY_LEFT_Pos<<2)+2))
|((uint32_t)0x2<<((KEY_RIGHT_Pos<<2)+2))); //PE2 PE3 PE4输入,上拉
pkey_handler = phandler;
} /****************************************************************************
* Function Name : led_modeset
* Description : 设置LED的状态
* Input : None
* Output : None
* Return : None
****************************************************************************/
void led_modeset(void *evt)
{
key_evt = *(uint32_t *)evt;
}
自己用定时器做了一个简单的任务调度,两个优先级,直接分配的数组,没有用动态申请的方式,毕竟图个简便快捷。
static task_t stm32_task_queue[TASK_PRIO_MAX][TASK_NUM_MAX] = {NULL};
/*@brief: Create new task.
*@param task: Pointer to task function.
*@param prio: Task priority.
*@return interval: Task execute interval,ms.
*/
int8_t stm32_create_task(ptask task,uint8_t prio,uint32_t interval)
{
uint8_t i;
if(prio > TASK_PRIO_MAX)
return -1;
for(i = 0;i<TASK_NUM_MAX;i++)
{
if(stm32_task_queue[prio].task == NULL)
{
stm32_task_queue[prio].task = task;
stm32_task_queue[prio].interval = interval;
if(i != 0)
{
stm32_task_queue[prio][i-1].next = &stm32_task_queue[prio];
}
return 0;
}
}
return -1;
}
/*@brief: STM32 dispatch task..
*@param : None.
*@return: None.
*/
void stm32_dispatch_task(void)
{
uint8_t i,j;
static uint64_t count = 0;
task_t *ptask;
count++;
for(j=0;j<TASK_PRIO_MAX;j++)
{
ptask = &stm32_task_queue[j][0];
for(i=0;i<TASK_NUM_MAX;i++)
{
if((ptask != NULL)&&(ptask->task != NULL))
{
if((count *TIM2_INTERVAL)%ptask->interval == 0)
{
ptask->task();
ptask = ptask->next;
}
}
else
break;
}
}
}
以上,基本上就把自己这个工程的思路交代清除了,接下来从头说说自己第一次用这个芯片遇到的一些坑吧。
- 第一个就是系统时钟初始化了,大家都知道单片机要跑起来肯定是少不了clock的。追求精度和稳定性的话,我们一般肯定是外部晶体或者时钟源优先了。具体不多说,各家芯片基本上都是同样的操作,选择时钟源,等待切换成功,如果需要倍频还需要借助PLL的帮助。这里我就卡住了一段事件,因为发现倍频到72MHz之后程序不运行了,后来才发现是系统频率太高,片上flash老胳膊老腿跟不上,需要增加等待时间,看了闪存编程手册才发现这一点,怪自己当初看手册不详细,user manual里面是有提到的。
- 读ds18b20的温度时,用示波器看什么都对,就是读不出来,后来google才发现,百度下了个不知道哪来的手册,气死我了,这里强烈推荐大家尽量谷歌。
- 中间有几次发现程序莫名奇妙的会跑飞,有时候又正常,后来debug发现原来是定时器初始化的比较早,当进定时器中断做任务的dispatch的时候,有些模块的回调函数还没有初始化,这个时候就会出问题。
- 还有一个问题自己现在也不是太明白,就是在操作w25q64的时候,比如sector erase和write enable,如果我只发送,忽略DO出来的数据不做读取,这个时候看波形,通讯时正常的,但是w25q64内部貌似没有执行对应的动作,因为我发现,没有擦除成功,虽然看手册上这两条指令把DO线也画出来了,但是理论上读不读应该没关系才对。
欢迎大家讨论交流,代码写得匆忙又丑陋,我就不全部贴上了,不过如果有什么感兴趣的地方可以拿出来和大家分享。
|