一、流程
printf函数, 是如何调用串口的呢?整理出这个流程,可以帮助我们修改SDK。
首先,我们知道,C语言中,printf函数循环调用fputc函数。在sdk工程中搜索fputc函数,我们可以得到如下内容。
int fputc(int ch, FILE *stream)
{
(void)stream;
if (console_handle == NULL) {
return -1;
}
if (ch == '\n') {
csi_usart_putchar(console_handle, '\r');
}
csi_usart_putchar(console_handle, ch);
return 0;
}
csi_usart_putchar函数
int32_t csi_usart_putchar(usart_handle_t handle, uint8_t ch)
{
return drv_usi_usart_putchar(handle, ch);
}
drv_usi_usart_putchar函数
int32_t drv_usi_usart_putchar(usart_handle_t handle, uint8_t ch)
{
wj_usi_usart_priv_t *usart_priv = handle;
wj_usi_reg_t *addr = (wj_usi_reg_t *)(usart_priv->base);
//addr->USI_EN = 0xb;
//addr->USI_EN = 0xf;
addr->USI_TX_RX_FIFO = ch;
while (!(addr->USI_FIFO_STA & 0x1));
return 0;
}
可以看出,这三个函数说明了printf是如何输出到串口的。printf函数循环调用fputc函数,fputc函数调用了csi_usart_putchar函数,csi_usart_putchar实际为drv_usi_usart_putchar函数,drv_usi_usart_putchar函数将USI_TX_RX_FIFO的数据取出送出去。这样,printf函数的内容,被UART串口送出,被PC端的串口接收,显示出来。流程图如下。
二、思路
看drv_usi_usart_putchar函数的代码,数据的基地址为addr,而addr是从(wj_usi_reg_t *)(usart_priv->base)来的。那我们在SDK工程中搜索wj_usi_reg_t和usart_priv。结果如下
typedef struct {
__IOM uint32_t USI_EN; /* Offset 0x000(R/W) */
__IOM uint32_t USI_MODE_SEL; /* Offset 0x004(R/W) */
__IOM uint32_t USI_TX_RX_FIFO; /* Offset 0x008(R/W) */
__IOM uint32_t USI_FIFO_STA; /* Offset 0x00c(R/W) */
__IOM uint32_t USI_CLK_DIV0; /* Offset 0x010(R/W) */
__IOM uint32_t USI_CLK_DIV1; /* Offset 0x014(R/W) */
__IOM uint32_t USI_UART_CTRL; /* Offset 0x018(R/W) */
__IOM uint32_t USI_UART_STA; /* Offset 0x01c(R/W) */
__IOM uint32_t USI_I2C_MODE; /* Offset 0x020(R/W) */
__IOM uint32_t USI_I2C_ADDR; /* Offset 0x024(R/W) */
__IOM uint32_t USI_I2CM_CTRL; /* Offset 0x028(R/W) */
__IOM uint32_t USI_I2CM_CODE; /* Offset 0x02c(R/W) */
__IOM uint32_t USI_I2CS_CTRL; /* Offset 0x030(R/W) */
__IOM uint32_t USI_I2C_FM_DIV; /* Offset 0x034(R/W) */
__IOM uint32_t USI_I2C_HOLD; /* Offset 0x038(R/W) */
__IOM uint32_t USI_I2C_STA; /* Offset 0x03c(R/W) */
__IOM uint32_t USI_SPI_MODE; /* Offset 0x040(R/W) */
__IOM uint32_t USI_SPI_CTRL; /* Offset 0x044(R/W) */
__IOM uint32_t USI_SPI_STA; /* Offset 0x048(R/W) */
__IOM uint32_t USI_INTR_CTRL; /* Offset 0x04c(R/W) */
__IOM uint32_t USI_INTR_EN; /* Offset 0x050(R/W) */
__IOM uint32_t USI_INTR_STA; /* Offset 0x054(R/W) */
__IOM uint32_t USI_RAW_INTR_STA; /* Offset 0x058(R/W) */
__IOM uint32_t USI_INTR_UNMASK; /* Offset 0x05c(R/W) */
__IOM uint32_t USI_INTR_CLR; /* Offset 0x060(R/W) */
__IOM uint32_t USI_DMA_CTRL; /* Offset 0x064(R/W) */
__IOM uint32_t USI_DMA_THRESHOLD; /* Offset 0x068(R/W) */
} wj_usi_reg_t;
wj_usi_usart_priv_t *usart_priv = &usi_usart_instance[idx];
wj_usi_usart_priv_t为一个结构体,usart_priv是一个指向它的指针,具体值为usi_usart_instance[idx],那我们继续查找usi_usart_instance和idx。usi_usart_instance结果如下,idx是传进来的参数,这个我们一会讨论。
static wj_usi_usart_priv_t usi_usart_instance[CONFIG_USI_NUM];
在工程中查找CONFIG_USI_NUM,结果如下。是3,暂时看不出来从哪里计算出来的3。看来走到了头。那我们走usi_usart_instance的路走不通,我们去走idx的路。
#define CONFIG_USI_NUM 3
wj_usi_usart_priv_t *usart_priv = &usi_usart_instance[idx];这句话,在工程中,出现了两次,都在wj_usi_usart.c中。我们去找到它俩,可以看出,它俩分别是两个函数中的一个话, 这两个函数是drv_usi_usart_initialize和wj_usi_usart_irqhandler。看名字,第一个是USI的UART初始化函数,第二个是IRQ的中断安装。我们分别去找这两条路。
IRQ路,查找该函数,找到如下内容。根据USI_MODE_SEL选择UART或者I2C或者SPI,这个和我们在前边学习到的,USI0中包含有这三种通信方式相符合。继续查找wj_usi_irqhandler函数
void wj_usi_irqhandler(int32_t idx)
{
wj_usi_priv_t *usi_priv = &usi_instance[idx];
wj_usi_reg_t *addr = (wj_usi_reg_t *)(usi_priv->base);
switch (addr->USI_MODE_SEL & 0x3) {
case USI_MODE_UART:
#ifndef CONFIG_CHIP_PANGU
wj_usi_usart_irqhandler(idx);
#endif
break;
case USI_MODE_I2C:
wj_usi_i2c_irqhandler(idx);
break;
case USI_MODE_SPI:
wj_usi_spi_irqhandler(idx);
break;
default:
return;
}
}
查到结果如下,同时观察上下,三个USI的中断安装实现,同时注意其中的wj_usi_irqhanlder的参数,0、1、2。看起来我们走到了终点。那换一条路。查找drv_usi_usart_initialize函数。
ATTRIBUTE_ISR void USI0_IRQHandler(void)
{
CSI_INTRPT_ENTER();
wj_usi_irqhandler(0);
CSI_INTRPT_EXIT();
}
ATTRIBUTE_ISR void USI1_IRQHandler(void)
{
CSI_INTRPT_ENTER();
wj_usi_irqhandler(1);
CSI_INTRPT_EXIT();
}
ATTRIBUTE_ISR void USI2_IRQHandler(void)
{
CSI_INTRPT_ENTER();
wj_usi_irqhandler(2);
CSI_INTRPT_EXIT();
}
查找结果如下,被csi_usart_initialize函数调用。继续。
usart_handle_t csi_usart_initialize(int32_t idx, usart_event_cb_t cb_event)
{
return drv_usi_usart_initialize(idx, cb_event);
}
在board_init.c中,如下。看名字是一个初始化函数。
console_handle = csi_usart_initialize(CONSOLE_IDX, NULL);
看board_init.c文件中的函数board_init(),其中,和UART相关的有两个,第一个是一个初始化函数,第二个是一个配置函数。
void board_init(void)
{
int32_t ret = 0;//32位有符号数
/* init the console*/
clock_timer_init();
clock_timer_start();
console_handle = csi_usart_initialize(CONSOLE_IDX, NULL);//csi_usart_initialize --> return drv_usi_usart_initialize(CONSOLE_IDX, NULL);
//CONSOLE_IDX = 0 in the file "pin.h"
/* config the UART */
ret = csi_usart_config(console_handle, 115200, USART_MODE_ASYNCHRONOUS, USART_PARITY_NONE, USART_STOP_BITS_1, USART_DATA_BITS_8);
//return drv_usi_usart_config(console_handle, 115200, USART_MODE_ASYNCHRONOUS, USART_PARITY_NONE, USART_STOP_BITS_1, USART_DATA_BITS_8);
if (ret < 0) {
return;
}
}
查找board_init()函数的调用情况。在START.s,即初始化文件中,可以查到如下。
#ifndef __NO_BOARD_INIT
120: jal board_init
121 #endif
查找csi_usart_initialize中的CONSOLE_IDX,结果如下
#define CONSOLE_IDX 0
是0,结合上面出现的0、1、2,还有CONFIG_USI_NUM=3,再想到无剑100一共有3个USI模块,看起来很巧合。我们在查找一下CONFIG_USI_NUM。
const sg_usi_config[CONFIG_USI_NUM] = {
{WJ_USI0_BASE, USI0_IRQn, USI0_IRQHandler},
{WJ_USI1_BASE, USI1_IRQn, USI1_IRQHandler},
{WJ_USI2_BASE, USI2_IRQn, USI2_IRQHandler},
};
在的devices.c中,如上,看起来和我们的猜测相符合,sg_usi_config是一个3行数组,每一行都是一个USI相关信息。我们分别取查找一下。
#define WJ_USI0_BASE (0x50028000UL)
#define WJ_USI1_BASE (0x60028000UL)
#define WJ_USI2_BASE (0x50029000UL)
USI0_IRQn = 28, /* usi0 Interrupt */
USI1_IRQn = 29, /* usi1 Interrupt */
USI2_IRQn = 30, /* usi2 Interrupt */
ATTRIBUTE_ISR void USI0_IRQHandler(void)
{
CSI_INTRPT_ENTER();
wj_usi_irqhandler(0);
CSI_INTRPT_EXIT();
}
ATTRIBUTE_ISR void USI1_IRQHandler(void)
{
CSI_INTRPT_ENTER();
wj_usi_irqhandler(1);
CSI_INTRPT_EXIT();
}
ATTRIBUTE_ISR void USI2_IRQHandler(void)
{
CSI_INTRPT_ENTER();
wj_usi_irqhandler(2);
CSI_INTRPT_EXIT();
}
?结合上面的信息,我们可以看到,在board_init()函数中,初始化了UART,并配置了UART的参数,而有3个USI,CONSOLE_IDX=0表明了使用的是USI0。将这一切整理为一个流程图如下。
说明各函数作用。
初始化路线,board_init调用csi_usart_initialize函数,csi_usart_initialize掉用drv_usi_usart_initialize。在drv_usi_usart_initialize中,共调用五个函数。
1)target_usi_usart_init,根据idx的值,将sg_usi_config的基地址、IRQ中断号和安装程序赋值给我们要使用的。默认情况下,idx为0,即将USI0作为要使用的USI模块。?
int32_t target_usi_usart_init(int32_t idx, uint32_t *base, uint32_t *irq, void **handler)
{
if (idx >= CONFIG_USI_SPI_NUM) {
return -1;
}
if (base != NULL) {
*base = sg_usi_config[idx].base;
}
if (irq != NULL) {
*irq = sg_usi_config[idx].irq;
}
if (handler != NULL) {
*handler = sg_usi_config[idx].handler;
}
return idx;
}
2)drv_usi_initializ函数,给usi_priv指向的w_usi_priv_t结构体赋值
int32_t drv_usi_initialize(int32_t idx)
{
uint32_t base = 0u;
uint32_t irq = 0u;
int32_t ret = target_usi_init(idx, &base, &irq);
if (ret < 0 || ret >= CONFIG_USI_NUM) {
return ERR_USI(DRV_ERROR_PARAMETER);
}
wj_usi_priv_t *usi_priv = &usi_instance[idx];
usi_priv->base = base;
usi_priv->irq = irq;
return 0;
}
3)wj_usi_set_rxfifo_th,设置FIFO
void wj_usi_set_rxfifo_th(wj_usi_reg_t *addr, uint32_t length)
{
addr->USI_INTR_CTRL &= ~USI_INTR_CTRL_TH_MODE;
addr->USI_INTR_CTRL &= USI_INTR_CTRL_RXFIFO_TH;
if (length >= USI_RX_MAX_FIFO) {
addr->USI_INTR_CTRL |= USI_INTR_CTRL_RXFIFO_TH_12 | USI_INTR_CTRL_TH_MODE;
} else if (length >= USI_RX_MAX_FIFO - 4) {
addr->USI_INTR_CTRL |= USI_INTR_CTRL_RXFIFO_TH_8 | USI_INTR_CTRL_TH_MODE;
} else if (length >= 4) {
addr->USI_INTR_CTRL |= USI_INTR_CTRL_RXFIFO_TH_4 | USI_INTR_CTRL_TH_MODE;
} else {
addr->USI_INTR_CTRL |= USI_INTR_CTRL_RXFIFO_TH_4;
}
}
4)drv_irq_register,配置IRQ的寄存器
void drv_irq_register(uint32_t irq_num, void *irq_handler)
{
g_irqvector[irq_num] = irq_handler;
}
5)drv_irq_enable,使能IRQ与否
void drv_irq_enable(uint32_t irq_num)
{
#ifdef CONFIG_SYSTEM_SECURE
csi_vic_enable_sirq(irq_num);
#else
csi_vic_enable_irq(irq_num);
#endif
}
配置UART串口参数路线,各个函数的功能由函数名就可以看出,再次不赘述,且与添加USI之后更改SDK无关。
三、更改
综上,我们得到了更改SDK所需要的信息。那么有两种思路可以选择。第一,将USI0的基地址,替换为dummy_top0的基地址。所谓偷梁换柱法。如下,即可
#define WJ_USI0_BASE (0x50028000UL)
换成
#define WJ_USI0_BASE (0x50004000UL)
第二种,讲我们挂的USI_myself,作为第四个USI,写入程序,并将初始化的USI从USI0改为USI_myself。具体需要更改的位置很多,如下。
1)soc.h中新增基地址
#define WJ_USI0_BASE (0x50028000UL)
#define WJ_USI1_BASE (0x60028000UL)
#define WJ_USI2_BASE (0x50029000UL)
#define WJ_USI0_BASE_myself (0x50004000UL)
2)soc.h中新增中断号
typedef enum IRQn {
User_Software_IRQn = 0, /* User software interrupt */
Supervisor_Software_IRQn = 1, /* Supervisor software interrupt */
Machine_Software_IRQn = 3, /* Machine software interrupt */
User_Timer_IRQn = 4, /* User timer interrupt */
Supervisor_Timer_IRQn = 5, /* Supervisor timer interrupt */
CORET_IRQn = 7, /* core Timer Interrupt */
GPIO0_IRQn = 16, /* uart Interrupt */
TIM0_IRQn = 17, /* timer0 Interrupt */
TIM1_IRQn = 18, /* timer1 Interrupt */
TIM2_IRQn = 19, /* timer2 Interrupt */
TIM3_IRQn = 20, /* timer3 Interrupt */
TIM4_IRQn = 21, /* timer4 Interrupt */
TIM5_IRQn = 22, /* timer5 Interrupt */
TIM6_IRQn = 23, /* timer6 Interrupt */
TIM7_IRQn = 24, /* timer7 Interrupt */
PWM_IRQn = 25, /* pwm Interrupt */
RTC_IRQn = 26, /* rtc Interrupt */
WDT_IRQn = 27, /* wdt Interrupt */
USI0_IRQn = 28, /* usi0 Interrupt */
USI1_IRQn = 29, /* usi1 Interrupt */
USI2_IRQn = 30, /* usi2 Interrupt */
PMU_IRQn = 31, /* pmu Interrupt */
DMAC0_IRQn = 32, /* dmac0 Interrupt */
TIM8_IRQn = 33, /* timer8 Interrupt */
TIM9_IRQn = 34, /* timer9 Interrupt */
TIM10_IRQn = 35, /* timer10 Interrupt */
TIM11_IRQn = 36, /* timer11 Interrupt */
TIM12_IRQn = 37, /* timer12 Interrupt */
TIM13_IRQn = 38, /* timer13 Interrupt */
TIM14_IRQn = 39, /* timer14 Interrupt */
TIM15_IRQn = 40, /* timer15 Interrupt */
USI0_IRQn_myself = 41 ,
}
IRQn_Type;
3)devices中,sg_usi_config,新增
const sg_usi_config[CONFIG_USI_NUM] = {
{WJ_USI0_BASE, USI0_IRQn, USI0_IRQHandler},
{WJ_USI1_BASE, USI1_IRQn, USI1_IRQHandler},
{WJ_USI2_BASE, USI2_IRQn, USI2_IRQHandler},
{WJ_USI0_BASE_myself, USI0_IRQn_myself, USI0_IRQHandler},
};
4)soc.h,更改CONFIG_USI_NUM
#define CONFIG_USI_NUM 4
5)devices.c中,新增
extern void USI0_IRQHandler(void);
extern void USI1_IRQHandler(void);
extern void USI2_IRQHandler(void);
extern void USI0_IRQHandler_myself(void);
6)isr.c中,新增
ATTRIBUTE_ISR void USI0_IRQHandler(void)
{
CSI_INTRPT_ENTER();
wj_usi_irqhandler(0);
CSI_INTRPT_EXIT();
}
ATTRIBUTE_ISR void USI1_IRQHandler(void)
{
CSI_INTRPT_ENTER();
wj_usi_irqhandler(1);
CSI_INTRPT_EXIT();
}
ATTRIBUTE_ISR void USI2_IRQHandler(void)
{
CSI_INTRPT_ENTER();
wj_usi_irqhandler(2);
CSI_INTRPT_EXIT();
}
ATTRIBUTE_ISR void USI0_IRQHandler_myself(void)
{
CSI_INTRPT_ENTER();
wj_usi_irqhandler(3);
CSI_INTRPT_EXIT();
}
7)pin.h
#define CONSOLE_IDX 3
四、验证
终于SDK也修改完成,我们要验证一下是否正确了。流程和本系列的文章③一样,看一下打印出来的是否正确就好了。
欢迎留言讨论。
|