简介在电力电子应用中单片机经常需要和各种外围设备进行通信，本文主要介绍几种常用的通信接口的使用方法低速时钟(LSPCLK)c2000的一些通信接口（McBSP、SCI、SPI）是将片上的低速时钟信号作为Bitclock的，低速时钟由系统时钟分频得到，默认是四分频，如果需要修改分频系数可以使用下面这个函数static inline void SysCtl_setLowSpeedClock(SysCtl_LSPCLKPrescaler prescaler)串行通信接口(SCI)SCI的发送和接收都有一个16字节的FIFO，还有一些常见的中断信号之类的，和其他串口区别不大捏妈，API手册SCI这块直接消失了，谢谢你，TI初始化设置GPIO//发送引脚 GPIO_setPadConfig(8,GPIO_PIN_TYPE_STD); GPIO_setPinConfig(GPIO_8_SCITXDA); GPIO_setQualificationMode(8,GPIO_QUAL_ASYNC); //接收引脚 GPIO_setPadConfig(9,GPIO_PIN_TYPE_STD); GPIO_setPinConfig(GPIO_9_SCIRXDA); GPIO_setQualificationMode(9,GPIO_QUAL_ASYNC);TI论坛上面有老哥说这个qualification不开也能用，笔者试了一下发现确实能用，按照TI的说法，这个qualification是用来防止在复杂环境下工作时受到各种噪声影响的，手册上面也让把这个打开，所以还是开了（重置SCI SCI_performSoftwareReset(SCIA_BASE);设置通信速率和帧格式波特率通过设置分频寄存器进行配置，手册给出了波特率的计算公式：BRR = (SCIHBAUD << 8) + (SCILBAUD)The SCI baud rate is calculated using the following equation:SCI Asynchronous Baud = LSPCLK / ((BRR + 1) *8)最大的可设置波特率是低速时钟频率除以十六使用Driverlib函数可以自动计算分频系数帧格式需要设置数据位数、停止位数和校验方式 SCI_setConfig(SCIA_BASE,50000000,115200,//低速时钟50MHz,波特率115200 (SCI_CONFIG_WLEN_8| //8位数据位 SCI_CONFIG_STOP_ONE| //1位停止位 SCI_CONFIG_PAR_NONE)); //无校验 设置FIFOSCI_enableFIFO(SCIA_BASE);//使能FIFO SCI_resetTxFIFO(SCIA_BASE);//重置FIFO SCI_resetRxFIFO(SCIA_BASE);设置中断下面这个是SCI的中断信号框图初始化中断向量表（如果没有初始化的话）Interrupt_enablePIE(); Interrupt_initModule(); Interrupt_initVectorTable();关中断DINT;注册中断服务函数，函数使用的中断号可以在hw_ints.h里面找到Interrupt_register(INT_SCIA_RX,Recv_Callback);//注册SCIA的串口接收中断服务函数使能中断Interrupt_enable(INT_SCIA_RX);//在CPU处使能SCIA串口接收中断 SCI_enableInterrupt(SCIA_BASE,SCI_INT_RXFF);//使能SCIA模块的接收中断设置FIFO中断条件，接收和发送可以分别设置SCI_setFIFOInterruptLevel(SCIA_BASE,SCI_FIFO_TX1,SCI_FIFO_RX1); //发送和接收FIFO都在接收到一个字节之后产生中断开中断EINT;其他通信外设的中断玩法和这个差不多，后面就不提了使能SCISCI_enableModule(SCIA_BASE);使用接收中断实现一个简单的echo功能__interrupt void Recv_Callback(void) { uint16_t recv_buffer[1]={0}; SCI_readCharArray(SCIA_BASE,recv_buffer,1); SCI_writeCharArray(SCIA_BASE,recv_buffer,1); SCI_clearOverflowStatus(SCIA_BASE); SCI_clearInterruptStatus(SCIA_BASE, SCI_INT_RXFF); Interrupt_clearACKGroup(INTERRUPT_ACK_GROUP9); }集成电路总线(I2C)初始化设置GPIO//SDA引脚 GPIO_setPadConfig(0,GPIO_PIN_TYPE_PULLUP); GPIO_setPinConfig(GPIO_0_SDAA); GPIO_setQualificationMode(0,GPIO_QUAL_ASYNC); //SCL引脚 GPIO_setPadConfig(1,GPIO_PIN_TYPE_PULLUP); GPIO_setPinConfig(GPIO_1_SCLA); GPIO_setQualificationMode(1,GPIO_QUAL_ASYNC);设置I2C使用下面的函数进行通信速率和时钟信号占空比的设置I2C_initController(I2CA_BASE, 200000000, 100000, I2C_DUTYCYCLE_50); //系统时钟200MHz，通信速率100000Hz，时钟信号占空比50%设置FIFOI2C_enableFIFO(I2CA_BASE);FIFO中断的设置方式和SCI是一样的使能I2CI2C_enableModule(I2CA_BASE);使用I2C进行通信（主模式）帧格式设置I2C_setSlaveAddress(I2CA_BASE, 0x56);//设置从机地址 I2C_setBitCount(I2CA_BASE, I2C_BITCOUNT_8);//设置每帧的数据位数 I2C_setDataCount(I2CA_BASE, 1);//设置要发送或接收的帧数开始条件和停止条件设置I2C_sendStartCondition(I2CA_BASE);//置位开始条件位 I2C_sendStopCondition(I2CA_BASE);//置位停止条件位发送开始条件后I2C会在总线上产生一个开始条件并且开始发送数据，之后将开始条件位清零设置停止条件后(1)如果工作在非连续模式下，I2C每发送一帧数据计数会减小一，当数据计数为零时向总线上发送一个停止条件并且将停止条件位清零(2)如果工作在连续模式下，数据计数不起作用，当停止条件被置位后立即产生一个停止条件注：当I2C工作在非连续模式下并且发送地址没有得到回应时，停止条件位会被直接清零，但不会在总线上产生停止条件，这会导致总线处于繁忙状态，遇到这种情况时需要再一次置位停止条件位以释放总线（手册没有提到这点，相当坑人）工作模式设置I2C_setConfig(I2CA_BASE, I2C_CONTROLLER_SEND_MODE);//主机发送模式 I2C_setConfig(I2CA_BASE, I2C_CONTROLLER_RECEIVE_MODE);//主机接收模式发送和接收void I2C_putData(uint32_t base, uint16_t data)//向发送寄存器送入一帧数据 uint16_t I2C_getData(uint32_t base)//从接收寄存器读取一帧数据串行外设接口(SPI)初始化设置GPIOGPIO_setPadConfig(58,GPIO_PIN_TYPE_PULLUP);//引脚输入时进行内部上拉 GPIO_setPadConfig(59,GPIO_PIN_TYPE_PULLUP); GPIO_setPadConfig(60,GPIO_PIN_TYPE_PULLUP); GPIO_setPadConfig(61,GPIO_PIN_TYPE_PULLUP); GPIO_setPinConfig(GPIO_58_SPISIMOA);//绑定外设 GPIO_setPinConfig(GPIO_59_SPISOMIA); GPIO_setPinConfig(GPIO_60_SPICLKA); GPIO_setPinConfig(GPIO_61_SPISTEA); GPIO_setQualificationMode(58,GPIO_QUAL_ASYNC);//开qualification GPIO_setQualificationMode(59,GPIO_QUAL_ASYNC); GPIO_setQualificationMode(60,GPIO_QUAL_ASYNC); GPIO_setQualificationMode(61,GPIO_QUAL_ASYNC);设置SPI使用下面的函数对协议格式，工作模式等进行设置SPI_setConfig(SPIA_BASE, 50000000, SPI_PROT_POL0PHA0, SPI_MODE_CONTROLLER, 100000, 8); //低速时钟50MHz，使用0型协议,主模式，通信速率100000Hz，一帧8位设置FIFOSPI_enableFIFO(SPIA_BASE);//使能FIFO SPI_resetTxFIFO(SPIA_BASE);//重置FIFO SPI_resetRxFIFO(SPIA_BASE);使能SPISPI_enableModule(SPIA_BASE);使用SPI进行通信使能/禁用发送SPI_enableTalk(SPIA_BASE); SPI_disableTalk(SPIA_BASE);发送SPI_transmitByte(base, txData)//发送一个字节 SPI_transmit16Bits(base, txData)//发送一帧16bit数据 SPI_transmitNBytes(base, txBuffer, numOfWords, txDelay)//发送N个字节 void SPI_writeDataBlockingNonFIFO(uint32_t base, uint16_t data)//等待发送缓冲非满并送入一帧 void SPI_writeDataBlockingFIFO(uint32_t base, uint16_t data)//等待发送FIFO非满并送入一帧 注意：使用最下面两个函数的时候要事先对发送数据进行左对齐，在一帧有N位的情况下，data的低16-N位会被丢弃接收SPI_receiveByte(base, dummyData)//接收一个字节 SPI_receiveNBytes(base, rxBuffer, numOfWords, txDelay)//接收N个字节 uint16_t SPI_readDataBlockingNonFIFO(uint32_t base)//等待接收缓冲非空并接收一帧 uint16_t SPI_readDataBlockingFIFO(uint32_t base)//等待接收FIFO非空并接收一帧 参考链接F28379D Driverlib API手册F28379数据手册F28379技术参考手册
介绍The enhanced pulse width modulator (ePWM) peripheral is a key element in controlling many of the powerelectronic systems found in both commercial and industrial equipment. These systems include digitalmotor control, switch mode power supply control, uninterruptible power supplies (UPS), and other forms ofpower conversion.增强型脉宽调制器(ePWM)外设是控制许多商业和工业设备中电力电子系统的关键元素，这些系统一般包括数字电机控制，开关电源控制，不间断电源控制和其他形式的功率转换系统由上图可见，c2000的epwm被分为了几个子模块，包括时基模块，比较模块，波形发生器模块等GPIO配置首先要配置epwm模块工作时需要使用到的引脚，把输出引脚配成推挽就行void GPIO_setPadConfig(uint32_t pin, uint32_t pinType)pinType的参数：GPIO_PIN_TYPE_STD specifies a push-pull output or a floating inputGPIO_PIN_TYPE_PULLUP specifies the pull-up is enabled for an inputGPIO_PIN_TYPE_OD specifies an open-drain output pinGPIO_PIN_TYPE_INVERT specifies inverted polarity on an input把引脚分配给EPWMvoid GPIO_setPinConfig(uint32_t pinConfig)pinConfig的宏定义可以在pin_map.h里找到,不同的芯片的driverlib可能会有一点区别，比如28379是GPIO_0_EPWM1A,280049就变成了GPIO_0_EPWM1_A我也不知道为什么非要整个不一样的EPWM点灯流程首先禁用计数器的时钟信号SysCtl_disablePeripheral(SYSCTL_PERIPH_CLK_TBCLKSYNC);配置时基(Time-Base)设置分频系数（笔者这里设置了不分频，可以根据需求改）EPWM_setClockPrescaler(EPWM1_BASE,EPWM_CLOCK_DIVIDER_1, EPWM_HSCLOCK_DIVIDER_1);This function sets the pre scaler(divider)value for the time base clock counter and the high speed time base clock counter. Valid values for pre-scaler and highSpeedPrescaler are EPWM_CLOCK_DIVIDER_X, where X is 1,2,4,8,16, 32,64 or 128. The actual numerical values for these macros represent values 0,1…7. The equation for the output clock is: TBCLK = EPWMCLK/(highSpeedPrescaler * pre-scaler)设置计数方向EPWM_setTimeBaseCounterMode(EPWM1_BASE,EPWM_COUNTER_MODE_UP);设置重装载值装载方式EPWM_setPeriodLoadMode(EPWM1_BASE,EPWM_PERIOD_SHADOW_LOAD);EPWM_PERIOD_SHADOW_LOAD 使用影子寄存器EPWM_PERIOD_DIRECT_LOAD 直接访问设置计数器重装载事件EPWM_selectPeriodLoadEvent(EPWM1_BASE, EPWM_SHADOW_LOAD_MODE_COUNTER_ZERO);设置重装载值EPWM_setTimeBasePeriod(EPWM1_BASE,10000);配置比较器(Counter Compare)设置比较值的装载事件EPWM_setCounterCompareShadowLoadMode(EPWM1_BASE, EPWM_COUNTER_COMPARE_A, EPWM_COMP_LOAD_ON_CNTR_ZERO_PERIOD);设置比较值EPWM_setCounterCompareValue(EPWM1_BASE,EPWM_COUNTER_COMPARE_A,5000);配置波形发生器(Action Qualifier)这玩意儿跟st的hrtim里的set/reset source 差不多,就是可以对每个事件设置输出置高、置低、翻转或者无动作void EPWM_setActionQualifierAction(uint32_t base, EPWM_ActionQualifierOutputModule epwmOutput, EPWM_ActionQualifierOutput output, EPWM_ActionQualifierOutputEvent event) void EPWM_setActionQualifierActionComplete(uint32_t base, EPWM_ActionQualifierOutputModule epwmOutput, uint16_t action)Driverlib提供了两种API,上面这种可以单独设置某个事件，下面的是一次设置所有事件举个例子EPWM_setActionQualifierActionComplete(EPWM1_BASE,EPWM_AQ_OUTPUT_A,EPWM_AQ_OUTPUT_HIGH_ZERO|EPWM_AQ_OUTPUT_LOW_UP_CMPA); //////////////////////////////////////////////////////////////////////////////////////////// EPWM_setActionQualifierAction(EPWM1_BASE, EPWM_AQ_OUTPUT_A, EPWM_AQ_OUTPUT_HIGH, EPWM_AQ_OUTPUT_ON_TIMEBASE_ZERO); EPWM_setActionQualifierAction(EPWM1_BASE, EPWM_AQ_OUTPUT_A, EPWM_AQ_OUTPUT_LOW, EPWM_AQ_OUTPUT_ON_TIMEBASE_UP_CMPA);两种写法效果是一样的最后重新打开计数器的时钟SysCtl_enablePeripheral(SYSCTL_PERIPH_CLK_TBCLKSYNC);完成以上配置之后epwm模块就能输出基本的pwm波形了高精度PWM(High Resolution Pulse Width Modulation)笔者没太看懂TI这玩意儿是啥原理，手册上就说使用了一种叫MEP技术可以在一个时钟周期内插入很多个脉冲，根据数据手册的说法F28379的MEP步长一般是150ps，并且会随着温度浮动参考手册上给出了详细的配置流程，笔者依葫芦画瓢做了一遍时基单元配置首先配置重装载值void HRPWM_setTimeBasePeriod(uint32_t base, uint32_t periodCount)由于使用了高精度模式，这里的重装载值是q16.8的定点数，看函数定义可以发现这个函数是把periodcount的低八位（也就是小数部分）给了TBPRDHR,高十六位给了TBPRD比较值也是同理void HRPWM_setCounterCompareValue(uint32_t base, HRPWM_CounterCompareModule compModule, uint32_t compCount)配置计数模式和装载模式，手册说高精度模式只支持向上计数和中心对齐计数,而且必须使用影子寄存器void HRPWM_setTimeBaseCounterMode(uint32_t base, EPWM_TimeBaseCountMode counterMode) void HRPWM_setPeriodLoadMode(uint32_t base, EPWM_PeriodLoadMode loadMode)设置比较值的装载事件,注意这玩意儿只影响CMPAHRstatic inline void HRPWM_setCounterCompareShadowLoadEvent(uint32_t base, HRPWM_Channel channel,HRPWM_LoadMode loadEvent)剩下的按照一般epwm的方式配置就行，其实除了一些HRPWM特有的设置外其他API都是和EPWM一样的MEP比例因子自动转换配置开自动转换void HRPWM_enableAutoConversion(uint32_t base)不开自动转换的话MEP的比例因子就要自己算，而且由于MEP的步长会随着温度缓慢漂移，比例因子也是在缓慢发生变化的，所以还是开自动转换比较好使用自动转换还有一些额外的东西需要配置:包含必要的头文件#include "F28x7x_Device.h" // F28x7x Headerfile #include "F28x7x_EPwm_defines.h" // init defines #include "SFO_V8.h" // SFO lib functions (needed for HRPWM定义变量（注意一定要定义成全局的）int MEP_ScaleFactor = 0; //scale factor value volatile struct EPWM_REGS *ePWM[] = {0, &EPwm1Regs, &EPwm2Regs, &EPwm3Regs, &EPwm4Regs};epwm数组里放芯片各个EPWM模块的基址调用SFO()对比例因子进行初始化while (SFO() == 0) {} // MEP_ScaleFactor calculated by MEP Cal Module在后面的程序里时不时调用一下SFO()来更新比例因子(可以放在大循环里)手册里其实也给了比例因子的计算方法MEP控制配置设置MEP控制的边沿，手册让同时控制上升沿和下降沿，我也不知道为啥反正跟着做就对了HRPWM_setMEPEdgeSelect(EPWM1_BASE,HRPWM_CHANNEL_A,HRPWM_MEP_CTRL_RISING_AND_FALLING_EDGE);使能高精度控制HRPWM_enablePeriodControl(EPWM1_BASE);发送一个软件同步脉冲EPWM_forceSyncPulse(EPWM1_BASE);到这HRPWM就配置好了错误信号控制模块(Trip Zone)c2000的epwm提供了错误信号控制模块，可以从外部引脚或者软件获取错误发生的信号并按照设定在硬件层面上进行保护，还可以产生相应的中断信号方便CPU进行处理，对于电力电子这种经常要加各种保护的应用来说比较有用实际上笔者一开始是因为在c2000上找不到像st那样的IDLE电平设置才用这个来实现类似的功能保护类型错误信号控制模块提供了两种保护方式，一种叫CBC(Cycle-by-Cycle)，顾名思义就是在每个PWM周期都重置保护状态，比较适合用于过流保护之类的场景，另一种叫OSHT(One-Shot)，这种保护一旦被触发就会一直持续下去直到收到解除的信号保护动作c2000可以通过下面这个函数设置保护事件和对应的动作，保护动作有高阻态，拉高输出，拉低输出或者无动作四种void EPWM_setTripZoneAction(uint32_t base, EPWM_TripZoneEvent tzEvent, EPWM_TripZoneAction tzAction)tzEventEPWM_TZ_ACTION_EVENT_DCBEVT2 - DCBEVT2 (Digital Compare B event 2)EPWM_TZ_ACTION_EVENT_DCBEVT1 - DCBEVT1 (Digital Compare B event 1)EPWM_TZ_ACTION_EVENT_DCAEVT2 - DCAEVT2 (Digital Compare A event 2)EPWM_TZ_ACTION_EVENT_DCAEVT1 - DCAEVT1 (Digital Compare A event 1)EPWM_TZ_ACTION_EVENT_TZB - TZ1 - TZ6, DCBEVT2, DCBEVT1EPWM_TZ_ACTION_EVENT_TZA - TZ1 - TZ6, DCAEVT2, DCAEVT1tzActionEPWM_TZ_ACTION_HIGH_Z - high impedance outputEPWM_TZ_ACTION_HIGH - high outputEPWM_TZ_ACTION_LOW - low lowEPWM_TZ_ACTION_DISABLE - disable action软件触发保护void EPWM_forceTripZoneEvent(uint32_t base, uint16_t tzForceEvent)解除保护状态void EPWM_clearTripZoneFlag(uint32_t base, uint16_t tzFlags)死区发生器(Dead Band)配置死区也是电力电子经常用到的功能，下图是DB模块的框图，该模块可以分别配置上升沿和下降沿的死区时间和输出极性等从左到右每一部分在Driverlib里面都有对应的函数进行操作设置RED和FED的信号源void EPWM_setRisingEdgeDeadBandDelayInput(uint32_t base, uint16_t input) void EPWM_setFallingEdgeDeadBandDelayInput(uint32_t base, uint16_t input)EPWM_DB_INPUT_EPWMA - Input signal is ePWMA(Valid for both Falling Edge and Rising Edge)EPWM_DB_INPUT_EPWMB - Input signal is ePWMB(Valid for both Falling Edge and Rising Edge)EPWM_DB_INPUT_DB_RED - Input signal is the output of Rising Edge delay. (Valid only for Falling Edge delay)设置RED和FED的延迟时间void EPWM_setRisingEdgeDelayCount(uint32_t base, uint16_t redCount) void EPWM_setFallingEdgeDelayCount(uint32_t base, uint16_t fedCount) 设置输出极性void EPWM_setDeadBandDelayPolarity(uint32_t base, EPWM_DeadBandDelayMode delayMode, EPWM_DeadBandPolarity polarity)delayModeEPWM_DB_RED - Rising Edge delayEPWM_DB_FED - Falling Edge delaypolarityEPWM_DB_POLARITY_ACTIVE_HIGH - polarity is not inverted.EPWM_DB_POLARITY_ACTIVE_LOW - polarity is inverted.设置是否使用死区生成void EPWM_setDeadBandDelayMode(uint32_t base, EPWM_DeadBandDelayMode delayMode, bool enableDelayMode)设置输出交换（我不知道这功能应用场景是啥）void EPWM_setDeadBandOutputSwapMode(uint32_t base, EPWM_DeadBandOutput output, bool enableSwapMode)outputEPWM_DB_OUTPUT_A - ePWM output AEPWM_DB_OUTPUT_B - ePWM output BenableSwapModetrue - the output is swappedfalse - the output and the signal path are the same.手册上给了几种配置对应的输出波形，可以结合前面的框图理解如何进行时基间的同步电力电子经常涉及多个定时器之间的同步，用过stm32的应该知道它的定时器可以通过设置触发事件来实现同步，c2000的epwm也有类似的功能，但是和stm32的玩法不太一样,c2000的时基单元的同步信号是一个串一个的，可以看底下这个图考虑同步的时候可以把每个epwm模块简化成下面这样要进行同步主要要做两步，第一步是把前面的模块的同步输出信号打开,第二步是把要进行同步的模块的移相输入打开void EPWM_setSyncOutPulseMode(uint32_t base, EPWM_SyncOutPulseMode mode) void EPWM_enablePhaseShiftLoad(uint32_t base) 这样两个epwm模块就可以同步了总结本文介绍了c2000 epwm模块的基本使用方法，其实还有很多功能笔者暂时没有用上，日后用上了再回来补充，如果文章有什么错误也欢迎批评指正参考链接F28379D Driverlib API手册F28379数据手册F28379技术参考手册
简介FSMC(Flexible Static Memory Controller，可变静态存储控制器)是STM32系列采用的一种新型的存储器扩展技术。在外部存储器扩展方面具有独特的优势，可根据系统的应用需要，方便地进行不同类型大容量静态存储器的扩展（还有一种用途是用来驱动并口屏）系统框图*本文使用的图片均出自STM32H750VB的参考手册，FMC的具体情况在不同型号的芯片内可能会有所不同。在STM32单片机中，FMC的作用相当于沟通了芯片内部的设备总线和外部存储器件，使得CPU能够通过总线访问到外部器件的内存。内存映射FMC内存框图FMC的内存被分为若干个内存块，其中内存块1拥有一个独立的2位片选信号，将该内存块分为了四个扇区，也就是支持最多连接四个内存设备。NOR/PSRAM设备的内存映射前文提到内存块1的2位片选信号由地址线的第27、28位控制。地址线的前26位用于访问外部内存设备中的指定位置。考虑到设备寄存器宽度不同，st官方给出了以上说明。在对外部通信过程中，若设备的寄存器宽度为16位，那么地址线的第26位将不被使用，32位寄存器同理，不使用第26、25位。同时，对于CPU而言，内存的单位仍然是一个字节，因此在使用外部设备的寄存器地址时应当将其地址左移相应位数。应用举例:ILI9341LCD驱动芯片ILI9341使用8080通信协议，STM32的FSMC使用该协议时要求用户指定地址线上的一位作为8080的数据、命令信号。实际上是将LCD当作了一种只有两个寄存器的SRAM芯片，从而将SRAM总线的通信转换成了8080.以下是正点原子写的驱动代码：//LCD地址结构体 typedef struct { u16 LCD_REG; u16 LCD_RAM; } LCD_TypeDef; //使用NOR/SRAM的 Bank1.sector1,地址位HADDR[27,26]=00 A18作为数据命令区分线 //注意设置时STM32内部会右移一位对齐! #define LCD_BASE ((u32)(0x60000000 | 0x00007FFFE)) #define LCD ((LCD_TypeDef *) LCD_BASE)结合上文不难得知LCD内存的“基地址”如何求取。