先把背景交代一下,上一篇讲了下基本的设计意图和框架,实际上整体框架已经有了,这篇先把整个框架再描述下,再来实现我们的第一个例子。
框架补遗
我把文件目录用 tree /F /A
命令打出来了如下:
-
app和boot:业务代码,其中timer0_task是定时器任务。
-
common:通用代码,一些通用接口,虚拟定时器这些可以放在这里。
-
firmwares:厂家提供的硬件库都放到这个文件夹下面,目前手头有2块开发板,一块是STM32的F103ZET6,一块是GD32的F450ZKT6,所以这里的库是这俩。
-
io:前面介绍过了,这里存放的是io封装层的代码。
+---app
| | main.c
| | timer0_task.c
|
+---boot
| main.c
|
+---common
| | virtual_timer.c
|
+---firmwares
| +---GD32F4xx_Firmware_Library
| | +
| | | ··· ···
| |
| \---STM32F10x_Firmware_Library
| | +
| | | ··· ···
|
|
\---io
| io.c
|
+---gd32
| interrupt.c
| io_crc.c
| io_gd32.c
| io_gd32.h
| io_gpio.c
| io_system.c
| io_timer.c
| io_uart.c
|
+---include
| io.h
| io_crc.h
| io_gpio.h
| io_system.h
| io_timer.h
| io_uart.h
|
\---stm32
interrupt.c
io_crc.c
io_gpio.c
io_stm32.c
io_stm32.h
io_system.c
io_timer.c
io_uart.c
由于本人比较认可兆易创新的封装方式,后续的代码封装尽量往GD上面靠;资源的命名按照常规的方式,串口1串口2这样的,而不是串口0串口1这样的。
开发板照片(红色箭头处是串口):
回到正题
我们先来做一个稍微比较不简单的例子:硬件计算CRC值并通过串口打印到电脑上。
先盘点下去需要的资源:
-
内核相关的:包括时钟分频,nvic分组,系统tick等等
-
虚拟OS:包括系统定时器
-
资源相关的:硬件CRC模块,串口模块
内核相关 - 时钟分频
按默认,具体的去参见system_型号.c,就不细说了。
内核相关 - 系统tick
通用的,来自Core_m标准。
void system_tick_config(void)
{
if (SysTick_Config(SystemCoreClock / SYSTEM_TICK_COUNT)){
/* capture error */
while (1){
}
}
/* configure the systick handler priority */
NVIC_SetPriority(SysTick_IRQn, 0x00U);
}
void delay_us(uint32_t count)
{
delay = count;
while(0U != delay){
}
}
void delay_ms(uint32_t count)
{
delay = 1000*count;
while(0U != delay){
}
}
void delay_decrement(void)
{
if (0U != delay){
delay--;
}
}
内核相关 - nvic优先级
通用的,来自Core_m标准。
void nvic_priority_group_0_4(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
}
void nvic_priority_group_1_3(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
}
void nvic_priority_group_2_2(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
}
void nvic_priority_group_3_1(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);
}
void nvic_priority_group_4_0(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
}
针对STM派,封装NVIC中断配置:
void nvic_irq_enable(uint8_t channel, uint8_t pre_priority, uint8_t sub_priority)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = channel;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = pre_priority;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = sub_priority;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
系统定时器 - 驱动虚拟OS
采用定时器2(STM中是TIM2,GD中是TIMER1,-_-),需要进行不同的封装。
这里可以比对下代码,其他资源的操作跟这里差不太多。
这里再多嘴一句系统资源软件开发的基本步骤就三步:初始化时钟 - 配置使能 - 中断。
针对STM派,代码如下:
#include "io.h"
#include "io_stm32.h"
static void timerx_init(TIM_TypeDef* TIMx, uint16_t prescaler, uint16_t period);
static void timerx_init_full(TIM_TypeDef* TIMx, uint16_t prescaler, uint16_t period, uint16_t clock_div, uint16_t counter_mode, uint8_t repetition_counter);
void timer2_init(void)
{
TIM_DeInit(TIM2);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
timerx_init(TIM2, 7199, 9); // 10KHz 1ms
nvic_irq_enable(TIM2_IRQn, 2, 0);
}
/******************************************************************************************/
/******************************************************************************************/
/******************************************************************************************/
/******************************************************************************************/
static void timerx_init(TIM_TypeDef* TIMx, uint16_t prescaler, uint16_t period)
{
timerx_init_full(TIMx, prescaler, period, TIM_CKD_DIV1, TIM_CounterMode_Up, 0);
}
static void timerx_init_full(TIM_TypeDef* TIMx, uint16_t prescaler, uint16_t period, uint16_t clock_div, uint16_t counter_mode, uint8_t repetition_counter)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Period = prescaler;
TIM_TimeBaseStructure.TIM_Prescaler = period;
TIM_TimeBaseStructure.TIM_ClockDivision = clock_div;
TIM_TimeBaseStructure.TIM_CounterMode = counter_mode;
TIM_TimeBaseInit(TIMx, &TIM_TimeBaseStructure);
TIM_ITConfig(TIMx, TIM_IT_Update, ENABLE );
TIM_Cmd(TIMx, ENABLE);
}
针对非STM派:
#include "io.h"
#include "io_gd32.h"
static void timerx_init(uint32_t TIMx, uint16_t prescaler, uint16_t period);
static void timerx_init_full(uint32_t TIMx, uint16_t prescaler, uint16_t period, uint16_t clock_div, uint16_t counter_mode, uint8_t repetition_counter);
void timer2_init(void)
{
timer_deinit(TIMER1);
rcu_periph_clock_enable(RCU_TIMER1);
timerx_init(TIMER1, 9999, 9); // 10KHz 1ms
timer_interrupt_enable(TIMER1, TIMER_INT_UP);
nvic_irq_enable(TIMER1_IRQn, 1, 2);
}
/******************************************************************************************/
/******************************************************************************************/
/******************************************************************************************/
/******************************************************************************************/
static void timerx_init(uint32_t TIMx, uint16_t prescaler, uint16_t period)
{
timerx_init_full(TIMx, prescaler, period, TIMER_CKDIV_DIV1, TIMER_COUNTER_UP, 0);
}
static void timerx_init_full(uint32_t TIMx, uint16_t prescaler, uint16_t period, uint16_t clock_div, uint16_t counter_mode, uint8_t repetition_counter)
{
timer_parameter_struct timer_initpara;
timer_initpara.prescaler = prescaler;
timer_initpara.alignedmode = TIMER_COUNTER_EDGE;
timer_initpara.counterdirection = counter_mode;
timer_initpara.period = period;
timer_initpara.clockdivision = clock_div;
timer_initpara.repetitioncounter = repetition_counter;
timer_init(TIMx, &timer_initpara);
timer_update_event_enable(TIMx);
timer_enable(TIMx);
}
资源 - 串口模块(打印)
采用串口1(STM中是USART1,GD中是USART0,-- --)。
串口的初始化没啥好说的,直接给代码吧。
STM派:
#include "io.h"
#include "io_stm32.h"
#include "io_uart.h"
void uart1_init(uint32_t baudrate)
{
USART_InitTypeDef USART_InitStructure;
USART_TypeDef* USART = USART1;
USART_DeInit(USART);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
gpio_init(GPIOA, GPIO_Mode_AF_PP, GPIO_Speed_50MHz, GPIO_Pin_9); ///< USART1_TX PA.9
gpio_init(GPIOA, GPIO_Mode_IN_FLOATING, GPIO_Speed_50MHz, GPIO_Pin_10); ///< USART1_RX PA.10
USART_StructInit(&USART_InitStructure);
USART_InitStructure.USART_BaudRate = baudrate; ///< 波特率
USART_Init(USART, &USART_InitStructure);
USART_ITConfig(USART, USART_IT_RXNE, ENABLE); ///< 使能中断
USART_Cmd(USART, ENABLE); ///< 使能串口
nvic_irq_enable(USART1_IRQn, 3, 3);
}
非STM派:
#include "io.h"
#include "io_gd32.h"
#include "io_uart.h"
void uart1_init(uint32_t baudrate)
{
uint32_t com = USART0;
usart_deinit(com);
rcu_periph_clock_enable(RCU_USART0);
rcu_periph_clock_enable(RCU_GPIOA);
///< USART1_TX PA.9
gpio_af_set(GPIOA, GPIO_AF_7, GPIO_PIN_9);
gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_9);
gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
///< USART1_RX PA.10
gpio_af_set(GPIOA, GPIO_AF_7, GPIO_PIN_10);
gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_10);
gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_10);
usart_baudrate_set(com, baudrate);
usart_word_length_set(com, USART_WL_8BIT);
usart_stop_bit_set(com, USART_STB_1BIT);
usart_parity_config(com, USART_PM_NONE);
usart_hardware_flow_rts_config(com, USART_RTS_DISABLE);
usart_hardware_flow_cts_config(com, USART_CTS_DISABLE);
usart_receive_config(com, USART_RECEIVE_ENABLE);
usart_transmit_config(com, USART_TRANSMIT_ENABLE);
usart_enable(com);
nvic_irq_enable(USART0_IRQn, 3, 3);
}
需要特别说明的是:如果需要串口支持printf,需要重新实现fputc函数并关闭半主机模式(semihosting),代码如下:
STM派:
#ifdef USING_PRINTF
#define PRINTF_UART USART1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
/* Whatever you require here. If the only file you are using is */
/* standard output using printf() for debugging, no file handling */
/* is required. */
};
/* FILE is typedef’ d in stdio.h. */
FILE __stdout;
void _sys_exit(int x)
{
x = x;
}
/* retarget the C library printf function to the USART */
int fputc(int ch, FILE *f)
{
USART_SendData(PRINTF_UART, (uint8_t)ch );
while(USART_GetFlagStatus(PRINTF_UART, USART_FLAG_TXE)==RESET);
return ch;
}
#endif
非STM派:
#ifdef USING_PRINTF
#define PRINTF_UART USART0
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
/* Whatever you require here. If the only file you are using is */
/* standard output using printf() for debugging, no file handling */
/* is required. */
};
/* FILE is typedef’ d in stdio.h. */
FILE __stdout;
void _sys_exit(int x)
{
x = x;
}
/* retarget the C library printf function to the USART */
int fputc(int ch, FILE *f)
{
usart_data_transmit(PRINTF_UART, (uint8_t)ch);
while(RESET == usart_flag_get(PRINTF_UART, USART_FLAG_TBE));
return ch;
}
#endif
资源 - 硬件CRC模块
硬件CRC的使用比较简单,具体的可以读一下参考手册;把数传进去,然后读结果,读完结果之后记得清寄存器。
硬件CRC是CRC32,标准上是:CRC32/MPEG-2,多项式是:x32 + x26 + x23 + x22 + x16 + x12 + x11 + x10 + x8 + x7 + x5 + x4 + x2 + x + 1,即:0x04C11DB7,初始值是:0xFFFFFFFF,输入数据反转:为false,输出数据反转:为false,结果异或值是:0x00000000。
关于CRC,可以参考我的另一篇文章:史上解释CRC最清楚的文章
需要把STM的接口进行封装:
#ifndef __IO_CRC_H__
#define __IO_CRC_H__
#ifdef STM32
#define crc_deinit CRC_ResetDR
#define crc_data_register_reset CRC_ResetDR
#define crc_single_data_calculate CRC_CalcCRC
#define crc_block_data_calculate CRC_CalcBlockCRC
#define crc_free_data_register_write CRC_SetIDRegister
#define crc_free_data_register_read CRC_GetIDRegister
#endif
void crc_init(void);
#endif /* __IO_CRC_H__ */
CRC测试代码:
static void crc_test(void)
{
uint32_t data = 0xabcd1234;
uint32_t crc_data = crc_single_data_calculate(data);
printf("crc32 of 0x%X = 0x%X\r\n", data, crc_data);
crc_data_register_reset();
}
结果演示
STM32F1-COM3,GD32F4-COM9
--EOF--
放个码维二,求粉,谢谢!