智能家居之主机--驱动层搭建
- bsp-底层驱动
- bsp_gpio
- bsp_adc
- bsp_uart
- bsp_timer
- 伪调度
bsp-底层驱动
bsp_gpio
利用一个config.h的配置文件,把所有要使用的gpio的属性配置好,这样有一个好处,比较集中,也比较好查,不需要一个文件一个文件去找,在config文件里面就把pin或者属性改掉了。首先定义一个结构体
typedef struct
{
uint32_t RCC_GPIOx :32;
uint16_t gpio_pin :16;
uint16_t gpio_mode :8;
uint16_t gpio_speed :8;
}gpio_config_t;
(讲解:后面的:<num>
是定义了一个num位的变量,但是只占用其中num位个内存,这样的好处是更加节省内存,名词叫位域)
一般刚入门的不太会去考虑在写代码的时候内存不够用的情况,要么就是全局变量满天飞,要么就是可能只需要存一个布尔型的参数的时候定义了一个占用32位内存的一个参数,如果是103c8t6这种内部flash并不大的芯片来说,如果代码量比较大的话肯定是不行的;可以来做个实验
#include<stdio.h>
#include<string.h>
typedef struct{
unsigned int a : 4;
unsigned int b : 1;
} test_t;
typedef struct{
unsigned int a;
unsigned int b;
} test1_t;
test_t test;
test1_t test1;
int main()
{
test.a = 10;
test.b = 1;
test1.a = 10;
test1.b = 1;
printf("test_size:%d\r\n",sizeof(test));
printf("test1_size:%d\r\n",sizeof(test1));
return 0;
}
两个一样的结构体,一个限制位域一个不限制,运行一下看看这两个结构体占用内存的大小情况。
test_size:4
test1_size:8
画个图就好理解了
这个是test结构体的存放
这是test1的
所以回看上面gpio的结构体,如果不定义位域的话应该是占用10个字节,但是现在只占用了8个字节,可以通过keil的map来看一下内存使用情况。
然后就可以通过config的形式进行配置了
#define ORANGE_LED_GPIO_CONFIG \
{ \
RCC_APB2Periph_GPIOB, \
GPIO_Pin_9, \
GPIO_Mode_Out_OD, \
GPIO_Speed_50MHz, \
}
bsp_adc
和bsp_gpio的定义方法相似,不过结构体更加复杂了一些,直接上代码;
typedef struct
{
uint32_t RCC_ADCx :32;
uint32_t adc_mode :20;
FunctionalState adc_scanconvmode :1;
FunctionalState adc_continuousconvmode :1;
uint32_t adc_externaltrigconv :20;
uint32_t adc_dataalign :12;
uint8_t adc_nbrofchannel :4;
uint8_t Rank :4;
uint8_t ADC_SampleTime :4;
}adc_config_t;
那么相应的配置就是
#define MQ_2_ADC_CONFIG \
{ \
RCC_APB2Periph_ADC1, \
ADC_Mode_Independent, \
DISABLE, \
DISABLE, \
ADC_ExternalTrigConv_None, \
ADC_DataAlign_Right, \
1, \
1, \
ADC_SampleTime_239Cycles5, \
}
再来看看map文件adc占用的内存字节数
那么bsp的ADC初始化函数就可以这样写
void BSP_ADC_Init(ADC_TypeDef* ADCx,adc_config_t adc_config)
{
ADC_InitTypeDef ADC_InitStructure;
RCC_APB2PeriphClockCmd(adc_config.RCC_ADCx,ENABLE);
ADC_DeInit(ADCx);
ADC_InitStructure.ADC_Mode = adc_config.adc_mode;
ADC_InitStructure.ADC_ScanConvMode = adc_config.adc_scanconvmode;
ADC_InitStructure.ADC_ContinuousConvMode = adc_config.adc_continuousconvmode;
ADC_InitStructure.ADC_ExternalTrigConv = adc_config.adc_externaltrigconv;
ADC_InitStructure.ADC_DataAlign = adc_config.adc_dataalign;
ADC_InitStructure.ADC_NbrOfChannel = adc_config.adc_nbrofchannel;
ADC_Init(ADCx, &ADC_InitStructure);
ADC_Cmd(ADCx, ENABLE);
ADC_ResetCalibration(ADCx);
while(ADC_GetResetCalibrationStatus(ADCx));
ADC_StartCalibration(ADCx);
while(ADC_GetCalibrationStatus(ADCx));
}
后面定义初始化ADC的时候就不需要一大堆了,只需要调用这个函数把参数传进去即可。
bsp_uart
串口结构体定义:
typedef struct
{
uint32_t RCC_USARTx :32;
uint32_t bound :32;
uint16_t wordlength :16;
uint16_t stopbit :16;
uint16_t parity :12;
uint16_t hwflowcontrol :12;
uint16_t mode :4;
uint8_t irqchannel :8;
uint8_t irqpreemption :8;
uint8_t irqSub :8;
}uart_config_t;
void BSP_UART_Init(USART_TypeDef* USARTx,uart_config_t uart_config)
{
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
if(USARTx == USART1)
RCC_APB2PeriphClockCmd(uart_config.RCC_USARTx,ENABLE);
else
RCC_APB1PeriphClockCmd(uart_config.RCC_USARTx,ENABLE);
USART_InitStructure.USART_BaudRate = uart_config.bound;
USART_InitStructure.USART_WordLength = uart_config.wordlength;
USART_InitStructure.USART_StopBits = uart_config.stopbit;
USART_InitStructure.USART_Parity = uart_config.parity;
USART_InitStructure.USART_HardwareFlowControl = uart_config.hwflowcontrol;
USART_InitStructure.USART_Mode = uart_config.mode;
USART_Init(USARTx, &USART_InitStructure);
USART_ITConfig(USARTx, USART_IT_RXNE, ENABLE);
USART_Cmd(USARTx, ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = uart_config.irqchannel;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=uart_config.irqpreemption;
NVIC_InitStructure.NVIC_IRQChannelSubPriority =uart_config.irqSub;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
注意由于串口1和串口2需要开的RCC时钟不同,底层直接区分开感觉会方便点;
bsp_timer
同理直接上代码吧
typedef struct
{
uint32_t period :16;
uint16_t Prescaler :16;
uint16_t countermode :8;
uint16_t clockdivision :12;
uint8_t irq_channel :8;
uint8_t irq_preemption :8;
uint8_t irq_subpriority :8;
bool enble_sta :1;
}timer_config_t;
void BSP_TIME_Init(uint32_t RCC_TIMx,TIM_TypeDef* TIMx,timer_config_t timer_config)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_TIMx,ENABLE);
TIM_TimeBaseInitStructure.TIM_Period = timer_config.period;
TIM_TimeBaseInitStructure.TIM_Prescaler=timer_config.Prescaler;
TIM_TimeBaseInitStructure.TIM_CounterMode=timer_config.countermode;
TIM_TimeBaseInitStructure.TIM_ClockDivision=timer_config.clockdivision;
TIM_TimeBaseInit(TIMx,&TIM_TimeBaseInitStructure);
TIM_ITConfig(TIMx,TIM_IT_Update,ENABLE);
NVIC_InitStructure.NVIC_IRQChannel=timer_config.irq_channel;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=timer_config.irq_preemption;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=timer_config.irq_subpriority;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIMx,timer_config.enble_sta);
}
伪调度
定时器初始化后就可以开始搭建伪调度了,首先定义一个伪调度的结构体;
typedef struct
{
void(*task_func)(void);
uint16_t rate_hz;
uint16_t interval_ticks;
uint32_t last_run;
}sched_task_t;
将每个任务和轮询时间定义好后放入放入结构体中;
static sched_task_t sched_task[]=
{
{loop_1ms,1000,0,0},
{loop_10ms,100,0,0},
{loop_100ms,10,0,0},
{loop_1s,1,0,0},
};
我定义了这些时间段的任务,然后进行轮询任务的轮询结束时间进行计算;
void loop_check(void)
{
u8 index = 0;
for(index = 0;index <task_num;index++ )
{
sched_task[index].interval_ticks = 1000/sched_task[index].rate_hz;
if(sched_task[index].interval_ticks < 1)
sched_task[index].interval_ticks = 1;
}
}
然后就可以开始轮询任务了,通过duty_loop函输,通过当前的时间和结束时间还有轮询时间进行对比,如果大于就执行对应函数;
void duty_loop(void)
{
u8 index = 0;
for(index = 0;index < task_num;index++)
{
uint32_t tnow = get_sys_time_ms();
if(tnow - sched_task[index].last_run >= sched_task[index].interval_ticks)
{
sched_task[index].last_run = tnow;
sched_task[index].task_func();
}
}
}
然后我们就可以在loop_1s
函数里面加入一个LED亮灭的函数,不需要使用delay函数看看是否能够1s切换。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)