感谢韬哥发来的SWM181CBT6开发板,板子是最小系统,我们仅仅是用于熟悉这个芯片,即使上面资源不多,也丝毫不影响我们对这个芯片外设进行开发。
对SWM181CBT6-LQFP48进行简单的性能介绍。
(1)内置16KB SRAM 120KB Flash
(2)32 位 ARM® Cortex™-M0 内核,可运行在48MHz
(3)SPI x 2 UART x4 I2C*2 CAN*1
8 通道 16 位 PWM 产生器
12 位 8 通道高精度 SAR ADC
16 位 6 通道高精度 SIGMA-DELTA ADC
24MHz、48MHz 精度可达 1%的片内时钟源
芯片淘宝官方店报价:4.6元零售价。看起来性价比还可以。
多余的便不作介绍,开发板直接上图。
板子上面资源不多,主要是:CH340G+芯片+20P大JTAG口。简单的例程调试基本上没有问题。支持一下国产MCU,思考使用什么姿势打开这款芯片的外设资源,后面再更贴。
最近编辑记录 LinjieGuo (2020-01-07 21:42:58)
离线
------------------------------------------------------------------------------------------------
尝试使用嵌入式点灯大法来打开这一款MCU,看看点的灯亮不亮,速度快不快。
找到官方例程,.\GPIO\KeyLED,使用J-link上位机通用工具Synwit_Jlink_Download_Check_V1.5.exe将程序下载进MCU。
工程文件里面有个readme.txt:功能说明:按下接在PA4上的按键,则接在PA5上的LED亮;放开按键则LED灭!
程序下载非常成功,但是没有丝毫反应,灯似乎不亮。
按下S2,不亮;松开,不亮。
------------------------------------------------------------------------------------------------
仔细管擦,板子上面似乎有一些跳线位,打开原理图,一看,原来如此,MCU并没有和LED/KEY相连。
(1)使用一根杜邦线将GPA5和LED相连,
(2)使用一根杜邦线将GPA4和KEY相连。
重启单板,按下按键S2,LED亮;松开,LED灭。
嵌入式点灯大法施展成功。
最近编辑记录 LinjieGuo (2020-01-09 14:58:19)
离线
尝试了解官方的工程架构,打开Keil,Target配置如下:
我们使用的SWM181CBT6,拥有16KB SRAM,240KB Flash,所以我们可以修改一下。物尽其用。
查看数据手册上面的存储器映射表:
填写Keil的配置(用于自动生成链接脚本)。
IROM 0X0000 0000, 0X0003 C000 ; IRAM 0X 2000 0000,0x0000 4000
上图:
这样就能用上240KB的Flash,16KB SRAM了。
最近编辑记录 LinjieGuo (2020-01-09 15:41:27)
离线
观察startup_xxx.s文件(启动文件),里面做了什么事情呢?
---------------------------------------------------------------------------------------------
(1)开辟栈,并使用标号__initial_sp获得栈顶。
Stack_Size EQU 0x800;
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
---------------------------------------------------------------------------------------------
(2)开辟堆,并使用__heap_limit获取堆的界限。
Heap_Size EQU 0x000;
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
此处明显堆的大小为0,这样的话,按道理来说是无法使用C库里面的malloc()函数分配内存的。
---------------------------------------------------------------------------------------------
(3)异常向量指定:RESET段。
AREA RESET, DATA, READONLY
__Vectors DCD Stack_Mem + Stack_Size ; Top of Stack 栈顶,等于__initial_sp
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD 0
DCD SRAM_SWITCH
DCD SVC_Handler ; SVCall Handler
DCD 0
DCD 0
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler
; External Interrupts
DCD IRQ0_Handler
DCD IRQ1_Handler
DCD IRQ2_Handler
DCD IRQ3_Handler
DCD IRQ4_Handler
DCD IRQ5_Handler
DCD IRQ6_Handler
DCD IRQ7_Handler
DCD IRQ8_Handler
DCD IRQ9_Handler
DCD IRQ10_Handler
DCD IRQ11_Handler
DCD IRQ12_Handler
DCD IRQ13_Handler
DCD IRQ14_Handler
DCD IRQ15_Handler
DCD IRQ16_Handler
DCD IRQ17_Handler
DCD IRQ18_Handler
DCD IRQ19_Handler
DCD IRQ20_Handler
DCD IRQ21_Handler
DCD IRQ22_Handler
DCD IRQ23_Handler
DCD IRQ24_Handler
DCD IRQ25_Handler
DCD IRQ26_Handler
DCD IRQ27_Handler
DCD IRQ28_Handler
DCD IRQ29_Handler
DCD IRQ30_Handler
DCD IRQ31_Handler
__Vectors_End
---------------------------------------------------------------------------------------------
为什么这样设定?
查看sct脚本:工程目录下的输入目录里面存在一个自动生成的KeyLED.sct链接脚本。
脚本的数据来源于,Target设置,作用:指定各段存放的位置。
LR_IROM1 0x00000000 0x00006000 { ; load region size_region
ER_IROM1 0x00000000 0x00006000 { ; 芯片Flash映射在0地址
*.o (RESET, +First) ;指定RESET段链接到头部
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}
RW_IRAM1 0x20000000 0x00002000 { ; RW data
.ANY (+RW +ZI) ;变量,堆栈等链接到此处。
}
}
因为芯片内存过小,而且这个芯片不适合扩展内存等操作,在内存管理方面便不多做介绍。
最近编辑记录 LinjieGuo (2020-01-11 10:35:30)
离线
CPU上电后,芯片内部自动读取存放在Flash 0地址上面的栈顶地址进而设置堆栈指针。
CPU 内部电压稳定后,发生reset异常,自动跳转到异常的处理函数中。
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
LDR R0, =__main
BX R0
ENDP
可用看到,这里只有一种操作,那就是跳转到__main()函数里面执行了。
__main()和Main() (以前的ARM9使用ADS编写程序用到)是存在差异的,有兴趣自行百度进行科普。
------------------------------------------------------------------------------------------------------
在main()里面,首先便调用以下函数,设置芯片工作频率。
void SystemInit(void)
{
uint32_t i;
SYS->CLKEN |= (1 << SYS_CLKEN_OSC_Pos);
switch(SYS_CLK)
{
case SYS_CLK_24MHz: //0 内部高频24MHz RC振荡器
if(SYS->CLKSEL & SYS_CLKSEL_SYS_Msk) //当前时钟是高频RC,修改高频RC时钟频率时需要先切到一个稳定时钟源
{
switchToRC32KHz();
}
switchToRC24MHz();
break;
case SYS_CLK_6MHz: //1 内部高频 6MHz RC振荡器
if(SYS->CLKSEL & SYS_CLKSEL_SYS_Msk) //当前时钟是高频RC,修改高频RC时钟频率时需要先切到一个稳定时钟源
{
switchToRC32KHz();
}
switchToRC6MHz();
break;
case SYS_CLK_48MHz: //2 内部高频48MHz RC振荡器
if(SYS->CLKSEL & SYS_CLKSEL_SYS_Msk) //当前时钟是高频RC,修改高频RC时钟频率时需要先切到一个稳定时钟源
{
switchToRC32KHz();
}
switchToRC48MHz();
break;
case SYS_CLK_12MHz: //3 内部高频12MHz RC振荡器
if(SYS->CLKSEL & SYS_CLKSEL_SYS_Msk) //当前时钟是高频RC,修改高频RC时钟频率时需要先切到一个稳定时钟源
{
switchToRC32KHz();
}
switchToRC12MHz();
break;
case SYS_CLK_32KHz: //4 内部低频32KHz RC振荡器
if((SYS->CLKSEL & SYS_CLKSEL_SYS_Msk) == 0)
{
switchToRC24MHz();
}
switchToRC32KHz();
break;
case SYS_CLK_XTAL: //5 外部XTAL晶体振荡器(2-30MHz)
if((SYS->CLKSEL & SYS_CLKSEL_SYS_Msk) == 0)
{
switchToRC24MHz();
}
switchToXTAL();
break;
}
for(i = 0;i <10000;i++); //等待时钟稳定。。。
SystemCoreClockUpdate();
}
以上代码中,存在一条神奇的语句,可能要耗费CPU一点时间。
for(i = 0;i <10000;i++); //等待时钟稳定。。。
MCU IO引脚上电后会存在默认高电平/低电平等情况,如果板子设计时没有考虑到这个原因导致的影响,可用在这行语句之前指定引脚状态,减少影响。
--------------------------------------------------------------------------------------------------
展开宏:#define SYS_CLK SYS_CLK_24MHz
可用看到,官方例程里面,MCU默认工作于24MHz。
理论上,可用修改该宏定义为SYS_CLK_48MHz,将MCU工作频率设置为48MHz。但是不知道是否稳定,目前没有尝试。
#define SYS_CLK_24MHz 0 //0 内部高频24MHz RC振荡器
#define SYS_CLK_6MHz 1 //1 内部高频 6MHz RC振荡器
#define SYS_CLK_48MHz 2 //2 内部高频48MHz RC振荡器
#define SYS_CLK_12MHz 3 //3 内部高频12MHz RC振荡器
#define SYS_CLK_32KHz 4 //4 内部低频32KHz RC振荡器
#define SYS_CLK_XTAL 5 //5 外部XTAL晶体振荡器(2-30MHz)
最近编辑记录 LinjieGuo (2020-01-11 10:52:41)
离线
尝试使用串口通讯进行通讯,我在原理图上面怎么找都找不到串口引脚。
翻查数据手册,发现并没有特定的串口引脚,芯片内部有一个神奇的数字信号引脚分配器——>PORTCON。
对于部分数字输入输出功能,可以配置到任意 I/O 引脚,以方便板级布局。包括如下功能:
UARTn_TX UARTn_RX
I2Cn_SDA I2Cn_CLK
PWMx_OUT PWM_BREAK
COUNTERn_IN CAPTURE_IN
CAN_RX CAN_TX
怎么用呢?我们看看下面这个图应该就明白了。
意思是可用把上面列出来的数字类型引脚分配到任意的带FUNCTION功能的引脚上面。
那我们看看那些引脚是带有FUNCTION的呢?上图:
看起来,除了电源引脚、晶振引脚、复位引脚,其余的引脚都支持配置为FUNCTION功能。
怎么配置呢?我们尽量不要关注寄存器层,直接使用官方的例程上面的库接口:
PORT_Init(PORTA, PIN0, FUNMUX_UART0_RXD, 1); //GPIOA.0配置为UART0输入引脚
PORT_Init(PORTA, PIN1, FUNMUX_UART0_TXD, 0); //GPIOA.1配置为UART0输出引脚
来到这里,终于明白为什么开发板上面CH340G的两根串口线不直连MCU了。
初始化串口,使用官方的代码,略微修改波特率为115200。
void SerialInit(void)
{
UART_InitStructure UART_initStruct;
PORT_Init(PORTA, PIN0, FUNMUX_UART0_RXD, 1); //GPIOA.0配置为UART0输入引脚
PORT_Init(PORTA, PIN1, FUNMUX_UART0_TXD, 0); //GPIOA.1配置为UART0输出引脚
UART_initStruct.Baudrate = 115200;
UART_initStruct.DataBits = UART_DATA_8BIT;
UART_initStruct.Parity = UART_PARITY_NONE;
UART_initStruct.StopBits = UART_STOP_1BIT;
UART_initStruct.RXThreshold = 3;
UART_initStruct.RXThresholdIEn = 0;
UART_initStruct.TXThreshold = 3;
UART_initStruct.TXThresholdIEn = 0;
UART_initStruct.TimeoutTime = 10;
UART_initStruct.TimeoutIEn = 0;
UART_Init(UART0, &UART_initStruct);
UART_Open(UART0);
}
重定位编译器C库的标准输出:
int fputc(int ch, FILE *f)
{
UART_WriteByte(UART0, ch);
while(UART_IsTXBusy(UART0));
return ch;
}
这样就能够愉快的使用printf(...)函数进行格式化打印数据了。
-----------------------------------------------------------------------------------------
直接把MCU串口引脚连接到CH340G的串口引脚上,不知道会不会烧掉
PA0(RX)-->TX
PA1(TX)-->RX
最近编辑记录 LinjieGuo (2020-01-11 15:19:31)
离线
刚刚想用keil直接下载程序,便按官方教程,修改Jflash中的相关文件,但是keil还是无法下载,还是要使用官方的Jlink下载软件。
写好串口的程序,下载进去。
芯片没有任何反应。下载之前的点灯程序,按下按键,依然没有任何反应。
芯片似乎挂掉了,可能是由以下原因导致,
①按照官方的教程中修改Jlink的Device.xml文件(重装Jlink,没有解决问题,排除!)
②CH340G IO无法直连MCU IO导致芯片不正常,
③使用了下载软件的校验功能,虽然没有成功过,可能影响了内部的数据
④使用keil下载,虽然下载失败,可能影响了内部的数据
反正现在情况就是显示下载成功,但是没有出现想要的实验现象,具体原因未知。
使用下载软件的校验功能,校验结果说不一致,到底是什么原因呢?
最近编辑记录 LinjieGuo (2020-01-11 15:59:00)
离线
离线
PA0(RX)-->TX
PA1(TX)-->RX
程序下载进去之后,串口调试助手上面没有任何数据,将2根线反过来,可用看到数据,说明上面这个接法是错的,应该修正为:
PA0(RX)-->RX
PA1(TX)-->TX
(此接法对应开发板上面的丝印,并非是芯片引脚的标识)
最近编辑记录 LinjieGuo (2020-01-11 18:51:24)
离线
既然串口能够发送数据,现在尝试使用串口来接收数据。串口接收数据使用中断方式,那么就要搞懂这款芯片的中断是怎么管理的。
我们一般写程序需要用到的是IRQ异常,也就是中断请求。从启动文件上看,异常向量表里面有32个IRQ异常向量,命名规则为IRQx_Handler(x=0~31)。
一般中断的流程如下:
外设产生中断-->中断管理器-->CPU
(1)外设产生中断
这款芯片能产生中断的外设有那些呢?
手册上面有个表,上面列举了非常的中断号,不知道怎么用,但是大概能回答本问题。
GPIO、PWM_HALT、IIC0、IIC1、WDT、ADC0、BOD、TIMER_PULSE、DMA、
CACHE、Flash、CAN、CMP、ADC1、HALL、UART0~3、PWM_CH0~3等等...
(2)中断管理器
这款芯片的中断管理器怎么管理外设产生的中断信号呢?
这款芯片的IRQx_Handler(x=0~31)异常向量,默认是不指定中断源的,需要我们配置它的中断信号来源。
①IRQ0~15的中断源可用来源于下表的任意一项。
②IRQ16~31的中断源可用来源于下表的任意1项或者2项(注意:可用配置2项)
怎么配置一个IRQ指向的是什么源呢?
还是看图,中断源的选择应该是由一个多路选择开关来决定的。
对于IRQ0~15,由IRQx_SRC 寄存器的bit[6:0]选择一个中断源:
对于IQR16~31,可由IRQx_SRC 寄存器的bit[4:0]选择中断源1,bit[9:5]选择中断源2,可用配置2个中断源:
(3)CPU处理中断事件
CPU接收到中断信号(异常信号),自动跳转到对应的异常向量处执行代码(异常向量实际上就是一条跳转指令)
最近编辑记录 LinjieGuo (2020-01-11 20:04:39)
离线
知道了中断的原理,那么,怎么开中断,怎么安装中断向量呢?
(1)使能外设中断
不同的外设拥有不同的中断,具体情况看对应外设的寄存器。
(2)编写中断服务函数
IRQ中断一共有32个,分别是IRQ0~IRQ31。以IRQ0为例:
void IRQ0_Handler(void)
{
//此处添加代码(判断中断标志,并处理)
}
(3)连接中断服务函数
①配置IRQ的中断源
②设置其优先级
③使能该中断
这三步操作,官方已经实现了操作库,以串口为例:
IRQ_Connect(IRQ0_15_UART0, IRQ0_IRQ, 1);
这样就能将串口0的中断连接到IRQ0,并且将其优先级设置为1。(优先级可设置为0~3,其中0为最高优先级)
---------------------------------------------------------------------------------------------------------------------------
以串口0为例,我们使能其接收中断,然后在中断中将接收的数据写入缓冲区。
SWM181的串口外设寄存器跟其他的芯片有一点点不一样,但是我们按照常规的方式来操作,便能找到共同点:
(1)使能外设时钟
(2)使能IO口时钟
(3)配置引脚功能
(4)配置串口通讯格式:波特率、数据位、停止位、校验位等...
(5)使能发送功能/接收功能
(6)使能发送中断/接收中断
(7)使能串口外设
--------------------------------------------------------
(1)按照上面这种流程去初始化串口,代码如下:
(2)编写中断服务函数:
void __irqUart0_RX(void)
{
u32 dat;
/*--接收中断或接收超时中断--*/
if(UART_INTRXThresholdStat(UART0) || UART_INTTimeoutStat(UART0)) //串口接收FIFO中断或者接收超时中断
{
while(UART_IsRXFIFOEmpty(UART0) == 0) //FIFO非空则不断提取数据
{
if(UART_ReadByte(UART0, &dat) == 0) //提取一个数据
{
printf("%c \r\n",dat); //将提取到得数据打印出来
}
}
}
}
最近编辑记录 LinjieGuo (2020-01-13 12:02:56)
离线
串口的大概就是这样子了,一个单片机控制系统开发应该需要有一个时间基准,我们现在看看定时器怎么使用。
通过手册可以看出,SWM181内部的定时器情况应该如下:
①1个24位系统滴答定时器
②4个通用32位定时器
③一个32位计数器
-----------------------------------------------------------------
按老套路来走,定时器的使用流程,一般是这样:
(1)设置定时器内部计数器的时钟源
(2)设置计数器的计数方式:自加/自减
(3)设置计数器的装载缓冲器(初值):
(4)使能溢出(自加)/置零(自减)中断
(5)使能定时器运行
-----------------------------------------------------------------
看看官方例程代码是怎么初始化系统滴答定时器的:
官方的库函数配置方法就一行代码:
SysTick_Config(SystemCoreClock/4); //每0.25秒钟触发一次中断
对应的系统滴答定时器中断代码,只需要写成以下格式便可:
void SysTick_Handler(void)
{
//代码
}
-----------------------------------------------------------------
问:上面这行代码为什么这样配置就可以得到"每0.25s触发一次中断"的效果呢?
我们需要了解系统滴答定时器的输入时钟源是什么?系统滴答定时器位于M0架构内部,跟我们这款MCU的外设设计无关,想要知道具体情况,应该翻阅M0架构的相关文档。暂且不做讨论。
我们追踪代码可以得知:
#define __HSI (24000000UL) //高速内部时钟
uint32_t SystemCoreClock = __HSI;
SysTick_Config(SystemCoreClock/4); //每0.25秒钟触发一次中断
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if ((ticks - 1) > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */
SysTick->LOAD = ticks - 1; /* set reload register */
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); /* set Priority for Systick Interrupt */
SysTick->VAL = 0; /* Load the SysTick Counter Value */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */
return (0); /* Function successful */
}
从代码可以看出,ticks就是定时器初值。
而且翻阅手册,得知:SysTick定时器是一个24位自减定时器。
我们只需要将(SystemCoreClock/4)再除以250,应该就可以将系统滴答定时器,配置为1ms触发一次中断。代码如下:
SysTick_Config(SystemCoreClock/4/250); //每1ms钟触发一次中断
中断服务程序里面放一个变量累加:
u32 gSysTick=0; //32位可以描述49天,若非项目要求,推荐使用32位,效率较高
void SysTick_Handler(void)
{
gSysTick++;
}
编写一个函数用于获取系统滴答值。
#define SysTick_t u64
SysTick_t GetSysTick(void)
{
SysTick_t temp=0;
/*关中断*/
SysTick->CTRL &= ~(1<<1);
temp = gSysTick;
/*开中断*/
SysTick->CTRL |= (1<<1);
return temp;
}
定义一下系统滴答的参数结构:
typedef struct{
SysTick_t Diff; //时间差
SysTick_t Old; //旧的滴答值
SysTick_t New; //新的滴答值
}TimeOut_t;
某些平台申请的变量,默认值非0,我们编写一个函数用于初始化这个结构。
/*--初始化软超时结构---------------------*/
void TimeOutArgsInit(TimeOut_t *tTimeOut)
{
tTimeOut->Diff = 0;
tTimeOut->New = 0;
tTimeOut->Old = 0;
}
这种结构,在使用单片机写裸机状态机程序时,非常好用。结构分明,具体后面再说。
-------------------------------------------------------------------------------------
最近编辑记录 LinjieGuo (2020-01-13 15:15:09)
离线
基本框架已经定好了,忽然觉得24MHz是否有点慢了,修改工作频率,只需要修改这行代码即可。
#define SYS_CLK SYS_CLK_48MHz
然后官方的库函数会帮我们进行时钟初始化。
切记,千万不要修改下面三个宏,这三个宏,应该是定死的参数,官方库函数用的,如果变动,可能很多地方需要修改。
/*以下三个参数应该是不需要修改的,作为掩码一样使用,勿动*/
#define __HSI (24000000UL) //高速内部时钟
#define __LSI ( 32000UL) //低速内部时钟
#define __HSE (24000000UL) //高速外部时钟
离线
======================================================
经过了上面的折腾,我们完成了这么多事情:
(1)设置CPU工作时钟
(2)使用GPIO
(3)使用串口
(4)使用中断
(5)使用系统定时器
那么,我们就利用这些外设,编写一个类似命令行一样的工程框架,用于调试项目吧。
======================================================
放出工程:Framework_V1.0.7z
(因代码写得乱,移植可能有些困难,有其他平台需要移植的可以联系我,如果哪位朋友优化好结构,希望也可以分享出来)
如果是使用同样的芯片,可以不关注控制台的实现,直接使用lib工程:
Framework_V1.0_lib.7z
测试:
(1)连接串口线
GPA0配置为RX-->连接官方开发板子上面上面丝印RX
GPA1配置为TX-->连接官方开发板子上面上面丝印TX
(2)连接LED线
GPA5-->LED
(1)打开putty,使用串口打开设备,给板子上电。可以看到如下图:
(2)输入help,回车:
(3)使用LED
(可以使用TAB补全指令,使用上方向键回到上一条指令)
①输入LedGPA5 con:配置LED IO
②输入LedGPA5 on:点亮板子上面的LED
③输入LedGPA5 off:熄灭板子上面的LED
④输入LedGPA5 cat:查看LED的情况
上图:
最近编辑记录 LinjieGuo (2020-01-13 23:07:12)
离线