编写读取内存的程序 ——keil uVision3 printf函数重定向到串口输出

今天同事让我帮忙打印DSP芯片的配置信息,该信息在内存里,所以要打印内存某个指定地址的内容。怎么实现?第一次做这么底层的事情,总结下经验。

刚好前两个星期把驱动看完了,知道实现思路如下:

1.因为用的是CortexM3系统,没有移植文件系统,所以,不能打印成文件,只能用串口输出。

2.串口输出用什么函数?printf就可以了,系统一般也提供了DEBUG宏,该宏只是封装了printf。

3.printf怎么实现的?要有串口驱动uart.c.他是库函数,好像需要先在串口驱动中实现putc函数,不过我没发现uart驱动有这个函数,以后有机会再研究。网络搜到解释:

printf需要调用 _putc函数,你在自己的代码里,重定向 _putc就行。在_putc里面向串口输出字符。具体的实现方法在对应的编译器里面有

我转了3篇文章,研究这个问题,估计在Keil或者其它开发工具里,要配置串口和GNU的接口,让你写好的UART的read, write函数关联到printf。这个过程也可能是自动的,只不过是我不知道去哪里找这个设置。

http://blog.csdn.net/xzongyuan/article/details/28626163

http://blog.csdn.net/xzongyuan/article/details/28625457

 http://blog.csdn.net/xzongyuan/article/details/28632495 (STM32 keil printf的使用

下面是最新的研究成果,在我的系统里,看到debug.c定义了fputc,应该是利用这个函数实现UART和fprintf的重定向的。

http://blog.csdn.net/xzongyuan/article/details/28634221

函数如:

void fputc_hook(char ch)
{
    if (DebugType == 0)
    {
        UARTWriteByte(ch, 1000);
    }
    else
    {
        VirtualUartWrite(ch);
    }
}

int fputc(int ch, FILE *f)
{
    uint8 dgbBuffer[DEBUG_TIME_LEN];
    uint32 tmpcnt, i;

    if (ch == '\n')
    {        
        tmpcnt = SysTickCounter;
        for (i = 0; i < DEBUG_TIME_LEN; i++)
        {
            dgbBuffer[i] = tmpcnt % 10;
            tmpcnt = tmpcnt / 10;
        }
        
        fputc_hook('\r');
        fputc_hook('\n');
        fputc_hook('[');
        for (i = 0; i < DEBUG_TIME_LEN; i++)
        {
            fputc_hook(dgbBuffer[DEBUG_TIME_LEN - 1 -i]+0x30);
            if (DEBUG_TIME_LEN - 1 -i == 2)
            {
                fputc_hook('.');
            }
        }
        fputc_hook(']');
        
        return OK;
    }
    
    fputc_hook(ch);

    return OK;
}


uart.c的相关代码如下,其它函数如debug.c会调用UARTWriteByte和其他的基础函数(用来实现fputc,建立printf和uart的关联):

int32 UARTReadByte(uint8 *pdata, uint32 uartTimeOut)
{
    while ((UartReg->UART_USR & UART_RECEIVE_FIFO_NOT_EMPTY) != UART_RECEIVE_FIFO_NOT_EMPTY)
    {
        if (uartTimeOut == 0)
        {
            return (-1);
        }
        uartTimeOut--;
    }
    
    *pdata = (uint8 )UartReg->UART_RBR;
    
    return (0);
}
#endif

void BT_UARTSend(char * data, int len)
{
   // memcpy(UartContext.TxBuffer, data, len);
   // while(UartContext.isInTx);

    UartContext.isInTx = 1;
    UartContext.TxBufferP = data;
    UartContext.TxBufferSize = len;
    UartContext.TxOutIndex = 0;
    UARTSetIntEnabled(UART_IE_TX);

    //UartContext.TxOutIndex++;
    //UartReg->UART_THR = UartContext.TxBuffer[0];
    //UartReg->UART_IIR = UartReg->UART_IIR | UART_IF_THR_EMPTY;

}

4.打印数据的驱动已经有了,我们不用管,只需要像普通的C语言一样写printf就行了。但是怎么获取DSP芯片的数据呢?

5.一般厂家都会提供DSP芯片的驱动,所以我只需要调用这个DSP芯片的API

uint8 Codec_DSPReadMem(uint16 wAdd, uint8 *pbData, uint16 wWordSize)

这个函数,虽然有很多判断语句,但是核心的就是读取I2C数据,因为Soc和DSP之间,一般都是通过I2C协议通信。所以,我找到了核心函数如下:

if (OK != I2CReadData(pbData))

判断读取数据是否成功,把数据读到pbData指针。

这个函数时怎么定义的呢?参考下面两句

