嵌入式软件相关的记录，包含STM32，ZYNQ，FPGA，上位机软件等
ZYNQ 以CPU私有定时器为例分析中断的使用
ZYNQ是由PS + PL两个部分组成的SoC，PS端有两个双核的ARM A9处理器，说到ARM处理器就不得不说一下其中断处理机制。通过Xilinx的官方手册，我们可以知道ZYNQ芯片当中也有一个ARM的通用中断控制器，用于启用、鉴别和分配不同来源的中断到对应的CPU进行处理。其中这些中断可以分为：软件中断、CPU私有外设中断、共享外设中断等三大类。其中CPU私有外设中断是每个CPU所独有的，无法跨CPU分配。而软件中断和共享外设中断则可以根据需要通用对中断控制器的配置分配给CPU0/CPU1进行处理。我们先以私有中断为例学习ZYNQ配置中断的流程。在ZYNQ的PS端每个CPU都有一个私有的定时器可以用于精确的计时，其工作频率总是为CPU主频的一半。可以通过轮询方式获得时间计数，或者将定时器配置工作在中断模式，每隔一个固定的重装载周期触发一次中断处理函数。接下来我们将介绍如何初始化ARM A9的中断异常处理，启动GIC对于私有定时器的中断使能，并配置私有定时器的重装载值使得CPU每经过1秒就会被中断一次。首先我们来看官方定时器中断示例中配置中断系统的过程：首先初始化中断控制器的配置启动ARM处理器的异常处理注册中断控制器的异常处理函数连接定时器中断处理到中断控制器启动中断和异常处理static int TimerSetupIntrSystem(XScuGic *IntcInstancePtr, XScuTimer *TimerInstancePtr, u16 TimerIntrId) { int Status; #ifndef TESTAPP_GEN XScuGic_Config *IntcConfig; /* * Initialize the interrupt controller driver so that it is ready to * use. */ IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID); if (NULL == IntcConfig) { return XST_FAILURE; } Status = XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig, IntcConfig->CpuBaseAddress); if (Status != XST_SUCCESS) { return XST_FAILURE; } Xil_ExceptionInit(); /* * Connect the interrupt controller interrupt handler to the hardware * interrupt handling logic in the processor. */ Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_IRQ_INT, (Xil_ExceptionHandler)XScuGic_InterruptHandler, IntcInstancePtr); #endif /* * Connect the device driver handler that will be called when an * interrupt for the device occurs, the handler defined above performs * the specific interrupt processing for the device. */ Status = XScuGic_Connect(IntcInstancePtr, TimerIntrId, (Xil_ExceptionHandler)TimerIntrHandler, (void *)TimerInstancePtr); if (Status != XST_SUCCESS) { return Status; } /* * Enable the interrupt for the device. */ XScuGic_Enable(IntcInstancePtr, TimerIntrId); /* * Enable the timer interrupts for timer mode. */ XScuTimer_EnableInterrupt(TimerInstancePtr); #ifndef TESTAPP_GEN /* * Enable interrupts in the Processor. */ Xil_ExceptionEnable(); #endif return XST_SUCCESS; }另一个关键的函数是中断处理函数，当中断产生后，将会触发中断服务函数的调用。因此我们可以再其中编写对应中断的处理逻辑。static void TimerIntrHandler(void *CallBackRef) { XScuTimer *TimerInstancePtr = (XScuTimer *) CallBackRef; /* * Check if the timer counter has expired, checking is not necessary * since that's the reason this function is executed, this just shows * how the callback reference can be used as a pointer to the instance * of the timer counter that expired, increment a shared variable so * the main thread of execution can see the timer expired. */ if (XScuTimer_IsExpired(TimerInstancePtr)) { XScuTimer_ClearInterruptStatus(TimerInstancePtr); TimerExpired++; if (TimerExpired == 30) { XScuTimer_DisableAutoReload(TimerInstancePtr); } } } 当然为了能够让定时器产生中断，我们需要在主函数中开启定时器的自动重装载，并启动定时器/* * Enable Auto reload mode. */ XScuTimer_EnableAutoReload(TimerInstancePtr); /* * Load the timer counter register. */ XScuTimer_LoadTimer(TimerInstancePtr, TIMER_LOAD_VALUE); /* * Start the timer counter and then wait for it * to timeout a number of times. */ XScuTimer_Start(TimerInstancePtr);
在之前的项目中知道zynq的PS端可以通过AXI总线向PL端的IP核写入数据，而PL端也可以通过AXI总线去访问PS端的DDR等设备。然而当PL端的IP在接收到PS端的指令完成操作后，该如何告知PS端我已经完成操作了呢？有一种方式是PS端使用AXI总线轮询，不停地访问AXI总线直到出现预期的标志位。另外一种方式则是使用PL端到PS端的中断，在完成操作后发出一个中断通知PS端可以进行后续的处理了。PL端向PS端发出触发的方式在zynq上有16个PL到PS端的常规中断接到双核心的PS端，每个CPU又有独立的1个常规中断和快速中断。在这里我们先介绍使用F2P的这一组中断。从PL端发出这个中断的方式非常简单，就是从模块上给出一个信号然后连接到zynq PS的F2P端口上。当需要触发这个中断时将模块的这个信号由低拉高即可。PS端F2P支持高电平触发或者是上升沿触发，可以根据实际的需求进行配置。PS端的中断处理#include <stdio.h> #include "platform.h" #include "xil_printf.h" #include "xil_io.h" #include "xscugic.h" #include "xparameters.h" #define PL_DDR_BASE_ADDR 0x10000000 #define INT_ID 61U #define XIL_EXCEPTION_ID_INIT 5U XScuGic ScuGic; void irq_handler(void* data) { u32 id; id = XScuGic_ReadReg(XPAR_SCUGIC_CPU_BASEADDR, XSCUGIC_INT_ACK_OFFSET); id = id & XSCUGIC_ACK_INTID_MASK; xil_printf("irq occur, id: %d\r\n", id); XScuGic_WriteReg(XPAR_SCUGIC_CPU_BASEADDR, XSCUGIC_EOI_OFFSET, id); } int pl_irq_init() { XScuGic_Config* pScuGicCfg; pScuGicCfg = XScuGic_LookupConfig(XPAR_PS7_SCUGIC_0_DEVICE_ID); XScuGic_CfgInitialize(&ScuGic, pScuGicCfg, pScuGicCfg->CpuBaseAddress); XScuGic_Connect(&ScuGic, INT_ID, (Xil_ExceptionHandler)irq_handler, NULL); XScuGic_SetPriorityTriggerType(&ScuGic, INT_ID, 0xA0, 0x03); XScuGic_Enable(&ScuGic, INT_ID); Xil_ExceptionInit(); Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INIT, (Xil_ExceptionHandler)irq_handler, &ScuGic); Xil_ExceptionEnable(); } int main() { init_platform(); pl_irq_init(); print("read ddr app\n\r"); while(1); cleanup_platform(); return 0; }参考链接https://adaptivesupport.amd.com/s/question/0D52E00006iHoxcSAC/irqf2p-width-mismatch?language=zh_CNhttps://adaptivesupport.amd.com/s/article/70347?language=en_US
之前在客户那里调试设备，控制器上用的FTDI的USB转串口芯片。上位机需要和控制器进行指令交互，然后不同的指令的超时时间不同，因此需要在打开串口后修改超时时间。但是调试下来发现，串口通信变得很不稳定，非常容易收不到数据。困扰了一段时间后，果断抓包看USB转串口的数据包，一看吓一跳setTimeout发送之后，系统再不停地重新配置USB转串口的各项参数，包括波特率，数据位等等。明明我只需要配置超时，却相当于重新开关了一遍串口。void Serial::SerialImpl::setTimeout (serial::Timeout &timeout) { timeout_ = timeout; if (is_open_) { reconfigurePort (); } }查看serial库的源码发现，这货真的牛啊，直接调用了reconfigurePort来配置超时，所以抓包setTimeout时的数据有一堆配置波特率的东西。我们只需要修改超时时间，因此修改这个函数就可以解决问题，只单纯的改变超时的等待时间void Serial::SerialImpl::setTimeout (serial::Timeout &timeout) { timeout_ = timeout; if (is_open_) { //reconfigurePort (); // Setup timeouts COMMTIMEOUTS timeouts = { 0 }; timeouts.ReadIntervalTimeout = timeout_.inter_byte_timeout; timeouts.ReadTotalTimeoutConstant = timeout_.read_timeout_constant; timeouts.ReadTotalTimeoutMultiplier = timeout_.read_timeout_multiplier; timeouts.WriteTotalTimeoutConstant = timeout_.write_timeout_constant; timeouts.WriteTotalTimeoutMultiplier = timeout_.write_timeout_multiplier; if (!SetCommTimeouts(fd_, &timeouts)) { THROW(IOException, "Error setting timeouts."); } } }
STM32在Keil集成开发环境中使用SWO进行调试信息打印
在单片机的开发过程中，打印信息是调试过程中非常重要的手段。通常打印信息是通过单片机的串口外设进行的。最近学到了一种通过SWD/JTAG调试接口来打印调试信息的方法，可以在串口资源紧张的时候减少一个调试串口资源占用。本文基于Keil + STLink和原子的STM32学习板进行演示首先我们基于原子开发板的串口教程实现printf 串口格式输出打印的方法，然后对fputc 这个函数实现做一些简单修改，代码工作就结束了。int fputc(int ch, FILE *f) { ITM_SendChar(ch); //SWO输出字符的方法 while((USART1->SR&0X40)==0); USART1->DR = (u8) ch; return ch; }部分情况下需要在工程选项中选择Use MicroLib这个选项确保代码正确编译，能够在串口获得打印信息后，再对调试器进行设置转到调试器的Trace 选项卡，依次设置核心时钟、追踪使能，并使能SWO时钟自动检测再ITM端口中需要选择端口0，因为从STM32 的标准库实现中可以看到ITM_SendChar 使用的是端口0进入调试模式，然后在视图菜单中打开调试输出查看器即可View => Trace => Debug (printf) Viewer参考文献STM32中重定向printf到SWO口[通俗易懂]printf系列教程03\_SWO打印输出配置，基于Keil『Debug（printf）Viewer』如何在 Keil µVision 中通过 SWO 显示 printf 输出。
libusb-1.0启动报错0x00007FFB0C21177F (libusb-1.0.dll)处(位于 USBTest.exe 中)引发的异常: 0xC0000005: 读取位置 0x0000000000000200 时发手写完整的libusb调用代码，一脸懵逼，为什么不好用了，其实是因为忘记初始化libusb的库了。。。libusb_init(/*ctx=*/NULL);usb bulk transfer的问题原先在死循环中发送usb bulk传输包，在上位机上使用libusb_bulk_transfer 总是收到timeout。因此需要注意传输的速率，控制一下传输速度，换成每10ms传输1个包就没问题了