#define I2CReadData(Data)           (((pI2CReadData)(Addr_I2CReadData))(Data))

typedef int32 (*pI2CReadData)(UINT8 *Data);

这样看,它是一个宏,定义了一个指针函数,通常,我的理解是这个指针函数会在某个地方被赋值,给他一个实际的函数值(这样,同样的对象,可以调用不同的函数)。但这个地方不是这样用的,他声明一个函数指针,是因为它的作用是把一个void类型的指针转化为函数指针类型,上面的Addr_I2CReadData其实就是一个宏,如下代码,定义了一个指针地址(通常,我们需要把一些常用的模块的函数,指定在一个指定的内存区域,压缩空间,避免系统随机申请函数地址,导致内存碎片太多):

#define    Addr_I2CReadData                (0x00004248)

那这个函数怎么定义呢?如下,把宏看做是一个函数名。里面的内容都是驱动的知识,控制I2C的控制寄存器,这部分内容不难,但是要弄懂不同控制器的datasheet对应的“时序”:

_ATTR_DRIVERLIB_CODE_ //这个是一个属性标识,见解释
int32 I2CReadData(UINT8 *Data)
{
    int intstatus;
    int timeout;  // 超时退出

    timeout = 200000;   // timeout要足够长,以适应个别I2C应答较慢的情况

    I2cReg->I2C_LCMR |= I2C_LCMR_RESUME;
    // waiting ACK
    do
    {

        intstatus = I2cReg->I2C_ISR;
        // Clear INT_MACK status

        if ((intstatus & I2C_INT_AL) != 0)
        {
            // Clear INT_AL status

            I2cReg->I2C_ISR &= ~(I2C_INT_AL);
            // stop
            I2CStop();

            return ERROR;
        }
        Delay10cyc(1);
        timeout--;
    }
    while (((intstatus & I2C_INT_MACKP) == 0) && (timeout > 0));


    *Data = (UINT8)(I2cReg->I2C_MRXR);

    // Clear INT_MACKP status

    I2cReg->I2C_ISR &= ~(I2C_INT_MACKP);

    return OK;
}
#endif
代码中的属性标识是一个宏

#define _ATTR_DRIVERLIB_CODE_       __attribute__((section("DriverLib")))

这是ARM中的scatter功能,通过自定义的scatter文档,可指定某个驱动模块在内存某个地址0x0000aaaa内运行。这个知识要查看ARM官网的scatter文件。

I2C时序图:



6.通过上面的API,我获得了一个指针地址pbData,该地址存放着从I2C中读到的数据。我只需要把这个数据用printf打印出来就可以了。代码如下,要注意打印的是十六进制,且有的数据时8位,有的16位,要小心,不然会打错。

下面的代码是打印0x4000(DSP_MEM_ADD_CRAMTOP)到0x43FF的内容。因为I2C缓存有限,一次读取太多数据会死机,所以,我每次读0x020个字节,循环读取并打印。 

BOOLEAN AudioPause(void)
{
    
uint8 _DSPResult = -1  ;
uint8 _Data[0x010] = {0} ;  
uint8 *_DataPointer = _Data;
uint8 i = 0;
uint8 j = 0;
uint16 wordSize = (uint16)0x10;
uint16 repeat = (uint16)0x20;
uint16 _startAddr = DSP_MEM_ADD_CRAMTOP;
 


    if (AUDIO_STATE_PLAY == AudioPlayState)
    {
<span style="white-space:pre">	</span>.......
<span style="white-space:pre">	</span>//add by norton , just for dump the data of yda174
<span style="white-space:pre">	</span>printf("\n*********data of yda174************\n");

<span style="white-space:pre">	</span>while(i<repeat)
<span style="white-space:pre">	</span>{
<span style="white-space:pre">	</span>_DSPResult = Codec_DSPReadMem(_startAddr+i*byteSize,_DataPointer+i*byteSize,wordSize);
  <span style="white-space:pre">	</span>// printf("result code is: %d\n",_DSPResult);
<span style="white-space:pre">	</span>printf("[%X-%X]:",_startAddr+i*byteSize,_startAddr+(i+1)*byteSize);
<span style="white-space:pre">	</span>for(j=0;j<32;j++){
<span style="white-space:pre">	</span>printf("%02X ",*(_DataPointer+i*byteSize+j));
<span style="white-space:pre">	</span>}
<span style="white-space:pre">	</span>printf("\n");
  <span style="white-space:pre">	</span>  i++ ;
<span style="white-space:pre">	</span>}


<span style="white-space:pre">	</span>i = 0;
<span style="white-space:pre">	</span>printf("\n*********data of yda174************\n");  .........

最后输出结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值